12 Commits

Author SHA1 Message Date
aixianling
50e56a9812 更换构建工具rsbuild 2024-10-23 17:06:18 +08:00
aixianling
b64b102682 Merge remote-tracking branch 'origin/build' into devops 2024-10-18 09:31:36 +08:00
aixianling
576553e962 为了保证jenkins的顺利执行 2024-10-17 17:19:49 +08:00
aixianling
ff5e2d4fe2 为了保证jenkins的顺利执行 2024-10-17 15:54:07 +08:00
aixianling
043aaf8399 在仓库制作打包脚本 2024-10-12 16:17:37 +08:00
aixianling
a5e0f3a6c5 优化代码 2024-10-12 14:27:26 +08:00
aixianling
5a2762a7c6 导航条修复一下 2024-10-11 12:04:44 +08:00
aixianling
e2e957880b 针对oms专项进行启动和页面优化 2024-10-11 10:10:06 +08:00
aixianling
104f5d1049 修复接口,模块引用等一些问题 2024-10-10 17:35:04 +08:00
aixianling
b3f11698f2 清理一下工程 2024-10-10 16:29:07 +08:00
aixianling
285e242e9e Merge branch 'build' into devops 2024-10-10 16:21:44 +08:00
aixianling
00b2dbb4a4 持续集成分支 2024-10-10 16:04:11 +08:00
59 changed files with 2687 additions and 559 deletions

View File

@@ -1,2 +0,0 @@
VUE_APP_SCOPE=ai
VUE_APP_API=http://192.168.1.87:9000

View File

@@ -1,5 +0,0 @@
VUE_APP_SCOPE=biaopin
#VUE_APP_API=https://web.fdfengshou.cn/
#VUE_APP_API=http://192.168.1.87:9000/
VUE_APP_API=https://www.wyzzb.com
#VUE_APP_API=http://test87web.cunwuyun.cn/

View File

@@ -1,2 +0,0 @@
VUE_APP_SCOPE=dv
VUE_APP_API=http://192.168.1.87:9000

View File

@@ -1,4 +0,0 @@
VUE_APP_SCOPE=fengdu
#VUE_APP_API=https://web.fdfengshou.cn/
VUE_APP_API=http://192.168.1.87:9000/
#VUE_APP_API=http://test87web.cunwuyun.cn/

3
.gitignore vendored
View File

@@ -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
View File

@@ -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/build.js Normal file
View File

@@ -0,0 +1,36 @@
const axios = require('axios')
const {fsExtra, copyFiles} = require("./tools");
const getBuildConfig = id => {
axios.post('http://192.168.1.87:12525/node/custom/detail', null, {params: {id}}).then(res => {
if (res?.data) {
const config = res.data.data
fsExtra.outputJson('src/config.json', config.extra)
createPages(config)
}
})
}
const createPages = (config = {}) => {
fsExtra.emptyDir("src/apps", err => {
if (!err) {
const {customPath, appList} = config
const stdApps = {}
appList.filter(e => !/project/.test(e.id))?.forEach(e => {
const paths = e.libPath.split('/').filter(Boolean) || []
paths.pop()
stdApps[paths.join("/")] = 1
})
Promise.all([
copyFiles("src/apps/core", "packages/core"),
copyFiles("src/apps/custom", `project/${customPath}`),
...Object.keys(stdApps).map(e => copyFiles(`src/apps/${e.replace(/^packages[\\\/]/, '')}`, e)),
]).then(() => fsExtra.ensureFile("src/apps/actions.js"))
}
})
}
const start = () => {
const buildId = process.argv[2] || 'f670cc46-7cf7-4a0f-86ee-3077044c0b17'
getBuildConfig(buildId)
}
start()

View File

@@ -1,33 +0,0 @@
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: ['.', paths.slice(1)].flat().join("/"),
name
})
}
}
const start = () => {
chalkTag.info("开始扫描库工程...")
const list = []
Promise.all([
findApp('packages', app => getAppInfo(app, list)),
findApp('project', app => getAppInfo(app, list)),
]).then(() => {
fsExtra.outputJson('examples/modules.json', {apps: list})
chalkTag.done("扫描完毕")
})
}
start()

