tremble il y a 2 ans
commit
3591f90813
100 fichiers modifiés avec 36127 ajouts et 0 suppressions
  1. 6 0
      .env
  2. 3 0
      .env.development
  3. 6 0
      .env.test
  4. 6 0
      .env.uat
  5. 23 0
      .gitignore
  6. 24 0
      README.md
  7. 5 0
      babel.config.js
  8. 27905 0
      package-lock.json
  9. 60 0
      package.json
  10. BIN
      public/favicon.png
  11. 23 0
      public/index.html
  12. 13 0
      src/App.vue
  13. 4 0
      src/app.js
  14. BIN
      src/assets/image/20220612_163209615.png
  15. BIN
      src/assets/image/bg.jpg
  16. BIN
      src/assets/image/close.png
  17. BIN
      src/assets/image/code.png
  18. BIN
      src/assets/image/edit_type_panorama.png
  19. BIN
      src/assets/image/folder-bg.png
  20. BIN
      src/assets/image/folder.png
  21. BIN
      src/assets/image/icon_add.png
  22. BIN
      src/assets/image/img_loginbg.png
  23. BIN
      src/assets/image/list_arrow.png
  24. BIN
      src/assets/image/logo@2x.png
  25. BIN
      src/assets/image/menu_vrhouse.png
  26. BIN
      src/assets/image/page_arrow.png
  27. BIN
      src/assets/image/photoview.png
  28. BIN
      src/assets/image/scene.png
  29. BIN
      src/assets/image/scene_error.png
  30. BIN
      src/assets/image/scene_map_3d.png
  31. BIN
      src/assets/image/scene_share.png
  32. BIN
      src/assets/image/top_exit.png
  33. BIN
      src/assets/image/top_my.png
  34. BIN
      src/assets/image/top_set.png
  35. BIN
      src/assets/image/vrmodel-calc.png
  36. BIN
      src/assets/image/vrmodel-err.png
  37. BIN
      src/assets/image/vrmodel-fengcun.png
  38. 360 0
      src/assets/style/public.scss
  39. 42 0
      src/components/common-select/index.vue
  40. 68 0
      src/components/company-select/index.vue
  41. 61 0
      src/components/dept-select/index.vue
  42. 60 0
      src/components/dialog/index.vue
  43. 49 0
      src/components/dialog/style.scss
  44. 117 0
      src/components/head/index.vue
  45. 135 0
      src/components/pagination/index.vue
  46. 101 0
      src/components/previewItem/index.vue
  47. 81 0
      src/components/record/index.vue
  48. 51 0
      src/components/region/index.vue
  49. 58 0
      src/components/role-select/index.vue
  50. 58 0
      src/components/select.js
  51. 161 0
      src/components/share/index.vue
  52. 120 0
      src/components/upload-com/index.vue
  53. 117 0
      src/components/upload-video/index.vue
  54. 14 0
      src/constant/REG.js
  55. 237 0
      src/constant/index.js
  56. 55 0
      src/constant/ssrLists.js
  57. 13 0
      src/constant/view.js
  58. 13 0
      src/main.js
  59. 25 0
      src/request/authentication.js
  60. 434 0
      src/request/config.js
  61. 18 0
      src/request/errorMsg.js
  62. 54 0
      src/request/loading.js
  63. 141 0
      src/request/normalizeRedirct.js
  64. 127 0
      src/request/setupAxios.js
  65. 192 0
      src/request/simulation.dev.js
  66. 110 0
      src/router/index.js
  67. 70 0
      src/state/navs.js
  68. 285 0
      src/state/tableRef.js
  69. 57 0
      src/state/user.js
  70. 36 0
      src/state/viewAuth.js
  71. 5 0
      src/util/file.js
  72. 112 0
      src/util/index.js
  73. 219 0
      src/view/camera/index.vue
  74. 469 0
      src/view/examine/estate/index.vue
  75. 311 0
      src/view/examine/other/community/body.vue
  76. 149 0
      src/view/examine/other/community/head.vue
  77. 123 0
      src/view/examine/other/community/state.js
  78. 65 0
      src/view/examine/other/layout.vue
  79. 308 0
      src/view/examine/other/second-house/body.vue
  80. 137 0
      src/view/examine/other/second-house/head.vue
  81. 127 0
      src/view/examine/other/second-house/state.js
  82. 161 0
      src/view/firmware/index.vue
  83. 179 0
      src/view/framework/index.vue
  84. 82 0
      src/view/layout/index.vue
  85. 64 0
      src/view/layout/player/index.vue
  86. 27 0
      src/view/layout/player/style.scss
  87. 55 0
      src/view/layout/slide/index.vue
  88. 24 0
      src/view/layout/slide/submenu.vue
  89. 3 0
      src/view/layout/structure.vue
  90. 98 0
      src/view/layout/top/index.vue
  91. 57 0
      src/view/layout/top/style.scss
  92. 147 0
      src/view/log/index.vue
  93. 211 0
      src/view/login/index.vue
  94. 0 0
      src/view/login/style.css
  95. 572 0
      src/view/properties/community/index.vue
  96. 82 0
      src/view/properties/community/record.vue
  97. 595 0
      src/view/properties/community/second-house/index.vue
  98. 71 0
      src/view/properties/community/second-house/queryvr.vue
  99. 76 0
      src/view/properties/community/second-house/record.vue
  100. 0 0
      src/view/properties/community/second-house/vrlist.vue

+ 6 - 0
.env

@@ -0,0 +1,6 @@
+VUE_APP_INTRANET_DOMAIN=
+VUE_APP_PREFIX=
+VUE_APP_DOMAIN=
+VUE_APP_SHOW_LINK=https://zhongmian.4dage.com/
+VUE_APP_SHOWVIEW_LINK=https://zhongmian.4dage.com/cdf/
+

+ 3 - 0
.env.development

@@ -0,0 +1,3 @@
+VUE_APP_INTRANET_DOMAIN=
+VUE_APP_PREFIX=
+VUE_APP_DOMAIN=

+ 6 - 0
.env.test

@@ -0,0 +1,6 @@
+VUE_APP_INTRANET_DOMAIN=
+VUE_APP_PREFIX=
+VUE_APP_DOMAIN=
+VUE_APP_SHOW_LINK=https://zhongmian.4dage.com/
+VUE_APP_SHOW_HK_LINK=https://zhongmian.4dage.com/
+VUE_APP_SHOWVIEW_LINK=https://zhongmian.4dage.com/cdf/

+ 6 - 0
.env.uat

@@ -0,0 +1,6 @@
+VUE_APP_INTRANET_DOMAIN=
+VUE_APP_PREFIX=
+VUE_APP_DOMAIN=
+VUE_APP_SHOW_LINK=https://vr.cdfmembers.com/
+VUE_APP_SHOW_HK_LINK=https://vr.dutyzero.com.hk/
+VUE_APP_SHOWVIEW_LINK=http://vr-admin.cdfmembers.com/cdf/

+ 23 - 0
.gitignore

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

+ 24 - 0
README.md

@@ -0,0 +1,24 @@
+# hd_vr_house_back
+
+## 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
babel.config.js

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

Fichier diff supprimé car celui-ci est trop grand
+ 27905 - 0
package-lock.json


+ 60 - 0
package.json

@@ -0,0 +1,60 @@
+{
+  "name": "hd_vr_house_back",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "build-test": "vue-cli-service build --mode test",
+    "build-uat": "vue-cli-service build --mode uat",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "axios": "^0.21.1",
+    "Base64": "^1.1.0",
+    "core-js": "^3.6.5",
+    "element-plus": "^1.0.1-beta.20",
+    "element-ui": "^2.14.1",
+    "js-base64": "^3.7.2",
+    "js-file-download": "^0.4.12",
+    "qrcode": "^1.4.4",
+    "qs": "^6.9.4",
+    "vue": "^3.0.0-beta.1",
+    "vue-router": "^4.0.2"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-plugin-eslint": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
+    "@vue/compiler-sfc": "^3.0.0-beta.1",
+    "babel-eslint": "^10.1.0",
+    "eslint": "^6.7.2",
+    "eslint-plugin-vue": "^7.0.0-alpha.0",
+    "less": "^4.0.0",
+    "less-loader": "^7.2.1",
+    "sass": "^1.32.0",
+    "sass-loader": "^10.1.0",
+    "style-resources-loader": "^1.3.2",
+    "vue-cli-plugin-style-resources-loader": "~0.1.4",
+    "vue-cli-plugin-vue-next": "~0.1.4"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/vue3-essential",
+      "eslint:recommended"
+    ],
+    "parserOptions": {
+      "parser": "babel-eslint"
+    },
+    "rules": {}
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead"
+  ]
+}

BIN
public/favicon.png


+ 23 - 0
public/index.html

@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html lang="zh">
+
+<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>中免VR商城管理後臺 </title>
+</head>
+
+<body>
+  <noscript>
+    <strong>We're sorry but 中免VR商城管理後臺 doesn't work properly without JavaScript enabled. Please enable it to
+      continue.</strong>
+  </noscript>
+  <div id="app"></div>
+  <div id="dialog"></div>
+  <!-- built files will be auto injected -->
+</body>
+</html>
+
+

+ 13 - 0
src/App.vue

@@ -0,0 +1,13 @@
+<template>
+  <router-view></router-view>
+</template>
+
+<script>
+import { setApp } from './app'
+
+export default {
+  mounted() {
+    setApp(this)
+  }
+}
+</script>

+ 4 - 0
src/app.js

@@ -0,0 +1,4 @@
+let app = null
+
+export const getApp = () => app
+export const setApp = a => app = a

BIN
src/assets/image/20220612_163209615.png


BIN
src/assets/image/bg.jpg


BIN
src/assets/image/close.png


BIN
src/assets/image/code.png


BIN
src/assets/image/edit_type_panorama.png


BIN
src/assets/image/folder-bg.png


BIN
src/assets/image/folder.png


BIN
src/assets/image/icon_add.png


BIN
src/assets/image/img_loginbg.png


BIN
src/assets/image/list_arrow.png


BIN
src/assets/image/logo@2x.png


BIN
src/assets/image/menu_vrhouse.png


BIN
src/assets/image/page_arrow.png


BIN
src/assets/image/photoview.png


BIN
src/assets/image/scene.png


BIN
src/assets/image/scene_error.png


BIN
src/assets/image/scene_map_3d.png


BIN
src/assets/image/scene_share.png


BIN
src/assets/image/top_exit.png


BIN
src/assets/image/top_my.png


BIN
src/assets/image/top_set.png


BIN
src/assets/image/vrmodel-calc.png


BIN
src/assets/image/vrmodel-err.png


BIN
src/assets/image/vrmodel-fengcun.png


+ 360 - 0
src/assets/style/public.scss

@@ -0,0 +1,360 @@
+/* 改变主题色变量 */
+$--color-primary: #D6000F;
+
+/* 改变 icon 字体路径变量,必需 */
+$--font-path: '~element-ui/lib/theme-chalk/fonts';
+
+@import "~element-ui/packages/theme-chalk/src/index";
+
+* {
+  margin: 0;
+  padding: 0;
+}
+
+html, body {
+  width: 100%;
+  height: 100%;
+}
+
+body {
+  --primaryColor: #D6000F;
+  --colorColor: #303133;
+  --bgColor: #f0f2f5;
+
+  font-size: 14px;
+  font-family: 'Microsoft YaHei';
+  color: var(--colorColor);
+}
+
+.fill.el-button {
+  width: 100%;
+}
+
+.slide {
+  .el-menu {
+    border-right: none;
+    background: #000;
+  }
+
+  .el-menu-item.is-active,
+  .el-menu-item:hover, 
+  .el-menu-item:focus,
+  .el-submenu__title:hover {
+    background: var(--primaryColor);
+  }
+
+  
+  .el-menu-item,
+  .el-menu-item i {
+    color: #fff !important;
+  }
+
+  .el-submenu__title {
+    background-color: #0C0E2E !important;
+  }
+
+  .el-submenu__title,
+  .el-submenu__title i{
+    color: rgb(191,191,191) !important;
+  }
+
+  .el-submenu i {
+    vertical-align: baseline;
+  }
+}
+
+.head-layer {
+  flex: 0 0 auto;
+
+}
+
+.body-layer {
+  flex: 1;
+  overflow-y: auto;
+  margin-top: 8px;
+  background-color: #fff;
+  border-radius: 4px;;
+  padding: 16px;
+  display: flex;
+  flex-direction: column;
+
+  .body-head {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+
+    h3 {
+      font-size: 1rem;
+      font-weight: normal;
+      color: var(--colorColor);
+    }
+  }
+
+  > * {
+    flex: 0 0 auto;
+  }
+  > .el-table {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+
+    > * {
+      flex: 0 0 auto;
+    }
+
+    .el-table__body-wrapper {
+      flex: 1;
+      overflow-y: auto;
+    }
+  }
+}
+
+.el-table th {
+  background: #FAFAFA;
+  .cell {
+    font-size: 0.825rem;
+    color: var(--colorColor);
+    font-weight: normal;
+  }
+}
+
+
+.pag-block {
+  margin-top: 16px;
+  .el-pagination {
+    display: flex;
+  }
+
+  &.no-sizes {
+     .el-pagination__total {
+        flex: 1;
+        text-align: left;
+     }
+  }
+  .el-pagination__sizes {
+    flex: 1;
+    text-align: left;
+  }
+
+}
+
+
+.oper-span {
+  color: #3366FF;
+  font-size: 0.825rem;
+  margin-right: 8px;
+
+  &.disable {
+    color: #BFBFBF;
+  }
+
+  &:not(.disable) {
+    cursor: pointer;
+  }
+}
+
+
+
+.el-dropdown-menu__item {
+  word-break: keep-all;
+}
+
+.head-content {
+  .el-form {
+    display: grid;
+    grid-gap: 20px;
+    grid-template-columns: repeat(3, 1fr);
+  }
+  .el-form-item {
+    display: flex;
+    flex: 0 0 auto;
+  }
+  .el-form-item__label {
+    flex: 0 0 auto;
+  }
+  .el-form-item__content {
+    flex: 1;
+  }
+
+  .el-select {
+    display: block;
+  }
+
+  .el-date-editor--daterange.el-input__inner,
+  .el-form-item__content {
+    width: 100%;
+    max-width: 300px;
+  }
+
+  
+  
+
+  .searh-btns {
+    max-width: 390px;
+    min-width: 200px;
+    grid-column-start: 3;
+    grid-column-end: 4;
+    text-align: right;
+
+    
+    .el-form-item__content {
+      width: 100%;
+      max-width: inherit;
+    }
+  }
+
+  .range-number {
+    display: flex;
+    position: relative;
+    padding: 1px;
+    z-index: 1;
+    > span {
+      border: 1px solid #DCDFE6;
+      border-left: 0;
+      border-right: 0;
+      height: 38px;
+      flex: none;
+    }
+    >.el-input {
+      flex: 1;
+    }
+    >.el-input:nth-of-type(1) .el-input__inner {
+      border-right: none;
+      border-top-right-radius: 0;
+      border-bottom-right-radius: 0;
+    }
+    >.el-input:nth-of-type(2) .el-input__inner {
+      border-left: none;
+      border-top-left-radius: 0;
+      border-bottom-left-radius: 0;
+    }
+
+    > span, 
+    >.el-input:nth-of-type(1) .el-input__inner,
+    >.el-input:nth-of-type(2) .el-input__inner {
+      border-color: #DCDFE6 !important;
+    }
+  }
+}
+
+
+.mandatory {
+  .el-form-item__label {
+    position: relative;
+
+    &::before {
+      content: '*';
+      color: var(--primaryColor);
+    }
+  }
+}
+
+.el-autocomplete-suggestion,
+.el-select-dropdown {
+  margin-top: -10px;
+}
+
+.info-from {
+  width: 380px;
+  margin: 0 auto;
+}
+
+.loading {
+  i,
+  .el-loading-text {
+    color: inherit;
+  }
+}
+
+/*滚动条整体部分,必须要设置*/
+::-webkit-scrollbar{
+  width: 5px;
+  height: 5px;
+}
+
+
+/*滚动条的滑块按钮*/
+::-webkit-scrollbar-thumb{
+  border-radius: 10px;
+  background: var(--bgColor);
+}
+
+/*滚动条的上下两端的按钮*/
+::-webkit-scrollbar-button{
+  display: none;
+}
+
+
+.el-message-box__message {
+  min-height: 50px;
+  padding: 0 30px;
+}
+
+.el-message-box__title span::before {
+  content: "\e7a3";
+  font-size: 1.5rem;
+  margin-right: 5px;
+  color: rgb(144,157,153);
+  font-family: "element-icons" !important;
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  vertical-align: text-bottom;
+  display: inline-block;
+  -webkit-font-smoothing: antialiased;
+}
+
+.click-row .el-table__body td:not(:first-child) {
+  cursor: pointer;
+}
+
+
+.stop-psw {
+  position: absolute;
+  left: -9999999999px;
+  top: -999999999px;
+}
+
+.el-popper.is-dark {
+  position: absolute;
+  border-radius: 4px;
+  padding: 10px;
+  z-index: 2000;
+  font-size: 12px;
+  line-height: 1.2;
+  min-width: 10px;
+  word-wrap: break-word;
+  background: #303133;
+  color: #fff;
+
+  .el-popper__arrow {
+    
+    position: absolute;
+    display: block;
+    width: 0;
+    height: 0;
+    border-color: transparent;
+    border-style: solid;
+    border-width: 6px;
+    bottom: -6px;
+    border-top-color: #303133;
+    border-bottom-width: 0;
+  }
+}
+
+
+.oper-link {
+  cursor: pointer;
+
+  &:hover {
+    color: #3366FF;
+  }
+}
+
+.el-table::before {
+  background: none;
+}

+ 42 - 0
src/components/common-select/index.vue

@@ -0,0 +1,42 @@
+<template>
+  <el-select v-model="value" placeholder="全部">
+    <el-option label="全部" :value="''" v-if="showAll"></el-option>
+    <el-option v-for="(item, i) in options" :key="i" :label="item[label] || '暫無'" :value="item[optid] || ''"></el-option>
+  </el-select>
+</template>
+
+<script>
+import selectComponentJs from "../select";
+import axios from "axios";
+
+const extObj = selectComponentJs();
+
+export default {
+  ...extObj,
+  async mounted() {
+    let res = await axios.post(this.url, {
+      pageNum: 1,
+      pageSize: 499,
+    });
+    let coms = Array.isArray(res.data) ? res.data : res.data.list;
+    console.log('cschoshcos',coms);
+
+    this.options = coms.map((item) => ({ ...item, id: item.id.toString() }));
+    if (this.filter) {
+      this.options = this.options.filter((item) => {
+        let flag = true;
+        for (let i = 0; i < this.filter.length; i++) {
+          const ii = this.filter[i];
+          flag = item[ii.key] == ii.value;
+          if (!flag) return;
+          
+        }
+        return flag;
+      });
+      // this.value = String(this.options[0][this.optid || 'id']).trim()
+    }
+    this.$emit("asyncLoad");
+    extObj.mounted.call(this);
+  },
+};
+</script>

+ 68 - 0
src/components/company-select/index.vue

@@ -0,0 +1,68 @@
+<template>
+  <el-select v-model="value" placeholder="全部">
+    <el-option label="全部" :value="''" v-if="showAll"></el-option>
+    <el-option v-for="item in options" :key="item.id" :label="item.deptName" :value="item.id"></el-option>
+  </el-select>
+</template>
+
+<script>
+import selectComponentJs from "../select";
+import { getDeptList } from "@/request/config";
+import axios from "axios";
+
+const extObj = selectComponentJs();
+
+let ret = [];
+
+const _getOptions = (items) => {
+  for (let i = 0; i < items.length; i++) {
+    if (items[i].children) {
+      _getOptions(items[i].children);
+    }
+
+    let item = {
+      ...items[i],
+      id: items[i].id.toString(),
+    };
+
+    ret.push(item);
+  }
+};
+
+export default {
+  ...extObj,
+  watch: {
+    filter() {
+      this.filterOptions()
+    },
+    ...extObj.watch
+  },
+  methods: {
+    filterOptions() {
+      if (typeof this.filter == "string") {
+        this.options = this.cache.filter((item) => {
+          return this.filter == 2 ? item.id == 1 : item.id != 1;
+        });
+        setTimeout(() => {
+          if (this.options[0]) {
+            this.value = String(this.options[0].id).trim();
+          }
+        });
+      }
+    },
+    ...extObj.methods
+  },
+  async mounted() {
+    ret = [];
+    let res = await axios.post(getDeptList);
+    let coms = Array.isArray(res.data) ? res.data : [res.data];
+    _getOptions(coms);
+
+    this.options = ret;
+    this.cache = JSON.parse(JSON.stringify(ret));
+    this.filterOptions()
+    this.$emit("asyncLoad");
+    extObj.mounted.call(this);
+  },
+};
+</script>

+ 61 - 0
src/components/dept-select/index.vue

@@ -0,0 +1,61 @@
+<template>
+  <el-select v-model="value" placeholder="全部">
+    <el-option label="全部" :value="''" v-if="showAll"></el-option>
+    <el-option v-for="item in options" :key="item.id" :label="item.deptName" :value="item.id"></el-option>
+  </el-select>
+</template>
+
+<script>
+import selectComponentJs from "../select";
+import { getCreateUserDept } from "@/request/config";
+import axios from "axios";
+
+const extObj = selectComponentJs();
+
+
+export default {
+  ...extObj,
+  watch: {
+    filter() {
+      this.getDeptList()
+    },
+    ...extObj.watch
+  },
+  methods: {
+    filterOptions() {
+      if (typeof this.filter == "string") {
+        this.options = this.cache.filter((item) => {
+          return this.filter == 2 ? item.id == 1 : item.id != 1;
+        });
+        setTimeout(() => {
+          if (this.options[0]) {
+            this.value = String(this.options[0].id).trim();
+          }
+        });
+      }
+    },
+    async getDeptList(){
+      if (this.filter) {
+        let res = await axios.get(getCreateUserDept,{ params: {type:this.filter == 2?0:1 }});
+        let temp = Array.isArray(res.data) ? res.data : [res.data];
+        this.options = temp.map(item=>{return {...item,id:item.id.toString()}})
+        console.log(this.options,'this.options');
+      }
+    },
+    ...extObj.methods
+  },
+  async mounted() {
+    // ret = [];
+    // let res = await axios.get(getCreateUserDept,{ params: {type:1 }});
+    // let coms = Array.isArray(res.data) ? res.data : [res.data];
+    // _getOptions(coms);
+
+    // this.options = ret;
+    // this.cache = JSON.parse(JSON.stringify(ret));
+    // this.filterOptions()
+    this.getDeptList()
+    this.$emit("asyncLoad");
+    extObj.mounted.call(this);
+  },
+};
+</script>

+ 60 - 0
src/components/dialog/index.vue

