持续集成分支
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,3 +27,6 @@ yarn-error.log*
|
|||||||
/project/*/dist
|
/project/*/dist
|
||||||
/ui/package-lock.json
|
/ui/package-lock.json
|
||||||
/examples/modules.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
|
email=aixianling@sinoecare.com
|
||||||
always-auth=true
|
|
||||||
package-lock=false
|
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: {
|
computed: {
|
||||||
...mapState(['user', 'apps']),
|
...mapState(['user']),
|
||||||
navs() {
|
navs() {
|
||||||
let reg = new RegExp(`.*${this.searchApp?.replace(/-/g,'')||''}.*`, 'gi')
|
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)) {
|
if (/\/project\//.test(e.path)) {
|
||||||
e.project = process.env.VUE_APP_SCOPE || e.path.replace(/.*project\/([^\/]+)\/.+/, '$1')
|
e.project = process.env.VUE_APP_SCOPE || e.path.replace(/.*project\/([^\/]+)\/.+/, '$1')
|
||||||
} else if (/\/core\//.test(e.path)) {
|
} else if (/\/core\//.test(e.path)) {
|
||||||
@@ -52,9 +52,9 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
menuPath() {
|
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 => {
|
const findParent = name => {
|
||||||
let menu = this.apps?.find(e => e.name == name)
|
let menu = this.$apps?.find(e => e.name == name)
|
||||||
if (menu) {
|
if (menu) {
|
||||||
paths.push(menu.name)
|
paths.push(menu.name)
|
||||||
if (!!menu.parentId) findParent(menu.parentId)
|
if (!!menu.parentId) findParent(menu.parentId)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import ui from 'element-ui';
|
import ui from 'element-ui';
|
||||||
import router from './router/router';
|
import router from './router';
|
||||||
import axios from './router/axios';
|
import axios from './router/axios';
|
||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
import dui from 'dui';
|
import dui from 'dui';
|
||||||
@@ -22,6 +22,7 @@ const app = new Vue({
|
|||||||
store,
|
store,
|
||||||
render: h => h(App)
|
render: h => h(App)
|
||||||
});
|
});
|
||||||
|
|
||||||
let theme = null
|
let theme = null
|
||||||
store.dispatch('getSystem').then(res => {
|
store.dispatch('getSystem').then(res => {
|
||||||
theme = JSON.parse(res?.colorScheme || null)
|
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 Vue from 'vue'
|
||||||
import VueRouter from 'vue-router'
|
import VueRouter from 'vue-router'
|
||||||
import autoRoutes from './autoRoutes'
|
import apps from "./apps";
|
||||||
|
|
||||||
Vue.use(VueRouter)
|
Vue.use(VueRouter)
|
||||||
autoRoutes.init()
|
Vue.prototype.$apps = apps
|
||||||
export default new VueRouter({
|
export default new VueRouter({
|
||||||
mode: 'history',
|
mode: 'history',
|
||||||
hashbang: false,
|
hashbang: false,
|
||||||
routes: autoRoutes.routes(),
|
routes: apps,
|
||||||
scrollBehavior(to) {
|
scrollBehavior(to) {
|
||||||
if (to.hash) {
|
if (to.hash) {
|
||||||
return {
|
return {
|
||||||
@@ -5,22 +5,8 @@ import * as modules from "dui/lib/js/modules"
|
|||||||
import xsActions from "../../project/xiushan/actions"
|
import xsActions from "../../project/xiushan/actions"
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
state: {
|
actions: {...xsActions},
|
||||||
apps: []
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
addApp(state, app) {
|
|
||||||
state.apps.push(app)
|
|
||||||
},
|
|
||||||
cleanApps(state) {
|
|
||||||
state.apps = []
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
...xsActions
|
|
||||||
},
|
|
||||||
modules,
|
modules,
|
||||||
plugins: [preState()]
|
plugins: [preState()]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
"preui": "npm publish -ws||(npm unpublish -f -ws&&npm publish -ws)",
|
"preui": "npm publish -ws||(npm unpublish -f -ws&&npm publish -ws)",
|
||||||
"ui": "npm i dui@latest @dui/dv@latest",
|
"ui": "npm i dui@latest @dui/dv@latest",
|
||||||
"sync": "node bin/appsSync.js",
|
"sync": "node bin/appsSync.js",
|
||||||
"preview": "vue-cli-service serve"
|
"preview": "vue-cli-service serve",
|
||||||
|
"predev": "node bin/scanApps.js"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"ui",
|
"ui",
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
"bin-ace-editor": "^3.2.0",
|
"bin-ace-editor": "^3.2.0",
|
||||||
"dayjs": "^1.8.35",
|
"dayjs": "^1.8.35",
|
||||||
"dui": "^2.0.0",
|
"dui": "^2.0.0",
|
||||||
|
"echarts": "^5.5.1",
|
||||||
"echarts-wordcloud": "^2.0.0",
|
"echarts-wordcloud": "^2.0.0",
|
||||||
"hash.js": "^1.1.7",
|
"hash.js": "^1.1.7",
|
||||||
"html2canvas": "^1.4.1",
|
"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/],
|
transpileDependencies: [/dui[\\\/]lib[\\\/]js/],
|
||||||
chainWebpack: (config) => {
|
chainWebpack: (config) => {
|
||||||
|
config.resolve.alias
|
||||||
|
.set('@packages', path.resolve(__dirname, 'packages'))
|
||||||
|
.set('@project', path.resolve(__dirname, 'project'))
|
||||||
config.module
|
config.module
|
||||||
.rule('js')
|
.rule('js')
|
||||||
.include
|
.include
|
||||||
.add(path.resolve(__dirname, 'packages'))
|
.add(path.resolve(__dirname, 'packages'))
|
||||||
.add(path.resolve(__dirname, 'components'))
|
|
||||||
.add(path.resolve(__dirname, 'project'))
|
.add(path.resolve(__dirname, 'project'))
|
||||||
.add(path.resolve(__dirname, 'examples'))
|
.add(path.resolve(__dirname, 'examples'))
|
||||||
.add(path.resolve(__dirname, 'ui'))
|
.add(path.resolve(__dirname, 'ui/packages'))
|
||||||
.end().use('babel').loader('babel-loader').tap(options => options);
|
.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)
|
config.plugin("limit").use(require("webpack/lib/optimize/LimitChunkCountPlugin"), [{maxChunks: 20}]).tap(options => options)
|
||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
|
|||||||
Reference in New Issue
Block a user