持续集成分支
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -27,3 +27,6 @@ yarn-error.log* | ||||
| /project/*/dist | ||||
| /ui/package-lock.json | ||||
| /examples/modules.json | ||||
| /examples/router/apps.js | ||||
| /src/apps/ | ||||
| /src/config.json | ||||
|   | ||||
							
								
								
									
										5
									
								
								.npmrc
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								.npmrc
									
									
									
									
									
								
							| @@ -1,5 +1,4 @@ | ||||
| registry=http://192.168.1.87:4873/ | ||||
| email=aixianling@sinoecare.com | ||||
| always-auth=true | ||||
| package-lock=false | ||||
| //192.168.1.87:4873/:_auth="YWRtaW46YWRtaW4xMjM=" | ||||
| registry=http://registry.npmmirror.com | ||||
| legacy-peer-deps=true | ||||
|   | ||||
							
								
								
									
										36
									
								
								bin/scanApps.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								bin/scanApps.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| const {chalkTag, findApp, fs, fsExtra} = require("./tools"); | ||||
| const compiler = require('vue-template-compiler') | ||||
| const getAppInfo = (file, apps) => { | ||||
|   if (/[\\\/](App[A-Z][^\\\/]+)\.vue$/g.test(file)) { | ||||
|     const name = file.replace(/.+[\\\/](App[^\\\/]+)\.vue$/, '$1'), | ||||
|       source = fs.readFileSync(file).toString(), | ||||
|       parsed = compiler.parseComponent(source), | ||||
|       script = parsed.script?.content || "", | ||||
|       label = script.match(/label:[^,]+/)?.[0]?.replace(/.+["']([^"']+).+/, '$1') | ||||
|     const paths = file.split(/[\\\/]/) | ||||
|     apps.push({ | ||||
|       id: file.replace(/\.vue$/, '').replace(/[\\\/]/g, '_'), | ||||
|       label: label || name, | ||||
|       path: `/${file.replace(/\.vue$/, '').replace(/[\\\/]/g, '/')}`, | ||||
|       workspace: paths.at(0), | ||||
|       esm: file.replace(/[\\\/]/g, '/'), | ||||
|       name | ||||
|     }) | ||||
|   } | ||||
| } | ||||
|  | ||||
| const start = () => { | ||||
|   chalkTag.info("开始扫描库工程...") | ||||
|   const list = [] | ||||
|   Promise.all([ | ||||
|     findApp('packages', app => getAppInfo(app, list)), | ||||
|     findApp('project', app => getAppInfo(app, list)), | ||||
|   ]).then(() => { | ||||
|     fsExtra.outputFile('examples/router/apps.js', `export default [${list.map(e => { | ||||
|       const {name, label, path, esm} = e | ||||
|       return `{name:"${name}",label:"${label}",path:"${path}",component:()=>import("@${esm}")}` | ||||
|     }).join(',\n')}]`) | ||||
|     chalkTag.done("扫描完毕") | ||||
|   }) | ||||
| } | ||||
| start() | ||||
| @@ -39,10 +39,10 @@ export default { | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapState(['user', 'apps']), | ||||
|     ...mapState(['user']), | ||||
|     navs() { | ||||
|       let reg = new RegExp(`.*${this.searchApp?.replace(/-/g,'')||''}.*`, 'gi') | ||||
|       return (this.apps || []).filter(e => !this.searchApp || reg?.test(e.name) || reg?.test(e.label)).map(e => { | ||||
|       return (this.$apps || []).filter(e => !this.searchApp || reg?.test(e.name) || reg?.test(e.label)).map(e => { | ||||
|         if (/\/project\//.test(e.path)) { | ||||
|           e.project = process.env.VUE_APP_SCOPE || e.path.replace(/.*project\/([^\/]+)\/.+/, '$1') | ||||
|         } else if (/\/core\//.test(e.path)) { | ||||
| @@ -52,9 +52,9 @@ export default { | ||||
|       }) | ||||
|     }, | ||||
|     menuPath() { | ||||
|       let paths = [], current = this.apps?.find(e => e.name == this.$route.name) | ||||
|       let paths = [], current = this.$apps?.find(e => e.name == this.$route.name) | ||||
|       const findParent = name => { | ||||
|         let menu = this.apps?.find(e => e.name == name) | ||||
|         let menu = this.$apps?.find(e => e.name == name) | ||||
|         if (menu) { | ||||
|           paths.push(menu.name) | ||||
|           if (!!menu.parentId) findParent(menu.parentId) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import Vue from 'vue'; | ||||
| import App from './App.vue'; | ||||
| import ui from 'element-ui'; | ||||
| import router from './router/router'; | ||||
| import router from './router'; | ||||
| import axios from './router/axios'; | ||||
| import utils from './utils'; | ||||
| import dui from 'dui'; | ||||
| @@ -22,6 +22,7 @@ const app = new Vue({ | ||||
|   store, | ||||
|   render: h => h(App) | ||||
| }); | ||||
|  | ||||
| let theme = null | ||||
| store.dispatch('getSystem').then(res => { | ||||
|   theme = JSON.parse(res?.colorScheme || null) | ||||
|   | ||||
| @@ -1,81 +0,0 @@ | ||||
| import store from "../store"; | ||||
| import {waiting} from "../utils"; | ||||
| import appEntry from "../views/appEntry"; | ||||
| import router from "./router"; | ||||
|  | ||||
| export default { | ||||
|   routes: () => store.state.apps, | ||||
|   init() { | ||||
|     //约束正则式 | ||||
|     store.commit("cleanApps") | ||||
|     // 自动化本工程应用 | ||||
|     waiting.init({innerHTML: '应用加载中..'}) | ||||
|     let startTime = new Date().getTime() | ||||
|     /** | ||||
|      * require.context 的路径变量范式只能为静态字符串 | ||||
|      */ | ||||
|     switch (process.env.VUE_APP_SCOPE) { | ||||
|       case 'dv': | ||||
|         this.esm = { | ||||
|           packages: require.context('../../packages/bigscreen', true, /\.(\/.+)\/App[A-Z][^\/]+\.vue$/, 'lazy') | ||||
|         } | ||||
|         break | ||||
|       case 'fengdu': | ||||
|         this.esm = { | ||||
|           project: require.context('../../project/fengdu', true, /\.(\/.+)\/App[A-Z][^\/]+\.vue$/, 'lazy') | ||||
|         } | ||||
|         break | ||||
|       case 'ai': | ||||
|         this.esm = { | ||||
|           biaopin: require.context('../../project/biaopin/AppCopilotConfig', true, /\.\/App[A-Z][^\/]+\.vue$/, 'lazy'), | ||||
|           project: require.context('../../project/ai', true, /\.(\/.+)\/App[A-Z][^\/]+\.vue$/, 'lazy') | ||||
|         } | ||||
|         break | ||||
|       case 'oms': | ||||
|         this.esm = { | ||||
|           project: require.context('../../project/oms', true, /\.(\/.+)\/App[A-Z][^\/]+\.vue$/, 'lazy') | ||||
|         } | ||||
|         break | ||||
|       default: | ||||
|         this.esm = { | ||||
|           packages: require.context('../../packages/', true, /\.(\/.+)\/App[A-Z][^\/]+\.vue$/, 'lazy'), | ||||
|           project: require.context('../../project/', true, /\.(\/.+)\/App[A-Z][^\/]+\.vue$/, 'lazy') | ||||
|         } | ||||
|     } | ||||
|     console.log('模块引用用了%s秒', (new Date().getTime() - startTime) / 1000) | ||||
|     startTime = new Date().getTime() | ||||
|     this.loadApps().finally(() => { | ||||
|       console.log('模块加载用了%s秒', (new Date().getTime() - startTime) / 1000) | ||||
|       waiting.close() | ||||
|     }) | ||||
|   }, | ||||
|   loadMods() { | ||||
|     // return Promise.all(mods.apps.map(e => { | ||||
|     //   Vue.component(e.name, this.esm[e.workspace](e.esm)) | ||||
|     //   const addApp = {...e, component: appEntry} | ||||
|     //   waiting.setContent(`加载${e.name}...`) | ||||
|     //   //命名规范入口文件必须以App开头 | ||||
|     //   return store.commit("addApp", addApp) | ||||
|     // })) | ||||
|   }, | ||||
|   loadApps() { | ||||
|     //新App的自动化格式 | ||||
|     const promise = (mods, base) => Promise.all(mods.keys().map(path => mods(path).then(file => { | ||||
|       if (file.default) { | ||||
|         const {name, label} = file.default | ||||
|         const addApp = { | ||||
|           name: [base, path.replace(/\.\/?(vue)?/g, '')?.split("/")].flat().join("_"), | ||||
|           label: label || name, | ||||
|           path: `/${base}${path.replace(/\.(\/.+\/App.+)\.vue$/, '$1')}`, | ||||
|           component: appEntry, | ||||
|           esm: file.default | ||||
|         } | ||||
|         waiting.setContent(`加载${name}...`) | ||||
|         router.addRoute(addApp) | ||||
|         //命名规范入口文件必须以App开头 | ||||
|         return store.commit("addApp", addApp) | ||||
|       } else return 0 | ||||
|     }).catch(err => console.log(err)))) | ||||
|     return Promise.all(Object.entries(this.esm).map(([root, mods]) => promise(mods, root))).catch(console.error) | ||||
|   } | ||||
| } | ||||
| @@ -1,13 +1,13 @@ | ||||
| import Vue from 'vue' | ||||
| import VueRouter from 'vue-router' | ||||
| import autoRoutes from './autoRoutes' | ||||
| import apps from "./apps"; | ||||
| 
 | ||||
| Vue.use(VueRouter) | ||||
| autoRoutes.init() | ||||
| Vue.prototype.$apps = apps | ||||
| export default new VueRouter({ | ||||
|   mode: 'history', | ||||
|   hashbang: false, | ||||
|   routes: autoRoutes.routes(), | ||||
|   routes: apps, | ||||
|   scrollBehavior(to) { | ||||
|     if (to.hash) { | ||||
|       return { | ||||
| @@ -5,22 +5,8 @@ import * as modules from "dui/lib/js/modules" | ||||
| import xsActions from "../../project/xiushan/actions" | ||||
|  | ||||
| Vue.use(Vuex) | ||||
|  | ||||
| export default new Vuex.Store({ | ||||
|   state: { | ||||
|     apps: [] | ||||
|   }, | ||||
|   mutations: { | ||||
|     addApp(state, app) { | ||||
|       state.apps.push(app) | ||||
|     }, | ||||
|     cleanApps(state) { | ||||
|       state.apps = [] | ||||
|     }, | ||||
|   }, | ||||
|   actions: { | ||||
|     ...xsActions | ||||
|   }, | ||||
|   actions: {...xsActions}, | ||||
|   modules, | ||||
|   plugins: [preState()] | ||||
| }) | ||||
|   | ||||
| @@ -15,7 +15,8 @@ | ||||
|     "preui": "npm publish -ws||(npm unpublish -f -ws&&npm publish -ws)", | ||||
|     "ui": "npm i dui@latest @dui/dv@latest", | ||||
|     "sync": "node bin/appsSync.js", | ||||
|     "preview": "vue-cli-service serve" | ||||
|     "preview": "vue-cli-service serve", | ||||
|     "predev": "node bin/scanApps.js" | ||||
|   }, | ||||
|   "workspaces": [ | ||||
|     "ui", | ||||
| @@ -33,6 +34,7 @@ | ||||
|     "bin-ace-editor": "^3.2.0", | ||||
|     "dayjs": "^1.8.35", | ||||
|     "dui": "^2.0.0", | ||||
|     "echarts": "^5.5.1", | ||||
|     "echarts-wordcloud": "^2.0.0", | ||||
|     "hash.js": "^1.1.7", | ||||
|     "html2canvas": "^1.4.1", | ||||
|   | ||||
							
								
								
									
										70
									
								
								src/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/App.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| <template> | ||||
|   <div id="app" :class="{greyFilter,[`theme-${$theme}`]:true}"> | ||||
|     <router-view/> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import {mapState} from "vuex"; | ||||
| import customConfig from "./config.json"; | ||||
|  | ||||
| export default { | ||||
|   name: 'app', | ||||
|   computed: { | ||||
|     ...mapState(['sys']), | ||||
|     greyFilter: v => v.sys?.theme?.enableGreyFilter == 1, | ||||
|   }, | ||||
|   methods: { | ||||
|     initFavicon(icon) { | ||||
|       const linkList = document.head.querySelectorAll('link') || {}, | ||||
|           url = `${this.$cdn}/favicon/${icon || "favicon"}.ico` | ||||
|       if (Object.values(linkList).findIndex(e => e.href == url) == -1) { | ||||
|         let favicon = document.createElement("link") | ||||
|         favicon.rel = "icon" | ||||
|         favicon.href = url | ||||
|         document.head.appendChild(favicon) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.initFavicon(customConfig.sysInfo?.favicon) | ||||
|     document.title = this.sys.info.fullTitle | ||||
|     customConfig?.hmt && this.$injectLib("https://hm.baidu.com/hm.js?4e5dd7c5512e5da68c025c3b956fbd5d") | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss"> | ||||
| html, body { | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   padding: 0; | ||||
|   margin: 0; | ||||
| } | ||||
|  | ||||
| body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, input, p, blockquote, th, td { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
|  | ||||
| #app { | ||||
|   font-family: Helvetica, Arial, sans-serif; | ||||
|   -webkit-font-smoothing: antialiased; | ||||
|   -moz-osx-font-smoothing: grayscale; | ||||
|   overflow: hidden; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
| } | ||||
|  | ||||
| li { | ||||
|   list-style-type: none; | ||||
| } | ||||
|  | ||||
| .greyFilter { | ||||
|   filter: grayscale(100%); | ||||
| } | ||||
|  | ||||
| .amap-logo, .amap-copyright { | ||||
|   display: none !important; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										
											BIN
										
									
								
								src/assets/bg_prolife.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/bg_prolife.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 94 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/building.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/building.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 22 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/loginLeft.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/loginLeft.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 616 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/loginRightBottom.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/loginRightBottom.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 23 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/loginRightTop.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/loginRightTop.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/nav_bg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/nav_bg.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 75 KiB | 
							
								
								
									
										125
									
								
								src/components/AiAreaPicker.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/components/AiAreaPicker.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| <template> | ||||
|   <section class="AiAreaPicker"> | ||||
|     <div @click="handleOpenDialog"> | ||||
|       <slot v-if="$scopedSlots.default" :selected="selected"/> | ||||
|       <el-button v-else type="text">选择地区</el-button> | ||||
|     </div> | ||||
|     <ai-dialog :visible.sync="dialog" title="选择地区" @onConfirm="submit" @close="selecting=[],init()" destroy-on-close> | ||||
|       <el-breadcrumb separator-class="el-icon-arrow-right"> | ||||
|         <el-breadcrumb-item v-for="(item,i) in path" :key="item.id"> | ||||
|           <el-button type="text" @click.native="handlePathClick(i)">{{ item.name }}</el-button> | ||||
|         </el-breadcrumb-item> | ||||
|       </el-breadcrumb> | ||||
|       <ai-table-select class="mar-t16" v-model="selecting" :instance="instance" :action="action" :isShowPagination="false" extra="hidden" search-key="name" | ||||
|                        multiple valueObj> | ||||
|         <template slot="extra" slot-scope="{row}"> | ||||
|           <el-button v-if="row.id!=root" type="text" icon="el-icon-arrow-right" @click.stop="getChildren(row)"/> | ||||
|         </template> | ||||
|       </ai-table-select> | ||||
|     </ai-dialog> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|  | ||||
| export default { | ||||
|   name: "AiAreaPicker", | ||||
|   model: { | ||||
|     prop: "value", | ||||
|     event: "change" | ||||
|   }, | ||||
|   props: { | ||||
|     value: {default: ""}, | ||||
|     meta: {default: null}, | ||||
|     root: {default: ""}, | ||||
|     instance: {type: Function, required: true}, | ||||
|     multiple: Boolean | ||||
|   }, | ||||
|   computed: { | ||||
|     action() { | ||||
|       let currentParent = this.path.slice(-1)?.[0]?.id | ||||
|       return !!currentParent && /[^0]0{0,2}$/.test(currentParent) ? `/admin/appresident/queryAreaIdGroup?currentAreaId=${currentParent}` : | ||||
|           `/admin/area/queryAreaByParentIdSelf?self=${currentParent == this.root ? 1 : ''}&id=${currentParent}` | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     value(v) { | ||||
|       this.dispatch('ElFormItem', 'el.form.change', [v]); | ||||
|     }, | ||||
|     selected(v) { | ||||
|       this.$emit("update:meta", v) | ||||
|     } | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       dialog: false, | ||||
|       options: [], | ||||
|       selecting: [], | ||||
|       path: [], | ||||
|       selected: [] | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     dispatch(componentName, eventName, params) { | ||||
|       let parent = this.$parent || this.$root; | ||||
|       let name = parent.$options.componentName; | ||||
|       while (parent && (!name || name !== componentName)) { | ||||
|         parent = parent.$parent; | ||||
|         if (parent) { | ||||
|           name = parent.$options.componentName; | ||||
|         } | ||||
|       } | ||||
|       if (parent) { | ||||
|         parent.$emit.apply(parent, [eventName].concat(params)); | ||||
|       } | ||||
|     }, | ||||
|     getChildren(row) { | ||||
|       let area = this.path.find(e => e.id == row.id) | ||||
|       if (!area) this.path.push(row) | ||||
|     }, | ||||
|     handlePathClick(index) { | ||||
|       this.path.splice(index + 1, 10) | ||||
|     }, | ||||
|     submit() { | ||||
|       this.$emit("change", this.selecting.map(e => e.id)) | ||||
|       this.selected = this.$copy(this.selecting) | ||||
|       this.$emit("select", this.selecting) | ||||
|       this.dialog = false | ||||
|     }, | ||||
|     init() { | ||||
|       this.path = [{id: this.root, name: "可选范围"}] | ||||
|     }, | ||||
|     handleOpenDialog() { | ||||
|       let areas = this.value.filter(e => /^\d{12}$/.test(e)) | ||||
|       this.instance.post("/admin/area/getAreaNameByids", null, { | ||||
|         params: {ids: areas?.toString()} | ||||
|       }).then(res => { | ||||
|         if (res?.data) { | ||||
|           this.selecting = this.value.map(id => ({id, name: res.data?.[id] || id})) | ||||
|           this.dialog = true | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     getAreaById(id) { | ||||
|       return this.instance.post("/admin/area/queryAreaByAreaid", null, { | ||||
|         params: {id} | ||||
|       }).then(res => { | ||||
|         if (res?.data) { | ||||
|           return res.data | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.init() | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .AiAreaPicker { | ||||
|   .mar-t16 { | ||||
|     margin-top: 16px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										181
									
								
								src/components/AppLicence.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								src/components/AppLicence.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | ||||
| <template> | ||||
|   <section class="AppLicence" v-if="showLicence"> | ||||
|     <ai-list> | ||||
|       <template #content> | ||||
|         <div class="licence-content"> | ||||
|           <img class="left-img" src="https://cdn.cunwuyun.cn/dvcp/key.png" alt="" /> | ||||
|           <div class="content-right"> | ||||
|             <h3 class="title">产品许可信息</h3> | ||||
|             <p class="mini-title">您当前的版本为Saas专业版,非常感谢您对我们产品的认可与支持!</p> | ||||
|             <div class="info"> | ||||
|               <span class="label">过期时间</span> | ||||
|               <span class="value color-f46" v-if="info.isExpired == 1">{{info.expireDate ? info.expireDate+'(已过期)' : '未激活'}}</span> | ||||
|               <span class="value color-26f" v-else>{{info.expireDate}}</span> | ||||
|             </div> | ||||
|             <div class="info"> | ||||
|               <span class="label">主板序列号</span> | ||||
|               <span class="value">{{info.mainBoard}}</span> | ||||
|             </div> | ||||
|             <div class="info"> | ||||
|               <span class="label">CPU</span> | ||||
|               <span class="value">{{info.cpu}}</span> | ||||
|             </div> | ||||
|             <div class="info"> | ||||
|               <span class="label">MAC地址</span> | ||||
|               <span class="value">{{info.mac}}</span> | ||||
|             </div> | ||||
|             <div class="info mar-b32"> | ||||
|               <span class="label">IP地址</span> | ||||
|               <span class="value">{{info.ip}}</span> | ||||
|             </div> | ||||
|             <el-upload | ||||
|               class="upload-demo mar-r16" | ||||
|               action | ||||
|               multiple | ||||
|               accept=".lic" | ||||
|               :http-request="uploadFile"> | ||||
|               <div class="btn">上传许可</div> | ||||
|             </el-upload> | ||||
|             <div class="btn" @click="showLicence = false">返回登录</div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </template> | ||||
|     </ai-list> | ||||
|   </section> | ||||
| </template> | ||||
| <script> | ||||
|  | ||||
| export default { | ||||
|   name: "AppLicence", | ||||
|   label: "产品许可", | ||||
|   props: { | ||||
|     instance: Function, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       files: [], | ||||
|       info: {}, | ||||
|       showLicence: false | ||||
|     } | ||||
|   }, | ||||
|   mounted() { | ||||
|  | ||||
|   }, | ||||
|   methods: { | ||||
|     show() { | ||||
|       this.showLicence = true | ||||
|       this.getDetail() | ||||
|     }, | ||||
|     hide() { | ||||
|       this.showLicence = false | ||||
|     }, | ||||
|     getDetail() { | ||||
|       this.instance.post(`/admin/license/detail`).then(res => { | ||||
|         if (res?.data) { | ||||
|           this.info = res.data | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     uploadFile: function (file) { | ||||
|       let formData = new FormData(); | ||||
|       formData.append("file", file.file); | ||||
|       this.instance.post(`/admin/license/save`, formData, {withoutToken: false}).then(res => { | ||||
|         if (res && res.code == 0) { | ||||
|           this.$message.success("证书上传成功!"); | ||||
|           this.getDetail() | ||||
|         } | ||||
|       }).catch((err) => { | ||||
|         this.$message.error(err); | ||||
|       }) | ||||
|     }, | ||||
|   }, | ||||
|  | ||||
| }; | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
| .AppLicence { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   position: fixed; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   z-index: 99; | ||||
|   :deep( .ai-list){ | ||||
|     background-color: #F5F6F9; | ||||
|   } | ||||
|   :deep( .ai-list .ai-list__content--right .ai-list__content--right-wrapper){ | ||||
|     background-color: #F5F6F9; | ||||
|     box-shadow: 0 0 0 0; | ||||
|     margin: 0!important; | ||||
|     padding: 0!important; | ||||
|   } | ||||
|   :deep( .el-upload-list){ | ||||
|     display: none; | ||||
|   } | ||||
|   .licence-content{ | ||||
|     display: flex; | ||||
|     width: 1000px; | ||||
|     margin: 200px auto 0; | ||||
|     .left-img{ | ||||
|       width: 200px; | ||||
|       height: 200px; | ||||
|     } | ||||
|     .content-right{ | ||||
|       width: 800px; | ||||
|       .title{ | ||||
|         font-size: 24px; | ||||
|         font-weight: bold; | ||||
|         color: #222; | ||||
|         line-height: 24px; | ||||
|         margin-bottom: 8px; | ||||
|       } | ||||
|       .mini-title{ | ||||
|         font-size: 14px; | ||||
|         color: #555; | ||||
|         line-height: 22px; | ||||
|         margin-bottom: 20px; | ||||
|       } | ||||
|       .info{ | ||||
|         font-size: 14px; | ||||
|         line-height: 22px; | ||||
|         margin-bottom: 8px; | ||||
|         display: flex; | ||||
|         color: #222; | ||||
|         .label{ | ||||
|           width: 164px; | ||||
|         } | ||||
|         .value{ | ||||
|           width: calc(100% - 164px); | ||||
|         } | ||||
|         .color-26f{ | ||||
|           color: #26f; | ||||
|         } | ||||
|         .color-f46{ | ||||
|           color: #f46; | ||||
|         } | ||||
|       } | ||||
|       .mar-b32{ | ||||
|         margin-bottom: 32px; | ||||
|       } | ||||
|       .upload-demo{ | ||||
|         display: inline-block; | ||||
|       } | ||||
|       .btn{ | ||||
|         display: inline-block; | ||||
|         width: 88px; | ||||
|         height: 32px; | ||||
|         line-height: 32px; | ||||
|         text-align: center; | ||||
|         background: linear-gradient(90deg, #299FFF 0%, #0C61FF 100%); | ||||
|         border-radius: 2px; | ||||
|         cursor: pointer; | ||||
|         color: #fff; | ||||
|         font-size: 14px; | ||||
|       } | ||||
|       .mar-r16{ | ||||
|         margin-right: 16px; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										255
									
								
								src/components/headerNav.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								src/components/headerNav.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,255 @@ | ||||
| <template> | ||||
|   <div class="headerNav navBg"> | ||||
|     <div style="position: relative"> | ||||
|       <ai-icon type="logo" :icon="system.logo||'iconcunwei'"/> | ||||
|       <ai-icon type="logo" :icon="system.logo||'iconcunwei'" class="textShadow"/> | ||||
|     </div> | ||||
|     <span class="headerTitle">{{ system.fullTitle }}<div class="textShadow" v-html="system.fullTitle"/></span> | ||||
|     <el-row type="flex" align="middle" class="toolbar"> | ||||
|       <!--下载中心--> | ||||
|       <downloan-center-btn v-if="extra.downloadCenter"/> | ||||
|       <!--大屏按钮--> | ||||
|       <dv-btn v-if="extra.dv"/> | ||||
|       <div class="btn" v-if="extra.showTool" @click="home.showTool=false">隐藏工具栏</div> | ||||
|       <div class="btn" v-if="extra.helpDoc" @click="openHelp">帮助文档</div> | ||||
|       <app-qrcode v-if="extra.appQRCode"/> | ||||
|       <div class="btn" v-if="extra.customerService" @click.native="openAiService"> | ||||
|         <div class="iconfont iconCustomer_Service"></div> | ||||
|         <div>智能客服</div> | ||||
|       </div> | ||||
|       <!--推荐链接--> | ||||
|       <link-btn/> | ||||
|     </el-row> | ||||
|     <el-dropdown @visible-change="v=>isClick=v" @command="doMenu" class="rightDropdown"> | ||||
|       <el-row type="flex" align="middle"> | ||||
|         <el-avatar :src="user.info.avatar"> | ||||
|           {{ defaultAvatar }} | ||||
|         </el-avatar> | ||||
|         <span>{{ [user.info.name, user.info.roleName].join(" - ") }}</span> | ||||
|         <i :class="dropdownIcon"/> | ||||
|       </el-row> | ||||
|       <el-dropdown-menu> | ||||
|         <el-dropdown-item command="user">用户中心</el-dropdown-item> | ||||
|         <el-dropdown-item command="signOut">退出</el-dropdown-item> | ||||
|       </el-dropdown-menu> | ||||
|     </el-dropdown> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import {mapState} from "vuex"; | ||||
| import DvBtn from "./headerTools/dvBtn"; | ||||
| import DownloanCenterBtn from "./headerTools/downloanCenterBtn"; | ||||
| import extra from "../config.json" | ||||
| import AppQrcode from "./headerTools/appQrcode"; | ||||
| import LinkBtn from "./headerTools/linkBtn"; | ||||
|  | ||||
| export default { | ||||
|   name: 'headerNav', | ||||
|   components: {LinkBtn, AppQrcode, DownloanCenterBtn, DvBtn}, | ||||
|   data() { | ||||
|     return { | ||||
|       extra, | ||||
|       isClick: false, | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapState(['user', 'sys']), | ||||
|     dropdownIcon() { | ||||
|       return this.isClick ? 'el-icon-caret-top' : 'el-icon-caret-bottom' | ||||
|     }, | ||||
|     defaultAvatar() { | ||||
|       return this.user.info.name?.slice(-2) || "游客" | ||||
|     }, | ||||
|     system: v => v.sys?.info || {} | ||||
|   }, | ||||
|   created() { | ||||
|     this.$dict.load('fileFrom'); | ||||
|   }, | ||||
|   methods: { | ||||
|     // 获取最新的安卓、ios下载二维码 | ||||
|     doMenu(comm) { | ||||
|       switch (comm) { | ||||
|         case 'signOut': | ||||
|           //登出 | ||||
|           this.$confirm("是否要登出?", {type: "warning"}).then(() => { | ||||
|             this.$store.commit("signOut") | ||||
|           }).catch(() => { | ||||
|           }) | ||||
|           break; | ||||
|         case 'user': | ||||
|           this.$router.push({name: "个人中心"}) | ||||
|           break; | ||||
|       } | ||||
|     }, | ||||
|     openAiService() { | ||||
|       window.open('http://v3.faqrobot.org/robot/chat1.html?sysNum=153543696570625098') | ||||
|     }, | ||||
|     openHelp() { | ||||
|       window.open('https://www.yuque.com/books/share/eeaaa5e3-a528-42eb-872e-20d661f3d0e2') | ||||
|     }, | ||||
|     changeStatus(id) { | ||||
|       this.$request.post(`/app/sysuserdownload/addOrUpdate`, { | ||||
|         id, status: "2" | ||||
|       }).then(res => { | ||||
|         if (res?.code == 0) { | ||||
|           this.getFiles(); | ||||
|         } | ||||
|  | ||||
|       }); | ||||
|     }, | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .headerNav { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   width: 100%; | ||||
|   background-repeat: no-repeat; | ||||
|   background-size: 100% 48px; | ||||
|   position: fixed; | ||||
|   z-index: 99; | ||||
|   height: 48px; | ||||
|   padding-left: 24px; | ||||
|   box-sizing: border-box; | ||||
|   top: 0; | ||||
|   color: white; | ||||
|   font-size: 14px; | ||||
|  | ||||
|   .AiIcon { | ||||
|     font-size: 38px; | ||||
|     width: auto; | ||||
|     height: auto; | ||||
|     background: linear-gradient(180deg, #FFFFFF 0%, #CCDBF6 100%); | ||||
|     -webkit-background-clip: text; | ||||
|     -webkit-text-fill-color: transparent; | ||||
|  | ||||
|     &:hover { | ||||
|       color: white; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .headerTitle { | ||||
|     flex: 1; | ||||
|     min-width: 0; | ||||
|     font-size: 24px; | ||||
|     color: #FFF; | ||||
|     line-height: 28px; | ||||
|     background: linear-gradient(180deg, #FFFFFF 0%, #CCDBF6 100%); | ||||
|     -webkit-background-clip: text; | ||||
|     -webkit-text-fill-color: transparent; | ||||
|     font-weight: bold; | ||||
|     margin-left: 8px; | ||||
|     position: relative; | ||||
|   } | ||||
|  | ||||
|   :deep(.toolbar) { | ||||
|     gap: 12px; | ||||
|     margin-right: 32px; | ||||
|  | ||||
|     .btn { | ||||
|       padding: 0 12px; | ||||
|       color: white; | ||||
|  | ||||
|       &:hover { | ||||
|         cursor: pointer; | ||||
|         color: rgba(#fff, .8); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .el-dropdown { | ||||
|     height: 48px; | ||||
|     line-height: 48px; | ||||
|     color: #fff; | ||||
|     padding: 0 12px; | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: rgba(46, 51, 68, .15); | ||||
|       color: white; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .el-image { | ||||
|     margin: 12px 0 12px 16px; | ||||
|   } | ||||
|  | ||||
|   .download-wrapper { | ||||
|     position: relative; | ||||
|  | ||||
|     &:hover .download { | ||||
|       display: flex; | ||||
|     } | ||||
|  | ||||
|     .download { | ||||
|       display: none; | ||||
|       position: absolute; | ||||
|       top: 100%; | ||||
|       left: 12px; | ||||
|       transform: translateX(-90%); | ||||
|       width: auto; | ||||
|       height: auto; | ||||
|       padding: 12px; | ||||
|       background: #fff; | ||||
|       box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.1); | ||||
|       border-radius: 2px; | ||||
|       box-sizing: border-box; | ||||
|       z-index: 999; | ||||
|     } | ||||
|  | ||||
|     .download-item { | ||||
|       text-align: center; | ||||
|  | ||||
|       &:first-child { | ||||
|         margin-right: 13px; | ||||
|       } | ||||
|  | ||||
|       & > img { | ||||
|         width: 105px; | ||||
|         height: 105px; | ||||
|         margin-bottom: 7px; | ||||
|       } | ||||
|  | ||||
|       p { | ||||
|         margin-top: 5px; | ||||
|         font-size: 13px; | ||||
|         color: #333; | ||||
|         text-align: center; | ||||
|       } | ||||
|  | ||||
|       .download-item__middle { | ||||
|         img { | ||||
|           width: 13px; | ||||
|           height: 16px; | ||||
|           vertical-align: sub; | ||||
|         } | ||||
|  | ||||
|         span { | ||||
|           padding-left: 8px; | ||||
|           font-size: 13px; | ||||
|           color: #333; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   :deep(.rightDropdown) { | ||||
|     font-size: 12px; | ||||
|     padding: 0 16px; | ||||
|     height: 48px; | ||||
|     background: rgba(#fff, .1); | ||||
|  | ||||
|     .el-avatar > img { | ||||
|       width: 100%; | ||||
|     } | ||||
|  | ||||
|     .el-row { | ||||
|       gap: 4px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| </style> | ||||
							
								
								
									
										121
									
								
								src/components/headerTools/appQrcode.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/components/headerTools/appQrcode.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| <template> | ||||
|   <section class="appQrcode"> | ||||
|     <div class="btn">手机APP</div> | ||||
|     <div class="download"> | ||||
|       <div class="download-item" v-if="!androidQRcode&&!iosQRcode"><p class="nowarp-text" v-text="`暂未发布`"/></div> | ||||
|       <template v-else> | ||||
|         <div class="download-item" v-if='iosQRcode'> | ||||
|           <img :src="iosQRcode" alt=""/> | ||||
|           <div class="download-item__middle"> | ||||
|             <span class="iconfont iconIOS"></span> | ||||
|             <span>iPhone</span> | ||||
|           </div> | ||||
|           <p>手机扫码下载APP</p> | ||||
|         </div> | ||||
|         <div class="download-item" v-if='androidQRcode'> | ||||
|           <img :src="androidQRcode" alt=""/> | ||||
|           <div class="download-item__middle"> | ||||
|             <span class="iconfont iconAndroid"></span> | ||||
|             <span>Android</span> | ||||
|           </div> | ||||
|           <p>手机扫码下载APP</p> | ||||
|         </div> | ||||
|       </template> | ||||
|     </div> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   name: "appQrcode", | ||||
|   data() { | ||||
|     return { | ||||
|       iosQRcode: null, | ||||
|       androidQRcode: null | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     getAppQRcode() { | ||||
|       this.$request.post(`/admin/sysversion/getLatestIosVersion`, null, { | ||||
|         params: {type: 3} | ||||
|       }).then(res => { | ||||
|         if (res?.data) { | ||||
|           this.iosQRcode = res.data.qrCodeUrl | ||||
|         } | ||||
|       }) | ||||
|  | ||||
|       this.$request.post(`/admin/sysversion/getLatestVersion`, null, { | ||||
|         params: {type: 1} | ||||
|       }).then(res => { | ||||
|         if (res?.data) { | ||||
|           this.androidQRcode = res.data.qrCodeUrl | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|   }, | ||||
|   created() { | ||||
|     this.getAppQRcode() | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .appQrcode { | ||||
|   position: relative; | ||||
|  | ||||
|   &:hover .download { | ||||
|     display: flex; | ||||
|   } | ||||
|  | ||||
|   .download { | ||||
|     display: none; | ||||
|     position: absolute; | ||||
|     top: 100%; | ||||
|     left: 12px; | ||||
|     transform: translateX(-90%); | ||||
|     width: auto; | ||||
|     height: auto; | ||||
|     padding: 12px; | ||||
|     background: #fff; | ||||
|     box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.1); | ||||
|     border-radius: 2px; | ||||
|     box-sizing: border-box; | ||||
|     z-index: 999; | ||||
|   } | ||||
|  | ||||
|   .download-item { | ||||
|     text-align: center; | ||||
|  | ||||
|     &:first-child { | ||||
|       margin-right: 13px; | ||||
|     } | ||||
|  | ||||
|     & > img { | ||||
|       width: 105px; | ||||
|       height: 105px; | ||||
|       margin-bottom: 7px; | ||||
|     } | ||||
|  | ||||
|     p { | ||||
|       margin-top: 5px; | ||||
|       font-size: 13px; | ||||
|       color: #333; | ||||
|       text-align: center; | ||||
|     } | ||||
|  | ||||
|     .download-item__middle { | ||||
|       img { | ||||
|         width: 13px; | ||||
|         height: 16px; | ||||
|         vertical-align: sub; | ||||
|       } | ||||
|  | ||||
|       span { | ||||
|         padding-left: 8px; | ||||
|         font-size: 13px; | ||||
|         color: #333; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										221
									
								
								src/components/headerTools/downloanCenterBtn.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								src/components/headerTools/downloanCenterBtn.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | ||||
| <template> | ||||
|   <section class="downloanCenterBtn"> | ||||
|     <el-badge :value="badgeNum" :hidden='badgeNum==0'> | ||||
|       <div class="btn" @click="openDrawer()">下载中心</div> | ||||
|     </el-badge> | ||||
|     <el-drawer title="下载中心" :visible.sync="drawer" :modal-append-to-body="false" size="520"> | ||||
|       <div class="downLoad_main"> | ||||
|         <div class="search_top "> | ||||
|           <p style="color:#999999;">仅显示最近90天的记录</p> | ||||
|           <el-input size="mini" v-model="fileName" placeholder="文件名" clearable prefix-icon="iconfont iconSearch" | ||||
|                     style="width:240px;" @change="getFiles()"/> | ||||
|         </div> | ||||
|         <ul class="infinite-list"> | ||||
|           <li v-for="(item,i) in filesList" class="infinite-list-item " :key="i"> | ||||
|             <div class="left"> | ||||
|               <svg class="svg" aria-hidden="true"> | ||||
|                 <use xlink:href="#iconZip"/> | ||||
|               </svg> | ||||
|             </div> | ||||
|             <div class="middle"> | ||||
|               <p class="fileName">{{ item.fileName }}【密码:{{ item.pwd }}】</p> | ||||
|               <p> | ||||
|                 <span>来源:</span> | ||||
|                 <span>{{ $dict.getLabel('fileFrom', item.fileFrom) }}</span> | ||||
|                 <span>{{ (item.size / 1000).toFixed(2) + "KB" }}</span> | ||||
|                 <span>{{ item.createTime }}</span> | ||||
|               </p> | ||||
|             </div> | ||||
|             <div class="right"> | ||||
|               <span class="iconfont iconResetting" v-if="item.status==0">处理中</span> | ||||
|               <span v-if="item.status==2">已下载</span> | ||||
|               <i class="iconfont iconDownload" @click="downFile(item)" v-if="item.status!=0">下载</i> | ||||
|             </div> | ||||
|           </li> | ||||
|         </ul> | ||||
|       </div> | ||||
|     </el-drawer> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import {mapState} from "vuex"; | ||||
|  | ||||
| export default { | ||||
|   name: "downloanCenterBtn", | ||||
|   data() { | ||||
|     return { | ||||
|       badgeNum: 0, | ||||
|       drawer: false,//抽屉 | ||||
|       filesList: [], | ||||
|       fileName: '', | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapState(['user']) | ||||
|   }, | ||||
|   methods: { | ||||
|     openDrawer() { | ||||
|       this.drawer = true; | ||||
|       this.getFiles(); | ||||
|     }, | ||||
|     getFiles() { | ||||
|       this.$request.post(`/app/sysuserdownload/list`, null, { | ||||
|         params: { | ||||
|           userId: this.user.info.id, | ||||
|           fileName: this.fileName, | ||||
|           current: 1, | ||||
|           size: 1000, | ||||
|         } | ||||
|       }).then(res => { | ||||
|         if (res?.data) { | ||||
|           this.filesList = res.data.records; | ||||
|           this.searchNum() | ||||
|         } | ||||
|       }); | ||||
|     }, | ||||
|     //查询未完成数量 | ||||
|     searchNum() { | ||||
|       this.$request.post(`/app/sysuserdownload/queryCountByUserId`, null, { | ||||
|         params: { | ||||
|           userId: this.user.info.id | ||||
|         } | ||||
|       }).then(res => { | ||||
|         if (res?.data) { | ||||
|           this.badgeNum = res.data; | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     downFile(item) { | ||||
|       this.changeStatus(item.id); | ||||
|       // window.open(item.accessUrl); | ||||
|       let elemIF = document.createElement('iframe'); | ||||
|       elemIF.src = item.accessUrl; | ||||
|       elemIF.style.display = 'none'; | ||||
|       document.body.appendChild(elemIF); | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.searchNum() | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .downloanCenterBtn { | ||||
|   .downLoad_main { | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     padding: 16px; | ||||
|     box-sizing: border-box; | ||||
|  | ||||
|     .search_top { | ||||
|       display: flex; | ||||
|       justify-content: space-between; | ||||
|       align-items: center; | ||||
|       padding-bottom: 8px; | ||||
|     } | ||||
|  | ||||
|     .infinite-list { | ||||
|       width: 100%; | ||||
|       height: 100%; | ||||
|  | ||||
|       .infinite-list-item { | ||||
|         width: 100%; | ||||
|         padding: 8px; | ||||
|         box-sizing: border-box; | ||||
|         background: rgba(255, 255, 255, 1); | ||||
|         border-radius: 4px; | ||||
|         border: 1px solid rgba(208, 212, 220, 1); | ||||
|         margin-bottom: 8px; | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|  | ||||
|         .left { | ||||
|           display: flex; | ||||
|           justify-content: center; | ||||
|           align-items: center; | ||||
|           width: 30px; | ||||
|  | ||||
|           .svg { | ||||
|             width: 24px; | ||||
|             height: 24px; | ||||
|             vertical-align: middle; | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         .middle { | ||||
|           flex: 1; | ||||
|  | ||||
|           .fileName { | ||||
|             color: #333333; | ||||
|             font-size: 14px; | ||||
|           } | ||||
|  | ||||
|           p:nth-child(2) { | ||||
|             color: #999999; | ||||
|             font-size: 12px; | ||||
|  | ||||
|             span { | ||||
|               padding: 0 4px; | ||||
|             } | ||||
|  | ||||
|             span:nth-child(2) { | ||||
|               border-right: solid 1px #999999; | ||||
|             } | ||||
|  | ||||
|             span:nth-child(3) { | ||||
|               border-right: solid 1px #999999; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         .right { | ||||
|           display: flex; | ||||
|           justify-content: center; | ||||
|           align-items: center; | ||||
|           font-size: 12px; | ||||
|           width: 90px; | ||||
|           text-align: center; | ||||
|  | ||||
|           span { | ||||
|             color: #999999; | ||||
|           } | ||||
|  | ||||
|           i { | ||||
|             display: block; | ||||
|             width: 50px; | ||||
|             color: #5088FF; | ||||
|             font-size: 12px; | ||||
|             cursor: pointer; | ||||
|           } | ||||
|         } | ||||
|  | ||||
|       } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     ::-webkit-scrollbar { | ||||
|       width: 4px; | ||||
|       background-color: #eee; | ||||
|     } | ||||
|  | ||||
|     ::-webkit-scrollbar-thumb { | ||||
|       background-color: #8888; | ||||
|  | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| :deep(.el-drawer__wrapper) { | ||||
|   position: fixed; | ||||
|   width: 100%; | ||||
|   top: 0; | ||||
|   bottom: 0; | ||||
|   right: 0; | ||||
|  | ||||
|   .el-drawer__header > span:focus { | ||||
|     outline: 0 | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										52
									
								
								src/components/headerTools/dvBtn.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/components/headerTools/dvBtn.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| <template> | ||||
|   <section class="dvBtn"> | ||||
|     <el-popover title="数据大屏" width="500" trigger="click"> | ||||
|       <div flex class="wrap"> | ||||
|         <div class="el-button--text pad-r8 pad-b8" style="width: 50%" v-for="op in dvOptions" :key="op.id" v-text="op.name||'无名大屏'" | ||||
|              @click="handleOpenDV(op.id)"/> | ||||
|       </div> | ||||
|       <div slot="reference" class="btn">数据大屏</div> | ||||
|     </el-popover> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import extra from "../../config.json" | ||||
|  | ||||
| export default { | ||||
|   name: "dvBtn", | ||||
|   data() { | ||||
|     return { | ||||
|       dvOptions: [] | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     getDvList() { | ||||
|       this.$request.post("/app/appdiylargescreen/allLargeScreenProjectByPage", null, { | ||||
|         params: {size: 9999, status: 1} | ||||
|       }).then(res => { | ||||
|         if (res?.data) { | ||||
|           this.dvOptions = res.data.records | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     handleOpenDV(id) { | ||||
|       window.open(`${location.origin}${extra.base || ""}/dv?id=${id}#dv`) | ||||
|     }, | ||||
|   }, | ||||
|   created() { | ||||
|     this.getDvList() | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .dvBtn { | ||||
|  | ||||
| } | ||||
|  | ||||
| :deep(.el-button--text) { | ||||
|   cursor: pointer; | ||||
|   user-select: none; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										46
									
								
								src/components/headerTools/linkBtn.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/components/headerTools/linkBtn.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| <template> | ||||
|   <section class="linkBtn"> | ||||
|     <el-dropdown v-if="links.length > 0" @command="handleOpenLink"> | ||||
|       <div class="btn">友情链接</div> | ||||
|       <el-dropdown-menu> | ||||
|         <el-dropdown-item v-for="op in links" :key="op.id" :command="op.url"> | ||||
|           {{ op.title }} | ||||
|         </el-dropdown-item> | ||||
|       </el-dropdown-menu> | ||||
|     </el-dropdown> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   name: "linkBtn", | ||||
|   label: "友情链接", | ||||
|   data() { | ||||
|     return { | ||||
|       links: [] | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     getLinks() { | ||||
|       this.$request.post("/app/appwebnavurl/list", null, { | ||||
|         params: {size: 9999, status: 1} | ||||
|       }).then(res => { | ||||
|         if (res?.data) { | ||||
|           this.links = res.data.records | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     handleOpenLink(url) { | ||||
|       window.open(url) | ||||
|     }, | ||||
|   }, | ||||
|   created() { | ||||
|     this.getLinks() | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .linkBtn { | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										40
									
								
								src/components/mainContent.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/components/mainContent.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| <template> | ||||
|   <section class="mainContent"> | ||||
|     <ai-nav-tab :fixed="homePage" :routes="routes"/> | ||||
|     <router-view v-if="refresh"/> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import {mapState} from "vuex"; | ||||
|  | ||||
| export default { | ||||
|   name: "mainContent", | ||||
|   computed: { | ||||
|     ...mapState(['user', 'homePage']), | ||||
|     routes: v => v.user.info?.menuSet?.map(e => ({...e, label: e.name, name: e.id})) | ||||
|   }, | ||||
|   watch: { | ||||
|     $route(v, old) { | ||||
|       if (v.meta == old.meta && v.fullPath != old.fullPath) { | ||||
|         this.refresh = false | ||||
|         this.$nextTick(() => this.refresh = true) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       refresh: true | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .mainContent { | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										296
									
								
								src/components/sliderNav.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								src/components/sliderNav.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,296 @@ | ||||
| <template> | ||||
|   <section class="sliderNav"> | ||||
|     <el-input class="searchApp" size="small" v-model="searchApp" placeholder="搜索应用" clearable | ||||
|               prefix-icon="iconfont iconSearch" @change="handleSearchApp"/> | ||||
|     <el-scrollbar class="ai-menu"> | ||||
|       <div v-for="(item,i) in navs" :key="i"> | ||||
|         <div class="rootMenu" :class="{isActive:menuPath.includes(item.id)}" @click.stop="openKidMenu(item)"> | ||||
|           <i :class="item.style||'iconfont iconloudongmoxing'"/> | ||||
|           <span class="fill mar-l8" v-text="item.name"/> | ||||
|           <i v-if="hasChildren(item.children)" class="iconfont mar-l8" :class="arrowIcon(item.showChildren)"/> | ||||
|         </div> | ||||
|         <div class="kidMenu" v-if="hasChildren(item.children)&&item.showChildren" @click.stop> | ||||
|           <div v-for="menu in item.children" :key="menu.id"> | ||||
|             <div class="submenu wrap pad-l16 pad-r16" flex v-if="hasChildren(menu.children)"> | ||||
|               <b v-text="menu.name" :class="{menuBtn:menu.type==1,current:menuPath.includes(menu.id)}" | ||||
|                  @click="handleSelect(menu)"/> | ||||
|               <div class="menuBtn" v-for="kid in menu.children" :key="kid.id" v-text="kid.name" :title="kid.name" | ||||
|                    @click="handleSelect(kid)" :class="{current:menuPath.includes(kid.id)}"/> | ||||
|             </div> | ||||
|             <div v-else class="lv2Btn" v-text="menu.name" @click="handleSelect(menu)" :class="{current:menuPath.includes(menu.id)}"/> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="divider"/> | ||||
|     </el-scrollbar> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import {mapGetters} from "vuex"; | ||||
| import qs from "querystring"; | ||||
|  | ||||
| export default { | ||||
|   name: "sliderNav", | ||||
|   data() { | ||||
|     return { | ||||
|       menuList: [], | ||||
|       searchApp: "", | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters(['mods']), | ||||
|     navs: v => v.sortList(v.menuList), | ||||
|     menuPath() { | ||||
|       let paths = [], current = this.mods?.find(e => e.route == this.$route.name) | ||||
|       const findParent = id => { | ||||
|         let menu = this.mods?.find(e => e.id == id) | ||||
|         if (menu) { | ||||
|           paths.push(menu.id) | ||||
|           if (!!menu.parentId) findParent(menu.parentId) | ||||
|         } | ||||
|       } | ||||
|       if (current) { | ||||
|         findParent(current.id) | ||||
|       } | ||||
|       return paths | ||||
|     }, | ||||
|     modList: v => v.mods.filter(e => e.isMenu == 1 || e.type == 0 || (e.level > 1 && e.type == 1)) | ||||
|   }, | ||||
|   methods: { | ||||
|     initMenu(menus = this.modList) { | ||||
|       //isMenu 旧版本判断是否为菜单 type<2 新版本判断是否是菜单或应用 | ||||
|       if (menus?.length > 0) { | ||||
|         this.menuList = this.$arr2tree(menus) | ||||
|         this.menuList = this.menuList.map(e => ({ | ||||
|           ...e, | ||||
|           showChildren: this.menuPath.includes(e.id) || !!this.searchApp | ||||
|         })) | ||||
|       } | ||||
|     }, | ||||
|     openKidMenu(parent) { | ||||
|       if (this.hasChildren(parent.children)) { | ||||
|         parent.showChildren = !parent.showChildren | ||||
|       } else { | ||||
|         this.handleSelect(parent) | ||||
|       } | ||||
|     }, | ||||
|     handleSelect(item) { | ||||
|       if (!item.path) return | ||||
|       if (item.route == this.$route.name) { | ||||
|         //避免同一路由跳转的BUG vue-router官方BUG | ||||
|       } else { | ||||
|         let {route: name, path} = item | ||||
|         if (!name) { | ||||
|           this.$message.warning("暂无应用") | ||||
|         } else { | ||||
|           this.goto({name, query: qs.parse(path.split("?")?.[1])}) | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     goto(item) { | ||||
|       return this.$router.push(item) | ||||
|     }, | ||||
|     sortList(list) { | ||||
|       return list?.sort((a, b) => a.showIndex - b.showIndex) || [] | ||||
|     }, | ||||
|     handleSearchApp() { | ||||
|       let {searchApp} = this | ||||
|       if (searchApp) { | ||||
|         let list = this.modList.filter(e => e.name?.indexOf(searchApp) > -1), map = {} | ||||
|         const findParent = e => { | ||||
|           map[e.id] = e | ||||
|           if (!!e.parentId) { | ||||
|             let parent = this.modList.find(m => m.id == e.parentId) | ||||
|             map[parent.id] = parent | ||||
|             if (!!parent.parentId) { | ||||
|               findParent(parent) | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|         list.forEach(e => findParent(e)) | ||||
|         console.log(map, list) | ||||
|         this.initMenu(Object.values(map)) | ||||
|       } else { | ||||
|         this.initMenu() | ||||
|       } | ||||
|     }, | ||||
|     arrowIcon(v) { | ||||
|       return v ? "iconArrow_Down" : "iconArrow_Right" | ||||
|     }, | ||||
|     hasChildren(arr) { | ||||
|       return arr?.length > 0 | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.initMenu() | ||||
|   } | ||||
| } | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
| .sliderNav { | ||||
|   width: 200px; | ||||
|   height: 100%; | ||||
|   transition: width .1s; | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   flex-direction: column; | ||||
|   border-right: 1px solid #e5e5e5; | ||||
|   flex-shrink: 0; | ||||
|   box-sizing: border-box; | ||||
|   background: #EFF1F4; | ||||
|   color: #222; | ||||
|   position: relative; | ||||
|   user-select: none; | ||||
|  | ||||
|   .kidMenu { | ||||
|     font-size: 13px; | ||||
|  | ||||
|     .rootName { | ||||
|       font-size: 20px; | ||||
|       color: #333; | ||||
|       cursor: default; | ||||
|     } | ||||
|  | ||||
|     .submenu { | ||||
|       margin-top: 8px; | ||||
|       width: 100%; | ||||
|       color: #aaa; | ||||
|  | ||||
|       & > b { | ||||
|         width: 100%; | ||||
|         line-height: 28px; | ||||
|       } | ||||
|  | ||||
|       & > * { | ||||
|         cursor: default; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .menuBtn { | ||||
|       display: block; | ||||
|       width: 50%; | ||||
|       cursor: pointer; | ||||
|       line-height: 32px; | ||||
|       color: #333; | ||||
|       flex-shrink: 0; | ||||
|       white-space: nowrap; | ||||
|       overflow: hidden; | ||||
|       text-overflow: ellipsis; | ||||
|  | ||||
|       &.line { | ||||
|         width: 100%; | ||||
|       } | ||||
|  | ||||
|       &:hover { | ||||
|         color: #26f; | ||||
|       } | ||||
|  | ||||
|       &.current { | ||||
|         color: #26f; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .rootMenu { | ||||
|     padding: 0 16px; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     height: 44px; | ||||
|     cursor: pointer; | ||||
|     box-shadow: 0px -1px 0px 0px #D8DCE3 inset, 0px 1px 0px 0px #FFF inset, -1px 0px 0px 0px #E5E5E5 inset; | ||||
|     font-size: 13px; | ||||
|  | ||||
|     .iconfont { | ||||
|       color: #89B; | ||||
|       font-size: 20px; | ||||
|     } | ||||
|  | ||||
|     &.isActive { | ||||
|       color: #26f; | ||||
|  | ||||
|       .iconfont { | ||||
|         color: #26f !important; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     &:hover { | ||||
|       color: #26f; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   :deep(.ai-menu ){ | ||||
|     padding-left: 0; | ||||
|     flex: 1; | ||||
|     min-height: 0; | ||||
|  | ||||
|     .el-scrollbar__wrap { | ||||
|       overflow-x: auto; | ||||
|     } | ||||
|  | ||||
|     &::-webkit-scrollbar { | ||||
|       display: none; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   :deep(.searchApp ){ | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     height: 44px; | ||||
|     padding: 0 16px; | ||||
|     box-shadow: 0px -1px 0px 0px #E5E5E5 inset; | ||||
|  | ||||
|     .el-input__inner { | ||||
|       border: none; | ||||
|       background: inherit; | ||||
|       padding: 0 28px; | ||||
|     } | ||||
|  | ||||
|     .el-input__prefix { | ||||
|       left: 16px; | ||||
|  | ||||
|       .iconSearch { | ||||
|         font-size: 20px; | ||||
|         width: fit-content; | ||||
|         color: #89B; | ||||
|         line-height: 44px; | ||||
|       } | ||||
|  | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .divider { | ||||
|     color: #aaa; | ||||
|     border-top: 1px solid #ddd; | ||||
|     position: relative; | ||||
|     font-size: 12px; | ||||
|     margin: 16px 16px 32px; | ||||
|  | ||||
|     &:before { | ||||
|       content: "到达底部"; | ||||
|       position: absolute; | ||||
|       top: 50%; | ||||
|       left: 50%; | ||||
|       transform: translate(-50%, -50%); | ||||
|       padding: 0 16px; | ||||
|       background: #EFF1F4; | ||||
|       white-space: nowrap; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .lv2Btn { | ||||
|     height: 44px; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     color: #222; | ||||
|     padding-left: 44px; | ||||
|     cursor: pointer; | ||||
|  | ||||
|     &.current { | ||||
|       background: linear-gradient(90deg, #298BFF 0%, #0C61FF 100%); | ||||
|       box-shadow: inset -1px 0 0 0 #E5E5E5, inset 0 2px 8px 0 #1E4599; | ||||
|       color: #fff; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										33
									
								
								src/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| import Vue from 'vue'; | ||||
| import App from './App.vue'; | ||||
| import ui from 'element-ui'; | ||||
| import router from './utils/router'; | ||||
| import utils from './utils'; | ||||
| import vcUI from 'dui'; | ||||
| import appComp from '@dui/dv'; | ||||
| import store from './utils/store'; | ||||
| import autoRoutes from "./utils/autoRoutes"; | ||||
| import extra from "./config.json" | ||||
| import axios from "./utils/axios"; | ||||
| //import ob from "dui/lib/js/observer" | ||||
| //备注底座信息,勿删 | ||||
| console.log("欢迎使用%s", extra.sysInfo?.name || "构建版本") | ||||
| //new ob() | ||||
| window.Vue = Vue | ||||
| Vue.use(ui); | ||||
| Vue.use(vcUI); | ||||
| Vue.use(appComp); | ||||
| Vue.config.productionTip = false; | ||||
| Vue.prototype.$cdn = "https://cdn.cunwuyun.cn" | ||||
| Vue.prototype.$request = axios | ||||
| Object.keys(utils).map((e) => (Vue.prototype[e] = utils[e])); | ||||
| const loadPage = () => autoRoutes.init().finally(() => new Vue({router, store, render: h => h(App)}).$mount("#app")) | ||||
| let theme = null | ||||
| store.dispatch('getSystem', extra.sysInfo).then(res => { | ||||
|   theme = JSON.parse(res?.colorScheme || null) | ||||
|   return import(`dui/lib/styles/theme.${theme?.web}.scss`).catch(() => 0) | ||||
| }).finally(() => { | ||||
|   Vue.prototype.$theme = theme?.web || "blue" | ||||
|   !!theme?.web && theme?.web != "blue" ? loadPage() : import(`dui/lib/styles/common.scss`).finally(loadPage) | ||||
| }) | ||||
|  | ||||
							
								
								
									
										107
									
								
								src/utils/autoRoutes.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/utils/autoRoutes.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| import {waiting} from "./index"; | ||||
| import router from "./router"; | ||||
| import store from "./store"; | ||||
| import {Message} from "element-ui"; | ||||
| import Vue from "vue"; | ||||
| import extra from "../config.json" | ||||
|  | ||||
| let {state: {user}, commit, dispatch} = store | ||||
| const signOut = () => commit("signOut"), | ||||
|     getUserInfo = () => dispatch("getUserInfo"), | ||||
|     existRoute = route => { | ||||
|       return router.getRoutes()?.find(e => e.name == route?.name || e.path == route?.path) | ||||
|     }, | ||||
|     goto = (route, next) => { | ||||
|       const exist = !!existRoute(route) | ||||
|       return exist ? route.name ? next() : router.replace(route) : | ||||
|           !route.name && route.path == "/" ? router.replace({name: "Home"}).catch(() => 0) : | ||||
|               Message.error("无法找到路由,请联系系统管理员!") | ||||
|     } | ||||
| const loadApps = () => { | ||||
|   //新App的自动化格式 | ||||
|   waiting.init({innerHTML: '应用加载中..'}) | ||||
|   let apps = require.context('../../apps', true, /\.(\/.+)\/App[A-Z][^\/]+\.vue$/, "lazy") | ||||
|   return Promise.all(apps.keys().map(path => apps(path).then(file => { | ||||
|     if (file.default) { | ||||
|       let {name} = file.default | ||||
|       waiting.setContent(`加载${name}...`) | ||||
|       Vue.component(name, file.default) | ||||
|     } else return 0 | ||||
|   }))).then(() => { | ||||
|     waiting.setContent(`正在进入系统...`) | ||||
|     setTimeout(() => waiting.close(), 1000) | ||||
|   }) | ||||
| } | ||||
| const addHome = homePage => { | ||||
|   const component = extra?.homePage || homePage.path | ||||
|   if (extra?.homePage && Vue.component(component)) { | ||||
|     homePage = {...homePage, path: component, component: () => import('../views/mainEntry'), meta: component} | ||||
|   } | ||||
|   router.addRoute('Home', homePage) | ||||
|   router.options.routes[2].children.unshift(homePage) | ||||
|   commit("setHomePage", { | ||||
|     ...homePage, | ||||
|     label: homePage.name, | ||||
|     id: `/v/${component}`, | ||||
|     isMenu: 1, | ||||
|     route: homePage.name, | ||||
|     component, | ||||
|     path: component, | ||||
|   }) | ||||
| } | ||||
| const generateRoutes = (to, from, next) => { | ||||
|   if (router.options.routes[2].children.length > 0) { | ||||
|     goto(to, next) | ||||
|   } else { | ||||
|     Promise.all([getUserInfo(), loadApps()]).then(() => { | ||||
|       //初始化默认工作台 | ||||
|       let homePage = {name: "工作台", path: "console", style: "iconfont iconNav_Dashborad", component: () => import('../views/console')} | ||||
|       addHome(homePage) | ||||
|       const mods = user.info.menuSet?.filter(e => !!e.component)?.map(e => ({route: e.id, ...e})) | ||||
|       mods?.map(({route: name, path, component}) => { | ||||
|         if (!!Vue.component(component) && path && !existRoute({name})) { | ||||
|           let search = path.split("?") | ||||
|           path = search?.[0] || path | ||||
|           const route = {name, path, component: () => import('../views/mainEntry'), meta: component} | ||||
|           router.addRoute('Home', route) | ||||
|           router.options.routes[2].children.push(route) | ||||
|         } | ||||
|       }) | ||||
|       to.name == "Home" ? next({name: homePage.name, replace: true}) : next({...to, replace: true}) | ||||
|     }).then(() => commit("setRoutes", router.options.routes[2].children)) | ||||
|   } | ||||
| } | ||||
| export const routes = [ | ||||
|   {path: "/login", name: "登录", component: () => import('../views/sign')}, | ||||
|   {path: '/dv', name: '数据大屏入口', component: () => import('../views/dvIndex')}, | ||||
|   {path: '/v', name: 'Home', component: () => import('../views/home'), children: []}, | ||||
|   {path: '/', name: "init"}, | ||||
| ] | ||||
| export default { | ||||
|   init: () => { | ||||
|     router.beforeEach((to, from, next) => { | ||||
|       console.log('%s=>%s', from.name, to.name) | ||||
|       if (to.hash == "#pddv") { | ||||
|         const {query} = to | ||||
|         dispatch("getToken", { | ||||
|           username: "18971406276", | ||||
|           password: "admin321!" | ||||
|         }).then(() => next({name: "数据大屏入口", query, hash: "#dv"})) | ||||
|       } else if (["数据大屏入口", "登录"].includes(to.name)) { | ||||
|         next() | ||||
|       } else if (to.hash == "#dv") { | ||||
|         //数据大屏进行的独立页面跳转 | ||||
|         let {query, hash} = to | ||||
|         next({name: "数据大屏入口", query, hash}) | ||||
|       } else if (user.token) { | ||||
|         to.name == "init" ? next({name: "Home"}) : generateRoutes(to, from, next) | ||||
|       } else { | ||||
|         signOut() | ||||
|       } | ||||
|     }) | ||||
|     router.onError(err => { | ||||
|       console.error(err) | ||||
|     }) | ||||
|     return Promise.resolve() | ||||
|   } | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/utils/axios.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/utils/axios.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import instance from 'dui/lib/js/request' | ||||
| import {Message} from 'element-ui' | ||||
| import extra from "../config.json"; | ||||
| import store from "./store" | ||||
|  | ||||
| let baseURLs = { | ||||
|   production: extra.base || "/", | ||||
|   development: extra.baseURL || '/lan', | ||||
| } | ||||
| instance.defaults.baseURL = baseURLs[process.env.NODE_ENV] | ||||
| instance.interceptors.request.use(config => { | ||||
|   config.timeout = 300000 | ||||
|   if (extra?.isSingleService) { | ||||
|     config.url = config.url.replace(/(app|auth|admin)\//, "api/") | ||||
|   } | ||||
|   if (config.url.startsWith("/node")) { | ||||
|     config.baseURL = "/ns" | ||||
|   } | ||||
|   return config | ||||
| }, error => Message.error(error)) | ||||
| instance.interceptors.response.use(res => res, err => { | ||||
|   if (err?.code == 401) { | ||||
|     store.commit('signOut', 1) | ||||
|   } | ||||
| }) | ||||
| export default instance | ||||
							
								
								
									
										100
									
								
								src/utils/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/utils/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| import {MessageBox} from 'element-ui' | ||||
| import tools from 'dui/lib/js/utils' | ||||
| import store from "./store"; | ||||
|  | ||||
| let {state: {user}} = store | ||||
| const addChildParty = (parent, pending) => { | ||||
|   let doBeforeCount = pending.length | ||||
|   parent["children"] = parent["children"] || [] | ||||
|   pending.map((e, index, arr) => { | ||||
|     if (e.partyOrgParentId == parent.partyOrgId) { | ||||
|       parent.children.push(e) | ||||
|       arr.splice(index, 1) | ||||
|       addChildParty(parent, arr) | ||||
|     } | ||||
|   }) | ||||
|   if (parent.children.length == 0) { | ||||
|     delete parent.children | ||||
|   } | ||||
|   if (pending.length > 0 && doBeforeCount > pending.length) { | ||||
|     parent.children.map(c => addChildParty(c, pending)) | ||||
|   } | ||||
| } | ||||
| /** | ||||
|  * 封装提示框 | ||||
|  */ | ||||
|  | ||||
|  | ||||
| const $confirm = (content, options) => { | ||||
|   return MessageBox.confirm(content, { | ||||
|     type: "warning", | ||||
|     confirmButtonText: "确认", | ||||
|     center: true, | ||||
|     title: "提示", | ||||
|     dangerouslyUseHTMLString: true, | ||||
|     ...options | ||||
|   }) | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 封装权限判断方法 | ||||
|  */ | ||||
|  | ||||
|  | ||||
| const $permissions = flag => { | ||||
|   const buttons = user?.info?.buttons | ||||
|   if (buttons) return buttons.some(b => b.id == flag || b.permission == flag) | ||||
|   else return false | ||||
| } | ||||
|  | ||||
| const $decimalCalc = (...arr) => { | ||||
|   //确认提升精度 | ||||
|   let decimalLengthes = arr.map(e => { | ||||
|     let index = ("" + e).indexOf(".") | ||||
|     return ("" + e).length - index | ||||
|   }) | ||||
|   let maxDecimal = Math.max(...decimalLengthes), precision = Math.pow(10, maxDecimal) | ||||
|   //计算 | ||||
|   let intArr = arr.map(e => (Number(e) || 0) * precision) | ||||
|   //返回计算值 | ||||
|   return intArr.reduce((t, a) => t + a) / precision | ||||
| } | ||||
| export const waiting = { | ||||
|   init(ops, count) { | ||||
|     if (document.body) { | ||||
|       let div = document.createElement('div') | ||||
|       div.id = "ai-waiting" | ||||
|       div.innerHTML = "信息正在加载中..." | ||||
|       div.className = "el-loading-mask is-fullscreen" | ||||
|       div.style.zIndex = '202204271710' | ||||
|       div.style.textAlign = 'center' | ||||
|       div.style.lineHeight = '100vh' | ||||
|       div.style.background = 'rgba(255,255,255,.8)' | ||||
|       div.style.backdropFilter = 'blur(6px)' | ||||
|       document.body.appendChild(div) | ||||
|     } else if (count < 10) { | ||||
|       setTimeout(() => this.init(ops, ++count), 500) | ||||
|     } | ||||
|   }, | ||||
|   getDom() { | ||||
|     return document.querySelector('#ai-waiting') | ||||
|   }, | ||||
|   setContent(html) { | ||||
|     let div = this.getDom() | ||||
|     div.innerHTML = html | ||||
|   }, | ||||
|   close() { | ||||
|     let div = this.getDom() | ||||
|     div.parentElement.removeChild(div) | ||||
|   } | ||||
| } | ||||
| export default { | ||||
|   ...tools, | ||||
|   addChildParty, | ||||
|   $confirm, | ||||
|   $permissions, | ||||
|   $decimalCalc, | ||||
|   $waiting: waiting | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										19
									
								
								src/utils/router.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/utils/router.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| import Vue from 'vue' | ||||
| import VueRouter from 'vue-router' | ||||
| import {routes} from "./autoRoutes" | ||||
| import config from "../config.json" | ||||
|  | ||||
| Vue.use(VueRouter) | ||||
| export default new VueRouter({ | ||||
|   base: config.base || '/', | ||||
|   mode: 'history', | ||||
|   hashbang: false, | ||||
|   routes, | ||||
|   scrollBehavior(to) { | ||||
|     if (to.hash) { | ||||
|       return { | ||||
|         selector: to.hash | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }) | ||||
							
								
								
									
										44
									
								
								src/utils/store.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/utils/store.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| import Vue from 'vue' | ||||
| import Vuex from 'vuex' | ||||
| import preState from 'vuex-persistedstate' | ||||
| import * as modules from "dui/lib/js/modules" | ||||
| import axios from "./axios"; | ||||
| import extra from "../config.json" | ||||
|  | ||||
| Vue.use(Vuex) | ||||
|  | ||||
| export default new Vuex.Store({ | ||||
|   state: { | ||||
|     homePage: {} | ||||
|   }, | ||||
|   mutations: { | ||||
|     setHomePage(state, home) { | ||||
|       state.homePage = home | ||||
|     }, | ||||
|     signOut(state, flag) { | ||||
|       const base = extra.base || "" | ||||
|       if (flag) { | ||||
|         state.user.token = null; | ||||
|         state.user.info = {} | ||||
|         sessionStorage.clear(); | ||||
|         location.href = base + '/login' + location.hash; | ||||
|       } else { | ||||
|         axios.delete('/auth/token/logout').then(() => { | ||||
|           state.user.token = null; | ||||
|           sessionStorage.clear(); | ||||
|           state.user.info = {} | ||||
|           location.href = base + '/login'; | ||||
|         }); | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
|   getters: { | ||||
|     //后台数据库中的应用集合,在本工程中不一定存在 | ||||
|     mods: state => [ | ||||
|       state.homePage, | ||||
|       state.user.info?.menuSet?.map(e => ({route: e.id, ...e, label: e.name})) | ||||
|     ].flat().filter(Boolean) | ||||
|   }, | ||||
|   modules, | ||||
|   plugins: [preState()] | ||||
| }) | ||||
							
								
								
									
										31
									
								
								src/views/building.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/views/building.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| <template> | ||||
|   <section class="building"> | ||||
|     <div class="title">功能开发中,敬请期待...</div> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   name: "building" | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .building { | ||||
|   position: relative; | ||||
|   height: 100%; | ||||
|   background-image: url("../assets/building.png"); | ||||
|   background-size: 400px 300px; | ||||
|   background-repeat: no-repeat; | ||||
|   background-position: center, center; | ||||
|  | ||||
|   .title { | ||||
|     position: absolute; | ||||
|     top: 50%; | ||||
|     left: 50%; | ||||
|     transform: translate(-50%, -50%); | ||||
|     margin-top: 150px; | ||||
|     font-weight: bold; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										42
									
								
								src/views/console.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/views/console.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| <template> | ||||
|   <section class="console"> | ||||
|     <div class="consoleBg" v-text="`欢迎使用${system.fullTitle}`"/> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import {mapState} from "vuex"; | ||||
|  | ||||
| export default { | ||||
|   name: "console", | ||||
|   label: "工作台", | ||||
|   computed: { | ||||
|     ...mapState(['sys']), | ||||
|     system: v => v.sys.info || {} | ||||
|   }, | ||||
| } | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
| .console { | ||||
|   height: 100%; | ||||
|  | ||||
|   .consoleBg { | ||||
|     position: absolute; | ||||
|     left: 50%; | ||||
|     top: 50%; | ||||
|     transform: translate(-50%, -50%); | ||||
|     background-image: url("https://cdn.cunwuyun.cn/dvcp/consoleBg.png"); | ||||
|     background-size: 600px 362px; | ||||
|     background-repeat: no-repeat; | ||||
|     background-position: center top; | ||||
|     padding-top: 402px; | ||||
|     font-size: 32px; | ||||
|     font-family: MicrosoftYaHei-Bold, MicrosoftYaHei; | ||||
|     font-weight: bold; | ||||
|     color: #95A1B0; | ||||
|     min-width: 600px; | ||||
|     text-align: center; | ||||
|   } | ||||
|  | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										83
									
								
								src/views/dvIndex.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/views/dvIndex.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| <template> | ||||
|   <section class="dvIndex"> | ||||
|     <ai-dv-wrapper v-model="activeTab" :views="views" :title="title" :theme="theme" v-if="views.length" :background="bgImg" :type="currentStyle" :titleSize="titleSize"> | ||||
|       <ai-dv-viewer urlPrefix="/app" :instance="instance" :dict="dict" :id="currentView.id"/> | ||||
|     </ai-dv-wrapper> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Vue from "vue"; | ||||
| import {waiting} from "../utils"; | ||||
|  | ||||
| export default { | ||||
|   name: "dvIndex", | ||||
|   provide() { | ||||
|     return { | ||||
|       dv: this | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     currentView: v => v.views.find(e => e.id == v.activeTab) || v.views?.[0] || {}, | ||||
|     background: v => JSON.parse(v.currentView.config || null)?.dashboard?.backgroundImage?.[0]?.url || "", | ||||
|     bgImg: v => v.theme == 1 ? 'https://cdn.cunwuyun.cn/dvcp/dv/img/dj-bg.png' : v.background, | ||||
|     theme() { | ||||
|       if (!this.currentView) return '0' | ||||
|       if (!this.currentView.config) return '0' | ||||
|       const config = JSON.parse(this.currentView.config) | ||||
|       if (config.custom) { | ||||
|         return '0' | ||||
|       } | ||||
|       return config.dashboard.theme | ||||
|     }, | ||||
|     currentStyle: v => JSON.parse(v.currentView.config || null)?.dashboard?.style || "black", | ||||
|     titleSize: v => JSON.parse(v.currentView.config || "{}").dashboard?.titleSize | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       instance: this.$request, | ||||
|       dict: this.$dict, | ||||
|       activeTab: 0, | ||||
|       views: [], | ||||
|       title: "", | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     getDvOptions() { | ||||
|       let {id} = this.$route.query | ||||
|       return id ? this.instance.post("/app/appdiylargescreen/queryLargeScreenProjectDetailById", null, { | ||||
|         params: {id, status: 1} | ||||
|       }).then(res => { | ||||
|         if (res?.data) { | ||||
|           this.title = res.data.name | ||||
|           this.views = res.data.lsList?.map(e => ({...e, label: e.title})) | ||||
|         } | ||||
|       }) : Promise.reject() | ||||
|     }, | ||||
|     loadDvs() { | ||||
|       //新App的自动化格式 | ||||
|       waiting.init({innerHTML: '应用加载中..'}) | ||||
|       let apps = require.context('../../apps', true, /\.(\/.+)\/App[A-Z][^\/]+D[Vv]\.vue$/, "lazy") | ||||
|       return Promise.all(apps.keys().map(path => apps(path).then(file => { | ||||
|         if (file.default) { | ||||
|           let {name} = file.default | ||||
|           waiting.setContent(`加载${name}...`) | ||||
|           Vue.component(name, file.default) | ||||
|         } else return 0 | ||||
|       }))).then(() => { | ||||
|         waiting.setContent(`正在进入系统...`) | ||||
|         setTimeout(() => waiting.close(), 1000) | ||||
|       }) | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.loadDvs().then(() => this.getDvOptions()) | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .dvIndex { | ||||
|   height: 100%; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										42
									
								
								src/views/home.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/views/home.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| <template> | ||||
|   <section class="home"> | ||||
|     <header-nav/> | ||||
|     <el-row class="fill" type="flex"> | ||||
|       <slider-nav/> | ||||
|       <main-content class="fill"/> | ||||
|     </el-row> | ||||
|     <ai-copilot v-if="useCopilot" :http="$request"/> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import SliderNav from "../components/sliderNav"; | ||||
| import MainContent from "../components/mainContent"; | ||||
| import HeaderNav from "../components/headerNav"; | ||||
| import configExtra from "../config.json" | ||||
|  | ||||
| export default { | ||||
|   name: 'app', | ||||
|   components: {HeaderNav, MainContent, SliderNav}, | ||||
|   computed: { | ||||
|     useCopilot: () => !!configExtra?.copilot | ||||
|   }, | ||||
|   created() { | ||||
|     import("../../apps/actions").then(extra => { | ||||
|       const actions = extra?.default || {} | ||||
|       this.$store.hotUpdate({actions}) | ||||
|       Object.keys(actions)?.map(action => this.$store.dispatch(action)) | ||||
|     }).catch(() => 0) | ||||
|   }, | ||||
| } | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
| .home { | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   padding-top: 48px; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										55
									
								
								src/views/mainEntry.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/views/mainEntry.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| <template> | ||||
|   <section class="mainEntry fill"> | ||||
|     <ai-detail list v-if="hasIntro"> | ||||
|       <template #content> | ||||
|         <ai-intro :id="currentApp.guideId" :instance="$request" @start="handleStartUse"/> | ||||
|       </template> | ||||
|     </ai-detail> | ||||
|     <component v-else :is="app" :instance="$request" :dict="$dict" :permissions="$permissions" :menuName="currentApp.name"/> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|  | ||||
| import Building from "./building"; | ||||
| import Vue from "vue"; | ||||
| import {mapGetters, mapMutations, mapState} from "vuex"; | ||||
|  | ||||
| export default { | ||||
|   name: "mainEntry", | ||||
|   components: {Building}, | ||||
|   computed: { | ||||
|     ...mapState(['logs']), | ||||
|     ...mapGetters(['mods']), | ||||
|     currentApp() { | ||||
|       const {fullPath, name} = this.$route | ||||
|       return this.mods.find(e => !name ? fullPath.indexOf(e.path) > -1 : name == e.route) || Building | ||||
|     }, | ||||
|     app() { | ||||
|       const {currentApp} = this | ||||
|       return Vue.component(currentApp?.component) ? currentApp.component : Building | ||||
|     }, | ||||
|     hasIntro() { | ||||
|       const {app, currentApp, logs} = this | ||||
|       return !!currentApp.guideId && !logs?.closeIntro?.includes(app) | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapMutations(['addCloseIntro']), | ||||
|     handleStartUse() { | ||||
|       this.addCloseIntro(this.app) | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .mainEntry { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|  | ||||
|   & > * { | ||||
|     height: 100%; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										172
									
								
								src/views/sign.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								src/views/sign.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,172 @@ | ||||
| <template> | ||||
|   <section class="sign"> | ||||
|     <div class="left signLeftBg"> | ||||
|       <el-row type="flex" align="middle"> | ||||
|         <img class="AiIcon" v-if="/[\\\/]/.test(logo.icon)" :src="logo.icon" alt=""/> | ||||
|         <ai-icon v-else type="logo" :icon="logo.icon"/> | ||||
|         <div v-if="logo.text" class="logoText mar-l8" v-text="logo.text"/> | ||||
|       </el-row> | ||||
|       <div class="signLeftContent"> | ||||
|         <div class="titlePane"> | ||||
|           <b v-text="system.name"/> | ||||
|           <div v-text="system.title"/> | ||||
|         </div> | ||||
|         <div class="subTitle" v-for="(t,i) in subTitles" :key="i" v-html="t"/> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="right"> | ||||
|       <div class="projectName mar-b48" :title="system.fullTitle">{{ system.fullTitle }}</div> | ||||
|       <ai-sign v-if="system.edition=='saas'" @login="login" :instance="instance" visible :tps="['wxwork']" :sassLogin="!isDev"/> | ||||
|       <ai-sign v-else isSignIn @login="login" :instance="instance" visible :showScanLogin="system.edition=='standard'||!system.edition"/> | ||||
|       <el-row type="flex" align="middle" class="bottomRecord"> | ||||
|         <div v-if="system.recordDesc" v-text="system.recordDesc"/> | ||||
|         <el-link v-if="system.recordNo" v-text="system.recordNo" :href="system.recordURL"/> | ||||
|         <div v-if="system.ssl" v-html="system.ssl"/> | ||||
|       </el-row> | ||||
|     </div> | ||||
|     <app-licence :instance="instance" ref="licence"/> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import {mapMutations, mapState} from 'vuex' | ||||
| import AppLicence from "../components/AppLicence"; | ||||
|  | ||||
| export default { | ||||
|   name: "sign", | ||||
|   components: {AppLicence}, | ||||
|   computed: { | ||||
|     ...mapState(['user', 'sys']), | ||||
|     instance: v => v.$request, | ||||
|     system: v => v.sys?.info || {}, | ||||
|     subTitles() { | ||||
|       let list = [ | ||||
|         "构建全域数字大脑,助力政府科学决策", | ||||
|         "全域统一应用入口,移动办公高效协同", | ||||
|         "直接触达居民微信,政民互动“零距离”" | ||||
|       ] | ||||
|       return (typeof this.system.desc == "object" ? this.system.desc : JSON.parse(this.system.desc || null)) || list | ||||
|     }, | ||||
|     logo: v => !!v.system.loginLogo ? {icon: v.system.loginLogo, text: v.system.loginLogoText} : {icon: v.system.logo, text: v.system.logoText}, | ||||
|     isDev: () => process.env.NODE_ENV == "development" | ||||
|   }, | ||||
|   created() { | ||||
|     if (this.user.token) { | ||||
|       this.handleGotoHome() | ||||
|     } else { | ||||
|       const {code, auth_code} = this.$route.query | ||||
|       if (code) { | ||||
|         this.toLogin(code) | ||||
|       } else if (auth_code) { | ||||
|         this.tpLogin(auth_code) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapMutations(['setToken']), | ||||
|     login(data) { | ||||
|       if (data.data == '999') { | ||||
|         return this.$refs.licence.show() | ||||
|       } | ||||
|       if (data?.access_token) { | ||||
|         this.setToken([data.token_type, data.access_token].join(" ")) | ||||
|         this.handleGotoHome() | ||||
|       } | ||||
|     }, | ||||
|     handleGotoHome() { | ||||
|       this.$message.success("登录成功!") | ||||
|       if (this.$route.hash == "#dv") { | ||||
|         this.$router.push({name: "数据大屏入口", hash: "#dv"}) | ||||
|       } else { | ||||
|         this.$router.push({name: "Home"}) | ||||
|       } | ||||
|     }, | ||||
|     toLogin(code) { | ||||
|       this.instance.post(`/auth/wechatcp-qr/token`, { | ||||
|         code: code, | ||||
|         type: 'cpuser' | ||||
|       }, { | ||||
|         auth: { | ||||
|           username: 'villcloud', | ||||
|           password: "villcloud" | ||||
|         }, | ||||
|         params: { | ||||
|           grant_type: 'password', | ||||
|           scope: 'server' | ||||
|         } | ||||
|       }).then(this.login) | ||||
|     }, | ||||
|     tpLogin(code) { | ||||
|       this.instance.post("/auth/wechatcp-qr/token", {code}, { | ||||
|         auth: { | ||||
|           username: 'villcloud', | ||||
|           password: "villcloud" | ||||
|         }, | ||||
|         params: { | ||||
|           grant_type: 'password', | ||||
|           scope: 'server' | ||||
|         } | ||||
|       }).then(this.login).catch(() => this.$router.push({})) | ||||
|     } | ||||
|   }, | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .sign { | ||||
|   display: flex; | ||||
|   box-sizing: border-box; | ||||
|   height: 100%; | ||||
|  | ||||
|   .AiIcon { | ||||
|     font-size: 40px; | ||||
|     height: 40px; | ||||
|   } | ||||
|  | ||||
|   .logoText { | ||||
|     font-size: 20px; | ||||
|   } | ||||
|  | ||||
|   :deep(.left ) { | ||||
|     width: 480px; | ||||
|     flex-shrink: 0; | ||||
|     background-size: 100% 100%; | ||||
|     background-repeat: no-repeat; | ||||
|     padding-left: 64px; | ||||
|     padding-top: 40px; | ||||
|     box-sizing: border-box; | ||||
|     color: #fff; | ||||
|     font-size: 16px; | ||||
|  | ||||
|     .iconcunwei1 { | ||||
|       font-size: 36px; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   :deep(.right ) { | ||||
|     flex: 1; | ||||
|     min-width: 0; | ||||
|     background-color: #F6F8FB; | ||||
|     background-image: url("../assets/loginRightTop.png"), url("../assets/loginRightBottom.png"); | ||||
|     background-repeat: no-repeat; | ||||
|     background-position: calc(100% - 80px) 0, calc(100% - 40px) 100%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
|     justify-content: center; | ||||
|  | ||||
|     .bottomRecord { | ||||
|       font-size: 12px; | ||||
|       color: #999; | ||||
|       gap: 16px; | ||||
|       position: fixed; | ||||
|       bottom: 20px; | ||||
|  | ||||
|       .el-link { | ||||
|         font-size: inherit; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
| } | ||||
| </style> | ||||
| @@ -13,15 +13,19 @@ module.exports = { | ||||
|   }, | ||||
|   transpileDependencies: [/dui[\\\/]lib[\\\/]js/], | ||||
|   chainWebpack: (config) => { | ||||
|     config.resolve.alias | ||||
|       .set('@packages', path.resolve(__dirname, 'packages')) | ||||
|       .set('@project', path.resolve(__dirname, 'project')) | ||||
|     config.module | ||||
|       .rule('js') | ||||
|       .include | ||||
|       .add(path.resolve(__dirname, 'packages')) | ||||
|       .add(path.resolve(__dirname, 'components')) | ||||
|       .add(path.resolve(__dirname, 'project')) | ||||
|       .add(path.resolve(__dirname, 'examples')) | ||||
|       .add(path.resolve(__dirname, 'ui')) | ||||
|       .end().use('babel').loader('babel-loader').tap(options => options); | ||||
|       .add(path.resolve(__dirname, 'ui/packages')) | ||||
|       .add(path.resolve(__dirname, 'ui/dv')) | ||||
|       .add(path.resolve(__dirname, 'ui/lib/js')) | ||||
|       .end().use('babel').loader('babel-loader').tap(options => options) | ||||
|     config.plugin("limit").use(require("webpack/lib/optimize/LimitChunkCountPlugin"), [{maxChunks: 20}]).tap(options => options) | ||||
|   }, | ||||
|   devServer: { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user