@@ -0,0 +1,60 @@
+<template>
+  <teleport to="#dialog">
+    <div class="dialog-bg" v-if="dialogVisible">
+      <div class="dialog" :style="{width:width}">
+        <div class="head">
+          <h3>{{title}}</h3>  
+          <i class="el-icon-close" @click="closeHandle"></i>
+        </div>
+        <div class="content">
+          <slot />
+        </div>
+        <div class="floot" v-if="showFloor">
+          <el-button @click="closeHandle">取 消</el-button>
+          <el-button type="primary" @click="enterHandle">{{enterText || '保 存'}}</el-button>
+        </div>
+      </div>
+    </div>
+  </teleport>
+</template>
+
+<script>
+import { computed, ref } from "vue";
+
+export default {
+  props: ["show", "title", "hideFloor", 'enterText',"width"],
+  emits: ['update:show', 'submit', 'quit'],
+  setup(props) {
+    const dialogVisible = ref(props.show);
+    const showFloor = computed(() => !(props.hideFloor || props.hideFloor === ''))
+
+    return { dialogVisible, showFloor };
+  },
+  watch: {
+    dialogVisible() {
+      if (this.show !== this.dialogVisible) {
+        this.$emit("update:show", this.dialogVisible);
+      }
+    },
+    show() {
+      if (this.show !== this.dialogVisible) {
+        this.dialogVisible = this.show;
+      }
+    }
+  },
+  methods: {
+    closeHandle() {
+      this.$emit('quit')
+      this.dialogVisible = false;
+    },
+    enterHandle() {
+      this.$emit('submit')
+      // this.dialogVisible = false;
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@import "./style.scss";
+</style>

+ 49 - 0
src/components/dialog/style.scss

@@ -0,0 +1,49 @@
+.dialog-bg {
+  background-color: rgba(0,0,0,0.5);
+  position: fixed;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 99;
+}
+
+.dialog {
+  $padding: 16px;
+  width: 680px;
+  background-color: #fff;
+  border-radius: 4px;
+
+  .head {
+    padding: $padding;
+    display: flex;
+    justify-content: space-between;
+
+    h3 {
+      font-size: 1.5rem;
+      color: var(--colorColor);
+      font-weight: normal;
+    }
+
+    i {
+      font-size: 2rem;
+      color: rgb(145,148,154);
+      cursor: pointer;
+    }
+  }
+
+  .content {
+    min-height: 200px;
+    padding: $padding;
+    border-top: 1px solid var(--bgColor);
+    border-bottom: 1px solid var(--bgColor);
+  }
+
+  .floot {
+    float: right;
+    padding: $padding;
+  }
+}

+ 117 - 0
src/components/head/index.vue

@@ -0,0 +1,117 @@
+<template>
+  <div class="head-layer">
+    <el-tabs v-model="activeValue" >
+      <el-tab-pane 
+        v-for="item in options" 
+        :key="item.value"
+        :label="item.name" 
+        :name="item.value" >
+      </el-tab-pane>
+    </el-tabs>
+    <div class="head-content-layer" :class="{show: show}" :style="showCtrl === undefined ? 'padding-right: 16px' : ''">
+      <div class="head-content" >
+        <slot />
+      </div>
+      <div class="display" @click="show = !show" v-if="showCtrl !== undefined">
+        <template v-if="show">
+          <span>收起</span>
+          <i class="el-icon-arrow-up"></i>
+        </template>
+        <template v-else>
+          <span>展開</span>
+          <i class="el-icon-arrow-down"></i>
+        </template>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { ref, computed } from 'vue'
+
+export default {
+  props: ['options', 'modelValue', 'showCtrl'],
+  setup(props) {
+    let currItem = props.options.find(({value}) => value === props.modelValue) || props.options[0]
+
+    let active = ref(currItem)
+    let show = ref(true)
+    let activeValue = computed({
+      get: () => active.value.value,
+      set: val => {
+        if (val !== active.value.value) {
+          active.value = props.options.find(({value}) => Number(value) === Number(val))
+        }
+      }
+    })
+
+    return { active, activeValue, show }
+  },
+  watch: {
+    active() {
+      if (this.modelValue !== this.active.value) {
+        this.$emit('update:modelValue', this.active.value)
+      }
+    },
+    modelValue(newVal) {
+      this.active = this.options.find(({value}) => value === newVal)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.head-layer {
+  background-color: #fff;
+  border-radius: 4px;
+  padding-top: 8px;
+}
+
+.head-content-layer {
+  padding: 16px 154px 32px 16px;
+  position: relative;
+
+  .head-content {
+    overflow: hidden;
+    height: 0;
+  }
+  
+  
+  &.show {
+    padding-bottom: 16px;
+
+    .head-content {
+      height: auto;
+    }
+  }
+
+  .display {
+    position: absolute;
+    top: 16px;
+    right: 16px;
+    color: #3366FF;
+    font-size: 0.85rem;
+    cursor: pointer;
+
+    i {
+      color: currentColor;
+      font-size: 1em;
+      margin-left: 5px;
+    }
+  }
+}
+</style>
+
+<style>
+.head-layer .el-tabs__nav-wrap {
+  padding: 0 16px;
+}
+
+.head-layer .el-tabs__header {
+  margin-bottom: 0;
+}
+
+.head-layer .el-form-item {
+  margin-bottom: 0;
+}
+</style>

+ 135 - 0
src/components/pagination/index.vue

@@ -0,0 +1,135 @@
+<template>
+  <div class="pag-block no-sizes">
+    <el-pagination
+      layout="total, prev, pager, next, jumper"
+      @current-change="data => $emit('current-change', data)"
+      :current-page="currPage"
+      :page-size="currSize"
+      :total="total"
+    >
+    </el-pagination>
+
+    <div class="sizes-down" :style="{left: 40 + total.toString().length * 10 + 'px'}">
+      <span class="el-dropdown-link" @click.stop="show = !show">
+        {{currSize}} 條/頁
+        <i :class="show ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
+      </span>
+      <ul @click.stop v-if="show">
+        <li
+          v-for="num in sizes"
+          :key="num"
+          :class="{selected: num === currSize}"
+          @click="clickHandle(num)">
+          {{num}}條/頁
+        </li>
+      </ul>
+    </div>
+  </div>
+</template>
+
+<script>
+import { ref, watch } from 'vue'
+export default {
+  props: ['currPage', 'size', 'total'],
+  setup(props, content) {
+    let sizes = ref([12, 24, 48, 72, 96])
+    let currSize = ref(props.size || 12)
+    let show = ref(false)
+
+    watch(currSize, () => content.emit('size-change', currSize))
+    
+    return { sizes, currSize, show }
+  },
+  methods: {
+    clickHandle(num) {
+      this.currSize = num
+      this.show = false
+    }
+  },
+  mounted() {
+    this.docClick = () => this.show = false
+    document.documentElement.addEventListener('click', this.docClick, false)
+  },
+  unmounted() {
+    document.documentElement.removeEventListener('click', this.docClick, false)
+  }
+}
+</script>
+
+<style lang="scss" scope>
+.pag-block {
+  position: relative;
+  z-index: 88;
+    
+  .sizes-down {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+
+    span {
+      box-sizing: border-box;
+      font-size: 13px;
+      appearance: none;
+      color: rgb(96, 98, 102);
+      display: inline-block;
+      height: 30px;
+      line-height: 30px;
+      min-width: 100px;
+      border: 1px solid rgb(220, 223, 230);
+      border-radius: 4px;
+      outline: none;
+      padding: 0px 35px 0 15px;
+      cursor: pointer;
+
+      i {
+        position: absolute;
+        right: 15px;
+        top: 50%;
+        transform: translateY(-50%);
+        color: inherit;
+      }
+    }
+
+    ul {
+      width: 100%;
+      bottom: 100%;
+
+      position: absolute;
+      z-index: 1001;
+      border: solid 1px #E4E7ED;
+      border-radius: 4px;
+      background-color: #FFFFFF;
+      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+      box-sizing: border-box;
+      margin: 5px 0;
+      list-style: none;
+      padding: 6px 0;
+      margin: 0;
+      box-sizing: border-box;
+
+      li {
+        font-size: 14px;
+        padding: 0 20px;
+        position: relative;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        color: #606266;
+        height: 34px;
+        line-height: 34px;
+        box-sizing: border-box;
+        cursor: pointer;
+
+        &:hover {
+          background-color: #F5F7FA;
+        }
+
+        &.selected {
+          color: #D6000F;
+          font-weight: bold;
+        }
+      }
+    }
+  }
+}
+</style>

+ 101 - 0
src/components/previewItem/index.vue

@@ -0,0 +1,101 @@
+<template>
+  <div class="item">
+    <div class="cover">
+      <span class="logo">
+        <img :src="imgMap[item.type]" alt="">
+      </span>
+      <img :src="item.QrCode" class="code" />
+    </div>
+    <div class="content">
+      <h4>{{ item.title }}</h4>
+      <div class="btns">
+        <el-button type="primary" size="small" @click="viewLink(item.link)">預覽</el-button>
+        <el-button type="primary" size="small" @click="copyLink(item.link)">復製鏈接</el-button>
+      </div>
+      <el-input placeholder="輸入鏈接" v-model="link" width="100%" />
+    </div>
+  </div>
+</template>
+
+<script>
+import { copyText } from '@/util'
+import { ref } from '@vue/reactivity'
+
+export default {
+  props: ['item'],
+  setup(props) {
+    const link = ref(props.item.link)
+    const imgMap = {
+      building: require('@/assets/image/edit_type_panorama.png'),
+      house: require('@/assets/image/scene_map_3d.png'),
+      garden: require('@/assets/image/photoview.png'),
+    }
+
+    return { imgMap, link }
+  },
+  methods: {
+    copyLink() {
+      copyText(this.link)
+      this.$message('鏈接復製成功');
+    },
+    viewLink() {
+      window.open(this.link)
+    }
+  },
+}
+</script>
+<style lang="less" scoped>
+
+.item {
+  margin-bottom: 12px;
+  display: flex;
+  align-items: center;
+}
+
+.cover {
+  flex: none;
+  margin-right: 8px;
+  position: relative;
+
+  .code {
+    width: 126px;
+    height: 126px;
+    display: block;
+    object-fit: cover;
+  }
+
+  .logo {
+    width: 32px;
+    height: 32px;
+    background: rgba(0, 0, 0, 0.5);
+    border-radius: 2px;
+    position: absolute;
+    left: 8px;
+    top: 8px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    img {
+      width: 20px;
+      height: 20px;
+    }
+  }
+}
+
+.content {
+  flex: 1;
+  
+  > h4 {
+    font-size: 14px;
+    font-weight: bold;
+    line-height: 22px;
+    color: #262626;
+    margin-bottom: 8px;
+  }
+
+  > .btns {
+    margin-bottom: 8px;
+  }
+}
+</style>

+ 81 - 0
src/components/record/index.vue

@@ -0,0 +1,81 @@
+<template>
+  <com-dialog
+    v-if="show"
+    title="審核記錄"
+    :show="show"
+    hideFloor
+    @update:show="val => $emit('update:show', val)">
+    
+    <div style="width: 100%; overflow: hidden">
+      <el-table ref="multipleTable" :data="dataList.state" style="width: 100%" >
+        <el-table-column label="姓名" prop="auditorName"></el-table-column>
+        <el-table-column label="操作類型"  v-slot:default="{ row }">
+          {{row.auditType === 0 ? '提審' : '審核'}}
+        </el-table-column>
+        <el-table-column label="公司" prop="belongCompanyName"></el-table-column>
+        <el-table-column label="更新時間" prop="auditTime"></el-table-column>
+        <el-table-column label="狀態" prop="auditStatus"  v-slot:default="{ row }">
+          <span>{{getExamineName(row.auditStatus)}}</span>
+        </el-table-column>
+        <el-table-column label="審核備註" prop="auditRemark"  v-slot:default="{ row }">
+          <div class="remark">
+            {{!row.auditType ? '' : row.auditRemark}}
+          </div>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <div class="pag-block no-sizes">
+      <el-pagination
+        @current-change="pag.currentChange"
+        :current-page="pag.state.currPage"
+        :page-size="pag.state.size"
+        layout="total, prev, pager, next, jumper"
+        :total="pag.state.total">
+      </el-pagination>
+    </div>
+
+  </com-dialog>
+</template>
+
+<script>
+import getTableState from '@/state/tableRef'
+import comDialog from "@/components/dialog";
+import { getExamineList } from '@/request/config'
+import { 
+  AUTH_COM_VIEW,
+  getName 
+} from '@/constant'
+
+export default {
+  props: ['show', 'data', 'url'],
+  setup(props) {
+    const state = getTableState({
+      getUrl: props.url || getExamineList,
+      searchAttr: { houseId: props.data }
+    })
+
+    state.pag.value.sizeChange(8)
+
+    return { ...state }
+  },
+  methods: {
+    getExamineName: getName(AUTH_COM_VIEW),
+  },
+  watch: {
+    data() {
+      this.search.state.houseId = this.props.data
+    }
+  },
+  components: {
+    "com-dialog": comDialog
+  }  
+}
+</script>
+
+<style lang="scss" scoped>
+.remark {
+  max-height: 100px;
+  overflow-y: auto;
+}
+</style>

+ 51 - 0
src/components/region/index.vue

@@ -0,0 +1,51 @@
+<template>
+  <el-select :modelValue="modelValue" @update:modelValue="val => $emit('update:modelValue', val)" filterable :placeholder="placeholder">
+    <el-option
+      v-for="item in regions"
+      :key="item.areaId"
+      :label="item.areaName"
+      :value="item.areaId">
+    </el-option>
+  </el-select>
+</template>
+
+<script>
+import { watch, ref } from '@vue/runtime-core'
+import axios from 'axios'
+import { getRegionList } from '@/request/config'
+
+
+export default {
+  props: ['modelValue', 'placeholder', 'cityId'],
+  setup(props) {
+    const regions = ref([])
+    const updateRegions = () => {
+      if (!props.cityId) {
+        regions.value = []
+      } else {
+        axios.post(
+          getRegionList, 
+          {
+            cityId: props.cityId,
+            currentPage: 1,
+            pageSize: 9999
+          }
+        ).then(res => {
+          console.log(res)
+          regions.value = res.data.records
+        })
+      }
+    }
+
+    watch(
+      () => props.cityId,
+      updateRegions,
+      { immediate: true }
+    )
+    
+    return {
+      regions
+    }
+  }
+}
+</script>

+ 58 - 0
src/components/role-select/index.vue

@@ -0,0 +1,58 @@
+<template>
+ <el-radio-group v-model="value">
+    <el-radio v-for="item in options" :key="item.id"  :label="item.id" >{{item.roleName}}</el-radio>
+  </el-radio-group>
+  <!-- <el-select v-model="value" placeholder="全部" ref="select" v-if="show1">
+    <el-option label="全部" :value="''" v-if="showAll"></el-option>
+    <el-option v-for="item in options" :key="item.id"  :label="item.roleName" :value="item.id"></el-option>
+  </el-select>
+  <el-select v-model="value" placeholder="全部" ref="select" v-else>
+    <el-option label="全部" :value="''" v-if="showAll"></el-option>
+    <el-option v-for="item in options" :key="item.id"  :label="item.roleName" :value="item.id"></el-option>
+  </el-select> -->
+</template>
+
+<script>
+import selectComponentJs from '../select'
+import { getRoleList } from '@/request/config'
+import axios from 'axios'
+
+const extObj = selectComponentJs()
+
+extObj.watch = {
+  ...extObj.watch,
+  data() {
+    let base = extObj.data ? extObj.data() : {}
+    return {
+      ...base,
+      allData: []
+    }
+  },
+  notShow() {
+    if (this.allData) {
+      // this.options = this.allData.filter(item => !this.notShow.includes(item.id))
+    }
+  }
+}
+
+export default {
+  ...extObj,
+  async mounted() {
+    
+    let res = await axios.post(getRoleList, {})
+    this.allData = res.data.map(item => ({...item, id: item.id.toString()}))
+    this.options = this.allData.filter(item => item.id!=1).reverse()
+    console.log(this.options);
+
+    // if (this.notShow) {
+      
+    //   this.options = this.allData.filter(item => !this.notShow.includes(item.id))
+    // }
+
+
+    this.$emit('asyncLoad')
+   
+    extObj.mounted.call(this)
+  }
+}
+</script>

+ 58 - 0
src/components/select.js

@@ -0,0 +1,58 @@
+import { ref } from "vue";
+
+export default () => {
+  return {
+    props: ["modelValue", "label", "hideAll", "filter", "notShow", "url", "optid"],
+    setup(props) {
+      let options = ref([]);
+      let cache = ref([]);
+
+      let url = ref(props.url);
+      let label = ref(props.label || "name");
+      let optid = ref(props.optid || "id");
+      let value = ref(props.modelValue || "");
+
+      return {
+        options,
+        value,
+        url,
+        label,
+        optid,
+        cache,
+      };
+    },
+    watch: {
+      value() {
+        this.$emit("update:modelValue", this.value);
+        this.$emit("update:label", this.getLabel());
+      },
+      modelValue() {
+        this.value = this.modelValue;
+      },
+    },
+    computed: {
+      showAll() {
+        return this.hideAll !== "" || this.hideAll;
+      },
+    },
+    methods: {
+      getLabel() {
+        let item = this.options.find((item) => item[this.optid || "id"] == this.value);
+        if (item) {
+          return item.name;
+        } else {
+          return "";
+        }
+      },
+    },
+    mounted() {
+      if (!this.modelValue && this.hideAll === "") {
+        if (this.options[0]) {
+          this.value = String(this.options[0][this.optid || "id"]).trim();
+        }
+      }
+      this.$emit("update:modelValue", String(this.value));
+      this.$emit("update:label", this.getLabel());
+    },
+  };
+};

+ 161 - 0
src/components/share/index.vue

@@ -0,0 +1,161 @@
+<template>
+  <com-dialog
+    title="分享"
+    :show="show"
+    hideFloor
+    @update:show="val => $emit('update:show', val)">
+
+    <div class="share-layer">
+
+      <div class="code">
+        <div class="share-code">
+          <img :src="codeImg + (noRandow ? '' : '?m=' + Date.now())" class="code" />
+          <img :src="_logo" class="logo" v-if="_logo">
+        </div>
+        <div class="share-upload" v-if="setLogo === '' || setLogo">
+          <span class="makre">LOGO</span>
+          <el-button type="primary" class="file-upload">
+            <label for="file-upload">自定义上传</label>
+          </el-button>
+          <input type="file" id="file-upload" @change="fileChange">
+          <span class="size">120*120px,jpg/png格式,不超过1M</span>
+        </div>
+      </div>
+
+      <div class="copy">
+        <label>分享链接:</label>
+        <el-input placeholder="输入链接" v-model="_link" />
+        <el-button type="primary" class="btn" @click="copyLink">复制</el-button>
+      </div>
+
+    </div>
+  </com-dialog>
+</template>
+
+<script>
+import comDialog from "@/components/dialog";
+import {ref} from 'vue'
+import { copyText } from '@/util'
+import { getApp } from '@/app'
+
+export default {
+  props: ['show', 'link', 'logo', 'setLogo', 'codeImg', 'noRandow'],
+  setup(props, context) {
+    const _link = ref(props.link)
+    const _logo = ref(props.logo)
+
+    const fileChange = (ev) => {
+      let file = ev.target.files[0]
+
+      if (file.type !== 'image/png' && file.type !== 'image/jpeg') return getApp().$alert('请上传jpg/png格式', '提示')
+      if (file.size / 1024 / 1024 > 1)  return getApp().$alert('请上传1M以内的图片', '提示')
+      
+      let render = new FileReader()
+
+      render.onload = ev => {
+        _logo.value = ev.target.result
+      }
+
+      render.readAsDataURL(file)
+      context.emit('changeLogo', file)
+    }
+    return { _link, fileChange, _logo }
+  },
+  methods: {
+    copyLink() {
+      copyText(this._link)
+      this.$message('链接复制成功');
+    }
+  },
+  components: {
+    "com-dialog": comDialog
+  }  
+}
+</script>
+
+<style lang="scss" scoped>
+.share-layer {
+  margin: 0 68px;
+}
+
+.code {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 24px;
+
+  .share-code {
+    width: 160px;
+    height: 160px;
+    position: relative;
+
+    .code {
+      width: 100%;
+      height: 100%;
+    }
+
+    .logo {
+      width: 40px;
+      height: 40px;
+      position: absolute;
+      z-index: 1;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+
+  .share-upload {
+    align-self: stretch;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-end;
+    font-size: 0.825rem;
+    margin-left: 8px;
+  }
+
+  .makre {
+    margin-bottom: 12px;
+  }
+
+  .size {
+    margin-top: 8px;
+    color: #909399;
+  }
+
+  .file-upload {
+    padding: 0;
+    max-width: 120px;
+
+    label {
+      display: inline-block;
+      padding: 12px 20px;
+    }
+  }
+
+  input[type='file'] {
+    display: none;
+  }
+}
+
+.copy {
+  padding: 0 80px 0 70px;
+  position: relative;
+
+  label,
+  .btn {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+  }
+
+  label {
+    left: 0;
+    font-size: 0.825rem;
+  }
+
+  .btn {
+    right: 0;
+  }
+}
+</style>

+ 120 - 0
src/components/upload-com/index.vue

@@ -0,0 +1,120 @@
+<template>
+  <div class="share-layer">
+    <div class="share-code" v-if="z_logo">
+      <img class="logo" :src="z_logo" alt="" />
+      <i class="el-icon-close" @click="$emit('close')"></i>
+    </div>
+    <div class="share-upload" v-else>
+      <i class="el-icon-plus"></i>
+
+      <label for="file-upload"></label>
+      <input accept="image/png,image/jpeg" ref="upload" type="file" id="file-upload" @change="fileChange" />
+    </div>
+    <span class="size">支持png/jpg圖片格式,大小不超過1MB</span>
+
+  </div>
+</template>
+
+<script>
+import { computed } from "vue";
+import { getApp } from "@/app";
+
+export default {
+  props: ["link", "logo", ],
+  setup(props, context) {
+
+    const fileChange = (ev) => {
+      let file = ev.target.files[0];
+
+      if (file.type !== "image/png" && file.type !== "image/jpeg") return getApp().$alert("請上傳jpg/png格式", "提示");
+      if (file.size / 1024 / 1024 > 1) return getApp().$alert("請上傳1MB以內的圖片", "提示");
+
+      let render = new FileReader();
+
+      render.onload = (ev) => {
+        z_logo.value = ev.target.result;
+      };
+
+      render.readAsDataURL(file);
+      context.emit("changeLogo", file);
+    };
+    const z_logo = computed(() => props.logo )
+    return {  fileChange, z_logo };
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.share-layer {
+  margin: 0;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  display: inline-block;
+  width: 160px;
+  height: 160px;
+  .share-code {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    cursor: pointer;
+    overflow: hidden;
+    .logo {
+      height: 100%;
+      position: absolute;
+      z-index: 1;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+    .el-icon-close {
+      position: absolute;
+      z-index: 33;
+      top: 10px;
+      right: 10px;
+      background: var(--primaryColor);
+      border-radius: 50%;
+      color: #fff;
+      display: none;
+    }
+    &:hover {
+      .el-icon-close {
+        display: inline-block;
+      }
+    }
+  }
+
+  .share-upload {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    overflow: hidden;
+
+    .el-icon-plus {
+      pointer-events: none;
+      font-size: 32px;
+      position: absolute;
+      z-index: 1;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+
+  label {
+    display: inline-block;
+    width: 100%;
+    height: 100%;
+    cursor: pointer;
+  }
+
+  input[type="file"] {
+    display: none;
+  }
+ .size {
+  font-size: 14px;
+  display: inline-block;
+  line-height: 2;
+  margin-top: 10px;
+}
+}
+</style>

+ 117 - 0
src/components/upload-video/index.vue

@@ -0,0 +1,117 @@
+<template>
+  <div>
+    <div class="share-layer">
+      <div class="share-code" v-if="z_video">
+        <video :src="z_video" controls autoplay loop></video>
+        <i class="el-icon-close" @click="$emit('close')"></i>
+      </div>
+      <div class="share-upload" v-else>
+        <i class="el-icon-plus"></i>
+        <label for="file-upload1"></label>
+        <input accept="video/mp4" ref="upload" type="file" id="file-upload1" @change="fileChange" />
+      </div>
+    </div>
+    <span class="size">支持MP4視頻格式,碼率小於2Mbps,大小不超過20M。</span>
+  </div>
+</template>
+
+<script>
+import { computed } from "vue";
+import { getApp } from "@/app";
+
+export default {
+  props: ["link", "video"],
+  setup(props, context) {
+
+    const fileChange = (ev) => {
+      let file = ev.target.files[0];
+
+      if (file.type !== "video/mp4") return getApp().$alert("請上傳mp4格式視頻", "提示");
+      if (file.size / 1024 / 1024 > 20) return getApp().$alert("請上傳20MB以內的視頻", "提示");
+
+      let render = new FileReader();
+
+      render.onload = (ev) => {
+        z_video.value = ev.target.result;
+      };
+
+      render.readAsDataURL(file);
+      context.emit("changeVideo", file);
+    };
+    const z_video = computed(() => props.video )
+    return { fileChange, z_video };
+  },
+ 
+};
+</script>
+
+<style lang="scss" scoped>
+.share-layer {
+  margin: 0;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  display: inline-block;
+  .share-code {
+    width: 300px;
+    height: 300px;
+    position: relative;
+    overflow: hidden;
+    cursor: pointer;
+    >video {
+      width: 100%;
+      position: absolute;
+      z-index: 1;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+    .el-icon-close {
+      position: absolute;
+      z-index: 33;
+      top: 10px;
+      right: 10px;
+      background: var(--primaryColor);
+      border-radius: 50%;
+      color: #fff;
+      display: none;
+    }
+    &:hover {
+      .el-icon-close {
+        display: inline-block;
+      }
+    }
+  }
+
+  .share-upload {
+    width: 160px;
+    height: 160px;
+    position: relative;
+    overflow: hidden;
+    .el-icon-plus {
+      pointer-events: none;
+      font-size: 32px;
+      position: absolute;
+      z-index: 1;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+
+  label {
+    display: inline-block;
+    width: 100%;
+    height: 100%;
+    cursor: pointer;
+  }
+
+  input[type="file"] {
+    display: none;
+  }
+}
+.size {
+  font-size: 14px;
+  display: inline-block;
+  line-height: 1.2;
+}
+</style>

+ 14 - 0
src/constant/REG.js

@@ -0,0 +1,14 @@
+// 至少8-16個字符,至少1個小寫字母和1個數字,其他可以是任意字符:
+export const PSW ={
+  REG: /^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]{8,16})$/,
+  // REG: /^(?=.*[a-z])(?=.*\d)[^]{8,16}$/,
+
+  tip: '密碼必須包含數字、字母,長度8-16個字符'
+}
+
+// 手機號校驗
+export const PHONE = {
+  REG: /([6|9])\d{7}$|[0-9]\d{8}$/,
+  // REG: /[1][3-8]\d{9}$|([6|9])\d{7}$|[0-9]\d{8}$|6\d{5}$/,
+  tip: '手機號格式不正確!'
+}

+ 237 - 0
src/constant/index.js

@@ -0,0 +1,237 @@
+import user from '@/state/user'
+
+export const UN_REQ_NUM = -99999
+
+// 弹框请求审核
+export const EXAMINE_REQ_SELECT = [
+  { name: '通过', value: 1 },
+  { name: '不通过', value: -1 },
+]
+
+export const EXAMINE_VIEW_SELECT = [
+  ...EXAMINE_REQ_SELECT,
+  { name: '待审核', value: 0 },
+]
+
+// 全部状态(搜索用)
+export const EXAMINE_ALL_STATUS = -2
+// 草稿状态
+export const EXAMINE_DRAFT_STATUS = 0
+// 待集团审核状态
+export const EXAMINE_NO_AUDIT_STATUS = 1
+// 待地区审核状态
+export const EXAMINE_NO_COM_AUDIT_STATUS = 3
+// 集团审核不通过
+export const EXAMINE_NO_ADOPT_STATUS = -1
+// 地区审核不通过
+export const EXAMINE_NO_COM_ADOPT_STATUS = -4
+// 审核不通过
+export const EXAMINE_NO_ADOPT_ALL_STATUS = -1
+// 审核通过状态
+export const EXAMINE_ADOPT_STATUS = 2
+// 已经下线
+export const EXAMINE_OFF_ONLINE_STATUS = -3
+
+// 二手房专用,已上线
+export const EXAMINE_UP_ONLINE_STATUS = 5
+
+// 审核通过特殊查询flag
+export const EXANINE_SUCCESS_STATUS = 10
+
+// VR项目审核与审核页面公用
+export const EXAMINE_PUBLIC_STATUS = [
+  { name: "待地区审核", value: EXAMINE_NO_COM_AUDIT_STATUS },
+  { name: "待集团审核", value: EXAMINE_NO_AUDIT_STATUS },
+  { name: "已审核", value: EXANINE_SUCCESS_STATUS },
+  { name: "已上线", value: EXAMINE_ADOPT_STATUS },
+  { name: "未通过", value: EXAMINE_NO_ADOPT_ALL_STATUS },
+]
+
+// 二手房状态管理
+export const COMMUNITY_PUBLIC_STATUS = [
+  ...EXAMINE_PUBLIC_STATUS.map(item => 
+    item.value === EXAMINE_ADOPT_STATUS
+      ? { ...item, name: '审核已通过'}
+      : item
+  )
+]
+
+// 审核结果,审核页面不展示草稿
+export const EXAMINE_SHOW_STATUS = [
+  { name: "全部", value: EXAMINE_ALL_STATUS },
+  ...EXAMINE_PUBLIC_STATUS
+]
+
+// 二手房审核结果
+export const EXAMINE_SHOW_STATUS_1 = [
+  { name: "全部", value: EXAMINE_ALL_STATUS },
+  { name: "待地区审核", value: EXAMINE_NO_COM_AUDIT_STATUS },
+  { name: "待集团审核", value: EXAMINE_NO_AUDIT_STATUS },
+  { name: "已上线", value: EXAMINE_ADOPT_STATUS },
+  { name: "未通过", value: EXAMINE_NO_ADOPT_ALL_STATUS },
+]
+
+// VR项目审核页面,展示草稿
+export const EXAMINE_SHOW_SELECT_STATUS = [
+  { name: "全部", value: UN_REQ_NUM },
+  { name: "草稿", value: EXAMINE_DRAFT_STATUS },
+  ...EXAMINE_PUBLIC_STATUS,
+]
+
+// VR二手房项目审核页面,展示草稿
+export const EXAMINE_SHOW_SELECT_STATUS_2 = [
+  { name: "全部", value: UN_REQ_NUM },
+  { name: "草稿", value: EXAMINE_DRAFT_STATUS },
+  { name: "待地区审核", value: EXAMINE_NO_COM_AUDIT_STATUS },
+  { name: "待集团审核", value: EXAMINE_NO_AUDIT_STATUS },
+  // { name: "已审核", value: EXAMINE_ADOPT_STATUS },
+  { name: "已上线", value: EXAMINE_UP_ONLINE_STATUS },
+  // { name: "已下线",value: EXAMINE_OFF_ONLINE_STATUS },
+  { name: "未通过", value: EXAMINE_NO_ADOPT_ALL_STATUS },
+]
+
+export const EXAMINE_SHOW_SELECT_STATUS_3 = [
+  { name: "全部", value: UN_REQ_NUM },
+  { name: "草稿", value: EXAMINE_DRAFT_STATUS },
+  { name: "待地区审核", value: EXAMINE_NO_COM_AUDIT_STATUS },
+  { name: "待集团审核", value: EXAMINE_NO_AUDIT_STATUS },
+  { name: "已审核", value: EXAMINE_ADOPT_STATUS },
+  { name: "已上线", value: EXAMINE_UP_ONLINE_STATUS },
+  { name: "已下线",value: EXAMINE_OFF_ONLINE_STATUS },
+  { name: "未通过", value: EXAMINE_NO_ADOPT_ALL_STATUS },
+]
+
+// 审核记录弹出使用
+export const AUTH_COM_VIEW = [
+  { name: "已审核", value: 4 },
+  { name: "待集团审核", value: 0 },
+  { name: "已上线", value: 1 },
+  { name: "待地区审核", value: 2 },
+  { name: "地区审核通过", value: 3 },
+  { name: "集团驳回", value: -1 },
+  { name: "地区驳回", value: -4 },
+]
+
+// 房间类型
+export const HOUSE_TYPE = [
+  { name: "新房", value: 1 },
+  // { name: "二手房", value: 2 }
+]
+
+// 请求展示需要带全部
+export const HOUSE_TYPE_SELECT = [
+  { name: '全部', value: UN_REQ_NUM },
+  ...HOUSE_TYPE
+]
+
+// 场景类型
+export const SCENE_TYPE = [
+  { name: '其他', value: '0' },
+  { name: '文博', value: 1 },
+  { name: '地产', value: 2 },
+  { name: '电商', value: 3 },
+  { name: '餐饮', value: 4 },
+  { name: '家居', value: 5 }
+]
+
+export const getName = (options, isVlog) => {
+  if (options === EXAMINE_SHOW_STATUS || options === EXAMINE_SHOW_SELECT_STATUS || options === EXAMINE_SHOW_SELECT_STATUS_2 || options === EXAMINE_SHOW_SELECT_STATUS_3) {
+    options = [
+      ...options.filter(item => item.value !== EXAMINE_NO_ADOPT_ALL_STATUS),
+      { name: "集团驳回", value: EXAMINE_NO_ADOPT_STATUS },
+      { name: "地区驳回", value: EXAMINE_NO_COM_ADOPT_STATUS }
+    ]
+  }
+  isVlog && console.log(options, options === EXAMINE_SHOW_SELECT_STATUS)
+  return (val) => {
+    let item = options.find(({value}) => Number(value) === Number(val))
+    return item ? item.name : ''
+  }
+}
+
+
+
+
+// 提审
+export const REDIRECT_AUDIT_BRING_STATUS = 1
+// 地区审核通过
+export const REDIRECT_AUDIT_REGION_STATUS = 2
+// 集团审核通过
+export const REDIRECT_AUDIT_GROUP_STATUS = 3
+// 上线
+export const REDIRECT_AUDIT_UP_STATUS = 4
+// 下线
+export const REDIRECT_AUDIT_OFF_STATUS = 5
+// 撤回
+export const REDIRECT_AUDIT_RECALL_STATUS = 6
+// 驳回
+export const REDIRECT_AUDIT_REJECT_STATUS = 7
+
+const EXAMINE_REDIRECT_BASE_SELECT = [
+  { name: '不通过', value: REDIRECT_AUDIT_REJECT_STATUS }
+]
+
+// 二手房弹框集团请求审核
+export const EXAMINE_REDIRECT_GROUP_SELECT = [
+  { name: '通过', value: REDIRECT_AUDIT_GROUP_STATUS },
+  ...EXAMINE_REDIRECT_BASE_SELECT
+]
+
+// 二手房弹框地区请求审核
+export const EXAMINE_REDIRECT_REGION_SELECT = [
+  { name: '通过', value: REDIRECT_AUDIT_REGION_STATUS },
+  ...EXAMINE_REDIRECT_BASE_SELECT
+]
+
+export const getRedirectCommunityAuditBody = (data, status, mark) => ({
+  updatorId: user.value.info.id,
+  updatorName: user.value.info.nickName,
+  vrGardenId: data.vrGardenId,
+  operateType: status,
+  vrUrl: data.vrUrl,
+  vrCover: data.vrCover,
+  remark: mark,
+  
+  reviewerId: user.value.info.id,
+  reviewer: user.value.info.nickName,
+  operatorCompanyId: user.value.info.departmentId,
+  operatorCompanyName: '1',
+  companyId: user.value.info.departmentId,
+  companyName: '',
+  maintenanceman: '',
+  maintenancemanId: ''
+})
+// , id = data.id
+
+export const getRedirectRoomAuditBody = (data, status, mark) => ({
+  roomlistingId: data.roomlistingId || data.id,
+  reviewerId: user.value.info.id,
+  reviewer: user.value.info.nickName,
+  reviewMark: mark,
+  gardenName: data.gardenName,
+  gardenId: data.gardenId,
+  vrModelName: data.vrModelName,
+  cityId: data.cityId,
+  num: data.num,
+
+
+  dataSource: data.dataSource,
+  auditStatus: status,
+  vRUrl: data.vRUrl,
+  vrCover: data.vrCover,
+  companyId: user.value.info.departmentId,
+  companyName: '',
+  maintenanceman: '',
+  maintenancemanId: ''
+})
+
+
+// 审核记录弹出使用
+export const AUTH_REDIRECT_COM_VIEW = [
+  { name: "集团驳回", value: EXAMINE_NO_ADOPT_STATUS },
+  { name: "草稿", value: EXAMINE_DRAFT_STATUS },
+  { name: "地区驳回", value: EXAMINE_NO_COM_ADOPT_STATUS },
+  { name: "已审核", value: EXAMINE_ADOPT_STATUS },
+  { name: "已上线", value: EXAMINE_UP_ONLINE_STATUS },
+  ...EXAMINE_PUBLIC_STATUS,
+]

+ 55 - 0
src/constant/ssrLists.js

@@ -0,0 +1,55 @@
+import axios from 'axios'
+import { reactive } from 'vue'
+import { 
+  getVrCityList,
+  getPropertyList,
+  getCompanyList
+} from '../request/config'
+
+const citys = reactive([])
+export const getCitys = () => {
+  return citys
+}
+
+const propertys = reactive([])
+export const getPropertys = () => {
+  return propertys
+}
+
+const companys = reactive([])
+export const getCompanys = () => {
+  return companys
+}
+export const getCompanyById = (qid) => {
+  return companys.find(({id}) => id === qid)
+}
+
+
+const requestLists = () => {
+  axios.post(getVrCityList, { currentPage: 1, pageSize: 999, keyword: '' })
+    .then(res => {
+      citys.length = 0
+      res.data.records.forEach(city => {
+        citys.push(city)
+      })
+    })
+  
+  axios.post(getPropertyList)
+    .then(res => {
+      propertys.length = 0
+      res.data.forEach(property => {
+        propertys.push(property)
+      })
+    })
+
+    
+  axios.post(getCompanyList)
+    .then(res => {
+      companys.length = 0
+      res.data.forEach(property => {
+        companys.push(property)
+      })
+    })
+}
+
+Promise.resolve().then(requestLists)

+ 13 - 0
src/constant/view.js

@@ -0,0 +1,13 @@
+
+// 服务端权限对应的router
+export const keyViewMap = {
+  "department": "framework",
+  "vrModel": "vrmodel",
+  "vrHouse": "estate"
+}
+
+// 如果不加入菜单,router依附到哪里
+export const attach = { 
+  housing: 'estate',
+  secondHouse: 'community'
+}

+ 13 - 0
src/main.js

@@ -0,0 +1,13 @@
+import { createApp } from 'vue';
+import App from './App.vue'
+import router from './router'
+import ElementPlus from 'element-plus'
+import locale from 'element-ui/lib/locale/lang/zh-CN'
+import '@/request/setupAxios'
+import '@/assets/style/public.scss'
+
+
+const app = createApp(App);
+app.use(router)
+app.use(ElementPlus, { locale })
+app.mount('#app')

+ 25 - 0
src/request/authentication.js

@@ -0,0 +1,25 @@
+import crypto from 'crypto'
+
+
+export const getHdConfig = (data = {}) => {
+  const client_secret = process.env.VUE_APP_HD_SECRENT
+  const config = {
+    client_code: process.env.VUE_APP_HD_CODE,
+    req_time: Date.now().toString().substr(0, 10),
+    ...data
+  }
+  const authData = {
+    ...config,
+    client_secret
+  }
+  const keys = Object.keys(authData).sort((a, b) => a.localeCompare(b))
+
+  const authStr = keys.map(key => authData[key]).join('')
+  const md5 = crypto.createHash('md5')
+  const authcode = md5.update(authStr).digest('hex').toLowerCase()
+
+  return {
+    ...config,
+    authcode
+  }
+}

+ 434 - 0
src/request/config.js

@@ -0,0 +1,434 @@
+/**  ----------------用户接口----------------   */
+
+
+export const userLogin = '/back/login'
+
+
+/** ----------------场景管理接口---------------- */
+// 获取场景列表
+export const getSceneList = '/back/scene/list'
+
+export const getFilterFolerSceneList = '/back/scene/allList'
+
+// 删除场景
+export const deleteScene = '/back/scene/delete'
+// 获取文件夹树
+export const getFolerTreeList = '/back/scene/findListTree'
+
+// 新建文件夹
+export const insertFoler = '/back/scene/folderSave'
+
+
+// 更新文件夹
+export const updateFoler = '/back/scene/folderUpdate'
+
+// 删除文件夹
+export const deleteFoler = '/back/scene/folderDelete'
+
+// 移动文件夹
+export const moveFoler = '/back/scene/move'
+
+
+/** ------------------------------------------ */
+
+
+
+/** ----------------店铺管理接口---------------- */
+
+// 获取店铺列表
+export const getShopList = '/back/shop/list'
+
+// 添加店铺
+export const insertShop = '/back/shop/saveOrUpdate'
+
+// 更新店铺
+export const updateShop = '/back/shop/saveOrUpdate'
+
+// 删除店铺
+export const deleteShop = '/back/shop/delete'
+
+
+/** ----------------店铺分类接口---------------- */
+
+
+export const getCategoryList = '/back/category/allList'
+
+
+//店鋪分類列表
+export const getShopCategoryList = '/back/category/list'
+
+// 添加分類
+export const insertCategory = '/back/category/saveOrUpdate'
+
+// 更新分類
+export const updateCategory = '/back/category/saveOrUpdate'
+
+// 删除分類
+export const deleteCategory = '/back/category/delete'
+
+
+
+
+
+/** ----------------开场视频接口---------------- */
+
+// 获取视频列表
+export const getVideoList = '/back/video/list'
+
+// 添加视频
+export const insertVideo = '/back/video/saveOrUpdate'
+
+// 更新视频
+export const updateVideo = '/back/video/saveOrUpdate'
+
+// 删除视频
+export const deleteVideo = '/back/video/delete'
+
+
+
+
+/** ----------------部门接口---------------- */
+// 获取公司列表
+export const getDeptList = '/back/dept/allList'
+// 获取公司列表(log)
+export const getDeptLogList = '/back/dept/allListLog'
+
+// 获取公司列表树
+export const getCompanyThree = '/api/manage/department/listTree'
+// 新增公司
+export const insertCompany = '/back/dept/saveOrUpdate'
+// 删除公司
+export const deleteCompany = '/back/dept/delete'
+// 新增公司
+export const updateCompany = '/back/dept/saveOrUpdate'
+
+
+
+
+/**  ----------------用户接口----------------   */
+// 登录
+// 登出
+export const userLogout = '/back/logout'
+// 获取用户列表
+export const getUserList = '/back/user/list'
+// 修改用户信息
+export const updateUser = '/back/user/saveOrUpdate'
+// 新增用户
+export const insertUser = '/back/user/saveOrUpdate'
+// 删除用户
+export const deleteUser = '/back/user/delete'
+// 修改用户密码
+export const updateUserPWD = '/back/user/updatePassword'
+// 重置用户密码
+export const resetUserPWD = '/back/user/rePassword'
+// 获取获取新建用户所属部门
+export const getCreateUserDept = '/back/user/getCreateUserDept'
+// 校验验证码
+export const checkCode = '/api/manage/user/checkVerify'
+// 获取验证码
+export const getCode = '/api/manage/user/getVerify'
+
+/** ------------------------------------------ */
+
+
+/**  ----------------角色接口----------------   */
+export const getRoleList = '/back/role/allList'
+
+
+/** ----------------日志接口---------------- */
+// 获取日志列表
+export const getLogList = '/back/log/list'
+/** ------------------------------------------ */
+
+
+
+/** ----------------上传接口---------------- */
+
+export const uploadFile = '/back/upload/file'
+
+
+
+
+
+// 以下是旧的
+
+
+/** ------------------------------------------ */
+
+
+
+
+
+/**  ----------------楼盘接口----------------   */
+// 根据条件拉取所有楼盘
+export const getEstateList = '/fcb/project/project/queryOrSearchList'
+// 修改楼盘信息
+export const updateEstate = '/fcb/project/project/updateEstate'
+// 增加楼盘信息
+export const insertEstate = '/fcb/project/project/addEstate'
+// 删除楼盘信息
+export const deleteEstate = '/fcb/project/project/deleteEstate'
+// 通过房源id获取楼盘信息
+export const getEstateByHouseId = '/fcb/project/house/getEstateDetail'
+/** ------------------------------------------ */
+
+
+/**  ----------------房源接口----------------   */
+// 根据条件拉取所有房源
+export const getHouseList = '/fcb/project/house/queryOrSearchList'
+// 获取所有房源id
+export const getHouseIdAll = '/fcb/project/house/listEstate'
+// 导出指定房源列表
+export const exportHouseList = '/fcb/project/house/exportProjectList'
+// 修改房源信息
+export const updateHouse = '/fcb/project/house/updateHouse'
+// 删除房源信息
+export const deleteHouse = '/fcb/project/house/deleteHouse'
+// 提审房源
+export const auditHouse = '/fcb/project/house/submitAudit'
+// 撤回审核房源
+export const dismissHouse = '/fcb/project/house/dismissAudit'
+// 添加房源
+export const insertHouse = '/fcb/project/house/addHouse'
+// 上传logo制作二维码
+export const uploadHouseLogo = '/fcb/project/house/uploadImage'
+// 获取房源二维码
+export const getHouseQrImg = '/fcb/project/house/getQrImage'
+// 获取房源所有分享链接上线
+export const getHouseUpShares = '/fcb/project/house/getVrAndIndex'
+// 获取房源所有分享链接
+export const getHouseShares = '/fcb/project/house/getShareLinks'
+
+/** ------------------------------------------ */
+
+
+
+/**  ----------------新房审核接口----------------   */
+// 根据条件拉取所有审核记录
+export const getExamineList = '/fcb/project/audit/queryAuditLogList'
+// 获取审核页面的审核记录
+export const getExamineViewList = '/fcb/project/house/getAuditHouseList'
+// 审核
+export const estateExamine = '/fcb/project/audit/doAudit'
+// 上下线
+export const estateOnline = '/fcb/project/audit/online'
+// 导出审核记录
+export const exporrtAuditEstate = '/fcb/project/house/exportAuditHouseList'
+/** ------------------------------------------ */
+
+
+
+
+
+/**  ----------------二手房小区接口----------------   */
+// 获取所有城市列表
+export const getVrCityList = '/fcb/project/redirct/101_getVrCityList'
+// 获取所有物业列表
+export const getPropertyList = '/fcb/project/redirct/101_propertySortList'
+// 获取片区列表
+export const getRegionList = '/fcb/project/redirct/101_getRegionList'
+
+// 获取小区总数统计
+export const getCommunityTotal = '/fcb/project/redirct/101_getVrGardenCountByStatus'
+
+// 获取小区列表
+export const getCommunityList = '/fcb/project/redirct/101_gardenVrList'
+export const getCommunityList1 = '/fcb/project/redirct/101_gardenVrListForAll'
+
+// 添加小区
+export const insertCommunity = '/fcb/project/redirct/101_saveVrGarden'
+// 删除小区
+export const deleteCommunity = '/fcb/project/redirct/101_deleteVrGarden'
+
+// 提审房源
+export const auditCommunity = '/fcb/project/redirct/101_gardenVrAudit'
+// 撤回审核房源
+export const dismissCommunity = '/fcb/project/redirct/101_gardenVrAudit'
+// 房源所有分享链接
+export const getCommunityShares  = '/fcb/project/usedestate/getShareLinks'
+
+// 获取所有房源id
+export const getCommunityIdAll = '/fcb/project/usedestate/listEstate'
+// 通过房源id获取楼盘信息
+export const getCommunityByHouseId = '/fcb/project/redirct/101_getFrontEndVrGardenDetailById'
+/** ------------------------------------------ */
+
+
+/**  ----------------二手房房源接口----------------   */
+// 获取小区列表
+export const getSecondHouseList = '/fcb/project/redirct/101_getVrRoomListByGardenId'
+// 获取小区列表
+export const getSecondHouseList1 = '/fcb/project/redirct/101_getVrReviewList'
+
+// 获取小区总数统计
+export const getSecondHouseTotal = '/fcb/project/redirct/101_getReviewListTotalNumber'
+
+// 添加小区
+export const insertSecondHouse = '/fcb/project/redirct/101_saveVrGarden'
+// 提审房源
+export const auditSecondHouse = '/fcb/project/redirct/101_setVrRoomStatus'
+// 撤回审核房源
+export const dismissSecondHouse = '/fcb/project/redirct/101_setVrRoomStatus'
+// 删除小区
+export const deleteSecondHouse = '/fcb/project/redirct/101_deleteVrGarden'
+
+// 获取房源所有分享链接
+export const getSecondHouseShares = '/fcb/project/usedhouse/getShareLinks'
+/** ------------------------------------------ */
+
+
+/**  ----------------二手房小区审核----------------   */
+
+// 根据条件拉取所有审核记录
+export const getCommunityExamineList = '/fcb/project/usedaudit/queryAuditLogList'
+// 获取审核页面的审核记录
+export const getCommunityExamineViewList = '/fcb/project/redirct/101_getVrGardenAuditRecordList'
+// 审核
+export const communityExamine = '/fcb/project/redirct/101_gardenVrAudit'
+// 上下线
+export const communityOnline = '/fcb/project/redirct/101_getVrGardenAuditRecordList'
+// 导出审核记录
+export const exporrtCommunityAuditEstate = '/fcb/project/redirct/101_getVrGardenExportList'
+
+/** ------------------------------------------ */
+
+
+/**  ----------------二手房房间审核----------------   */
+
+// 根据条件拉取所有审核记录
+export const getSecondHouseExamineList = '/fcb/project/usedaudit/queryAuditLogList'
+// 获取审核页面的审核记录
+export const getSecondHouseExamineViewList = '/fcb/project/redirct/101_getVrReviewLogList'
+// 审核
+export const secondHouseExamine = '/fcb/project/redirct/101_setVrRoomStatus'
+// 上下线
+export const secondHouseOnline = '/fcb/project/redirct/101_setVrRoomStatus'
+// 导出审核记录
+export const exporrtSecondHouseAuditEstate = '/fcb/project/redirct/101_exportVrReviewList'
+
+/** ------------------------------------------ */
+
+
+
+
+
+/** ----------------相机接口---------------- */
+// 获取相机列表
+export const getCameraList = '/api/scene/camera/findListPage'
+// 添加相机
+export const insertCamera = '/api/scene/camera/save'
+// 修改相机
+export const updateCamera = '/api/scene/camera/update'
+// 删除相机
+export const deleteCamera = '/api/scene/camera/delete'
+// 相机绑定设备
+export const bindCamera = '/api/scene/camera/bindCamera'
+// 相机解除绑定设备
+export const unbindCamera = '/api/scene/camera/unbind'
+/** ------------------------------------------ */
+
+
+
+
+
+
+
+/** ----------------获取恒大签名---------------- */
+export const getAuthConfig = '/fcb/project/api/query/authCode'
+/** ------------------------------------------- */
+
+
+/** ----------------恒大接口---------------- */
+// 获取恒大所有楼盘信息
+export const getHdEstateNames = '/vr/prodvr/prod/v1/prodinfo'
+
+// 获取恒大所有二手房源信息
+export const getHdCommunityNames = '/fcb/project/redirct/101_searchGardenVrList'
+
+// 判断恒大VR连接是否删除
+// export const hasHouseIsDel = '/vr/prodvr/prod/v1/isOffline'
+export const hasHouseIsDel = '/omc/external/prod/v1/isOffline'
+// export const hasHouseIsDel = 'https://broker-sit2-api.fcb.com.cn/omc/external/prod/v1/isOffline'
+
+// 判断恒大VR小区连接是否删除
+export const hasCommunityIsDel = '/vr/prodvr/prod/v1/isOffline'
+
+// 判断恒大VR连接是否删除
+export const hasSecondHouseIsDel = '/vr/prodvr/prod/v1/isOffline'
+/** ------------------------------------------- */
+
+
+// 转发类型接口
+export const RedirctUrls = [
+  getVrCityList,
+  getPropertyList,
+  getRegionList,
+  getCommunityList,
+  insertCommunity,
+  deleteCommunity,
+  auditCommunity,
+  getHdCommunityNames,
+  getSecondHouseList1,
+  dismissCommunity,
+  getCommunityShares,
+  getCommunityIdAll,
+  getCommunityByHouseId,
+  getSecondHouseList,
+  insertSecondHouse,
+  auditSecondHouse,
+  getCommunityTotal,
+  getCommunityList1,
+  getSecondHouseTotal,
+  dismissSecondHouse,
+  deleteSecondHouse,
+  getSecondHouseShares,
+  getCommunityExamineList,
+  getCommunityExamineViewList,
+  communityExamine,
+  communityOnline,
+  exporrtCommunityAuditEstate,
+  getSecondHouseExamineList,
+  getSecondHouseExamineViewList,
+  secondHouseExamine,
+  secondHouseOnline,
+  exporrtSecondHouseAuditEstate
+]
+
+export const RedirctPageUrls = [
+  getCommunityList,
+  getCommunityList1,
+  getSecondHouseList,
+  getCommunityExamineList,
+  getSecondHouseList1,
+  getCommunityExamineViewList,
+  getSecondHouseExamineList,
+  getSecondHouseExamineViewList
+]
+
+// 不需要登录就能请求的接口
+export const notLoginUrls = [userLogin, checkCode, getCode]
+// 需要用表单提交的数据
+export const fromUrls = [auditHouse, dismissHouse]
+// 带文件的请求
+export const fileUrls = [uploadFile]
+// 需要限定卫GET请求方式的url
+export const GetUrls = []
+// 需要限定请求方式的url
+export const PostUrls = [
+  getSceneList, 
+  getFilterFolerSceneList,
+  getCameraList, 
+  getUserList,
+  getCommunityList,
+  getCommunityShares,
+  getCommunityIdAll,
+  getCommunityByHouseId,
+  getSecondHouseList,
+  getSecondHouseShares,
+  getCommunityExamineList,
+  getCommunityExamineViewList,
+].concat(RedirctUrls)
+// 恒大接口
+export const HdUrls = [getHdEstateNames, hasHouseIsDel]
+// 文件流接口
+export const FileFlow = [exporrtAuditEstate, exporrtSecondHouseAuditEstate]

+ 18 - 0
src/request/errorMsg.js

@@ -0,0 +1,18 @@
+import {getApp} from '@/app'
+
+const STOP = 2000
+let showIng = false
+
+
+export const openErrorMsg = (msg) => {
+  if (showIng) return;
+
+  showIng = true
+  getApp().$message({
+    type: 'error',
+    duration: STOP,
+    message: msg
+  });
+
+  setTimeout(() => showIng = false, STOP)
+}

+ 54 - 0
src/request/loading.js

@@ -0,0 +1,54 @@
+import {
+  getUserList,
+  getEstateList,
+  getHouseList,
+  getExamineList,
+  getCompanyThree,
+  getSceneList,
+  getCameraList,
+  getLogList,
+  getRoleList,
+  getHdEstateNames,
+  getHouseIdAll,
+  getAuthConfig,
+  getExamineViewList
+} from './config'
+import { getApp } from '@/app'
+
+const notOpenUrls = [
+  getUserList,
+  getEstateList,
+  getHouseList,
+  getExamineList,
+  getCompanyThree,
+  getSceneList,
+  getExamineViewList,
+  getCameraList,
+  getLogList,
+  getRoleList,
+  getHdEstateNames,
+  getHouseIdAll,
+  getAuthConfig
+]
+
+let loading
+
+export const openLoading = (url) => {
+  // if (loading) return;
+  if (loading || ~notOpenUrls.indexOf(url)) return;
+
+  loading = getApp().$loading({
+    lock: true,
+    text: '加载中',
+    spinner: 'el-icon-loading',
+    background: 'rgba(255, 255, 255, 0.4)',
+    customClass: 'loading'
+  });
+}
+
+export const closeLoading = () => {
+  if (loading) {
+    loading.close()
+    loading = void 0
+  }
+}

+ 141 - 0
src/request/normalizeRedirct.js

@@ -0,0 +1,141 @@
+import user from '../state/user'
+import * as config from './config'
+
+const NOT_ADDS = [
+  config.exporrtSecondHouseAuditEstate,
+  config.getSecondHouseList1,
+  config.getSecondHouseTotal
+]
+const normalizeData = (data, url) => {
+  
+  if (data.pageNum) {
+    data.currentPage = data.pageNum
+    // delete data.pageNum
+    // test
+    data.current = data.pageNum
+    data.size = data.pageSize
+  }
+  
+
+  if ((user.value.role !== 'admin' && user.value.role !== 'group') || !NOT_ADDS.includes(url)) {
+    data.companyId || (data.companyId = user.value.info.departmentId)
+    data.userId = user.value.info.id
+    data.companyName || (data.companyName = '')
+  }
+  
+
+  data.maintenanceman || (data.maintenanceman = '')
+  data.maintenancemanId || (data.maintenancemanId = '')
+  data.updatorId || (data.updatorId = '')
+  data.updatorName || (data.updatorName = '')
+  data.roleKey || (data.roleKey = user.value.role)
+  
+
+}
+
+export const normalizeRequest = config => {
+  if (!config.data) {
+    config.data = {}
+  }
+
+  if (config.params) {
+    normalizeData(config.params, config.url)
+  }
+  if (config.data) {
+    normalizeData(config.data, config.url)
+  }
+
+  if (!config.params && !config.data) {
+    config.data = {}
+  }
+
+  config.data = {
+    ...config.params,
+    ...config.data
+  }
+  return config
+}
+
+
+export const normalizeResponse = response => {
+  if (response.status !== 200) {
+    return response
+  }
+
+  let data = response.data
+  if (data.resultType !== 1) {
+    data = { code: -1, msg: data.resultMsg }
+  } else if (data.resultData.state) {
+    if (data.resultData.state === 'FAIL'){
+      data = { code: -1, msg: data.resultData.msg }
+    } else {
+      data = {
+        ...data.resultData,
+        code: 0,
+        msg: '接口调用成功',
+        data: data.resultData.data
+      }
+    }
+  } else if (data.resultData.code) {
+    if (data.resultData.code !== '00000'){
+      data = { code: -1, msg: data.resultData.message }
+    } else {
+      data = {
+        ...data.resultData,
+        code: 0,
+        msg: '接口调用成功',
+        data: data.resultData.data
+      }
+    }
+  }
+
+  return {
+    ...response,
+    data
+  }
+}
+
+
+export const normalizePagesContent = response => {
+  let data = response.data
+
+  if (data.code === 0) {
+    const params = response.config.params
+    let content 
+
+    if (Array.isArray(data.data)) {
+      const serviceContent = data  
+      content = {
+        list: serviceContent.data.map(item => ({
+          ...item,
+          auditStatus: Number(item.auditStatus) || 0
+        })),
+        curPage: params.current,
+        totalNum: serviceContent.total, 
+        totalPageNum: Math.ceil(serviceContent.total / params.pageSize)
+      }
+
+    } else {
+      const serviceContent =  data.data
+      content = {
+        list: serviceContent.records.map(item => ({
+          ...item,
+          auditStatus: Number(item.auditStatus) || 0
+        })),
+        curPage: params.currentPage,
+        totalNum: serviceContent.recordCount, 
+        totalPageNum: Math.ceil(serviceContent.recordCount / params.pageSize)
+      }
+    }
+
+    data = {
+      ...data,
+      data: content
+    }
+  }
+  
+  return {
+    ...response,
+    data
+  }
+}

+ 127 - 0
src/request/setupAxios.js

@@ -0,0 +1,127 @@
+import axios from 'axios'
+import qs from 'qs'
+import user from '@/state/user'
+import { getApp } from '@/app'
+import { openLoading, closeLoading } from './loading'
+import { openErrorMsg } from './errorMsg'
+import { 
+  fromUrls, 
+  fileUrls, 
+  GetUrls, 
+  PostUrls, 
+  notLoginUrls, 
+  HdUrls, 
+  getAuthConfig, 
+  FileFlow,
+  RedirctUrls,
+  RedirctPageUrls
+} from './config'
+import {
+  normalizePagesContent,
+  normalizeResponse,
+  normalizeRequest
+} from './normalizeRedirct'
+import devData from './simulation.dev'
+
+const gotoLoginCodes = [2001,3004,2005]
+
+axios.defaults.baseURL =  process.env.VUE_APP_PREFIX
+
+axios.interceptors.request.use(async config => {
+  const app = getApp()
+
+  if (!user.value.token && !~notLoginUrls.indexOf(config.url)) {
+    app.$router.replace({name: 'login'})
+    throw '用户未登录'
+  }
+
+  config.headers.token = user.value.token || ''
+  config.headers.userid = user.value.info.id
+
+  
+  if (~RedirctUrls.indexOf(config.url)) {
+    config = await normalizeRequest(config)
+  }
+
+  if (~HdUrls.indexOf(config.url)) {
+    let { data } = await axios.post(getAuthConfig, config.data)
+    config.data = {
+      client_code: process.env.VUE_APP_HD_CODE, 
+      req_time: data.timeStamp,
+      authcode: data.authcode,
+      ...config.data
+    }
+  }
+
+  if (~GetUrls.indexOf(config.url)) {
+    config.method = 'GET'
+  } else if (~PostUrls.indexOf(config.url)) {
+    config.method = 'POST'
+    if (!config.data && config.params) {
+      config.data = config.params 
+    }
+  }
+
+  // 处理需要用表单上传的请求
+  if (~fromUrls.indexOf(config.url)) {
+
+    config.data = qs.stringify(config.data)
+    config.headers['Content-Type'] = "application/x-www-form-urlencoded; charset=utf-8;"
+
+  } else if (~fileUrls.indexOf(config.url)) {
+    const fromData = new FormData()
+
+    Object.keys(config.data).forEach(key => {
+      fromData.append(key, config.data[key])
+    })
+    config.data = fromData
+    config.headers['Content-Type'] = "multipart/form-data"
+  }
+
+  openLoading(config.url)
+  return config
+})
+
+const resInterceptor = res => {
+  const app = getApp()
+  
+  closeLoading()
+
+  if (~FileFlow.indexOf(res.config.url)) {
+    return res
+  }
+  
+  if (~RedirctUrls.indexOf(res.config.url)) {
+    res = normalizeResponse(res)
+  }
+
+  if (~RedirctPageUrls.indexOf(res.config.url)) {
+    res = normalizePagesContent(res)
+  }
+
+  if (res.data.code !== 0 && res.data.code !== "000000" && res.data.code !== 200) {
+    if (~gotoLoginCodes.indexOf(res.data.code)) {
+      app.$router.replace({name: 'login'})
+      user.value.token = ''
+    } else if (process.env.NODE_ENV == 'development' && devData[res.config.url]) {
+      return devData[res.config.url]()
+    }
+    let errMsg = res.data.msg || res.data.message || '请求失败,服务端发生了点小故障!'
+    openErrorMsg(errMsg)
+
+    throw res.data.msg
+  }
+  return res.data
+}
+
+axios.interceptors.response.use(resInterceptor, (error) => {
+  closeLoading()
+  if (error.response && error.response.data) {
+    return resInterceptor(error.response || '请求失败,服务端发生了点小故障!')
+  } else {
+    openErrorMsg(typeof error === 'string' ? error : '请求失败,服务端发生了点小故障!')
+    return Promise.reject(error);
+  }
+  
+})
+

+ 192 - 0
src/request/simulation.dev.js

@@ -0,0 +1,192 @@
+import {
+  getCommunityList,
+  getHdCommunityNames,
+  getCommunityIdAll,
+  deleteCommunity,
+  communityOnline,
+  getVrCityList,
+  insertSecondHouse,
+  getCommunityByHouseId,
+  getSecondHouseList,
+  getPropertyList
+} from './config'
+
+const genList = (item, updateProps, total = 10) => {
+  const ret = []
+  for (let i = 0; i < total; i++) {
+    const copyItem = {
+      ...item
+    }
+    for (let prop of updateProps) {
+      copyItem[prop] = copyItem[prop] ? copyItem[prop] + i : copyItem[prop]
+    }
+    ret.push(copyItem)
+  }
+  return ret
+}
+
+
+const devData = {
+  [getSecondHouseList]: () => ({
+    "status": 200,
+    "code": 0,
+    "msg":"成功",
+    "data": {
+      "curPage":1,
+      "totalNum":448,
+      "totalPageNum":38,
+      "list": genList({
+        "gardenId": "HUS000011430366527977459712",
+        "building_Unit": "123",
+        "roomNumber": "142",
+        "constructionArea": "180㎡",
+        countNum: 10,
+        "insideArea": "160㎡",
+        "updateTime": "2021-08-25 11:18:46",
+        "reviewTime": "2021-08-25 11:18:46",
+        "reviewer": "梁勇-测试",
+        "maintenanceMan": "梁勇-测试",
+        "status": 2,
+      }, ['gardenName', 'building_Unit', 'roomNumber'], 15)
+    }
+  }),
+  [getCommunityByHouseId]: () => ({
+    "status":200,
+    "code":0,
+    "msg":"成功",
+    data: {
+      "gardenId": "HUS000011430366527977459712",
+      "estateId": "EST000011430366527914545152",
+      "fcbEstateId": "2994907509988755961",
+      "estateName": "VR-验收楼盘-请勿动",
+      "createTime": "2021-08-25 11:08:50",
+      "updateTime": "2021-08-25 11:18:46",
+      "buildYears": "2021-08-25 11:18:46",
+      "auditStatus": 2,
+      "cityId": 2,
+      "companyName": "房车宝",
+      "cityName": "珠海",
+      "propertyTypeName": "恒大",
+      "gardenName": "海湾城",
+      "areaName": "华发",
+      "number": "123123",
+      "creatorName": "梁勇-测试",
+      "auditorName": "梁勇-测试",
+      "auditTime": "2021-08-25 11:18:46",
+      "coverImagUrl": "https://vr-web02-uat.fcb.com.cn/cms_pano_fcb/image/20210825_110909542.jpg?d=1629860949632",
+      "createByName": "vr权限-拍摄人员",
+      "houseTitle": "VR-验收楼盘-请勿动",
+      "submitAuditTime": "2021-08-25 11:15:13",
+      "vrLink": null,
+      "vrPath": null,
+      "innerVrLink": null,
+      "type": 1,
+      "countNum": 1,
+      "garden": 1,
+      "house": 1,
+      "building": 1,
+      "sales": 0,
+      "points": 2,
+      "saleVideos": 0,
+      "houseVideos": 1
+    }
+  }),
+  [insertSecondHouse]: () => ({
+    "status":200,
+    "code":0,
+    "msg":"成功"
+  }),
+  [getVrCityList]: () => ({
+    "status":200,
+    "code":0,
+    "msg":"成功",
+    data: {
+      "resultData": genList({cityId: '0', cityName: '珠海'}, ['cityId', 'cityName'], 100)
+    }
+  }),
+  [getPropertyList]: () => ({
+    "status":200,
+    "code":0,
+    "msg":"成功",
+    data: {
+      "resultData": genList({propertyId: '0', propertyName: '物业'}, ['propertyId', 'propertyName'], 100)
+    }
+  }),
+  [communityOnline]: () => ({"status":200,"code":0,"msg":"成功"}),
+  [deleteCommunity]: () => ({"status":200,"code":0,"msg":"成功"}),
+  [getCommunityIdAll]: () => ({"status":200,"code":0,"msg":"成功","data":[1,2]}),
+  [getHdCommunityNames]: () => ({
+    "code": "000000",
+    "message": null,
+    "data": {
+      "records": genList({
+        "gardenId": "4271257006895227235",
+        "number": "123123123",
+        "gardenName": "宁夏回族自治区石嘴山市大武口区",
+        "areaName": "华发",
+        "cityName": "宁夏"
+      }, ['gardenId', 'gardenName'], 100),
+      "total": 297,
+      "size": 100,
+      "current": 1,
+      "orders": [],
+      "hitCount": false,
+      "type": 1,
+      "searchCount": true,
+      "pages": 3
+    }
+  }),
+  [getCommunityList]:() => ({
+    "status": 200,
+    "code": 0,
+    "msg":"成功",
+    "data": {
+      "curPage":1,
+      "totalNum":448,
+      "totalPageNum":38,
+      "list": genList({
+        "gardenId": "HUS000011430366527977459712",
+        "estateId": "EST000011430366527914545152",
+        "fcbEstateId": "2994907509988755961",
+        "estateName": "VR-验收楼盘-请勿动",
+        "createTime": "2021-08-25 11:08:50",
+        "updateTime": "2021-08-25 11:18:46",
+        "buildYears": "2021-08-25 11:18:46",
+        "auditStatus": 1,
+        "cityId": 2,
+        "companyName": "房车宝",
+        "cityName": "珠海",
+        "propertyTypeName": "恒大",
+        "gardenName": "海湾城",
+        "areaName": "华发",
+        "number": "123123",
+        "creatorName": "梁勇-测试",
+        "auditorName": "梁勇-测试",
+        "auditTime": "2021-08-25 11:18:46",
+        "coverImagUrl": "https://vr-web02-uat.fcb.com.cn/cms_pano_fcb/image/20210825_110909542.jpg?d=1629860949632",
+        "createByName": "vr权限-拍摄人员",
+        "houseTitle": "VR-验收楼盘-请勿动",
+        "submitAuditTime": "2021-08-25 11:15:13",
+        "vrLink": null,
+        "vrPath": null,
+        "innerVrLink": null,
+        "type": 1,
+        "countNum": 1,
+        "garden": 1,
+        "house": 1,
+        "building": 1,
+        "sales": 0,
+        "points": 2,
+        "saleVideos": 0,
+        "houseVideos": 1
+      }, ['gardenId', 'gardenName'])
+    }
+  })
+}
+
+export default new Proxy(devData, {
+  get() {
+    return void 0
+    // return Reflect.get(...arguments)
+  }
+})

+ 110 - 0
src/router/index.js

@@ -0,0 +1,110 @@
+import { createRouter, createWebHashHistory } from 'vue-router'
+import LoginView from '@/views/login'
+import ViewLayout from '@/view/layout'
+import Shop from '@/views/shop'
+import Category from '@/views/category'
+import Scene from '@/views/scene'
+import Log from '@/views/log'
+import Openvideo from '@/views/openvideo'
+import Framework from '@/views/framework'
+import User from '@/views/user'
+
+import navs from '@/state/navs'
+// import FirmwareView from '@/view/firmware'
+
+const routes = [
+  {
+    name: 'login',
+    path: '/login',
+    component: LoginView,
+    meta: { title: '登錄' }
+  },
+  {
+    name: 'viewLayout',
+    path: '/',
+    component: ViewLayout,
+    meta: { title: 'VR看店管理' },
+    children: [
+      {
+        name: 'scene',
+        path: 'scene',
+        meta: { title: '場景管理', checkAuth: true },
+        component: Scene,
+      },
+      {
+        name: 'shop',
+        path: 'shop',
+        meta: { title: '店鋪管理', checkAuth: true },
+        component: Shop,
+      },
+      {
+        name: 'category',
+        path: 'category',
+        meta: { title: '店鋪分類', checkAuth: true },
+        component: Category,
+      },
+      {
+        name: 'openvideo',
+        path: 'openvideo',
+        meta: { title: '開場視頻', checkAuth: true },
+        component: Openvideo,
+      },
+      {
+        name: 'framework',
+        path: 'framework',
+        meta: { title: '組織架構', checkAuth: true },
+        component: Framework,
+      },
+      {
+        name: 'user',
+        path: 'user',
+        meta: { title: '用戶管理', checkAuth: true },
+        component: User,
+      },
+      {
+        name: 'log',
+        path: 'log',
+        meta: { title: '操作日誌', checkAuth: true },
+        component: Log,
+      }
+      
+    ]
+  }
+]
+
+const router = createRouter({ 
+  history: createWebHashHistory(),
+  routes,
+})
+
+const isVisual = (navs, name) => 
+  {
+    return navs.some(item => 
+      item.name === name || 
+      (item.children && isVisual(item.children, name))
+    )
+  }
+
+router.beforeEach(async (to, from, next) => {
+  console.log(navs,'navs');
+  if (!to.name || to.name === 'viewLayout') {
+    router.replace({name: 'scene'})
+    return;
+  }
+
+  if (to.meta && to.meta.checkAuth) {
+    console.log(!navs.value);
+    while(!navs.value || navs.value.length === 0) {
+      await new Promise(r => setTimeout(r, 100))
+    }
+    
+    if (!isVisual(navs.value, to.name)) {
+      isVisual(navs.value, 'scene') ? router.replace({name: 'scene'}): router.replace({name: 'login'})
+      return;
+    }
+  }
+  next()
+})
+
+export const config = routes
+export default router

+ 70 - 0
src/state/navs.js

@@ -0,0 +1,70 @@
+import { config } from "@/router";
+import user from "./user";
+import { watch, ref } from "vue";
+
+// 不加入导航条的页面
+const NOT_JOIN_NAVS = [];
+// 所有权限都有的router
+const MUST_JOIN_NAVS = ["viewLayout"];
+// router对应的icon
+const ICON_MAP = { viewLayout: "el-icon-s-home" };
+
+const getNames = (config) => {
+  let names = [];
+  config.forEach((item) => {
+    names.push(item.name);
+    item.children && names.push(...getNames(item.children));
+  });
+  return names;
+};
+
+const _getNavs = (items, notJoinNavs) => {
+  let ret = [];
+  for (let i = 0; i < items.length; i++) {
+    if (~notJoinNavs.indexOf(items[i].name)) {
+      continue;
+    } else {
+      let item = {
+        ...items[i],
+        children: items[i].children ? _getNavs(items[i].children, notJoinNavs) : null,
+        icon: ICON_MAP[items[i].name],
+      };
+
+      delete item.component;
+      ret.push(item);
+    }
+  }
+  console.log(ret,'ret');
+  return ret;
+};
+
+const getNavs = () => {
+  let names = getNames(config);
+  let noShowNames = ['login']
+  if (user.value.role == 3) {
+    let layout = config.find((item) => item.name == "viewLayout");
+    layout.children.forEach((item) => {
+      if (item.name != "shop" && item.name != "scene") {
+        noShowNames.push(item.name);
+      }
+    });
+  }
+  console.log(noShowNames,'noShowNames');
+  let notJoinNavs = names.filter((name) => ~NOT_JOIN_NAVS.indexOf(name) || (
+    ~noShowNames.indexOf(name) && 
+    !~MUST_JOIN_NAVS.indexOf(name)
+  ));
+  console.log(notJoinNavs,'itemitem');
+
+  return _getNavs(config, notJoinNavs);
+};
+
+const navs = ref([]);
+
+watch(
+  () => user.value.role,
+  () => (navs.value = getNavs())
+);
+setTimeout(() => (navs.value = getNavs()));
+
+export default navs;

+ 285 - 0
src/state/tableRef.js

@@ -0,0 +1,285 @@
+import axios from 'axios'
+import { ref, watch, onActivated, onMounted } from 'vue'
+import { getApp } from '@/app'
+import { UN_REQ_NUM } from '@/constant'
+import { throttle } from '@/util'
+
+const getOperState = (attr) => {
+  const operState = ref({
+    state: {
+      show: false,
+      id: 0, 
+      ...JSON.parse(JSON.stringify(attr)) 
+    },
+    _readyUpdate(item) {
+      operState.value.state.id = item.id
+      operState.value.state.show = true
+  
+      Object.keys(attr).forEach(key => {
+        operState.value.state[key] = attr[key]
+      })
+
+      Object.keys(attr).forEach(key => {
+        if (item[key] !== void 0) {
+          operState.value.state[key] = item[key]
+        }
+      })
+    },
+    readyInsert() {
+      operState.value.reset()
+      operState.value.state.show = true
+    },
+    reset() {
+      Object.keys(attr).forEach(key => {
+        operState.value.state[key] = attr[key]
+      })
+      operState.value.state.show = false
+      operState.value.state.id = void 0
+    },
+    quit() {
+      operState.value.reset()
+    }
+  })
+
+  return operState
+}
+
+const getSearchState = (attr) => {
+  const searchState = ref({
+    state: { ...JSON.parse(JSON.stringify(attr)) },
+    reset() {
+      Object.keys(attr).forEach(key => {
+        searchState.value.state[key] = attr[key]
+      })
+    }
+  })
+
+  return searchState
+}
+
+
+const getDataListState = (isSelect) => {
+  const listStateState = ref({
+    state: [],
+  })
+
+  if (isSelect) {
+    listStateState.value.selectRows = []
+    listStateState.value.changeSelectRows = (data) => {
+      listStateState.value.selectRows = data
+    }
+  }
+
+  return listStateState
+}
+
+const getPagState = () => {
+  const pagState = ref({
+    state: {
+      currPage: 1,
+      size: 12,
+      total: 0
+    },
+    sizeChange(attr) {
+      pagState.value.state.size = attr
+    },
+    currentChange(attr) {
+      pagState.value.state.currPage = attr
+    }
+  })
+
+  return pagState
+}
+
+export const getParams = (ret) => {
+  let params = {}
+  Object.keys(ret).forEach(key => {
+    if (UN_REQ_NUM !== ret[key]) {
+      if (typeof ret[key] === 'string') {
+        params[key] = ret[key].trim()
+      } else {
+        params[key] = ret[key]
+      }
+    }
+  })
+  return params
+}
+
+const referListData = async (ret, getUrl, pagAttr = {}) => {
+  let params = {
+    ...(ret.search ? getParams(ret.search.value.state) : {}),
+    pageNum: ret.pag.value.state.currPage,
+    pageSize: ret.pag.value.state.size
+  }
+  let paramsStr = JSON.stringify(params)
+  let dateNow = Date.now()
+
+  if (ret.dataList.cacheNow && paramsStr === ret.dataList.cacheParams && dateNow - ret.dataList.cacheNow < 1000) {
+    return;
+  }
+  ret.dataList.cacheParams = JSON.stringify(params)
+  ret.dataList.cacheNow = dateNow
+
+  let res = await axios.post(getUrl,  params)
+
+  let list = pagAttr['list'] ? res.data[pagAttr['list']] : res.data.list
+
+  if (pagAttr.fixBooStatus) {
+    list.forEach(item => {
+      item[pagAttr.fixBooStatus] = Boolean(item[pagAttr.fixBooStatus])
+    })
+  }
+  
+  if (pagAttr.listMap) {
+    list.forEach(item => {
+      item.showStatus = Boolean(item.showStatus)
+      for (let key of Object.keys(pagAttr.listMap)) {
+        if (key in item) {
+          item[pagAttr.listMap[key]] = item[key]
+        }
+      }
+    })
+  }
+  
+  ret.pag.value.state.total = pagAttr['totalNum'] ? res.data[pagAttr['totalNum']] : res.data.totalNum
+  ret.dataList.value.state = res.data.list = list
+}
+
+
+export default ({operAttr, searchAttr, throttleTime,deletealert, pagAttr, isSelect = true, getUrl, updateUrl, insertUrl, delUrl } = {}) => {
+  let ret = {
+    dataList: getDataListState(isSelect),
+    pag: getPagState()
+  }
+  let watchItems = [
+    () => ret.pag.value.state.currPage,
+    () => ret.pag.value.state.size,
+  ]
+
+  if (operAttr) {
+    const oper = getOperState(operAttr)
+
+    oper.value.readyUpdate = function(...args) {
+      oper.value._readyUpdate(...args)
+      oper.value.state.__oldData = ret.dataList.value.state.find(({id}) => id === oper.value.state.id)
+    }
+
+    updateUrl && (
+      oper.value.update = async (data) => {
+        // if (pagAttr.fixBooStatus) {
+        //   pagAttr.fixBooStatus
+        //   list.forEach(item => {
+        //     item[] = Boolean(item[pagAttr.fixBooStatus])
+        //   })
+        // }
+        let target = data || {...oper.value.state}
+        let updateIndex = ret.dataList.value.state.findIndex(({id}) => id === target.id)
+        let item = { ...ret.dataList.value.state[updateIndex] }
+
+        if (item) {
+          Object.keys(operAttr).forEach(key => {
+            if (key==pagAttr.fixBooStatus) {
+              item[key] = Number(target[key])
+            }
+            else{
+              item[key] = target[key]
+            }
+          })
+        }
+        
+        await axios.post(updateUrl, data || item)
+        
+        ret.dataList.value.state[updateIndex] = item
+        oper.value.reset()
+        ret.dataList.value.refer()
+      }
+    )
+
+    insertUrl && (
+      oper.value.insert = async (data) => {
+        let target = data || oper.value.state
+        let item = {}
+        if (item) {
+          Object.keys(operAttr).forEach(key => {
+            if (key==pagAttr.fixBooStatus) {
+              item[key] = Number(target[key])
+            }
+            else{
+              item[key] = target[key]
+            }
+          })
+        }
+        
+
+        await axios.post(insertUrl, data || item)
+
+        ret.dataList.value.state.push(item)
+        oper.value.reset()
+        ret.dataList.value.refer()
+      }
+    )
+
+    ret.oper = oper
+  }
+
+  if (searchAttr) {
+    const search = getSearchState(searchAttr)
+
+    search.value.submit = () => {
+      ret.dataList.value.refer()
+    }
+    ret.search = search
+
+    watchItems.push(search.value.state)
+  }
+
+
+  if (updateUrl || delUrl) {
+    if (delUrl) {
+      ret.dataList.value._delete = async (data) => {
+        await axios.post(delUrl, {id: data.id,sceneNum:data.num})
+      }
+    } else {
+      ret.dataList.value._delete = async (data) => {
+        data.isDelete = 1
+        await axios.post(updateUrl, data)
+      }
+    }
+
+    ret.dataList.value.delete = async (data) => {
+      if (await getApp().$confirm(deletealert || '確定要刪除此數據嗎?', '提示')) {
+        await ret.dataList.value._delete(data)
+        getApp().$alert('刪除成功')
+        ret.dataList.value.refer()
+      }
+    }
+
+    if (isSelect) {
+      ret.dataList.value.deleteSelect = async () => {
+        const rows = ret.dataList.value.selectRows
+        if (rows.length === 0) {
+          getApp().$alert('請勾選數據後再刪除數據!', '提示')
+        } else if (await getApp().$confirm(`確定要刪除這${rows.length}條數據嗎?`, '提示')) {
+          await Promise.all(rows.map(item => ret.dataList.value._delete(item)))
+          getApp().$alert('刪除成功')
+          ret.dataList.value.refer()
+        }
+      }
+    }
+  }
+
+  ret.dataList.value.refer = throttle(
+    () => {
+      referListData(ret, getUrl, pagAttr)
+    },
+    throttleTime === 0 ? 0 : 300
+  )
+
+  // 刷新數據
+  watch( watchItems, ret.dataList.value.refer )
+  // 激活再刷新一次
+  onActivated(() => referListData(ret, getUrl, pagAttr))
+  onMounted(() => referListData(ret, getUrl, pagAttr))
+
+  return ret
+}

+ 57 - 0
src/state/user.js

@@ -0,0 +1,57 @@
+import { ref, watch } from 'vue'
+
+
+const strToJson = (str, def) => {
+  try {
+    str = JSON.parse(str)
+  } catch {
+    str = def
+  }
+  return str || def
+}
+
+const user = ref({
+  token: localStorage.getItem('token'),
+  info: strToJson(localStorage.getItem('info'), {}),
+  role: localStorage.getItem('role'),
+  fdkkToken: localStorage.getItem('fdkkToken')
+})
+
+watch(
+  () => user.value.token,
+  () => localStorage.setItem('token', user.value.token)
+)
+
+watch(
+  () => user.value.role,
+  () => localStorage.setItem('role', user.value.role)
+)
+
+
+watch(
+  () => user.value.info,
+  () => localStorage.setItem('info', JSON.stringify(user.value.info))
+)
+
+watch(
+  () => user.value.fdkkToken,
+  () => localStorage.setItem('fdkkToken', user.value.fdkkToken)
+)
+
+export const setFDKKToken = val => {
+  user.value.fdkkToken = val
+}
+
+export const setToken = val => {
+  user.value.token = val
+}
+
+export const setInfo = val => {
+  user.value.info = val
+}
+
+export const setRole = val => {
+  user.value.role = val
+}
+
+export default user

+ 36 - 0
src/state/viewAuth.js

@@ -0,0 +1,36 @@
+import { keyViewMap, attach } from '@/constant/view'
+import { ref, watch } from 'vue'
+import router from '@/router'
+
+const auth = ref({})
+
+
+const setCurrentAuth = () => {
+  let viewName = router.currentRoute.value.name
+  
+  viewName = attach[viewName] ? attach[viewName] : viewName
+
+  let key = Object.keys(keyViewMap).find(key => keyViewMap[key] === viewName) || viewName
+  let authParent = [].find(item => item.resourceKey === key)
+
+
+  if (authParent) {
+    let viewAuths = authParent.children.map(item => item.resourceKey.substr(key.length + 1))
+    let newValue = {}
+    viewAuths.forEach(key => newValue[key] = true)
+    auth.value = newValue
+  } else {
+    auth.value = {}
+  }
+}
+
+setTimeout(() => {
+  watch(
+    router.currentRoute, 
+    setCurrentAuth, 
+    { immediate: true }
+  )
+})
+
+
+export default auth

+ 5 - 0
src/util/file.js

@@ -0,0 +1,5 @@
+export function convertBlob2File(blob, name) {
+  return new File([blob], name, {
+      type: blob.type,
+  })
+}

+ 112 - 0
src/util/index.js

@@ -0,0 +1,112 @@
+import * as Base64 from 'js-base64'
+
+export const dateFormat = (date, fmt) => {
+  var o = {
+    "M+": date.getMonth() + 1,                 //月份 
+    "d+": date.getDate(),                    //日 
+    "h+": date.getHours(),                   //小时 
+    "m+": date.getMinutes(),                 //分 
+    "s+": date.getSeconds(),                 //秒 
+    "q+": Math.floor((date.getMonth() + 3) / 3), //季度 
+    "S": date.getMilliseconds()             //毫秒 
+  };
+  if (/(y+)/.test(fmt)) {
+    fmt = fmt.replace(RegExp.$1, (date.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 const copyText = (text) => {
+  const input = document.createElement('input')
+  document.body.appendChild(input);
+
+  input.setAttribute('value', text);
+  input.select()
+
+  document.execCommand('copy')
+
+  document.body.removeChild(input)
+}
+
+
+export const throttle = (fn, mis = 500) => {
+  let time
+
+  return (...args) => {
+    clearTimeout(time)
+    time = setTimeout(() => fn(...args), mis)
+  }
+}
+
+export 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
+}
+
+export const encryption = (str, strv = '') => {
+  str = Base64.encode(str)
+  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
+}
+
+
+/**
+ *获取id
+ */
+export const guid = () => {
+  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+    let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
+    return v.toString(16);
+  });
+}
+
+
+export const strToParams = str => {
+  if (str[0] === '?') {
+    str = str.substr(1)
+  }
+
+  const result = {}
+  const splitRG = /([^=&]+)(?:=([^&]*))?&?/
+
+  let rgRet
+  while ((rgRet = str.match(splitRG))) {
+    result[rgRet[1]] = rgRet[2]
+    str = str.substr(rgRet[0].length)
+  }
+  
+  return result
+}
+
+export const paramsToStr = params => 
+  '?' + Object.keys(params)
+    .map(key => `${key}${params[key] == null ? '' : `=${params[key]}`}`)
+    .join('&')

+ 219 - 0
src/view/camera/index.vue

@@ -0,0 +1,219 @@
+<template>
+  <com-head :options="headList" showCtrl>
+    <el-form label-width="90px" inline="true">
+      <el-form-item label="S/N:">
+        <el-input v-model="search.state.snCode" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="所属公司:">
+        <com-company v-model="search.state.departmentId" />
+        <!-- <el-input v-model="search.state.searchKey" placeholder="请输入"></el-input> -->
+      </el-form-item>
+      <el-form-item label="激活时间:">
+        <el-date-picker
+          v-model="time"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item class="searh-btns">
+        <el-button @click="search.reset">重置</el-button>
+        <el-button type="primary" @click="search.submit">查询</el-button>
+      </el-form-item>
+    </el-form>
+  </com-head>
+
+  <div class="body-layer">
+    <div class="body-head">
+      <h3>相机列表</h3>
+      <div class="table-ctrl-right">
+        <!-- <el-input placeholder="关键词" suffix-icon="el-icon-search" class="search-scene" v-model="search.state.searchKey" style="width: auto" /> -->
+        <el-button
+          type="primary"
+          icon="el-icon-plus"
+          @click="oper.readyInsert"
+          v-if="auth.add"
+          >添加相机</el-button
+        >
+      </div>
+    </div>
+    <el-table
+      ref="multipleTable"
+      :data="dataList.state"
+      tooltip-effect="dark"
+      style="width: 100%"
+      @row-click="dataList.selectRow"
+    >
+      <el-table-column label="S/N" prop="snCode"></el-table-column>
+      <el-table-column label="Wi-Fi名称" prop="wifiName"></el-table-column>
+      <el-table-column label="激活时间" prop="activatedTime"></el-table-column>
+      <el-table-column label="所属架构" prop="departmentName"></el-table-column>
+      <el-table-column label="拍摄VR数量" prop="sceneCount"></el-table-column>
+      <el-table-column label="最后拍摄时间" prop="lastTime"></el-table-column>
+      <el-table-column label="操作" v-slot:default="{ row }" v-if="auth.unbind || auth.update">
+        <span class="oper-span" @click="oper.readyUpdate(row)" v-if="auth.update">编辑</span>
+        <span class="oper-span" 
+          @click="unbindCamrea(row)" 
+          v-if="auth.unbind && row.departmentId" 
+          style="color: var(--primaryColor)">
+         解绑
+        </span>
+      </el-table-column>
+    </el-table>
+
+    <com-pagination
+      @size-change="pag.sizeChange"
+      @current-change="pag.currentChange"
+      :current-page="pag.state.currPage"
+      :page-size="pag.state.size"
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="pag.state.total"/>
+  </div>
+  
+  <com-dialog
+    :title="(oper.state.id ? '修改' : '添加') + '相机'"
+    v-model:show="oper.state.show"
+    @submit="submit"
+  >
+    <el-form ref="form" :model="form" label-width="100px" class="camera-from">
+      <!-- <el-form-item label="相机类型:" class="mandatory">
+        <el-select v-model="oper.state.region" placeholder="相机类型:">
+          <el-option label="区域一" value="shanghai"></el-option>
+          <el-option label="区域二" value="beijing"></el-option>
+        </el-select>
+      </el-form-item> -->
+      <el-form-item label="Wi-Fi名称:" class="mandatory">
+        <el-input :modelValue="oper.state.wifiName" @update:modelValue="val => oper.state.wifiName = val.trim()"></el-input>
+      </el-form-item>
+      <el-form-item label="物理地址:" class="mandatory">
+        <el-input :modelValue="oper.state.childName" @update:modelValue="val => oper.state.childName = val.trim()"></el-input>
+      </el-form-item>
+      <el-form-item label="S/N:" class="mandatory">
+        <el-input :modelValue="oper.state.snCode" @update:modelValue="val => oper.state.snCode = val.trim()"></el-input>
+      </el-form-item>
+      <el-form-item label="所属架构:" class="mandatory">
+        <com-company :modelValue="oper.state.departmentId" @update:modelValue="val => oper.state.departmentId = val.trim()" hideAll />
+      </el-form-item>
+    </el-form>
+  </com-dialog>
+
+</template>
+
+<script>
+import { ref, watch } from "vue";
+import getTableState from "@/state/tableRef";
+import auth from "@/state/viewAuth";
+import comDialog from "@/components/dialog";
+import comCompany from "@/components/company-select";
+import comPagination from "@/components/pagination";
+import comHead from "@/components/head";
+import { dateFormat } from '@/util'
+import {
+  getCameraList,
+  insertCamera,
+  updateCamera,
+  deleteCamera,
+  unbindCamera
+} from '@/request/config'
+import axios from 'axios';
+
+export default {
+  name: 'camera',
+  setup() {
+    const state = getTableState({
+      updateUrl: updateCamera,
+      insertUrl: insertCamera,
+      getUrl: getCameraList,
+      delUrl: deleteCamera,
+      searchAttr: { startTime: "", endTime: '', departmentId: '', snCode: '' },
+      operAttr: {
+        wifiName: '',
+        snCode: '',
+        childName: '',
+        departmentId: ''
+      },
+      pagAttr: {totalNum: 'total'}
+    });
+    const headList = ref([{ name: "我的相机", value: 2 }]);
+    const time = ref(null)
+
+    watch(time, () => {
+      if (time.value) {
+        state.search.value.state.startTime = dateFormat(new Date(time.value[0]), 'yyyy-MM-dd hh:mm:ss')
+        state.search.value.state.endTime = dateFormat(new Date(time.value[1]), 'yyyy-MM-dd 23:59:59')
+      } else {
+        state.search.value.state.startTime = state.search.value.state.endTime = ''
+      }
+    })
+    watch(
+      [() => state.search.value.state.startTime, () => state.search.value.state.endTime],
+      () => {
+        if (!state.search.value.state.startTime || !state.search.value.state.endTime) {
+          time.value = null
+        }
+      }
+    )
+    
+    watch(
+      () => state.pag.value.state.total,
+      () => headList.value[0].name = '我的相机(' + state.pag.value.state.total +')'
+    )
+
+
+    return { ...state, headList, time, auth };
+  },
+  methods: {
+    async unbindCamrea(data) {
+      if (data.departmentId && (await this.$confirm('确定要解绑此相机吗?', '提示'))) {
+        await axios.post(unbindCamera, data)
+        data.departmentName = data.departmentId = null
+      }
+
+      this.dataList.refer()
+    },
+    submit() {
+      if (!this.oper.state.wifiName.trim()) {
+        return this.$alert('Wi-Fi名称不能为空!', '提示')
+      } else if (!this.oper.state.snCode.trim()) {
+        return this.$alert('S/N不能为空!', '提示')
+      } else if (!this.oper.state.childName.trim()) {
+        return this.$alert('物理地址不能为空!', '提示')
+      } else if (!this.oper.state.departmentId.trim()) {
+        return this.$alert('所属架构不能为空!', '提示')
+      }
+
+      this.oper.state.id ? this.oper.update() : this.oper.insert()
+    }
+  },
+  components: {
+    "com-dialog": comDialog,
+    "com-company": comCompany,
+    "com-head": comHead,
+    "com-pagination": comPagination
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.table-ctrl-right {
+  .search-scene {
+    margin: 0 20px 0 26px;
+  }
+  i {
+    margin-left: 20px;
+    font-size: 1.7rem;
+    vertical-align: middle;
+    cursor: pointer;
+
+    &.active {
+      color: var(--primaryColor);
+    }
+  }
+}
+
+.camera-from {
+  width: 320px;
+  margin: 0 auto;
+}
+</style>

+ 469 - 0
src/view/examine/estate/index.vue

@@ -0,0 +1,469 @@
+<template>
+  <com-head :options="headList" showCtrl v-model="mpStatus">
+    <el-form label-width="90px" inline="true">
+      <el-form-item label="VR项目:">
+        <el-input v-model="search.state.houseTitle" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="项目类型:">
+        <el-select v-model="search.state.type" placeholder="全部">
+          <el-option v-for="(item) in houseType" :key="item.value" :label="item.name" :value="item.value"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="所属楼盘:">
+        <el-input v-model="search.state.estateName" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="公司:">
+        <com-company v-model="search.state.belongCompany" />
+      </el-form-item>
+      <el-form-item label="审核状态:">
+        <el-select v-model="mpStatus" placeholder="全部">
+          <el-option v-for="(item) in status" :key="item.value" :label="item.name" :value="item.value"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="提审时间:">
+        <el-date-picker
+          v-model="time"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+      <!-- <el-form-item label="提审时间:">
+        <el-date-picker
+          v-model="submitAuditTime"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item> -->
+      <el-form-item class="searh-btns">
+        <el-button @click="search.reset">重置</el-button>
+        <el-button type="primary" @click="search.submit">查询</el-button>
+        <el-button type="primary" @click="exportExe" :disabled="!pag.state.total">导出</el-button>
+      </el-form-item>
+    </el-form>
+  </com-head>
+
+  <div class="body-layer">
+    <div class="body-head">
+      <h3>{{title}}</h3>
+    </div>
+
+    <el-table
+      ref="multipleTable"
+      :data="dataList.state"
+      tooltip-effect="dark"
+      style="width: 100%"
+      @row-click="selectRow"
+    >
+      <el-table-column label="VR项目" prop="houseTitle" v-slot:default="{ row }">
+        <span  @click="shareRow(row)" style="cursor: pointer">{{row.houseTitle}}</span>
+      </el-table-column>
+      <el-table-column label="项目类型" prop="type" v-slot:default="{ row }">
+        <span>{{getHouseName(row.type)}}</span>
+      </el-table-column>
+      <el-table-column label="所属楼盘" prop="estateName"></el-table-column>
+      <el-table-column label="公司" prop="belongCompanyName"></el-table-column>
+      <el-table-column label="创建人" prop="createByName"></el-table-column>
+      <el-table-column label="提审时间" prop="submitAuditTime"></el-table-column>
+      <el-table-column label="审核状态" prop="auditStatus" v-slot:default="{ row }">
+        <div class="status" :class="{interactive: row.countNum > 0}" @click="row.countNum > 0  && showSelectRow(row)">
+          <span>{{row.status === adoptStatus ? row.online ? '已上线': '已审核' : getExamineName(row.status)}}</span>
+          <template v-if="row.countNum > 0">
+            <i class="el-icon-document" v-if="row.status !== noAdoptStatus && row.status !== noComAdoptStatus"></i>
+            <i class="el-icon-warning-outline error" v-else></i>
+          </template>
+        </div>
+      </el-table-column>
+      <el-table-column label="审核人" prop="auditorName"></el-table-column>
+      <el-table-column label="审核时间" prop="auditTime"></el-table-column>
+      <el-table-column label="操作" v-slot:default="{ row }">
+        <span class="oper-span" @click="shareRow(row)">预览</span>
+        <span class="oper-span"
+          :class="{
+            disable: !(
+              (row.status === noAuditStatis && (user.role === 'admin' || user.role === 'group')) ||
+              (row.status === noComAuditStatus && (user.role === 'admin' || user.role === 'region'))
+            )
+          }" 
+          @click="(
+            (row.status === noAuditStatis && (user.role === 'admin' || user.role === 'group')) ||
+            (row.status === noComAuditStatus && (user.role === 'admin' || user.role === 'region'))
+          ) && oper.readyUpdate(row); (oper.state.operStatus = 1)" 
+          v-if="auth.update">
+          
+          审核
+        </span>
+        <!-- <span
+          v-if="auth.update"
+          class="oper-span"
+          :class="{disable: row.status !== adoptStatus}"
+          @click="row.status === adoptStatus && offlineItem(row)">
+          {{row.online === 1 ? '下线' : '上线' }}
+        </span> -->
+      </el-table-column>
+    </el-table>
+    <com-pagination
+      @size-change="pag.sizeChange"
+      @current-change="pag.currentChange"
+      :current-page="pag.state.currPage"
+      :page-size="pag.state.size"
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="pag.state.total"/>
+  </div>
+
+  <com-record v-model:show="recordData.show" :data="recordData.id" v-if="recordData.show" />
+  
+  <com-dialog title="审核" v-model:show="oper.state.show" @submit="updateExamine()">
+    <el-form ref="form" :model="oper.state" label-width="120px" class="examine-from">
+      <el-form-item label="审核结果:">
+        <el-radio-group v-model="oper.state.operStatus">
+          <el-radio v-for="item in examineMap" :key="item.value" :label="item.value">{{item.name}}</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="审核备注:">
+        <el-input type="textarea" v-model="oper.state.maker" maxlength="500" show-word-limit></el-input>
+      </el-form-item>
+    </el-form>
+  </com-dialog>
+
+  <com-up-select 
+    v-if="upData.show"
+    :items="upData.data"
+    v-model:show="upData.show" 
+    v-model="upData.value"
+    @online="offlineItem(upData.row, true)"
+  />
+  
+  <com-share
+    :setLogo="auth.update"
+    v-if="shareData.show"
+    v-model:show="shareData.show" 
+    :items="shareData.data" />
+
+</template>
+
+<script>
+import axios from 'axios';
+import { computed, ref, watch, watchEffect } from "vue";
+import getTableState, {getParams} from "@/state/tableRef";
+import comDialog from "@/components/dialog";
+import auth from "@/state/viewAuth";
+import comPagination from "@/components/pagination";
+import comCompany from "@/components/company-select";
+import comRecord from "@/components/record";
+import comHead from "@/components/head";
+import comShare from "@/view/properties/estate/share";
+import comUpSelect from "@/view/properties/estate/up-select";
+import { dateFormat } from '@/util'
+import user from '@/state/user'
+import {
+  getExamineViewList,
+  estateExamine,
+  estateOnline,
+  getHouseShares,
+  getHouseUpShares,
+  hasHouseIsDel,
+  exporrtAuditEstate
+} from '@/request/config'
+import { 
+  HOUSE_TYPE_SELECT, 
+  EXAMINE_REQ_SELECT,
+  EXAMINE_SHOW_STATUS, 
+  EXAMINE_ALL_STATUS,
+  EXAMINE_NO_ADOPT_STATUS,
+  EXAMINE_NO_COM_ADOPT_STATUS,
+  EXAMINE_NO_AUDIT_STATUS,
+  UN_REQ_NUM,
+  EXAMINE_ADOPT_STATUS,
+  EXAMINE_NO_ADOPT_ALL_STATUS,
+  EXAMINE_NO_COM_AUDIT_STATUS,
+  EXAMINE_DRAFT_STATUS,
+  EXANINE_SUCCESS_STATUS,
+  getName 
+} from '@/constant'
+
+export default {
+  name: 'examine',
+  setup() {
+    const mpStatus = ref(user.value.role === 'region' ? EXAMINE_NO_COM_AUDIT_STATUS : EXAMINE_NO_AUDIT_STATUS)
+    const headList = ref(EXAMINE_SHOW_STATUS);
+    const state = getTableState({
+      getUrl: getExamineViewList,
+      updateUrl: estateExamine,
+      operAttr: { 
+        operStatus: 1,
+        maker: '',
+        auditId: ''
+      },
+      searchAttr: { 
+        time: "", estateName: '', houseTitle: '', type: UN_REQ_NUM, 
+        status: mpStatus.value, 
+        statuslist: '',
+        belongCompany: '', 
+        online: '',
+        startSubmitAuditTime: '', endSubmitAuditTime: '' , 
+        startTime: '', endTime: '' 
+      }
+    });
+
+    watchEffect(() => {
+      if (mpStatus.value === EXANINE_SUCCESS_STATUS) {
+        state.search.value.state.statuslist = EXAMINE_ADOPT_STATUS + ''
+        state.search.value.state.online = 0
+      } else if (mpStatus.value === EXAMINE_ADOPT_STATUS) {
+        state.search.value.state.online = 1
+        state.search.value.state.statuslist = mpStatus.value + ''
+      } else if (mpStatus.value === EXAMINE_NO_ADOPT_ALL_STATUS) {
+        state.search.value.state.online = ''
+        state.search.value.state.statuslist = [EXAMINE_NO_COM_ADOPT_STATUS, EXAMINE_NO_ADOPT_STATUS].join(',')
+      } else if (mpStatus.value === EXAMINE_ALL_STATUS){
+        state.search.value.state.statuslist = ''
+      } else {
+        state.search.value.state.online = ''
+        state.search.value.state.statuslist = mpStatus.value  + ''
+      }
+
+    })
+
+    const title = computed(() => {
+      let item = headList.value.find(({value}) => value === state.search.value.state.operStatus)
+      return item ? item.name : ''
+    })
+    const time = ref(null)
+    const submitAuditTime = ref(null)
+
+    watch(time, () => {
+      if (time.value) {
+        state.search.value.state.startTime = dateFormat(new Date(time.value[0]), 'yyyy-MM-dd hh:mm:ss')
+        state.search.value.state.endTime = dateFormat(new Date(time.value[1]), 'yyyy-MM-dd 23:59:59')
+      } else {
+        state.search.value.state.startTime = state.search.value.state.endTime = ''
+      }
+    })
+
+    watch(
+      [() => state.search.value.state.startTime, () => state.search.value.state.endTime],
+      () => {
+        if (!state.search.value.state.startTime || !state.search.value.state.endTime) {
+          time.value = null
+        }
+      }
+    )
+
+    
+    watch(submitAuditTime, () => {
+      if (submitAuditTime.value) {
+        state.search.value.state.startSubmitAuditTime = dateFormat(new Date(submitAuditTime.value[0]), 'yyyy-MM-dd hh:mm:ss')
+        state.search.value.state.endSubmitAuditTime = dateFormat(new Date(submitAuditTime.value[1]), 'yyyy-MM-dd 23:59:59')
+      } else {
+        state.search.value.state.startSubmitAuditTime = state.search.value.state.endSubmitAuditTime = ''
+      }
+    })
+    watch(
+      [() => state.search.value.state.startSubmitAuditTime, () => state.search.value.state.endSubmitAuditTime],
+      () => {
+        if (!state.search.value.state.startSubmitAuditTime || !state.search.value.state.endSubmitAuditTime) {
+          submitAuditTime.value = null
+        }
+      }
+    )
+
+    
+    
+
+    return { ...state, headList, title, auth, time, submitAuditTime, mpStatus };
+  },
+  data() { 
+    return {
+      
+      recordData: { show: false, id: 0 },
+      examineMap: EXAMINE_REQ_SELECT,
+      status: EXAMINE_SHOW_STATUS,
+      draftStatus: EXAMINE_DRAFT_STATUS,
+      noAdoptStatus: EXAMINE_NO_ADOPT_STATUS,
+      adoptStatus: EXAMINE_ADOPT_STATUS,
+      noAuditStatis: EXAMINE_NO_AUDIT_STATUS,
+      noComAuditStatus: EXAMINE_NO_COM_AUDIT_STATUS,
+      noComAdoptStatus: EXAMINE_NO_COM_ADOPT_STATUS,
+      upData: {show: false, data: 0, value: {init: null, vrList: []}, row: null},
+      houseType: HOUSE_TYPE_SELECT,
+      shareData: { show: false, data: 0 },
+      user
+    }
+  },
+
+  methods: {
+    async exportExe() {
+      let params = {
+        ...getParams(this.search.state),
+        pageNum: this.pag.state.currPage,
+        pageSize: this.pag.state.size
+      }
+      if (!params.startTime || !params.endTime) {
+        return this.$alert('请先选择提审时间段', '提示')
+      } else {
+        const monthDiff = (startDate, endDate) => Math.max(0, (endDate.getFullYear() - startDate.getFullYear()) * 12 - startDate.getMonth() + endDate.getMonth());
+        if (monthDiff(this.time[0], this.time[1]) > 3) {
+          return this.$alert('最多支持导出连续四个月的数据', '提示')
+        }
+      }
+      
+      let res = await axios.get(exporrtAuditEstate, { params })
+      location.href = res.data.data
+    },
+    async shareRow(data) {
+      let res = await axios.get(getHouseShares, { params: {houseId: data.id} })
+      let shareItems = []
+      if (res.data) {
+        shareItems = res.data.map(item => ({
+          ...item,
+          QrCode: item.icon,
+          type: item.type,
+          houseTitle: item.sceneTitle || data.houseTitle,
+          vrLink: process.env.VUE_APP_DOMAIN + item.webSite,
+          innerVrLink: process.env.VUE_APP_INTRANET_DOMAIN + item.webSite,
+          online: data.online,
+          status: data.status,
+        }))
+      }
+
+      if (shareItems.length) {
+        this.shareData.show = true
+        this.shareData.data = shareItems
+      } else {
+        await this.$confirm('暂无预览链接,请先设置初始场景。', '提示')
+      }
+    },
+    getExamineName: getName(EXAMINE_SHOW_STATUS),
+    getHouseName: getName(HOUSE_TYPE_SELECT),
+    showSelectRow(data) {
+      this.recordData.show = true
+      this.recordData.id = data.id
+    },
+    async updateExamine() {
+      if (!this.oper.state.operStatus) {
+        return this.$alert('审核结果不能为空!', '提示')
+      }
+
+      await axios.post(estateExamine, {
+        id: this.oper.state.auditId,
+        auditStatus: this.oper.state.operStatus,
+        auditRemark: this.oper.state.maker,
+        auditHouseType: 1
+      })
+
+      this.$alert('审核成功', '提示')
+      
+      this.oper.reset()
+      this.dataList.refer()
+    },
+
+    async offlineItem(data, isSelect = false) {
+      if (data.operStatus === 0) return;
+      let online = Number(!data.online)
+
+      if (!online) {
+        let res = await axios.post(hasHouseIsDel, {prodId: data.fcbHouseId})
+        if (!res.data) {
+          return this.$alert('下线失败', '提示')
+        }
+        
+
+        if (!(await this.$msgbox({
+          message: '下线后VR链接将无法继续访问,确定要下线吗?', 
+          title: '下线',
+          showCancelButton: true,
+          confirmButtonText: '下线',
+          cancelButtonText: '取消',
+        }))) {
+          return;
+        }
+      } else if (!isSelect){
+        let res = await axios.get(getHouseUpShares, { params: {houseId: data.id} })
+        this.upData.show = true
+        this.upData.data = res.data
+        this.upData.row = data
+
+        return;
+      }
+
+      
+      let requestOnlineList = []
+      requestOnlineList.push(
+        this.upData.data.find(({id}) => id === this.upData.value.init)
+      )
+      requestOnlineList.push(
+        ...this.upData.value.vrList.map(id => this.upData.data.find(({id: qid}) => qid === id))
+      )
+      
+      requestOnlineList = requestOnlineList.map(item => ({
+        ...item,
+        isDomeVideo: Number(!!data.points)
+      }))
+
+      await axios.post(estateOnline, { houseId: data.id, online: online, requestOnlineList }, {})
+      data.online = online
+      this.upData.show = false
+      this.upData.data = null
+      this.upData.row = null
+
+      this.$alert((online ? '上' : '下') + '线成功', '提示')
+      this.dataList.refer()
+    },
+    preview(data) {
+      let link = data.status === this.adoptStatus ? data.vrLink : data.innerVrLink
+
+      if (link) {
+        window.open(link)
+      } else {
+        this.$alert('当前项没有链接!', '提示')
+      }
+    },
+    
+  },
+  components: {
+    comUpSelect,
+    "com-dialog": comDialog,
+    "com-company": comCompany,
+    "com-head": comHead,
+    "com-record": comRecord,
+    "com-share": comShare,
+    "com-pagination": comPagination
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.table-ctrl-right {
+  .search-scene {
+    margin: 0 20px 0 26px;
+  }
+  i {
+    margin-left: 20px;
+    font-size: 1.7rem;
+    vertical-align: middle;
+    cursor: pointer;
+
+    &.active {
+      color: var(--primaryColor);
+    }
+  }
+}
+
+.examine-from {
+  width: 320px;
+  margin: 0 auto;
+}
+
+.interactive {
+  cursor: pointer;
+}
+
+.error {
+  color: var(--primaryColor);
+}
+
+</style>

+ 311 - 0
src/view/examine/other/community/body.vue

@@ -0,0 +1,311 @@
+<template>
+  <div class="body-layer">
+    <div class="body-head">
+      <h3>{{title}}</h3>
+    </div>
+
+    <el-table
+      ref="multipleTable"
+      :data="dataList.state"
+      tooltip-effect="dark"
+      style="width: 100%"
+    >
+      
+      <el-table-column label="小区编码" v-slot:default="{ row }">
+        {{row.gardenNumber}}
+      </el-table-column>
+      <el-table-column label="小区名称" v-slot:default="{ row }">
+        {{row.gardenName}}
+      </el-table-column>
+
+      <el-table-column label="城市" prop="cityName"></el-table-column>
+      <el-table-column label="片区|商圈" v-slot:default="{ row }" >
+        <span v-if="row.regionName || row.geographyAreaName">{{row.regionName}}|{{row.geographyAreaName}}</span>
+      </el-table-column>
+      <el-table-column label="物业类型" prop="propertySortName"></el-table-column>
+      
+      <el-table-column label="建成年代" v-slot:default="{ row }">
+        <span>{{row.buildStartTime}}-{{row.buildEndTime}}</span>
+      </el-table-column>
+      <el-table-column label="创建人" prop="creatorName"></el-table-column>
+      <el-table-column label="所属公司" prop="companyName"></el-table-column>
+      <el-table-column label="创建时间" prop="createTime"></el-table-column>
+      <el-table-column label="状态" v-slot:default="{row}" width="90">
+        <div class="status" :class="{interactive: row.auditStatus !== draftStatus}" @click="row.auditStatus !== draftStatus && selectRow(row)">
+          <span>{{getStatusName(row.auditStatus)}}</span>
+          <template v-if="row.auditStatus !== draftStatus || row.auditorName || row.auditTime">
+            <i class="el-icon-document" v-if="row.auditStatus !== noAdoptStatus && row.auditStatus !== noComAdoptStatus"></i>
+            <i class="el-icon-warning-outline error" v-else></i>
+          </template>
+        </div>
+      </el-table-column>
+      <el-table-column label="审核人" prop="auditorName"></el-table-column>
+      <el-table-column label="审核时间" prop="auditTime"></el-table-column>
+      <el-table-column label="操作" v-slot:default="{ row }">
+        <span class="oper-span" @click="shareRow(row)" v-if="false">预览</span>
+        <span class="oper-span"
+          :class="{
+            disable: !(
+              (row.auditStatus === noAuditStatis && (user.role === 'admin' || user.role === 'group')) ||
+              (row.auditStatus === noComAuditStatus && (user.role === 'admin' || user.role === 'region'))
+            )
+          }" 
+          @click="() => {(
+            (row.auditStatus === noAuditStatis && (user.role === 'admin' || user.role === 'group')) ||
+            (row.auditStatus === noComAuditStatus && (user.role === 'admin' || user.role === 'region'))
+          ) && oper.readyUpdate(row); oper.state.operStatus = examineMap.find(({name}) => name === '通过').value}" 
+          v-if="auth.update">
+          审核
+        </span>
+        <!-- <span class="oper-span" @click.stop="offlineItem(row)" :class="{disable: !~[adoptStatus, uplineStatus].indexOf(row.auditStatus)}">
+          {{row.auditStatus === uplineStatus ? '下线' : '上线' }}
+        </span> -->
+
+      </el-table-column>
+    </el-table>
+    <com-pagination
+      @size-change="pag.sizeChange"
+      @current-change="pag.currentChange"
+      :current-page="pag.state.currPage"
+      :page-size="pag.state.size"
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="pag.state.total"/>
+  </div>
+
+  <com-record v-model:show="recordData.show" :data="recordData.id" v-if="recordData.show" />
+  
+  <com-dialog title="审核" v-model:show="oper.state.show" @submit="updateExamine()">
+    <el-form ref="form" :model="oper.state" label-width="120px" class="examine-from">
+      <el-form-item label="审核结果:">
+        <el-radio-group v-model="oper.state.operStatus">
+          <el-radio v-for="item in examineMap" :key="item.value" :label="item.value">{{item.name}}</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="审核备注:">
+        <el-input type="textarea" v-model="oper.state.maker" maxlength="500" show-word-limit></el-input>
+      </el-form-item>
+    </el-form>
+  </com-dialog>
+
+  
+  <com-share
+    :setLogo="auth.update"
+    v-if="shareData.show"
+    v-model:show="shareData.show" 
+    :items="shareData.data" />
+
+</template>
+
+<script>
+
+import axios from 'axios';
+import { computed } from "vue";
+import qrcode from 'qrcode'
+import comDialog from "@/components/dialog";
+import comPagination from "@/components/pagination";
+import comRecord from "@/view/properties/community/record";
+import comShare from "@/view/properties/community/share";
+import user from '@/state/user'
+import {
+  communityExamine,
+  communityOnline
+} from '@/request/config'
+import { 
+  HOUSE_TYPE_SELECT, 
+  EXAMINE_REDIRECT_GROUP_SELECT,
+  EXAMINE_REDIRECT_REGION_SELECT,
+  EXAMINE_SHOW_SELECT_STATUS_3 as EXAMINE_SHOW_SELECT_STATUS, 
+  EXAMINE_NO_ADOPT_STATUS,
+  EXAMINE_NO_COM_ADOPT_STATUS,
+  EXAMINE_NO_AUDIT_STATUS,
+  EXAMINE_ADOPT_STATUS,
+  EXAMINE_NO_COM_AUDIT_STATUS,
+  EXAMINE_DRAFT_STATUS,
+  getName,
+  
+  EXAMINE_UP_ONLINE_STATUS,
+
+  REDIRECT_AUDIT_UP_STATUS,
+  REDIRECT_AUDIT_OFF_STATUS,
+  getRedirectCommunityAuditBody,
+  REDIRECT_AUDIT_REJECT_STATUS,
+  REDIRECT_AUDIT_REGION_STATUS,
+  REDIRECT_AUDIT_GROUP_STATUS
+} from '@/constant'
+import getState from './state'
+
+export default {
+  name: 'examine',
+  props: {
+    parentVM: Object
+  },
+  setup(props) {
+    const { headList, ...state } = getState(props.parentVM)
+    const title = computed(() => {
+      let item = headList.value.find(({value}) => value === state.search.value.state.auditStatus)
+      return item ? item.name : ''
+    })
+
+    return { ...state, headList, title };
+  },
+  data() { 
+    return {
+      recordData: { show: false, id: 0 },
+      examineMap: user.value.role === 'region' ? EXAMINE_REDIRECT_REGION_SELECT : EXAMINE_REDIRECT_GROUP_SELECT,
+      draftStatus: EXAMINE_DRAFT_STATUS,
+      noAdoptStatus: EXAMINE_NO_ADOPT_STATUS,
+      adoptStatus: EXAMINE_ADOPT_STATUS,
+      noAuditStatis: EXAMINE_NO_AUDIT_STATUS,
+      noComAuditStatus: EXAMINE_NO_COM_AUDIT_STATUS,
+      noComAdoptStatus: EXAMINE_NO_COM_ADOPT_STATUS,
+      houseType: HOUSE_TYPE_SELECT,
+      shareData: { show: false, data: 0 },
+      user,
+    }
+  },
+
+  methods: {
+    selectRow(data) {
+      this.recordData.show = true
+      this.recordData.id = data.vrGardenId
+    },
+    async shareRow(raw, type = 1) {
+      if (!raw.vrUrl) {
+        return this.$confirm('暂无预览链接,请先设置初始场景。', '提示')
+      }
+
+      const vrLink = process.env.VUE_APP_DOMAIN + raw.vrUrl
+      const QrCode = await qrcode.toDataURL(vrLink)
+
+      let shareItems = [{
+        QrCode: QrCode,
+        type: 'building',
+        noRandow: true,
+        houseTitle: raw.gardenName,
+        epcVrLink: vrLink.replace('hengda.html', 'epc.html'),
+        vrLink: vrLink,
+        innerVrLink: process.env.VUE_APP_INTRANET_DOMAIN + raw.vrUrl,
+        status: raw.auditStatus,
+      }]
+
+      if (type === 1) {
+        this.shareData.show = true
+        this.shareData.data = shareItems
+      } else {
+        window.open(type === 1 ? shareItems[0].vrLink : shareItems[0].epcVrLink)
+      }
+    },
+    getStatusName: getName(EXAMINE_SHOW_SELECT_STATUS),
+    showSelectRow(data) {
+      this.recordData.show = true
+      this.recordData.id = data.id
+    },
+    async updateExamine() {
+      if (!this.oper.state.operStatus) {
+        return this.$alert('审核结果不能为空!', '提示')
+      }
+
+      const current = ~this.oper.state.operStatus 
+        ? this.oper.state.auditStatus === EXAMINE_NO_AUDIT_STATUS 
+          ? REDIRECT_AUDIT_GROUP_STATUS
+          : REDIRECT_AUDIT_REGION_STATUS
+        : this.oper.state.auditStatus === EXAMINE_NO_AUDIT_STATUS 
+          ? REDIRECT_AUDIT_REJECT_STATUS
+          : REDIRECT_AUDIT_REJECT_STATUS
+      
+      await axios.post(
+        communityExamine, 
+        getRedirectCommunityAuditBody(
+          this.oper.state,
+          current,
+          this.oper.state.maker
+        )
+      )
+      
+
+      if (current === REDIRECT_AUDIT_GROUP_STATUS) {
+        await axios.post(communityExamine, 
+          getRedirectCommunityAuditBody(
+            this.oper.state,
+            REDIRECT_AUDIT_UP_STATUS,
+            this.oper.state.maker
+          )
+        )
+      }
+
+      this.$alert('审核成功', '提示')
+      
+      this.oper.reset()
+      this.dataList.refer()
+    },
+
+    async offlineItem(data) {
+      if (data.auditStatus !== EXAMINE_ADOPT_STATUS && data.auditStatus !== EXAMINE_UP_ONLINE_STATUS) return;
+
+      if (data.auditStatus === EXAMINE_UP_ONLINE_STATUS) {
+        if (!(await this.$msgbox({
+          message: '下线后VR链接将无法继续访问,确定要下线吗?', 
+          title: '下线',
+          showCancelButton: true,
+          confirmButtonText: '下线',
+          cancelButtonText: '取消',
+        }))) {
+          return;
+        }
+      }
+
+      const status = data.auditStatus === EXAMINE_UP_ONLINE_STATUS ? REDIRECT_AUDIT_OFF_STATUS : REDIRECT_AUDIT_UP_STATUS
+
+      await axios.post(
+        communityOnline,
+        getRedirectCommunityAuditBody(data, status)
+      )
+
+      this.$alert((status === REDIRECT_AUDIT_UP_STATUS ? '上' : '下') + '线成功', '提示')
+      this.dataList.refer()
+    },
+    preview(data) {
+      this.shareRow(data, 0)
+    },
+    
+  },
+  components: {
+    "com-dialog": comDialog,
+    "com-record": comRecord,
+    "com-share": comShare,
+    "com-pagination": comPagination
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.table-ctrl-right {
+  .search-scene {
+    margin: 0 20px 0 26px;
+  }
+  i {
+    margin-left: 20px;
+    font-size: 1.7rem;
+    vertical-align: middle;
+    cursor: pointer;
+
+    &.active {
+      color: var(--primaryColor);
+    }
+  }
+}
+
+.examine-from {
+  width: 320px;
+  margin: 0 auto;
+}
+
+.interactive {
+  cursor: pointer;
+}
+
+.error {
+  color: var(--primaryColor);
+}
+
+</style>

+ 149 - 0
src/view/examine/other/community/head.vue

@@ -0,0 +1,149 @@
+<template>
+  <com-head :options="headList"  v-model="selectStatus" v-if="!loadding">
+    <el-form label-width="90px" inline="true">
+      <el-form-item label="小区编码:">
+        <el-input v-model="search.state.gardenNumber" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="小区名称:">
+        <el-input v-model="search.state.gardenName" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="城市:">
+        <el-select v-model="search.state.cityId" filterable clearable  placeholder="请选择">
+          <el-option
+            v-for="item in citys"
+            :key="item.cityId"
+            :label="item.cityName"
+            :value="item.cityId">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="行政区:">
+        <com-region :cityId="search.state.cityId" clearable  v-model="search.state.areaId" placeholder="请输入" />
+      </el-form-item>
+      <el-form-item label="物业类型:">
+        <el-select v-model="search.state.propertySort" clearable  filterable placeholder="请选择">
+          <el-option
+            v-for="item in propertys"
+            :key="item.propertySort"
+            :label="item.propertySortName"
+            :value="item.propertySort">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="建成年代:">
+        <el-date-picker
+          v-model="yearsTime"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="更新人:">
+        <el-input v-model="search.state.updateByName" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="创建时间:">
+        <el-date-picker
+          v-model="createTime"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+
+      <el-form-item class="searh-btns">
+        <el-button @click="resetSearch">重置</el-button>
+        <el-button type="primary" @click="search.submit">查询</el-button>
+        <el-button type="primary" @click="exportExe" :disabled="!pag.state.total">导出</el-button>
+      </el-form-item>
+    </el-form>
+  </com-head>
+</template>
+
+<script>
+import comHead from "@/components/head";
+import getState from './state'
+import { getParams } from "@/state/tableRef";
+import axios from 'axios';
+import { EXAMINE_SHOW_STATUS } from '@/constant'
+import { exporrtCommunityAuditEstate, getCommunityTotal } from '@/request/config'
+// import { getCitys, getPropertys } from '@/constant/ssrLists'
+import comRegion from "@/components/region";
+import { ref,reactive } from '@vue/reactivity';
+
+export default {
+  props: {
+    parentVM: Object
+  },
+  setup(props) {
+    const state = getState(props.parentVM)
+
+
+    return {
+      ...state,
+      loadding: ref(true),
+      citys: reactive([]),
+      propertys: reactive([]),
+      status: EXAMINE_SHOW_STATUS,
+    }
+  },
+  watch: {
+    'dataList.state'() {
+      this.referTotal()
+    }
+  },
+  methods: {
+    resetSearch() {
+      let type = this.search.state.type
+      this.search.reset()
+      this.search.state.type = type
+    },
+    async exportExe() {
+      let params = {
+        ...getParams(this.search.state),
+        pageNum: this.pag.state.currPage,
+        pageSize: this.pag.state.size
+      }
+      // if (!params.startTime || !params.endTime) {
+      //   return this.$alert('请先选择提审时间段', '提示')
+      // } else {
+      //   const monthDiff = (startDate, endDate) => Math.max(0, (endDate.getFullYear() - startDate.getFullYear()) * 1022 - startDate.getMonth() + endDate.getMonth());
+      //   if (monthDiff(this.time[0], this.time[1]) > 3) {
+      //     return this.$alert('最多支持导出连续四个月的数据', '提示')
+      //   }
+      // }
+      
+      let res = await axios.get(exporrtCommunityAuditEstate, { params })
+      location.href = res.data.resultData
+    },
+    async referTotal() {
+      let res = await axios.post(getCommunityTotal)
+      const map = {
+        allCount: -2,
+        areaAuditingCount: 3,
+        groupAuditingCount: 1,
+        onlineCount: 2,
+        unPassCount: -1 
+      }
+      this.headList = this.initHeadList.map(item => {
+        for (let key in res.data) {
+          if (item.value === map[key]) {
+            return {...item, name: `${item.name}(${res.data[key]})`}
+          }
+        }
+        return item
+      })
+    }
+  },
+  async mounted() {
+    this.initHeadList = this.headList.map(item => ({...item}))
+    await this.referTotal()
+    this.loadding = false
+  },
+  components: {
+    "com-region": comRegion,
+    comHead
+  }
+}
+</script>

+ 123 - 0
src/view/examine/other/community/state.js

@@ -0,0 +1,123 @@
+
+import { ref, watch } from "vue";
+import getTableState from "@/state/tableRef";
+import auth from "@/state/viewAuth";
+import { dateFormat } from '@/util'
+import user from '@/state/user'
+import {
+  getCommunityList,
+  communityExamine,
+} from '@/request/config'
+import { 
+  EXAMINE_NO_AUDIT_STATUS,
+  EXAMINE_SHOW_STATUS_1 as EXAMINE_SHOW_STATUS, 
+  UN_REQ_NUM,
+  EXAMINE_NO_COM_AUDIT_STATUS,
+  EXAMINE_ADOPT_STATUS,
+  EXAMINE_NO_ADOPT_ALL_STATUS,
+  EXAMINE_ALL_STATUS,
+  EXANINE_SUCCESS_STATUS
+  
+} from '@/constant'
+
+
+const getTimeMapSearch = (search, start, end) => {
+  const time = ref(null)
+
+  watch(time, () => {
+    if (time.value) {
+      search.value.state[start] = dateFormat(new Date(time.value[0]), 'yyyy-MM-dd hh:mm:ss').toString()
+      search.value.state[end] = dateFormat(new Date(time.value[1]), 'yyyy-MM-dd 23:59:59').toString()
+    } else {
+      search.value.state[start] = search.value.state[end] = ''
+    }
+  })
+
+  watch(
+    () => search.value.state[start], 
+    () => {
+      if (!search.value.state[start]) {
+        time.value = null
+      }
+    }
+  )
+  watch(
+    () => search.value.state[end], 
+    () => {
+      if (!search.value.state[end]) {
+        time.value = null
+      }
+    }
+  )
+
+  return time
+}
+
+const getState = () => {
+  const headList = ref(EXAMINE_SHOW_STATUS);
+
+  const status = ref(user.value.role === 'region' 
+  ? EXAMINE_NO_COM_AUDIT_STATUS : EXAMINE_NO_AUDIT_STATUS)
+
+
+  const state = getTableState({
+    getUrl: getCommunityList,
+    updateUrl: communityExamine,
+    pagAttr: {listMap: {vrGardenId: 'id'}},
+    operAttr: { 
+      vrGardenId: "",
+      gardenId: "", 
+      gardenName: '',
+      creatorName: user.value.info.nickName,
+      creatorId: user.value.info.id,
+      companyId: user.value.info.departmentId,
+      maker: '',
+      auditStatus: null,
+      operStatus: 1,
+      companyName: ''
+    },
+    searchAttr: {
+      gardenNumber: "",
+      gardenName: '',
+      cityId: '',
+      readType: 'all',
+      areaId: '',
+      propertySort: '',
+      updateByName: '',
+      startCreateTime: '',
+      endCreateTime: '',
+      buildStartTime: '',
+      buildEndTime: '',
+      type: UN_REQ_NUM
+    }
+  });
+  
+  watch(status, () => {
+    const map = {
+      [EXAMINE_ALL_STATUS]: 'all',
+      [EXAMINE_NO_COM_AUDIT_STATUS]: 'areaAuditing',
+      [EXAMINE_NO_AUDIT_STATUS]: 'groupAuditing',
+      [EXAMINE_ADOPT_STATUS]: 'online',
+      [EXAMINE_NO_ADOPT_ALL_STATUS]: 'unPass',
+      [EXANINE_SUCCESS_STATUS]: 'auditPass'
+    }
+    state.search.value.state.readType = map[status.value]
+  }, {immediate: true})
+
+  const yearsTime = getTimeMapSearch(state.search, 'buildStartTime', 'buildEndTime')
+  const createTime = getTimeMapSearch(state.search, 'startCreateTime', 'endCreateTime')
+
+  return { ...state, headList, auth, yearsTime, createTime, selectStatus: status };
+}
+
+
+const map = new WeakMap()
+export default (parentVm) => {
+  if (map.has(parentVm)) {
+    return map.get(parentVm)
+  } else {
+    const state = getState()
+    map.set(parentVm, state)
+    return state
+  }
+}

+ 65 - 0
src/view/examine/other/layout.vue

@@ -0,0 +1,65 @@
+<template>
+  <com-head :options="tabList" showCtrl v-model="tab">
+    <component :is="head" :parentVM="identification" />
+  </com-head>
+
+  <component :is="body" :parentVM="identification" />
+</template>
+
+<script>
+import comHead from "@/components/head";
+import CommunityBody from './community/body'
+import CommunityHead from './community/head'
+import SecondHouseBody from './second-house/body'
+import SecondHouseHead from './second-house/head'
+
+const tabs = [
+  { name: '小区航拍', value: 1, type: 'community' },
+  { name: '房源VR', value: 2, type: 'secondHouse' },
+]
+const queryTab = (data) => 
+  typeof data === 'number' 
+    ? tabs.find(({value}) => value === data)
+    : tabs.find(({type}) => type === data)
+
+
+export default {
+  setup() {
+    return {
+      tabList: tabs,
+      identification: {} 
+    };
+  },
+  data() {
+    return {
+      tab: queryTab(this.$route.params.type).value
+    }
+  },
+  watch: {
+    tab() {
+      this.identification = {}
+      this.$router.push({
+        name: 'otherExamine',
+        params: { 
+          type: queryTab(this.tab).type
+        }
+      })
+    }
+  },
+  computed: {
+    head() {
+      return queryTab(this.tab).value === 1 ? 'CommunityHead' : 'SecondHouseHead'
+    },
+    body() {
+      return queryTab(this.tab).value === 1 ? 'CommunityBody' : 'SecondHouseBody'
+    }
+  },
+  components: {
+    comHead,
+    CommunityBody,
+    CommunityHead,
+    SecondHouseBody,
+    SecondHouseHead
+  }
+}
+</script>

+ 308 - 0
src/view/examine/other/second-house/body.vue

@@ -0,0 +1,308 @@
+<template>
+  <div class="body-layer">
+    <div class="body-head">
+      <h3>{{title}}</h3>
+    </div>
+
+    <el-table
+      ref="multipleTable"
+      :data="dataList.state"
+      tooltip-effect="dark"
+      style="width: 100%"
+      @row-click="selectRow"
+    >
+      <el-table-column label="房源编码" prop="houseTitle" v-slot:default="{ row }">
+        <span  @click="shareRow(row)" style="cursor: pointer">{{row.roomlistingId}}</span>
+      </el-table-column>
+      <el-table-column label="所属小区" prop="gardenName"></el-table-column>
+      <el-table-column label="楼栋-单元" v-slot:default="{ row }">
+        {{row.buildingUnit}}
+      </el-table-column>
+      <el-table-column label="房号" v-slot:default="{ row }">
+        {{row.roomNumber}}
+      </el-table-column>
+      <el-table-column label="建筑面积" prop="buildArea"></el-table-column>
+      <el-table-column label="套内面积" prop="roomArea"></el-table-column>
+      <el-table-column label="维护人" prop="maintenanceman"></el-table-column>
+      
+      <el-table-column label="提审时间" prop="arraignmentTime"></el-table-column>
+      <el-table-column label="审核状态" prop="auditStatus" v-slot:default="{ row }">
+        <div class="status" :class="{interactive: row.auditStatus !== draftStatus || row.reviewer || row.reviewTime}" @click="(row.auditStatus !== draftStatus || row.reviewer || row.reviewTime) && showSelectRow(row)">
+          <span>{{getStatusName(row.auditStatus)}}</span>
+          <template v-if="row.auditStatus !== draftStatus || row.reviewer || row.reviewTime">
+            <i class="el-icon-document" v-if="row.auditStatus !== noAdoptStatus && row.auditStatus !== noComAdoptStatus"></i>
+            <i class="el-icon-warning-outline error" v-else></i>
+          </template>
+        </div>
+      </el-table-column>
+      <el-table-column label="审核人" prop="reviewer"></el-table-column>
+      <el-table-column label="审核时间" prop="reviewTime"></el-table-column>
+      <el-table-column label="操作" v-slot:default="{ row }">
+        <span class="oper-span" @click="shareRow(row)" v-if="false">预览</span>
+        <span class="oper-span"
+          :class="{
+            disable: !(
+              (row.auditStatus === noAuditStatis && (user.role === 'admin' || user.role === 'group')) ||
+              (row.auditStatus === noComAuditStatus && (user.role === 'admin' || user.role === 'region'))
+            )
+          }" 
+          @click="() => {(
+            (row.auditStatus === noAuditStatis && (user.role === 'admin' || user.role === 'group')) ||
+            (row.auditStatus === noComAuditStatus && (user.role === 'admin' || user.role === 'region'))
+          ) &&  oper.readyUpdate(row); oper.state.operStatus = 1}" 
+          v-if="auth.update">
+          审核
+        </span>
+        <!-- <span
+          v-if="auth.update"
+          class="oper-span"
+          :class="{disable: !~[adoptStatus, uplineStatus].indexOf(row.auditStatus)}"
+          @click="~[adoptStatus, uplineStatus].indexOf(row.auditStatus) && offlineItem(row)">
+          {{row.auditStatus === uplineStatus ? '下线' : '上线' }}
+        </span> -->
+      </el-table-column>
+    </el-table>
+    <com-pagination
+      @size-change="pag.sizeChange"
+      @current-change="pag.currentChange"
+      :current-page="pag.state.currPage"
+      :page-size="pag.state.size"
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="pag.state.total"/>
+  </div>
+
+  <com-record v-model:show="recordData.show" :data="recordData.id" v-if="recordData.show" />
+  
+  <com-dialog title="审核" v-model:show="oper.state.show" @submit="updateExamine()">
+    <el-form ref="form" :model="oper.state" label-width="120px" class="examine-from">
+      <el-form-item label="审核结果:">
+        <el-radio-group v-model="oper.state.operStatus">
+          <el-radio v-for="item in examineMap" :key="item.value" :label="item.value">{{item.name}}</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="审核备注:">
+        <el-input type="textarea" v-model="oper.state.maker" maxlength="500" show-word-limit></el-input>
+      </el-form-item>
+    </el-form>
+  </com-dialog>
+
+  
+  <com-share
+    :setLogo="auth.update"
+    v-if="shareData.show"
+    v-model:show="shareData.show" 
+    :items="shareData.data" />
+
+</template>
+
+<script>
+
+import axios from 'axios';
+import { computed } from "vue";
+import comDialog from "@/components/dialog";
+import comPagination from "@/components/pagination";
+import comRecord from "@/view/properties/community/second-house/record";
+import comShare from "@/view/properties/estate/share";
+import qrcode from 'qrcode'
+import user from '@/state/user'
+import {
+  secondHouseOnline,
+  secondHouseExamine
+} from '@/request/config'
+import { 
+  EXAMINE_SHOW_SELECT_STATUS_3 as EXAMINE_SHOW_SELECT_STATUS_TEXT,
+  HOUSE_TYPE_SELECT, 
+  EXAMINE_REQ_SELECT,
+  EXAMINE_SHOW_STATUS, 
+  EXAMINE_NO_ADOPT_STATUS,
+  EXAMINE_NO_COM_ADOPT_STATUS,
+  EXAMINE_NO_AUDIT_STATUS,
+  EXAMINE_ADOPT_STATUS,
+  EXAMINE_NO_COM_AUDIT_STATUS,
+  EXAMINE_UP_ONLINE_STATUS,
+  EXAMINE_DRAFT_STATUS,
+  getRedirectRoomAuditBody,
+  getName 
+} from '@/constant'
+import getState from './state'
+
+export default {
+  name: 'examine',
+  props: {
+    parentVM: Object
+  },
+  setup(props) {
+    const { headList, ...state } = getState(props.parentVM)
+    const title = computed(() => {
+      let item = headList.value.find(({value}) => value === state.search.value.state.auditStatus)
+      return item ? item.name : ''
+    })
+
+    return { ...state, headList, title };
+  },
+  data() { 
+    return {
+      recordData: { show: false, id: 0 },
+      examineMap: EXAMINE_REQ_SELECT,
+      uplineStatus: EXAMINE_UP_ONLINE_STATUS,
+      draftStatus: EXAMINE_DRAFT_STATUS,
+      noAdoptStatus: EXAMINE_NO_ADOPT_STATUS,
+      adoptStatus: EXAMINE_ADOPT_STATUS,
+      noAuditStatis: EXAMINE_NO_AUDIT_STATUS,
+      noComAuditStatus: EXAMINE_NO_COM_AUDIT_STATUS,
+      noComAdoptStatus: EXAMINE_NO_COM_ADOPT_STATUS,
+      houseType: HOUSE_TYPE_SELECT,
+      shareData: { show: false, data: 0 },
+      user,
+    }
+  },
+
+  methods: {
+    getStatusName: getName(EXAMINE_SHOW_SELECT_STATUS_TEXT),
+    async shareRow(raw, type = 1) {
+      if (!raw.vRUrl) {
+        return this.$confirm('暂无预览链接,请先设置初始场景。', '提示')
+      }
+
+      const vrLink = process.env.VUE_APP_DOMAIN + raw.vRUrl
+      const QrCode = await qrcode.toDataURL(vrLink)
+
+      let shareItems = [{
+        QrCode: QrCode,
+        type: 'building',
+        noRandow: true,
+        houseTitle: raw.buildingUnit + raw.roomNumber,
+        epcVrLink: vrLink.replace('hengda.html', 'epc.html'),
+        vrLink: vrLink,
+        innerVrLink: process.env.VUE_APP_INTRANET_DOMAIN + raw.vRUrl,
+        status: raw.auditStatus,
+      }]
+
+      if (type === 1) {
+        this.shareData.show = true
+        this.shareData.data = shareItems
+      } else {
+        window.open(type === 1 ? shareItems[0].vrLink : shareItems[0].epcVrLink)
+      }
+    },
+    getExamineName: getName(EXAMINE_SHOW_STATUS),
+    getHouseName: getName(HOUSE_TYPE_SELECT),
+    showSelectRow(data) {
+      this.recordData.show = true
+      this.recordData.id = data.vrId
+    },
+    async updateExamine() {
+      if (!this.oper.state.operStatus) {
+        return this.$alert('审核结果不能为空!', '提示')
+      }
+
+      const current = ~this.oper.state.operStatus 
+        ? this.oper.state.auditStatus === EXAMINE_NO_AUDIT_STATUS 
+          ? EXAMINE_ADOPT_STATUS
+          : EXAMINE_NO_AUDIT_STATUS
+        : this.oper.state.auditStatus === EXAMINE_NO_AUDIT_STATUS 
+          ? EXAMINE_NO_ADOPT_STATUS
+          : EXAMINE_NO_COM_ADOPT_STATUS
+      
+      await axios.post(secondHouseExamine, 
+        getRedirectRoomAuditBody(
+          this.oper.state,
+          current,
+          this.oper.state.maker,
+          this.oper.state.roomlistingId
+        )
+      )
+
+      if (current === EXAMINE_ADOPT_STATUS) {
+        await axios.post(secondHouseExamine, 
+          getRedirectRoomAuditBody(
+            this.oper.state,
+            EXAMINE_UP_ONLINE_STATUS,
+            this.oper.state.maker,
+          this.oper.state.roomlistingId
+          )
+        )
+      }
+      
+
+      this.$alert('审核成功', '提示')
+      
+      this.oper.reset()
+      this.dataList.refer()
+    },
+
+    async offlineItem(data) {
+      if (data.auditStatus !== EXAMINE_ADOPT_STATUS && data.auditStatus !== EXAMINE_UP_ONLINE_STATUS) return;
+
+      if (data.auditStatus === EXAMINE_UP_ONLINE_STATUS) {
+        if (!(await this.$msgbox({
+          message: '下线后VR链接将无法继续访问,确定要下线吗?', 
+          title: '下线',
+          showCancelButton: true,
+          confirmButtonText: '下线',
+          cancelButtonText: '取消',
+        }))) {
+          return;
+        }
+      }
+
+      const status = data.auditStatus === EXAMINE_UP_ONLINE_STATUS ? EXAMINE_DRAFT_STATUS : EXAMINE_UP_ONLINE_STATUS
+
+      await axios.post(
+        secondHouseOnline,
+        getRedirectRoomAuditBody(data, status, '', data.roomlistingId)
+      )
+      this.$alert((status === EXAMINE_UP_ONLINE_STATUS ? '上' : '下') + '线成功', '提示')
+      this.dataList.refer()
+    },
+    preview(data) {
+      let link = data.status === this.adoptStatus ? data.vrLink : data.innerVrLink
+
+      if (link) {
+        window.open(link)
+      } else {
+        this.$alert('当前项没有链接!', '提示')
+      }
+    },
+    
+  },
+  components: {
+    "com-dialog": comDialog,
+    "com-record": comRecord,
+    "com-share": comShare,
+    "com-pagination": comPagination
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.table-ctrl-right {
+  .search-scene {
+    margin: 0 20px 0 26px;
+  }
+  i {
+    margin-left: 20px;
+    font-size: 1.7rem;
+    vertical-align: middle;
+    cursor: pointer;
+
+    &.active {
+      color: var(--primaryColor);
+    }
+  }
+}
+
+.examine-from {
+  width: 320px;
+  margin: 0 auto;
+}
+
+.interactive {
+  cursor: pointer;
+}
+
+.error {
+  color: var(--primaryColor);
+}
+
+</style>

+ 137 - 0
src/view/examine/other/second-house/head.vue

@@ -0,0 +1,137 @@
+<template>
+  <com-head :options="headList"  v-model="statusa" v-if="!loadding">
+    <el-form label-width="90px" inline="true">
+      <el-form-item label="房源编码:">
+        <el-input v-model="search.state.roomlistingId" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="所属小区:">
+        <el-input v-model="search.state.gardenName" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="维护人:">
+        <el-input v-model="search.state.maintenanceman" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="提审时间:">
+        <el-date-picker
+          v-model="arraignmentTime"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="审核人:">
+        <el-input v-model="search.state.reviewer" placeholder="请输入"></el-input>
+      </el-form-item>
+
+      <el-form-item label="审核状态:">
+        <el-select v-model="statusa" placeholder="全部">
+          <el-option v-for="(item) in status" :key="item.value" :label="item.name" :value="item.value"></el-option>
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="审核时间:">
+        <el-date-picker
+          v-model="reviewTime"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+
+      <el-form-item class="searh-btns">
+        <el-button @click="search.reset">重置</el-button>
+        <el-button type="primary" @click="search.submit">查询</el-button>
+        <el-button type="primary" @click="exportExe" :disabled="!pag.state.total">导出</el-button>
+      </el-form-item>
+    </el-form>
+  </com-head>
+</template>
+
+<script>
+import comHead from "@/components/head";
+import getState from './state'
+import { getParams } from "@/state/tableRef";
+import axios from 'axios';
+import { EXAMINE_SHOW_STATUS } from '@/constant'
+import { exporrtSecondHouseAuditEstate, getSecondHouseTotal } from '@/request/config'
+import { ref } from '@vue/reactivity';
+
+export default {
+  props: {
+    parentVM: Object
+  },
+  setup(props) {
+    
+    return {
+      ...getState(props.parentVM),
+      loadding: ref(true),
+      status: EXAMINE_SHOW_STATUS,
+    }
+  },
+  
+  watch: {
+    'dataList.state'() {
+      this.referTotal()
+    }
+  },
+  methods: {
+    resetSearch() {
+      let type = this.search.state.type
+      this.search.reset()
+      this.search.state.type = type
+    },
+    async exportExe() {
+      let params = {
+        ...getParams(this.search.state),
+        pageNum: this.pag.state.currPage,
+        pageSize: this.pag.state.size
+      }
+      console.log(params)
+      if (!params.arraignmentTimeEnd || !params.arraignmentTimeStart) {
+        return this.$alert('请先选择提审时间段', '提示')
+      } else {
+        const monthDiff = (startDate, endDate) => Math.max(0, (endDate.getFullYear() - startDate.getFullYear()) * 12 - startDate.getMonth() + endDate.getMonth());
+        if (monthDiff(this.arraignmentTime[0], this.arraignmentTime[1]) > 3) {
+          return this.$alert('最多支持导出连续四个月的数据', '提示')
+        }
+      }
+      
+      let res = await axios.get(exporrtSecondHouseAuditEstate, { params })
+      location.href = res.data.resultData
+    },
+    async referTotal() {
+      try {
+        let res = await axios.post(getSecondHouseTotal)
+        const map = {
+          allCount: -2,
+          areaAuditingCount: 3,
+          groupAuditingCount: 1,
+          onlineCount: 2,
+          unPassCount: -1 
+        }
+        this.headList = this.initHeadList.map(item => {
+          for (let key in res.data[0]) {
+            if (item.value === map[key]) {
+              return {...item, name: `${item.name}(${res.data[0][key]})`}
+            }
+          }
+          return item
+        })
+        console.log('...', this.headList)
+      } catch(e) {
+        console.log(e)
+      }
+    }
+  },
+  async mounted() {
+    this.initHeadList = this.headList.map(item => ({...item}))
+    await this.referTotal()
+    
+    this.loadding = false
+  },
+  components: {
+    comHead,
+  }
+}
+</script>

+ 127 - 0
src/view/examine/other/second-house/state.js

@@ -0,0 +1,127 @@
+
+import { ref, watch } from "vue";
+import getTableState from "@/state/tableRef";
+import auth from "@/state/viewAuth";
+import { dateFormat } from '@/util'
+import user from '@/state/user'
+import {
+  getSecondHouseList1 as getSecondHouseList,
+  communityExamine,
+} from '@/request/config'
+import { 
+  EXAMINE_ADOPT_STATUS,
+  EXAMINE_SHOW_STATUS_1 as EXAMINE_SHOW_STATUS, 
+  EXAMINE_NO_AUDIT_STATUS,
+  EXAMINE_UP_ONLINE_STATUS,
+  UN_REQ_NUM,
+  EXAMINE_NO_ADOPT_ALL_STATUS,
+  EXAMINE_NO_COM_ADOPT_STATUS,
+  EXANINE_SUCCESS_STATUS,
+  EXAMINE_NO_COM_AUDIT_STATUS,
+} from '@/constant'
+
+const getTimeMapSearch = (search, searchKey) => {
+  const time = ref(null)
+  const start = `${searchKey}Start`
+  const end = `${searchKey}End`
+
+  watch(time, () => {
+    if (time.value) {
+      search.value.state[start] = dateFormat(new Date(time.value[0]), 'yyyy-MM-dd hh:mm:ss').toString()
+      search.value.state[end] = dateFormat(new Date(time.value[1]), 'yyyy-MM-dd 23:59:59').toString()
+    } else {
+      search.value.state[start] = search.value.state[end] = ''
+    }
+  })
+
+  watch(
+    () => search.value.state[start], 
+    () => {
+      if (!search.value.state[start]) {
+        time.value = null
+      }
+    }
+  )
+  watch(
+    () => search.value.state[end], 
+    () => {
+      if (!search.value.state[end]) {
+        time.value = null
+      }
+    }
+  )
+
+  return time
+}
+
+const getState = () => {
+  const headList = ref(EXAMINE_SHOW_STATUS);
+  const statusa = ref(user.value.role === 'region' 
+  ? EXAMINE_NO_COM_AUDIT_STATUS : EXAMINE_NO_AUDIT_STATUS)
+
+  const state = getTableState({
+    getUrl: getSecondHouseList,
+    updateUrl: communityExamine,
+    operAttr: { 
+      auditStatus: null, 
+      maker: '', 
+      cityId: '',
+      vRUrl: '',
+      dataSource: '',
+      vrCover: '',
+      operStatus: 1,
+      gardenId: "", 
+      auditId: '' 
+    },
+    searchAttr: {
+      roomlistingId: "", 
+      createName: '',
+      gardenName: '', 
+      maintenanceman: '',
+      reviewer: '',
+      reviewTimeStart: '',
+      reviewTimeEnd: '',
+      arraignmentTimeStart: '',
+      arraignmentTimeEnd: '',
+      type: UN_REQ_NUM,
+      auditStatus: statusa.value
+    }
+  });
+  
+  watch(statusa, () => {
+    console.log(statusa.value)
+    if (statusa.value !== -2) {
+      state.search.value.state.auditStatus = statusa.value === EXANINE_SUCCESS_STATUS 
+        ? EXAMINE_ADOPT_STATUS
+        : statusa.value === EXAMINE_ADOPT_STATUS 
+          ? EXAMINE_UP_ONLINE_STATUS
+          : statusa.value === EXAMINE_NO_ADOPT_ALL_STATUS 
+            ? user.value.role === 'admin' || user.value.role === 'group'
+              ? `${EXAMINE_NO_ADOPT_ALL_STATUS},${EXAMINE_NO_COM_ADOPT_STATUS}`
+              : EXAMINE_NO_COM_ADOPT_STATUS
+            : statusa.value
+    } else {
+      delete state.search.value.state.auditStatus
+    }
+  }, {immediate: true})
+
+
+  const arraignmentTime = getTimeMapSearch(state.search, 'arraignmentTime')
+  const reviewTime = getTimeMapSearch(state.search, 'reviewTime')
+
+  
+  return { ...state, headList, auth, arraignmentTime, reviewTime, statusa };
+}
+
+
+const map = new WeakMap()
+export default (parentVm) => {
+  console.log(map.has(parentVm))
+  if (map.has(parentVm)) {
+    return map.get(parentVm)
+  } else {
+    const state = getState()
+    map.set(parentVm, state)
+    return state
+  }
+}

+ 161 - 0
src/view/firmware/index.vue

@@ -0,0 +1,161 @@
+<template>
+  <com-head :options="headList" showCtrl>
+    <el-form label-width="90px" inline="true">
+      <el-form-item label="发布时间:">
+        <el-date-picker
+          v-model="search.state.time"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item class="searh-btns">
+        <el-button @click="search.reset">重置</el-button>
+        <el-button type="primary" @click="search.submit">查询</el-button>
+      </el-form-item>
+    </el-form>
+  </com-head>
+
+  <div class="body-layer">
+    <div class="body-head">
+      <h3>固件列表</h3>
+      <div class="table-ctrl-right">
+        <el-input placeholder="关键词" suffix-icon="el-icon-search" class="search-scene" v-model="search.state.keyword" style="width: auto" />
+        <el-button
+          type="primary"
+          icon="el-icon-upload2"
+          @click="oper.readyInsert"
+          >上传</el-button
+        >
+      </div>
+    </div>
+
+    <el-table
+      ref="multipleTable"
+      :data="dataList.state"
+      tooltip-effect="dark"
+      style="width: 100%"
+      @row-click="selectRow"
+    >
+      <el-table-column type="selection" width="55"></el-table-column>
+      <el-table-column label="序号" prop="name"></el-table-column>
+      <el-table-column label="描述" prop="name"></el-table-column>
+      <el-table-column label="版本" prop="name"></el-table-column>
+      <el-table-column label="文件地址" prop="name"></el-table-column>
+      <el-table-column label="状态" prop="name" v-slot:default="{ row }">
+        <el-switch
+          @change="changeStatus(row)"
+          v-model="row.address"
+          active-color="#13ce66"
+          inactive-color="#ff4949">
+        </el-switch>
+      </el-table-column>
+      <el-table-column label="发布日期" prop="name"></el-table-column>
+    </el-table>
+    <div class="pag-block">
+      <el-pagination
+        @size-change="pag.sizeChange"
+        @current-change="pag.currentChange"
+        :current-page="pag.state.currPage"
+        :page-size="pag.state.size"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="pag.state.total"
+      >
+      </el-pagination>
+    </div>
+  </div>
+  
+  <com-dialog
+    title="添加版本"
+    v-model:show="oper.state.show"
+    @submit="insertItem"
+  >
+    <el-form ref="form" :model="form" label-width="100px" class="firmware-from">
+      <el-form-item label="版本:" class="mandatory">
+        <el-input v-model="oper.state.name"></el-input>
+      </el-form-item>
+      <el-form-item label="描述:">
+        <el-input type="textarea" v-model="oper.state.name"></el-input>
+      </el-form-item>
+      <el-form-item label="文件上传:" class="mandatory">
+        <el-button type="primary" class="file-upload">
+          <label for="file-upload">文件上传</label>
+        </el-button>
+        <input type="file" id="file-upload" @change="fileChange">
+      </el-form-item>
+    </el-form>
+  </com-dialog>
+</template>
+
+<script>
+import { ref } from "vue";
+import getTableState from "@/state/tableRef";
+import comDialog from "@/components/dialog";
+import comHead from "@/components/head";
+
+export default {
+  name: 'firmware',
+  setup() {
+    const state = getTableState({
+      operAttr: { name: "" },
+      searchAttr: { time: "", keyword: '' },
+    });
+    const headList = ref([{ name: "我的固件(12)", value: 2 }]);
+
+    return { ...state, headList };
+  },
+  methods: {
+    changeStatus(data) {
+      console.log(data)
+    },
+    insertItem() {
+
+    },
+    fileChange() {
+      
+    }
+  },
+  components: {
+    "com-dialog": comDialog,
+    "com-head": comHead
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.table-ctrl-right {
+  .search-scene {
+    margin: 0 20px 0 26px;
+  }
+  i {
+    margin-left: 20px;
+    font-size: 1.7rem;
+    vertical-align: middle;
+    cursor: pointer;
+
+    &.active {
+      color: var(--primaryColor);
+    }
+  }
+}
+
+.firmware-from {
+  width: 320px;
+  margin: 0 auto;
+}
+
+
+.file-upload {
+  padding: 0;
+
+  label {
+    display: inline-block;
+    padding: 12px 20px;
+  }
+}
+
+input[type='file'] {
+  display: none;
+}
+</style>

+ 179 - 0
src/view/framework/index.vue

@@ -0,0 +1,179 @@
+<template>
+  <com-head :options="headList" class="frame-head">
+  </com-head>
+  <div class="farme">
+    <el-tree
+      node-key="id"
+      default-expand-all
+      class="framework"
+      :data="origin"
+      :expand-on-click-node="false"
+    >
+      <template v-slot="{node, data}">
+        <div class="custom-tree-node">
+          <span class="text">{{ data.name }}</span>
+
+          <span @click="readyInsert(data)" class="btn" v-if="auth.add && data.level === 0">
+            <i class="el-icon-plus"></i>
+          </span>
+          
+          <span @click="readyUpdate(data)" class="btn">
+            <i class="el-icon-edit"></i>
+          </span>
+
+          <span @click="remove(node, data)" class="btn" v-if="data.level !== 0 && auth.delete">
+            <i class="el-icon-delete"></i>
+          </span>
+
+
+        </div>
+      </template>
+    </el-tree>
+
+    <com-dialog :title="(operState.isInsert ? '添加' : '修改') + '品牌专柜'" v-model:show="operState.show" @submit="append">
+      <el-form ref="form" label-width="100px" class="framework-from">
+        <el-form-item label="公司名称:" class="mandatory">
+          <el-input v-model="operState.name" placeholder="请输入" maxlength="15" show-word-limit></el-input>
+        </el-form-item>
+      </el-form>
+    </com-dialog>
+
+  </div>
+</template>
+
+<script>
+import { ref } from "vue";
+import comHead from "@/components/head";
+import comDialog from "@/components/dialog";
+import auth from "@/state/viewAuth";
+import axios from 'axios'
+import { 
+  getCompanyThree,
+  insertCompany,
+  deleteCompany,
+  updateCompany
+} from '@/request/config'
+
+const analysis = (data, level) => {
+  return data.map(item => ({
+    id: item.id,
+    name: item.name,
+    level: level,
+    children: item.children ? analysis(item.children, level + 1) : []
+  }))
+}
+
+export default {
+  name: 'framework',
+  setup() {
+    const headList = ref([{ name: "组织架构", value: 1 }]);
+    const origin = ref([]);
+    const operState = ref({ name: '', data: null, show: false, isInsert: false })
+
+
+    const readyInsert = (data) => {
+      operState.value.data = data
+      operState.value.show = true
+      operState.value.isInsert = true
+      operState.value.name = ''
+    }
+    const readyUpdate = data => {
+      operState.value.data = data
+      operState.value.show = true
+      operState.value.isInsert = false
+      operState.value.name = data.name
+    }
+    return { headList, origin, operState, readyInsert, readyUpdate, auth };
+  },
+  async mounted() {
+    let res = await axios.post(getCompanyThree, {})
+    // res.data[0].children = res.data[0].children.concat(res.data[0].children).concat(res.data[0].children)
+    this.origin = analysis(res.data, 0)
+  },
+  methods: {
+    async remove(node, data) {
+      if (!(await this.$confirm(`确定要删除${data.name}吗?`, '提示'))) return;
+
+      const parent = node.parent;
+      const children = parent.data.children || parent.data;
+      const index = children.findIndex(d => d.id === data.id);
+
+      await axios.post(deleteCompany, {id: data.id})
+
+      children.splice(index, 1);
+      parent.data.children = [...children]
+
+      if (parent.id === 0) {
+        this.origin = [...parent.data.children]
+      }
+    },
+    async append() {
+      const data = this.operState.data
+
+      if (!this.operState.name) {
+        return this.$alert('请输入公司名称!', '提示')
+      }
+
+      if (this.operState.isInsert) {
+        const newCompany = { name: this.operState.name }
+        const res = await axios.post(insertCompany, { ...newCompany, parentId: data.id })
+        const newChild = { id: res.data.id, ...newCompany, level: data.level + 1,  children: [] };
+        data.children = data.children ? [...data.children, newChild] : [newChild]
+      } else {
+        await axios.post(updateCompany, { id: data.id, name: this.operState.name })
+        data.name = this.operState.name
+      }
+      this.operState.name = ''
+      this.operState.show = false
+    }
+  },
+  components: {
+    "com-dialog": comDialog,
+    "com-head": comHead,
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.farme {
+  padding: 16px 124px 32px 16px;
+  background-color: #fff;
+  flex: 1;
+  overflow-y: auto;
+}
+
+.custom-tree-node {
+  margin: 10px 0;
+  .text {
+    font-size: 1rem;
+    color: #303133;
+  }
+
+  .btn {
+    color: rgb(144,144,144);
+    border: 1px dashed currentColor;
+    display: inline-block;
+    margin-left: 10px;
+    padding: 3px 5px;
+
+    i {
+      font-size: 0.8rem;
+    }
+  }
+}
+
+.framework-from {
+  width: 320px;
+  margin: 80px auto;
+}
+
+</style>
+
+<style lang="scss">
+.framework .el-tree-node__content {
+  height: auto;
+}
+.frame-head .head-content-layer.show {
+  padding: 0;
+}
+</style>

+ 82 - 0
src/view/layout/index.vue

@@ -0,0 +1,82 @@
+<template>
+  <div class="layer">
+    <ly-top />
+    <div class="content">
+      <ly-slide class="slide" />
+      <div class="view">
+        <ly-player class="player" />
+        <div class="main">
+          <router-view v-slot="{ Component }">
+            <!-- <keep-alive > -->
+              <component :is="Component" />
+            <!-- </keep-alive> -->
+          </router-view>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import lyTop from './top'
+import lySlide from './slide'
+import lyPlayer from './player'
+
+export default {
+  components: {
+    'ly-top': lyTop,
+    'ly-slide': lySlide,
+    'ly-player': lyPlayer,
+    
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.layer {
+  position: absolute;
+  z-index: 1;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+
+  .top {
+    flex: 0 0 auto;
+    height: 5.33rem;
+    box-sizing: border-box;
+  }
+
+  .content {
+    flex: 1 0;
+    display: flex;
+    overflow: hidden;
+
+    .slide {
+      width: 16.66rem;
+      flex: 0 0 auto;
+    }
+
+    .view {
+      flex: 0 0 auto;
+      width: calc(100% - 16.66rem);
+      background-color: var(--bgColor);
+      flex-direction: column;
+      display: flex;
+
+      .player {
+        flex: 0 0 auto;
+        height: 32px;
+      }
+
+      .main {
+        margin: 16px;
+        display: flex;
+        flex-direction: column;
+        overflow: hidden;
+        flex: 1;
+      }
+    }
+  }
+}
+</style>

+ 64 - 0
src/view/layout/player/index.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="player">
+    <div
+      v-for="item in records"
+      :key="item.name"
+      :class="{active: item.name === $route.name}"
+      @click="item.name !== $route.name && gotoRecord(item)"
+      >
+      <span>{{item.meta.title}}</span>
+      <i class="el-icon-close" @click.stop="delRecord(item)"></i>
+    </div>
+  </div>
+</template>
+
+<script>
+import { ref } from 'vue'
+
+export default {
+  setup() {
+    const records = ref([])
+    return { records }
+  },
+  watch: {
+    '$route': {
+      immediate: true,
+      handler() {
+        if (this.$route.name !== 'viewLayout') {
+          let index = this.records.findIndex(record => record.name === this.$route.name)
+          if (~index) {
+            this.records.splice(index, 1, this.$route)
+          } else {
+            this.records.push(this.$route)
+          }
+        }
+      }
+    }
+  },
+  methods: {
+    delRecord(record) {
+      let index = this.records.indexOf(record)
+      if (!~index) return;
+      this.records.splice(index, 1);
+
+      if (this.records.length <= index) {
+        index = index - 1
+      }
+
+      if (~index) {
+        this.$router.replace({path: this.records[index].path})
+      } else {
+        this.$router.push({name: 'viewLayout'})
+      }
+    },
+    gotoRecord(record) {
+      this.$router.push({path: record.path})
+    }
+  }
+}
+</script>
+
+
+<style lang="scss" scoped>
+@import "./style.scss";
+</style>

+ 27 - 0
src/view/layout/player/style.scss

@@ -0,0 +1,27 @@
+.player {
+  background: #FFFFFF;
+  display: flex;
+
+  > div {
+    height: 100%;
+    font-size: 1rem;
+    color: #909399;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 0 4px 0 8px;
+    cursor: pointer;
+    border-right: 1px solid var(--bgColor);;
+    border-left: 1px solid var(--bgColor);;
+
+    i {
+      margin-left: 5px;
+      font-size: 1.1em;
+      color: #909399;
+    }
+
+    &.active {
+      background-color: var(--bgColor);;
+    }
+  }
+}

+ 55 - 0
src/view/layout/slide/index.vue

@@ -0,0 +1,55 @@
+<template>
+  <div class="slide">
+    <el-menu :default-active="activeName" @select="selectHandle">
+      <sub-menu :nav="nav" v-for="nav in navs" :key="nav.name" />
+    </el-menu>
+  </div>
+</template>
+
+<script>
+import subMenu from './submenu'
+import navs from '@/state/navs'
+import { attach } from '@/constant/view'
+
+export default {
+  setup() {
+    console.log(navs)
+    return { navs }
+  },
+  computed: {
+    activeName() {
+      let routeName = this.$route.name
+      return attach[routeName] ? attach[routeName] : routeName
+    }
+  },
+  methods: {
+    queryNav(navs, name) {
+      for (let nav of navs) {
+        if (nav.name === name) {
+          return nav
+        } else if (nav.children) {
+          const qNav = this.queryNav(nav.children, name)
+          if (qNav) return qNav
+        }
+      }
+    },
+    selectHandle(data) {
+      const nav = this.queryNav(this.navs, data)
+      const config = nav && nav.meta && nav.meta.default 
+        ? nav.meta.default  : {}
+        
+      this.$router.push({name: data, ...config})
+    }
+  },
+  components: {
+    'sub-menu': subMenu
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.slide {
+  background-color: #0C0E2E;
+  padding: 16px 0;
+}
+</style>

+ 24 - 0
src/view/layout/slide/submenu.vue

@@ -0,0 +1,24 @@
+<template>
+  <el-submenu :index="nav.name">
+    <template v-slot:title>
+      <i :class="nav.icon" v-if="nav.icon"></i>
+      <span>{{nav.meta.title}}</span>
+    </template>
+
+    <template v-for="item in nav.children">
+      <nav-sub-menu :nav="item" v-if="item.children && item.children.length" :key="item.name" />
+      <el-menu-item :index="item.name" :key="item.name" v-else-if="!item.children">
+        <i :class="item.icon" v-if="item.icon"></i>
+        <span>{{item.meta.title}}</span>
+      </el-menu-item>
+    </template>
+
+  </el-submenu>
+</template>
+
+<script>
+export default {
+  name: 'nav-sub-menu',
+  props: ['nav']
+}
+</script>

+ 3 - 0
src/view/layout/structure.vue

@@ -0,0 +1,3 @@
+<template>
+  <router-view />
+</template>

+ 98 - 0
src/view/layout/top/index.vue

@@ -0,0 +1,98 @@
+<template>
+  <div class="top">
+    <div class="title">
+      <!-- <img src="@/assets/image/img_login_logo.png"> -->
+      <h2>中免VR商城</h2>
+    </div>
+    <div class="oper-btns">
+      <div class="info">
+        <img src="@/assets/image/top_my.png">
+        <span>{{info.nickName}}</span>
+        <span>{{info.userName}}</span>
+      </div>
+      <span @click="data.show = true"><img src="@/assets/image/top_set.png"></span>
+      <span @click="logout"><img src="@/assets/image/top_exit.png"></span>
+    </div>
+  </div>
+  
+  <com-dialog title="修改密碼" v-model:show="data.show" @submit="updatePsw">
+    <el-form ref="form" :model="data" label-width="100px" class="info-from">
+      <el-form-item label="舊密碼:" class="mandatory">
+        <el-input v-model="data.oldPassword" type="password" placeholder="請輸入當前密碼"></el-input>
+      </el-form-item>
+      <el-form-item label="新密碼:" class="mandatory">
+        <el-input v-model="data.password" type="password" placeholder="必須包含數字、字母,長度8-16個字符"></el-input>
+      </el-form-item>
+      <el-form-item label="確認密碼:" class="mandatory">
+        <el-input v-model="data.confimPsw" type="password" placeholder="必須包含數字、字母,長度8-16個字符"></el-input>
+      </el-form-item>
+    </el-form>
+
+  </com-dialog>
+
+</template>
+<script>
+import { encryption } from '@/util'
+import comDialog from "@/components/dialog";
+import axios from 'axios';
+import { updateUserPWD, userLogout } from '@/request/config'
+import { setToken, setInfo,setRole } from '@/state/user'
+import { computed, ref } from 'vue'
+import user from '@/state/user'
+import {PSW} from '@/constant/REG'
+import { openErrorMsg } from '@/request/errorMsg.js'
+
+export default {
+  setup() {
+    const data = ref({show: false, oldPassword: '', password: '', confimPsw: ''})
+    const info = computed(() => user.value.info)
+
+    return { data, info }
+  },
+  methods: {
+    async updatePsw() {
+      if (this.data.oldPassword.length === 0) {
+        return this.$alert('請輸入舊密碼', '提示')
+      }
+      if (this.data.password !== this.data.confimPsw) {
+        return this.$alert('兩次密碼不一致!', '提示')
+      }
+      if (!PSW.REG.test(this.data.password)) {
+        return this.$alert(PSW.tip, '提示')
+      }
+      
+
+      await axios.post(updateUserPWD, {
+        oldPassword: encryption(this.data.oldPassword),
+        newPassword: encryption(this.data.password)
+      })
+
+      this.data.show = false
+      this.data.oldPassword = this.data.password = this.data.confimPsw = ''
+
+      openErrorMsg('密碼修改成功,請重新登錄。')
+
+      this._loginout()
+    },
+    async _loginout() {
+      await axios.post(userLogout)
+      setToken('')
+      setInfo({})
+      setRole('')
+      this.$router.replace({name: 'login'})
+    },
+    async logout() {
+      if (await this.$confirm('確定要退出登錄嗎?', '提示')) {
+        this._loginout()
+      }
+    }
+  },
+  components: {
+    "com-dialog": comDialog,
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+@import './style.scss';
+</style>

+ 57 - 0
src/view/layout/top/style.scss

@@ -0,0 +1,57 @@
+.top {
+  background-color: #1D2049;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px;
+
+  .title {
+    display: flex;
+    align-items: center;
+
+    img {
+      width: 40px;
+    }
+
+    h2 {
+      font-size: 1.3rem;
+      font-weight: normal;
+      color: #fff;
+      margin-left: 0.66rem;
+    }
+  }
+
+  .oper-btns {
+    display: flex;
+    align-items: center;
+
+    .info {
+      color: #909399;
+      margin-right: 14px;
+      font-size: 1rem;
+      margin-top: -3px;
+      display: flex;
+      align-items: center;
+
+      span {
+        font-weight: normal;
+        font-size: 0.9rem;
+      }
+
+      span:last-child {
+        margin-left: 8px;
+      }
+    }
+
+    > span {
+      display: inline-block;
+      margin-right: 14px;
+      cursor: pointer;
+
+      img {
+        width: 34px;
+        height: 34px;
+      }
+    }
+  }
+}

+ 147 - 0
src/view/log/index.vue

@@ -0,0 +1,147 @@
+<template>
+  <com-head :options="headList" showCtrl>
+    <el-form label-width="90px" inline="true">
+      <el-form-item label="时间段:">
+        <el-date-picker
+          v-model="time"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        >
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="操作人:">
+        <el-input
+          v-model="search.state.operatorName"
+          placeholder="请输入"
+        ></el-input>
+      </el-form-item>
+      <el-form-item label="所属公司:">
+        <com-company v-model="search.state.companyId" />
+      </el-form-item>
+      <el-form-item label="手机号:">
+        <el-input
+          v-model="search.state.phone"
+          placeholder="请输入"
+        ></el-input>
+      </el-form-item>
+      <el-form-item class="searh-btns">
+        <el-button @click="search.reset">重置</el-button>
+        <el-button type="primary" @click="search.submit">查询</el-button>
+      </el-form-item>
+    </el-form>
+  </com-head>
+
+  <div class="body-layer">
+    <div class="body-head">
+      <h3>日志列表</h3>
+    </div>
+
+    <el-table
+      ref="multipleTable"
+      :data="dataList.state"
+      tooltip-effect="dark"
+      style="width: 100%"
+      @row-click="selectRow"
+    >
+      <el-table-column label="操作时间" prop="operateTime"></el-table-column>
+      <el-table-column label="操作员" prop="operatorName"></el-table-column>
+      <el-table-column label="手机号" prop="operatorPhone"></el-table-column>
+      <el-table-column
+        label="所属公司"
+        prop="operatorCompanyName"
+      ></el-table-column>
+      <el-table-column label="角色" prop="operatorRoleName"></el-table-column>
+      <el-table-column label="操作类型" prop="operateType"></el-table-column>
+      <el-table-column label="操作记录" prop="operateContent"></el-table-column>
+    </el-table>
+    
+    <com-pagination
+      @size-change="pag.sizeChange"
+      @current-change="pag.currentChange"
+      :current-page="pag.state.currPage"
+      :page-size="pag.state.size"
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="pag.state.total"/>
+
+  </div>
+</template>
+
+<script>
+import { ref, watch } from "vue";
+import getTableState from "@/state/tableRef";
+import auth from "@/state/viewAuth";
+import comHead from "@/components/head";
+import comCompany from "@/components/company-select";
+import comPagination from "@/components/pagination";
+import { dateFormat } from '@/util'
+import { getLogList } from "@/request/config";
+
+export default {
+  name: 'log',
+  setup() {
+    const headList = ref([{ name: "操作日志", value: 1 }]);
+    const state = getTableState({
+      getUrl: getLogList,
+      searchAttr: {
+        startTime: "",
+        endTime: "",
+        operatorName: "",
+        companyId: "",
+        phone: "",
+      },
+    });
+    const time = ref(null);
+    
+    watch(time, () => {
+      if (time.value) {
+        state.search.value.state.startTime = dateFormat(new Date(time.value[0]), 'yyyy-MM-dd hh:mm:ss')
+        state.search.value.state.endTime = dateFormat(new Date(time.value[1]),'yyyy-MM-dd 23:59:59')
+      } else {
+        state.search.value.state.startTime = state.search.value.state.endTime = ''
+      }
+    })
+    watch(
+      [() => state.search.value.state.startTime, () => state.search.value.state.endTime],
+      () => {
+        if (!state.search.value.state.startTime || !state.search.value.state.endTime) {
+          time.value = null
+        }
+      }
+    )
+
+
+
+    return { ...state, headList, time, auth };
+  },
+  components: {
+    "com-head": comHead,
+    "com-company": comCompany,
+    "com-pagination": comPagination
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.table-ctrl-right {
+  .search-scene {
+    margin: 0 20px 0 26px;
+  }
+  i {
+    margin-left: 20px;
+    font-size: 1.7rem;
+    vertical-align: middle;
+    cursor: pointer;
+
+    &.active {
+      color: var(--primaryColor);
+    }
+  }
+}
+
+.examine-from {
+  width: 320px;
+  margin: 0 auto;
+}
+</style>

+ 211 - 0
src/view/login/index.vue

@@ -0,0 +1,211 @@
+<template>
+  <div class="layer">
+    <div class="content">
+      <div class="info">
+        <h1>中免VR商城管理后台</h1>
+        <p>宅家中,云逛街,轻松买</p>
+      </div>
+      <el-form class="login" :model="form" @submit.stop>
+        <h2>登录</h2>
+        <el-form-item class="login-form-item">
+          <p class="err-info">{{verification.phone}}</p>
+          <el-input v-model="form.phone" placeholder="手机号" @keydown.enter="submitClick"></el-input>
+        </el-form-item>
+        <el-form-item class="login-form-item">
+          <p class="err-info">{{verification.psw}}</p>
+          <el-input v-model="form.psw" placeholder="密码" type="password" @keydown.enter="submitClick"></el-input>
+        </el-form-item>
+        
+        <el-form-item class="login-form-item">
+          <el-button type="primary" class="fill" @click="submitClick">登录</el-button>
+        </el-form-item>
+
+        
+        <el-form-item class="login-form-item remember">
+          <el-checkbox label="记住密码(公共场所请勿勾选)" v-model="form.remember"></el-checkbox>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script>
+import { reactive, watch } from 'vue'
+import axios from 'axios'
+import { setToken, setInfo, setRole } from '@/state/user'
+import { userLogin } from '@/request/config'
+import {PHONE} from '@/constant/REG'
+import { openErrorMsg } from '@/request/errorMsg.js'
+import { encryption } from '@/util'
+
+export default {
+  name: 'login',
+  setup() {
+    const form = reactive({
+      phone: '',
+      psw: '',
+      // phone: '13888888888',
+      // psw: 'Aa123456',
+      remember: localStorage.getItem('remember') === '1'
+    })
+    const verification = reactive({ phone: '', psw: '' })
+
+
+    watch(form, () => {
+      if (!form.phone) {
+        verification.phone = '请输入手机号'
+      } else {
+        verification.phone = PHONE.REG.test(form.phone) ? '': PHONE.tip
+      }
+      if (!form.psw) {
+        verification.psw = '请输入密码'
+      } else {
+        verification.psw = ''
+      }
+    }, {immediate: true})
+
+    return {
+      form,
+      verification
+    }
+  },
+  methods: {
+    async submitClick(ev) {
+      ev.stopPropagation()
+
+      if (this.verification.psw) return openErrorMsg(this.verification.psw, '提示')
+      if (this.verification.code) return openErrorMsg(this.verification.code, '提示')
+
+      try {
+        // await axios.get(checkCode, {params: { code: this.form.code, key: this.imgKey }})
+        let res = await axios.post(userLogin, {
+          phone: this.form.phone,
+          password: encryption(this.form.psw)
+        })
+
+
+        if (this.form.remember) {
+          localStorage.setItem('userName', this.form.phone)
+          localStorage.setItem('password', this.form.psw)
+          localStorage.setItem('remember', '1')
+        } else {
+          localStorage.setItem('userName', '')
+          localStorage.setItem('password', '')
+          localStorage.setItem('remember', '0')
+        }
+
+        setRole(res.data.userVo.roleId)
+        setInfo(res.data.userVo)
+        setToken(res.data.token)
+        this.$router.replace({ name:  res.data.userVo.roleId == 3 ?'shop':'scene' })
+        
+      } catch(e) {
+        console.log(e);
+      }
+
+
+    },
+  
+  },
+  data() {
+
+    return {
+    }
+  },
+  computed: {
+    
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.layer {
+  position: absolute;
+  z-index: 1;
+  width: 100%;
+  min-height: 100%;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+
+  >.content {
+    min-height: 626px;
+    background: url('~@/assets/image/img_loginbg.png') no-repeat center center;
+    background-size: cover;
+    width: 100%;
+  }
+}
+
+.content {
+  display: flex;
+  align-items: center;
+  justify-content: space-around;
+
+  >.info {
+    color: #fff;
+
+    img {
+      width: 76px;
+      height: 76px;
+    }
+    h1 {
+      font-size: 3rem;
+      line-height: 3.5rem;
+      font-weight: normal;
+      margin: 0.41rem 0 0.91rem;
+    }
+    p {
+      font-size: 1.6rem;
+      line-height: 2.3rem;
+
+    }
+  }
+
+  >.login {
+    background: #FFFFFF;
+    border-radius: 2px;
+    width: 264px;
+    padding: 0 20px 20px;
+
+    h2 {
+      color: var(--primaryColor);
+      font-size: 1.66rem;
+      padding: 15px 0;
+      border-bottom: 1px solid #D9D9D9;
+      text-align: center;
+      margin-bottom: 1rem;
+    }
+  }
+}
+
+.login-form-item {
+  position: relative;
+  padding: 1rem 0;
+  margin: 0;
+
+ 
+  &.remember {
+    padding: 0;
+  }
+
+
+  .err-info {
+    position: absolute;
+    bottom: 100%;
+    font-size: 1rem;
+    line-height: 1.5em;
+    color: #F2969B;
+  }
+}
+</style>
+
+<style>
+.code-input .el-input-group__append{
+  padding: 0;
+}
+
+.code-input .el-input-group__append img {
+  height: 100%;
+  cursor: pointer;
+}
+</style>

+ 0 - 0
src/view/login/style.css


+ 572 - 0
src/view/properties/community/index.vue

@@ -0,0 +1,572 @@
+<template>
+  <com-head :options="headList" v-model="search.state.type" showCtrl>
+    <el-form label-width="90px" inline="true">
+      <el-form-item label="小区编码:">
+        <el-input v-model="search.state.gardenNumber" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="小区名称:">
+        <el-input v-model="search.state.gardenName" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="城市:">
+        <el-select v-model="search.state.cityId" clearable  filterable placeholder="请选择">
+          <el-option
+            v-for="item in citys"
+            :key="item.cityId"
+            :label="item.cityName"
+            :value="item.cityId">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="行政区:">
+        <com-region :cityId="search.state.cityId" v-model="search.state.areaId" clearable filterable  placeholder="请输入" />
+      </el-form-item>
+      <el-form-item label="物业类型:">
+        <el-select v-model="search.state.propertySort" clearable  filterable placeholder="请选择">
+          <el-option
+            v-for="item in propertys"
+            :key="item.propertySort"
+            :label="item.propertySortName"
+            :value="item.propertySort">
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="建成年代:">
+        <el-date-picker
+          v-model="yearsTime"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="审核人:">
+        <el-input v-model="search.state.auditorName" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="创建人:">
+        <el-input v-model="search.state.creatorName" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="创建时间:">
+        <el-date-picker
+          v-model="createTime"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+
+      <el-form-item class="searh-btns">
+        <el-button @click="resetSearch">重置</el-button>
+        <el-button type="primary" @click="search.submit">查询</el-button>
+      </el-form-item>
+    </el-form>
+  </com-head>
+
+  <div class="body-layer">
+    <div class="body-head">
+      <h3>
+        <span>全部小区</span> 
+      </h3>
+      <div>
+        <el-button type="primary" @click="oper.readyInsert" v-if="auth.add">创建小区</el-button>
+        <el-button type="primary" @click="dataList.deleteSelect" v-if="auth.delete"  :disabled="!dataList.state.length">删除</el-button>
+      </div>
+    </div>
+    <el-table ref="multipleTable" :data="dataList.state" style="width: 100%" @selection-change="dataList.changeSelectRows">
+      <el-table-column type="selection" width="55" @click.stop v-if="auth.delete"></el-table-column>
+      <!-- <el-table-column label="封面" v-slot:default="{ row }">
+        <img :src="row.coverImagUrl" class="cover-img">
+      </el-table-column> -->
+      <el-table-column label="小区编码" v-slot:default="{ row }">
+        {{row.gardenNumber}}
+      </el-table-column>
+      <el-table-column label="小区名称" v-slot:default="{ row }">
+        <a class="oper-link" @click="$router.push({name: 'secondHouse', params: { id: row.gardenId, cityId: row.cityId }})">{{row.gardenName}}</a>
+      </el-table-column>
+
+      <el-table-column label="城市" prop="cityName"></el-table-column>
+      <el-table-column label="片区|商圈" v-slot:default="{ row }" >
+        <span v-if="row.regionName || row.geographyAreaName">{{row.regionName}}|{{row.geographyAreaName}}</span>
+      </el-table-column>
+      <el-table-column label="物业类型" prop="propertySortName"></el-table-column>
+      <el-table-column label="建成年代" v-slot:default="{ row }">
+        <span>{{row.buildStartTime}}-{{row.buildEndTime}}</span>
+      </el-table-column>
+      <el-table-column label="创建人" prop="creatorName"></el-table-column>
+      <el-table-column label="所属公司" prop="companyName"></el-table-column>
+      <el-table-column label="创建时间" prop="createTime"></el-table-column>
+      <el-table-column label="状态" v-slot:default="{row}" width="90">
+        <div class="status" :class="{interactive: row.auditStatus !== draftStatus}" @click="row.auditStatus !== draftStatus && selectRow(row)">
+          <span>{{getStatusName(row.auditStatus)}}</span>
+          <template v-if="row.auditStatus !== draftStatus || row.auditorName || row.auditTime">
+            <i class="el-icon-document" v-if="row.auditStatus !== noAdoptStatus && row.auditStatus !== noComAdoptStatus"></i>
+            <i class="el-icon-warning-outline error" v-else></i>
+          </template>
+        </div>
+        
+      </el-table-column>
+      <el-table-column label="审核人" prop="auditorName"></el-table-column>
+      <el-table-column label="审核时间" prop="auditTime"></el-table-column>
+      <el-table-column label="操作" width="180" v-slot:default="{row}">
+        <span
+          class="oper-span" 
+          @click="(~[draftStatus, noAdoptStatus, noComAdoptStatus].indexOf(row.auditStatus)) && editHouse(row)" 
+          :class="{disable: !~[draftStatus, noAdoptStatus, noComAdoptStatus].indexOf(row.auditStatus)}" 
+          v-if="auth.update"
+          >
+          编辑航拍
+        </span>
+        <span
+          class="oper-span" 
+          @click="~[uplineStatus].indexOf(row.auditStatus) && editHouse(row, 1)" 
+          :class="{disable: (!~[uplineStatus].indexOf(row.auditStatus))}" >
+          查看
+        </span>
+        <!-- <span
+          class="oper-span" 
+          @click="~[draftStatus, noAdoptStatus, noComAdoptStatus].indexOf(row.status) && editHouse(row)" 
+          :class="{disable: !~[draftStatus, noAdoptStatus, noComAdoptStatus].indexOf(row.status)}" 
+          v-if="auth.deleteProject">
+          删除
+        </span>
+         -->
+        <span
+          class="oper-span"
+          v-if="user.role !== 'group'"
+          :class="{disable: user.role === 'group' || adoptStatus === row.auditStatus || uplineStatus === row.auditStatus}"
+          @click.stop="(user.role !== 'group' && row.auditStatus !== adoptStatus && row.auditStatus !== uplineStatus) && examine(row)">
+          {{ ~[draftStatus, noAdoptStatus, adoptStatus, noComAdoptStatus].indexOf(row.auditStatus) ? '提审' : '撤回'}}
+          
+        </span>
+        <span class="oper-span" @click.stop="shareRow(row)" v-if="false">预览</span>
+        <span class="oper-span" @click.stop="offlineItem(row)" v-if="row.auditStatus === uplineStatus" :class="{disable: !~[adoptStatus, uplineStatus].indexOf(row.auditStatus)}">
+          {{row.auditStatus === uplineStatus ? '下线' : '上线' }}
+        </span>
+      </el-table-column>
+    </el-table>
+
+    <com-pagination
+      @size-change="pag.sizeChange"
+      @current-change="pag.currentChange"
+      :current-page="pag.state.currPage"
+      :page-size="pag.state.size"
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="pag.state.total"/>
+  </div>
+
+  <com-record v-model:show="recordData.show" :data="recordData.id" v-if="recordData.show" />
+
+  <com-share
+    :setLogo="auth.update"
+    v-if="shareData.show"
+    v-model:show="shareData.show" 
+    :items="shareData.data" />
+
+  
+  <com-dialog
+    title="新增二手房小区" v-model:show="oper.state.show" @submit="insertItem" @quit="oper.quit"
+  >
+    <el-form ref="form" :model="form" label-width="100px" class="info-from" style="width: 480px">
+      <el-form-item label="小区名称:" class="mandatory" style="margin-top:60px">
+        <el-autocomplete
+          popper-class="my-autocomplete"
+          class="inline-input"
+          v-model="oper.state.gardenName"
+          :fetch-suggestions="searchName"
+          @focus="oper.state.gardenId = ''"
+          placeholder="小区名称:"
+          @select="handleSelect"
+        >
+          <template v-slot="{ item }">
+            <div class="item-layout" :class="{disable: item.disable}" @click="$event => clickEstateItem($event, item)">
+              <span class="name">{{ item.gardenNumber }}</span>
+              <span class="addr">{{ item.gardenName }}</span>
+              <span class="addr">{{ item.regionName }}|{{ item.geographyAreaName }}</span>
+              <span class="addr">{{ item.cityName }}</span>
+            </div>
+          </template>
+        </el-autocomplete>
+      </el-form-item>
+    </el-form>
+
+  </com-dialog>
+
+
+  
+</template>
+
+<script>
+import axios from 'axios'
+import qrcode from 'qrcode'
+import {ref,reactive, watch} from 'vue'
+import getTableState from '@/state/tableRef'
+import comHead from "@/components/head";
+import comDialog from "@/components/dialog";
+import comRegion from "@/components/region";
+import comShare from "./share";
+import comRecord from "./record";
+import auth from "@/state/viewAuth";
+import comPagination from "@/components/pagination";
+import { dateFormat } from '@/util'
+import user from '@/state/user'
+import {
+  EXAMINE_SHOW_SELECT_STATUS_2 as EXAMINE_SHOW_SELECT_STATUS,
+  EXAMINE_SHOW_SELECT_STATUS_3 as EXAMINE_SHOW_SELECT_STATUS_TEXT,
+  EXAMINE_DRAFT_STATUS,
+  EXAMINE_NO_ADOPT_STATUS,
+  EXAMINE_NO_AUDIT_STATUS,
+  EXAMINE_NO_COM_ADOPT_STATUS,
+  EXAMINE_ADOPT_STATUS,
+  EXAMINE_UP_ONLINE_STATUS,
+  getName,
+
+  REDIRECT_AUDIT_BRING_STATUS,
+  REDIRECT_AUDIT_RECALL_STATUS,
+  REDIRECT_AUDIT_UP_STATUS,
+  REDIRECT_AUDIT_OFF_STATUS,
+  getRedirectCommunityAuditBody
+} from '@/constant'
+import { 
+  getCommunityList1 as getCommunityList,
+  deleteCommunity,
+  communityOnline,
+  auditCommunity,
+  dismissCommunity,
+  insertCommunity,
+  getHdCommunityNames
+} from '@/request/config'
+
+const getTimeMapSearch = (search, start, end) => {
+  const time = ref(null)
+
+  watch(time, () => {
+    if (time.value) {
+      search.value.state[start] = dateFormat(new Date(time.value[0]), 'yyyy-MM-dd hh:mm:ss').toString()
+      search.value.state[end] = dateFormat(new Date(time.value[1]), 'yyyy-MM-dd 23:59:59').toString()
+    } else {
+      search.value.state[start] = search.value.state[end] = ''
+    }
+  })
+
+  watch(
+    () => search.value.state[start], 
+    () => {
+      if (!search.value.state[start]) {
+        time.value = null
+      }
+    }
+  )
+  watch(
+    () => search.value.state[end], 
+    () => {
+      if (!search.value.state[end]) {
+        time.value = null
+      }
+    }
+  )
+
+  return time
+}
+
+export default {
+  name: 'housing',
+  setup() {
+    const headList = ref([{ name: "二手房", value: 1 }])
+    const state = getTableState({
+      getUrl: getCommunityList,
+      insertUrl: insertCommunity,
+      delUrl: deleteCommunity,
+      pagAttr: {listMap: {vrGardenId: 'id'}},
+      operAttr: { 
+        gardenId: "", 
+        vrGardenId: "",
+        gardenName: '',
+        creatorName: user.value.info.nickName,
+        creatorId: user.value.info.id,
+        companyId: user.value.info.departmentId,
+        companyName: ''
+      },
+      searchAttr: {
+        gardenNumber: "",
+        gardenName: '',
+        cityId: '',
+        readType: 'all',
+        areaId: '',
+        propertySort: '',
+        creatorName: '',
+        auditorName: '',
+        startCreateTime: '',
+        endCreateTime: '',
+        buildStartTime: '',
+        buildEndTime: ''
+      }
+    })
+
+
+    state.dataList.value._delete = async (data) => {
+      await axios.post(deleteCommunity, {}, {params: {vrGardenId: data.vrGardenId} })
+    }
+
+    const names = ref([])
+    const initNames = ref([])
+    const searchName = async (keyword = '', cb) => {
+      if (keyword === '') return cb && cb([])
+      cb && cb(names.value.map(item => ({...item, value: item.gardenName})));
+
+      let res = await axios.post(getHdCommunityNames, {keyword: keyword, pageSize: 100, currentPage: 1})
+      names.value = res.data.records
+      if (!keyword) {
+        initNames.value = names.value
+      }
+      cb && cb(names.value.map(item => ({...item, value: item.gardenName, disable: item.isGray})))
+    }
+
+    return { 
+      ...state, 
+      headList, 
+      auth, 
+      user, 
+      searchName, 
+      initNames, 
+      names,
+      yearsTime: getTimeMapSearch(state.search, 'buildStartTime', 'buildEndTime'),
+      createTime: getTimeMapSearch(state.search, 'startCreateTime', 'endCreateTime'),
+      citys: reactive([]),
+      propertys: reactive([])
+    };
+  },
+  data() {
+    return {
+      gardenName: '',
+      status: EXAMINE_SHOW_SELECT_STATUS,
+      draftStatus: EXAMINE_DRAFT_STATUS,
+      noAdoptStatus: EXAMINE_NO_ADOPT_STATUS,
+      noComAdoptStatus: EXAMINE_NO_COM_ADOPT_STATUS,
+      noAuditStatus: EXAMINE_NO_AUDIT_STATUS,
+      adoptStatus: EXAMINE_ADOPT_STATUS,
+      uplineStatus: EXAMINE_UP_ONLINE_STATUS,
+      recordData: { show: false, id: 0 },
+      shareData: { show: false, data: 0 }
+    }
+  },
+  methods: {
+    clickEstateItem(ev, item) {
+      if (item.disable) {
+        ev.stopPropagation()
+      }
+    },
+    getStatusName: getName(EXAMINE_SHOW_SELECT_STATUS_TEXT),
+    async insertItem() {
+      if (!this.oper.state.gardenName) {
+          return this.$alert('小区名称不能为空!', '提示')
+      }
+
+      if (!this.oper.state.gardenId.trim()) {
+        let estate = this.names.find(({gardenName}) => gardenName === this.oper.state.gardenName)
+        if (!estate) {
+          return this.$alert('无法匹配当前名称的小区!', '提示')
+        } else {
+          this.oper.state.gardenId = estate.gardenId
+        }
+      }
+      
+      if (this.oper.state.id) {
+        this.oper.update();
+      } else {
+        this.oper.insert();
+      }
+    },
+    selectRow(data) {
+      this.recordData.show = true
+      this.recordData.id = data.vrGardenId
+    },
+    async shareRow(raw, type = 1) {
+      if (!raw.vrUrl) {
+        return this.$confirm('暂无有效链接,请先添加小区航拍。', '提示')
+      }
+
+      const vrLink = process.env.VUE_APP_DOMAIN + raw.vrUrl
+      const QrCode = await qrcode.toDataURL(vrLink)
+
+      let shareItems = [{
+        QrCode: QrCode,
+        type: 'building',
+        noRandow: true,
+        houseTitle: raw.gardenName,
+        epcVrLink: vrLink.replace('hengda.html', 'epc.html'),
+        vrLink: vrLink,
+        innerVrLink: process.env.VUE_APP_INTRANET_DOMAIN + raw.vrUrl,
+        status: raw.auditStatus,
+      }]
+
+      if (type === 1) {
+        this.shareData.show = true
+        this.shareData.data = shareItems
+      } else {
+        window.open(type === 1 ? shareItems[0].vrLink : shareItems[0].epcVrLink)
+      }
+    },
+    async offlineItem(data) {
+      if (data.auditStatus !== EXAMINE_ADOPT_STATUS && data.auditStatus !== EXAMINE_UP_ONLINE_STATUS) return;
+
+      if (data.auditStatus === EXAMINE_UP_ONLINE_STATUS) {
+        if (!(await this.$msgbox({
+          message: '下线后VR链接将无法继续访问,确定要下线吗?', 
+          title: '下线',
+          showCancelButton: true,
+          confirmButtonText: '下线',
+          cancelButtonText: '取消',
+        }))) {
+          return;
+        }
+      }
+
+      const status = data.auditStatus === 5 ? REDIRECT_AUDIT_OFF_STATUS : REDIRECT_AUDIT_UP_STATUS
+
+      await axios.post(
+        communityOnline,
+        getRedirectCommunityAuditBody(data, status)
+      )
+      this.$alert((status === REDIRECT_AUDIT_UP_STATUS ? '上' : '下') + '线成功', '提示')
+      this.dataList.refer()
+    },
+
+    async examine(data) {
+      if (~[this.draftStatus, this.noAdoptStatus, this.adoptStatus, this.noComAdoptStatus].indexOf(data.auditStatus)) {
+        // await this.shareRow(data)
+        // if (!this.shareData.show) {
+        //   return;
+        // }
+        // this.shareData.show = false
+
+        await axios.post(auditCommunity, getRedirectCommunityAuditBody(data, REDIRECT_AUDIT_BRING_STATUS))
+        // data.auditId = res.data.auditId
+        this.$alert('提审成功', '提示')
+      } else {
+        await axios.post(dismissCommunity, getRedirectCommunityAuditBody(data, REDIRECT_AUDIT_RECALL_STATUS))
+        this.$alert('撤销成功', '提示')
+      }
+
+      this.dataList.refer()
+    },
+    resetSearch() {
+      let type = this.search.state.type
+      this.search.reset()
+      this.search.state.type = type
+    },
+    editHouse(item, isView = 0) {
+      let link = process.env.VUE_APP_PREFIX + '/sh_house/edit.html?m=' + item.id + '&h=' + item.gardenId + '&canView=' + isView
+      window.open(link)
+    }
+  },
+  mounted() {
+    this.searchName()
+  },
+  components: {
+    "com-head": comHead,
+    "com-dialog": comDialog,
+    "com-record": comRecord,
+    "com-share": comShare,
+    "com-pagination": comPagination,
+    "com-region": comRegion
+  },
+};
+</script>
+
+<style lang="less">
+
+.my-autocomplete {
+  .el-autocomplete-suggestion {
+    min-width: 220px;
+    // width: auto !important;
+  }
+  li {
+    line-height: normal;
+    padding: 0 !important;
+    .item-layout {
+      padding: 7px !important;
+      display: flex;
+      justify-content: space-between;
+      span {
+        margin: 0 5px;
+        flex: none;
+      }
+
+    }
+    .item-layout.disable {
+      cursor: not-allowed;
+
+      *{
+        color: #c0c4cc;
+      }
+    }
+    .name {
+      text-overflow: ellipsis;
+      overflow: hidden;
+      line-height: 16px;
+    }
+    .addr {
+      text-overflow: ellipsis;
+      overflow: hidden;
+      line-height: 16px;
+    }
+
+    .highlighted .addr {
+      color: #ddd;
+    }
+  }
+}
+
+
+.info-from .el-form-item__content{
+  width: calc(100% - 100px);
+}
+.info-from .el-form-item__content .inline-input {
+  width: 100%;
+}
+</style>
+
+<style lang="scss" scoped>
+
+.add-input {
+  width: 250px;
+  margin: 40px auto;
+  padding-left: 80px;
+  position: relative;
+
+  label {
+    position: absolute;
+    left: 0;
+    top: 50%;
+    transform: translateY(-50%);
+
+    span {
+      color: var(--primaryColor);
+    }
+  }
+}
+
+.cover-img {
+  width: 32px;
+  height: 32px;
+}
+
+.status {
+  &.interactive {
+    cursor: pointer;
+  }
+
+  i {
+    vertical-align: text-bottom;
+    margin-left: 5px;
+    font-size: 1.2rem;
+    color: rgb(144,144,150);
+    
+    &.error {
+      color: var(--primaryColor);
+    }
+  }
+}
+
+</style>
+

+ 82 - 0
src/view/properties/community/record.vue

@@ -0,0 +1,82 @@
+<template>
+  <com-dialog
+    v-if="show"
+    title="审核记录"
+    :show="show"
+    hideFloor
+    @update:show="val => $emit('update:show', val)">
+    
+    <div style="width: 100%; overflow: hidden">
+      <el-table ref="multipleTable" :data="dataList.state" style="width: 100%" >
+        <el-table-column label="姓名" prop="auditorName"></el-table-column>
+        <el-table-column label="操作类型"  v-slot:default="{ row }">
+          {{row.oldAuditStatus === EXAMINE_DRAFT_STATUS 
+            ? '提审' 
+            : row.newAuditStatus === EXAMINE_DRAFT_STATUS 
+              ?  '撤回'
+              : '审核'}}
+        </el-table-column>
+        <el-table-column label="公司" prop="operatorCompanyName"></el-table-column>
+        <el-table-column label="更新时间" prop="auditTime"></el-table-column>
+        <el-table-column label="状态" prop="newAuditStatus"  v-slot:default="{ row }">
+          {{row.newAuditStatus}}
+          <span>{{getExamineName(row.newAuditStatus)}}</span>
+        </el-table-column>
+        <el-table-column label="审核备注" prop="auditRemark"  v-slot:default="{ row }">
+          <div class="remark">
+            {{row.auditRemark}}
+          </div>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <div class="pag-block no-sizes">
+      <el-pagination
+        @current-change="pag.currentChange"
+        :current-page="pag.state.currPage"
+        :page-size="pag.state.size"
+        layout="total, prev, pager, next, jumper"
+        :total="pag.state.total">
+      </el-pagination>
+    </div>
+
+  </com-dialog>
+</template>
+
+<script>
+import getTableState from '@/state/tableRef'
+import comDialog from "@/components/dialog";
+import { getCommunityExamineViewList } from '@/request/config'
+import { 
+  AUTH_REDIRECT_COM_VIEW,
+  EXAMINE_DRAFT_STATUS,
+  getName 
+} from '@/constant'
+
+export default {
+  props: ['show', 'data'],
+  setup(props) {
+    const state = getTableState({
+      getUrl: getCommunityExamineViewList,
+      searchAttr: { vrGardenId: props.data }
+    })
+
+    state.pag.value.sizeChange(8)
+
+    return { ...state, EXAMINE_DRAFT_STATUS }
+  },
+  methods: {
+    getExamineName: getName(AUTH_REDIRECT_COM_VIEW),
+  },
+  components: {
+    "com-dialog": comDialog
+  }  
+}
+</script>
+
+<style lang="scss" scoped>
+.remark {
+  max-height: 100px;
+  overflow-y: auto;
+}
+</style>

+ 595 - 0
src/view/properties/community/second-house/index.vue

@@ -0,0 +1,595 @@
+<template>
+  <com-head :options="headList" v-model="search.state.type" showCtrl>
+    <el-form label-width="90px" inline="true">
+      <el-form-item label="楼栋-单元:">
+        <el-input v-model="search.state.buildingUnit" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="房号:">
+        <el-input v-model="search.state.roomNumber" placeholder="请输入"></el-input>
+      </el-form-item>
+      <el-form-item label="建筑面积:" >
+        <div class="range-number">
+          <el-input v-model="search.state.constructionAreaStar" placeholder="请输入"></el-input>
+          <span>至</span>
+          <el-input v-model="search.state.constructionAreaEnd" placeholder="请输入"></el-input>
+        </div>
+      </el-form-item>
+      <el-form-item label="套内面积:">
+        <div class="range-number">
+          <el-input v-model="search.state.insideAreaStar" placeholder="请输入"></el-input>
+          <span>至</span>
+          <el-input v-model="search.state.insideAreaSnd" placeholder="请输入"></el-input>
+        </div>
+      </el-form-item>
+
+      <el-form-item label="更新时间:">
+        <el-date-picker
+          v-model="updateTime"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始时间"
+          end-placeholder="结束时间">
+        </el-date-picker>
+      </el-form-item>
+
+      <el-form-item label="维护人:">
+        <el-input v-model="search.state.maintenanceman" placeholder="请输入"></el-input>
+      </el-form-item>
+      
+      <el-form-item label="状态:">
+        <el-select v-model="search.state.auditStatus" placeholder="全部">
+          <el-option v-for="(item) in status" :key="item.value" :label="item.name" :value="item.value"></el-option>
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="审核人:">
+        <el-input v-model="search.state.reviewer" placeholder="请输入"></el-input>
+      </el-form-item>
+
+      <el-form-item label="创建时间:">
+        <el-date-picker
+          v-model="createTime"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+
+      <el-form-item label="审核时间:">
+        <el-date-picker
+          v-model="reviewTime"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期">
+        </el-date-picker>
+      </el-form-item>
+
+      <el-form-item class="searh-btns">
+        <el-button @click="resetSearch">重置</el-button>
+        <el-button type="primary" @click="search.submit">查询</el-button>
+      </el-form-item>
+    </el-form>
+  </com-head>
+
+  <div class="body-layer">
+    <div class="body-head">
+      <h3>
+        <span>全部房源VR</span> 
+      </h3>
+      <!-- <div>
+        <el-button type="primary" @click="dataList.deleteSelect" v-if="auth.delete"  :disabled="!dataList.state.length">删除</el-button>
+      </div> -->
+    </div>
+
+    <el-table ref="multipleTable" :data="dataList.state" style="width: 100%" @selection-change="dataList.changeSelectRows">
+      <el-table-column type="selection" width="55" @click.stop v-if="auth.deleteProject"></el-table-column>
+      
+      <el-table-column label="楼栋-单元" v-slot:default="{ row }">
+        {{row.buildingUnit}}
+      </el-table-column>
+      <el-table-column label="房号" v-slot:default="{ row }">
+        {{row.roomNumber}}
+      </el-table-column>
+
+      <el-table-column label="建筑面积" prop="buildArea"></el-table-column>
+      <el-table-column label="套内面积" prop="roomArea"></el-table-column>
+      <el-table-column label="状态" v-slot:default="{row}" width="90">
+        <div class="status" :class="{interactive:row.auditStatus !== draftStatus || row.reviewer || row.reviewTime}" @click="(row.auditStatus !== draftStatus || row.reviewer || row.reviewTime) && selectRow(row)">
+          <span>{{getStatusName(row.auditStatus)}}</span>
+          <template v-if="row.auditStatus !== draftStatus || row.reviewer || row.reviewTime">
+            <i class="el-icon-document" v-if="row.auditStatus !== noAdoptStatus && row.auditStatus !== noComAdoptStatus"></i>
+            <i class="el-icon-warning-outline error" v-else></i>
+          </template>
+        </div>
+        
+      </el-table-column>
+      <el-table-column label="维护人" prop="maintenanceman"></el-table-column>
+      <el-table-column label="更新时间" prop="lastupdateTime"></el-table-column>
+      <el-table-column label="审核人" prop="reviewer"></el-table-column>
+      <el-table-column label="审核时间" prop="reviewTime"></el-table-column>
+      <el-table-column label="操作" width="180" v-slot:default="{row}">
+        <span
+          class="oper-span" 
+          @click="(~[draftStatus, noAdoptStatus, noComAdoptStatus].indexOf(row.auditStatus)) && editHouse(row)" 
+          :class="{disable: !~[draftStatus, noAdoptStatus, noComAdoptStatus].indexOf(row.auditStatus)}" 
+          v-if="auth.update"
+          >
+          编辑VR
+        </span>
+        <span
+          class="oper-span" 
+          @click="() => {~[adoptStatus, uplineStatus].indexOf(row.auditStatus) && (queryVR.show = true ,queryVR.data = row)}" 
+          :class="{disable: (!~[adoptStatus, uplineStatus].indexOf(row.auditStatus))}" >
+          查看
+        </span>
+        <!-- <span
+          class="oper-span" 
+          @click="~[draftStatus, noAdoptStatus, noComAdoptStatus].indexOf(row.status) && editHouse(row)" 
+          :class="{disable: !~[draftStatus, noAdoptStatus, noComAdoptStatus].indexOf(row.status)}" 
+          v-if="auth.deleteProject">
+          删除
+        </span>
+         -->
+        <span
+          class="oper-span"
+          v-if="user.role !== 'group'"
+          :class="{disable: user.role === 'group' || adoptStatus === row.auditStatus || uplineStatus === row.auditStatus}"
+          @click.stop="(user.role !== 'group' && row.auditStatus !== adoptStatus && row.auditStatus !== uplineStatus) && examine(row)">
+          {{ ~[draftStatus, noAdoptStatus, adoptStatus, noComAdoptStatus].indexOf(row.auditStatus) ? '提审' : '撤回'}}
+        </span>
+
+        <span
+          class="oper-span" 
+          @click="(~[draftStatus, noAdoptStatus, noComAdoptStatus].indexOf(row.auditStatus)) && delModel(row)" 
+          :class="{disable: !~[draftStatus, noAdoptStatus, noComAdoptStatus].indexOf(row.auditStatus)}" 
+          v-if="auth.update"
+          >
+          删除VR
+        </span>
+        <span class="oper-span" @click.stop="shareRow(row)" v-if="false">预览</span>
+        <span class="oper-span" @click.stop="offlineItem(row)" v-if="row.auditStatus === uplineStatus" :class="{disable: !~[adoptStatus, uplineStatus].indexOf(row.auditStatus)}">
+          {{row.auditStatus === uplineStatus ? '下线' : '上线' }}
+        </span>
+      </el-table-column>
+    </el-table>
+
+    <com-pagination
+      @size-change="pag.sizeChange"
+      @current-change="pag.currentChange"
+      :current-page="pag.state.currPage"
+      :page-size="pag.state.size"
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="pag.state.total"/>
+  </div>
+
+  <com-query-vr v-model:show="queryVR.show" :data="queryVR.data" v-if="queryVR.show" />
+  <com-record v-model:show="recordData.show" :data="recordData.id" v-if="recordData.show" />
+
+  <com-share
+    :setLogo="auth.update"
+    v-if="shareData.show"
+    v-model:show="shareData.show" 
+    :items="shareData.data" />
+
+
+  <com-vrlist v-model:show="selectVR.show" v-if="selectVR.show" @enter="item => updateModel(item)" v-model="selectVR.value" />
+
+
+</template>
+
+<script>
+import axios from 'axios'
+import {ref, watch} from 'vue'
+import getTableState from '@/state/tableRef'
+import comHead from "@/components/head";
+import comShare from "../share";
+import comRecord from "./record";
+import comVrlist from "./vrlist";
+import comQueryVr from "./queryvr";
+
+import qrcode from 'qrcode'
+import auth from "@/state/viewAuth";
+import comPagination from "@/components/pagination";
+import router from '@/router'
+import { dateFormat, strToParams } from '@/util'
+import user from '@/state/user'
+import {
+  EXAMINE_SHOW_SELECT_STATUS_2 as EXAMINE_SHOW_SELECT_STATUS,
+  EXAMINE_SHOW_SELECT_STATUS_3 as EXAMINE_SHOW_SELECT_STATUS_TEXT,
+  EXAMINE_DRAFT_STATUS,
+  UN_REQ_NUM,
+  EXAMINE_NO_ADOPT_STATUS,
+  EXAMINE_NO_AUDIT_STATUS,
+  EXAMINE_NO_COM_ADOPT_STATUS,
+  EXAMINE_ADOPT_STATUS,
+  EXAMINE_UP_ONLINE_STATUS,
+  EXAMINE_NO_COM_AUDIT_STATUS,
+  getName,
+  
+  getRedirectRoomAuditBody
+} from '@/constant'
+import { 
+  getCommunityByHouseId,
+  getSecondHouseList,
+  deleteSecondHouse,
+  auditSecondHouse,
+  dismissSecondHouse,
+  secondHouseOnline
+} from '@/request/config'
+
+const getTimeMapSearch = (searchKey, search) => {
+  const time = ref(null)
+  const start = `${searchKey}Start`
+  const end = `${searchKey}End`
+
+  watch(time, () => {
+    if (time.value) {
+      search.value.state[start] = dateFormat(new Date(time.value[0]), 'yyyy-MM-dd hh:mm:ss').toString()
+      search.value.state[end] = dateFormat(new Date(time.value[1]), 'yyyy-MM-dd 23:59:59').toString()
+    } else {
+      search.value.state[start] = search.value.state[end] = ''
+    }
+  })
+
+  watch(
+    () => search.value.state[start], 
+    () => {
+      if (!search.value.state[start]) {
+        time.value = null
+      }
+    }
+  )
+  watch(
+    () => search.value.state[end], 
+    () => {
+      if (!search.value.state[end]) {
+        time.value = null
+      }
+    }
+  )
+
+  return time
+}
+
+export default {
+  name: 'housing',
+  setup() {
+    const headList = ref([{ name: "二手房", value: 1 }])
+    const state = getTableState({
+      getUrl: getSecondHouseList,
+      delUrl: deleteSecondHouse,
+      pagAttr: {listMap: {roomid: 'id'}},
+      searchAttr: {
+        gardenId: router.currentRoute.value.params.id,
+        cityId: router.currentRoute.value.params.cityId,
+        buildingUnit: "",
+        roomNumber: '',
+        maintenanceman: '',
+        auditStatus: UN_REQ_NUM,
+        reviewer: '',
+        updateTimeStart: '',
+        updateTimeEnd: '',
+        createTimeStart: '',
+        createTimeEnd: '',
+        reviewTimeStart: '',
+        reviewTimeEnd: '',
+        insideAreaSnd: '',
+        insideAreaStar: '',
+        constructionAreaStar: '',
+        constructionAreaEnd: '',
+      }
+    })
+
+    state.dataList.value._delete = async (data) => {
+      await axios.post(deleteSecondHouse, {}, {params: {roomid: data.roomid} })
+    }
+
+    const names = ref([])
+    const initNames = ref([])
+    
+    return { 
+      ...state, 
+      headList, 
+      auth, 
+      user, 
+      initNames, 
+      names,
+      updateTime: getTimeMapSearch('updateTime', state.search),
+      auditTime: getTimeMapSearch('auditTime', state.search),
+      createTime: getTimeMapSearch('createTime', state.search),
+      reviewTime: getTimeMapSearch('reviewTime', state.search)
+    };
+  },
+  data() {
+    return {
+      gardenName: '',
+      status: EXAMINE_SHOW_SELECT_STATUS,
+      uplineStatus: EXAMINE_UP_ONLINE_STATUS,
+      draftStatus: EXAMINE_DRAFT_STATUS,
+      noAdoptStatus: EXAMINE_NO_ADOPT_STATUS,
+      noComAdoptStatus: EXAMINE_NO_COM_ADOPT_STATUS,
+      noAuditStatus: EXAMINE_NO_AUDIT_STATUS,
+      adoptStatus: EXAMINE_ADOPT_STATUS,
+      recordData: { show: false, id: 0 },
+      selectVR: { show: false, data: null, value: null },
+      queryVR: { show: false, data: null },
+      shareData: { show: false, data: 0 }
+    }
+  },
+  methods: {
+    clickEstateItem(ev, item) {
+      if (item.disable) {
+        ev.stopPropagation()
+      }
+    },
+    getStatusName: getName(EXAMINE_SHOW_SELECT_STATUS_TEXT),
+    selectRow(data) {
+      this.recordData.show = true
+      this.recordData.id = data.vrId
+    },
+    async shareRow(raw, type = 1) {
+      if (!raw.vRUrl) {
+        return this.$confirm('暂无有效链接', '提示')
+      }
+
+      const vrLink = process.env.VUE_APP_DOMAIN + raw.vRUrl
+      const QrCode = await qrcode.toDataURL(vrLink)
+
+      let shareItems = [{
+        QrCode: QrCode,
+        type: 'building',
+        noRandow: true,
+        houseTitle: raw.buildingUnit + raw.roomNumber,
+        epcVrLink: vrLink.replace('hengda.html', 'epc.html'),
+        vrLink: vrLink,
+        innerVrLink: process.env.VUE_APP_INTRANET_DOMAIN + raw.vRUrl,
+        status: raw.auditStatus,
+      }]
+
+      if (type === 1) {
+        this.shareData.show = true
+        this.shareData.data = shareItems
+      } else {
+        window.open(type === 1 ? shareItems[0].vrLink : shareItems[0].epcVrLink)
+      }
+    },
+    openHouse(data) {
+      return this.shareRow(data)
+      // if (data.status === this.adoptStatus) {
+      //   window.open(data.vrLink)
+      // } else {
+      //   window.open(data.innerVrLink)
+      // }
+    },
+    async offlineItem(data) {
+      data.cityId = this.$route.params.cityId
+      data.gardenName = this.gardenName
+      if (data.auditStatus !== EXAMINE_ADOPT_STATUS && data.auditStatus !== EXAMINE_UP_ONLINE_STATUS) return;
+
+      if (data.auditStatus === EXAMINE_UP_ONLINE_STATUS) {
+        if (!(await this.$msgbox({
+          message: '下线后VR链接将无法继续访问,确定要下线吗?', 
+          title: '下线',
+          showCancelButton: true,
+          confirmButtonText: '下线',
+          cancelButtonText: '取消',
+        }))) {
+          return;
+        }
+      }
+
+      const status = data.auditStatus === EXAMINE_UP_ONLINE_STATUS ? EXAMINE_DRAFT_STATUS : EXAMINE_UP_ONLINE_STATUS
+
+      await axios.post(
+        secondHouseOnline,
+        getRedirectRoomAuditBody(data, status)
+      )
+      this.$alert((status === EXAMINE_UP_ONLINE_STATUS ? '上' : '下') + '线成功', '提示')
+      this.dataList.refer()
+    },
+
+    async examine(data) {
+      data.cityId = this.$route.params.cityId
+      data.gardenName = this.gardenName
+
+      console.log(data)
+      if (~[this.draftStatus, this.noAdoptStatus, this.adoptStatus, this.noComAdoptStatus].indexOf(data.auditStatus)) {
+        await this.shareRow(data)
+        if (!this.shareData.show) {
+          return;
+        }
+        this.shareData.show = false
+
+        // await axios.post(auditSecondHouse, getRedirectRoomAuditBody(data, EXAMINE_NO_COM_AUDIT_STATUS))
+        const status = user.value.role === 'group' || user.value.role === 'admin' || user.value.role === 'region' 
+          ? EXAMINE_NO_AUDIT_STATUS
+          : EXAMINE_NO_COM_AUDIT_STATUS
+        
+        await axios.post(auditSecondHouse, getRedirectRoomAuditBody(data, status))
+        
+        // data.auditId = res.data.auditId
+        this.$alert('提审成功', '提示')
+      } else {
+        await axios.post(dismissSecondHouse, getRedirectRoomAuditBody(data, EXAMINE_DRAFT_STATUS))
+        this.$alert('撤销成功', '提示')
+      }
+
+      this.dataList.refer()
+    },
+    resetSearch() {
+      let type = this.search.state.type
+      this.search.reset()
+      this.search.state.type = type
+    },
+    async updateModel(raw) {
+      const upload = {
+        vrModelName: raw.sceneName,
+        num: raw.num,
+        vrCover: raw.thumb,
+        vRUrl: `/hengda.html?m=${raw.num}&prodId=${this.search.state.gardenId}&houseId=${this.selectVR.data.roomlistingId}&businessType=2`
+      }
+
+      await axios.post(dismissSecondHouse, getRedirectRoomAuditBody({
+        ...this.selectVR.data,
+        ...upload
+      }, EXAMINE_DRAFT_STATUS, '', this.selectVR.data.roomlistingId))
+
+      this.selectVR.show = false
+      this.dataList.refer()
+    },
+    async delModel(row) {
+      if (!row.vRUrl) {
+        return this.$alert('暂无有效链接', '提示')
+      }
+      await axios.post(dismissSecondHouse, getRedirectRoomAuditBody({
+        ...row,
+        vrCover: '',
+        vRUrl: '',
+      }, EXAMINE_DRAFT_STATUS, '', this.selectVR.data.roomlistingId))
+
+      this.selectVR.show = false
+      this.dataList.refer()
+    },
+    editHouse(item, isView = 0) {
+      if (isView) {
+        const params = strToParams(item.vRUrl.substr(item.vRUrl.indexOf('?')))
+        
+        const link = '/epc.html?m=' + params.m + '&token=' + user.value.token
+        return window.open(link)
+      } else {
+        if (item.vRUrl) {
+          const params = strToParams(item.vRUrl.substr(item.vRUrl.indexOf('?')))
+          this.selectVR.value = params.m
+        } else {
+          this.selectVR.value = null
+        }
+        
+        this.selectVR.show = true
+        this.selectVR.data = item
+      }
+      // let link = process.env.VUE_APP_PREFIX + '/sh_house/edit.html?m=' + item.id + '&h=' + item.gardenId + '&canView=' + isView
+      // window.open(link)
+    }
+  },
+  watch: {
+    '$route': {
+      immediate: true,
+      async handler(newVal, oldVal) {
+        if (newVal.name !== "secondHouse" || (this.search.state.gardenId === newVal.params.id && oldVal)) return;
+        try {
+          this.search.state.gardenId = newVal.params.id
+          let res = await axios.get(getCommunityByHouseId, { params: { gardenId: newVal.params.id } })
+          this.gardenName = res.data.gardenName
+          document.querySelector('.player').querySelector('.active span').textContent = this.gardenName
+          this.gardenId = res.data.gardenId
+        } catch (e) {
+          this.$router.back()
+        }
+      } 
+    }
+  },
+  components: {
+    comVrlist,
+    comQueryVr,
+    "com-head": comHead,
+    "com-record": comRecord,
+    "com-share": comShare,
+    "com-pagination": comPagination
+  },
+};
+</script>
+
+<style lang="less">
+
+.my-autocomplete {
+  li {
+    line-height: normal;
+    padding: 0 !important;
+    .item-layout {
+      padding: 7px !important;
+      display: flex;
+      justify-content: space-between;
+      span {
+        flex: none;
+      }
+
+    }
+    .item-layout.disable {
+      cursor: not-allowed;
+
+      *{
+        color: #c0c4cc;
+      }
+    }
+    .name {
+      text-overflow: ellipsis;
+      overflow: hidden;
+      line-height: 16px;
+    }
+    .addr {
+      text-overflow: ellipsis;
+      overflow: hidden;
+      line-height: 16px;
+    }
+
+    .highlighted .addr {
+      color: #ddd;
+    }
+  }
+}
+
+
+.info-from .el-form-item__content{
+  width: calc(100% - 100px);
+}
+.info-from .el-form-item__content .inline-input {
+  width: 100%;
+}
+</style>
+
+<style lang="scss" scoped>
+
+.add-input {
+  width: 250px;
+  margin: 40px auto;
+  padding-left: 80px;
+  position: relative;
+
+  label {
+    position: absolute;
+    left: 0;
+    top: 50%;
+    transform: translateY(-50%);
+
+    span {
+      color: var(--primaryColor);
+    }
+  }
+}
+
+.cover-img {
+  width: 32px;
+  height: 32px;
+}
+
+.status {
+  &.interactive {
+    cursor: pointer;
+  }
+
+  i {
+    vertical-align: text-bottom;
+    margin-left: 5px;
+    font-size: 1.2rem;
+    color: rgb(144,144,150);
+    
+    &.error {
+      color: var(--primaryColor);
+    }
+  }
+}
+
+</style>
+

+ 71 - 0
src/view/properties/community/second-house/queryvr.vue

@@ -0,0 +1,71 @@
+<template>
+  <com-dialog
+    v-if="show"
+    title="VR模型"
+    :show="show"
+    hideFloor
+    @update:show="val => $emit('update:show', val)">
+
+    <el-table
+      ref="multipleTable"
+      :data="dataList"
+      tooltip-effect="dark"
+      class="table-layer"
+      style="width: 100%"
+    >
+      <el-table-column label="场景封面" v-slot:default="{ row }">
+        <div class="table-img">
+          <img :src="`${row.vrCover}?time=${Date.now()}`">
+          <!-- <div v-if="row.status === 0">计算中</div> -->
+        </div>
+      </el-table-column>
+      <el-table-column label="模型名称" prop="vrModelName"></el-table-column>
+    </el-table>
+
+  </com-dialog>
+</template>
+
+<script>
+import comDialog from "@/components/dialog";
+
+export default {
+  props: ['show', 'data'],
+  setup(props) {
+    return { dataList: [props.data] }
+  },
+  components: {
+    "com-dialog": comDialog
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+.table-img {
+  width: 100px;
+  position: relative;
+
+  img {
+    width: 100%;
+  }
+
+  div {
+    position: absolute;
+    z-index: 1;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    background-color: rgba(0,0,0,0.8);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #fff;
+  }
+}
+
+.table-layer {
+  height: 400px;
+  overflow-y: auto;
+}
+</style>

+ 76 - 0
src/view/properties/community/second-house/record.vue

@@ -0,0 +1,76 @@
+<template>
+  <com-dialog
+    v-if="show"
+    title="审核记录"
+    :show="show"
+    hideFloor
+    @update:show="val => $emit('update:show', val)">
+    
+    <div style="width: 100%; overflow: hidden">
+      <el-table ref="multipleTable" :data="dataList.state" style="width: 100%" >
+        <el-table-column label="姓名" prop="reviewer"></el-table-column>
+        <el-table-column label="操作类型"  prop="processOperation">
+        </el-table-column>
+        <el-table-column label="公司" prop="companyName"></el-table-column>
+        <el-table-column label="更新时间" prop="lastupdateTime"></el-table-column>
+        <el-table-column label="状态" prop="auditStatus"  v-slot:default="{ row }">
+          <span>{{getExamineName(row.auditStatus)}}</span>
+        </el-table-column>
+        <el-table-column label="审核备注" prop="reviewMark"  v-slot:default="{ row }">
+          <div class="remark">
+            {{row.reviewMark}}
+          </div>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <div class="pag-block no-sizes">
+      <el-pagination
+        @current-change="pag.currentChange"
+        :current-page="pag.state.currPage"
+        :page-size="pag.state.size"
+        layout="total, prev, pager, next, jumper"
+        :total="pag.state.total">
+      </el-pagination>
+    </div>
+
+  </com-dialog>
+</template>
+
+<script>
+import getTableState from '@/state/tableRef'
+import comDialog from "@/components/dialog";
+import { getSecondHouseExamineViewList } from '@/request/config'
+import { 
+  AUTH_REDIRECT_COM_VIEW,
+  EXAMINE_DRAFT_STATUS,
+  getName 
+} from '@/constant'
+
+export default {
+  props: ['show', 'data'],
+  setup(props) {
+    const state = getTableState({
+      getUrl: getSecondHouseExamineViewList,
+      searchAttr: { vrId: props.data }
+    })
+
+    state.pag.value.sizeChange(8)
+
+    return { ...state, EXAMINE_DRAFT_STATUS }
+  },
+  methods: {
+    getExamineName: getName(AUTH_REDIRECT_COM_VIEW),
+  },
+  components: {
+    "com-dialog": comDialog
+  }  
+}
+</script>
+
+<style lang="scss" scoped>
+.remark {
+  max-height: 100px;
+  overflow-y: auto;
+}
+</style>

+ 0 - 0
src/view/properties/community/second-house/vrlist.vue


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff