Kaynağa Gözat

first commit

tremble 5 yıl önce
işleme
2ce2fdbae7
100 değiştirilmiş dosya ile 20652 ekleme ve 0 silme
  1. 3 0
      sh_backstage/.browserslistrc
  2. 17 0
      sh_backstage/.eslintrc.js
  3. 21 0
      sh_backstage/.gitignore
  4. 24 0
      sh_backstage/README.md
  5. 5 0
      sh_backstage/babel.config.js
  6. 11714 0
      sh_backstage/package-lock.json
  7. 31 0
      sh_backstage/package.json
  8. BIN
      sh_backstage/public/favicon.png
  9. 17 0
      sh_backstage/public/index.html
  10. 31 0
      sh_backstage/src/App.vue
  11. 0 0
      sh_backstage/src/assets/css/color.less
  12. 4 0
      sh_backstage/src/assets/css/globalVars.less
  13. 220 0
      sh_backstage/src/assets/css/public.less
  14. 109 0
      sh_backstage/src/assets/css/reset.less
  15. 539 0
      sh_backstage/src/assets/font/demo.css
  16. 607 0
      sh_backstage/src/assets/font/demo_index.html
  17. BIN
      sh_backstage/src/assets/font/download.zip
  18. 93 0
      sh_backstage/src/assets/font/iconfont.css
  19. BIN
      sh_backstage/src/assets/font/iconfont.eot
  20. 1 0
      sh_backstage/src/assets/font/iconfont.js
  21. 142 0
      sh_backstage/src/assets/font/iconfont.json
  22. 83 0
      sh_backstage/src/assets/font/iconfont.svg
  23. BIN
      sh_backstage/src/assets/font/iconfont.ttf
  24. BIN
      sh_backstage/src/assets/font/iconfont.woff
  25. BIN
      sh_backstage/src/assets/font/iconfont.woff2
  26. BIN
      sh_backstage/src/assets/img/01jk.png
  27. BIN
      sh_backstage/src/assets/img/01jks.png
  28. BIN
      sh_backstage/src/assets/img/02fb.png
  29. BIN
      sh_backstage/src/assets/img/03mg.png
  30. BIN
      sh_backstage/src/assets/img/04fb.png
  31. BIN
      sh_backstage/src/assets/img/4dage-logo.png
  32. BIN
      sh_backstage/src/assets/img/bg.jpg
  33. BIN
      sh_backstage/src/assets/img/biyan.png
  34. BIN
      sh_backstage/src/assets/img/logout.png
  35. BIN
      sh_backstage/src/assets/img/noPicture.png
  36. BIN
      sh_backstage/src/assets/img/user.png
  37. BIN
      sh_backstage/src/assets/img/wenli.png
  38. BIN
      sh_backstage/src/assets/img/zhoushan-logo.jpg
  39. BIN
      sh_backstage/src/assets/logo.png
  40. 58 0
      sh_backstage/src/components/crumbs/index.vue
  41. 64 0
      sh_backstage/src/components/main-top/index.vue
  42. 60 0
      sh_backstage/src/configue/base.js
  43. 3 0
      sh_backstage/src/configue/bus.js
  44. 112 0
      sh_backstage/src/configue/http.js
  45. 108 0
      sh_backstage/src/main.js
  46. 260 0
      sh_backstage/src/pages/content/Architecture.vue
  47. 461 0
      sh_backstage/src/pages/content/Comment.vue
  48. 381 0
      sh_backstage/src/pages/content/Parts.vue
  49. 380 0
      sh_backstage/src/pages/content/Roam.vue
  50. 370 0
      sh_backstage/src/pages/content/Structure.vue
  51. 340 0
      sh_backstage/src/pages/edit/architecture.vue
  52. 408 0
      sh_backstage/src/pages/edit/parts.vue
  53. 335 0
      sh_backstage/src/pages/edit/roam.vue
  54. 235 0
      sh_backstage/src/pages/edit/structure.vue
  55. 36 0
      sh_backstage/src/pages/home/index.vue
  56. 143 0
      sh_backstage/src/pages/layout/aside.vue
  57. 47 0
      sh_backstage/src/pages/layout/footer.vue
  58. 140 0
      sh_backstage/src/pages/layout/head.vue
  59. 56 0
      sh_backstage/src/pages/layout/index.vue
  60. 219 0
      sh_backstage/src/pages/login/index.vue
  61. 3 0
      sh_backstage/src/pages/login/style.css
  62. 363 0
      sh_backstage/src/pages/show/parts.vue
  63. 245 0
      sh_backstage/src/pages/show/roam.vue
  64. 236 0
      sh_backstage/src/pages/show/structure.vue
  65. 484 0
      sh_backstage/src/pages/system/Download.vue
  66. 276 0
      sh_backstage/src/pages/system/Password.vue
  67. 624 0
      sh_backstage/src/pages/system/User.vue
  68. 264 0
      sh_backstage/src/pages/system/Worklog.vue
  69. 134 0
      sh_backstage/src/router/index.js
  70. 122 0
      sh_backstage/src/util/index.js
  71. 1 0
      sh_backstage/theme/alert.css
  72. 1 0
      sh_backstage/theme/aside.css
  73. 1 0
      sh_backstage/theme/autocomplete.css
  74. 1 0
      sh_backstage/theme/avatar.css
  75. 1 0
      sh_backstage/theme/backtop.css
  76. 1 0
      sh_backstage/theme/badge.css
  77. 1 0
      sh_backstage/theme/base.css
  78. 0 0
      sh_backstage/theme/breadcrumb-item.css
  79. 1 0
      sh_backstage/theme/breadcrumb.css
  80. 0 0
      sh_backstage/theme/button-group.css
  81. 1 0
      sh_backstage/theme/button.css
  82. 1 0
      sh_backstage/theme/calendar.css
  83. 1 0
      sh_backstage/theme/card.css
  84. 1 0
      sh_backstage/theme/carousel-item.css
  85. 1 0
      sh_backstage/theme/carousel.css
  86. 1 0
      sh_backstage/theme/cascader-panel.css
  87. 1 0
      sh_backstage/theme/cascader.css
  88. 0 0
      sh_backstage/theme/checkbox-button.css
  89. 0 0
      sh_backstage/theme/checkbox-group.css
  90. 1 0
      sh_backstage/theme/checkbox.css
  91. 1 0
      sh_backstage/theme/col.css
  92. 0 0
      sh_backstage/theme/collapse-item.css
  93. 1 0
      sh_backstage/theme/collapse.css
  94. 1 0
      sh_backstage/theme/color-picker.css
  95. 1 0
      sh_backstage/theme/container.css
  96. 1 0
      sh_backstage/theme/date-picker.css
  97. 1 0
      sh_backstage/theme/dialog.css
  98. 1 0
      sh_backstage/theme/display.css
  99. 1 0
      sh_backstage/theme/divider.css
  100. 0 0
      sh_backstage/theme/drawer.css

+ 3 - 0
sh_backstage/.browserslistrc

@@ -0,0 +1,3 @@
+> 1%
+last 2 versions
+not dead

+ 17 - 0
sh_backstage/.eslintrc.js

@@ -0,0 +1,17 @@
+module.exports = {
+  root: true,
+  env: {
+    node: true
+  },
+  'extends': [
+    'plugin:vue/essential',
+    'eslint:recommended'
+  ],
+  parserOptions: {
+    parser: 'babel-eslint'
+  },
+  rules: {
+    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
+  }
+}

+ 21 - 0
sh_backstage/.gitignore

@@ -0,0 +1,21 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 24 - 0
sh_backstage/README.md