36
bin/scanApps.js Normal file
View 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 {VUE_APP_SCOPE} = process.env
const list = []
let scanScope = ['packages', 'project']
if (VUE_APP_SCOPE) scanScope = [`project/${VUE_APP_SCOPE}`]
Promise.all(scanScope.map(e => findApp(e, 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()

View File

@@ -9,13 +9,13 @@
</template>
</header-nav>
<ai-dv-wrapper class="fill" v-if="dvDev">
<router-view/>
<router-view v-bind="commonAttrs"/>
</ai-dv-wrapper>
<el-row v-else-if="showTools" class="fill mar-t48" type="flex">
<slider-nav/>
<main-content class="fill"/>
<main-content class="fill" :commonAttrs="commonAttrs"/>
</el-row>
<router-view class="fill" v-else/>
<router-view class="fill" v-else v-bind="commonAttrs"/>
<div v-if="dialog" class="sign-box">
<ai-sign style="margin: auto" :instance="$request" :action="{login}"
visible @login="getToken" :showScanLogin="false"/>
@@ -30,7 +30,7 @@ import MainContent from "./components/mainContent";
import HeaderNav from "./components/headerNav";
import {mapActions, mapMutations, mapState} from "vuex";
import Mock from "./components/mock";
import AiDvWrapper from "@dui/dv/layout/AiDvWrapper/AiDvWrapper.vue";
import AiDvWrapper from "dui/dv/layout/AiDvWrapper/AiDvWrapper.vue";
export default {
name: 'app',
@@ -42,6 +42,13 @@ export default {
/project\/sass/g.test(location.pathname) && (url += "?corpId=ww596787bb70f08288")
return url
},
commonAttrs() {
return {
instance: this.$request,
dict: this.$dict,
permissions: this.$permissions
}
}
},
data() {
return {
@@ -76,7 +83,8 @@ export default {
}
},
created() {
wx = jWeixin
const {jWeixin} = window
window.wx = jWeixin
if (this.user.token) this.getUserInfo().finally(() => {
if (/^\/project\/xiushan/.test(location.pathname)) {
this.getFinanceUser()

View File

@@ -1,8 +1,8 @@
<template>
<section class="mainContent">
<ai-nav-tab :routes="apps"/>
<ai-nav-tab :routes="$apps"/>
<div class="fill">
<router-view/>
<router-view v-bind="$attrs.commonAttrs"/>
<ai-empty v-if="isHome">欢迎使用村微产品库</ai-empty>
</div>
</section>
@@ -10,13 +10,11 @@
<script>
import AiNavTab from "dui/packages/basic/AiNavTab";
import {mapState} from "vuex"
export default {
name: "mainContent",
components: {AiNavTab},
computed: {
...mapState(['apps']),
isHome: v => v.$route.path == '/',
},
}

View File

@@ -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)

View File

@@ -1,13 +1,13 @@
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';
import dui from 'dui/packages';
import store from './store';
import dataV from '@jiaminghi/data-view';
import dvui from '@dui/dv'
import dvui from 'dui/dv'
Vue.use(dataV)
Vue.use(ui);
@@ -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)

View File

@@ -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)
}
}

View File

@@ -1,5 +1,5 @@
import {Message} from 'element-ui'
import instance from '../../ui/lib/js/request'
import instance from 'dui/lib/js/request'
let baseURLs = {
production: "/",
@@ -8,31 +8,7 @@ let baseURLs = {
instance.defaults.baseURL = baseURLs[process.env.NODE_ENV]
instance.interceptors.request.use(config => {
if (config.url.startsWith("/node")) {
config.baseURL = "/ns"
} else if (config.url.startsWith("/sse")) {
config.baseURL = "/"
} else if (/\/project\/activeAnalysis/.test(location.pathname)) {
config.baseURL = "/analysis"
} else if (/\/project\/beta/.test(location.pathname)) {
config.baseURL = "/wg"
} else if (/\/project\/sass/.test(location.pathname)) {
config.baseURL = "/saas"
} else if (/\/tianfuxing/.test(location.pathname)) {
config.baseURL = "/tfx"
} else if (/\/qianxinan/.test(location.pathname)) {
// config.baseURL = "/qxn"
} else if (/\/xiushan/.test(location.pathname)) {
config.baseURL = "/xsjr"
} else if (/project\/oms/.test(location.pathname)) {
config.baseURL = "/omsapi"
} else if (/#url-/.test(location.hash)) {
config.baseURL = location.hash.replace(/#url-/, '/')
}
if (["/xsjr", "/tfx", "/omsapi"].includes(config.baseURL)) {
config.url = config.url.replace(/(app|auth|admin|api)\//, "api/")
}
if (['/qxn', '/analysis'].includes(config.baseURL)) {
config.url = config.url.replace(/(app|auth|admin)\//, "api/")
config.url = "/ns" + config.url
}
if (process.env.VUE_APP_IS_SIMPLE_SERVER == 1) {
config.url = config.url.replace(/(app|auth|admin)\//, "api/")

View File

@@ -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 {

View File

@@ -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()]
})

View File

@@ -1,46 +0,0 @@
<template>
<section class="appEntry">
<component v-if="app" :is="app" ref="currentPage" :instance="$request" :dict="$dict" :permissions="$permissions"/>
<ai-empty v-else>无法找到应用文件</ai-empty>
</section>
</template>
<script>
import {mapState} from "vuex";
import Vue from "vue";
export default {
name: "appEntry",
label: "应用库-应用",
computed: {
...mapState(['apps']),
app() {
const app = this.apps.find(e => e.name == this.$route.name)
return app.esm ?? ""
}
},
mounted() {
this.$vm.$on("mock", v => {
if (!!this.$refs.currentPage.$children?.[0]?.form) {
this.$refs.currentPage.$children[0].form = v
this.$refs.currentPage.$children[0].$forceUpdate()
}
})
}
}
</script>
<style lang="scss" scoped>
.appEntry {
width: 100%;
flex: 1;
min-width: 0;
min-height: 0;
height: 100%;
& > * {
height: 100%;
}
}
</style>

View File

@@ -1,47 +1,38 @@
{
"name": "dvcp-web-apps",
"version": "3.0.0",
"version": "4.0.0",
"private": false,
"author": "kubbo",
"scripts": {
"dev": "vue-cli-service serve examples/main.js",
"build": "vue-cli-service build",
"dev:ai": "vue-cli-service serve examples/main.js --mode ai",
"dev:oms": "vue-cli-service serve examples/main.js --mode oms",
"dev:biaopin": "vue-cli-service serve examples/main.js --mode biaopin",
"dev:dv": "vue-cli-service serve examples/main.js --mode dv",
"dev:fengdu": "vue-cli-service serve examples/main.js --mode fengdu",
"lib": "npm publish||(npm unpublish -f&&npm publish)",
"preui": "npm publish -ws||(npm unpublish -f -ws&&npm publish -ws)",
"ui": "npm i dui@latest @dui/dv@latest",
"dev": "rsbuild dev",
"build": "rsbuild build",
"oms": "rsbuild dev --mode oms",
"sync": "node bin/appsSync.js",
"preview": "vue-cli-service serve"
"preview": "node bin/build.js && rsbuild dev",
"predev": "node bin/scanApps.js",
"preoms": "dotenv -e .env.oms node bin/scanApps.js"
},
"workspaces": [
"ui",
"ui/dv"
],
"files": [
"packages",
"project"
],
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@dui/dv": "^1.0.0",
"@ckeditor/ckeditor5-vue2": "^3.0.1",
"@jiaminghi/data-view": "^2.10.0",
"@logicflow/core": "^1.2.1",
"bin-ace-editor": "^3.2.0",
"crypto-js": "^4.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",
"jspdf": "^2.5.2",
"mp4box": "^0.4.1",
"print-js": "^1.0.63",
"serialize-javascript": "^6.0.0",
"sortablejs": "^1.12.0",
"vue-carousel": "^0.18.0",
"vue-cropper": "^0.6.5",
"vue-draggable-resizable": "^2.3.0",
"vue-qr": "^4.0.9",
"vue-ruler-tool": "^1.2.4",
"vue-style-loader": "^4.1.3",
"vuedraggable": "^2.24.3"
@@ -50,15 +41,18 @@
"@babel/plugin-proposal-logical-assignment-operators": "^7.10.4",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4",
"@babel/plugin-proposal-optional-chaining": "^7.10.4",
"@vue/cli-plugin-babel": "^3.6.0",
"@vue/cli-plugin-eslint": "^3.6.0",
"@vue/cli-service": "^3.6.0",
"@rsbuild/core": "^1.0.17",
"@rsbuild/plugin-babel": "^1.0.2",
"@rsbuild/plugin-sass": "^1.0.4",
"@rsbuild/plugin-vue2": "^1.0.1",
"autoprefixer": "^10.4.20",
"axios": "^0.19.2",
"babel-eslint": "^10.1.0",
"core-js": "^2.6.11",
"dotenv-cli": "^7.4.2",
"element-ui": "^2.15.9",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"fs-extra": "^11.2.0",
"image-webpack-loader": "^6.0.0",
"inquirer": "^6.5.2",
"mockjs": "^1.1.0",

View File

@@ -168,7 +168,7 @@ import {monitorTypes} from "../config";
import ConfigItem from "./configItem.vue";
import DatasourcePicker from "./datasourcePicker.vue";
import AiSelect from "dui/packages/basic/AiSelect.vue";
import {DvCompData} from "@dui/dv";
import {DvCompData} from "dui/dv";
export default {
name: 'dataConfig',

View File

@@ -4,7 +4,7 @@ import ConfigItem from "./configItem";
import ChartPicker from "./chartPicker";
import JsonEditor from "./jsonEditor";
import {layers, monitorTypes} from "../config";
import AiDvSummary from "@dui/dv/layout/AiDvSummary/AiDvSummary";
import AiDvSummary from "dui/dv/layout/AiDvSummary/AiDvSummary";
export default {
name: "configExtra",

View File

@@ -34,7 +34,7 @@ import 'brace/snippets/json';
import 'brace/theme/github';
import 'brace/theme/monokai';
import JsonEditor from "./jsonEditor.vue";
import {DvCompData} from "@dui/dv";
import {DvCompData} from "dui/dv";
export default {
name: "datasourcePicker",

View File

@@ -14,7 +14,7 @@
<el-row type="flex">
<div class="fill">
<el-form-item label="系统类型" prop="type">
<ai-select v-model="form.type" :selectList="dict.getDict('systemType')" @change="form.apps = [],handleSysTypeChange(form.type)"/>
<ai-select v-model="form.type" :selectList="dict.getDict('systemType')" @change="form.apps = []"/>
</el-form-item>
<el-form-item label="更新项目路径" prop="dist">
<el-input v-model="form.dist" placeholder="常填写nginx路径,下载包从这里取" clearable/>
@@ -38,173 +38,7 @@
<ai-empty v-else>请先选择系统类型</ai-empty>
</template>
</ai-card>
<ai-card title="扩展设置">
<template #right>
<template v-if="form.type=='web'">
<ai-dialog-btn text="设置系统信息" dialogTitle="系统信息">
<el-form size="small" label-width="140px">
<el-form-item label="系统标题">
<el-input v-model="form.sysInfo.fullTitle" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="favicon">
<el-input v-model="form.sysInfo.favicon" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="logo">
<el-row type="flex">
<el-input v-model="form.sysInfo.logo" placeholder="请输入..." clearable/>
<el-input class="mar-l10" v-model="form.sysInfo.logoText" placeholder="logo文字">
<template #prepend>logo文字</template>
</el-input>
</el-row>
</el-form-item>
<el-form-item label="登录页">
<el-row type="flex" class="mar-b10">
<el-input v-model="form.sysInfo.loginLogo" placeholder="登录页左上角logo,带/代表图片" clearable>
<template #prepend>左页首logo</template>
</el-input>
<el-input class="mar-l10" v-model="form.sysInfo.loginLogoText" placeholder="logo文字" clearable>
<template #prepend>logo文字</template>
</el-input>
</el-row>
<el-row type="flex">
<el-input v-model="form.sysInfo.name" placeholder="左上角标题" clearable>
<template #prepend>左上角标题</template>
</el-input>
<el-input class="mar-l10" v-model="form.sysInfo.title" placeholder="左上角副标题" clearable>
<template #prepend>左上角副标题</template>
</el-input>
</el-row>
<el-input class="mar-t10" type="textarea" rows="5" v-model="form.sysInfo.desc" placeholder="副标题" clearable/>
</el-form-item>
<el-row type="flex">
<div class="fill">
<el-form-item label="版权所有">
<el-input v-model="form.sysInfo.recordDesc" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="备案号">
<el-input v-model="form.sysInfo.recordNo" placeholder="请输入..." clearable/>
</el-form-item>
</div>
<div class="fill">
<el-form-item label="框架版本">
<!--edition 版本标准版standard上架版saas 简易版(不带扫码):simple -->
<el-input v-model="form.sysInfo.edition" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="备案跳转链接">
<el-input v-model="form.sysInfo.recordURL" placeholder="请输入..." clearable/>
</el-form-item>
</div>
</el-row>
<el-form-item label="可信证书">
<el-input type="textarea" v-model="form.sysInfo.ssl" placeholder="请输入可信证书的html代码" clearable rows="5"/>
</el-form-item>
</el-form>
</ai-dialog-btn>
</template>
</template>
<template #content>
<template v-if="form.type=='mp'">
<el-form-item label="小程序AppId">
<el-input v-model="form.appId" clearable placeholder="小程序appId"/>
</el-form-item>
<el-form-item label="半屏小程序">
<el-input v-model="form.embeddedAppIdList" clearable placeholder="如果有多个,请用英文','分割"/>
</el-form-item>
<el-form-item label="接口是否单服务">
<el-checkbox v-model="form.isSingleService"/>
</el-form-item>
<el-form-item label="引导页">
<el-input v-model="form.guide" placeholder="带'/'会被认为是输入的页面路径" clearable/>
</el-form-item>
<ai-title title="底部导航栏">
<el-button type="text" slot="rightBtn" icon="iconfont iconAdd" @click="tabBar.list.push({})">添加</el-button>
</ai-title>
<ai-table :tableData="tabBar.list" :colConfigs="colConfigs" tableSize="mini" :isShowPagination="false" border ref="TabBar">
<el-table-column slot="options" label="操作" width="260" align="center">
<template slot-scope="{row,$index}">
<el-row type="flex" class="tabBarOptions">
<ai-dialog-btn text="更换" dialogTitle="选择应用">
<ai-lib-table :meta="appList" v-model="row.id" @select="v=>handleTabbarChange(row,v)" :isShowPagination="false" v-bind="$props"
:border="false"/>
</ai-dialog-btn>
<ai-dialog-btn text="编辑" dialogTitle="编辑导航栏" width="600px">
<el-form-item label="名称">
<el-input v-model="row.text" placeholder="请输入" clearable/>
</el-form-item>
<el-form-item label="默认图标">
<el-input v-model="row.iconPath" placeholder="请输入" clearable/>
</el-form-item>
<el-form-item label="选中图标">
<el-input v-model="row.selectedIconPath" placeholder="请输入" clearable/>
</el-form-item>
</ai-dialog-btn>
<el-button type="text" @click="handleTabbarDelete($index)">删除</el-button>
<el-button type="text" @click="handleTabbarPosition($index,-1)" v-if="$index>0">上移</el-button>
<el-button type="text" @click="handleTabbarPosition($index,1)" v-if="$index<tabBar.list.length-1">下移</el-button>
</el-row>
</template>
</el-table-column>
</ai-table>
</template>
<template v-else-if="form.type=='wxwork'">
<el-row type="flex">
<div class="fill">
<el-form-item label="接口是否单服务">
<el-checkbox v-model="form.isSingleService"/>
</el-form-item>
<el-form-item label="是否启用水印">
<el-checkbox v-model="form.waterMarker"/>
</el-form-item>
<el-form-item label="自定义登录">
<el-checkbox v-model="form.customLogin"/>
</el-form-item>
</div>
<div class="fill">
<el-form-item label="域名根目录(baseURL)">
<el-input v-model="form.base" clearable placeholder="域名根目录"/>
</el-form-item>
<el-form-item label="默认首页">
<el-input v-model="form.homePage" clearable placeholder="填写应用的文件名"/>
</el-form-item>
<el-form-item label="开启百度流量">
<el-checkbox v-model="form.hmt"/>
</el-form-item>
</div>
</el-row>
</template>
<template v-else-if="form.type=='web'">
<el-form-item label="头部导航工具栏">
<el-checkbox v-model="form.downloadCenter">下载中心</el-checkbox>
<el-checkbox v-model="form.dv">数据大屏</el-checkbox>
<el-checkbox v-model="form.showTool">显示/隐藏导航栏</el-checkbox>
<el-checkbox v-model="form.helpDoc">帮助文档</el-checkbox>
<el-checkbox v-model="form.customerService">智能客服</el-checkbox>
<el-checkbox v-model="form.appQRCode">手机APP</el-checkbox>
</el-form-item>
<el-row type="flex">
<div class="fill">
<el-form-item label="接口是否单服务">
<el-checkbox v-model="form.isSingleService"/>
</el-form-item>
<el-form-item label="默认首页">
<el-input v-model="form.homePage" clearable placeholder="填写应用的文件名"/>
</el-form-item>
<el-form-item label="是否加载AI助手">
<el-checkbox v-model="form.copilot"/>
</el-form-item>
</div>
<div class="fill">
<el-form-item label="域名根目录">
<el-input v-model="form.base" clearable placeholder="填写域名根目录(baseURL)"/>
</el-form-item>
<el-form-item label="开启百度流量">
<el-checkbox v-model="form.hmt"/>
</el-form-item>
</div>
</el-row>
</template>
</template>
</ai-card>
<component class="extraConfig" title="扩展设置" :is="extraConfig" v-model="form.extra"/>
</el-form>
</el-tab-pane>
<el-tab-pane label="方案应用" lazy>
@@ -233,6 +67,9 @@
<script>
import {mapActions} from "vuex"
import AiLibTable from "./AiLibTable";
import webConfig from "./config/webConfig.vue";
import wxcpConfig from "./config/wxcpConfig.vue";
import wxmpConfig from "./config/wxmpConfig.vue";
export default {
name: "add",
@@ -256,6 +93,11 @@ export default {
return e
}).filter(e => e.project != "core") || []
},
extraConfig: v => ({
web: webConfig,
wxwork: wxcpConfig,
mp: wxmpConfig
}[v.form.type])
},
data() {
return {
@@ -265,12 +107,6 @@ export default {
type: {required: true, message: "请选择"},
// customPath: {required: true, message: "请输入"},
},
colConfigs: [
{prop: 'text', label: "名称", width: 120},
{prop: 'pagePath', label: "应用路径"},
{prop: 'iconPath', label: "默认图标"},
{prop: 'selectedIconPath', label: "选中图标"},
],
appListConfigs: [
{prop: 'label', label: "应用名称", render: (h, {row}) => h(row.tabbar ? 'b' : 'p', row.label + ` ${row.tabbar ? '(底部导航栏)' : ''}`)},
{prop: 'project', label: "项目/框架"},
@@ -302,7 +138,6 @@ export default {
}).then(res => {
if (res?.data) {
this.form = {...this.form, ...res.data}
this.handleSysTypeChange(this.form.type, this.form.extra)
}
})
},
@@ -313,37 +148,9 @@ export default {
submit() {
this.$refs.AddForm.validate(v => {
if (v) {
const {
tabBar,
form: {
type,
appId,
embeddedAppIdList,
isSingleService,
homePage,
hmt,
dv,
downloadCenter,
showTool,
helpDoc,
customerService,
sysInfo,
appQRCode,
customLogin,
base,
guide,
copilot
}
} = this
if (type == 'mp') {
this.form.extra = {tabBar, appId, embeddedAppIdList, isSingleService, guide}
} else if (type == 'web') {
const {desc} = sysInfo
sysInfo.desc = JSON.parse(desc || null)
sysInfo.ssl = sysInfo.ssl?.replace(/"/g, "'")
this.form.extra = {isSingleService, homePage, hmt, dv, downloadCenter, showTool, helpDoc, customerService, sysInfo, appQRCode, base, copilot}
} else if (type == 'wxwork') {
this.form.extra = {isSingleService, homePage, hmt, customLogin, base}
if (this.form.type == 'web') {
this.form.extra.sysInfo.desc = JSON.parse(this.form.extra.sysInfo.desc || null)
this.form.extra.sysInfo.ssl = this.form.extra.sysInfo.ssl?.replace(/"/g, "'")
}
this.instance.post("/node/custom/addOrUpdate", this.form).then(res => {
if (res?.code == 0) {
@@ -354,34 +161,11 @@ export default {
}
})
},
handleSysTypeChange(v, data = {}) {
let values = this.$copy(data)
if (v == 'mp') {
if (values?.tabBar) {
this.tabBar = values.tabBar || this.tabBar
delete values.tabBar
}
} else if (v == 'web') {
if (values?.sysInfo?.desc) values.sysInfo.desc = JSON.stringify(values.sysInfo.desc)
}
Object.keys(values).map(e => this.$set(this.form, e, values[e]))
},
handleTabbarChange(row, {name, label}) {
row.text = label
row.pagePath = `pages/${name}/${name}`
},
handleTabbarDelete(i) {
this.tabBar.list?.splice(i, 1)
},
handleAppEdit(row) {
const i = this.form.appList.findIndex(e => e.id == row.id)
this.form.appList.splice(i, 1, row)
},
handleTabbarPosition(i, offset) {
const row = this.tabBar.list[i]
this.tabBar.list.splice(i, 1, this.tabBar.list[i + offset])
this.tabBar.list.splice(i + offset, 1, row)
}
},
created() {
this.getDetail()
@@ -404,5 +188,16 @@ export default {
margin-left: 0;
}
}
:deep(.extraConfig) {
.ai-card__body {
display: grid;
grid-template-columns: 1fr 1fr;
.row {
grid-column: span 2;
}
}
}
}
</style>

View File

@@ -0,0 +1,117 @@
<script>
export default {
name: "webConfig",
model: {
prop: "form",
event: "input"
},
props: {
form: Object,
title: String
},
watch: {
form: {
handler() {
this.$emit("input", this.form)
},
deep: true
}
}
}
</script>
<template>
<ai-card :title="title" class="webConfig">
<template #right>
<ai-dialog-btn text="设置系统信息" dialogTitle="系统信息">
<el-form size="small" label-width="140px">
<el-form-item label="系统标题">
<el-input v-model="form.sysInfo.fullTitle" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="favicon">
<el-input v-model="form.sysInfo.favicon" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="logo">
<el-row type="flex">
<el-input v-model="form.sysInfo.logo" placeholder="请输入..." clearable/>
<el-input class="mar-l10" v-model="form.sysInfo.logoText" placeholder="logo文字">
<template #prepend>logo文字</template>
</el-input>
</el-row>
</el-form-item>
<el-form-item label="登录页">
<el-row type="flex" class="mar-b10">
<el-input v-model="form.sysInfo.loginLogo" placeholder="登录页左上角logo,带/代表图片" clearable>
<template #prepend>左页首logo</template>
</el-input>
<el-input class="mar-l10" v-model="form.sysInfo.loginLogoText" placeholder="logo文字" clearable>
<template #prepend>logo文字</template>
</el-input>
</el-row>
<el-row type="flex">
<el-input v-model="form.sysInfo.name" placeholder="左上角标题" clearable>
<template #prepend>左上角标题</template>
</el-input>
<el-input class="mar-l10" v-model="form.sysInfo.title" placeholder="左上角副标题" clearable>
<template #prepend>左上角副标题</template>
</el-input>
</el-row>
<el-input class="mar-t10" type="textarea" rows="5" v-model="form.sysInfo.desc" placeholder="副标题" clearable/>
</el-form-item>
<el-row type="flex">
<div class="fill">
<el-form-item label="版权所有">
<el-input v-model="form.sysInfo.recordDesc" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="备案号">
<el-input v-model="form.sysInfo.recordNo" placeholder="请输入..." clearable/>
</el-form-item>
</div>
<div class="fill">
<el-form-item label="框架版本">
<!--edition 版本标准版standard上架版saas 简易版(不带扫码):simple -->
<el-input v-model="form.sysInfo.edition" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="备案跳转链接">
<el-input v-model="form.sysInfo.recordURL" placeholder="请输入..." clearable/>
</el-form-item>
</div>
</el-row>
<el-form-item label="可信证书">
<el-input type="textarea" v-model="form.sysInfo.ssl" placeholder="请输入可信证书的html代码" clearable rows="5"/>
</el-form-item>
</el-form>
</ai-dialog-btn>
</template>
<template #content>
<el-form-item label="头部导航工具栏" class="row">
<el-checkbox v-model="form.downloadCenter">下载中心</el-checkbox>
<el-checkbox v-model="form.dv">数据大屏</el-checkbox>
<el-checkbox v-model="form.showTool">显示/隐藏导航栏</el-checkbox>
<el-checkbox v-model="form.helpDoc">帮助文档</el-checkbox>
<el-checkbox v-model="form.customerService">智能客服</el-checkbox>
<el-checkbox v-model="form.appQRCode">手机APP</el-checkbox>
</el-form-item>
<el-form-item label="接口是否单服务">
<el-checkbox v-model="form.isSingleService"/>
</el-form-item>
<el-form-item label="域名根目录">
<el-input v-model="form.base" clearable placeholder="填写域名根目录(baseURL)"/>
</el-form-item>
<el-form-item label="默认首页">
<el-input v-model="form.homePage" clearable placeholder="填写应用的文件名"/>
</el-form-item>
<el-form-item label="开启百度流量">
<el-checkbox v-model="form.hmt"/>
</el-form-item>
<el-form-item label="是否加载AI助手">
<el-checkbox v-model="form.copilot"/>
</el-form-item>
</template>
</ai-card>
</template>
<style scoped lang="scss">
.webConfig {
}
</style>

View File

@@ -0,0 +1,51 @@
<script>
export default {
name: "wxcpConfig",
model: {
prop: "form",
event: "input"
},
props: {
form: Object,
title: String
},
watch: {
form: {
handler() {
this.$emit("input", this.form)
},
deep: true
}
}
}
</script>
<template>
<ai-card :title="title" class="wxcpConfig">
<template #content>
<el-form-item label="默认首页">
<el-input v-model="form.homePage" clearable placeholder="填写应用的文件名"/>
</el-form-item>
<el-form-item label="域名根目录(baseURL)">
<el-input v-model="form.base" clearable placeholder="域名根目录"/>
</el-form-item>
<el-form-item label="是否启用水印">
<el-checkbox v-model="form.waterMarker"/>
</el-form-item>
<el-form-item label="接口是否单服务">
<el-checkbox v-model="form.isSingleService"/>
</el-form-item>
<el-form-item label="自定义登录">
<el-checkbox v-model="form.customLogin"/>
</el-form-item>
<el-form-item label="开启百度流量">
<el-checkbox v-model="form.hmt"/>
</el-form-item>
</template>
</ai-card>
</template>
<style scoped lang="scss">
.wxcpConfig {
}
</style>

View File

@@ -0,0 +1,115 @@
<script>
import AiLibTable from "../AiLibTable.vue";
export default {
name: "wxmpConfig",
components: {AiLibTable},
model: {
prop: "form",
event: "input"
},
props: {
form: Object,
title: String
},
watch: {
form: {
handler() {
this.$emit("input", this.form)
},
deep: true
}
},
data() {
return {
colConfigs: [
{prop: 'text', label: "名称", width: 120},
{prop: 'pagePath', label: "应用路径"},
{prop: 'iconPath', label: "默认图标"},
{prop: 'selectedIconPath', label: "选中图标"},
],
tabBar: {
color: "#666666",
selectedColor: "#197DF0",
backgroundColor: "#ffffff",
list: [
{pagePath: "pages/AppHome/AppHome", text: "首页", iconPath: "static/TabBar/home.png", selectedIconPath: "static/TabBar/home_selected.png"},
{pagePath: "pages/AppModules/AppModules", text: "应用", iconPath: "static/TabBar/service.png", selectedIconPath: "static/TabBar/service_selected.png"},
{
pagePath: "pages/AppEnteringVillage/AppEnteringVillage", text: "进村",
iconPath: "static/TabBar/custom.png", selectedIconPath: "static/TabBar/custom_selected.png"
},
{pagePath: "pages/AppMine/AppMine", text: "我的", iconPath: "static/TabBar/me.png", selectedIconPath: "static/TabBar/me_selected.png"}
]
}
}
},
methods: {
handleTabbarChange(row, {name, label}) {
row.text = label
row.pagePath = `pages/${name}/${name}`
},
handleTabbarDelete(i) {
this.tabBar.list?.splice(i, 1)
},
handleTabbarPosition(i, offset) {
const row = this.tabBar.list[i]
this.tabBar.list.splice(i, 1, this.tabBar.list[i + offset])
this.tabBar.list.splice(i + offset, 1, row)
}
}
}
</script>
<template>
<ai-card :title="title" class="wxmpConfig">
<template #content>
<el-form-item label="小程序AppId">
<el-input v-model="form.appId" clearable placeholder="小程序appId"/>
</el-form-item>
<el-form-item label="半屏小程序">
<el-input v-model="form.embeddedAppIdList" clearable placeholder="如果有多个,请用英文','分割"/>
</el-form-item>
<el-form-item label="接口是否单服务">
<el-checkbox v-model="form.isSingleService"/>
</el-form-item>
<el-form-item label="引导页">
<el-input v-model="form.guide" placeholder="带'/'会被认为是输入的页面路径" clearable/>
</el-form-item>
<ai-title class="row" title="底部导航栏">
<el-button type="text" slot="rightBtn" icon="iconfont iconAdd" @click="tabBar.list.push({})">添加</el-button>
</ai-title>
<ai-table class="row" :tableData="tabBar.list" :colConfigs="colConfigs" tableSize="mini" :isShowPagination="false" border ref="TabBar">
<el-table-column slot="options" label="操作" width="260" align="center">
<template slot-scope="{row,$index}">
<el-row type="flex" class="tabBarOptions">
<ai-dialog-btn text="更换" dialogTitle="选择应用">
<ai-lib-table :meta="appList" v-model="row.id" @select="v=>handleTabbarChange(row,v)" :isShowPagination="false" v-bind="$props"
:border="false"/>
</ai-dialog-btn>
<ai-dialog-btn text="编辑" dialogTitle="编辑导航栏" width="600px">
<el-form-item label="名称">
<el-input v-model="row.text" placeholder="请输入" clearable/>
</el-form-item>
<el-form-item label="默认图标">
<el-input v-model="row.iconPath" placeholder="请输入" clearable/>
</el-form-item>
<el-form-item label="选中图标">
<el-input v-model="row.selectedIconPath" placeholder="请输入" clearable/>
</el-form-item>
</ai-dialog-btn>
<el-button type="text" @click="handleTabbarDelete($index)">删除</el-button>
<el-button type="text" @click="handleTabbarPosition($index,-1)" v-if="$index>0">上移</el-button>
<el-button type="text" @click="handleTabbarPosition($index,1)" v-if="$index<tabBar.list.length-1">下移</el-button>
</el-row>
</template>
</el-table-column>
</ai-table>
</template>
</ai-card>
</template>
<style scoped lang="scss">
.wxmpConfig {
}
</style>

View File

@@ -4,18 +4,18 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"/>
<link rel="stylesheet" href="<%= BASE_URL %>cdn/viewerjs/1.11.6/viewer.css"/>
<link rel="stylesheet" href="<%= BASE_URL %>cdn/avue/2.10.18/index.css">
<link rel="stylesheet" href="<%= BASE_URL %>cdn/jsoneditor/10.0.2/jsoneditor.min.css">
<link rel="stylesheet" href="<%= BASE_URL %>cdn/highlight/atom-one-dark.min.css">
<script src="<%= BASE_URL %>cdn/jsoneditor/10.0.2/jsoneditor.min.js" charset="utf-8"></script>
<script src="<%= BASE_URL %>cdn/viewerjs/1.11.6/viewer.min.js" charset="utf-8"></script>
<script src="<%= BASE_URL %>cdn/avue/2.10.18/avue.min.js" charset="utf-8"></script>
<script src="<%= BASE_URL %>cdn/echarts/echarts@5.5.0.min.js" charset="utf-8"></script>
<script src="<%= BASE_URL %>cdn/echarts/echarts-gl@2.0.9.min.js" charset="utf-8"></script>
<script src="<%= BASE_URL %>cdn/markdown-it/markdown-it.min.js" charset="utf-8"></script>
<script src="<%= BASE_URL %>cdn/highlight/highlight.min.js" charset="utf-8"></script>
<link rel="icon" href="<%= assetPrefix %>favicon.ico"/>
<link rel="stylesheet" href="<%= assetPrefix %>cdn/viewerjs/1.11.6/viewer.css"/>
<link rel="stylesheet" href="<%= assetPrefix %>cdn/avue/2.10.18/index.css">
<link rel="stylesheet" href="<%= assetPrefix %>cdn/jsoneditor/10.0.2/jsoneditor.min.css">
<link rel="stylesheet" href="<%= assetPrefix %>cdn/highlight/atom-one-dark.min.css">
<script src="<%= assetPrefix %>cdn/jsoneditor/10.0.2/jsoneditor.min.js" charset="utf-8"></script>
<script src="<%= assetPrefix %>cdn/viewerjs/1.11.6/viewer.min.js" charset="utf-8"></script>
<script src="<%= assetPrefix %>cdn/avue/2.10.18/avue.min.js" charset="utf-8"></script>
<script src="<%= assetPrefix %>cdn/echarts/echarts@5.5.0.min.js" charset="utf-8"></script>
<script src="<%= assetPrefix %>cdn/echarts/echarts-gl@2.0.9.min.js" charset="utf-8"></script>
<script src="<%= assetPrefix %>cdn/markdown-it/markdown-it.min.js" charset="utf-8"></script>
<script src="<%= assetPrefix %>cdn/highlight/highlight.min.js" charset="utf-8"></script>
<title>web端应用库-展示页面</title>
<script src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js" referrerpolicy="origin"></script>
<script src="https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js" referrerpolicy="origin"></script>

70
rsbuild.config.js Normal file
View File

@@ -0,0 +1,70 @@
import {defineConfig} from '@rsbuild/core';
import {pluginVue2} from '@rsbuild/plugin-vue2';
import {pluginSass} from '@rsbuild/plugin-sass';
import {pluginBabel} from '@rsbuild/plugin-babel';
import path from "path";
const proxy = process.env.VUE_APP_API || "http://192.168.1.87:9000"
const port = process.env.VUE_APP_PORT || 7000
const entry = process.env.NODE_ENV == "production" ? "./src/main.js" : "./examples/main.js"
console.log("当前模式:", process.env.NODE_ENV)
console.log("当前后台服务地址:", proxy, "\n")
export default defineConfig({
plugins: [pluginVue2(),
pluginSass({
sassLoaderOptions: {
additionalData: `@import "~dui/lib/styles/vars.scss";`
}
}),
// pluginBabel({
// include: [
// path.resolve(__dirname, 'packages'),
// path.resolve(__dirname, 'project'),
// path.resolve(__dirname, 'examples'),
// path.resolve(__dirname, 'src'),
// path.resolve(__dirname, 'ui/packages'),
// path.resolve(__dirname, 'ui/dv'),
// path.resolve(__dirname, 'ui/lib/js'),
// ]
// })
],
source: {
// 指定入口文件
entry: {
index: entry,
},
alias: {
'@packages': path.resolve(__dirname, 'packages'),
'@project': path.resolve(__dirname, 'project'),
'dui': path.resolve(__dirname, 'ui'),
}
},
html: {
template: './public/index.html',
},
server: {
host: '0.0.0.0', //主机地址
port, //端口号
open: true,
proxy: {
//设置代理,可解决跨
'/lan': {
target: proxy,
changeOrigin: true,
pathRewrite: {
//地址重写
'^/lan': '/'
}
},
'/sse': {
target: "http://192.168.1.87:10409/v1",
changeOrigin: true,
pathRewrite: {
//地址重写
'^/sse': '/'
}
},
},
disableHostCheck: true,
}
});

70
src/App.vue Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
src/assets/building.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
src/assets/loginLeft.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/assets/nav_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

31
src/main.js Normal file
View File

@@ -0,0 +1,31 @@
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/packages';
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";
//备注底座信息,勿删
console.log("欢迎使用%s", extra.sysInfo?.name || "构建版本")
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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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>

View File

@@ -1,16 +0,0 @@
{
"name": "@dui/dv",
"version": "1.0.0",
"author": "kubbo",
"scripts": {
"lib": "npm unpublish --force&&npm publish"
},
"main": "index.js",
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@jiaminghi/data-view": "^2.10.0",
"vuedraggable": "^2.24.3",
"element-ui": "^2.13.2",
"vue": "^2.6.11"
}
}

View File

@@ -1,32 +0,0 @@
{
"name": "dui",
"version": "2.0.0",
"author": "kubbo",
"scripts": {
"lib": "npm unpublish --force&&npm publish"
},
"files": [
"lib",
"packages"
],
"main": "packages/index.js",
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@ckeditor/ckeditor5-vue2": "^3.0.1",
"@jiaminghi/data-view": "^2.10.0",
"crypto-js": "^4.1.1",
"dayjs": "^1.8.35",
"html2canvas": "^1.4.1",
"jspdf": "^2.5.1",
"vue-cropper": "^0.5.5",
"vue-qr": "^2.2.1",
"vuedraggable": "^2.24.3",
"element-ui": "^2.13.2",
"vue": "^2.6.11"
},
"web-types": "docs/web-types.json",
"vetur": {
"tags": "docs/tags.json",
"attributes": "docs/attributes.json"
}
}

View File

@@ -11,17 +11,22 @@ module.exports = {
}
}
},
transpileDependencies: [/dui[\\\/]lib[\\\/]js/],
chainWebpack: (config) => {
config.resolve.alias
.set('@packages', path.resolve(__dirname, 'packages'))
.set('@project', path.resolve(__dirname, 'project'))
.set('dui', path.resolve(__dirname, 'ui'))
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, 'src'))
.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: {