@@ -0,0 +1,24 @@
+# sh_backstage
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Lints and fixes files
+```
+npm run lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 5 - 0
sh_backstage/babel.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset'
+  ]
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 11714 - 0
sh_backstage/package-lock.json


+ 31 - 0
sh_backstage/package.json

@@ -0,0 +1,31 @@
+{
+  "name": "sh_backstage",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "axios": "^0.19.2",
+    "core-js": "^3.6.4",
+    "element-ui": "^2.13.2",
+    "js-base64": "^2.6.2",
+    "vue": "^2.6.11",
+    "vue-router": "^3.1.6",
+    "vue2-editor": "^2.10.2"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.3.0",
+    "@vue/cli-plugin-eslint": "~4.3.0",
+    "@vue/cli-plugin-router": "~4.3.0",
+    "@vue/cli-service": "~4.3.0",
+    "babel-eslint": "^10.1.0",
+    "eslint": "^6.7.2",
+    "eslint-plugin-vue": "^6.2.2",
+    "less": "^3.0.4",
+    "less-loader": "^5.0.0",
+    "vue-template-compiler": "^2.6.11"
+  }
+}

BIN
sh_backstage/public/favicon.png


+ 17 - 0
sh_backstage/public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <link rel="icon" href="<%= BASE_URL %>favicon.png">
+    <title>上海市历史博物馆三维数据库管理系统</title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 31 - 0
sh_backstage/src/App.vue

@@ -0,0 +1,31 @@
+<template>
+  <div id="app">
+    <router-view/>
+  </div>
+</template>
+
+<script>
+import '@/assets/css/reset.less'
+import '@/assets/css/public.less'
+import '@/assets/font/iconfont.css'
+import Vue from 'vue'
+
+
+export default {
+  name: 'App',
+  mounted(){
+    if (window.localStorage.getItem('userInfo')) {
+      Vue.prototype.$role = JSON.parse(window.localStorage.getItem('userInfo')).role
+    }
+  }
+}
+
+</script>
+
+<style>
+  #app{
+    width: 100%;
+    height: 100%;
+    background-color: #f0f0f2;
+  }
+</style>

+ 0 - 0
sh_backstage/src/assets/css/color.less


+ 4 - 0
sh_backstage/src/assets/css/globalVars.less

@@ -0,0 +1,4 @@
+@cdn:'https://4d-tjw.oss-cn-shenzhen.aliyuncs.com/shls_museum/images/';
+@inputH:30px;
+@theme:#9D362F;
+@sub-theme:#C58C63;

+ 220 - 0
sh_backstage/src/assets/css/public.less

@@ -0,0 +1,220 @@
+.layout{
+  width: 100%;
+  height: 100%;
+}
+
+.layout-con{
+  width: 100%;
+  height: 100%;
+  position: fixed;
+}
+
+.middle-title, .register-title,.forget-title{
+  color: #4d4d4d;
+  font-size: 1.75rem;
+  text-align: center;
+  margin-bottom: 3.4375rem;
+  line-height: 1.5;
+}
+
+.register-title,.forget-title{
+  margin-top: 1.25rem;
+}
+
+.middle{
+  position: absolute;
+  top: 30vh;
+  left: 50vw;
+  width: 23.75rem;
+  height: 18.75rem;
+  transform: translate(-50%,-50%);
+}
+.middle-subtitle{
+  font-size: .875rem;
+  font-weight: 500;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.middle-subtitle::before,.middle-subtitle::after{
+  content: '';
+  height: 0rem;
+  width: 1.25rem;
+  border: .03125rem solid #000; 
+  display: inline-block;
+  margin: 0 .625rem;
+}
+
+.form-bottom{
+  display: flex;
+  justify-content: space-between;
+  padding: 0 .625rem;
+  color: #888;
+}
+
+.form-bottom span{
+  cursor: pointer;
+}
+
+.bottom-div{
+  text-align: center;
+  cursor: pointer;
+  color: #888888;
+}
+
+.test-btn{
+  color: #67c23a!important;
+}
+
+.view-btn{
+  color: #409eff!important;
+}
+.re-apply-btn{
+  color: #f56c6c!important;
+}
+.apply-btn{
+  color: #ec652d!important;
+}
+
+.o-span{
+  cursor: pointer;
+  color: rgb(7, 152, 244);
+}
+
+.o-delete{
+    cursor: pointer;
+    color: #707070;
+    margin-left: 5px;
+    &:hover{
+      color: #409EFF;
+    }
+}
+
+
+.e-pagination{
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 1.5625rem;
+}
+
+:root{
+  --bg_color:rgba(255, 255, 255, 1);
+  --font_color:rgba(0, 0, 0, 0.88);
+  --font_color1:rgba(0, 0, 0, 0.38);
+}
+
+.theme{
+  background: #f2ecde!important;
+}
+
+.theme-color{
+  color: var(--font_color)!important;
+}
+
+.theme-color1{
+  color: var(--font_color1)!important;
+}
+
+.card,.top-body {
+  background: var(--bg_color)!important;
+  border-radius: 2px!important;
+  color: #000!important;
+  border: none!important;
+}
+.top-body {
+  margin: 0 auto 1rem!important;
+}
+
+.info-right{
+  padding-right: 15px;
+}
+
+.el-icon-upload{
+  margin: 20px auto!important;
+}
+
+.el-upload__text p{
+  line-height:1.2!important;
+  font-size:12px!important;
+  margin-bottom: 10px!important;
+  color: #ccc!important;
+}
+
+
+.li-img{
+  max-height: 237px;
+  overflow: hidden;
+  width: 100%;
+  position: relative;
+  img{
+  }
+  .eye{
+    z-index: 9999;
+    position: absolute;
+    right: 10px;
+    top: 10px;
+    width: 20px!important;
+  }
+}
+.collection-con .nodata{
+  width: 100%;
+  padding-top: 10%;
+  text-align: center;
+}
+
+@media screen and (max-width: 1700px) {
+  html,body{
+    font-size: 15px;
+  }
+}
+
+.con-btn{
+  margin-right: 20px;
+}
+
+.o-span{
+  padding-left: 10px;
+}
+.el-tabs__item{
+  font-size: 16px!important;
+  font-weight: bold!important;
+}
+
+.elInput{
+  width:200px!important;
+  margin:0 20px!important;
+}
+.elSelect{
+  width:150px!important;
+}
+
+@media screen and (max-width: 1400px) {
+  html,body{
+    font-size: 13px;
+  }
+  .collection-con{
+    height: 58vh!important;
+  }
+  .elInput{
+    width:150px!important;
+    margin:0 20px!important;
+  }
+  .elSelect{
+    width:100px!important;
+  }
+}
+
+
+@media screen and (max-width: 1300px) {
+  html,body{
+    font-size: 12px;
+  }
+  .collection-con{
+    height: 61vh!important;
+  }
+  .elInput{
+    width:130px!important;
+    margin:0 10px!important;
+  }
+}

+ 109 - 0
sh_backstage/src/assets/css/reset.less

@@ -0,0 +1,109 @@
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed, 
+figure, figcaption, footer, header, hgroup, 
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+  font-family: "Microsoft YaHei","Microsoft JhengHei";;
+	margin: 0;
+	padding: 0;
+	border: 0;
+	font-size: 100%;
+	vertical-align: baseline;
+}
+*{
+  box-sizing: border-box;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure, 
+footer, header, hgroup, menu, nav, section, main {
+	display: block;
+}
+html{
+	height: 100%;
+}
+body {
+	line-height: 1;
+	height: 100%;
+}
+html,body{
+	font-size: 16px;
+}
+ol, ul {
+	list-style: none;
+}
+blockquote, q {
+	quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+	content: '';
+	content: none;
+}
+table {
+	border-collapse: collapse;
+	border-spacing: 0;
+}
+button{outline:none; border: none;}
+input, button, select, textarea {
+outline: none;
+appearance: none;
+-webkit-appearance: none;
+border-radius: 0;
+-webkit-appearance: textfield;
+    background-color: white;
+    -webkit-rtl-ordering: logical;
+    cursor: text;
+    padding: 1px;
+    border-color: initial;
+    border-image: initial;
+}
+input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{
+        -webkit-appearance:textfield;
+}
+input[type="number"]{
+        -moz-appearance:textfield;
+}
+input:focus { outline: none; } 
+select::-ms-expand {
+display: none;
+}
+
+
+::-webkit-scrollbar-track-piece {  
+	background-color:#ffffff;  
+}  
+::-webkit-scrollbar {  
+	width:8px;  
+	height:13px;  
+}  
+::-webkit-scrollbar-thumb {  
+	background-color:#e5e5e5;  
+	background-clip:padding-box;  
+	min-height:20px;  
+	border-radius: 3px;
+}  
+::-webkit-scrollbar-thumb:hover {  
+	background-color:#929292;  
+}  
+
+table th,
+table td {
+  text-align: center !important;
+}
+
+table th > .cell{
+	color: #000;
+}
+
+textarea{
+  height: 8rem!important;
+}

+ 539 - 0
sh_backstage/src/assets/font/demo.css

@@ -0,0 +1,539 @@
+/* Logo 字体 */
+@font-face {
+  font-family: "iconfont logo";
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
+}
+
+.logo {
+  font-family: "iconfont logo";
+  font-size: 160px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+/* tabs */
+.nav-tabs {
+  position: relative;
+}
+
+.nav-tabs .nav-more {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  height: 42px;
+  line-height: 42px;
+  color: #666;
+}
+
+#tabs {
+  border-bottom: 1px solid #eee;
+}
+
+#tabs li {
+  cursor: pointer;
+  width: 100px;
+  height: 40px;
+  line-height: 40px;
+  text-align: center;
+  font-size: 16px;
+  border-bottom: 2px solid transparent;
+  position: relative;
+  z-index: 1;
+  margin-bottom: -1px;
+  color: #666;
+}
+
+
+#tabs .active {
+  border-bottom-color: #f00;
+  color: #222;
+}
+
+.tab-container .content {
+  display: none;
+}
+
+/* 页面布局 */
+.main {
+  padding: 30px 100px;
+  width: 960px;
+  margin: 0 auto;
+}
+
+.main .logo {
+  color: #333;
+  text-align: left;
+  margin-bottom: 30px;
+  line-height: 1;
+  height: 110px;
+  margin-top: -50px;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.main .logo a {
+  font-size: 160px;
+  color: #333;
+}
+
+.helps {
+  margin-top: 40px;
+}
+
+.helps pre {
+  padding: 20px;
+  margin: 10px 0;
+  border: solid 1px #e7e1cd;
+  background-color: #fffdef;
+  overflow: auto;
+}
+
+.icon_lists {
+  width: 100% !important;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.icon_lists li {
+  width: 100px;
+  margin-bottom: 10px;
+  margin-right: 20px;
+  text-align: center;
+  list-style: none !important;
+  cursor: default;
+}
+
+.icon_lists li .code-name {
+  line-height: 1.2;
+}
+
+.icon_lists .icon {
+  display: block;
+  height: 100px;
+  line-height: 100px;
+  font-size: 42px;
+  margin: 10px auto;
+  color: #333;
+  -webkit-transition: font-size 0.25s linear, width 0.25s linear;
+  -moz-transition: font-size 0.25s linear, width 0.25s linear;
+  transition: font-size 0.25s linear, width 0.25s linear;
+}
+
+.icon_lists .icon:hover {
+  font-size: 100px;
+}
+
+.icon_lists .svg-icon {
+  /* 通过设置 font-size 来改变图标大小 */
+  width: 1em;
+  /* 图标和文字相邻时,垂直对齐 */
+  vertical-align: -0.15em;
+  /* 通过设置 color 来改变 SVG 的颜色/fill */
+  fill: currentColor;
+  /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
+      normalize.css 中也包含这行 */
+  overflow: hidden;
+}
+
+.icon_lists li .name,
+.icon_lists li .code-name {
+  color: #666;
+}
+
+/* markdown 样式 */
+.markdown {
+  color: #666;
+  font-size: 14px;
+  line-height: 1.8;
+}
+
+.highlight {
+  line-height: 1.5;
+}
+
+.markdown img {
+  vertical-align: middle;
+  max-width: 100%;
+}
+
+.markdown h1 {
+  color: #404040;
+  font-weight: 500;
+  line-height: 40px;
+  margin-bottom: 24px;
+}
+
+.markdown h2,
+.markdown h3,
+.markdown h4,
+.markdown h5,
+.markdown h6 {
+  color: #404040;
+  margin: 1.6em 0 0.6em 0;
+  font-weight: 500;
+  clear: both;
+}
+
+.markdown h1 {
+  font-size: 28px;
+}
+
+.markdown h2 {
+  font-size: 22px;
+}
+
+.markdown h3 {
+  font-size: 16px;
+}
+
+.markdown h4 {
+  font-size: 14px;
+}
+
+.markdown h5 {
+  font-size: 12px;
+}
+
+.markdown h6 {
+  font-size: 12px;
+}
+
+.markdown hr {
+  height: 1px;
+  border: 0;
+  background: #e9e9e9;
+  margin: 16px 0;
+  clear: both;
+}
+
+.markdown p {
+  margin: 1em 0;
+}
+
+.markdown>p,
+.markdown>blockquote,
+.markdown>.highlight,
+.markdown>ol,
+.markdown>ul {
+  width: 80%;
+}
+
+.markdown ul>li {
+  list-style: circle;
+}
+
+.markdown>ul li,
+.markdown blockquote ul>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown>ul li p,
+.markdown>ol li p {
+  margin: 0.6em 0;
+}
+
+.markdown ol>li {
+  list-style: decimal;
+}
+
+.markdown>ol li,
+.markdown blockquote ol>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown code {
+  margin: 0 3px;
+  padding: 0 5px;
+  background: #eee;
+  border-radius: 3px;
+}
+
+.markdown strong,
+.markdown b {
+  font-weight: 600;
+}
+
+.markdown>table {
+  border-collapse: collapse;
+  border-spacing: 0px;
+  empty-cells: show;
+  border: 1px solid #e9e9e9;
+  width: 95%;
+  margin-bottom: 24px;
+}
+
+.markdown>table th {
+  white-space: nowrap;
+  color: #333;
+  font-weight: 600;
+}
+
+.markdown>table th,
+.markdown>table td {
+  border: 1px solid #e9e9e9;
+  padding: 8px 16px;
+  text-align: left;
+}
+
+.markdown>table th {
+  background: #F7F7F7;
+}
+
+.markdown blockquote {
+  font-size: 90%;
+  color: #999;
+  border-left: 4px solid #e9e9e9;
+  padding-left: 0.8em;
+  margin: 1em 0;
+}
+
+.markdown blockquote p {
+  margin: 0;
+}
+
+.markdown .anchor {
+  opacity: 0;
+  transition: opacity 0.3s ease;
+  margin-left: 8px;
+}
+
+.markdown .waiting {
+  color: #ccc;
+}
+
+.markdown h1:hover .anchor,
+.markdown h2:hover .anchor,
+.markdown h3:hover .anchor,
+.markdown h4:hover .anchor,
+.markdown h5:hover .anchor,
+.markdown h6:hover .anchor {
+  opacity: 1;
+  display: inline-block;
+}
+
+.markdown>br,
+.markdown>p>br {
+  clear: both;
+}
+
+
+.hljs {
+  display: block;
+  background: white;
+  padding: 0.5em;
+  color: #333333;
+  overflow-x: auto;
+}
+
+.hljs-comment,
+.hljs-meta {
+  color: #969896;
+}
+
+.hljs-string,
+.hljs-variable,
+.hljs-template-variable,
+.hljs-strong,
+.hljs-emphasis,
+.hljs-quote {
+  color: #df5000;
+}
+
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-type {
+  color: #a71d5d;
+}
+
+.hljs-literal,
+.hljs-symbol,
+.hljs-bullet,
+.hljs-attribute {
+  color: #0086b3;
+}
+
+.hljs-section,
+.hljs-name {
+  color: #63a35c;
+}
+
+.hljs-tag {
+  color: #333333;
+}
+
+.hljs-title,
+.hljs-attr,
+.hljs-selector-id,
+.hljs-selector-class,
+.hljs-selector-attr,
+.hljs-selector-pseudo {
+  color: #795da3;
+}
+
+.hljs-addition {
+  color: #55a532;
+  background-color: #eaffea;
+}
+
+.hljs-deletion {
+  color: #bd2c00;
+  background-color: #ffecec;
+}
+
+.hljs-link {
+  text-decoration: underline;
+}
+
+/* 代码高亮 */
+/* PrismJS 1.15.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (http://dabblet.com)
+ * @author Lea Verou
+ */
+code[class*="language-"],
+pre[class*="language-"] {
+  color: black;
+  background: none;
+  text-shadow: 0 1px white;
+  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+  text-align: left;
+  white-space: pre;
+  word-spacing: normal;
+  word-break: normal;
+  word-wrap: normal;
+  line-height: 1.5;
+
+  -moz-tab-size: 4;
+  -o-tab-size: 4;
+  tab-size: 4;
+
+  -webkit-hyphens: none;
+  -moz-hyphens: none;
+  -ms-hyphens: none;
+  hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection,
+pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection,
+code[class*="language-"] ::-moz-selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection,
+pre[class*="language-"] ::selection,
+code[class*="language-"]::selection,
+code[class*="language-"] ::selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+@media print {
+
+  code[class*="language-"],
+  pre[class*="language-"] {
+    text-shadow: none;
+  }
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+  padding: 1em;
+  margin: .5em 0;
+  overflow: auto;
+}
+
+:not(pre)>code[class*="language-"],
+pre[class*="language-"] {
+  background: #f5f2f0;
+}
+
+/* Inline code */
+:not(pre)>code[class*="language-"] {
+  padding: .1em;
+  border-radius: .3em;
+  white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+  color: slategray;
+}
+
+.token.punctuation {
+  color: #999;
+}
+
+.namespace {
+  opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+  color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+  color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+  color: #9a6e3a;
+  background: hsla(0, 0%, 100%, .5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+  color: #07a;
+}
+
+.token.function,
+.token.class-name {
+  color: #DD4A68;
+}
+
+.token.regex,
+.token.important,
+.token.variable {
+  color: #e90;
+}
+
+.token.important,
+.token.bold {
+  font-weight: bold;
+}
+
+.token.italic {
+  font-style: italic;
+}
+
+.token.entity {
+  cursor: help;
+}

+ 607 - 0
sh_backstage/src/assets/font/demo_index.html

@@ -0,0 +1,607 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8"/>
+  <title>IconFont Demo</title>
+  <link rel="shortcut icon" href="https://img.alicdn.com/tps/i4/TB1_oz6GVXXXXaFXpXXJDFnIXXX-64-64.ico" type="image/x-icon"/>
+  <link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
+  <link rel="stylesheet" href="demo.css">
+  <link rel="stylesheet" href="iconfont.css">
+  <script src="iconfont.js"></script>
+  <!-- jQuery -->
+  <script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
+  <!-- 代码高亮 -->
+  <script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
+</head>
+<body>
+  <div class="main">
+    <h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">&#xe86b;</a></h1>
+    <div class="nav-tabs">
+      <ul id="tabs" class="dib-box">
+        <li class="dib active"><span>Unicode</span></li>
+        <li class="dib"><span>Font class</span></li>
+        <li class="dib"><span>Symbol</span></li>
+      </ul>
+      
+      <a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=1855608" target="_blank" class="nav-more">查看项目</a>
+      
+    </div>
+    <div class="tab-container">
+      <div class="content unicode" style="display: block;">
+          <ul class="icon_lists dib-box">
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6f1;</span>
+                <div class="name">previous</div>
+                <div class="code-name">&amp;#xe6f1;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6f2;</span>
+                <div class="name">Page_ele</div>
+                <div class="code-name">&amp;#xe6f2;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6f3;</span>
+                <div class="name">next</div>
+                <div class="code-name">&amp;#xe6f3;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6f4;</span>
+                <div class="name">sys_nav_index</div>
+                <div class="code-name">&amp;#xe6f4;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6f5;</span>
+                <div class="name">search</div>
+                <div class="code-name">&amp;#xe6f5;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6f6;</span>
+                <div class="name">sys_edit</div>
+                <div class="code-name">&amp;#xe6f6;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6f7;</span>
+                <div class="name">Page</div>
+                <div class="code-name">&amp;#xe6f7;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6f8;</span>
+                <div class="name">sys_down</div>
+                <div class="code-name">&amp;#xe6f8;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6f9;</span>
+                <div class="name">sys_nav_work</div>
+                <div class="code-name">&amp;#xe6f9;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6fa;</span>
+                <div class="name">sys_nav_system</div>
+                <div class="code-name">&amp;#xe6fa;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6fb;</span>
+                <div class="name">tit_ele</div>
+                <div class="code-name">&amp;#xe6fb;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6ef;</span>
+                <div class="name">mode</div>
+                <div class="code-name">&amp;#xe6ef;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6f0;</span>
+                <div class="name">nav_guestbook</div>
+                <div class="code-name">&amp;#xe6f0;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6e9;</span>
+                <div class="name">guestbook_btnbg_first</div>
+                <div class="code-name">&amp;#xe6e9;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6ea;</span>
+                <div class="name">btnbg_normal</div>
+                <div class="code-name">&amp;#xe6ea;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6eb;</span>
+                <div class="name">guestbook_success</div>
+                <div class="code-name">&amp;#xe6eb;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6ec;</span>
+                <div class="name">guestbook_close</div>
+                <div class="code-name">&amp;#xe6ec;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6ed;</span>
+                <div class="name">logo_tit</div>
+                <div class="code-name">&amp;#xe6ed;</div>
+              </li>
+          
+            <li class="dib">
+              <span class="icon iconfont">&#xe6ee;</span>
+                <div class="name">logo_normal</div>
+                <div class="code-name">&amp;#xe6ee;</div>
+              </li>
+          
+          </ul>
+          <div class="article markdown">
+          <h2 id="unicode-">Unicode 引用</h2>
+          <hr>
+
+          <p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
+          <ul>
+            <li>兼容性最好,支持 IE6+,及所有现代浏览器。</li>
+            <li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
+            <li>但是因为是字体,所以不支持多色。只能使用平台里单色的图标,就算项目里有多色图标也会自动去色。</li>
+          </ul>
+          <blockquote>
+            <p>注意:新版 iconfont 支持多色图标,这些多色图标在 Unicode 模式下将不能使用,如果有需求建议使用symbol 的引用方式</p>
+          </blockquote>
+          <p>Unicode 使用步骤如下:</p>
+          <h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
+<pre><code class="language-css"
+>@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.eot');
+  src: url('iconfont.eot?#iefix') format('embedded-opentype'),
+      url('iconfont.woff2') format('woff2'),
+      url('iconfont.woff') format('woff'),
+      url('iconfont.ttf') format('truetype'),
+      url('iconfont.svg#iconfont') format('svg');
+}
+</code></pre>
+          <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
+<pre><code class="language-css"
+>.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+</code></pre>
+          <h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
+<pre>
+<code class="language-html"
+>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
+</code></pre>
+          <blockquote>
+            <p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
+          </blockquote>
+          </div>
+      </div>
+      <div class="content font-class">
+        <ul class="icon_lists dib-box">
+          
+          <li class="dib">
+            <span class="icon iconfont iconprevious"></span>
+            <div class="name">
+              previous
+            </div>
+            <div class="code-name">.iconprevious
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconPage_ele"></span>
+            <div class="name">
+              Page_ele
+            </div>
+            <div class="code-name">.iconPage_ele
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconnext"></span>
+            <div class="name">
+              next
+            </div>
+            <div class="code-name">.iconnext
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconsys_nav_index"></span>
+            <div class="name">
+              sys_nav_index
+            </div>
+            <div class="code-name">.iconsys_nav_index
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconsearch"></span>
+            <div class="name">
+              search
+            </div>
+            <div class="code-name">.iconsearch
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconsys_edit"></span>
+            <div class="name">
+              sys_edit
+            </div>
+            <div class="code-name">.iconsys_edit
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconPage"></span>
+            <div class="name">
+              Page
+            </div>
+            <div class="code-name">.iconPage
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconsys_down"></span>
+            <div class="name">
+              sys_down
+            </div>
+            <div class="code-name">.iconsys_down
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconsys_nav_work"></span>
+            <div class="name">
+              sys_nav_work
+            </div>
+            <div class="code-name">.iconsys_nav_work
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconsys_nav_system"></span>
+            <div class="name">
+              sys_nav_system
+            </div>
+            <div class="code-name">.iconsys_nav_system
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont icontit_ele"></span>
+            <div class="name">
+              tit_ele
+            </div>
+            <div class="code-name">.icontit_ele
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconmode"></span>
+            <div class="name">
+              mode
+            </div>
+            <div class="code-name">.iconmode
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconnav_guestbook"></span>
+            <div class="name">
+              nav_guestbook
+            </div>
+            <div class="code-name">.iconnav_guestbook
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconguestbook_btnbg_first"></span>
+            <div class="name">
+              guestbook_btnbg_first
+            </div>
+            <div class="code-name">.iconguestbook_btnbg_first
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconbtnbg_normal"></span>
+            <div class="name">
+              btnbg_normal
+            </div>
+            <div class="code-name">.iconbtnbg_normal
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconguestbook_success"></span>
+            <div class="name">
+              guestbook_success
+            </div>
+            <div class="code-name">.iconguestbook_success
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconguestbook_close"></span>
+            <div class="name">
+              guestbook_close
+            </div>
+            <div class="code-name">.iconguestbook_close
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconlogo_tit"></span>
+            <div class="name">
+              logo_tit
+            </div>
+            <div class="code-name">.iconlogo_tit
+            </div>
+          </li>
+          
+          <li class="dib">
+            <span class="icon iconfont iconlogo_normal"></span>
+            <div class="name">
+              logo_normal
+            </div>
+            <div class="code-name">.iconlogo_normal
+            </div>
+          </li>
+          
+        </ul>
+        <div class="article markdown">
+        <h2 id="font-class-">font-class 引用</h2>
+        <hr>
+
+        <p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
+        <p>与 Unicode 使用方式相比,具有如下特点:</p>
+        <ul>
+          <li>兼容性良好,支持 IE8+,及所有现代浏览器。</li>
+          <li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
+          <li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
+          <li>不过因为本质上还是使用的字体,所以多色图标还是不支持的。</li>
+        </ul>
+        <p>使用步骤如下:</p>
+        <h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
+<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
+</code></pre>
+        <h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
+<pre><code class="language-html">&lt;span class="iconfont iconxxx"&gt;&lt;/span&gt;
+</code></pre>
+        <blockquote>
+          <p>"
+            iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
+        </blockquote>
+      </div>
+      </div>
+      <div class="content symbol">
+          <ul class="icon_lists dib-box">
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconprevious"></use>
+                </svg>
+                <div class="name">previous</div>
+                <div class="code-name">#iconprevious</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconPage_ele"></use>
+                </svg>
+                <div class="name">Page_ele</div>
+                <div class="code-name">#iconPage_ele</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconnext"></use>
+                </svg>
+                <div class="name">next</div>
+                <div class="code-name">#iconnext</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconsys_nav_index"></use>
+                </svg>
+                <div class="name">sys_nav_index</div>
+                <div class="code-name">#iconsys_nav_index</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconsearch"></use>
+                </svg>
+                <div class="name">search</div>
+                <div class="code-name">#iconsearch</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconsys_edit"></use>
+                </svg>
+                <div class="name">sys_edit</div>
+                <div class="code-name">#iconsys_edit</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconPage"></use>
+                </svg>
+                <div class="name">Page</div>
+                <div class="code-name">#iconPage</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconsys_down"></use>
+                </svg>
+                <div class="name">sys_down</div>
+                <div class="code-name">#iconsys_down</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconsys_nav_work"></use>
+                </svg>
+                <div class="name">sys_nav_work</div>
+                <div class="code-name">#iconsys_nav_work</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconsys_nav_system"></use>
+                </svg>
+                <div class="name">sys_nav_system</div>
+                <div class="code-name">#iconsys_nav_system</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#icontit_ele"></use>
+                </svg>
+                <div class="name">tit_ele</div>
+                <div class="code-name">#icontit_ele</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconmode"></use>
+                </svg>
+                <div class="name">mode</div>
+                <div class="code-name">#iconmode</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconnav_guestbook"></use>
+                </svg>
+                <div class="name">nav_guestbook</div>
+                <div class="code-name">#iconnav_guestbook</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconguestbook_btnbg_first"></use>
+                </svg>
+                <div class="name">guestbook_btnbg_first</div>
+                <div class="code-name">#iconguestbook_btnbg_first</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconbtnbg_normal"></use>
+                </svg>
+                <div class="name">btnbg_normal</div>
+                <div class="code-name">#iconbtnbg_normal</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconguestbook_success"></use>
+                </svg>
+                <div class="name">guestbook_success</div>
+                <div class="code-name">#iconguestbook_success</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconguestbook_close"></use>
+                </svg>
+                <div class="name">guestbook_close</div>
+                <div class="code-name">#iconguestbook_close</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconlogo_tit"></use>
+                </svg>
+                <div class="name">logo_tit</div>
+                <div class="code-name">#iconlogo_tit</div>
+            </li>
+          
+            <li class="dib">
+                <svg class="icon svg-icon" aria-hidden="true">
+                  <use xlink:href="#iconlogo_normal"></use>
+                </svg>
+                <div class="name">logo_normal</div>
+                <div class="code-name">#iconlogo_normal</div>
+            </li>
+          
+          </ul>
+          <div class="article markdown">
+          <h2 id="symbol-">Symbol 引用</h2>
+          <hr>
+
+          <p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
+            这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
+          <ul>
+            <li>支持多色图标了,不再受单色限制。</li>
+            <li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
+            <li>兼容性较差,支持 IE9+,及现代浏览器。</li>
+            <li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
+          </ul>
+          <p>使用步骤如下:</p>
+          <h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
+<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
+</code></pre>
+          <h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
+<pre><code class="language-html">&lt;style&gt;
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+&lt;/style&gt;
+</code></pre>
+          <h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
+<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
+  &lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
+&lt;/svg&gt;
+</code></pre>
+          </div>
+      </div>
+
+    </div>
+  </div>
+  <script>
+  $(document).ready(function () {
+      $('.tab-container .content:first').show()
+
+      $('#tabs li').click(function (e) {
+        var tabContent = $('.tab-container .content')
+        var index = $(this).index()
+
+        if ($(this).hasClass('active')) {
+          return
+        } else {
+          $('#tabs li').removeClass('active')
+          $(this).addClass('active')
+
+          tabContent.hide().eq(index).fadeIn()
+        }
+      })
+    })
+  </script>
+</body>
+</html>

BIN
sh_backstage/src/assets/font/download.zip


Dosya farkı çok büyük olduğundan ihmal edildi
+ 93 - 0
sh_backstage/src/assets/font/iconfont.css


BIN
sh_backstage/src/assets/font/iconfont.eot


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/src/assets/font/iconfont.js


+ 142 - 0
sh_backstage/src/assets/font/iconfont.json

@@ -0,0 +1,142 @@
+{
+  "id": "1855608",
+  "name": "上海历史博物馆",
+  "font_family": "iconfont",
+  "css_prefix_text": "icon",
+  "description": "展示界面和管理后台",
+  "glyphs": [
+    {
+      "icon_id": "15079056",
+      "name": "previous",
+      "font_class": "previous",
+      "unicode": "e6f1",
+      "unicode_decimal": 59121
+    },
+    {
+      "icon_id": "15079057",
+      "name": "Page_ele",
+      "font_class": "Page_ele",
+      "unicode": "e6f2",
+      "unicode_decimal": 59122
+    },
+    {
+      "icon_id": "15079058",
+      "name": "next",
+      "font_class": "next",
+      "unicode": "e6f3",
+      "unicode_decimal": 59123
+    },
+    {
+      "icon_id": "15079059",
+      "name": "sys_nav_index",
+      "font_class": "sys_nav_index",
+      "unicode": "e6f4",
+      "unicode_decimal": 59124
+    },
+    {
+      "icon_id": "15079060",
+      "name": "search",
+      "font_class": "search",
+      "unicode": "e6f5",
+      "unicode_decimal": 59125
+    },
+    {
+      "icon_id": "15079061",
+      "name": "sys_edit",
+      "font_class": "sys_edit",
+      "unicode": "e6f6",
+      "unicode_decimal": 59126
+    },
+    {
+      "icon_id": "15079062",
+      "name": "Page",
+      "font_class": "Page",
+      "unicode": "e6f7",
+      "unicode_decimal": 59127
+    },
+    {
+      "icon_id": "15079063",
+      "name": "sys_down",
+      "font_class": "sys_down",
+      "unicode": "e6f8",
+      "unicode_decimal": 59128
+    },
+    {
+      "icon_id": "15079064",
+      "name": "sys_nav_work",
+      "font_class": "sys_nav_work",
+      "unicode": "e6f9",
+      "unicode_decimal": 59129
+    },
+    {
+      "icon_id": "15079065",
+      "name": "sys_nav_system",
+      "font_class": "sys_nav_system",
+      "unicode": "e6fa",
+      "unicode_decimal": 59130
+    },
+    {
+      "icon_id": "15079066",
+      "name": "tit_ele",
+      "font_class": "tit_ele",
+      "unicode": "e6fb",
+      "unicode_decimal": 59131
+    },
+    {
+      "icon_id": "15079018",
+      "name": "mode",
+      "font_class": "mode",
+      "unicode": "e6ef",
+      "unicode_decimal": 59119
+    },
+    {
+      "icon_id": "15079019",
+      "name": "nav_guestbook",
+      "font_class": "nav_guestbook",
+      "unicode": "e6f0",
+      "unicode_decimal": 59120
+    },
+    {
+      "icon_id": "15078995",
+      "name": "guestbook_btnbg_first",
+      "font_class": "guestbook_btnbg_first",
+      "unicode": "e6e9",
+      "unicode_decimal": 59113
+    },
+    {
+      "icon_id": "15078996",
+      "name": "btnbg_normal",
+      "font_class": "btnbg_normal",
+      "unicode": "e6ea",
+      "unicode_decimal": 59114
+    },
+    {
+      "icon_id": "15078997",
+      "name": "guestbook_success",
+      "font_class": "guestbook_success",
+      "unicode": "e6eb",
+      "unicode_decimal": 59115
+    },
+    {
+      "icon_id": "15078998",
+      "name": "guestbook_close",
+      "font_class": "guestbook_close",
+      "unicode": "e6ec",
+      "unicode_decimal": 59116
+    },
+    {
+      "icon_id": "15078999",
+      "name": "logo_tit",
+      "font_class": "logo_tit",
+      "unicode": "e6ed",
+      "unicode_decimal": 59117
+    },
+    {
+      "icon_id": "15079000",
+      "name": "logo_normal",
+      "font_class": "logo_normal",
+      "unicode": "e6ee",
+      "unicode_decimal": 59118
+    }
+  ]
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 83 - 0
sh_backstage/src/assets/font/iconfont.svg


BIN
sh_backstage/src/assets/font/iconfont.ttf


BIN
sh_backstage/src/assets/font/iconfont.woff


BIN
sh_backstage/src/assets/font/iconfont.woff2


BIN
sh_backstage/src/assets/img/01jk.png


BIN
sh_backstage/src/assets/img/01jks.png


BIN
sh_backstage/src/assets/img/02fb.png


BIN
sh_backstage/src/assets/img/03mg.png


BIN
sh_backstage/src/assets/img/04fb.png


BIN
sh_backstage/src/assets/img/4dage-logo.png


BIN
sh_backstage/src/assets/img/bg.jpg


BIN
sh_backstage/src/assets/img/biyan.png


BIN
sh_backstage/src/assets/img/logout.png


BIN
sh_backstage/src/assets/img/noPicture.png


BIN
sh_backstage/src/assets/img/user.png


BIN
sh_backstage/src/assets/img/wenli.png


BIN
sh_backstage/src/assets/img/zhoushan-logo.jpg


BIN
sh_backstage/src/assets/logo.png


+ 58 - 0
sh_backstage/src/components/crumbs/index.vue

@@ -0,0 +1,58 @@
+<!--  -->
+<template>
+<div class='crumbs card'><span v-for="(item,i) in data" :key='i'>{{item.name + (i === data.length - 1?'':' > ')}}</span></div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+export default {
+// import引入的组件需要注入到对象中才能使用
+  props: {
+    data: {
+      default: () => [],
+      type: Array
+    }
+  },
+  components: {},
+  data () {
+    // 这里存放数据
+    return {
+
+    }
+  },
+  // 监听属性 类似于data概念
+  computed: {},
+  // 监控data中的数据变化
+  watch: {},
+  // 方法集合
+  methods: {
+
+  },
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created () {
+
+  },
+  // 生命周期 - 挂载完成(可以访问DOM元素)
+  mounted () {
+
+  },
+  beforeCreate () {}, // 生命周期 - 创建之前
+  beforeMount () {}, // 生命周期 - 挂载之前
+  beforeUpdate () {}, // 生命周期 - 更新之前
+  updated () {}, // 生命周期 - 更新之后
+  beforeDestroy () {}, // 生命周期 - 销毁之前
+  destroyed () {}, // 生命周期 - 销毁完成
+  activated () {} // 如果页面有keep-alive缓存功能,这个函数会触发
+}
+</script>
+<style scoped>
+.crumbs{
+  color: #532F1C!important;
+  padding: 20px;
+  font-size: 1.25rem;
+  font-weight: bold;
+}
+
+</style>

+ 64 - 0
sh_backstage/src/components/main-top/index.vue

@@ -0,0 +1,64 @@
+<!--  -->
+<template>
+<div class='main-top'>
+  <crumbs :data="crumb" />
+  <slot name='con'></slot>
+</div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+import Crumbs from '../crumbs'
+
+export default {
+// import引入的组件需要注入到对象中才能使用
+  props: {
+    crumb: {
+      default: () => [],
+      type: Array
+    }
+  },
+  components: {Crumbs},
+  data () {
+    // 这里存放数据
+    return {
+
+    }
+  },
+  // 监听属性 类似于data概念
+  computed: {},
+  // 监控data中的数据变化
+  watch: {},
+  // 方法集合
+  methods: {
+
+  },
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created () {
+
+  },
+  // 生命周期 - 挂载完成(可以访问DOM元素)
+  mounted () {
+
+  },
+  beforeCreate () {}, // 生命周期 - 创建之前
+  beforeMount () {}, // 生命周期 - 挂载之前
+  beforeUpdate () {}, // 生命周期 - 更新之前
+  updated () {}, // 生命周期 - 更新之后
+  beforeDestroy () {}, // 生命周期 - 销毁之前
+  destroyed () {}, // 生命周期 - 销毁完成
+  activated () {} // 如果页面有keep-alive缓存功能,这个函数会触发
+}
+</script>
+<style scoped>
+.main-top{
+  background:#fff;
+  width: 100%;
+  margin-bottom: 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+</style>

+ 60 - 0
sh_backstage/src/configue/base.js

@@ -0,0 +1,60 @@
+const base = {
+  reg: {
+    idCard: /^\d{6}(18|19|20)?\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}(\d|[xX])$/,
+    phone: /^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$/,
+    email: /^([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/
+  },
+  isImg: function (url) {
+    var temp = []
+    temp = url.toString().split('.')
+    if (temp[temp.length - 1].toLowerCase() === 'png' || temp[temp.length - 1].toLowerCase() === 'jpg') {
+      return true
+    } else {
+      return false
+    }
+  },
+  timestampToTime: function (timestamp) {
+    var date = new Date(Number(timestamp))// 时间戳为10位需*1000,时间戳为13位的话不需乘1000
+
+    var Y = date.getFullYear() + '-'
+
+    var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
+
+    var D = date.getDate() + ' '
+
+    // var h = date.getHours() + ':'
+
+    // var m = date.getMinutes() + ':'
+
+    // var s = date.getSeconds()
+
+    return Y + M + D
+  },
+  dateFormat: function (fmt, that) {
+    var o = {
+      'M+': that.getMonth() + 1, // 月份
+      'd+': that.getDate(), // 日
+      'h+': that.getHours(), // 小时
+      'm+': that.getMinutes(), // 分
+      's+': that.getSeconds(), // 秒
+      'q+': Math.floor((that.getMonth() + 3) / 3), // 季度
+      S: that.getMilliseconds() // 毫秒
+    }
+    if (/(y+)/.test(fmt)) {
+      fmt = fmt.replace(
+        RegExp.$1,
+        (that.getFullYear() + '').substr(4 - RegExp.$1.length)
+      )
+    }
+    for (var k in o) {
+      if (new RegExp('(' + k + ')').test(fmt)) {
+        fmt = fmt.replace(
+          RegExp.$1,
+          RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)
+        )
+      }
+    }
+    return fmt
+  }
+}
+export { base }

+ 3 - 0
sh_backstage/src/configue/bus.js

@@ -0,0 +1,3 @@
+import Vue from 'vue'
+
+export default new Vue()

+ 112 - 0
sh_backstage/src/configue/http.js

@@ -0,0 +1,112 @@
+import axios from 'axios'
+// import qs from 'qs'
+import Vue from 'vue'
+import router from '../router'
+
+const vue = new Vue()
+
+var isProduction = process.env.NODE_ENV === 'production'
+let loading = ''
+// 配置请求域名
+
+// const serverName = isProduction ? '' : 'http://ly.4dage.com/'
+// const serverName = isProduction ? '' : 'http://192.168.0.207:8100'
+const serverName = isProduction ? '/' : 'http://192.168.0.44:8100/'
+// http://192.168.0.44:8100/web/index.html#/
+
+const serverLocation = window.location.hostname
+
+axios.defaults.baseURL = serverName
+axios.defaults.headers['X-Requested-with'] = 'XMLHttpRequest'
+axios.defaults.headers['token'] =  window.localStorage.getItem('token')
+
+axios.interceptors.request.use(function (config) {
+  if (config.method === 'post') {
+    config.data = {
+      ...config.data,
+      rnd: Math.random()
+    }
+    // config.data = qs.stringify(config.data)
+  } else if (config.method === 'get') {
+    config.params = {
+      rnd: Math.random(),
+      ...config.params
+    }
+    // config.params = qs.stringify(config.params)
+  }
+  loading = vue.$loading({
+    lock: true,
+    text: 'Loading',
+    spinner: 'el-icon-loading',
+    background: 'rgba(0, 0, 0, 0.7)'
+  });
+  return config
+}, function (error) {
+  // 对请求错误做些什么
+  return Promise.reject(error)
+})
+
+// 配置response拦截器
+axios.interceptors.response.use(
+  response => {
+    let data = response.data
+    let code = Number(response.data.code)
+    loading.close()
+    switch (code) {
+      case -1:
+        break
+        case 4500:
+          vue.$alert('没有获得授权,请联系系统管理员', '提示', {
+            confirmButtonText: '确定',
+            callback: function () {
+            }
+          })
+          break
+        case 5001:
+          if (window.localStorage.getItem('token')) {
+            window.localStorage.setItem('token', '')
+            vue.$alert('登录状态失效,请重新登录', '提示', {
+              confirmButtonText: '确定',
+              callback: function () {
+                window.localStorage.setItem('userInfo', '')
+                router.push('/login')
+              }
+            })
+          }
+          break
+        case 5002:
+          if (window.localStorage.getItem('token')) {
+            window.localStorage.setItem('token', '')
+            vue.$alert('登录状态失效,请重新登录', '提示', {
+              confirmButtonText: '确定',
+              callback: function () {
+                window.localStorage.setItem('userInfo', '')
+                router.push('/login')
+              }
+            })
+          }
+          break
+        case 500:
+          vue.$alert(data.message || '服务器错误', '提示', {
+            confirmButtonText: '确定',
+            callback: function () {
+            }
+          })
+          break
+      case 0:
+        break
+    }
+    // tryHideFullScreenLoading()
+    return data
+  },
+  error => {
+    if (error.response) {
+      switch (error.response.status) {
+        case 500:
+          break
+      }
+    }
+    return Promise.reject(error)
+  }
+)
+export { serverName, axios, serverLocation }

+ 108 - 0
sh_backstage/src/main.js

@@ -0,0 +1,108 @@
+import Vue from 'vue'
+import App from './App.vue'
+import router from './router'
+import ElementUI from 'element-ui';
+import {base} from '@/configue/base'
+import 'element-ui/lib/theme-chalk/index.css';
+import {axios, serverName} from './configue/http'
+import '../theme/index.css'
+
+Vue.use(ElementUI)
+Vue.config.productionTip = false
+
+Vue.prototype.$base = base
+Vue.prototype.$bus = new Vue()
+Vue.prototype.$http = axios
+
+Vue.prototype.MAXLENGTH = 200
+Vue.prototype.FONTLENGTH = 15
+
+
+
+Vue.prototype.BLOCK = {
+  1:'东楼',
+  2:'西楼',
+  3:'钟楼'
+}
+
+Vue.prototype.ZONE = {
+  "in":'室内',
+  'out':'室外'
+}
+
+
+Vue.prototype.loutiArr=[
+  {
+    name:'东楼',
+    id:1
+  },
+  {
+    name:'西楼',
+    id:2
+  },
+    {
+    name:'钟楼',
+    id:3
+  }
+]
+
+Vue.prototype.zoneArr=[
+  {
+    name:'室内',
+    id:'in'
+  },
+  {
+    name:'室外',
+    id:'out'
+  }
+]
+
+
+Vue.prototype.roamArr=[
+  {
+    name:'楼立面展示',
+    id:1
+  },
+  {
+    name:'场景漫游',
+    id:2
+  }
+]
+
+Vue.prototype.partsArr=[
+  {
+    name:'雕饰构件',
+    id:3
+  },
+  {
+    name:'栏柱构件',
+    id:4
+  },
+  {
+    name:'楼梯构件',
+    id:5
+  },
+  {
+    name:'墙面构件',
+    id:6
+  },
+  {
+    name:'墙饰面',
+    id:7
+  },
+  {
+    name:'廊道构件',
+    id:8
+  },
+  {
+    name:'精品馆藏',
+    id:9
+  }
+]
+
+Vue.prototype.$serverName = serverName// 挂载到Vue实例上面
+
+new Vue({
+  router,
+  render: h => h(App)
+}).$mount('#app')

+ 260 - 0
sh_backstage/src/pages/content/Architecture.vue

@@ -0,0 +1,260 @@
+<template>
+  <div>
+    <main-top :crumb="crumbData"></main-top>
+    <div class="home-layout">
+      <el-tabs class="etabs" v-model="activeName" @tab-click="handleClick">
+        <el-tab-pane class="etabs-item" v-for="(item,i) in tlist" :key="i" :label="item.name" :name="item.name">
+          <template v-if="activeName===item.name">
+            <!-- {{item.video}} -->
+            <video v-if="item.displayModel === 2&&activeName===item.name" :src="item.video||item.webSite" autoplay loop></video>
+            <iframe v-else :src="item.webSite" frameborder="0"></iframe>
+          </template>
+          <div class="c-right">
+            <div class="intro">
+            <h2>
+              <span>建筑介绍</span>
+              <i class="iconfont iconsys_edit" @click="goto(item)"></i>
+            </h2>
+            <p v-html="item.description||'无'"></p>
+          </div>
+          <div class="media">
+            <h2>
+              <span>多媒体资料</span>
+              <i class="iconfont iconsys_down" @click="apply(item)"></i>
+            </h2>
+            <p v-if="item.files.length<=0" style="color:#999">暂无</p>
+            <p v-for="(sub,idx) in item.files" :key="idx">{{sub.fileName}}</p>
+          </div>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+
+    <el-dialog title="提交下载申请" :visible.sync="dialogFormVisible" width="40%">
+      <div class="dialog">
+        <p>请选择要下载的内容:</p>
+        <div class="content">
+            <el-checkbox-group v-model="checkList" @change='handleChange'>
+              <div v-for="(sub,idx) in activeItem.files" :key="idx">
+                <el-checkbox :label="sub.id" :false-label="''" :true-label="''">{{sub.fileName}}</el-checkbox>
+              </div>
+            </el-checkbox-group>
+        </div>
+        <p>请填写下载用途:</p>
+        <div class="content textarea">
+          <el-input
+            type="textarea"
+            :rows="2"
+            placeholder="请输入内容"
+            v-model="textarea">
+          </el-input>
+        </div>
+        <p>请选择审核人:</p>
+        <ul class="content">
+          <li v-for="(item,i) in adminList" :key="i">
+            <el-radio v-model="radio" :label="item.id">{{item.userName}}</el-radio>
+          </li>
+        </ul>
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogFormVisible = false">取 消</el-button>
+        <el-button type="primary" @click="applyDownload">确 定</el-button>
+      </div>
+    </el-dialog>
+    </div>
+  </div>
+</template>
+
+<script>
+import MainTop from '@/components/main-top'
+
+const crumbData = [
+  {
+    name: '建筑信息',
+    id: 2
+  }
+]
+
+export default {
+  components:{
+    MainTop
+  },
+  data(){
+    return {
+      activeName:'东楼',
+      crumbData,
+      dialogFormVisible: false,
+      formLabelWidth: '7.5rem',
+      checkList: [],
+      textarea:'',
+      radio:'',
+      tlist:[],
+      activeItem:'',
+      adminList:[]
+    }
+  },
+  methods:{
+    handleClick(){
+
+    },
+    apply(item){
+      if (item.files.length===0) {
+        return
+      }
+      this.dialogFormVisible = true
+      this.activeItem = item
+
+      this.getAdminList()
+    },
+    handleChange(item,test){
+      console.log(item,test);
+      
+    },
+    async applyDownload(){
+      
+      if (!this.radio||!this.checkList.length) {
+        return this.$alert("请填写勾选信息", "提示", {
+            confirmButtonText: "确定",
+            callback: () => {
+              return
+            },
+          })
+      }
+      
+      let params ={
+        auditId: this.radio,
+        fileIds: this.checkList.join(','),
+        fkId: '',
+        name: this.activeName,
+        reason: this.textarea,
+        type: 'building'
+      }
+    let result = await this.$http({
+        method: 'post',
+        data:params,
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: '/manage/download/save'
+      })
+
+      
+      if (result.code === 0) {
+        this.$alert('申请成功', '提示', {
+          confirmButtonText: '确定',
+          callback: () => {
+            this.getList()
+          }
+        })
+      } else {
+        this.$notify.error({
+          title: '错误',
+          message: result.msg
+        })
+      }
+
+    },
+      // /manage/building/list
+    async getList () {
+      this.dialogFormVisible = false
+      let result = await this.$http({
+        method: 'post',
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: '/manage/building/list'
+      })
+
+      this.activeName = result.data.list[0].name
+      this.tlist = result.data.list
+    },
+    async getAdminList(){
+    let result = await this.$http({
+        method: 'get',
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: '/manage/download/admin'
+      })
+
+      this.adminList = result.data
+    },
+    goto (item) {
+      this.$router.push({ name: 'edit-architecture', params: { type: item.id } })
+    },
+  },
+  mounted(){
+    this.getList()
+  }
+}
+</script>
+
+
+<style lang="less" scoped>
+.home-layout{
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem - 1.25rem);
+  .etabs{
+    background: #fff;
+    padding: 1.25rem;
+  
+    .etabs-item{
+      width: 100%;
+      justify-content: space-between;
+      display: flex;
+    }
+    iframe,video{
+      height: 39.5rem;
+      margin-right: 20px;
+      flex: 3;
+    }
+
+    .c-right{
+      flex: 1;
+    }
+    .intro, .media{
+      line-height: 1.5;
+      margin-top: 1.25rem;
+      h2{
+        padding-right: .625rem;
+        display: flex;
+        justify-content: space-between;
+        color: #532F1C;
+        font-size: 1.125rem;
+        font-weight: bold;
+        border-bottom: .0625rem solid #532F1C;
+        i{
+          cursor: pointer;
+          font-size: 1.25rem;
+        }
+      }
+      p{
+        font-size: .875rem;
+      }
+    }
+    .media{
+      p{
+        color: rgba(0, 176, 255, 1);
+        cursor: pointer;
+        line-height: 2;
+      }
+    }
+  }
+}
+
+.dialog{
+  .content{
+    border: .0625rem solid #ccc;
+    padding: .625rem;
+    margin: .625rem auto;
+    div,li{
+      line-height: 2;
+    }
+  }
+  .textarea{
+    padding: 0;
+    border: none;
+  }
+}
+
+</style>

+ 461 - 0
sh_backstage/src/pages/content/Comment.vue

@@ -0,0 +1,461 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData"></main-top>
+    <div class="table-interface">
+      <div class="top-body">
+        <div class="info-top">
+          <div class="info-left">
+            <span>按时间查看:</span>
+               <el-date-picker
+                v-model="value1"
+                type="daterange"
+                range-separator="-"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期">
+              </el-date-picker>
+              <el-input style="width:220px;margin:0 20px;" v-model="inputKey" placeholder="请输入关键字"></el-input>
+              <el-button type="primary" @click="getInformation(value1)">查找</el-button>
+              <el-button style="margin-left:20px" @click="reset">重置</el-button>
+          </div>
+          <div class="info-right">
+            <!-- <el-button type="primary" @click="show('', 'add')">新增评论</el-button> -->
+          </div>
+        </div>
+
+        <el-table :data="tableData" style="width: 100%">
+          <el-table-column
+            v-for="(item, idx) in data"
+            :key="idx"
+            :prop="item.prop"
+            :label="item.label"
+          >
+            <template slot-scope="scope">
+              <span>{{ scope.row[item.prop] || "-" }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作">
+            <template slot-scope="scope">
+              <span class="o-span" @click="show(scope.row, 'edit')"
+                >查看详情</span
+              >
+              <span class="o-delete" v-if="$role==='admin'" @click="del(scope.row)">删除</span>
+            </template>
+          </el-table-column>
+       
+        </el-table>
+        <div class="e-pagination">
+          <el-pagination
+            @current-change="handleCurrentChange"
+            :current-page.sync="currentPage"
+            :page-size="size"
+            layout="prev, pager, next, jumper"
+            :total="total"
+          ></el-pagination>
+        </div>
+      </div>
+    </div>
+    <el-dialog title="观众评论详情" :visible.sync="dialogFormVisible" width="40%">
+      <el-form :model="form">
+        <el-form-item
+          label="游客名称:"
+          disabled
+          style="width:60%;"
+          :label-width="formLabelWidth"
+        >
+          <el-input
+            v-model="form.userName"
+            disabled
+            placeholder=""
+            autocomplete="off"
+          ></el-input>
+        </el-form-item>
+        <el-form-item
+          label="手机号码(选填):"
+          style="width:60%;"
+          :label-width="formLabelWidth"
+        >
+          <el-input
+            disabled
+            v-model="form.phone"
+            placeholder=""
+            autocomplete="off"
+          ></el-input>
+        </el-form-item>
+
+         <el-form-item
+          label="邮箱地址(选填):"
+          disabled
+          style="width:60%;"
+          :label-width="formLabelWidth"
+        >
+          <el-input
+            v-model="form.email"
+            disabled
+            placeholder=""
+            autocomplete="off"
+          ></el-input>
+        </el-form-item>
+
+          <el-form-item
+          label="留言内容:"
+          style="width:94%;"
+          :label-width="formLabelWidth"
+        >
+          <el-input
+            type="textarea"
+            disabled
+            v-model="form.message"
+            placeholder="请输入留言内容"
+            autocomplete="off"
+          ></el-input>
+        </el-form-item>
+       
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="handleIdx('prev')" type="primary">上一条</el-button>
+        <el-button @click="handleIdx('next')" type="primary">下一条</el-button>
+        <el-button @click="dialogFormVisible = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from "@/components/main-top";
+const crumbData = [
+  {
+    name: "观众评论管理",
+    id: 10,
+  },
+];
+
+let urlType = {
+  add: "/equipment/addEquipment",
+  edit: "/equipment/updateEquipment",
+};
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop,
+  },
+  data() {
+    // 这里存放数据
+    let data = [
+      {
+        prop: "idx",
+        label: "序号",
+      },
+      {
+        prop: "userName",
+        label: "游客名称",
+      },
+      {
+        prop: "message",
+        label: "评论内容",
+      },
+      {
+        prop: "phone",
+        label: "联系方式",
+      },
+      {
+        prop: "email",
+        label: "邮箱地址",
+      },
+      {
+        prop: "createTime",
+        label: "发布时间",
+      },
+    ];
+
+    return {
+      crumbData,
+      data,
+      infoName: "",
+      tableData: [],
+      dialogTableVisible: false,
+      dialogFormVisible: false,
+      typeId: "",
+      timeId: "",
+      tlist: [],
+      form: {
+        id: "",
+        name: "",
+        uuid: "",
+        positionId: "",
+        state: "",
+        positionName: "",
+      },
+      plist: [],
+      inputKey: "",
+      currentPage: 1,
+      size: 10,
+      total: 0,
+      formLabelWidth: "150px",
+      imageUrl: "",
+      type: "add",
+      value1:['',''],
+      currentIdx:''
+    };
+  },
+  watch: {
+    currentPage() {
+      this.refresh();
+    },
+    size() {
+      this.refresh();
+    },
+    
+    value1(val){
+      this.getInformation(val)
+    },
+    currentIdx(val){
+      this.form = this.tableData[val];
+    }
+  },
+  mounted() {
+    this.refresh();
+  },
+  methods: {
+    goto(item) {
+      window.localStorage.setItem("editInfo", JSON.stringify(item));
+      this.$router.push({ name: "edit-information", params: { type: 1 } });
+      this.$bus.$emit("editinfo", item);
+    },
+
+    refresh() {
+      this.loading = true;
+      this.getInformation();
+      this.loading = false;
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+    },
+    handleIdx(type){
+      if (type==='prev' && this.currentIdx >= 1) {
+        this.currentIdx -= 1
+      } else if(type==='next' && this.currentIdx < this.tableData.length - 1){
+        this.currentIdx += 1
+      }
+    },
+    save() {
+      let { userName, roleId, phone, head, state } = this.form;
+      let data = { userName, roleId, phone, head, state };
+
+      this.$http.post(urlType[this.type], data).then((res) => {
+        if (res.code === 0) {
+          this.$alert("操作成功", "提示", {
+            confirmButtonText: "确定",
+            callback: () => {
+              this.dialogFormVisible = false;
+              this.refresh();
+            },
+          });
+        } else {
+          this.$notify.error({
+            title: "错误",
+            message: res.message,
+          });
+        }
+      });
+    },
+
+    show(item = "") {
+      this.dialogFormVisible = true;
+      this.form = {
+        createTime: "",
+        email: "",
+        id: '',
+        idx:'',
+        message: "",
+        phone: "",
+        updateTime: "",
+        userName: ""
+      };
+      this.currentIdx = item.idx - 1
+      this.form = item;
+    },
+    reset(){
+      this.inputKey=''
+      this.value1=['','']
+    },
+
+     del (item) {
+      let ids = item.id
+      this.$confirm('此操作将删除该数据, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.$http.get(`/manage/message/removes/${ids}`,{headers: {
+          token: window.localStorage.getItem('token')
+        }}).then(res => {
+          if (res.code === 0) {
+            this.$alert('删除成功', '提示', {
+              confirmButtonText: '确定',
+              callback: () => {
+                this.refresh()
+              }
+            })
+          } else {
+            this.$notify.error({
+              title: '错误',
+              message: res.msg
+            })
+          }
+        })
+      }).catch(() => {
+        this.$message({
+          type: 'info',
+          message: '已取消删除'
+        })
+      })
+    },
+
+    async getInformation(time=['','']) {
+      let params = {
+        endTime: time[1].replace('00:00:00','23:59:59'),
+        pageNum: this.currentPage,
+        pageSize: this.size,
+        searchKey: this.inputKey,
+        startTime: time[0],
+      };
+
+      let result = await this.$http({
+        method: "post",
+        data: params,
+        headers: {
+          token: window.localStorage.getItem("token"),
+        },
+        url: "/manage/message/list",
+      });
+
+      if (result.code !== 0) {
+        return;
+      }
+      this.tableData = result.data.list;
+      this.total = result.data.total;
+      this.tableData.forEach((item, i) => {
+        item['idx'] = i + 1
+      })
+    }
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.top-body {
+  border-top: 0.0625rem solid #e6e6e6;
+  line-height: 1.5;
+  padding: 0 1.25rem 1.25rem;
+  align-items: center;
+  box-sizing: border-box;
+  background: #fff;
+  margin: 1rem 0;
+}
+
+.top-body .top-con {
+  font-weight: bold;
+}
+.table-title {
+  padding: 1rem 1rem 1rem 0;
+  font-size: 1rem;
+  color: #2d2d2d;
+  border-bottom: 1px solid #ccc;
+  display: flex;
+  justify-content: space-between;
+}
+
+.more {
+  color: #ec652d;
+  cursor: pointer;
+}
+
+.top-right {
+  background-color: #ec652d;
+  height: 3.5625rem;
+  line-height: 3.5625rem;
+  color: #fff;
+  padding: 0 1.875rem;
+  border-radius: 0.3125rem;
+  font-size: 1.125rem;
+  font-weight: bold;
+}
+
+.search-body {
+  background-color: #fff;
+  margin: 1.25rem 0;
+}
+
+.interface-table {
+  height: calc(100% - 17.1875rem);
+  overflow-x: hidden;
+  background-color: #fff;
+  padding: 0 1.125rem 2.375rem;
+  box-sizing: border-box;
+}
+
+.zan-con {
+  display: flex;
+  width: 100%;
+  justify-content: space-around;
+  margin-top: 1.5rem;
+  text-align: center;
+  font-size: 1.125rem;
+}
+
+.zan-con .line {
+  height: 8rem;
+  width: 1px;
+  background: #ccc;
+}
+
+.zan-con .zan-contain {
+  display: flex;
+  justify-content: space-between;
+  flex-direction: column;
+}
+
+.zan-con .zan-contain p {
+  font-size: 2.25rem;
+}
+
+.zan-con .zan-contain p:first-child {
+  font-size: 0.875rem;
+  line-height: 1.5;
+}
+
+.zan-sub {
+  text-align: right;
+  margin-top: 1.25rem;
+  color: #a5a5a5;
+  font-size: 0.875rem;
+}
+
+.table-interface {
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem);
+}
+
+.info-top {
+  padding: 20px 0;
+  display: flex;
+  justify-content: space-between;
+  border-bottom: 1px #a5a5a5 solid;
+}
+
+.o-span {
+  cursor: pointer;
+  color: rgb(7, 152, 244);
+}
+
+.dialog-footer{
+  text-align: center;
+}
+</style>

+ 381 - 0
sh_backstage/src/pages/content/Parts.vue

@@ -0,0 +1,381 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData"></main-top>
+    <div class="table-interface">
+      <div class="top-body">
+        <div class="info-top">
+          <div class="info-left">
+            <span>按楼体查看:</span>
+            <el-select style="width:6.25rem;" v-model="loutiId" placeholder="请选择">
+              <el-option label="全部" value=""></el-option>
+              <el-option v-for="(item,i) in loutiArr" :key="i" :label="item.name" :value="item.id"></el-option>
+            </el-select>
+            <span style="margin-left:1.25rem;">按位置查看:</span>
+            <el-select style="width:6.25rem;" v-model="zoneId" placeholder="请选择">
+              <el-option label="全部" value=""></el-option>
+              <el-option v-for="(item,i) in zoneArr" :key="i" :label="item.name" :value="item.id"></el-option>
+            </el-select>
+            <span style="margin-left:1.25rem;">按类型查看:</span>
+            <el-select class="elSelect" v-model="partsId" placeholder="请选择">
+              <el-option label="全部" value=""></el-option>
+              <el-option v-for="(item,i) in partsArr" :key="i" :label="item.name" :value="item.id"></el-option>
+            </el-select>
+            <el-input class="elInput" v-model="inputKey" placeholder="请输入部件名称搜索"></el-input>
+            <el-button type="primary" @click="getInformation">查找</el-button>
+            <el-button @click="reset">重置</el-button>
+          </div>
+          <div class="info-right">
+            <el-button type="primary" @click="$router.push({name:'edit-parts',params:{type:0,id:'none'}})">新增</el-button>
+          </div>
+        </div>
+        <div class="collection-con">
+          <div class="nodata" v-if="tableData.length<=0">暂无数据</div>
+          <ul>
+            <li class="theme-color" @click="gotoShow(item)" v-for="(item,i) in tableData" :key="i">
+              <div class="li-img">
+                <img :src="item.thumb" alt />
+                <img class="eye" v-if="!item.display" :src="require('@/assets/img/biyan.png')" alt="">
+              </div>
+              <div class="li-txt">
+                <span>{{item.typeName}}</span>
+                <span>{{BLOCK[item.block]}} {{ZONE[item.zone]}}</span>
+              </div>
+              <div class="li-name">
+                <div>{{item.name}}</div>
+                <div>
+                  <span  @click.stop="goto(item)">编辑</span>
+                  <span  @click.stop="del(item)" v-if="$role==='admin'">删除</span>
+                </div>
+              </div>
+            </li>
+          </ul>
+        </div>
+        <div class="e-pagination">
+          <el-pagination
+            @current-change="handleCurrentChange"
+            :current-page.sync="currentPage"
+            :page-size="size"
+            layout="prev, pager, next, jumper"
+            :total="total"
+          ></el-pagination>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from '@/components/main-top'
+const crumbData = [
+  {
+    name: '部件信息',
+    id: 4
+  }
+]
+
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop,
+  },
+  data () {
+    return {
+      crumbData,
+      tableData: [],
+      inputKey: '',
+      currentPage: 1,
+      size: 12,
+      total: 0,
+      loading: false,
+      plist: [],
+      tlist: [],
+      loutiId:'',
+      zoneId:'',
+      partsId:'',
+    }
+  },
+  watch: {
+    currentPage () {
+      this.refresh()
+    },
+    size () {
+      this.refresh()
+    }
+  },
+  mounted () {
+    this.refresh()
+  },
+ 
+  methods: {
+    reset(){
+      this.inputKey=''
+      this.partsId=''
+      this.zoneId=''
+      this.loutiId=''
+    },
+    del (item) {
+      let ids = item.id
+      this.$confirm('此操作将删除该数据, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.$http.get(`/manage/part/removes/${ids}`,{headers: {
+          token: window.localStorage.getItem('token')
+        }}).then(res => {
+          if (res.code === 0) {
+            this.$alert('删除成功', '提示', {
+              confirmButtonText: '确定',
+              callback: () => {
+                this.refresh()
+              }
+            })
+          } else {
+            this.$notify.error({
+              title: '错误',
+              message: res.msg
+            })
+          }
+        })
+      }).catch(() => {
+        this.$message({
+          type: 'info',
+          message: '已取消删除'
+        })
+      })
+    },
+    
+    gotoShow (item) {
+      this.$router.push({ name: 'show-parts', params: { id: item.id } })
+    },
+
+    goto (item) {
+      this.$router.push({ name: 'edit-parts', params: { type: 1,id:item.id } })
+    },
+ 
+    refresh () {
+      this.loading = true
+      this.getInformation()
+      this.loading = false
+    },
+    handleCurrentChange (val) {
+      this.currentPage = val
+    },
+    async getInformation () {
+      let params = {
+          block: this.loutiId,
+          pageNum:this.currentPage,
+          pageSize: this.size,
+          searchKey: this.inputKey,
+          typeId: this.partsId,
+          zone: this.zoneId
+      }
+
+      let result = await this.$http({
+        method: 'post',
+        data: params,
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: '/manage/part/list'
+      })
+
+      if (result.code !== 0) {
+        return
+      }
+      this.tableData = result.data.list
+      this.total = result.data.total
+    }
+  }
+}
+</script>
+
+
+<style lang="less" scoped>
+.top-body{
+  border-top: .0625rem solid #e6e6e6;
+  line-height: 1.5;
+  padding: 0 1.25rem 1.25rem;
+  align-items: center;
+  box-sizing: border-box;
+  background: #fff;
+  margin: 1rem 0;
+}
+
+.top-body .top-con{
+  font-weight: bold;
+}
+.table-title{
+  padding: 1rem 1rem 1rem 0 ;
+  font-size: 1rem;
+  color: #2d2d2d;
+  border-bottom: .0625rem solid #ccc;
+  display: flex;
+  justify-content: space-between;
+}
+
+.more{
+  color: #ec652d;
+  cursor: pointer;
+}
+
+.top-right{
+  background-color: #ec652d;
+  height: 3.5625rem;
+  line-height: 3.5625rem;
+  color: #fff;
+  padding: 0 1.875rem;
+  border-radius: .3125rem;
+  font-size: 1.125rem;
+  font-weight: bold;
+}
+
+.search-body{
+  background-color: #fff;
+  margin: 1.25rem 0;
+}
+
+.interface-table{
+  height: calc(100% - 17.1875rem);
+  overflow-x: hidden;
+  background-color: #fff;
+  padding: 0 1.125rem 2.375rem;
+  box-sizing: border-box;
+}
+
+.zan-con{
+  display: flex;
+  width: 100%;
+  justify-content: space-around;
+  margin-top: 1.5rem;
+  text-align: center;
+  font-size: 1.125rem;
+}
+
+.zan-con .line{
+  height: 8rem;
+  width: .0625rem;
+  background: #ccc;
+}
+
+.zan-con .zan-contain{
+  display: flex;
+  justify-content: space-between;
+  flex-direction: column;
+}
+
+.zan-con .zan-contain p{
+  font-size: 2.25rem;
+}
+
+.zan-con .zan-contain p:first-child{
+  font-size: .875rem;
+  line-height: 1.5;
+}
+
+.zan-sub{
+  text-align: right;
+  margin-top: 1.25rem;
+  color: #a5a5a5;
+  font-size: .875rem;
+}
+
+.table-interface{
+  height: calc(100% - 3rem);
+}
+
+
+.info-top{
+  padding: 1.25rem 0;
+  display: flex;
+  justify-content: space-between;
+  border-bottom: .0625rem #a5a5a5 solid;
+}
+
+.o-span{
+  cursor: pointer;
+  color: rgb(7, 152, 244);
+}
+
+.collection-con{
+  width: 100%;
+  overflow-x: hidden;
+  overflow-y: auto;
+  height: 61vh;
+}
+
+
+
+.collection-con ul{
+  display: flex;
+  flex-wrap: wrap;
+  margin-top: 1.25rem;
+}
+
+.collection-con ul li {
+  width: 24%;
+  margin-right: 1%;
+  cursor: pointer;
+  font-size: .8rem;
+  color: #2d2d2d;
+  margin-bottom: 1.25rem;
+}
+
+.collection-con ul .li-img {
+  position: relative;
+  
+}
+.collection-con ul .li-img div{
+  position: absolute;
+  left: .625rem;
+  bottom: .625rem;
+  color: #fff;
+}
+
+.collection-con ul .li-img div span{
+  margin-right: .625rem;
+}
+
+
+.collection-con ul img {
+  width: 100%;
+}
+
+.collection-con{
+  .li-txt,.li-name{
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  .li-name{
+    margin-top: .5rem;
+    div{
+      font-size: 1rem;
+      font-weight: bold;
+      color: #532F1C;
+      &:last-of-type{
+        color: #707070;
+        font-size: .875rem;
+        font-weight: normal;
+        span{
+          margin-left: .30rem;
+          &:last-of-type{
+            &:hover{
+              color: #409EFF;
+            }
+          }
+          &:hover{
+            color: #c56351;
+          }
+        }
+        
+      }
+    }
+  }
+}
+</style>

+ 380 - 0
sh_backstage/src/pages/content/Roam.vue

@@ -0,0 +1,380 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData"></main-top>
+    <div class="table-interface">
+      <div class="top-body">
+        <div class="info-top">
+          <div class="info-left">
+            <span>按楼体查看:</span>
+            <el-select style="width:100px;" v-model="loutiId" placeholder="请选择">
+              <el-option label="全部" value=""></el-option>
+              <el-option v-for="(item,i) in loutiArr" :key="i" :label="item.name" :value="item.id"></el-option>
+            </el-select>
+            <span style="margin-left:20px;">按位置查看:</span>
+            <el-select style="width:100px;" v-model="zoneId" placeholder="请选择">
+              <el-option label="全部" value=""></el-option>
+              <el-option v-for="(item,i) in zoneArr" :key="i" :label="item.name" :value="item.id"></el-option>
+            </el-select>
+            <span style="margin-left:20px;">按类型查看:</span>
+            <el-select class="elSelect" v-model="partsId" placeholder="请选择">
+              <el-option label="全部" value=""></el-option>
+              <el-option v-for="(item,i) in roamArr" :key="i" :label="item.name" :value="item.id"></el-option>
+            </el-select>
+            <el-input class="elInput" v-model="inputKey" placeholder="请输入漫游名称搜索"></el-input>
+            <el-button type="primary" @click="getInformation">查找</el-button>
+            <el-button @click="reset">重置</el-button>
+          </div>
+          <div class="info-right">
+            <el-button type="primary" @click="$router.push({name:'edit-roam',params:{type:0,id:'none'}})">新增</el-button>
+          </div>
+        </div>
+        <div class="collection-con">
+          <div class="nodata" v-if="tableData.length<=0">暂无数据</div>
+
+          <ul>
+            <li class="theme-color" @click="gotoShow(item)" v-for="(item,i) in tableData" :key="i">
+              <div class="li-img">
+                <img :src="item.thumb" alt />
+                <img class="eye" v-if="!item.display" :src="require('@/assets/img/biyan.png')" alt="">
+              </div>
+              <div class="li-txt">
+                <span>{{item.typeName}}</span>
+                <span>{{BLOCK[item.block]}} {{ZONE[item.zone]}}</span>
+              </div>
+              <div class="li-name">
+                <div>{{item.name}}</div>
+                <div>
+                  <span  @click.stop="goto(item)">编辑</span>
+                  <span  @click.stop="del(item)" v-if="$role==='admin'">删除</span>
+                </div>
+              </div>
+            </li>
+          </ul>
+        </div>
+        <div class="e-pagination">
+          <el-pagination
+            @current-change="handleCurrentChange"
+            :current-page.sync="currentPage"
+            :page-size="size"
+            layout="prev, pager, next, jumper"
+            :total="total"
+          ></el-pagination>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from '@/components/main-top'
+const crumbData = [
+  {
+    name: '漫游信息',
+    id: 4
+  }
+]
+
+
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop,
+  },
+  data () {
+    return {
+      crumbData,
+      tableData: [],
+      inputKey: '',
+      currentPage: 1,
+      size: 12,
+      total: 0,
+      loading: false,
+      plist: [],
+      tlist: [],
+      loutiId:'',
+      zoneId:'',
+      partsId:'',
+    }
+  },
+  watch: {
+    currentPage () {
+      this.refresh()
+    },
+    size () {
+      this.refresh()
+    },
+    inputKey () {
+      this.refresh()
+    }
+  },
+  mounted () {
+    this.refresh()
+  },
+  methods: {
+    del (item) {
+      let ids = item.id
+      this.$confirm('此操作将删除该数据, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.$http.get(`/manage/roam/removes/${ids}`,{headers: {
+          token: window.localStorage.getItem('token')
+        }}).then(res => {
+          if (res.code === 0) {
+            this.$alert('删除成功', '提示', {
+              confirmButtonText: '确定',
+              callback: () => {
+                this.refresh()
+              }
+            })
+          } else {
+            this.$notify.error({
+              title: '错误',
+              message: res.msg
+            })
+          }
+        })
+      }).catch(() => {
+        this.$message({
+          type: 'info',
+          message: '已取消删除'
+        })
+      })
+    },
+    
+    gotoShow (item) {
+      this.$router.push({ name: 'show-roam', params: { id: item.id } })
+    },
+    goto (item) {
+      this.$router.push({ name: 'edit-roam', params: { type: 1, id:item.id} })
+    },
+    refresh () {
+      this.loading = true
+      this.getInformation()
+      this.loading = false
+    },
+    handleCurrentChange (val) {
+      this.currentPage = val
+    },
+    reset(){
+      this.inputKey=''
+      this.partsId=''
+      this.zoneId=''
+      this.loutiId=''
+    },
+    async getInformation () {
+      let params = {
+          block: this.loutiId,
+          pageNum:this.currentPage,
+          pageSize: this.size,
+          searchKey: this.inputKey,
+          typeId: this.partsId,
+          zone: this.zoneId
+      }
+
+      let result = await this.$http({
+        method: 'post',
+        data: params,
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: '/manage/roam/list'
+      })
+
+      if (result.code !== 0) {
+        return
+      }
+      this.tableData = result.data.list
+      this.total = result.data.total
+    }
+  }
+}
+</script>
+
+
+<style lang="less" scoped>
+.top-body{
+  border-top: .0625rem solid #e6e6e6;
+  line-height: 1.5;
+  padding: 0 1.25rem 1.25rem;
+  align-items: center;
+  box-sizing: border-box;
+  background: #fff;
+  margin: 1rem 0;
+}
+
+.top-body .top-con{
+  font-weight: bold;
+}
+.table-title{
+  padding: 1rem 1rem 1rem 0 ;
+  font-size: 1rem;
+  color: #2d2d2d;
+  border-bottom: 1px solid #ccc;
+  display: flex;
+  justify-content: space-between;
+}
+
+.more{
+  color: #ec652d;
+  cursor: pointer;
+}
+
+.top-right{
+  background-color: #ec652d;
+  height: 3.5625rem;
+  line-height: 3.5625rem;
+  color: #fff;
+  padding: 0 1.875rem;
+  border-radius: .3125rem;
+  font-size: 1.125rem;
+  font-weight: bold;
+}
+
+.search-body{
+  background-color: #fff;
+  margin: 1.25rem 0;
+}
+
+.interface-table{
+  height: calc(100% - 17.1875rem);
+  overflow-x: hidden;
+  background-color: #fff;
+  padding: 0 1.125rem 2.375rem;
+  box-sizing: border-box;
+}
+
+.zan-con{
+  display: flex;
+  width: 100%;
+  justify-content: space-around;
+  margin-top: 1.5rem;
+  text-align: center;
+  font-size: 1.125rem;
+}
+
+.zan-con .line{
+  height: 8rem;
+  width: 1px;
+  background: #ccc;
+}
+
+.zan-con .zan-contain{
+  display: flex;
+  justify-content: space-between;
+  flex-direction: column;
+}
+
+.zan-con .zan-contain p{
+  font-size: 2.25rem;
+}
+
+.zan-con .zan-contain p:first-child{
+  font-size: .875rem;
+  line-height: 1.5;
+}
+
+.zan-sub{
+  text-align: right;
+  margin-top: 1.25rem;
+  color: #a5a5a5;
+  font-size: .875rem;
+}
+
+.table-interface{
+  height: calc(100% - 3rem);
+}
+
+
+.info-top{
+  padding: 20px 0;
+  display: flex;
+  justify-content: space-between;
+  border-bottom: 1px #a5a5a5 solid;
+}
+
+.o-span{
+  cursor: pointer;
+  color: rgb(7, 152, 244);
+}
+
+.collection-con{
+  width: 100%;
+  overflow-x: hidden;
+  overflow-y: auto;
+  height: 61vh;
+}
+
+.collection-con ul{
+  display: flex;
+  flex-wrap: wrap;
+  margin-top: 20px;
+}
+
+.collection-con ul li {
+  width: 24%;
+  margin-right: 1%;
+  cursor: pointer;
+  font-size: .8rem;
+  color: #2d2d2d;
+  margin-bottom: 20px;
+}
+
+.collection-con ul .li-img {
+  position: relative;
+}
+.collection-con ul .li-img div{
+  position: absolute;
+  left: 10px;
+  bottom: 10px;
+  color: #fff;
+}
+
+.collection-con ul .li-img div span{
+  margin-right: 10px;
+}
+
+
+.collection-con ul img {
+  width: 100%;
+}
+
+.collection-con{
+  .li-txt,.li-name{
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  .li-name{
+    margin-top: .5rem;
+    div{
+      font-size: 1rem;
+      font-weight: bold;
+      color: #532F1C;
+      &:last-of-type{
+        color: #707070;
+        font-size: .875rem;
+        font-weight: normal;
+        span{
+          margin-left: .30rem;
+          &:last-of-type{
+            &:hover{
+              color: #409EFF;
+            }
+          }
+          &:hover{
+            color: #c56351;
+          }
+        }
+        
+      }
+    }
+  }
+}
+</style>

+ 370 - 0
sh_backstage/src/pages/content/Structure.vue

@@ -0,0 +1,370 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData"></main-top>
+    <div class="table-interface">
+      <div class="top-body">
+        <div class="info-top">
+          <div class="info-left">
+            <span>按楼体查看:</span>
+            <el-select style="width:100px;" v-model="loutiId" placeholder="请选择">
+              <el-option label="全部" value=""></el-option>
+              <el-option v-for="(item,i) in loutiArr" :key="i" :label="item.name" :value="item.id"></el-option>
+            </el-select>
+            <el-input style="width:220px;margin:0 20px;" v-model="inputKey" placeholder="请输入结构名称搜索"></el-input>
+            <el-button type="primary" @click="getInformation">查找</el-button>
+            <el-button @click="reset">重置</el-button>
+          </div>
+          <div class="info-right">
+            <el-button type="primary" @click="$router.push({name:'edit-structure',params:{type:0,id:'none'}})">新增</el-button>
+          </div>
+        </div>
+        <div class="collection-con">
+          <div class="nodata" v-if="tableData.length<=0">暂无数据</div>
+
+          <ul>
+            <li class="theme-color" @click="gotoShow(item)" v-for="(item,i) in tableData" :key="i">
+              <div class="li-img">
+                <img :src="item.thumb" alt />
+              </div>
+              <div class="li-txt">
+                <span>{{item.typeName}}</span>
+                <span>{{BLOCK[item.block]}} {{ZONE[item.zone]}}</span>
+              </div>
+              <div class="li-name">
+                <div>{{item.name}}</div>
+                <div>
+                  <span  @click.stop="goto(item)">编辑</span>
+                  <span  @click.stop="del(item)" v-if="$role==='admin'">删除</span>
+                </div>
+              </div>
+            </li>
+          </ul>
+        </div>
+        <div class="e-pagination">
+          <el-pagination
+            @current-change="handleCurrentChange"
+            :current-page.sync="currentPage"
+            :page-size="size"
+            layout="prev, pager, next, jumper"
+            :total="total"
+          ></el-pagination>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from '@/components/main-top'
+const crumbData = [
+  {
+    name: '结构信息',
+    id: 4
+  }
+]
+
+
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop,
+  },
+  data () {
+    return {
+      crumbData,
+      tableData: [],
+      inputKey: '',
+      currentPage: 1,
+      size: 12,
+      total: 0,
+      loading: false,
+      plist: [],
+      tlist: [],
+      loutiId:'',
+      zoneId:'',
+      partsId:'',
+    }
+  },
+  watch: {
+    currentPage () {
+      this.refresh()
+    },
+    size () {
+      this.refresh()
+    },
+    inputKey () {
+      this.refresh()
+    }
+  },
+  mounted () {
+    this.refresh()
+  },
+  methods: {
+    del (item) {
+      let ids = item.id
+      this.$confirm('此操作将删除该数据, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.$http.get(`/manage/structure/removes/${ids}`,{headers: {
+          token: window.localStorage.getItem('token')
+        }}).then(res => {
+          if (res.code === 0) {
+            this.$alert('删除成功', '提示', {
+              confirmButtonText: '确定',
+              callback: () => {
+                this.refresh()
+              }
+            })
+          } else {
+            this.$notify.error({
+              title: '错误',
+              message: res.msg
+            })
+          }
+        })
+      }).catch(() => {
+        this.$message({
+          type: 'info',
+          message: '已取消删除'
+        })
+      })
+    },
+    
+    gotoShow (item) {
+      this.$router.push({ name: 'show-structure', params: { id: item.id } })
+    },
+    goto (item) {
+      this.$router.push({ name: 'edit-structure', params: { type: 1,id:item.id } })
+    },
+    refresh () {
+      this.loading = true
+      this.getInformation()
+      this.loading = false
+    },
+    handleCurrentChange (val) {
+      this.currentPage = val
+    },
+    reset(){
+      this.inputKey=''
+      this.partsId=''
+      this.zoneId=''
+      this.loutiId=''
+    },
+    async getInformation () {
+      let params = {
+          block: this.loutiId,
+          pageNum:this.currentPage,
+          pageSize: this.size,
+          searchKey: this.inputKey,
+          typeId: this.partsId,
+          zone: this.zoneId
+      }
+
+      let result = await this.$http({
+        method: 'post',
+        data: params,
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: '/manage/structure/list'
+      })
+
+      if (result.code !== 0) {
+        return
+      }
+      this.tableData = result.data.list
+      this.total = result.data.total
+    }
+  }
+}
+</script>
+
+
+<style lang="less" scoped>
+.top-body{
+  border-top: .0625rem solid #e6e6e6;
+  line-height: 1.5;
+  padding: 0 1.25rem 1.25rem;
+  align-items: center;
+  box-sizing: border-box;
+  background: #fff;
+  margin: 1rem 0;
+}
+
+.top-body .top-con{
+  font-weight: bold;
+}
+.table-title{
+  padding: 1rem 1rem 1rem 0 ;
+  font-size: 1rem;
+  color: #2d2d2d;
+  border-bottom: 1px solid #ccc;
+  display: flex;
+  justify-content: space-between;
+}
+
+.more{
+  color: #ec652d;
+  cursor: pointer;
+}
+
+.top-right{
+  background-color: #ec652d;
+  height: 3.5625rem;
+  line-height: 3.5625rem;
+  color: #fff;
+  padding: 0 1.875rem;
+  border-radius: .3125rem;
+  font-size: 1.125rem;
+  font-weight: bold;
+}
+
+.search-body{
+  background-color: #fff;
+  margin: 1.25rem 0;
+}
+
+.interface-table{
+  height: calc(100% - 17.1875rem);
+  overflow-x: hidden;
+  background-color: #fff;
+  padding: 0 1.125rem 2.375rem;
+  box-sizing: border-box;
+}
+
+.zan-con{
+  display: flex;
+  width: 100%;
+  justify-content: space-around;
+  margin-top: 1.5rem;
+  text-align: center;
+  font-size: 1.125rem;
+}
+
+.zan-con .line{
+  height: 8rem;
+  width: 1px;
+  background: #ccc;
+}
+
+.zan-con .zan-contain{
+  display: flex;
+  justify-content: space-between;
+  flex-direction: column;
+}
+
+.zan-con .zan-contain p{
+  font-size: 2.25rem;
+}
+
+.zan-con .zan-contain p:first-child{
+  font-size: .875rem;
+  line-height: 1.5;
+}
+
+.zan-sub{
+  text-align: right;
+  margin-top: 1.25rem;
+  color: #a5a5a5;
+  font-size: .875rem;
+}
+
+.table-interface{
+  height: calc(100% - 3rem);
+}
+
+
+.info-top{
+  padding: 20px 0;
+  display: flex;
+  justify-content: space-between;
+  border-bottom: 1px #a5a5a5 solid;
+}
+
+.o-span{
+  cursor: pointer;
+  color: rgb(7, 152, 244);
+}
+
+.collection-con{
+  width: 100%;
+  overflow-x: hidden;
+  overflow-y: auto;
+  height: 61vh;
+}
+
+.collection-con ul{
+  display: flex;
+  flex-wrap: wrap;
+  margin-top: 20px;
+}
+
+.collection-con ul li {
+  width: 24%;
+  margin-right: 1%;
+  cursor: pointer;
+  font-size: .8rem;
+  color: #2d2d2d;
+  margin-bottom: 20px;
+}
+
+.collection-con ul .li-img {
+  position: relative;
+  
+}
+.collection-con ul .li-img div{
+  position: absolute;
+  left: 10px;
+  bottom: 10px;
+  color: #fff;
+}
+
+.collection-con ul .li-img div span{
+  margin-right: 10px;
+}
+
+
+.collection-con ul img {
+  width: 100%;
+}
+
+.collection-con{
+  .li-txt,.li-name{
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  .li-name{
+    margin-top: .5rem;
+    div{
+      font-size: 1rem;
+      font-weight: bold;
+      color: #532F1C;
+      &:last-of-type{
+        color: #707070;
+        font-size: .875rem;
+        font-weight: normal;
+        span{
+          margin-left: .30rem;
+          &:last-of-type{
+            &:hover{
+              color: #409EFF;
+            }
+          }
+          &:hover{
+            color: #c56351;
+          }
+        }
+        
+      }
+    }
+  }
+}
+</style>

+ 340 - 0
sh_backstage/src/pages/edit/architecture.vue

@@ -0,0 +1,340 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData">
+      <div slot="con" class="con-btn">
+        <el-button type="primary" @click="$router.back()">返回</el-button>
+      </div>
+    </main-top>
+    <div class="edit-arch">
+      <el-form ref="archform" :rules="rules" :model="form" label-width="100px">
+        <el-form-item label="建筑名称:" prop="name" style="width:318px;">
+          <el-input v-model="form.name" :maxlength="FONTLENGTH" show-word-limit placeholder="请输入建筑名称"></el-input>
+        </el-form-item>
+
+        <el-form-item label="建筑介绍:" prop="description">
+          <el-input
+            type="textarea"
+            :maxlength="MAXLENGTH"
+            show-word-limit
+            v-model="form.description"
+          ></el-input>
+        </el-form-item>
+
+        <el-form-item label="展示内容" style="width:318px;">
+          <el-radio-group v-model="form.displayModel">
+            <el-radio :label="0">模型链接</el-radio>
+            <el-radio :label="2">动画视频</el-radio>
+          </el-radio-group>
+          <template v-if="form.displayModel === 2">
+            <el-upload
+              class="avatar-uploader"
+              :action="uploadUrl"
+              :headers="{
+                token,
+              }"
+              :show-file-list="false"
+              :on-success="handleVideoSuccess"
+              :before-upload="beforeVideoUpload"
+            >
+              <i class="el-icon-plus avatar-uploader-icon"></i>
+            </el-upload>
+            <el-input style="width:618px;" disabled placeholder="请输入链接" v-model="form.video"></el-input>
+          </template>
+          <el-input  style="width:418px;" v-else placeholder="请输入链接" v-model="form.webSite"></el-input>
+        </el-form-item>
+
+        <el-form-item label="展示封面">
+          <el-upload
+            class="avatar-uploader"
+            :action="uploadUrl"
+            :headers="{
+              token,
+            }"
+            :show-file-list="false"
+            :on-success="handleAvatarSuccess"
+            :before-upload="beforeAvatarUpload"
+          >
+            <img v-if="form.ico" :src="form.ico" class="avatar" />
+            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
+          </el-upload>
+          <span style="color:#999">建议图片尺寸为316*200</span>
+        </el-form-item>
+
+        <el-form-item label="多媒体资料">
+          <el-upload
+            class="upload-demo"
+            drag
+            :action="uploadUrl"
+            :headers="{
+              token,
+            }"
+            :file-list="form.fileList"
+            multiple
+            :before-upload="beforeMultipleUpload"
+            :on-success="handleSuccess"
+            :on-remove="handleRemove"
+          >
+            <i class="el-icon-upload"></i>
+            <div class="el-upload__text">
+              将文件拖到此处,或
+              <em>点击上传</em>
+              <p>
+                支持视频、音频、图片格式:.jpg,.jpgc,.png,.wma,.wav,.mp4,.mp3,大小不得超过10m
+              </p>
+            </div>
+          </el-upload>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button @click="save" type="primary">保存</el-button>
+          <el-button @click="$router.back()">取消</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from "@/components/main-top";
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop,
+  },
+  data() {
+    const crumbData = [
+      {
+        name: "建筑信息",
+        id: 0,
+      },
+      {
+        name: this.$route.params.type ? "编辑建筑" : "新增建筑",
+        id: 1,
+      },
+    ];
+    return {
+      crumbData,
+      form: {
+        description: "",
+        fileIds: "",
+        ico: "",
+        id: 0,
+        name: "",
+        webSite: "",
+        displayModel:"",
+        video:""
+      },
+      id: this.$route.params.type,
+      token: window.localStorage.getItem("token"),
+      uploadUrl: `${this.$serverName}manage/common/upload`,
+      rules: {
+        name: [{ required: true, message: "请选择建筑名称", trigger: "blur" }],
+        description: [
+          { required: true, message: "请输入建筑介绍", trigger: "blur" },
+        ],
+        webSite: [
+          { required: true, message: "请输入模型链接", trigger: "blur" },
+        ],
+      },
+    };
+  },
+  methods: {
+    handlePictureCardPreview() {},
+    handleRemove(file) {
+     let key = "";
+
+      if (file.response) {
+        let { data } = file.response;
+         key = Object.keys(data)[0];
+      } else {
+        key = file.id;
+      }
+
+      this.form.fileList = this.form.fileList.filter(
+        (item) => key !== item.id
+      );
+    },
+
+    handleSuccess(res) {
+      let { data } = res;
+      let key = Object.keys(data)[0];
+      let val = data[key];
+      this.form.fileList.push({
+        url: val,
+        name: val.substring(val.lastIndexOf("/") + 1),
+        id: key,
+      });
+    },
+    handleAvatarSuccess(res) {
+      let { data } = res;
+      let key = Object.keys(data)[0];
+      let val = data[key];
+
+      this.form.ico = val;
+    },
+    beforeMultipleUpload(file){
+      const isLt2M = file.size / 1024 / 1024 < 10;
+
+      if (!isLt2M) {
+        this.$message.error("上传文件大小不能超过 10MB!");
+      }
+      return isLt2M;
+    },
+    beforeAvatarUpload(file) {
+      const isJPG = file.type === "image/jpeg";
+      const isLt2M = file.size / 1024 / 1024 < 2;
+
+      if (!isJPG) {
+        this.$message.error("上传头像图片只能是 JPG 格式!");
+      }
+      if (!isLt2M) {
+        this.$message.error("上传头像图片大小不能超过 2MB!");
+      }
+      return isJPG && isLt2M;
+    },
+    async save() {
+      this.$refs["archform"].validate(async (valid) => {
+        if (valid) {
+          if (!this.form.video && !this.form.webSite) {
+            return this.$notify.error({
+              title: "错误",
+              message: "展示内容不能为空",
+            });
+          }
+          if (this.form.description.length > this.MAXLENGTH) {
+            return this.$notify.error({
+              title: "错误",
+              message: `建筑介绍内容不能超过${this.MAXLENGTH}字`,
+            });
+          }
+          let { description, ico, id, name,video,displayModel, webSite } = this.form;
+          let fileIds = this.form.fileList.map((item) => item.id).join(",");
+          let params = {
+            description,
+            fileIds,
+            ico,
+            id,
+            name,
+            webSite,
+            video,
+            displayModel
+          };
+          let result = await this.$http({
+            method: "post",
+            data: params,
+            headers: {
+              token: window.localStorage.getItem("token"),
+            },
+            url: `/manage/building/save`,
+          });
+
+          if (result.code === 0) {
+            this.$alert("保存成功", "提示", {
+              confirmButtonText: "确定",
+              callback: () => {
+                this.$router.back();
+              },
+            });
+          } else {
+            this.$notify.error({
+              title: "错误",
+              message: result.msg,
+            });
+          }
+        }
+      });
+    },
+    async getDetail() {
+      let result = await this.$http({
+        method: "get",
+        headers: {
+          token: window.localStorage.getItem("token"),
+        },
+        url: `/manage/building/detail/${this.id}`,
+      });
+
+      this.form = result.data.building;
+
+      this.form.fileList = result.data.file.map((item) => {
+        return {
+          name: item.fileName,
+          url: item.filePath,
+          id: item.id,
+        };
+      });
+    },
+    handleVideoSuccess(res) {
+      let { data } = res;
+      let key = Object.keys(data)[0];
+      let val = data[key];
+
+      this.form.video = val;
+    },
+    beforeVideoUpload(file) {
+      console.log(file.type);
+      
+      const isMp4 = file.type === "video/mp4";
+      const isLt10M = file.size / 1024 / 1024 < 10;
+
+      if (!isMp4) {
+        this.$message.error("上传视频只能是 mp4 格式!");
+      }
+      if (!isLt10M) {
+        this.$message.error("上传视频大小不能超过 10MB!");
+      }
+      return isMp4 && isLt10M;
+    }
+  },
+  mounted() {
+    if (this.id && this.id !== "none") {
+      this.getDetail();
+    }
+    else{
+      setTimeout(() => {
+        this.form.fileList = []
+      });
+    }
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.edit-arch {
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem - 20px);
+  background-color: #fff;
+  padding: 20px 40px 20px 20px;
+}
+</style>
+
+<style>
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #409eff;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 178px;
+  height: 178px;
+  line-height: 178px;
+  text-align: center;
+}
+.avatar {
+  width: 178px;
+  /* height: 178px; */
+  display: block;
+}
+</style>

+ 408 - 0
sh_backstage/src/pages/edit/parts.vue

@@ -0,0 +1,408 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData">
+      <div slot="con" class="con-btn">
+        <el-button type="primary" @click="$router.back()">返回</el-button>
+      </div>
+    </main-top>
+    <div class="edit-arch">
+      <el-form ref="partform" :rules="rules" :model="form" label-width="120px">
+        <el-form-item label="部件名称" style="width:318px;" prop="name">
+          <el-input placeholder="请输入部件名称" :maxlength="FONTLENGTH*0.6" show-word-limit v-model="form.name"></el-input>
+        </el-form-item>
+
+        <el-form-item label="部件类别" prop="typeId">
+          <el-select v-model="form.typeId" placeholder="请选择部件类别">
+            <el-option
+              v-for="(item, i) in partsArr"
+              :key="i"
+              :label="item.name"
+              :value="item.id"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="所属楼体" prop="block">
+          <el-select v-model="form.block" placeholder="请选择所属楼体">
+            <el-option
+              v-for="(item, i) in loutiArr"
+              :key="i"
+              :label="item.name"
+              :value="item.id"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="部件位置" prop="zone">
+          <el-select v-model="form.zone" placeholder="请选择部件位置">
+            <el-option
+              v-for="(item, i) in zoneArr"
+              :key="i"
+              :label="item.name"
+              :value="item.id"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="部件介绍">
+            <el-input type="textarea" :maxlength="MAXLENGTH*0.25" show-word-limit v-model="form.content"></el-input>
+        </el-form-item>
+
+        <el-form-item label="部件材质" style="width:318px;">
+          <el-input
+            placeholder="请输入部件材质"
+            v-model="form.material"
+            :maxlength="5" 
+            show-word-limit
+          ></el-input>
+        </el-form-item>
+
+        <el-form-item label="展示内容" style="width:318px;">
+          <el-radio-group v-model="form.displayModel">
+            <el-radio :label="2">照片</el-radio>
+            <el-radio :label="0">模型链接</el-radio>
+          </el-radio-group>
+          <template v-if="form.displayModel !== ''">
+            <el-upload
+              v-if="form.displayModel === 2"
+              class="avatar-uploader"
+              :action="uploadUrl"
+              :headers="{
+                token,
+              }"
+              :show-file-list="false"
+              :on-success="handleAvatarSuccess"
+              :before-upload="beforeAvatarUpload"
+            >
+              <img v-if="form.ico" :src="form.ico" class="avatar" />
+              <i v-else class="el-icon-plus avatar-uploader-icon"></i>
+            </el-upload>
+            <el-input
+              v-else
+              placeholder="请输入模型链接"
+              v-model="form.webSite"
+            ></el-input>
+          </template>
+        </el-form-item>
+
+        <el-form-item label="展示封面" prop="thumb">
+          <el-upload
+            class="avatar-uploader"
+            :action="uploadUrl"
+            :headers="{
+              token,
+            }"
+            :show-file-list="false"
+            :on-success="handleThumbSuccess"
+            :before-upload="beforeAvatarUpload"
+          >
+            <img v-if="form.thumb" :src="form.thumb" class="avatar" />
+            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
+          </el-upload>
+          <span style="color:#999">建议图片尺寸为316*200</span>
+        </el-form-item>
+
+        <el-form-item label="多媒体资料">
+          <el-upload
+            class="upload-demo"
+            drag
+            :action="uploadUrl"
+            :headers="{
+              token,
+            }"
+            :file-list="form.fileList"
+            multiple
+            :before-upload="beforeMultipleUpload"
+            :on-success="handleSuccess"
+            :on-remove="handleRemove"
+          >
+            <i class="el-icon-upload"></i>
+            <div class="el-upload__text">
+              将文件拖到此处,或
+              <em>点击上传</em>
+              <p>
+                支持视频、音频、图片格式:.jpg,.jpgc,.png,.wma,.wav,.mp4,.mp3,大小不得超过10m
+              </p>
+            </div>
+          </el-upload>
+        </el-form-item>
+
+        <el-form-item label="是否在程序显示">
+          <el-radio-group v-model="form.display">
+            <el-radio :label="1">是</el-radio>
+            <el-radio :label="0">否</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button @click="save" type="primary">保存</el-button>
+          <el-button @click="$router.back()">取消</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from "@/components/main-top";
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop
+  },
+  data() {
+    const crumbData = [
+      {
+        name: "部件信息",
+        id: 0,
+      },
+      {
+        name: this.$route.params.type ? "编辑部件" : "新增部件",
+        id: 1,
+      },
+    ];
+    
+    return {
+      crumbData,
+      form: {
+        content: "",
+        fileIds: "",
+        thumb: "",
+        id: "",
+        name: "",
+        webSite: "",
+        display: 1,
+        ico: "",
+        typeId: "",
+        displayModel: 0,
+        zone: "",
+        material: "",
+        block: ""
+      },
+      type: this.$route.params.type,
+      id: this.$route.params.id,
+      token: window.localStorage.getItem("token"),
+      uploadUrl: `${this.$serverName}manage/common/upload`,
+      rules: {
+        name: [{ required: true, message: "请选择部件名称", trigger: "blur" }],
+        typeId: [
+          { required: true, message: "请选择部件类别", trigger: "change" },
+        ],
+        block: [
+          { required: true, message: "请选择所属楼体", trigger: "change" },
+        ],
+        zone: [{ required: true, message: "请选择部件位置", trigger: "blur" }],
+        thumb: [{ required: true, message: "请选择展示封面", trigger: "blur" }],
+      },
+    };
+  },
+  methods: {
+    handlePictureCardPreview() {},
+
+    handleRemove(file) {
+      let key = "";
+
+      if (file.response) {
+        let { data } = file.response;
+         key = Object.keys(data)[0];
+      } else {
+        key = file.id;
+      }
+
+      this.form.fileList = this.form.fileList.filter(
+        (item) => key !== item.id
+      );
+    },
+
+    handleSuccess(res) {
+      let { data } = res;
+      let key = Object.keys(data)[0];
+      let val = data[key];
+      this.form.fileList.push({
+        url: val,
+        name: val.substring(val.lastIndexOf("/") + 1),
+        id: key,
+      });
+    },
+
+    handleThumbSuccess(res) {
+      let { data } = res;
+      let key = Object.keys(data)[0];
+      let val = data[key];
+
+      this.form.thumb = val;
+    },
+    handleAvatarSuccess(res) {
+      let { data } = res;
+      let key = Object.keys(data)[0];
+      let val = data[key];
+
+      this.form.ico = val;
+    },
+    beforeMultipleUpload(file){
+      const isLt2M = file.size / 1024 / 1024 < 10;
+
+      if (!isLt2M) {
+        this.$message.error("上传文件大小不能超过 10MB!");
+      }
+      return  isLt2M;
+    },
+    beforeAvatarUpload(file) {
+      const isJPG = file.type === "image/jpeg";
+      const isLt2M = file.size / 1024 / 1024 < 2;
+
+      if (!isJPG) {
+        this.$message.error("上传头像图片只能是 JPG 格式!");
+      }
+      if (!isLt2M) {
+        this.$message.error("上传头像图片大小不能超过 2MB!");
+      }
+      return isJPG && isLt2M;
+    },
+
+    save() {
+      this.$refs["partform"].validate(async (valid) => {
+        console.log(valid);
+
+        if (valid) {
+          if (!this.form.ico && !this.form.webSite) {
+            return this.$notify.error({
+              title: "错误",
+              message: "展示内容不能为空",
+            });
+          }
+          if (this.form.content.length > this.MAXLENGTH) {
+            return this.$notify.error({
+              title: "错误",
+              message: `部件介绍内容不能超过${this.MAXLENGTH}字`,
+            });
+          }
+          
+          let {
+            content,
+            ico,
+            id = "",
+            typeId,
+            zone,
+            material,
+            block,
+            displayModel,
+            thumb,
+            name,
+            webSite,
+            display,
+          } = this.form;
+          let fileIds = this.form.fileList.map((item) => item.id).join(",");
+          let params = {
+            content,
+            fileIds,
+            ico,
+            id,
+            name,
+            webSite,
+            display,
+            thumb,
+            typeId,
+            displayModel,
+            zone,
+            material,
+            block,
+          };
+          let result = await this.$http({
+            method: "post",
+            data: params,
+            headers: {
+              token: window.localStorage.getItem("token"),
+            },
+            url: `/manage/part/save`,
+          });
+
+          if (result.code === 0) {
+            this.$alert("保存成功", "提示", {
+              confirmButtonText: "确定",
+              callback: () => {
+                this.$router.back();
+              },
+            });
+          } else {
+            this.$notify.error({
+              title: "错误",
+              message: result.msg,
+            });
+          }
+        }
+      });
+    },
+    async getDetail() {
+      let result = await this.$http({
+        method: "get",
+        headers: {
+          token: window.localStorage.getItem("token"),
+        },
+        url: `/manage/part/detail/${this.id}`,
+      });
+
+      this.form = result.data.part;
+
+      this.form.fileList = result.data.file.map((item) => {
+        return {
+          name: item.fileName,
+          url: item.filePath,
+          id: item.id,
+        };
+      });
+    },
+  },
+  mounted() {
+    
+    if (this.id && this.id !== "none") {
+      this.getDetail();
+    }
+    else{
+      setTimeout(() => {
+        this.form.fileList = []
+      });
+    }
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.edit-arch {
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem - 20px);
+  background-color: #fff;
+  padding: 20px 40px 20px 20px;
+}
+</style>
+<style>
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #409eff;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 178px;
+  height: 178px;
+  line-height: 178px;
+  text-align: center;
+}
+.avatar {
+  width: 178px;
+  /* height: 178px; */
+  display: block;
+}
+</style>

+ 335 - 0
sh_backstage/src/pages/edit/roam.vue

@@ -0,0 +1,335 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData">
+      <div slot="con" class="con-btn">
+        <el-button type="primary" @click="$router.back()">返回</el-button>
+      </div>
+    </main-top>
+    <div class="edit-arch">
+      <el-form ref="roamform" :rules="rules" :model="form" label-width="120px">
+        <el-form-item label="漫游名称" prop="name" style="width:318px;">
+          <el-input v-model="form.name" :maxlength="FONTLENGTH" show-word-limit></el-input>
+        </el-form-item>
+
+        <el-form-item label="漫游类别" prop="typeId">
+          <el-select v-model="form.typeId" placeholder="请选择漫游类别">
+            <el-option
+              v-for="(item, i) in roamArr"
+              :key="i"
+              :label="item.name"
+              :value="item.id"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="所属楼体" prop="block">
+          <el-select v-model="form.block" placeholder="请选择所属楼体">
+            <el-option
+              v-for="(item, i) in loutiArr"
+              :key="i"
+              :label="item.name"
+              :value="item.id"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="漫游位置" prop="zone">
+          <el-select v-model="form.zone" placeholder="请选择漫游位置">
+            <el-option
+              v-for="(item, i) in zoneArr"
+              :key="i"
+              :label="item.name"
+              :value="item.id"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="漫游介绍">
+            <el-input type="textarea" :maxlength="MAXLENGTH" show-word-limit v-model="form.content"></el-input>
+        </el-form-item>
+
+        <el-form-item label="展示内容" style="width:618px;">
+          <el-radio-group v-model="form.displayModel">
+            <el-radio :label="0">模型链接</el-radio>
+            <el-radio :label="1">场景链接</el-radio>
+            <el-radio :label="2">动画视频</el-radio>
+          </el-radio-group>
+          <template v-if="form.displayModel === 2">
+            <el-upload
+              class="avatar-uploader"
+              :action="uploadUrl"
+              :headers="{
+                token,
+              }"
+              :show-file-list="false"
+              :on-success="handleVideoSuccess"
+              :before-upload="beforeVideoUpload"
+            >
+              <i class="el-icon-plus avatar-uploader-icon"></i>
+            </el-upload>
+            <el-input style="width:618px;" disabled placeholder="请输入链接" v-model="form.video"></el-input>
+          </template>
+          <el-input  style="width:418px;" v-else placeholder="请输入链接" v-model="form.webSite"></el-input>
+        </el-form-item>
+
+        <el-form-item label="展示封面" prop="thumb">
+          <el-upload
+            class="avatar-uploader"
+            :action="uploadUrl"
+            :headers="{
+              token,
+            }"
+            :show-file-list="false"
+            :on-success="handleAvatarSuccess"
+            :before-upload="beforeAvatarUpload"
+          >
+            <img v-if="form.thumb" :src="form.thumb" class="avatar" />
+            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
+          </el-upload>
+          <span style="color:#999">建议图片尺寸为316*200</span>
+        </el-form-item>
+
+        <el-form-item label="是否在程序显示">
+          <el-radio-group v-model="form.display">
+            <el-radio :label="1">是</el-radio>
+            <el-radio :label="0">否</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button @click="save" type="primary">保存</el-button>
+          <el-button @click="$router.back()">取消</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from "@/components/main-top";
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop
+  },
+  data() {
+    const crumbData = [
+      {
+        name: "漫游信息",
+        id: 0,
+      },
+      {
+        name: this.$route.params.type ? "编辑漫游" : "新增漫游",
+        id: 1,
+      },
+    ];
+
+    var validateName = (rule, value, callback) => {
+        if (value === '') {
+          callback(new Error('请输入漫游名称'));
+        } else if (value > this.MAXLENGTH/2) {
+          callback(new Error(`漫游名称不能超过${this.MAXLENGTH/2}字`));
+        } else {
+          callback();
+        }
+      };
+    return {
+      crumbData,
+      form: {
+        block: "",
+        content: "",
+        display: 1,
+        displayModel: "",
+        video:'',
+        id: "",
+        thumb: "",
+        name: "",
+        typeId: "",
+        webSite: "",
+        zone: "",
+      },
+      type: this.$route.params.type,
+      id: this.$route.params.id,
+      token: window.localStorage.getItem("token"),
+      uploadUrl: `${this.$serverName}manage/common/upload`,
+      rules: {
+        name: [{required: true, validator: validateName, trigger: "blur" }],
+        typeId: [
+          { required: true, message: "请选择漫游类别", trigger: "change" },
+        ],
+        block: [
+          { required: true, message: "请选择所属楼体", trigger: "change" },
+        ],
+        zone: [{ required: true, message: "请选择漫游位置", trigger: "blur" }],
+        thumb: [{ required: true, message: "请选择展示封面", trigger: "blur" }],
+        webSite: [{ required: true, message: "请选择url", trigger: "blur" }],
+      },
+    };
+  },
+  methods: {
+    async save() {
+      this.$refs["roamform"].validate(async (valid) => {
+        if (valid) {
+          if (!this.form.video && !this.form.webSite) {
+            return this.$notify.error({
+              title: "错误",
+              message: "展示内容不能为空",
+            });
+          }
+          if (this.form.content.length > this.MAXLENGTH) {
+            return this.$notify.error({
+              title: "错误",
+              message: `漫游介绍内容不能超过${this.MAXLENGTH}字`,
+            });
+          }
+          let {
+            block,
+            content,
+            display,
+            displayModel,
+            id = "",
+            thumb,
+            name,
+            typeId,
+            webSite,
+            video,
+            zone,
+          } = this.form;
+          let params = {
+            block,
+            content,
+            display,
+            displayModel,
+            id,
+            thumb,
+            name,
+            typeId,
+            webSite,
+            video,
+            zone,
+          };
+          let result = await this.$http({
+            method: "post",
+            data: params,
+            headers: {
+              token: window.localStorage.getItem("token"),
+            },
+            url: `/manage/roam/save`,
+          });
+
+          if (result.code === 0) {
+            this.$alert("保存成功", "提示", {
+              confirmButtonText: "确定",
+              callback: () => {
+                this.$router.back();
+              },
+            });
+          } else {
+            this.$notify.error({
+              title: "错误",
+              message: result.msg,
+            });
+          }
+        }
+      });
+    },
+    async getDetail() {
+      let result = await this.$http({
+        method: "get",
+        headers: {
+          token: window.localStorage.getItem("token"),
+        },
+        url: `/manage/roam/detail/${this.id}`,
+      });
+
+      this.form = result.data;
+    },
+    handleAvatarSuccess(res) {
+      let { data } = res;
+      let key = Object.keys(data)[0];
+      let val = data[key];
+
+      this.form.thumb = val;
+    },
+    beforeAvatarUpload(file) {
+      const isJPG = file.type === "image/jpeg";
+      const isLt2M = file.size / 1024 / 1024 < 2;
+
+      if (!isJPG) {
+        this.$message.error("上传头像图片只能是 JPG 格式!");
+      }
+      if (!isLt2M) {
+        this.$message.error("上传头像图片大小不能超过 2MB!");
+      }
+      return isJPG && isLt2M;
+    },
+    handleVideoSuccess(res) {
+      let { data } = res;
+      let key = Object.keys(data)[0];
+      let val = data[key];
+
+      this.form.video = val;
+    },
+    beforeVideoUpload(file) {
+      console.log(file.type);
+      
+      const isMp4 = file.type === "video/mp4";
+      const isLt10M = file.size / 1024 / 1024 < 10;
+
+      if (!isMp4) {
+        this.$message.error("上传视频只能是 mp4 格式!");
+      }
+      if (!isLt10M) {
+        this.$message.error("上传视频大小不能超过 10MB!");
+      }
+      return isMp4 && isLt10M;
+    }
+  },
+  mounted() {
+    if (this.id && this.id !== "none") {
+      this.getDetail();
+    }
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.edit-arch {
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem - 20px);
+  background-color: #fff;
+  padding: 20px 40px 20px 20px;
+}
+</style>
+
+<style>
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #409eff;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 178px;
+  height: 178px;
+  line-height: 178px;
+  text-align: center;
+}
+.avatar {
+  width: 178px;
+  /* height: 178px; */
+  display: block;
+}
+</style>

+ 235 - 0
sh_backstage/src/pages/edit/structure.vue

@@ -0,0 +1,235 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData">
+      <div slot="con" class="con-btn">
+        <el-button type="primary" @click="$router.back()">返回</el-button>
+      </div>
+    </main-top>
+    <div class="edit-arch">
+      <el-form ref="form" :model="form" label-width="120px">
+        <el-form-item
+          label="模型名称"
+          :rules="[
+              { required: true, message: '模型名称不能为空'}
+            ]"
+          style="width:318px;"
+        >
+          <el-input placeholder="请输入模型名称" :maxlength="FONTLENGTH" show-word-limit v-model="form.name"></el-input>
+        </el-form-item>
+
+        <el-form-item label="所属楼体" :rules="[
+              { required: true}
+            ]">
+          <el-select v-model="form.block" placeholder="请选择所属楼体">
+            <el-option v-for="(item,i) in loutiArr" :key="i" :label="item.name" :value="item.id"></el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="模型介绍">
+          <el-input type="textarea" :maxlength="MAXLENGTH" show-word-limit v-model="form.content"></el-input>
+        </el-form-item>
+
+        <el-form-item label="展示封面">
+          <el-upload
+            class="avatar-uploader"
+            :action="uploadUrl"
+            :headers="{
+              token,
+            }"
+            :show-file-list="false"
+            :on-success="handleAvatarSuccess"
+            :before-upload="beforeAvatarUpload"
+          >
+            <img v-if="form.thumb" :src="form.thumb" class="avatar" />
+            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
+          </el-upload>
+          <span style="color:#999">建议图片尺寸为316*200</span>
+        </el-form-item>
+
+        <el-form-item label="模型链接" style="width:318px;">
+          <el-input v-model="form.webSite"></el-input>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button @click="save" type="primary">保存</el-button>
+          <el-button @click="$router.back()">取消</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from "@/components/main-top";
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop
+  },
+  data() {
+    const crumbData = [
+      {
+        name: "结构模型信息",
+        id: 0
+      },
+      {
+        name: this.$route.params.type ? "编辑结构模型" : "新增结构模型",
+        id: 1
+      }
+    ];
+    return {
+      crumbData,
+      form: {
+        block:"",
+        content:"",
+        display:"",
+        displayModel:"",
+        thumb:'',
+        id:"",
+        name:"",
+        typeId:"",
+        webSite:"",
+        zone:""
+      },
+      type: this.$route.params.type,
+      id: this.$route.params.id,
+      radio: 1,
+      token: window.localStorage.getItem("token"),
+      uploadUrl: `${this.$serverName}manage/common/upload`,
+    };
+  },
+  methods: {
+     handleAvatarSuccess(res) {
+      let {data} = res
+      let key = Object.keys(data)[0]
+      let val = data[key]
+      
+      this.form.thumb = val;
+    },
+    beforeAvatarUpload(file) {
+      const isJPG = file.type === "image/jpeg";
+      const isLt2M = file.size / 1024 / 1024 < 2;
+
+      if (!isJPG) {
+        this.$message.error("上传头像图片只能是 JPG 格式!");
+      }
+      if (!isLt2M) {
+        this.$message.error("上传头像图片大小不能超过 2MB!");
+      }
+      return isJPG && isLt2M;
+    },
+    async save() {
+      if (this.form.content.length > this.MAXLENGTH) {
+        return this.$notify.error({
+          title: "错误",
+          message: `结构介绍内容不能超过${this.MAXLENGTH}字`,
+        });
+      }
+      let {
+        block,
+        content,
+        display,
+        displayModel,
+        id = "",
+        name,
+        thumb,
+        typeId,
+        webSite,
+        zone
+      } = this.form;
+      let params = {
+        block,
+        content,
+        display,
+        displayModel,
+        id,
+        name,
+        typeId,
+        thumb,
+        webSite,
+        zone
+      };
+      let result = await this.$http({
+        method: "post",
+        data: params,
+        headers: {
+          token: window.localStorage.getItem("token")
+        },
+        url: `/manage/structure/save`
+      });
+
+      if (result.code === 0) {
+        this.$alert("保存成功", "提示", {
+          confirmButtonText: "确定",
+          callback: () => {
+            this.$router.back();
+          }
+        });
+      } else {
+        this.$notify.error({
+          title: "错误",
+          message: result.msg
+        });
+      }
+    },
+    async getDetail() {
+      let result = await this.$http({
+        method: "get",
+        headers: {
+          token: window.localStorage.getItem("token")
+        },
+        url: `/manage/structure/detail/${this.id}`
+      });
+
+      this.form = result.data;
+    }
+  },
+  mounted() {
+    if (this.id&&this.id!=='none') {
+      this.getDetail();
+    }
+  }
+};
+</script>
+
+<style lang="less" scoped>
+
+.edit-arch {
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem - 20px);
+  background-color: #fff;
+  padding: 20px 40px 20px 20px;
+}
+</style>
+
+<style>
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #409eff;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 178px;
+  height: 178px;
+  line-height: 178px;
+  text-align: center;
+}
+.avatar {
+  width: 178px;
+  /* height: 178px; */
+  display: block;
+}
+</style>

+ 36 - 0
sh_backstage/src/pages/home/index.vue

@@ -0,0 +1,36 @@
+<template>
+  <div>
+    <div class="home-layout">
+      <video src="https://4d-tjw.oss-cn-shenzhen.aliyuncs.com/shls_museum/video/Final_new_web.mp4" autoplay loop></video>
+    </div>
+  </div>
+</template>
+
+<script>
+
+const crumbData = [
+  {
+    name: '首页',
+    id: 1
+  }
+]
+export default {
+  data(){
+    return {
+      crumbData
+    }
+  }
+}
+</script>
+
+
+<style lang="less" scoped>
+.home-layout{
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: 100%;
+  video{
+    width: 100%;
+  }
+}
+</style>

+ 143 - 0
sh_backstage/src/pages/layout/aside.vue

@@ -0,0 +1,143 @@
+<!--  -->
+<template>
+<div class="aside">
+  <div class="aside-list">
+    <div class="aside-item">
+      <div :class="{active:activeIdx === -1}" @click="go(-1,'/')"><i class="iconfont iconsys_nav_index"></i>首页</div>
+      <!-- <div @click="go(0,'/message')" :class="{active:activeIdx === 0}">消息管理</div> -->
+    </div>
+    <div class="aside-item">
+      <div :class="{active:activeIdx>=2&&activeIdx<=6}"><i class="iconfont iconsys_nav_work"></i>内容管理</div>
+      <div @click="go(2,'/architecture')" :class="{activeFont:activeIdx === 2}">建筑信息管理</div>
+      <div @click="go(3,'/parts')" :class="{activeFont:activeIdx === 3}">部件信息管理</div>
+      <div @click="go(4,'/roam')" :class="{activeFont:activeIdx === 4}">漫游信息管理</div>
+      <div @click="go(5,'/structure')" :class="{activeFont:activeIdx === 5}">结构模型管理</div>
+      <div @click="go(6,'/comment')" v-if="$role === 'admin'" :class="{activeFont:activeIdx === 6}">观众评论管理</div>
+      
+    </div>
+    <div class="aside-item" >
+      <div :class="{active:activeIdx>=7&&activeIdx<=10}"><i class="iconfont iconsys_nav_system"></i>系统管理</div>
+      <div @click="go(7,'/user')" v-if="$role === 'admin'" :class="{activeFont:activeIdx === 7}">用户管理</div>
+      <div @click="go(8,'/download')" :class="{activeFont:activeIdx === 8}">下载管理</div>
+      <div @click="go(9,'/worklog')" v-if="$role === 'admin'" :class="{activeFont:activeIdx === 9}">工作日志</div>
+      <div @click="go(10,'/password')" :class="{activeFont:activeIdx === 10}">密码修改</div>
+    </div>
+  </div>
+</div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {},
+  data () {
+    // 这里存放数据
+    return {
+      // activeIdx: 0
+    }
+  },
+  // 监听属性 类似于data概念
+  computed: {
+    activeIdx: {
+      get: function () {
+        return this.$route.meta.index
+      },
+      set: function () {
+      }
+    }
+  },
+  // 监控data中的数据变化
+  watch: {
+
+  },
+  // 方法集合
+  methods: {
+    go (id, url) {
+      this.activeIdx = this.$route.meta.index
+      this.$router.push(url)
+    }
+  },
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created () {
+
+  },
+  // 生命周期 - 挂载完成(可以访问DOM元素)
+  mounted () {
+
+  }
+
+}
+</script>
+
+<style scoped>
+.aside-list{
+  width: 100%;
+  padding-top: 1.5rem;
+}
+.aside-item {
+  padding: .625rem 0;
+  text-align: center;
+}
+
+
+.aside-item:last-child{
+  border-bottom:none;
+}
+
+.aside-item div{
+  height: 2.875rem;
+  line-height: 2.875rem;
+  margin: .0625rem 0;
+  width: 89%;
+  padding-left: 5.7rem;
+  font-size: .875rem;
+  cursor: pointer;
+  color: #707070;
+  text-align: left;
+}
+.aside-item div:not(:first-child):hover{
+  color: #B63C25;
+}
+
+.aside-item div:first-child{
+  padding-left: 3.81rem;
+  display: flex;
+  align-items: center;
+  color: #532F1C;
+  font-size: 1.25rem;
+  font-weight: bold;
+  margin-bottom: 0.2rem;
+}
+
+.aside-item div:first-child img{
+  width: 1.1rem;
+  height: 1.1rem;
+  margin-right: .625rem;
+}
+
+.aside-item .iconfont{
+  color: #532F1C;
+  margin-right: .8125rem;
+}
+
+.aside-item .active{
+  background-color: #B63C25;
+  border-top-right-radius: 80px;
+  border-bottom-right-radius: 80px;
+  color: #F2ECDE!important;
+}
+
+.aside-item .active .iconfont{
+  color: #F2ECDE;
+}
+
+
+.aside-item .activeFont{
+  color: #B63C25;
+}
+
+
+</style>

+ 47 - 0
sh_backstage/src/pages/layout/footer.vue

@@ -0,0 +1,47 @@
+<!--  -->
+<template>
+<div class=''></div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+export default {
+// import引入的组件需要注入到对象中才能使用
+  components: {},
+  data () {
+    // 这里存放数据
+    return {
+
+    }
+  },
+  // 监听属性 类似于data概念
+  computed: {},
+  // 监控data中的数据变化
+  watch: {},
+  // 方法集合
+  methods: {
+
+  },
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created () {
+
+  },
+  // 生命周期 - 挂载完成(可以访问DOM元素)
+  mounted () {
+
+  },
+  beforeCreate () {}, // 生命周期 - 创建之前
+  beforeMount () {}, // 生命周期 - 挂载之前
+  beforeUpdate () {}, // 生命周期 - 更新之前
+  updated () {}, // 生命周期 - 更新之后
+  beforeDestroy () {}, // 生命周期 - 销毁之前
+  destroyed () {}, // 生命周期 - 销毁完成
+  activated () {} // 如果页面有keep-alive缓存功能,这个函数会触发
+}
+</script>
+<style scoped>
+/* 引入公共css类 */
+
+</style>

+ 140 - 0
sh_backstage/src/pages/layout/head.vue

@@ -0,0 +1,140 @@
+<!--  -->
+<template>
+<div class='header card'>
+  <div class="header-title">
+    <img src="@/assets/img/4dage-logo.png" alt="">
+    <!-- <span>大屏后台管理系统</span> -->
+  </div>
+  <div class="header-user">
+    <div class="avatar">
+      <img :src="head" alt="">
+      <span>{{userName}}</span>
+    </div>
+    <div  @click="logout" class="logout"><img src="@/assets/img/logout.png" alt="">退出</div>
+  </div>
+</div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {},
+  data () {
+    return {
+      head: '',
+      userName:''
+    }
+  },
+  // 监控data中的数据变化
+  watch: {},
+  // 方法集合
+  methods: {
+    logout () {
+      this.$http({
+        method: 'get',
+        url: '/admin/logout',
+        headers: {
+          token: window.localStorage.getItem('token')
+        }
+      }).then(res => {
+        if (res.code === 0) {
+          window.localStorage.setItem('token', '')
+          this.$alert('退出成功', '提示', {
+            confirmButtonText: '确定',
+            callback: () => {
+              window.localStorage.setItem('userInfo', '')
+              this.$router.push('/login')
+            }
+          })
+        }
+      })
+    }
+  },
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created () {
+
+  },
+  // 生命周期 - 挂载完成(可以访问DOM元素)
+  mounted () {
+    let userInfo = JSON.parse(window.localStorage.getItem('userInfo')) || ''
+    this.userName = userInfo.userName
+    this.head = userInfo.ico
+
+    // this.updateInfo()
+  },
+  beforeCreate () {}, // 生命周期 - 创建之前
+  beforeMount () {}, // 生命周期 - 挂载之前
+  beforeUpdate () {}, // 生命周期 - 更新之前
+  updated () {}, // 生命周期 - 更新之后
+  beforeDestroy () {}, // 生命周期 - 销毁之前
+  destroyed () {}, // 生命周期 - 销毁完成
+  activated () {} // 如果页面有keep-alive缓存功能,这个函数会触发
+}
+</script>
+
+<style scoped>
+.header {
+  height: 4.25rem;
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  line-height: 4.25rem;
+  padding: 0 1.875rem;
+  box-sizing: border-box;
+}
+
+.header-title {
+  color: #b9b47f;
+  font-size: 1.25rem;
+  text-align: center;
+  vertical-align: middle;
+}
+.header-title img{
+  vertical-align: middle;
+  height: 34px;
+  margin-right: 30px;
+}
+.header-title span{
+  vertical-align: middle;
+  display: inline-block;
+}
+.header-user {
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+}
+
+.header-user .logout img {
+  width: 20px;
+  height: 20px;
+  margin-right: 10px;
+}
+
+.header-user .logout {
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+}
+
+.header-user .avatar{
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  height: 3rem;
+  margin-right: 10px;
+}
+
+.header-user .avatar span{
+  margin-left: 10px;
+}
+
+.header-user .avatar img{
+  width:3rem;
+  height:3rem;
+  border-radius:50%;
+}
+</style>

+ 56 - 0
sh_backstage/src/pages/layout/index.vue

@@ -0,0 +1,56 @@
+<template>
+<div class="body theme" >
+  <Head />
+  <div class="aside-con card">
+    <Aside />
+  </div>
+    <router-view class="main-view" />
+  <Footer />
+</div>
+</template>
+
+<script>
+import Head from './head'
+import Footer from './footer'
+import Aside from './aside'
+
+export default {
+  components: {
+    Head,
+    Aside,
+    Footer
+  },
+  computed: {
+    userId () {
+      return window.localStorage.getItem('userInfo')
+    }
+  },
+  mounted () {
+    if (!this.userId) {
+      this.$router.push('/login')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.body{
+  width: 100%;
+  height: 100%;
+}
+.aside-con {
+  width: 15rem;
+  position: fixed;
+  height: calc(100% - 8rem);
+  margin: 1.875rem 1.25rem 1.875rem 1.875rem;
+  border-radius: .625rem;
+  overflow: hidden;
+  background-color: #ccc;
+}
+.main-view{
+  margin: 1.875rem 1.875rem 0 18.25rem;
+  width: calc(100% - 20.125rem);
+  height: calc(100% - 8rem);
+  overflow: hidden;
+}
+</style>

+ 219 - 0
sh_backstage/src/pages/login/index.vue

@@ -0,0 +1,219 @@
+<!--  -->
+<template>
+<div class='layout'>
+  <div class="layout-con">
+    <img class="logo" :src="require('@/assets/img/4dage-logo.png')" alt="">
+    <img class="bg" :src="require('@/assets/img/bg.jpg')" alt="">
+    <div class="middle">
+      <div class="middle-left">
+        <div>上海市历史博物馆<br/>三维数据库管理系统</div>
+        <img class="top" :src="require('@/assets/img/wenli.png')" alt="">
+        <img class="bottom" :src="require('@/assets/img/wenli.png')" alt="">
+
+      </div>
+      <div class="middle-right">
+        <el-form class="middle-form" :rules="ruleLogin" status-icon :model="formLogin" ref="formLogin">
+          <div>欢迎登录</div>
+          <el-form-item prop="username">
+            <el-input v-model="formLogin.username" placeholder="账号名"></el-input>
+          </el-form-item>
+          <el-form-item prop="password">
+            <el-input @keyup.enter.native="submitForm('formLogin')" v-model="formLogin.password" placeholder="账号密码" type="password"></el-input>
+          </el-form-item>
+          <el-form-item>
+            <el-button  @click="submitForm('formLogin')" type="primary" >登录</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+    </div>
+  </div>
+</div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+import { encodeStr } from '@/util'
+import { Base64 } from 'js-base64'
+
+import Vue from 'vue'
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {},
+  data () {
+    var checkUsername = (rule, value, callback) => {
+      if (!value) {
+        return callback(new Error('用户名不能为空'))
+      }
+      setTimeout(() => {
+        if (value.length > 10) {
+          callback(new Error('用户名字符不能大于10位'))
+        } else {
+          callback()
+        }
+      }, 1000)
+    }
+    var validatePass = (rule, value, callback) => {
+      if (value === '') {
+        callback(new Error('请输入密码'))
+      } else {
+        callback()
+      }
+    }
+    // 这里存放数据
+    return {
+      formLogin: {
+        username: '',
+        password: '',
+        region: 'high'
+      },
+      ruleLogin: {
+        username: [
+          { validator: checkUsername, trigger: 'blur' }
+        ],
+        password: [
+          { validator: validatePass, trigger: 'blur' }
+        ]
+      }
+    }
+  },
+  // 监听属性 类似于data概念
+  computed: {
+    userId () {
+      return window.localStorage.getItem('userInfo')
+    }
+  },
+  // 监控data中的数据变化
+  watch: {},
+  // 方法集合
+  methods: {
+    submitForm (formName) {
+      this.$refs[formName].validate((valid) => {
+        let data = {
+          userName: this.formLogin.username,
+          password: encodeStr(Base64.encode(this.formLogin.password))
+        }
+        if (valid) {
+          this.$http.post('/admin/login', data).then(res => {
+            if (res.code === 0) {
+              window.localStorage.setItem('token', String(res.data.token))
+              window.localStorage.setItem('userInfo', JSON.stringify(res.data.user))
+              Vue.prototype.$role = res.data.user.role
+              this.$router.push('/')
+            } else {
+              this.$notify.error({
+                title: '错误',
+                message: res.msg
+              })
+            }
+          })
+        } else {
+          return false
+        }
+      })
+    }
+  },
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created () {
+
+  },
+  // 生命周期 - 挂载完成(可以访问DOM元素)
+  mounted () {
+    if (this.userId) {
+      this.$router.push({path:'/'})
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.layout{
+  .layout-con{
+     width: 100%;
+     position: relative;
+     .logo{
+       position: absolute;
+       left: 35px;
+       top: 40px;
+       z-index: 999;
+       width: 310px;
+       cursor: pointer;
+       height: auto;
+     }
+    .bg{
+      width: 100%;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%,-50%);
+      z-index: 0;
+      position: fixed;
+      user-select: none;
+    }
+    .middle{
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      width: 800px;
+      min-height: 56vh;
+      position: absolute;
+      transform: translate(-50%,-50%);
+      left: 50%;
+      top: 50%;
+      .middle-left{
+        background: rgba(#9D362F, 0.7);
+        width: 50%;
+        height: 100%;
+        position: relative;
+        >div{
+          width: 100%;
+          text-align: center;
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%,-50%);
+          font-size: 32px;
+          text-shadow: 0 4px 6px rgba(0, 0, 0, 0.7);
+          color: #f2ecde;
+          line-height: 40px;
+        }
+        img{
+          position: absolute;
+          width: 100%;
+          left: 0;
+        }
+        .top{
+          top: 0;
+        }
+        .bottom{
+          bottom: 0;
+          transform: rotate(180deg);
+        }
+      }
+      .middle-right{
+        position: relative;
+        height: 100%;
+        background: rgba(#fff, 0.7);
+        width: 50%;
+        .middle-form{
+          width: 80%;
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%,-50%);
+          >div{
+            text-align: center;
+            margin-bottom: 50px;
+            font-size: 24px;
+          }
+        }
+      }
+    }
+  }
+}
+</style>
+
+<style scoped>
+.el-button--primary {
+  width: 100%;
+}
+</style>

+ 3 - 0
sh_backstage/src/pages/login/style.css

@@ -0,0 +1,3 @@
+.el-button--primary {
+  width: 100%;
+}

+ 363 - 0
sh_backstage/src/pages/show/parts.vue

@@ -0,0 +1,363 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData">
+      <div slot="con" class="con-btn">
+        <el-button type="primary" @click="$router.back()">返回</el-button>
+      </div>
+    </main-top>
+      <div class="table-interface">
+        <div class="collection-con">
+          <div class="c-left">
+            <iframe v-if="part.displayModel == 0" :src="part.webSite" frameborder="0"></iframe>
+            <img v-else :src="part.ico" alt="">
+          </div>
+          <div class="c-right theme-color">
+            <div class="c-h1">{{part.name}}</div>
+            <p class="c-title">
+              <span>部件详细信息</span>
+              <i class="iconfont iconsys_edit" @click="goto"></i>
+            </p>
+            <p>部件类别:{{part.typeName}}</p>
+            <p>所属楼体:{{BLOCK[part.block]}}</p>
+            <p>部件位置:<span v-html="ZONE[part.zone]"></span></p>
+            <p>是否在程序显示:
+              <el-switch
+                @change="handleShow"
+                v-model="part.display">
+              </el-switch>
+            </p>
+            <p class="c-title" style="margin-bottom:0">
+              <span>部件介绍</span>
+            </p>
+            <p v-html="part.content||'暂无介绍'" style="margin-top:20px"></p>
+
+            <p class="c-title">
+              <span>多媒体资料</span>
+              <i class="iconfont iconsys_down" @click="apply(file)"></i>
+            </p>
+            
+            <div class="media-cls">
+              <p v-if="file.length===0" style="color:#999">暂无内容</p>
+              <p v-for="(sub,idx) in file" :key="idx">{{sub.fileName}}</p>
+            </div>
+          </div>
+        </div>
+      </div>
+          <el-dialog title="提交下载申请" :visible.sync="dialogFormVisible" width="40%">
+      <div class="dialog">
+        <p>请选择要下载的内容:</p>
+        <div class="content">
+            <el-checkbox-group v-model="checkList" @change='handleChange'>
+              <div v-for="(sub,idx) in activeFiles" :key="idx">
+                <el-checkbox :label="sub.id" :false-label="''" :true-label="''">{{sub.fileName}}</el-checkbox>
+              </div>
+            </el-checkbox-group>
+        </div>
+        <p>请填写下载用途:</p>
+        <div class="content textarea">
+          <el-input
+            type="textarea"
+            :rows="2"
+            placeholder="请输入内容"
+            v-model="textarea">
+          </el-input>
+        </div>
+        <p>请选择审核人:</p>
+        <ul class="content">
+          <li v-for="(item,i) in adminList" :key="i">
+            <el-radio v-model="radio" :label="item.id">{{item.userName}}</el-radio>
+          </li>
+        </ul>
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogFormVisible = false">取 消</el-button>
+        <el-button type="primary" @click="applyDownload">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from '@/components/main-top'
+
+const crumbData = [
+  {
+    name: '部件信息',
+    id: 0
+  },
+  {
+    name: '查看详情',
+    id: 1
+  }
+]
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop
+  },
+  data () {
+    return {
+      crumbData,
+      id: this.$route.params.id,
+      type: 'picture',
+      realType: '',
+      file:'',
+      part:'',
+      value1:true,      
+      dialogFormVisible: false,
+      checkList: [],
+      textarea:'',
+      activeFiles:'',
+      adminList:[],
+      radio:''
+    }
+  },
+  methods: {
+     handleChange(){
+    },
+    async getDetail(){
+      let result = await this.$http({
+        method: 'get',
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: `/manage/part/detail/${this.id}`
+      })
+
+      this.dialogFormVisible = false
+      this.file = result.data.file
+      this.part = result.data.part
+      this.part.display = Boolean(this.part.display)
+      
+    },
+    apply(files){
+      if (files.length===0) {
+        return
+      }
+      this.dialogFormVisible = true
+      this.activeFiles = files
+
+      this.getAdminList()
+    },
+
+    async applyDownload(){
+      
+      if (!this.radio||!this.checkList.length) {
+        return this.$alert("请填写勾选信息", "提示", {
+            confirmButtonText: "确定",
+            callback: () => {
+              return
+            },
+          })
+      }
+      
+      let params ={
+        auditId: this.radio,
+        fileIds: this.checkList.join(','),
+        fkId: '',
+        name: this.part.name,
+        reason: this.textarea,
+        type: 'part'
+      }
+    let result = await this.$http({
+        method: 'post',
+        data:params,
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: '/manage/download/save'
+      })
+
+      
+      if (result.code === 0) {
+        this.$alert('申请成功', '提示', {
+          confirmButtonText: '确定',
+          callback: () => {
+            this.getDetail()
+          }
+        })
+      } else {
+        this.$notify.error({
+          title: '错误',
+          message: result.msg
+        })
+      }
+
+    },
+
+    async getAdminList(){
+    let result = await this.$http({
+        method: 'get',
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: '/manage/download/admin'
+      })
+
+      this.adminList = result.data
+    },
+
+    goto () {
+      this.$router.push({ name: 'edit-parts', params: { type: 1,id:this.part.id } })
+    },
+    async handleShow(e){
+      this.part['display'] = e
+      let params = {}
+      Object.assign(params,this.part,{
+        display:e|0
+      })
+      await this.$http({
+        method: "post",
+        data: params,
+        headers: {
+          token: window.localStorage.getItem("token")
+        },
+        url: `/manage/part/save`
+      });
+    }
+  },
+  mounted () {
+    this.getDetail()
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.top-body{
+  border-top: .0625rem solid #e6e6e6;
+  line-height: 1.5;
+  padding: 1.25rem;
+  align-items: center;
+  box-sizing: border-box;
+  background: #fff;
+  margin: 1rem 0;
+}
+
+
+.table-interface{
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem);
+  background: #fff;
+  padding: 2.625rem 1.25rem;
+}
+
+.collection-con{
+  display: flex;
+}
+
+.c-left{
+  flex: 3;
+  margin-right: 20px;
+}
+
+.c-left p{
+  margin: 10px 20px;
+}
+
+.c-left iframe,.c-left img{
+  width: 100%;
+  min-height: 70vh;
+}
+
+.c-right{
+  flex: 1;
+  .c-h1{
+    color: #B63C25;
+    font-size: 1.25rem;
+    font-weight: bold;
+  }
+}
+
+.c-right .c-title{
+  font-weight: bold;
+  display: flex;
+  justify-content: space-between;
+  border-bottom: 1px solid #532F1C;
+  color: #532F1C;
+  font-size: 1.125rem;
+}
+
+.edit{
+  cursor: pointer;
+}
+.c-right p{
+  margin: 40px 20px 40px 0;
+  word-break: break-all;
+  line-height: 1.5;
+  font-size: 1rem;
+}
+.con-btn{
+  padding-right: 1.25rem;
+}
+
+.media-cls{
+  color: rgba(0, 176, 255, 1);
+  cursor: pointer;
+  margin-top: 20px;
+  p{
+    line-height: 2;
+    margin: 0;
+  }
+}
+
+.iconfont{
+  cursor: pointer;
+}
+
+.dialog{
+  .content{
+    border: .0625rem solid #ccc;
+    padding: .625rem;
+    margin: .625rem auto;
+    div,li{
+      line-height: 2;
+    }
+  }
+  .textarea{
+    padding: 0;
+    border: none;
+  }
+}
+
+</style>
+
+<style>
+.ql-picker-label {
+  line-height: 1;
+  overflow: hidden;
+}
+
+.avatar-uploader .el-upload {
+  border: 1px dashed #000;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #409eff;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 178px;
+  height: 178px;
+  line-height: 178px;
+  text-align: center;
+}
+.avatar {
+  width: 178px;
+  height: 178px;
+  display: block;
+}
+.iconfont{
+  font-size: 20px;
+}
+
+</style>

+ 245 - 0
sh_backstage/src/pages/show/roam.vue

@@ -0,0 +1,245 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData">
+      <div slot="con" class="con-btn">
+        <el-button type="primary" @click="$router.back()">返回</el-button>
+      </div>
+    </main-top>
+      <div class="table-interface">
+        <div class="collection-con">
+          <div class="c-left">
+            <video v-if="detail.displayModel === 2" :src="detail.video||detail.webSite" autoplay loop></video>
+            <iframe v-else :src="detail.webSite" frameborder="0"></iframe>
+          </div>
+          <div class="c-right theme-color">
+            <div class="c-h1">{{detail.name}}</div>
+            <p class="c-title">
+              <span>漫游信息</span>
+              <i class="iconfont iconsys_edit" @click="goto"></i>
+            </p>
+            <p>漫游类别:{{detail.typeName}}</p>
+            <p>所属楼体:{{BLOCK[detail.block]}}</p>
+            <p>漫游位置:<span v-html="ZONE[detail.zone]"></span></p>
+            <p>是否在程序显示:
+              <el-switch
+                @change="handleShow"
+                v-model="detail.display">
+              </el-switch>
+            </p>
+            <p class="c-title" style="margin-bottom:0">
+              <span>漫游介绍</span>
+            </p>
+            <p v-html="detail.content||'暂无介绍'" style="margin-top:20px"></p>
+
+          </div>
+        </div>
+      </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from '@/components/main-top'
+
+const crumbData = [
+  {
+    name: '漫游信息',
+    id: 0
+  },
+  {
+    name: '查看详情',
+    id: 1
+  }
+]
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop
+  },
+  data () {
+    return {
+      crumbData,
+      detail: '',
+      id: this.$route.params.id,
+      type: 'picture',
+      realType: '',
+      value1:true
+    }
+  },
+  methods: {
+    async getDetail(){
+      let result = await this.$http({
+        method: 'get',
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: `/manage/roam/detail/${this.id}`
+      })
+
+      this.detail = result.data
+      this.detail.display = Boolean(this.detail.display)
+
+    },
+
+    goto () {
+      this.$router.push({ name: 'edit-roam', params: { type: 1, id:this.detail.id} })
+    },
+    async handleShow(e){
+      this.detail['display'] = e
+      let params = {}
+      Object.assign(params,this.detail,{
+        display:e|0
+      })
+      await this.$http({
+        method: "post",
+        data: params,
+        headers: {
+          token: window.localStorage.getItem("token")
+        },
+        url: `/manage/roam/save`
+      });
+    }
+  },
+  mounted () {
+    this.getDetail()
+  }
+}
+</script>
+<style lang="less" scoped>
+.top-body{
+  border-top: .0625rem solid #e6e6e6;
+  line-height: 1.5;
+  padding: 1.25rem;
+  align-items: center;
+  box-sizing: border-box;
+  background: #fff;
+  margin: 1rem 0;
+}
+
+
+.table-interface{
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem);
+  background: #fff;
+  padding: 2.625rem 1.25rem;
+}
+
+.collection-con{
+  display: flex;
+}
+
+.c-left{
+  flex: 3;
+  margin-right: 20px;
+}
+
+.c-left p{
+  margin: 10px 20px;
+}
+
+.c-left iframe,.c-left video{
+  width: 100%;
+  min-height: 70vh;
+}
+
+.c-right{
+  flex: 1;
+  .c-h1{
+    color: #B63C25;
+    font-size: 1.25rem;
+    font-weight: bold;
+  }
+}
+
+.c-right .c-title{
+  font-weight: bold;
+  display: flex;
+  justify-content: space-between;
+  border-bottom: 1px solid #532F1C;
+  color: #532F1C;
+  font-size: 1.125rem;
+}
+
+.edit{
+  cursor: pointer;
+}
+.c-right p{
+  margin: 40px 20px 40px 0;
+  word-break: break-all;
+  line-height: 1.5;
+  font-size: 1rem;
+}
+.con-btn{
+  padding-right: 1.25rem;
+}
+
+.media-cls{
+  color: rgba(0, 176, 255, 1);
+  cursor: pointer;
+  margin-top: 20px;
+  p{
+    line-height: 2;
+    margin: 0;
+  }
+}
+
+.iconfont{
+  cursor: pointer;
+}
+
+.dialog{
+  .content{
+    border: .0625rem solid #ccc;
+    padding: .625rem;
+    margin: .625rem auto;
+    div,li{
+      line-height: 2;
+    }
+  }
+  .textarea{
+    padding: 0;
+    border: none;
+  }
+}
+
+</style>
+
+<style>
+.ql-picker-label {
+  line-height: 1;
+  overflow: hidden;
+}
+
+.avatar-uploader .el-upload {
+  border: 1px dashed #000;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #409eff;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 178px;
+  height: 178px;
+  line-height: 178px;
+  text-align: center;
+}
+.avatar {
+  width: 178px;
+  height: 178px;
+  display: block;
+}
+.iconfont{
+  font-size: 20px;
+}
+
+</style>

+ 236 - 0
sh_backstage/src/pages/show/structure.vue

@@ -0,0 +1,236 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData">
+      <div slot="con" class="con-btn">
+        <el-button type="primary" @click="$router.back()">返回</el-button>
+      </div>
+    </main-top>
+      <div class="table-interface">
+                <div class="collection-con">
+          <div class="c-left">
+            <iframe v-if="detail.webSite" :src="detail.webSite||detail.ico" frameborder="0"></iframe>
+            <img v-else :src="detail.ico" alt="">
+          </div>
+          <div class="c-right theme-color">
+            <div class="c-h1">{{detail.name}}</div>
+            <p class="c-title">
+              <span>结构信息</span>
+              <i class="iconfont iconsys_edit" @click="goto"></i>
+            </p>
+            <p>所属楼体:{{BLOCK[detail.block]}}</p>
+            <p class="c-title" style="margin-bottom:0">
+              <span>结构介绍</span>
+            </p>
+            <p v-html="detail.content||'暂无介绍'" style="margin-top:20px"></p>
+          </div>
+        </div>
+      </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from '@/components/main-top'
+
+const crumbData = [
+  {
+    name: '结构信息',
+    id: 0
+  },
+  {
+    name: '查看详情',
+    id: 1
+  }
+]
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop
+  },
+  data () {
+    return {
+      crumbData,
+      detail: '',
+      id: this.$route.params.id,
+      type: 'picture',
+      realType: '',
+      value1:true
+    }
+  },
+  methods: {
+    async getDetail(){
+      let result = await this.$http({
+        method: 'get',
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: `/manage/structure/detail/${this.id}`
+      })
+
+      this.detail = result.data
+    },
+
+    goto () {
+      this.$router.push({ name: 'edit-structure', params: { type: 1, id:this.detail.id} })
+    },
+    async handleShow(e){
+      this.detail['display'] = e
+      console.log(this.detail['display']);
+      let params = {}
+      Object.assign(params,this.detail,{
+        display:e|0
+      })
+      await this.$http({
+        method: "post",
+        data: params,
+        headers: {
+          token: window.localStorage.getItem("token")
+        },
+        url: `/manage/structure/save`
+      });
+    }
+  },
+  mounted () {
+    this.getDetail()
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.top-body{
+  border-top: .0625rem solid #e6e6e6;
+  line-height: 1.5;
+  padding: 1.25rem;
+  align-items: center;
+  box-sizing: border-box;
+  background: #fff;
+  margin: 1rem 0;
+}
+
+
+.table-interface{
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem);
+  background: #fff;
+  padding: 2.625rem 1.25rem;
+}
+
+.collection-con{
+  display: flex;
+}
+
+.c-left{
+  flex: 3;
+  margin-right: 20px;
+}
+
+.c-left p{
+  margin: 10px 20px;
+}
+
+.c-left iframe,.c-left img{
+  width: 100%;
+  min-height: 70vh;
+}
+
+.c-right{
+  flex: 1;
+  .c-h1{
+    color: #B63C25;
+    font-size: 1.25rem;
+    font-weight: bold;
+  }
+}
+
+.c-right .c-title{
+  font-weight: bold;
+  display: flex;
+  justify-content: space-between;
+  border-bottom: 1px solid #532F1C;
+  color: #532F1C;
+  font-size: 1.125rem;
+}
+
+.edit{
+  cursor: pointer;
+}
+.c-right p{
+  margin: 40px 20px 40px 0;
+  word-break: break-all;
+  line-height: 1.5;
+  font-size: 1rem;
+}
+.con-btn{
+  padding-right: 1.25rem;
+}
+
+.media-cls{
+  color: rgba(0, 176, 255, 1);
+  cursor: pointer;
+  margin-top: 20px;
+  p{
+    line-height: 2;
+    margin: 0;
+  }
+}
+
+.iconfont{
+  cursor: pointer;
+}
+
+.dialog{
+  .content{
+    border: .0625rem solid #ccc;
+    padding: .625rem;
+    margin: .625rem auto;
+    div,li{
+      line-height: 2;
+    }
+  }
+  .textarea{
+    padding: 0;
+    border: none;
+  }
+}
+
+</style>
+
+<style>
+.ql-picker-label {
+  line-height: 1;
+  overflow: hidden;
+}
+
+.avatar-uploader .el-upload {
+  border: 1px dashed #000;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #409eff;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 178px;
+  height: 178px;
+  line-height: 178px;
+  text-align: center;
+}
+.avatar {
+  width: 178px;
+  height: 178px;
+  display: block;
+}
+.iconfont{
+  font-size: 20px;
+}
+
+</style>

+ 484 - 0
sh_backstage/src/pages/system/Download.vue

@@ -0,0 +1,484 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData"></main-top>
+    <div class="table-interface">
+      <div class="top-body">
+        <div class="info-top">
+          <div class="info-left">
+            <span>按功能模块查看:</span>
+            <el-select v-model="typeId" placeholder="请选择">
+              <el-option label="全部" value></el-option>
+              <el-option v-for="(item, i) in typeArr" :key="i" :label="item.name" :value="item.id"></el-option>
+            </el-select>
+            <span style="margin-left:20px;">按状态查看:</span>
+            <el-select v-model="statusId" placeholder="请选择">
+              <el-option label="全部" value></el-option>
+              <el-option
+                v-for="(item, i) in statusArr"
+                :key="i"
+                :label="item.name"
+                :value="item.id"
+              ></el-option>
+            </el-select>
+            <el-input style="width:220px;margin:0 20px;" v-model="inputKey" placeholder="请输入关键字搜索"></el-input>
+            <el-button type="primary" @click="getInformation">查找</el-button>
+            <el-button @click="reset">重置</el-button>
+          </div>
+          <!-- <div class="info-right">
+            <el-button type="primary" @click="$router.push({name:'edit-parts',params:{type:0,id:'none'}})">新增文物</el-button>
+          </div>-->
+        </div>
+        <el-table :data="tableData" style="width: 100%">
+          <el-table-column
+            v-for="(item, idx) in data"
+            :key="idx"
+            :prop="item.prop"
+            :label="item.label"
+          >
+            <template slot-scope="scope">
+              <span>{{ scope.row[item.prop] || "-" }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作">
+            <template slot-scope="scope">
+              <span @click="show(scope.row)" class="o-span">查看详情</span>
+            </template>
+          </el-table-column>
+        </el-table>
+        <div class="e-pagination">
+          <el-pagination
+            @current-change="handleCurrentChange"
+            :current-page.sync="currentPage"
+            :page-size="size"
+            layout="prev, pager, next, jumper"
+            :total="total"
+          ></el-pagination>
+        </div>
+
+        <el-dialog title="审核申请内容" :visible.sync="dialogFormVisible" width="40%">
+          <div class="add-con">
+            <h2 v-if="form.status!==1">你的申请{{statusStr[form.status]}}!</h2>
+
+            <el-form :model="form">
+              <el-form-item label="申请人:" :label-width="formLabelWidth">
+                <span>{{ form.userName || "-" }}</span>
+              </el-form-item>
+              <el-form-item label="申请时间:" :label-width="formLabelWidth">
+                <span>{{ form.createTime || "-" }}</span>
+              </el-form-item>
+              <el-form-item label="申请原因:" :label-width="formLabelWidth">
+                <span>{{ form.reason || "-" }}</span>
+              </el-form-item>
+            </el-form>
+            <template v-if="form.status !== 3||$role==='admin'">
+               <div class="file_cls">申请下载的内容:</div>
+                <ul class="file_ul">
+                  <el-checkbox-group v-model="checkList" @change='handleChange'>
+                    <div style="line-height:2" v-for="(sub,idx) in files" :key="idx">
+                      <el-checkbox :label="sub.id" :false-label="''" :true-label="''">{{sub.fileName}}</el-checkbox>
+                    </div>
+                  </el-checkbox-group>
+                  
+                  <el-button v-if="form.status !== 1||$role==='admin'" size="mini" style="margin-top:20px" @click="downLoad" type="primary">下载</el-button>
+                </ul>
+            </template>
+          </div>
+          <div slot="footer" class="dialog-footer" v-if="$role==='admin'" >
+            <el-button type="primary" @click="shenHe(2)">{{form.status === 1?'同 意':'确 定'}}</el-button>
+            <el-button @click="shenHe(3)">{{form.status === 1?'驳 回':'返 回'}}</el-button>
+          </div>
+        </el-dialog>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from "@/components/main-top";
+const crumbData = [
+  {
+    name: "下载管理",
+    id: 5
+  }
+];
+
+const TYPENAME = {
+  part: "部件信息管理",
+  building: "建筑物信息管理"
+};
+
+const STATUSNAME = {
+  1: "待审核",
+  2: "已同意",
+  3: "已驳回"
+};
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop
+  },
+  data() {
+    // 这里存放数据
+    let data = [
+      {
+        prop: "idx",
+        label: "序号"
+      },
+      {
+        prop: "typeName",
+        label: "资料所属模块"
+      },
+      {
+        prop: "name",
+        label: "部件/建筑物名称"
+      },
+      {
+        prop: "userName",
+        label: "申请人"
+      },
+      {
+        prop: "createTime",
+        label: "申请时间"
+      },
+      {
+        prop: "statusStr",
+        label: "当前状态"
+      }
+    ];
+
+    const statusArr = [
+      {
+        id: 1,
+        name: "待审核"
+      },
+      {
+        id: 2,
+        name: "已同意"
+      },
+      {
+        id: 3,
+        name: "已驳回"
+      }
+    ];
+
+    const statusStr =       {
+        2: "已同意",
+        3: "已驳回",
+      }
+    ;
+
+    const typeArr = [
+      {
+        id: "part",
+        name: "部件信息管理"
+      },
+      {
+        id: "building",
+        name: "建筑物信息管理"
+      }
+    ];
+    return {
+      crumbData,
+      data,
+      checkList:[],
+      infoName: "",
+      currentPage: 1,
+      dialogFormVisible: false,
+      size: 10,
+      total: 0,
+      tableData: [],
+      statusArr,
+      typeArr,
+      statusStr,
+      statusId: "",
+      typeId: "",
+      inputKey: "",
+      form: {
+        createTime: "",
+        fileIds: "",
+        id: 0,
+        name: "",
+        reason: "",
+        status: 0,
+        type: "",
+        updateTime: "",
+        userName: ""
+      },
+      formLabelWidth: "120px",
+      files:[]
+    };
+  },
+  watch: {
+    currentPage() {
+      this.refresh();
+    },
+    size() {
+      this.refresh();
+    }
+  },
+  mounted() {
+    this.refresh();
+  },
+  methods: {
+    handleChange(){},
+    async downLoad(){
+      if (!this.checkList.length) {
+        return this.$alert("请勾选下载内容", "提示", {
+            confirmButtonText: "确定",
+            callback: () => {
+              return
+            },
+        })
+      }
+      // let loading = this.$loading({
+      //   lock: true,
+      //   text: 'Loading',
+      //   spinner: 'el-icon-loading',
+      //   background: 'rgba(0, 0, 0, 0.7)'
+      // });
+      let result = await this.$http({
+        method: "get",
+        headers: {
+          token: window.localStorage.getItem("token")
+        },
+        url: `/manage/download/download/${this.checkList.join(',')}`
+      });
+      // loading.close()
+
+      if (result.code === 0) {
+        window.location.href = result.msg
+      } else {
+        this.$notify.error({
+          title: "错误",
+          message: '下载失败'
+        });
+      }
+    },
+    async shenHe(status = 3) {
+      if (this.form.status!==1) {
+        return this.dialogFormVisible = false
+      }
+      let result = await this.$http({
+        method: "get",
+        headers: {
+          token: window.localStorage.getItem("token")
+        },
+        url: `/manage/download/audit/${this.form.id}/${status}`
+      });
+
+      if (result.code === 0) {
+        this.$alert("审核成功", "提示", {
+          confirmButtonText: "确定",
+          callback: () => {
+            this.refresh();
+          }
+        });
+      } else {
+        this.$notify.error({
+          title: "错误",
+          message: result.msg
+        });
+      }
+    },
+    refresh() {
+      this.dialogFormVisible = false;
+      this.loading = true;
+      this.getInformation();
+      this.loading = false;
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+    },
+    reset() {
+      this.inputKey = "";
+      this.statusId = "";
+      this.typeId = ""
+    },
+
+    async getDetail(id){
+      let result = await this.$http({
+        method: 'get',
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: `/manage/download/detail/${id}`
+      })
+
+      this.form = result.data.obj
+      this.files = result.data.file
+    },
+
+    show(item = {}) {
+      this.dialogFormVisible = true;
+      this.checkList = []
+      this.getDetail(item.id)
+    },
+
+    async getInformation() {
+      let params = {
+        pageNum: this.currentPage,
+        pageSize: this.size,
+        searchKey: this.inputKey,
+        type:this.typeId,
+        status:this.statusId
+      };
+
+      let result = await this.$http({
+        method: "post",
+        data: params,
+        headers: {
+          token: window.localStorage.getItem("token")
+        },
+        url: "/manage/download/list"
+      });
+
+      if (result.code !== 0) {
+        return;
+      }
+      this.tableData = result.data.list;
+      this.total = result.data.total;
+      this.tableData.forEach((item, i) => {
+        item["idx"] = i + 1;
+        item["typeName"] = TYPENAME[item.type];
+        item["statusStr"] = STATUSNAME[item.status];
+      });
+    }
+  }
+};
+</script>
+
+<style lang="less" scoped>
+.top-body {
+  border-top: 0.0625rem solid #e6e6e6;
+  line-height: 1.5;
+  padding: 0 1.25rem 1.25rem;
+  align-items: center;
+  box-sizing: border-box;
+  background: #fff;
+  margin: 1rem 0;
+}
+
+.top-body .top-con {
+  font-weight: bold;
+}
+.table-title {
+  padding: 1rem 1rem 1rem 0;
+  font-size: 1rem;
+  color: #2d2d2d;
+  border-bottom: 1px solid #ccc;
+  display: flex;
+  justify-content: space-between;
+}
+
+.more {
+  color: #ec652d;
+  cursor: pointer;
+}
+.add-con{
+  h2{
+    text-align: center;
+    font-size: 20px;
+    margin-bottom: 30px;
+  }
+}
+.top-right {
+  background-color: #ec652d;
+  height: 3.5625rem;
+  line-height: 3.5625rem;
+  color: #fff;
+  padding: 0 1.875rem;
+  border-radius: 0.3125rem;
+  font-size: 1.125rem;
+  font-weight: bold;
+}
+
+.search-body {
+  background-color: #fff;
+  margin: 1.25rem 0;
+}
+
+.interface-table {
+  height: calc(100% - 17.1875rem);
+  overflow-x: hidden;
+  background-color: #fff;
+  padding: 0 1.125rem 2.375rem;
+  box-sizing: border-box;
+}
+
+.file_cls{
+  padding-left: 38px;  
+  margin-bottom: 20px;
+}
+
+.file_ul{
+  border: 1px solid #ccc;
+  padding: 20px 40px;  
+  margin-bottom: 20px;
+}
+
+.file_ul li{
+  color: #409eff;
+  margin-bottom: 6px;
+  cursor: pointer;
+}
+
+.zan-con {
+  display: flex;
+  width: 100%;
+  justify-content: space-around;
+  margin-top: 1.5rem;
+  text-align: center;
+  font-size: 1.125rem;
+}
+
+.zan-con .line {
+  height: 8rem;
+  width: 1px;
+  background: #ccc;
+}
+
+.zan-con .zan-contain {
+  display: flex;
+  justify-content: space-between;
+  flex-direction: column;
+}
+
+.zan-con .zan-contain p {
+  font-size: 2.25rem;
+}
+
+.zan-con .zan-contain p:first-child {
+  font-size: 0.875rem;
+  line-height: 1.5;
+}
+
+.zan-sub {
+  text-align: right;
+  margin-top: 1.25rem;
+  color: #a5a5a5;
+  font-size: 0.875rem;
+}
+
+.table-interface {
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem);
+}
+
+.info-top {
+  padding: 20px 0;
+  display: flex;
+  justify-content: space-between;
+  border-bottom: 1px #a5a5a5 solid;
+}
+
+.o-span {
+  cursor: pointer;
+  color: rgb(7, 152, 244);
+}
+</style>

+ 276 - 0
sh_backstage/src/pages/system/Password.vue

@@ -0,0 +1,276 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData"></main-top>
+    <div class="table-interface">
+      <div class="top-body">
+        <div class="body-middle">
+          <el-form
+            :rules="ruleForget"
+            status-icon
+            :model="formForget"
+            ref="formForget"
+          >
+            <el-form-item prop="oldPass">
+              <el-input
+                v-model="formForget.oldPass"
+                placeholder="当前密码"
+                type="password"
+              ></el-input>
+            </el-form-item>
+            <el-form-item prop="newPassword">
+              <el-input
+                v-model="formForget.newPassword"
+                placeholder="新密码"
+                type="password"
+              ></el-input>
+            </el-form-item>
+            <el-form-item prop="comfirm">
+              <el-input
+                v-model="formForget.comfirm"
+                placeholder="重复新密码"
+                type="password"
+              ></el-input>
+            </el-form-item>
+            <el-form-item>
+              <el-button @click="updatePass" type="primary">确定</el-button>
+              <el-button>取消</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from "@/components/main-top";
+import { encodeStr } from '@/util'
+import { Base64 } from 'js-base64'
+const crumbData = [
+  {
+    name: "密码修改",
+    id: 8,
+  },
+];
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop,
+  },
+  data() {
+    var validatePass = (rule, value, callback) => {
+      if (value === "") {
+        callback(new Error("请输入密码"));
+      } else {
+        if (this.formForget.newPassword !== "") {
+          this.$refs.formForget.validateField("comfirm");
+        }
+        callback();
+      }
+    };
+    var validatePass2 = (rule, value, callback) => {
+      if (value === "") {
+        callback(new Error("请再次输入密码"));
+      } else if (value !== this.formForget.newPassword) {
+        callback(new Error("两次输入密码不一致!"));
+      } else {
+        callback();
+      }
+    };
+
+    // 这里存放数据
+    return {
+      crumbData,
+      formForget: {
+        oldPass: "",
+        newPassword: "",
+        comfirm: "",
+      },
+      ruleForget: {
+        newPassword: [{ validator: validatePass, trigger: "blur" }],
+        oldPass: [{ validator: validatePass, trigger: "blur" }],
+        comfirm: [{ validator: validatePass2, trigger: "blur" }],
+      },
+    };
+  },
+  methods: {
+    async logout () {
+      await this.$http({
+        method: 'get',
+        url: '/admin/logout',
+        headers: {
+          token: window.localStorage.getItem('token')
+        }
+      }).then(res => {
+        if (res.code === 0) {
+          window.localStorage.setItem('token', '')
+          this.$alert('退出成功', '提示', {
+            confirmButtonText: '确定',
+            callback: () => {
+              window.localStorage.setItem('userInfo', '')
+              this.$router.push('/login')
+            }
+          })
+        }
+      })
+    },
+    updatePass() {
+      this.$refs["formForget"].validate(async (valid) => {
+        if (valid) {
+            let temp = encodeStr(Base64.encode(this.formForget.newPassword), Base64.encode(this.formForget.oldPass))
+
+            let result = await this.$http({
+              method: "post",
+              data: {
+                newPassword: temp[0] ,
+                oldPassword: temp[1]
+              },
+              headers: {
+                token: window.localStorage.getItem("token"),
+              },
+              url: `/manage/user/updatePwd`,
+            });
+
+          if (result.code === 0) {
+            this.$alert("修改成功", "提示", {
+              confirmButtonText: "确定",
+              callback: () => {
+                this.logout()
+              },
+            });
+          } else {
+            this.$notify.error({
+              title: "错误",
+              message: result.msg,
+            });
+          }
+        } else {
+          return this.$notify.error({
+              title: "错误",
+              message: '修改失败',
+            });
+        }
+      });
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.top-body {
+  border-top: 0.0625rem solid #e6e6e6;
+  line-height: 1.5;
+  padding: 1.25rem 1.25rem;
+  align-items: center;
+  box-sizing: border-box;
+  background: #fff;
+  margin: 1rem 0;
+}
+
+.top-body .top-con {
+  font-weight: bold;
+}
+.table-title {
+  padding: 1rem 1rem 1rem 0;
+  font-size: 1rem;
+  color: #2d2d2d;
+  border-bottom: 1px solid #ccc;
+  display: flex;
+  justify-content: space-between;
+}
+
+.more {
+  color: #ec652d;
+  cursor: pointer;
+}
+
+.top-right {
+  background-color: #ec652d;
+  height: 3.5625rem;
+  line-height: 3.5625rem;
+  color: #fff;
+  padding: 0 1.875rem;
+  border-radius: 0.3125rem;
+  font-size: 1.125rem;
+  font-weight: bold;
+}
+
+.search-body {
+  background-color: #fff;
+  margin: 1.25rem 0;
+}
+
+.interface-table {
+  height: calc(100% - 17.1875rem);
+  overflow-x: hidden;
+  background-color: #fff;
+  padding: 0 1.125rem 2.375rem;
+  box-sizing: border-box;
+}
+
+.zan-con {
+  display: flex;
+  width: 100%;
+  justify-content: space-around;
+  margin-top: 1.5rem;
+  text-align: center;
+  font-size: 1.125rem;
+}
+
+.zan-con .line {
+  height: 8rem;
+  width: 1px;
+  background: #ccc;
+}
+
+.zan-con .zan-contain {
+  display: flex;
+  justify-content: space-between;
+  flex-direction: column;
+}
+
+.zan-con .zan-contain p {
+  font-size: 2.25rem;
+}
+
+.zan-con .zan-contain p:first-child {
+  font-size: 0.875rem;
+  line-height: 1.5;
+}
+
+.zan-sub {
+  text-align: right;
+  margin-top: 1.25rem;
+  color: #a5a5a5;
+  font-size: 0.875rem;
+}
+
+.table-interface {
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem);
+}
+
+.info-top {
+  padding: 20px 0;
+  display: flex;
+  justify-content: space-between;
+  border-bottom: 1px #a5a5a5 solid;
+}
+
+.o-span {
+  cursor: pointer;
+  color: rgb(7, 152, 244);
+}
+
+.body-middle {
+  width: 20%;
+  margin: 40px auto;
+  text-align: center;
+}
+</style>

+ 624 - 0
sh_backstage/src/pages/system/User.vue

@@ -0,0 +1,624 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData"></main-top>
+    <div class="table-interface">
+      <div class="top-body">
+        <div class="info-top">
+          <div class="info-left">
+            <span>按用户状态查看:</span>
+            <el-select style="width:90px;" v-model="region" placeholder="请选择">
+              <el-option label="全部" value></el-option>
+              <el-option label="启用" :value="0"></el-option>
+              <el-option label="停用" :value="1"></el-option>
+              <el-option label="注销" :value="2"></el-option>
+
+            </el-select>
+            <el-input style="width:220px;margin:0 20px;" v-model="inputKey" placeholder="请输入关键字搜索"></el-input>
+            <el-button type="primary" @click="getInformation">查找</el-button>
+            <el-button @click="()=>{inputKey='',region=''}">重置</el-button>
+          </div>
+          <div class="info-right">
+            <el-button type="primary" @click="show('','add')">新增用户</el-button>
+          </div>
+        </div>
+        <el-table :data="tableData" style="width: 100%">
+          <el-table-column
+            v-for="(item, idx) in data"
+            :key="idx"
+            :prop="item.prop"
+            :label="item.label"
+          >
+            <template slot-scope="scope">
+              <img
+                style="width:80px;height:80px;border-radius:50%;"
+                v-if="item.prop === 'ico'"
+                :src="scope.row[item.prop]"
+                alt
+              />
+              <span v-else>{{scope.row[item.prop]||'-'}}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作">
+            <template slot-scope="scope" >
+              <template></template>
+              <span v-if="scope.row.qiyong === '注销'">已注销</span>
+              <template v-else>
+                <span class="o-span" @click="show(scope.row,'edit')">修改</span>
+                <span
+                  class="o-span"
+                  @click="changeState(scope.row)"
+                >{{scope.row.qiyong==='启用'?'停用':'启用'}}</span>
+                <span class="o-span" @click="del(scope.row)">注销</span>
+              </template>
+            </template>
+          </el-table-column>
+        </el-table>
+        <div class="e-pagination">
+          <el-pagination
+            @current-change="handleCurrentChange"
+            :current-page.sync="currentPage"
+            :page-size="size"
+            layout="prev, pager, next, jumper"
+            :total="total"
+          ></el-pagination>
+        </div>
+      </div>
+    </div>
+    <el-dialog :title="editTitle" :visible.sync="dialogFormVisible" width="40%">
+      <div class="add-con">
+        <div class="add-left">
+          <el-form ref="userform" :rules="rules" :model="form">
+            <el-form-item label="用户账号:" prop="userName" :label-width="formLabelWidth">
+              <el-input :disabled="type==='edit'" placeholder="请输入用户账号" v-model="form.userName" autocomplete="off"></el-input>
+            </el-form-item>
+            <el-form-item label="用户角色:"  prop="role" :label-width="formLabelWidth">
+              <el-select v-model="form.role" placeholder="请选择角色">
+                <el-option label="普通管理员" :value="'normal'"></el-option>
+                <el-option label="高级管理员" :value="'admin'"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="手机号码:" prop="phone" :label-width="formLabelWidth">
+              <el-input v-model="form.phone" placeholder="请输入手机号码" autocomplete="off"></el-input>
+            </el-form-item>
+            <el-form-item label="是否启用:" :label-width="formLabelWidth">
+              <el-radio-group v-model="form.status">
+                <el-radio :label="0">是</el-radio>
+                <el-radio :label="1">否</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="add-right">
+          <el-upload
+            class="avatar-uploader"
+            :action="uploadUrl"
+            :headers="{
+              token,
+            }"
+            :show-file-list="false"
+            :on-success="handleAvatarSuccess"
+            :before-upload="beforeAvatarUpload"
+          >
+            <img v-if="form.ico" :src="form.ico" class="avatar" />
+            <i v-else class="el-icon-plus avatar-uploader-icon"></i>
+          </el-upload>
+          <div class="avatar-img">用户头像</div>
+          <div class="avatar-img">
+            <el-button v-if="type==='edit'" @click="reset">重置密码</el-button>
+          </div>
+        </div>
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="save">保 存</el-button>
+        <el-button @click="dialogFormVisible = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from "@/components/main-top";
+const crumbData = [
+  {
+    name: "用户管理",
+    id: 6
+  }
+];
+
+let juese = {
+  admin: "高级管理员",
+  normal: "普通管理员"
+};
+
+let zt = {
+  0: "启用",
+  1: "停用",
+  2: "注销"
+};
+
+
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop
+  },
+  data() {
+    // 这里存放数据
+    let data = [
+      {
+        prop: "idx",
+        label: "序号"
+      },
+      {
+        prop: "ico",
+        label: "用户头像"
+      },
+      {
+        prop: "userName",
+        label: "账号"
+      },
+      {
+        prop: "roleName",
+        label: "角色"
+      },
+      {
+        prop: "phone",
+        label: "手机号"
+      },
+      {
+        prop: "qiyong",
+        label: "状态"
+      }
+    ];
+
+    return {
+      crumbData,
+      data,
+      region: "",
+      infoName: "",
+      tableData: [],
+      dialogTableVisible: false,
+      dialogFormVisible: false,
+      form: {
+        userName: "",
+        roleId: 0,
+        phone: "",
+        head: "",
+        password: "",
+        repeatPassword: ""
+      },
+      inputKey: "",
+      currentPage: 1,
+      size: 10,
+      total: 0,
+      formLabelWidth: "120px",
+      imageUrl: "",
+      type: "add",
+      editTitle: "新增用户",
+      token: window.localStorage.getItem("token"),
+      uploadUrl: `${this.$serverName}manage/common/upload`,
+      rules: {
+        userName: [
+          { required: true, message: "请输入用户账号", trigger: "blur" },
+        ],
+        role: [
+          { required: true, message: "请选择角色", trigger: "change" },
+        ],
+        phone: [{ required: true, message: "请选择漫游位置", trigger: "blur" }],
+      },
+    };
+  },
+  watch: {
+    currentPage() {
+      this.refresh();
+    },
+    size() {
+      this.refresh();
+    },
+    type(newVal) {
+      this.editTitle = newVal === "add" ? "新增用户" : "修改用户信息";
+    }
+  },
+  mounted() {
+    this.refresh();
+  },
+  methods: {
+    reset() {
+      this.$confirm("此操作将重置该用户密码, 是否继续?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      })
+        .then(() => {
+          this.$http.get(`/manage/user/resetPass/${this.form.id}`).then(res => {
+            if (res.code === 0) {
+              this.$alert("重置成功", "提示", {
+                confirmButtonText: "确定",
+                callback: () => {}
+              });
+            } else {
+              this.$notify.error({
+                title: "错误",
+                message: res.msg
+              });
+            }
+          });
+        })
+        .catch(() => {
+          this.$message({
+            type: "info",
+            message: "已取消"
+          });
+        });
+    },
+    goto(item) {
+      window.localStorage.setItem("editInfo", JSON.stringify(item));
+      this.$router.push({ name: "edit-information", params: { type: 1 } });
+      this.$bus.$emit("editinfo", item);
+    },
+    upload_success(data) {
+      this.form.head = data.data;
+    },
+    refresh() {
+      this.loading = true;
+      this.getInformation();
+      this.loading = false;
+    },
+    handleCurrentChange(val) {
+      this.currentPage = val;
+    },
+
+    handleAvatarSuccess(res) {
+      let {data} = res
+      let key = Object.keys(data)[0]
+      let val = data[key]
+      
+      this.form.ico = val;
+    },
+    beforeAvatarUpload(file) {
+      const isJPG = file.type === "image/jpeg";
+      const isLt2M = file.size / 1024 / 1024 < 2;
+
+      if (!isJPG) {
+        this.$message.error("上传头像图片只能是 JPG 格式!");
+      }
+      if (!isLt2M) {
+        this.$message.error("上传头像图片大小不能超过 2MB!");
+      }
+      return isJPG && isLt2M;
+    },
+
+    async save() {
+      this.$refs["userform"].validate(async (valid) => {
+        if (valid) {
+        let { ico, id, phone, role, status, userName } = this.form;
+        if (!ico) {
+          return this.$alert("头像不能为空", "提示", {
+            confirmButtonText: "确定",
+          });
+        }
+          let params = {
+            ico,
+            id,
+            phone,
+            role,
+            status,
+            userName
+          };
+          let result = await this.$http({
+            method: "post",
+            data: params,
+            headers: {
+              token: window.localStorage.getItem("token")
+            },
+            url: `/manage/user/save`
+          });
+
+          if (result.code === 0) {
+            this.$alert("保存成功", "提示", {
+              confirmButtonText: "确定",
+              callback: () => {
+                this.getInformation()
+              }
+            });
+          } else {
+            this.$notify.error({
+              title: "错误",
+              message: result.msg
+            });
+          }
+        }
+      })
+     
+    },
+    show(item = "", type) {
+      this.dialogFormVisible = true;
+      this.type = type;
+      this.form = {
+        createTime: "2020-05-09 14:47:44",
+        ico: "",
+        id: "",
+        phone: "",
+        role: "",
+        status: 1,
+        updateTime: "",
+        userName: ""
+      };
+
+      if (type === "edit") {
+        this.form = item;
+      }
+    },
+    changeState(item) {
+      let status = item.status === 1 || item.status === 2 ? 0 : 1;
+
+      this.$confirm("此操作将导致用户状态发生改变, 是否继续?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      })
+        .then(() => {
+          this.$http
+            .get(`/manage/user/setStatus/${item.id}/${status}`, {
+              headers: {
+                token: window.localStorage.getItem("token")
+              }
+            })
+            .then(res => {
+              if (res.code === 0) {
+                this.$alert("修改成功", "提示", {
+                  confirmButtonText: "确定",
+                  callback: () => {
+                    this.refresh();
+                  }
+                });
+              } else {
+                this.$notify.error({
+                  title: "错误",
+                  message: res.msg
+                });
+              }
+            });
+        })
+        .catch(() => {
+          this.$message({
+            type: "info",
+            message: "已取消"
+          });
+        });
+    },
+
+    del(item) {
+      this.$confirm("此操作将注销该用户, 是否继续?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      })
+        .then(() => {
+          this.$http
+            .get(`/manage/user/setStatus/${item.id}/2`, {
+              headers: {
+                token: window.localStorage.getItem("token")
+              }
+            })
+            .then(res => {
+              if (res.code === 0) {
+                this.$alert("注销成功", "提示", {
+                  confirmButtonText: "确定",
+                  callback: () => {
+                    this.refresh();
+                  }
+                });
+              } else {
+                this.$notify.error({
+                  title: "错误",
+                  message: res.msg
+                });
+              }
+            });
+        })
+        .catch(() => {
+          this.$message({
+            type: "info",
+            message: "已取消删除"
+          });
+        });
+    },
+
+    async getInformation() {
+      this.dialogFormVisible = false
+      let params = {
+        pageNum: this.currentPage,
+        pageSize: this.size,
+        searchKey: this.inputKey,
+        status:this.region
+      };
+
+      let result = await this.$http({
+        method: "post",
+        data: params,
+        headers: {
+          token: window.localStorage.getItem("token")
+        },
+        url: "/manage/user/list"
+      });
+
+      if (result.code !== 0) {
+        return;
+      }
+      this.tableData = result.data.list;
+      this.total = result.data.total;
+      this.tableData.forEach((item, i) => {
+        item["date"] =
+          this.$base.timestampToTime(item["startTime"]) +
+          "至" +
+          this.$base.timestampToTime(item["endTime"]);
+        item["roleName"] = juese[item["role"]];
+        item["qiyong"] = zt[item["status"]];
+        item["idx"] = i + 1;
+      });
+    }
+  }
+};
+</script>
+
+<style lang="less" scoped>
+.top-body {
+  border-top: 0.0625rem solid #e6e6e6;
+  line-height: 1.5;
+  padding: 0 1.25rem 1.25rem;
+  align-items: center;
+  box-sizing: border-box;
+  background: #fff;
+  margin: 1rem 0;
+}
+
+.top-body .top-con {
+  font-weight: bold;
+}
+.table-title {
+  padding: 1rem 1rem 1rem 0;
+  font-size: 1rem;
+  color: #2d2d2d;
+  border-bottom: 1px solid #ccc;
+  display: flex;
+  justify-content: space-between;
+}
+
+.more {
+  color: #ec652d;
+  cursor: pointer;
+}
+
+.top-right {
+  background-color: #ec652d;
+  height: 3.5625rem;
+  line-height: 3.5625rem;
+  color: #fff;
+  padding: 0 1.875rem;
+  border-radius: 0.3125rem;
+  font-size: 1.125rem;
+  font-weight: bold;
+}
+
+.search-body {
+  background-color: #fff;
+  margin: 1.25rem 0;
+}
+
+.interface-table {
+  height: calc(100% - 17.1875rem);
+  overflow-x: hidden;
+  background-color: #fff;
+  padding: 0 1.125rem 2.375rem;
+  box-sizing: border-box;
+}
+
+.zan-con {
+  display: flex;
+  width: 100%;
+  justify-content: space-around;
+  margin-top: 1.5rem;
+  text-align: center;
+  font-size: 1.125rem;
+}
+
+.zan-con .line {
+  height: 8rem;
+  width: 1px;
+  background: #ccc;
+}
+
+.zan-con .zan-contain {
+  display: flex;
+  justify-content: space-between;
+  flex-direction: column;
+}
+
+.zan-con .zan-contain p {
+  font-size: 2.25rem;
+}
+
+.zan-con .zan-contain p:first-child {
+  font-size: 0.875rem;
+  line-height: 1.5;
+}
+
+.zan-sub {
+  text-align: right;
+  margin-top: 1.25rem;
+  color: #a5a5a5;
+  font-size: 0.875rem;
+}
+
+.table-interface {
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem);
+}
+
+.info-top {
+  padding: 20px 0;
+  display: flex;
+  justify-content: space-between;
+  border-bottom: 1px #a5a5a5 solid;
+}
+
+.o-span {
+  cursor: pointer;
+  color: rgb(7, 152, 244);
+}
+
+.add-con {
+  display: flex;
+  justify-content: space-between;
+}
+
+.add-left {
+  flex: 4;
+}
+
+.add-right {
+  flex: 1;
+  margin-left: 10%;
+}
+
+.avatar-img {
+  text-align: center;
+  margin-top: 10px;
+}
+
+.dialog-footer {
+  text-align: center;
+}
+</style>
+
+<style>
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #409eff;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 178px;
+  height: 178px;
+  line-height: 178px;
+  text-align: center;
+}
+.avatar {
+  width: 178px;
+  height: 178px;
+  display: block;
+}
+</style>

+ 264 - 0
sh_backstage/src/pages/system/Worklog.vue

@@ -0,0 +1,264 @@
+<!--  -->
+<template>
+  <div>
+    <main-top :crumb="crumbData"></main-top>
+    <div class="table-interface">
+      <div class="top-body">
+        <div class="info-top">
+          <div class="info-left">
+            <el-input style="width:220px;margin:0 20px;" v-model="inputKey" placeholder="请输入关键字"></el-input>
+            <el-button type="primary" @click="refresh">查找</el-button>
+            <el-button @click="inputKey = ''">重置</el-button>
+          </div>
+        </div>
+        <el-table :data="tableData" style="width: 100%">
+          <el-table-column
+            v-for="(item, idx) in data"
+            :key="idx"
+            :prop="item.prop"
+            :label="item.label"
+          >
+          <template slot-scope="scope">
+            <span>{{scope.row[item.prop]||'-'}}</span>
+          </template>
+          </el-table-column>
+        </el-table>
+        <div class="e-pagination">
+          <el-pagination
+            @current-change="handleCurrentChange"
+            :current-page.sync="currentPage"
+            :page-size="size"
+            layout="prev, pager, next, jumper"
+            :total="total"
+          ></el-pagination>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+// 这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
+// 例如:import 《组件名称》 from '《组件路径》';
+
+import MainTop from '@/components/main-top'
+const crumbData = [
+  {
+    name: '工作日志',
+    id: 7
+  }
+]
+
+
+const TYPENAME = {
+  'part':'部件信息管理',
+  'building':'建筑物信息管理'
+
+}
+
+const STATUSNAME = {
+  1:'待审核',
+  2:'已同意',
+  3:'已驳回'
+}
+export default {
+  // import引入的组件需要注入到对象中才能使用
+  components: {
+    MainTop
+  },
+  watch: {
+    currentPage () {
+      this.refresh()
+    },
+    size () {
+      this.refresh()
+    }
+  },
+  mounted () {
+    this.refresh()
+  },
+  methods:{
+     refresh () {
+      this.loading = true
+      this.getInformation()
+      this.loading = false
+    },
+     handleCurrentChange (val) {
+      this.currentPage = val
+    },
+     async getInformation () {
+      let params = {
+          pageNum:this.currentPage,
+          pageSize: this.size,
+          searchKey: this.inputKey
+      }
+
+      let result = await this.$http({
+        method: 'post',
+        data: params,
+        headers: {
+          token: window.localStorage.getItem('token')
+        },
+        url: '/manage/log/list'
+      })
+
+      if (result.code !== 0) {
+        return
+      }
+      this.tableData = result.data.list
+      this.total = result.data.total
+      this.tableData.forEach((item, i) => {
+        item['idx'] = i + 1
+        item['typeName'] = TYPENAME[item.type]
+        item['statusStr'] = STATUSNAME[item.status]
+      })
+    }
+  },
+  data () {
+    // 这里存放数据
+    let data = [
+      {
+        prop: 'idx',
+        label: '序号'
+      },
+      {
+        prop: 'userName',
+        label: '账号'
+      },
+      {
+        prop: 'type',
+        label: '操作模块'
+      },
+      {
+        prop: 'description',
+        label: '操作事件'
+      },
+      {
+        prop: 'createTime',
+        label: '操作时间'
+      }
+    ]
+
+    return {
+      crumbData,
+      data,
+      region: 'all',
+      region1: 'all',
+      inputKey: '',
+      tableData: [],
+      currentPage: 1,
+      size: 10,
+      total:0
+    }
+  }
+}
+</script>
+
+<style lang="less" scoped>
+.top-body{
+  border-top: .0625rem solid #e6e6e6;
+  line-height: 1.5;
+  padding: 0 1.25rem 1.25rem;
+  align-items: center;
+  box-sizing: border-box;
+  background: #fff;
+  margin: 1rem 0;
+}
+
+.top-body .top-con{
+  font-weight: bold;
+}
+.table-title{
+  padding: 1rem 1rem 1rem 0 ;
+  font-size: 1rem;
+  color: #2d2d2d;
+  border-bottom: 1px solid #ccc;
+  display: flex;
+  justify-content: space-between;
+}
+
+.more{
+  color: #ec652d;
+  cursor: pointer;
+}
+
+.top-right{
+  background-color: #ec652d;
+  height: 3.5625rem;
+  line-height: 3.5625rem;
+  color: #fff;
+  padding: 0 1.875rem;
+  border-radius: .3125rem;
+  font-size: 1.125rem;
+  font-weight: bold;
+}
+
+.search-body{
+  background-color: #fff;
+  margin: 1.25rem 0;
+}
+
+.interface-table{
+  height: calc(100% - 17.1875rem);
+  overflow-x: hidden;
+  background-color: #fff;
+  padding: 0 1.125rem 2.375rem;
+  box-sizing: border-box;
+}
+
+.zan-con{
+  display: flex;
+  width: 100%;
+  justify-content: space-around;
+  margin-top: 1.5rem;
+  text-align: center;
+  font-size: 1.125rem;
+}
+
+.zan-con .line{
+  height: 8rem;
+  width: 1px;
+  background: #ccc;
+}
+
+.zan-con .zan-contain{
+  display: flex;
+  justify-content: space-between;
+  flex-direction: column;
+}
+
+.zan-con .zan-contain p{
+  font-size: 2.25rem;
+}
+
+.zan-con .zan-contain p:first-child{
+  font-size: .875rem;
+  line-height: 1.5;
+}
+
+.zan-sub{
+  text-align: right;
+  margin-top: 1.25rem;
+  color: #a5a5a5;
+  font-size: .875rem;
+}
+
+.table-interface{
+  overflow-y: auto;
+  overflow-x: hidden;
+  height: calc(100% - 3rem);
+}
+
+
+.info-top{
+  padding: 20px 0;
+  display: flex;
+  justify-content: space-between;
+  border-bottom: 1px #a5a5a5 solid;
+}
+
+.o-span{
+  cursor: pointer;
+  color: rgb(7, 152, 244);
+}
+</style>

+ 134 - 0
sh_backstage/src/router/index.js

@@ -0,0 +1,134 @@
+import Vue from "vue";
+import VueRouter from "vue-router";
+
+Vue.use(VueRouter);
+const originalPush = VueRouter.prototype.push;
+VueRouter.prototype.push = function push(location) {
+  return originalPush.call(this, location).catch((err) => err);
+};
+
+const routes = [
+  {
+    path: "/",
+    name: "Layout",
+    component: () => import( "../pages/layout/index.vue"),
+    children:[
+      {
+        path:'/',
+        name: 'home',
+        component:  () => import( "../pages/home/index.vue"),
+        meta: {index: -1}
+      },
+      {
+        path:'/architecture',
+        name: 'architecture',
+        component:  () => import( "../pages/content/Architecture.vue"),
+        meta: {index: 2}
+      },
+      {
+        path:'/edit-architecture/:type',
+        name: 'edit-architecture',
+        component:  () => import( "../pages/edit/architecture.vue"),
+        meta: {index: 2}
+      },
+      {
+        path:'/parts',
+        name: 'parts',
+        component:  () => import( "../pages/content/Parts.vue"),
+        meta: {index: 3}
+      },
+      {
+        path:'/edit-parts/:type/:id',
+        name: 'edit-parts',
+        component:  () => import( "../pages/edit/parts.vue"),
+        meta: {index: 3}
+      },
+      {
+        path: '/show-parts/:id',
+        name: 'show-parts',
+        component:  () => import( "../pages/show/parts.vue"),
+        meta: {index: 3}
+      },
+      
+      {
+        path:'/roam',
+        name: 'roam',
+        component:  () => import( "../pages/content/Roam.vue"),
+        meta: {index: 4}
+      },
+      {
+        path: '/show-roam/:id',
+        name: 'show-roam',
+        component:  () => import( "../pages/show/roam.vue"),
+        meta: {index: 4}
+      },
+      {
+        path:'/edit-roam/:type/:id',
+        name: 'edit-roam',
+        component:  () => import( "../pages/edit/roam.vue"),
+        meta: {index: 4}
+      },
+
+      {
+        path:'/structure',
+        name: 'structure',
+        component:  () => import( "../pages/content/Structure.vue"),
+        meta: {index: 5}
+      },
+      {
+        path: '/show-structure/:id',
+        name: 'show-structure',
+        component:  () => import( "../pages/show/structure.vue"),
+        meta: {index: 5}
+      },
+      {
+        path:'/edit-structure/:type/:id',
+        name: 'edit-structure',
+        component:  () => import( "../pages/edit/structure.vue"),
+        meta: {index: 5}
+      },
+
+      {
+        path:'/comment',
+        name: 'comment',
+        component:  () => import( "../pages/content/Comment.vue"),
+        meta: {index: 6}
+      },
+      {
+        path:'/user',
+        name: 'user',
+        component:  () => import( "../pages/system/User.vue"),
+        meta: {index: 7}
+      },
+      {
+        path:'/download',
+        name: 'download',
+        component:  () => import( "../pages/system/Download.vue"),
+        meta: {index: 8}
+      },
+      {
+        path:'/worklog',
+        name: 'worklog',
+        component:  () => import( "../pages/system/Worklog.vue"),
+        meta: {index: 9}
+      },
+      {
+        path:'/password',
+        name: 'password',
+        component:  () => import( "../pages/system/Password.vue"),
+        meta: {index: 10}
+      }
+    ]
+  },
+  {
+    path: '/login',
+    name: 'login',
+    component: () => import( "../pages/login/")
+  }
+];
+
+const router = new VueRouter({
+  routes,
+});
+
+export default router;

+ 122 - 0
sh_backstage/src/util/index.js

@@ -0,0 +1,122 @@
+function randomWord (randomFlag, min, max) {
+  let str = ''
+  let range = min
+  let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+  // 随机产生
+  if (randomFlag) {
+    range = Math.round(Math.random() * (max - min)) + min
+  }
+  for (var i = 0; i < range; i++) {
+    let pos = Math.round(Math.random() * (arr.length - 1))
+    str += arr[pos]
+  }
+  return str
+}
+module.exports = {
+  getPosition: function (dom, parent) {
+    var ret = {
+      x: 0,
+      y: 0
+    }
+    while (dom && dom !== parent && dom !== document.documentElement) {
+      ret.x += dom.offsetLeft
+      ret.y += dom.offsetTop
+      dom = dom.offsetParent
+    }
+    return ret
+  },
+  reg: {
+    idCard: /^\d{6}(18|19|20)?\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}(\d|[xX])$/,
+    phone: /^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$/,
+    email: /^[A-Za-z\d]+([_.-][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/,
+    guhua: /^(\(\d{3,4}\)|\d{3,4}-|\s)?\d{7,14}$/
+  },
+  isEmptyObject: function (obj) {
+    for (var key in obj) {
+      return false
+    }
+    return true
+  },
+  
+  isWide: function () {
+    return Math.round(devicePixelRatio * 100) === 100 ? window.innerWidth > 1600 : true
+  },
+  isWorkPhone: function (phone) {
+    let phones = [
+      /^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$/,
+      /^(\+?213|0)(5|6|7)\d{8}$/,
+      /^(!?(\+?963)|0)?9\d{8}$/,
+      /^(!?(\+?966)|0)?5\d{8}$/,
+      /^(\+?1)?[2-9]\d{2}[2-9](?!11)\d{6}$/,
+      /^(\+?420)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/,
+      /^(\+?49[ .-])?([(]{1}[0-9]{1,6}[)])?([0-9 .-/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/,
+      /^(\+?45)?(\d{8})$/,
+      /^(\+?30)?(69\d{8})$/,
+      /^(\+?61|0)4\d{8}$/,
+      /^(\+?44|0)7\d{9}$/,
+      /^(\+?852-?)?[569]\d{3}-?\d{4}$/,
+      /^(\+?91|0)?[789]\d{9}$/,
+      /^(\+?64|0)2\d{7,9}$/,
+      /^(\+?27|0)\d{9}$/,
+      /^(\+?26)?09[567]\d{7}$/,
+      /^(\+?34)?(6\d{1}|7[1234])\d{7}$/,
+      /^(\+?358|0)\s?(4(0|1|2|4|5)?|50)\s?(\d\s?){4,8}\d$/,
+      /^(\+?33|0)[67]\d{8}$/,
+      /^(\+972|0)([23489]|5[0248]|77)[1-9]\d{6}/,
+      /^(\+?36)(20|30|70)\d{7}$/,
+      /^(\+?39)?\s?3\d{2} ?\d{6,7}$/,
+      /^(\+?81|0)\d{1,4}[ -]?\d{1,4}[ -]?\d{4}$/,
+      /^(\+?6?01){1}(([145]{1}(-|\s)?\d{7,8})|([236789]{1}(\s|-)?\d{7}))$/,
+      /^(\+?47)?[49]\d{7}$/,
+      /^(\+?32|0)4?\d{8}$/,
+      /^(\+?47)?[49]\d{7}$/,
+      /^(\+?48)? ?[5-8]\d ?\d{3} ?\d{2} ?\d{2}$/,
+      /^(\+?55|0)-?[1-9]{2}-?[2-9]{1}\d{3,4}-?\d{4}$/,
+      /^(\+?351)?9[1236]\d{7}$/,
+      /^(\+?7|8)?9\d{9}$/,
+      /^(\+3816|06)[- \d]{5,9}$/,
+      /^(\+?90|0)?5\d{9}$/,
+      /^(\+?84|0)?((1(2([0-9])|6([2-9])|88|99))|(9((?!5)[0-9])))([0-9]{7})$/,
+      /^(\+?0?86-?)?1[345789]\d{9}$/,
+      /^(\+?886-?|0)?9\d{8}$/
+    ]
+    for (let i = 0; i < phones.length; i++) {
+      if (phones[i].test(phone)) {
+        return true
+      }
+    }
+    return false
+  },
+  formatDate: function (time) {
+    var date = new Date(time)
+    var year = date.getFullYear()
+    var month = date.getMonth() + 1
+    var day = date.getDate()
+    // var hour = date.getHours()
+    // var minute = date.getMinutes()
+    // var second = date.getSeconds()
+    //  return year+"-"+month+"-"+date+" "+hour+":"+minute+":"+second;
+    return year + '-' + (String(month).length > 1 ? month : '0' + month) + '-' +
+      (String(day).length > 1 ? day : '0' + day)
+      // + ' ' + (String(hour).length > 1 ? hour : '0' + hour) + ':' + (String(minute).length > 1 ? minute : '0' + minute) +
+      //  ':' + (String(second).length > 1 ? second : '0' + second)
+  },
+
+  encodeStr: function (str, strv = '') {
+    const NUM = 2
+    const front = randomWord(false, 8)
+    const middle = randomWord(false, 8)
+    const end = randomWord(false, 8)
+
+    let str1 = str.substring(0, NUM)
+    let str2 = str.substring(NUM)
+
+    if (strv) {
+      let strv1 = strv.substring(0, NUM)
+      let strv2 = strv.substring(NUM)
+      return [front + str2 + middle + str1 + end, front + strv2 + middle + strv1 + end]
+    }
+
+    return front + str2 + middle + str1 + end
+  }
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/alert.css


+ 1 - 0
sh_backstage/theme/aside.css

@@ -0,0 +1 @@
+.el-aside{overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/autocomplete.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/avatar.css


+ 1 - 0
sh_backstage/theme/backtop.css

@@ -0,0 +1 @@
+.el-backtop{position:fixed;background-color:#FFF;width:40px;height:40px;border-radius:50%;color:#532F1C;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;font-size:20px;-webkit-box-shadow:0 0 6px rgba(0,0,0,.12);box-shadow:0 0 6px rgba(0,0,0,.12);cursor:pointer;z-index:5}.el-backtop:hover{background-color:#F2F6FC}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/badge.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/base.css


+ 0 - 0
sh_backstage/theme/breadcrumb-item.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/breadcrumb.css


+ 0 - 0
sh_backstage/theme/button-group.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/button.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/calendar.css


+ 1 - 0
sh_backstage/theme/card.css

@@ -0,0 +1 @@
+.el-card{border-radius:4px;border:1px solid #EBEEF5;background-color:#FFF;overflow:hidden;color:#303133;-webkit-transition:.3s;transition:.3s}.el-card.is-always-shadow,.el-card.is-hover-shadow:focus,.el-card.is-hover-shadow:hover{-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-card__header{padding:18px 20px;border-bottom:1px solid #EBEEF5;-webkit-box-sizing:border-box;box-sizing:border-box}.el-card__body{padding:20px}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/carousel-item.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/carousel.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/cascader-panel.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/cascader.css


+ 0 - 0
sh_backstage/theme/checkbox-button.css


+ 0 - 0
sh_backstage/theme/checkbox-group.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/checkbox.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/col.css


+ 0 - 0
sh_backstage/theme/collapse-item.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/collapse.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/color-picker.css


+ 1 - 0
sh_backstage/theme/container.css

@@ -0,0 +1 @@
+.el-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;-webkit-box-sizing:border-box;box-sizing:border-box;min-width:0}.el-container.is-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/date-picker.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/dialog.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/display.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
sh_backstage/theme/divider.css


+ 0 - 0
sh_backstage/theme/drawer.css


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor