73 Commits

Author SHA1 Message Date
bdbff3c9bd 代理设置已完成 2022-11-21 14:50:29 +08:00
e968059ecd 适应远程办公设置 2022-11-21 10:59:28 +08:00
liuye
c3d761d05f 删除 2022-10-13 14:48:27 +08:00
liuye
f210317222 发展党员 2022-10-13 13:52:35 +08:00
shijingjing
45b4c5ad24 删除dev分支的彭刘杨 2022-09-30 10:40:01 +08:00
shijingjing
a2d506a56d 彭刘杨 2022-09-30 10:31:04 +08:00
yanran200730
aed4f4bdc7 大屏 2022-09-09 09:10:26 +08:00
yanran200730
7a30018e52 大屏 2022-09-09 09:02:48 +08:00
yanran200730
95eb08e8c1 兴义大屏 2022-09-08 18:09:30 +08:00
yanran200730
52d7c20639 兴义大屏 2022-09-08 17:39:57 +08:00
yanran200730
3f713a8d15 兴义大屏 2022-09-08 16:06:01 +08:00
yanran200730
36f4283374 郫都大屏 2022-09-08 13:18:33 +08:00
yanran200730
a9ae034fcd 兴义大屏 2022-09-08 10:35:13 +08:00
yanran200730
ec379e6b72 兴义大屏 2022-09-08 09:03:41 +08:00
yanran200730
85caf086b7 兴义大屏 2022-09-07 14:54:41 +08:00
yanran200730
db431af916 大屏 2022-09-07 14:33:22 +08:00
yanran200730
022a15cc9d 大屏 2022-09-07 14:04:28 +08:00
yanran200730
6dfb287c9f 兴义大屏 2022-09-07 10:09:29 +08:00
yanran200730
5ed578faa0 30948 2022-09-05 17:35:23 +08:00
yanran200730
faffbaeafe 协同宣发 2022-09-05 14:31:44 +08:00
yanran200730
9cf8ffc5c1 协同宣发 2022-09-05 10:54:11 +08:00
yanran200730
e2acdacf1f Merge branch 'dev' of http://git.sinoecare.com/sinoecare/digital_village_v2/dvcp_v2_webapp into dev 2022-09-02 16:11:50 +08:00
yanran200730
db077cb9d5 协同宣发 2022-09-02 16:11:29 +08:00
liuye
0aa51096a3 企微配置 2022-09-02 14:16:20 +08:00
liuye
a863c70ea4 企微应用配置 2022-09-01 17:46:23 +08:00
liuye
9f8f035b83 企微配置 2022-09-01 15:58:10 +08:00
liuye
71bebd8048 企微应用配置 2022-09-01 15:31:58 +08:00
yanran200730
4a75301322 协同宣发 2022-09-01 15:08:54 +08:00
yanran200730
ec24628e02 协同宣发 2022-09-01 13:47:14 +08:00
yanran200730
7f51812208 Merge branch 'dev' of http://git.sinoecare.com/sinoecare/digital_village_v2/dvcp_v2_webapp into dev 2022-09-01 13:45:49 +08:00
yanran200730
9f5b6e10a3 协同宣发 2022-09-01 13:45:42 +08:00
shijingjing
7e918cbf4a 取绝对值 2022-09-01 11:10:02 +08:00
shijingjing
3383deabd2 31011 2022-09-01 09:30:23 +08:00
shijingjing
6c99807d85 size 2022-08-31 17:43:01 +08:00
yanran200730
564a4e57d8 31001 2022-08-31 15:16:37 +08:00
yanran200730
dd8e0b8f5f 31006 2022-08-31 14:36:52 +08:00
yanran200730
f50166c297 协同宣发 2022-08-31 14:25:01 +08:00
yanran200730
aea4f4e5c0 协同宣发 2022-08-31 14:19:04 +08:00
yanran200730
8a91728452 协同宣发 2022-08-31 10:29:57 +08:00
yanran200730
70ddb69006 群发居民群 2022-08-30 17:05:02 +08:00
yanran200730
72eae2fb4b 协同宣发 2022-08-30 16:02:41 +08:00
yanran200730
e92613391f 郫都优化 2022-08-30 10:48:59 +08:00
yanran200730
bef847e128 bug 2022-08-30 10:37:15 +08:00
yanran200730
8f17c453e1 秀山 2022-08-29 17:17:44 +08:00
shijingjing
57628dc429 网格员积分优化 2022-08-26 15:09:02 +08:00
yanran200730
53aa333967 bug 2022-08-25 09:48:47 +08:00
yanran200730
d2f611623b bug 2022-08-25 09:39:56 +08:00
yanran200730
8fa7e9c083 优化 2022-08-25 09:34:43 +08:00
yanran200730
ff973bf62f bug 2022-08-25 09:19:16 +08:00
aixianling
d3c8292035 头部导航增加埋点设置 2022-08-24 18:18:17 +08:00
yanran200730
c7718b024b Merge branch 'dev' of http://git.sinoecare.com/sinoecare/digital_village_v2/dvcp_v2_webapp into dev 2022-08-24 17:58:14 +08:00
yanran200730
7e52c337c3 优化 2022-08-24 17:56:10 +08:00
shijingjing
3c37b1b4ad css 2022-08-24 17:29:47 +08:00
shijingjing
0538342491 人员类别 2022-08-24 16:53:32 +08:00
yanran200730
883aca74e1 优化网格选择组件 2022-08-24 16:07:00 +08:00
yanran200730
64d2edd2cd bug 2022-08-24 15:11:43 +08:00
aixianling
36dc08d8ab 构建版收录web端产品库 2022-08-24 11:04:18 +08:00
aixianling
b42d755e9b BUG 30974 2022-08-23 16:57:42 +08:00
aixianling
afa698cf72 BUG 30970 2022-08-23 16:04:42 +08:00
aixianling
8bebe9f661 统一一下字典加载 2022-08-23 16:00:44 +08:00
aixianling
e1bc2c9c2e BUG 30967 2022-08-23 15:54:03 +08:00
aixianling
831faf4a34 BUG 30968 2022-08-23 15:47:47 +08:00
aixianling
07c7c267aa BUG 30968 2022-08-23 15:45:28 +08:00
aixianling
2650c69ff7 BUG 30969 2022-08-23 15:40:57 +08:00
aixianling
d8db5ef7bb BUG 30961 2022-08-23 15:39:17 +08:00
aixianling
11d24d2f2c BUG 30961 2022-08-23 15:37:21 +08:00
aixianling
ae1c7ce5eb 百度流量埋点设置 2022-08-23 15:19:35 +08:00
aixianling
e8774de7d4 BUG 30961 2022-08-23 14:59:30 +08:00
aixianling
df89da46f1 BUG 30957 2022-08-23 14:24:35 +08:00
aixianling
35ac06a05b BUG 30962 2022-08-23 14:22:37 +08:00
aixianling
cef6c224ee 调整名字 2022-08-23 14:18:35 +08:00
aixianling
6ac1f0a87a BUG 30961 2022-08-23 14:15:04 +08:00
aixianling
ec729eb1e9 BUG 30959 2022-08-23 11:27:01 +08:00
212 changed files with 31315 additions and 1031 deletions

24
.gitignore vendored
View File

@@ -1,28 +1,24 @@
# Logs
logs
*.log
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/package-lock.json
/lib
.prettierrc

13
.npmignore Normal file
View File

@@ -0,0 +1,13 @@
# 忽略目录
examples/
packages/
subPackages/
core/
public/
project/
.idea/
# 忽略指定文件
vue.config.js
babel.config.js
*.map

2
.npmrc
View File

@@ -1,4 +1,4 @@
registry=http://192.168.1.87:4873/
registry=http://cli.sinoecare.net/
email=aixianling@sinoecare.com
always-auth=true
_auth="YWRtaW46YWRtaW4xMjM="

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# dvcp_v2_webapp
村微2.0 前端应用库

11
babel.config.js Normal file
View File

@@ -0,0 +1,11 @@
module.exports = {
presets: [
'@vue/app',
],
plugins: [
// 可选链插件, 其他babel插件也是一样的安装方式
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
"@babel/plugin-proposal-logical-assignment-operators"
]
}

View File

@@ -11,6 +11,7 @@ import store from './store';
import dataV from '@jiaminghi/data-view';
import dvui from '../project/dvui/entries'
Vue.use(dataV);
Vue.use(ui);
Vue.use(vcUI);
Vue.use(dvui)

View File

@@ -1,17 +1,16 @@
import store from "../store";
import appEntry from "../views/appEntry";
import {waiting} from "../utils";
import appEntry from "../views/apps/appEntry";
import router from "./router";
import axios from "./axios";
export default {
routes: [],
routes: () => store.state.apps,
init() {
//约束正则式
store.commit("cleanApps")
this.routes = []
// 自动化本工程应用
return this.loadApps()
this.loadApps()
},
loadApps() {
//新App的自动化格式
@@ -37,12 +36,12 @@ export default {
promise(apps, "packages"),
promise(projects, "project")
]).then(() => {
axios.post("/node/wechatapps/addOrUpdate", {
type: "web",
list: this.routes().map(({path: libPath, label, module: {name}, name: id}) => ({
id, type: 'web', libPath, label, name
}))
}, {baseURL: "/ns"}).catch(() => 0)
// axios.post("/node/wechatapps/addOrUpdate", {
// type: "web",
// list: this.routes().map(({path: libPath, label, module: {name}, name: id}) => ({
// id, type: 'web', libPath, label, name
// }))
// }, {baseURL: "/ns"}).catch(() => 0)
waiting.close()
})
}

View File

@@ -1,13 +1,13 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import autoRoutes from './autoRoutes'
import Vue from "vue";
autoRoutes.init()
Vue.use(VueRouter)
autoRoutes.init()
export default new VueRouter({
mode: 'history',
hashbang: false,
routes: [{path: "/", redirect: "/v", name: "产品库", component: import('../App')}],
routes: autoRoutes.routes(),
scrollBehavior(to) {
if (to.hash) {
return {

View File

@@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>村务云应用库-展示页面</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>
</head>
<body>
<div id="app"></div>
<script type="module" src="/examples/main.js"></script>
</body>
</html>

View File

@@ -1,13 +1,13 @@
{
"name": "dvcp-vite-webapps",
"name": "cw-webapps",
"version": "2.0.0",
"private": false,
"version": "0.0.0",
"main": "lib/dvcp-vite-webapps.umd.js",
"author": "kubbo",
"main": "lib/cw-webapps.common.js",
"scripts": {
"dev": "vite",
"preview": "vite preview",
"lib": "vite build --outDir lib --emptyOutDir&&npm unpublish --force&&npm publish",
"lib:core": "vue-cli-service build --target lib --dest core/dist core/index.js --name vc-app-core&&npm unpublish --force&&npm publish",
"dev": "vue-cli-service serve",
"lib": "vue-cli-service build --no-clean --target lib --dest lib packages/index.js&&npm unpublish --force&&npm publish",
"lib:core": "vue-cli-service build --target lib --dest core/dist core/index.js --name dvcp-core&&npm unpublish dvcp-core --force&&npm publish core/",
"lib:project": "node project/build.js",
"lib:all": "node project/allProject.js&&npm unpublish --workspaces --force&&npm publish --workspaces",
"ui": "npm i dvcp-ui@latest"
@@ -15,22 +15,76 @@
"workspaces": [
"project/*"
],
"devDependencies": {
"v-viewer": "^1.6.4",
"vite": "^2.9.5",
"vite-plugin-vue2": "^2.0.0",
"vue": "^2.6.14",
"vue-router": "^3.3.4",
"vue-template-compiler": "^2.6.14",
"vuex-persistedstate": "^3.2.1"
},
"dependencies": {
"dvcp-dv-ui": "^2.0.1",
"@amap/amap-jsapi-loader": "^1.0.1",
"@jiaminghi/c-render": "^0.4.3",
"@jiaminghi/charts": "^0.2.18",
"@jiaminghi/data-view": "^2.10.0",
"bin-code-editor": "^0.9.0",
"dayjs": "^1.8.35",
"dvcp-ui": "^1.42.2",
"element-ui": "^2.15.8",
"echarts": "^5.1.2",
"mp4box": "^0.4.1",
"print-js": "^1.0.63",
"sass": "^1.51.0",
"vue-draggable-resizable": "^2.3.0"
}
"serialize-javascript": "^6.0.0",
"sortablejs": "^1.12.0",
"vue-draggable-resizable": "^2.3.0",
"vue-json-editor": "^1.4.3",
"vue-ruler-tool": "^1.2.4",
"vuedraggable": "^2.24.3"
},
"devDependencies": {
"@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",
"axios": "^0.19.2",
"babel-eslint": "^10.1.0",
"core-js": "^2.6.11",
"element-ui": "^2.15.9",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"image-webpack-loader": "^6.0.0",
"inquirer": "^6.5.2",
"readline": "^1.3.0",
"sass": "~1.32.12",
"sass-loader": "^7.3.1",
"uglifyjs-webpack-plugin": "^2.2.0",
"v-viewer": "^1.6.4",
"vue": "^2.6.14",
"vue-router": "^3.3.4",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.6.14",
"vuex": "^3.5.1",
"vuex-persistedstate": "^2.7.1"
},
"vetur": {
"attributes": "./attributes.json"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}

View File

@@ -0,0 +1,81 @@
<template>
<section class="AppDesigner">
<component :is="currentPage" v-bind="$props"/>
</section>
</template>
<script>
import List from './components/List.vue'
import Add from './components/Add'
import SourceData from './components/SourceData'
import Preview from "./components/preview";
export default {
name: 'AppDesigner',
label: '大屏设计',
components: {Preview, List, Add, SourceData},
props: {
instance: Function,
dict: Object,
permissions: Function,
urlPrefix: {
type: String,
default: '/app'
}
},
computed: {
currentPage() {
const {hash} = this.$route
return hash == "#sourceData" ? SourceData :
hash == "#add" ? Add :
hash == "#preview" ? Preview : List
},
tabs() {
return [
{label: '大屏列表', name: 'FormList', comp: List, permission: ''}
].filter(() => {
return true
})
}
},
data() {
return {
currIndex: '0',
componentName: '',
params: {},
areaId: '',
isShowDetail: false
}
},
methods: {
onChange(data) {
if (data.type === 'list') {
this.componentName = 'List'
this.isShowDetail = false
this.params = data.params
}
if (data.type === 'add') {
this.componentName = 'Add'
this.isShowDetail = true
this.params = data.params
}
if (data.type === 'SourceData') {
this.componentName = 'SourceData'
this.isShowDetail = true
this.params = data.params
}
}
},
}
</script>
<style lang="scss" scoped>
.AppDesigner {
height: 100%;
}
</style>

View File

@@ -0,0 +1,316 @@
<template>
<ai-detail>
<template slot="title">
<ai-title :title="id ? '编辑项目' : '添加项目'" isShowBack isShowBottomBorder @onBackClick="cancel"/>
</template>
<template slot="content">
<el-form ref="form" :model="form" label-width="110px" label-position="right">
<ai-card title="基本信息">
<template #content>
<div class="ai-form">
<el-form-item label="名称" prop="name" :rules="[{ required: true, message: '请输入大屏项目名称', trigger: 'blur' }]">
<el-input size="small" :maxlength="30" placeholder="请输入大屏项目名称" v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="描述" style="width: 100%;" prop="description">
<el-input size="small" :maxlength="200" :rows="5" type="textarea" style="width: 100%;" placeholder="请输入描述" v-model="form.description"></el-input>
</el-form-item>
<el-form-item label="是否开启" style="width: 100%;" prop="status">
<el-switch
v-model="form.status"
active-value="1"
inactive-value="0">
</el-switch>
</el-form-item>
</div>
</template>
</ai-card>
<ai-card title="大屏">
<template #right>
<el-button @click="add('')" type="primary">添加大屏</el-button>
<el-button @click="dialog=true" type="primary">定制大屏</el-button>
</template>
<template #content>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 6px;"
:border="true"
row-key="id"
:isShowPagination="false"
@getList="() => {}">
<el-table-column slot="options" label="状态" align="center">
<template slot-scope="{ row }">
<el-switch
v-model="row.status"
active-value="1"
@change="() => onStatusChange(row.id)">
</el-switch>
</template>
</el-table-column>
<el-table-column slot="options" width="160px" fixed="right" label="操作" align="center">
<template slot-scope="{ row, column, $index }">
<div class="table-options">
<el-button type="text" @click="toEdit(row.id, row.isCustom, row)">编辑</el-button>
<el-button type="text" @click="toViewer(row.id)">预览</el-button>
<el-button type="text" @click="remove($index)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-card>
</el-form>
<Layout
v-if="isShowLayout"
:instance="instance"
:dict="dict"
:params="query"
@change="onChange"
:urlPrefix="urlPrefix"
:theme="config.theme"
@close="isShowLayout = false">
</Layout>
<ai-dialog :visible.sync="dialog" title="定制大屏" @closed="custom={}" @onConfirm="handleCustomizedDV">
<el-form ref="CustomDVFrom" size="small" :model="custom" :rules="rules" label-width="80px">
<el-form-item label="大屏标题" prop="title">
<el-input v-model="custom.title" clearable placeholder="请填写"/>
</el-form-item>
<el-form-item label="选择大屏" prop="dv">
<ai-select v-model="custom.dv" :selectList="dict.getDict('customizedDVs')"/>
</el-form-item>
<el-form-item label="静态数据">
<el-input type="textarea" rows="5" v-model="custom.meta"/>
</el-form-item>
</el-form>
</ai-dialog>
</template>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="confirm">提交</el-button>
</template>
</ai-detail>
</template>
<script>
import Layout from './Layout.vue'
import Sortable from 'sortablejs'
export default {
name: 'Add',
props: {
instance: Function,
dict: Object,
urlPrefix: String
},
inject: {
home: {default: ''}
},
components: {
Layout
},
data() {
return {
info: {},
department: [],
form: {
name: '',
relationLsIds: '',
relationLsNames: '',
status: '1',
description: ''
},
screenId: '',
query: {},
total: 0,
colConfigs: [
{prop: 'title', label: '标题'},
{prop: 'id', label: 'ID'}
],
tableData: [],
isShowLayout: false,
id: '',
dialog: false,
custom: {},
rules: {
dv: [{required: true, message: "请选择 定制大屏"}],
title: [{required: true, message: "请输入 大屏标题"}],
},
config: {
backgroundImage: []
}
}
},
created() {
this.dict.load('customizedDVs')
this.getInfo()
},
methods: {
getInfo() {
let {id} = this.$route.query
if (!id) return
this.instance.post(`${this.urlPrefix}/appdiylargescreen/queryLargeScreenProjectDetailById?id=${id}`).then(res => {
if (res?.data) {
this.form = {
...res.data
}
if (res.data.relationLsIds) {
this.tableData = res.data.lsList.map(v => {
let conf = JSON.parse(v.config || '') || {}
return {
id: v.id,
title: v.title,
dv: conf.custom || '',
meta: JSON.stringify(conf.meta),
isCustom: !!conf.custom,
status: v.status
}
})
this.total = res.data.lsList.length
this.$nextTick(() => {
this.rowDrop()
})
}
}
})
},
onStatusChange (id) {
this.instance.post(`${this.urlPrefix}/appdiylargescreen/enableLargeScreen?id=${id}`).then(res => {
if (res.code === 0) {
this.$message.success('操作成功')
}
})
},
rowDrop() {
const tbody = document.querySelector('.el-table__body-wrapper tbody')
const _this = this
Sortable.create(tbody, {
onEnd({newIndex, oldIndex}) {
const currRow = _this.tableData.splice(oldIndex, 1)[0]
_this.tableData.splice(newIndex, 0, currRow)
}
})
},
toViewer(id) {
this.$router.push({query: {id}, hash: "#preview"})
},
onChange(e) {
const ids = this.tableData.map(v => v.id)
if (ids.indexOf(e.id) < 0) {
this.tableData.push({
title: e.title,
id: e.id,
status: '1'
})
} else {
const index = this.tableData.findIndex(v => v.id === e.id)
this.$set(this.tableData[index], 'title', e.title)
}
this.$nextTick(() => {
if (this.$route.query.id) {
const ids = this.tableData.map(v => v.id).join(',')
const names = this.tableData.map(v => v.name).join(',')
this.instance.post(`${this.urlPrefix}/appdiylargescreen/addOrUpdateLargeScreenProject`, {
...this.form,
relationLsIds: ids,
relationLsNames: names
}).then(res => {
if (res.code == 0) {
}
})
}
})
},
add() {
this.query = {
id: '',
name: this.form.name
}
this.isShowLayout = true
},
toEdit(id, isCustom, form) {
if (!isCustom) {
this.query = {
id,
name: this.form.name
}
this.isShowLayout = true
} else {
this.dialog = true
this.custom = {
...form,
}
}
},
remove(index) {
this.tableData.splice(index, 1)
},
confirm() {
this.$refs.form.validate((valid) => {
if (valid) {
const ids = this.tableData.map(v => v.id).join(',')
const names = this.tableData.map(v => v.name).join(',')
this.instance.post(`${this.urlPrefix}/appdiylargescreen/addOrUpdateLargeScreenProject`, {
...this.form,
relationLsIds: ids,
relationLsNames: names
}).then(res => {
if (res.code == 0) {
this.$message.success('提交成功')
this.home && this.home.refreshDvOptions && this.home.refreshDvOptions()
setTimeout(() => {
this.cancel()
}, 600)
}
})
}
})
},
cancel() {
this.$router.push({})
},
handleCustomizedDV() {
this.$refs.CustomDVFrom.validate(v => {
if (v) {
this.instance.post(`${this.urlPrefix}/appdiylargescreen/addOrUpdateLargeScreen`, {
config: JSON.stringify({
custom: this.custom.dv,
meta: JSON.parse(this.custom.meta?.replace(/\\n/g, '') || null)
}),
status: 1,
id: this.custom.id,
title: this.custom.title,
}).then(res => {
if (res?.code == 0 && res?.data) {
this.$message.success('保存成功')
this.onChange(res.data)
this.dialog = false
}
})
}
})
}
}
}
</script>
<style scoped lang="scss">
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,167 @@
<template>
<ai-list>
<ai-title slot="title" title="大屏列表" isShowBottomBorder/>
<template slot="content">
<ai-search-bar>
<template #left>
<ai-select
v-model="search.status"
@change="search.current = 1, getList()"
placeholder="发布状态"
:selectList="dict.getDict('cwpStatus')">
</ai-select>
<el-button type="primary" @click="toEdit('')">添加大屏</el-button>
<el-button type="primary" @click="toAddData">数据源管理</el-button>
</template>
<template #right>
<el-input
v-model="search.name"
size="small"
placeholder="请输入模板名称或创建人"
clearable
v-throttle="() => {search.current = 1, getList()}"
@clear="search.current = 1, search.title = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
v-loading="loading"
style="margin-top: 6px;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="options" width="160px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="toEdit(row.id)">编辑</el-button>
<el-button type="text" @click="copy(row.id)">复制</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<ai-dialog
:visible.sync="isShowAdd"
width="780px"
title="复制大屏"
@close="onClose"
@onConfirm="onConfirm">
<el-form ref="form" :model="form" label-width="110px" label-position="right">
<div class="ai-form" :model="form" label-width="110px" label-position="right">
<el-form-item label="名称" prop="name" style="width: 100%;" :rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]">
<el-input size="small" placeholder="请输入名称" v-model="form.name"></el-input>
</el-form-item>
</div>
</el-form>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
export default {
name: 'FormList',
props: {
instance: Function,
dict: Object,
areaId: String,
urlPrefix: String
},
data() {
return {
search: {
current: 1,
status: '',
size: 10,
name: ''
},
form: {
name: ''
},
id: '',
isShowAdd: false,
colConfigs: [
{prop: 'name', label: '模板名称'},
{prop: 'createUserName', align: 'center', label: '创建人'},
{prop: 'description', align: 'center', label: '描述'},
{prop: 'status', align: 'center', label: '状态', formart: v => this.dict.getLabel('cwpStatus', v)},
{prop: 'createTime', align: 'center', label: '创建时间'}
],
tableData: [],
total: 0,
loading: false
}
},
created() {
this.dict.load('cwpStatus').then(() => {
this.getList()
})
},
mounted() {
this.loading = true
},
methods: {
copy(id) {
this.id = id
this.isShowAdd = true
},
onClose() {
this.id = ''
this.form.name = ''
},
onConfirm() {
this.$refs.form.validate((valid) => {
if (valid) {
this.instance.post(`${this.urlPrefix}/appdiylargescreen/copyLargeScreenProject`, null, {
params: {
...this.form,
id: this.id
}
}).then(res => {
if (res.code === 0) {
this.$message.success('复制成功')
this.isShowAdd = false
this.getList()
}
})
}
})
},
getList() {
this.instance.post(`${this.urlPrefix}/appdiylargescreen/allLargeScreenProjectByPage`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
this.loading = false
} else {
this.loading = false
}
}).catch(() => {
this.loading = false
})
},
toEdit(id) {
this.$router.push({hash: "#add", query: {id}})
},
toAddData() {
this.$router.push({hash: "#sourceData"})
}
}
}
</script>

View File

@@ -0,0 +1,460 @@
<template>
<ai-detail>
<template slot="title">
<ai-title title="数据源" isShowBack isShowBottomBorder @onBackClick="cancel">
</ai-title>
</template>
<template slot="content">
<ai-card title="数据源列表">
<template #right>
<el-button type="primary" @click="isShow = true">添加数据源</el-button>
</template>
<template #content>
<ai-search-bar class="search-bar">
<template slot="right">
<el-input
v-model="search.name"
size="small"
v-throttle="() => {search.current = 1, getList()}"
placeholder="请输入名字"
clearable
@clear="search.current = 1, search.name = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 6px;"
:border="true"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="options" width="160px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="toEdit(row)">编辑</el-button>
<el-button type="text" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-card>
<ai-dialog
:visible.sync="isShow"
width="920px"
title="数据源"
@close="onClose"
@onConfirm="onConfirm">
<el-form ref="form" :model="form" label-width="110px" label-position="right">
<div class="ai-form">
<el-form-item label="数据源描述" style="width: 100%;" prop="description" :rules="[{ required: true, message: '请输入数据源描述', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入数据源描述"
v-model="form.description">
</el-input>
</el-form-item>
<el-form-item label="类型" style="width: 100%;" prop="dataRange" :rules="[{ required: true, message: '请选择数据范围', trigger: 'change' }]">
<el-radio-group v-model="form.type">
<el-radio label="0">村微应用</el-radio>
<el-radio label="1">SQL</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.type === '1'" label="sql语句" style="width: 100%;" prop="description"
:rules="[{ required: true, message: '请输入sql语句', trigger: 'blur' }]">
<el-input
:rows="8"
size="small"
type="textarea"
placeholder="请输入数据源描述"
v-model="form.sqlContent">
</el-input>
</el-form-item>
<el-form-item v-if="form.type === '0'" label="村微应用" style="width: 100%;" prop="appId"
:rules="[{ required: true, message: '请选择村微应用', trigger: 'change' }]">
<el-select size="small" style="width: 100%;" v-model="form.appId" placeholder="请选择村微应用" @change="onAppChange">
<el-option
v-for="item in appList"
:key="item.id"
:label="item.appName"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item v-if="form.type === '0'" label="数据范围" style="width: 100%;" prop="dataRange"
:rules="[{ required: true, message: '请选择数据范围', trigger: 'change' }]">
<el-radio-group v-model="form.dataRange">
<el-radio label="0">全部</el-radio>
<el-radio label="1">自定义条数</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="数据条数" style="width: 100%;" v-if="form.dataRange === '1' && form.type === '0'" prop="count"
:rules="[{ required: true, message: '请输入数据条数', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入数据条数"
v-model="form.count">
</el-input>
</el-form-item>
<div v-if="form.type === '0'">
<el-form-item :label="'统计项' + index" style="width: 100%;" v-for="(item, index) in form.statisticsConfigs" :key="'statisticsConfigs' + index">
<div class="form-flex">
<div>
<el-select size="small" style="width: 160px;" v-model="item.fieldName" @change="e => onChooseField('statisticsConfigs', e, index)"
placeholder="请选择" clearable>
<el-option
v-for="(item, index) in filedList"
:key="index"
:label="item.fieldCnName"
:value="item.fieldName">
</el-option>
</el-select>
<el-select size="small" style="margin: 0 10px; width: 160px;" v-model="item.calcType" placeholder="请选择" clearable>
<el-option
v-for="item in dict.getDict('diyLargeScreenDatasourceCalcType2')"
:key="item.id"
:label="item.dictName"
:value="item.dictValue">
</el-option>
</el-select>
<el-input size="small" style="width: 165px;" placeholder="请输入别名" v-model="item.alias"></el-input>
</div>
<el-button type="danger" v-if="index > 0" @click="removeConfig('statisticsConfigs', index)">删除</el-button>
</div>
</el-form-item>
</div>
<el-form-item style="width: 100%;" v-if="form.type === '0'">
<el-button type="primary" @click="add('statisticsConfigs')">添加统计项</el-button>
</el-form-item>
<div v-if="form.type === '0'">
<el-form-item :label="'分组' + index" style="width: 100%;" v-for="(item, index) in form.groupConfigs" :key="'groupConfigs' + item.fieldName">
<div class="form-flex">
<div>
<el-select size="small" style="width: 160px;" v-model="item.fieldName" placeholder="请选择" clearable>
<el-option
v-for="item in filedList"
:key="item.id"
:label="item.fieldCnName"
:value="item.fieldName">
</el-option>
</el-select>
</div>
<el-button type="danger" @click="removeConfig('groupConfigs', index)">删除</el-button>
</div>
</el-form-item>
</div>
<el-form-item style="width: 100%;" v-if="form.type === '0'">
<el-button type="primary" @click="add('groupConfigs')">添加分组</el-button>
</el-form-item>
<div v-if="form.type === '0'">
<el-form-item :label="'统计项' + index" style="width: 100%;" v-for="(item, index) in form.orderConfigs" :key="'orderConfigs' + item.fieldName">
<div class="form-flex">
<div>
<el-select size="small" style="width: 160px;" v-model="item.fieldName" placeholder="请选择" clearable>
<el-option
v-for="item in filedList"
:key="item.id"
:label="item.fieldCnName"
:value="item.fieldName">
</el-option>
</el-select>
<el-select size="small" style="width: 160px; margin-left: 10px;" v-model="item.orderType" placeholder="请选择" clearable>
<el-option
v-for="item in orderTypeDict"
:key="item.id"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
<el-button type="danger" @click="removeConfig('orderConfigs', index)">删除</el-button>
</div>
</el-form-item>
</div>
<el-form-item style="width: 100%;" v-if="form.type === '0'">
<el-button type="primary" @click="add('orderConfigs')">添加排序</el-button>
</el-form-item>
</div>
</el-form>
</ai-dialog>
</template>
</ai-detail>
</template>
<script>
export default {
name: 'SourceData',
props: {
instance: Function,
dict: Object,
params: Object,
urlPrefix: String
},
data() {
return {
isShow: false,
search: {
current: 1,
size: 10,
name: ''
},
total: 0,
colConfigs: [
{prop: 'appName', label: '应用名'},
{prop: 'appTableName', align: 'center', label: '表名'},
{prop: 'description', align: 'center', label: '描述'},
{prop: 'type', align: 'center', label: '类型', formart: v => v === '0' ? '村微应用' : 'sql语句'},
{prop: 'createUserName', align: 'center', label: '创建人'},
{prop: 'createTime', align: 'center', label: '创建时间'}
],
tableData: [],
filedList: [],
appList: [],
form: {
orderType: 'asc',
dataRange: '0',
type: '0',
sqlContent: '',
appId: '',
description: '',
appName: '',
appTableName: '',
count: '100',
statisticsConfigs: [{
alias: '',
calcType: '',
dictCode: '',
fieldCnName: '',
fieldName: ''
}],
orderConfigs: [{
alias: '',
calcType: '',
dictCode: '',
fieldCnName: '',
fieldName: ''
}],
groupConfigs: [{
alias: '',
calcType: '',
dictCode: '',
fieldCnName: '',
fieldName: ''
}]
},
id: '',
orderTypeDict: [
{
value: 'asc',
label: '升序'
},
{
value: 'desc',
label: '降序'
}
]
}
},
created() {
this.dict.load(['diyLargeScreenDatasourceCalcType2']).then(() => {
this.getList()
this.getAppList()
})
},
methods: {
getInfo(id) {
this.instance.post(`${this.urlPrefix}/wxcp/wxuser/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.form = {
...res.data
}
}
})
},
onChooseField(type, fieldName, index) {
const dictCode = this.filedList.filter(v => v.fieldName === fieldName)[0].dictCode
this.$set(this.form[type][index], 'dictCode', dictCode)
},
add(type) {
this.form[type].push({
alias: '',
calcType: '',
dictCode: '',
fieldCnName: '',
fieldName: ''
})
},
removeConfig(type, index) {
this.form[type].splice(index, 1)
},
toEdit(e) {
this.id = e.id
this.instance.post(`${this.urlPrefix}/appdiylargescreen/queryDatasourceDetailById?id=${e.id}`).then(res => {
if (res.code === 0) {
this.form = {
...res.data
}
this.form.dataRange = this.form.dataRange === '0' ? '0' : '1'
this.form.count = res.data.dataRange
this.filedList = this.appList.filter(v => v.id === res.data.appId).length && this.appList.filter(v => v.id === res.data.appId)[0].fields.map(item => {
let value = []
res.data.configs.forEach(v => {
if (v.fieldName === item.fieldName) {
value.push(v.calcType)
}
})
return {
...item,
value
}
})
this.isShow = true
}
})
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`${this.urlPrefix}/appquestionnairetemplate/delete?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
getList() {
this.instance.post(`${this.urlPrefix}/appdiylargescreen/allDatasourceByPage`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
getAppList() {
this.instance.post(`${this.urlPrefix}/appdiylargescreen/allDatasourceApp`).then(res => {
if (res.code === 0) {
this.appList = res.data
}
})
},
onClose() {
this.id = ''
this.form.orderType = 'asc'
this.form.dataRange = 0
this.form.appId = ''
this.form.description = ''
this.form.appName = ''
this.form.appTableName = ''
this.form.count = 100
},
onAppChange(e) {
const value = this.appList.filter(v => v.id === e)[0]
this.form.appTableName = value.appTableName
this.form.appName = value.appName
this.form.statisticsConfigs = [{
alias: '',
calcType: '',
dictCode: '',
fieldCnName: '',
fieldName: ''
}]
this.form.orderConfigs = [{
alias: '',
calcType: '',
dictCode: '',
fieldCnName: '',
fieldName: ''
}]
this.form.groupConfigs = [{
alias: '',
calcType: '',
dictCode: '',
fieldCnName: '',
fieldName: ''
}]
this.filedList = value.fields.map(v => {
return {
...v,
value: ''
}
})
},
onConfirm() {
this.$refs.form.validate((valid) => {
if (valid) {
let configs = []
if (this.filedList.length) {
this.filedList.forEach(item => {
if (item.value.length) {
item.value.forEach(v => {
configs.push({
alias: item.fieldCnName,
calcType: v,
fieldCnName: item.fieldCnName,
fieldName: item.fieldName,
dictCode: item.dictCode,
orderType: this.orderType
})
})
}
})
}
this.instance.post(`${this.urlPrefix}/appdiylargescreen/addOrUpdateDatasource`, {
...this.form,
configs,
id: this.id ? this.id : '',
dataRange: this.form.dataRange === '0' ? 0 : this.form.count
}).then(res => {
if (res.code === 0) {
this.$message.success(this.id ? '编辑成功' : '添加成功')
this.search.current = 1
this.isShow = false
this.getList()
}
})
}
})
},
cancel() {
this.$router.push({})
}
}
}
</script>
<style scoped lang="scss">
.form-flex {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@@ -0,0 +1,463 @@
<template>
<div class="layout-config__group--wrapper">
<ai-dialog
append-to-body
:visible.sync="isShowEditor"
width="1000px"
class="layout-config__edit"
title="编辑器"
@onConfirm="onConfirm">
<div>
<code-editor v-model="json" :lint="true" auto-format height="440px"></code-editor>
</div>
</ai-dialog>
<div class="layout-config__group" v-if="options.monitorType !== 'hik' && options.monitorType !== 'dahua'">
<h2>基础设置</h2>
<div class="layout-config__item">
<label>数据类型</label>
<div class="layout-config__item--right">
<el-select size="mini" v-model="options.dataType" placeholder="请选择数据类型">
<el-option
v-for="item in dataTypes"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
</div>
<div class="layout-config__code" v-if="options.dataType === 'staticData'">
<el-button @click="showEditor" class="layout-config__code--btn" title="编辑" type="text" icon="iconfont iconjdq_led_edit"></el-button>
<vue-json-editor
:value="options.staticData"
:show-btns="false"
mode="view"
lang="zh">
</vue-json-editor>
</div>
<template v-else-if="options.dataType === 'dynamicData'">
<div class="layout-config__item">
<label>数据源</label>
<div class="layout-config__item--right">
<el-select size="mini" v-model="options.sourceDataId" placeholder="请选择数据源" @change="onDataChange">
<el-option
v-for="item in sourceData"
:key="item.id"
:label="item.description"
:value="item.id">
</el-option>
</el-select>
</div>
</div>
</template>
<template v-else>
<div class="layout-config__item">
<label>接口地址</label>
<div class="layout-config__item--right">
<el-input size="mini" v-model="options.api" @blur="onApiChange"></el-input>
</div>
</div>
</template>
</div>
<div class="layout-config__group" v-if="options.monitorType === 'hik' || options.monitorType === 'dahua'">
<h2>基础设置</h2>
<div class="layout-config__item">
<label>视频地址</label>
<div class="layout-config__item--right">
<el-input size="mini" v-model="options.src"></el-input>
</div>
</div>
</div>
<div class="layout-config__group" v-if="options.dataType !== 'staticData' && options.type === 'monitor' && (options.monitorType === 'cmcc' || options.monitorType === 'slw')">
<h2>字段设置</h2>
<div class="layout-config__item">
<label>监控视频</label>
<div class="layout-config__item--right">
<el-select size="mini" v-model="options.moniterId" placeholder="请选择监控视频" @change="onMoniterId">
<el-option
v-for="(item, index) in monitorList"
:key="index"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</div>
</div>
</div>
<div class="layout-config__group" v-if="options.dataType !== 'staticData' && options.type !== 'monitor' && keys.length && options.type !== 'table'">
<h2>字段设置</h2>
<div class="layout-config__item">
<label>X轴设置</label>
<div class="layout-config__item--right">
<el-select size="mini" v-model="options.dataX" placeholder="请选择X轴" @change="onChooseChange">
<el-option
v-for="(item, index) in keys"
:key="index"
:label="item"
:value="item">
</el-option>
</el-select>
</div>
</div>
<div class="layout-config__item">
<label>Y轴设置</label>
<div class="layout-config__item--right">
<el-select size="mini" multiple :multiple-limit="options.type.indexOf('pie') > -1 ? 1 : 100" v-model="options.dataY" collapse-tags placeholder="请选择Y轴" @change="onChooseChange">
<el-option
v-for="(item, index) in keys"
:key="index"
:label="item"
:value="item">
</el-option>
</el-select>
</div>
</div>
</div>
</div>
</template>
<script>
import vueJsonEditor from 'vue-json-editor'
import { CodeEditor } from 'bin-code-editor'
import 'bin-code-editor/lib/styles/index.css'
export default {
name: 'dataCofing',
props: {
options: Object,
instance: Function,
dict: Object,
params: Object,
urlPrefix: String
},
data () {
return {
dataTypes: [
{
value: 'staticData',
label: '静态数据'
},
{
value: 'dynamicData',
label: '动态数据'
},
{
value: 'apiData',
label: '接口'
}
],
isShowEditor: false,
json: {},
sourceDataId: '',
sourceData: [],
keys: [],
monitorList: [],
list: [],
jsonData: `{"title":"测试json数据","children":[{"name":"子项名称", "desc":"子项说明" },{"name":"子项名称1", "desc":"子项说明1" }]}`
}
},
components: {
vueJsonEditor,
CodeEditor
},
mounted () {
this.getDataList()
if ((this.options.dataY && this.options.dataY.length && this.options.dataX) || this.options.type === 'monitor') {
const api = this.options.dataType === 'apiData' ? this.options.api : `${this.urlPrefix}/appdiylargescreen/statisticsByLsid?id=${this.options.sourceDataId}`
this.instance.post(api).then(res => {
if (res.code == 0) {
if (res.data.length && this.options.type !== 'monitor') {
this.list = res.data
this.keys = Object.keys(res.data[0])
this.$nextTick(() => {
this.onChooseChange()
})
} else if (this.options.type === 'monitor') {
this.monitorList = res.data
if (this.options.src) {
const obj = res.data.filter(v => this.options.title === v.name)
if (obj.length) {
this.options.src = obj[0].url
}
}
}
}
})
}
},
methods: {
showEditor () {
this.json = JSON.stringify(this.options.staticData)
this.isShowEditor = true
},
onMoniterId (e) {
this.instance.post(`${this.urlPrefix}/appzyvideoequipment/getWebSdkUrl?deviceId=${e}`).then(res => {
if (res.code == 0) {
this.options.src = JSON.parse(res.data).url
}
})
},
getDataList () {
this.instance.post(`${this.urlPrefix}/appdiylargescreen/allDatasourceByPage`, null, {
params: {
current: 1,
size: 10000
}
}).then(res => {
if (res.code == 0) {
this.sourceData = res.data.records
}
})
},
onApiChange () {
this.options.dataX = ''
this.options.dataY = []
this.instance.post(this.options.api).then(res => {
if (res.code == 0) {
if (res.data.length) {
if (this.options.type === 'table') {
const keys = Object.keys(res.data[0])
const list = res.data
this.options.apiData = keys.map(v => {
let obj = {}
list.forEach((item, index) => {
obj[`v${index}`] = item[v]
})
return {
row: v,
...obj
}
})
} else if (this.options.type === 'summary') {
if (this.options.display === 'summary9') {
this.options.apiData = res.data
} else {
this.options.apiData = Object.keys(res.data[0]).map(item => {
return {
key: item,
value: res.data[0][item]
}
})
}
} else if (this.options.type === 'monitor') {
this.monitorList = res.data
} else {
this.list = res.data
this.keys = Object.keys(res.data[0])
}
}
} else {
this.options.dynamicData = []
}
})
},
onChooseChange () {
let arr = []
if (this.options.dataX && this.options.dataY.length) {
this.list.forEach(item => {
let obj = {}
this.options.dataY.forEach(v => {
obj[v] = item[v]
})
arr.push({
[this.options.dataX]: item[this.options.dataX],
...obj
})
})
this.options[this.options.dataType] = arr
}
},
onDataChange (e) {
this.options.dataX = ''
this.options.dataY = []
this.instance.post(`${this.urlPrefix}/appdiylargescreen/statisticsByLsid?id=${e}`).then(res => {
if (res.code == 0) {
if (res.data.length) {
if (this.options.type === 'table') {
const keys = Object.keys(res.data[0])
const list = res.data
this.options.dynamicData = keys.map(v => {
let obj = {}
list.forEach((item, index) => {
obj[`v${index}`] = item[v]
})
return {
row: v,
...obj
}
})
} else if (this.options.type === 'summary') {
this.options.dynamicData = Object.keys(res.data[0]).map(item => {
return {
key: item,
value: res.data[0][item]
}
})
} else {
this.list = res.data
this.keys = Object.keys(res.data[0])
}
} else {
this.options.dynamicData = []
}
}
})
},
onConfirm () {
this.$set(this.options, 'staticData', JSON.parse(this.json))
this.isShowEditor = false
this.$emit('change')
}
}
}
</script>
<style lang="scss">
.el-dialog__body {
.jsoneditor-vue {
height: 480px;
.jsoneditor-poweredBy {
display: none;
}
}
}
.layout-config__group--wrapper {
.layout-config__code .jsoneditor-vue {
.jsoneditor-menu {
display: none;
}
.jsoneditor {
border: 1px solid #030411;
background: #0e1013;
}
.jsoneditor-field {
color: gray;
}
.jsoneditor-tree button:focus {
background-color: transparent;
outline: none;
}
}
.layout-config__group {
padding: 10px 10px 20px;
border-bottom: 1px solid #000000;
&:last-child {
border: none;
}
.layout-config__code {
position: relative;
padding-left: 10px;
.layout-config__code--btn {
position: absolute;
right: 0;
top: 0;
color: gray;
z-index: 1;
&:hover {
opacity: 0.8 ;
}
}
}
& > h2 {
margin-bottom: 20px;
color: #FFFFFF;
font-size: 15px;
font-weight: 700;
}
}
.layout-config__item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
label {
flex-shrink: 0;
width: 60px;
color: #FFFFFF;
font-size: 12px;
text-align: right;
}
.layout-config__item--right {
display: flex;
align-items: center;
justify-content: flex-end;
width: 200px;
text-align: right;
}
.el-select .el-tag {
color: #fff;
background: transparent;
}
input {
background: #262C33;
font-size: 12px;
color: #fff;
border: 1px solid #030411;
}
.el-input__icon {
color: #fff;
}
.el-switch__label {
color: #fff;
}
.el-select {
width: 100%;
&:last-child {
margin-right: 0;
}
input {
background: #262C33;
font-size: 12px;
color: #fff;
border: 1px solid #030411;
}
.el-input__icon {
color: #fff;
}
}
}
}
</style>

View File

@@ -0,0 +1,734 @@
const components = [
{
type: 'chart',
label: '图表',
list: [
{
label: '柱状图',
type: 'bar',
list: [
{
code: 'widget-linechart',
type: 'barChart1',
label: '柱状图1',
title: '柱状图',
border: 'border1',
icon: 'icontext_box',
value: '',
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/bar1.png',
width: 500,
sourceDataId: '',
height: 300,
dataX: '',
zIndex: 1,
dataY: [],
top: 0,
left: 0,
api: '',
apiData: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23},
{name: '水电费', v1: 12},
{name: '凡哥', v1: 67},
{name: '党费', v1: 98}
],
config: 'barChart1',
dynamicData: []
},
{
code: 'widget-linechart',
type: 'barChart2',
label: '柱状图2',
icon: 'icontext_box',
value: '',
title: '柱状图2',
width: 500,
border: 'border1',
sourceDataId: '',
height: 300,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/bar2.png',
dataX: '',
dataY: [],
top: 0,
zIndex: 1,
left: 0,
dataType: 'staticData',
api: '',
apiData: [],
staticData: [
{name: '阿斯达', v1: 23},
{name: '水电费', v1: 12},
{name: '凡哥', v1: 67},
{name: '党费', v1: 98}
],
config: 'barChart2',
dynamicData: []
},
{
code: 'widget-linechart',
type: 'barChart3',
label: '柱状图3',
title: '柱状图3',
icon: 'icontext_box',
value: '',
width: 500,
sourceDataId: '',
height: 300,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/bar3.png',
dataX: '',
dataY: [],
border: 'border1',
top: 0,
left: 0,
zIndex: 1,
dataType: 'staticData',
api: '',
apiData: [],
staticData: [
{name: '阿斯达', v1: 23},
{name: '水电费', v1: 12},
{name: '凡哥', v1: 67},
{name: '党费', v1: 98}
],
config: 'barChart3',
dynamicData: []
},
{
code: 'widget-linechart',
type: 'barChart5',
label: '柱状图4',
title: '柱状图4',
icon: 'icontext_box',
value: '',
width: 500,
sourceDataId: '',
height: 300,
dataX: '',
dataY: [],
zIndex: 1,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/bar5.png',
top: 0,
left: 0,
border: 'border1',
api: '',
apiData: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23},
{name: '水电费', v1: 12},
{name: '凡哥', v1: 67},
{name: '党费', v1: 98}
],
config: 'barChart5',
dynamicData: []
},
{
code: 'widget-linechart',
type: 'barChart7',
label: '柱状图5',
title: '柱状图5',
icon: 'icontext_box',
value: '',
width: 500,
sourceDataId: '',
height: 300,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/bar7.png',
dataX: '',
zIndex: 1,
dataY: [],
top: 0,
left: 0,
api: '',
apiData: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23},
{name: '水电费', v1: 12},
{name: '凡哥', v1: 67},
{name: '党费', v1: 98}
],
config: 'barChart7',
dynamicData: []
},
{
code: 'widget-linechart',
type: 'barChart8',
label: '柱状图6',
title: '柱状图6',
icon: 'icontext_box',
value: '',
width: 500,
sourceDataId: '',
border: 'border1',
height: 300,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/bar8.png',
dataX: '',
dataY: [],
zIndex: 1,
top: 0,
left: 0,
api: '',
apiData: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23},
{name: '水电费', v1: 12},
{name: '凡哥', v1: 67},
{name: '党费', v1: 98}
],
config: 'barChart8',
dynamicData: []
},
{
code: 'widget-linechart',
type: 'barChart9',
label: '柱状图7',
title: '柱状图7',
border: 'border1',
icon: 'icontext_box',
value: '',
width: 500,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/bar9.png',
sourceDataId: '',
height: 300,
dataX: '',
dataY: [],
zIndex: 1,
top: 0,
left: 0,
api: '',
apiData: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23},
{name: '水电费', v1: 12},
{name: '凡哥', v1: 67},
{name: '党费', v1: 98}
],
config: 'barChart9',
dynamicData: []
}
]
},
{
label: '折线图',
type: 'line',
list: [
{
code: 'widget-linechart',
type: 'lineChart1',
label: '折线图1',
title: '折线图1',
border: 'border1',
icon: 'icontext_box',
value: '',
width: 500,
height: 300,
top: 0,
left: 0,
zIndex: 1,
api: '',
apiData: [],
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/line1.png',
dataX: '',
dataY: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23, v2: 33},
{name: '水电费', v1: 12, v2: 34},
{name: '凡哥', v1: 67, v2: 25},
{name: '党费', v1: 98, v2: 85}
],
config: 'lineChart1',
sourceDataId: '',
dynamicData: []
},
{
code: 'widget-linechart',
type: 'lineChart2',
label: '折线图2',
title: '折线图2',
border: 'border1',
icon: 'icontext_box',
value: '',
width: 500,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/line2.png',
height: 300,
top: 0,
left: 0,
dataX: '',
dataY: [],
zIndex: 1,
api: '',
apiData: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23, v2: 33},
{name: '水电费', v1: 12, v2: 34},
{name: '凡哥', v1: 67, v2: 25},
{name: '党费', v1: 98, v2: 85}
],
config: 'lineChart2',
sourceDataId: '',
dynamicData: []
},
{
code: 'widget-linechart',
type: 'lineChart3',
label: '折线图3',
title: '折线图3',
icon: 'icontext_box',
border: 'border1',
value: '',
width: 500,
height: 300,
zIndex: 1,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/line3.png',
top: 0,
left: 0,
dataX: '',
dataY: [],
api: '',
apiData: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23, v2: 33},
{name: '水电费', v1: 12, v2: 34},
{name: '凡哥', v1: 67, v2: 25},
{name: '党费', v1: 98, v2: 85}
],
config: 'lineChart3',
sourceDataId: '',
dynamicData: []
},
{
code: 'widget-linechart',
type: 'lineChart4',
label: '折线图4',
title: '折线图4',
icon: 'icontext_box',
value: '',
border: 'border1',
width: 500,
height: 300,
zIndex: 1,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/line4.png',
top: 0,
left: 0,
dataX: '',
dataY: [],
api: '',
apiData: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23, v2: 33},
{name: '水电费', v1: 12, v2: 34},
{name: '凡哥', v1: 67, v2: 25},
{name: '党费', v1: 98, v2: 85}
],
config: 'lineChart4',
sourceDataId: '',
dynamicData: []
},
{
code: 'widget-linechart',
type: 'lineChart5',
label: '折线图5',
title: '折线图5',
icon: 'icontext_box',
value: '',
border: 'border1',
width: 500,
height: 300,
zIndex: 1,
top: 0,
left: 0,
dataX: '',
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/line5.png',
dataY: [],
api: '',
apiData: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23, v2: 33},
{name: '水电费', v1: 12, v2: 34},
{name: '凡哥', v1: 67, v2: 25},
{name: '党费', v1: 98, v2: 85}
],
config: 'lineChart5',
sourceDataId: '',
dynamicData: []
}
]
},
{
label: '饼图',
type: 'pie',
list: [
{
code: 'widget-linechart',
type: 'pieChart2',
config: 'pieChart2',
label: '饼图',
title: '饼图',
icon: 'icontext_box',
value: '',
border: 'border1',
width: 500,
height: 300,
zIndex: 1,
top: 0,
left: 0,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/pie.png',
dataX: '',
dataY: [],
api: '',
apiData: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23, v2: 33},
{name: '水电费', v1: 12, v2: 34},
{name: '凡哥', v1: 67, v2: 25},
{name: '党费', v1: 98, v2: 85}
],
sourceDataId: '',
dynamicData: []
},
{
code: 'widget-linechart',
type: 'pieChart1',
label: '饼图',
title: '饼图',
icon: 'icontext_box',
value: '',
border: 'border1',
width: 500,
height: 300,
zIndex: 1,
top: 0,
left: 0,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/pie.png',
dataX: '',
dataY: [],
api: '',
apiData: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23, v2: 33},
{name: '水电费', v1: 12, v2: 34},
{name: '凡哥', v1: 67, v2: 25},
{name: '党费', v1: 98, v2: 85}
],
config: 'pieChart1',
sourceDataId: '',
dynamicData: []
},
{
code: 'widget-linechart',
type: 'pieChart3',
label: '饼图',
title: '饼图',
icon: 'icontext_box',
value: '',
border: 'border1',
width: 500,
height: 300,
zIndex: 1,
top: 0,
left: 0,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/pie.png',
dataX: '',
dataY: [],
api: '',
apiData: [],
dataType: 'staticData',
staticData: [
{name: '阿斯达', v1: 23, v2: 33},
{name: '水电费', v1: 12, v2: 34},
{name: '凡哥', v1: 67, v2: 25},
{name: '党费', v1: 98, v2: 85}
],
config: 'pieChart1',
sourceDataId: '',
dynamicData: []
}
]
}
]
},
{
type: 'table',
label: '表格',
list: [
{
label: '表格',
type: 'table',
list: [
{
type: 'table',
label: '表格',
title: '表格',
border: 'border1',
width: 650,
height: 400,
zIndex: 1,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/table.png',
dataX: '',
dataY: [],
rowNum: 7,
isShowIndex: '1',
sourceDataId: '',
api: '',
apiData: [],
dataType: 'staticData',
dynamicData: [],
staticData: [
{name: '列1', v: 23, v2: 3},
{name: '列2', v: 12, v2: 4},
{name: '列2', v: 12, v2: 4}
]
}
]
}
]
},
{
type: 'ai3d', label: "3D", list: [
{
label: "3D楼栋", type: 'building', list: [{
type: 'building1', label: "楼栋模型1",
width: 840,
height: 800,
zIndex: 1,
title: "楼栋模型1",
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/total.png'
}]
}
]
},
{
type: 'other',
label: '其他',
list: [
{
label: '地图',
type: 'map',
list: [
{
type: 'map',
label: '地图',
display: 'map',
width: 840,
height: 534,
left: 0,
top: 0,
mask: '2',
pulseLines: 1,
mapStyle: "e51987628aee5206d4c9ca8c6e98b4f7",
areaId: '',
zIndex: 1,
apiData: [],
dataType: 'staticData',
dynamicData: [],
staticData: [
{
content: '中卫慧通',
lng: 117.1339399,
lat: 36.7190487,
地区: '中卫慧通',
经度: 117.1339399,
纬度: 36.7190487
}
],
api: '',
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/map.png'
}
]
},
{
label: '党组织',
type: 'partyOrg',
list: [
{
type: 'partyOrg',
label: '党组织',
width: 840,
height: 800,
zIndex: 1,
top: 0,
left: 0,
dataX: '',
dataY: [],
title: '党组织',
border: 'border3',
sourceDataId: '',
dataType: 'staticData',
staticData: [
{
key: '个人服务办理',
value: 247
},
{
key: '同比上月',
value: 247
}
],
dynamicData: [],
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/total.png'
}
]
},
{
label: '边框',
type: 'panel',
list: [
{
type: 'panel',
label: '边框',
title: '边框',
border: 'border0',
width: 400,
height: 400,
isZoom: false,
zIndex: 1,
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/border.png'
}
]
},
{
label: '装饰',
type: 'display',
list: [
{
type: 'display',
label: '装饰',
display: 'display0',
width: 840,
height: 540,
isZoom: false,
zIndex: 1,
title: '标题',
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/display.png',
sourceDataId: '',
dataType: 'staticData',
staticData: [
{
label: '个人服务办理',
value: 247
},
{
label: '同比上月',
value: 247
}
]
}
]
},
{
label: '轮播图',
type: 'swiper',
list: [
{
type: 'swiper',
label: '轮播图',
width: 400,
height: 300,
zIndex: 1,
border: 'border2',
dataType: 'staticData',
staticData: [
{
img: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/pie.png',
title: '湖羊',
content: `歙县众城湖羊养殖专业合作社
徐晓红 - 18273645627
歙县郑村镇唐跃村碉墅`
}
],
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/swiper.png'
}
]
},
{
label: '视频播放器',
type: 'video',
list: [
{
type: 'video',
label: '视频播放器',
width: 400,
height: 300,
zIndex: 1,
src: '',
border: 'border2',
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/swiper.png'
}
]
},
{
label: '数据统计',
type: 'summary',
list: [
{
type: 'summary',
label: '数据统计',
display: 'summary0',
width: 480,
height: 240,
zIndex: 1,
top: 0,
left: 0,
dataX: '',
dataY: [],
summaryTitle: '',
border: 'border3',
sourceDataId: '',
title: '数据统计',
dataType: 'staticData',
staticData: [
{
key: '个人服务办理',
value: 247
},
{
key: '同比上月',
value: 247
}
],
dynamicData: [],
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/total.png'
}
]
},
{
label: '视频监控',
type: 'monitor',
list: [
{
type: 'monitor',
label: '视频监控',
src: '',
width: 480,
height: 240,
zIndex: 1,
top: 0,
left: 0,
title: '',
moniterId: '',
monitorType: 'cmcc',
api: '/app/appzyvideoequipment/list2',
border: 'border2',
sourceDataId: '',
dataType: 'staticData',
staticData: '',
dynamicData: '',
thumb: 'https://cdn.cunwuyun.cn/dvcp/dv/tpl/video.png'
}
]
}
]
}
]
export {components}

View File

@@ -0,0 +1,211 @@
<template>
<section class="AppGigscreenViewer">
<div v-if="!component">
<div
class="component-item"
:style="{
width: item.width + 'px',
height: item.height + 'px',
left: item.left * scale + 'px',
top: item.top * scale + 'px',
position: 'absolute',
zIndex: item.zIndex,
transform: `scale(${scale})`
}"
v-for="(item, index) in componentList"
:key="index">
<ai-dv-render :instance="instance" :data="item" :index="index" :theme="dashboard.theme"/>
</div>
</div>
<components v-else :is="component" :dict="dict" :instance="instance" :nav="meta"/>
</section>
</template>
<script>
export default {
name: 'AppGigscreenViewer',
label: '大屏预览',
props: {
instance: Function,
dict: Object,
id: String,
urlPrefix: {
type: String,
default: '/app'
}
},
watch: {
id(v) {
this.getInfo(v)
}
},
data() {
return {
component: '',
dashboard: {
title: '大屏',
width: 1920,
height: 1080,
backgroundColor: '',
theme: '0',
backgroundImage: []
},
componentList: [],
scale: 1,
meta: {}
}
},
created() {
this.getInfo(this.id)
},
mounted() {
this.$nextTick(() => {
let content = document.querySelector('#dv-full-screen-container')
if (content) {
const transform = content.style.transform
const scale = transform.replace('scale', '').replace('(', '').replace(')', '')
if (scale == 1) {
this.scale = document.body.clientWidth / 1920
}
}
})
},
methods: {
getInfo(id) {
this.component = null
id && this.instance.post(`${this.urlPrefix}/appdiylargescreen/queryLargeScreenDetailById?id=${id}`).then(res => {
if (res?.data) {
const config = JSON.parse(res.data.config)
if (config.custom) {
this.component = config.custom
this.meta = config?.meta || {}
} else {
this.componentList = JSON.parse(res.data.config).config
this.dashboard = JSON.parse(res.data.config).dashboard
this.componentList.forEach((item, index) => {
if (item.dataType !== 'staticData' && ((item.type.indexOf('Chart') > -1) || ['display', 'table', 'map', 'summary'].includes(item.type))) {
this.getSourceData(item, index)
}
if (item.type === 'monitor' && item.monitorType === 'cmcc') {
this.instance.post(`${this.urlPrefix}/appzyvideoequipment/getWebSdkUrl?deviceId=${item.moniterId}`).then(res => {
if (res.code == 0) {
this.$set(this.componentList[index], 'src', JSON.parse(res.data).url)
}
})
}
if (item.type === 'monitor' && item.monitorType === 'slw') {
this.instance.post(`${this.urlPrefix}/appzyvideoequipment/getWebSdkUrl?deviceId=${item.moniterId}`).then(res => {
if (res.code == 0) {
this.$set(this.componentList[index], 'src', res.data)
}
})
}
})
}
}
})
},
getSourceData(item, index) {
const api = item.dataType === 'apiData' ? item.api : `${this.urlPrefix}/appdiylargescreen/statisticsByLsid?id=${item.sourceDataId}`
this.instance.post(api).then(res => {
if (res.code == 0) {
if (res.data.length) {
const keys = Object.keys(res.data[0])
const list = res.data
let dynamicData = []
if (item.type === 'table') {
dynamicData = keys.map(v => {
let obj = {}
list.forEach((item, index) => {
obj[`v${index}`] = item[v]
})
return {
row: v,
...obj
}
})
} else if (item.type === 'summary') {
if (item.display === 'summary9') {
dynamicData = res.data
} else {
dynamicData = Object.keys(res.data[0]).map(item => {
return {
key: item,
value: res.data[0][item]
}
})
}
} else if (item.dataType === 'dynamicData' && !item.dataX && !item.dataY.length) {
dynamicData = Object.keys(res.data[0]).map(item => {
return {
label: item,
value: res.data[0][item]
}
})
} else {
if (item.dataX && item.dataY.length) {
list.forEach(i => {
let obj = {}
item.dataY.forEach(v => {
obj[v] = i[v]
})
dynamicData.push({
[item.dataX]: i[item.dataX],
...obj
})
})
} else {
dynamicData = res.data
}
}
this.$set(this.componentList[index], item.dataType, dynamicData)
} else {
this.$set(this.componentList[index], item.dataType, [])
}
}
})
},
close() {
this.$emit('close')
}
}
}
</script>
<style lang="scss">
.AppGigscreenViewer {
position: relative;
height: 100%;
background: transparent !important;
.component-item {
transform-origin: 0 0;
}
.dv-scroll-board {
height: calc(100%) !important;
.header-item {
color: #7e8697;
font-size: 16px;
}
.index {
display: inline-block;
width: 26px;
height: 26px;
line-height: 26px;
font-size: 16px;
background-color: #4F57FF !important;
}
}
.vdr {
border: none;
}
}
</style>

View File

@@ -16,7 +16,7 @@
<ai-info-item label="身份证号" :value="info.idNumber"></ai-info-item>
<ai-info-item label="手机号码" :value="info.phone"></ai-info-item>
<ai-info-item label="人员类别" isLine>
<span :style="info.type == 0 ? 'color:#42D784;' : 'color:#f46;'">{{dict.getLabel('epidemicMemberType', info.type)}}</span>
<span :style="(info.type == 0 || info.type == 3 || info.type ==6 || info.type == 9)? 'color:#42D784;' : 'color:#f46;'">{{dict.getLabel('epidemicRecentPersonType', info.type)}}</span>
</ai-info-item>
</ai-wrapper>
</template>
@@ -87,7 +87,7 @@
<ai-info-item label="填报时间" :value="info.createTime"></ai-info-item>
<ai-info-item label="身份证号" :value="info.idNumber"></ai-info-item>
<ai-info-item label="手机号码" :value="info.phone"></ai-info-item>
<ai-info-item label="人员类别" isLine :value="dict.getLabel('epidemicMemberType', info.type)"></ai-info-item>
<ai-info-item label="人员类别" isLine :value="dict.getLabel('epidemicRecentPersonType', info.type)"></ai-info-item>
<ai-info-item label="异常状况" isLine>
<span :style="{color: info.unusual ? 'red' : '#333'}">{{ info.unusual || '-' }}</span>
</ai-info-item>
@@ -172,7 +172,7 @@
created () {
if (this.params && this.params.id) {
this.id = this.params.id
this.$dict.load(['epidemicRecentHealth', 'epidemicRecentTravel', 'epidemicTouchInFourteen', 'epidemicMemberType', 'epidemicRecentTestResult']).then(() => {
this.$dict.load(['epidemicRecentHealth', 'epidemicRecentTravel', 'epidemicTouchInFourteen', 'epidemicRecentPersonType', 'epidemicRecentTestResult']).then(() => {
this.getInfo(this.params.id)
})

View File

@@ -42,7 +42,7 @@
<el-form-item label="事件类型" prop="systemRuleId">
<el-cascader v-model="form.systemRuleId" ref="cascaderArr" :props="etOps" clearable placeholder="请选择" @change="handleTypeForm" :options="rulesOps"/>
</el-form-item>
<el-form-item label="自定义事件" v-if="form.systemRuleId == '自定义'" prop="ruleName" :required="form.systemRuleId == '自定义'">
<el-form-item label="自定义事件" v-if="form.systemRuleId == '自定义'" prop="ruleName">
<el-input placeholder="请输入,周期范围内,不填写表示不限制" v-model="form.ruleName" clearable maxlength="10" show-word-limit/>
</el-form-item>
@@ -78,11 +78,11 @@
</el-form-item> -->
<el-form-item label="周期范围" prop="scoringCycle">
<ai-select v-model="form.scoringCycle" :selectList="$dict.getDict('integralRuleScoringCycle')"/>
<ai-select v-model="form.scoringCycle" :selectList="$dict.getDict('integralRuleScoringCycle')" :disabled="isOneAndTen"/>
</el-form-item>
<el-form-item label="奖励次数">
<el-input type="number" placeholder="请输入,周期范围内,不填写表示不限制" v-model.number="form.numberLimit" clearable/>
<el-input type="number" placeholder="请输入,周期范围内,不填写表示不限制" v-model.number="form.numberLimit" clearable :disabled="isOneAndTen"/>
</el-form-item>
<el-form-item label="积分分值" prop="integral">
@@ -241,6 +241,7 @@ export default {
rulueType: "0",
girdNameList: [],
list: [],
isOneAndTen: false,
};
},
created() {
@@ -352,6 +353,15 @@ export default {
},
handleTypeForm(v) {
if (this.dialog) {
if(v[0] == '1' || v[0]== '10') {
this.form.scoringCycle = '0'
this.form.numberLimit = '1'
this.isOneAndTen = true
} else {
this.form.scoringCycle = ''
this.form.numberLimit = ''
this.isOneAndTen = false
}
this.form.systemRuleId = v?.[v.length - 1];
}
},
@@ -367,7 +377,7 @@ export default {
},
getRulesList() {
this.instance
.post(`/app/appintegralsystemrule/list?current=1&sizes=3000`)
.post(`/app/appintegralsystemrule/list?current=1&size=3000`)
.then((res) => {
if (res?.data) {
this.rulesOps = this.toTree(res.data.records);

View File

@@ -30,7 +30,7 @@
</div>
<div class="card">
<h2>消耗积分</h2>
<p class="color1">{{ data.reduceIntegral || 0 }}</p>
<p class="color1">{{ Math.abs(data.reduceIntegral) || 0 }}</p>
</div>
</div>
<div class="echertsBox">

View File

@@ -54,7 +54,7 @@
</div>
</template>
<script lang="jsx">
<script>
import {mapState} from "vuex"
export default {

View File

@@ -34,12 +34,12 @@
</section>
</template>
<script lang="jsx">
import {mapState} from 'vuex'
import DeviceSlider from "../components/deviceSlider"
import LocateDialog from "../components/locateDialog"
import AiMonitor from "../components/AiSlwVideo"
import Synergy from "../components/Synergy"
<script>
import { mapState } from 'vuex'
import DeviceSlider from '../components/deviceSlider'
import LocateDialog from '../components/locateDialog'
import AiMonitor from '../components/AiSlwVideo'
import Synergy from '../components/Synergy'
export default {
name: 'AppISManage',

View File

@@ -26,169 +26,168 @@
</el-form>
</ai-dialog>
<locate-dialog v-model="locate" :ins="instance" :latlng="latlng" @confirm="v=>handleLocate(selected,v)"/>
<ai-area custom-clicker :input-clicker="false" v-model="selected.areaId" :hideLevel="disabledLevel"
:instance="instance" ref="BindArea"
<ai-area custom-clicker :input-clicker="false" v-model="selected.areaId" :hideLevel="disabledLevel" :instance="instance" ref="BindArea"
@change="handleSubmit(selected)"/>
</section>
</template>
<script lang="jsx">
import {mapState} from 'vuex'
import DeviceSlider from "../components/deviceSlider";
import LocateDialog from "../components/locateDialog";
<script>
import { mapState } from 'vuex'
import DeviceSlider from "../components/deviceSlider";
import LocateDialog from "../components/locateDialog";
export default {
name: "AppMonitorManage",
components: {LocateDialog, DeviceSlider},
label: "监控实况",
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
splitOps() {
return [
{label: "单分屏", value: 1, per: "100%"},
{label: "四分屏", value: 4, per: "49%"},
{label: "九分屏", value: 9, per: "32%"}
]
export default {
name: "AppMonitorManage",
components: {LocateDialog, DeviceSlider},
label: "监控实况",
props: {
instance: Function,
dict: Object,
permissions: Function
},
currentSplitStyle() {
let per = this.splitOps.find(e => e.value == this.splitScreen)?.per || "100%"
return {width: per, height: per}
},
...mapState(['user'])
},
data() {
return {
slider: true,
fullscreen: false,
splitScreen: 1,
monitors: [],
dialog: false,
locate: false,
selected: {
areaId: ''
computed: {
splitOps() {
return [
{label: "单分屏", value: 1, per: "100%"},
{label: "四分屏", value: 4, per: "49%"},
{label: "九分屏", value: 9, per: "32%"}
]
},
latlng: null,
disabledLevel: 0,
rules: {
name: [{required: true, message: "请填写 设备名称"}]
}
}
},
created() {
this.selected.areaId = this.user.info.areaId
this.disabledLevel = this.user.info.areaList.length
},
methods: {
handleFullscreen() {
this.fullscreen = !this.fullscreen
this.$fullscreen(this.fullscreen)
currentSplitStyle() {
let per = this.splitOps.find(e => e.value == this.splitScreen)?.per || "100%"
return {width: per, height: per}
},
...mapState(['user'])
},
handleSelectMonitor(monitor) {
let {id} = monitor,
index = this.monitors.findIndex(e => e.id == id)
if (index > -1) {
this.monitors.splice(index, 1)
this.monitors.map((e, i) => {
if (i > index) {
this.showMonitor(e, true)
data() {
return {
slider: true,
fullscreen: false,
splitScreen: 1,
monitors: [],
dialog: false,
locate: false,
selected: {
areaId: ''
},
latlng: null,
disabledLevel: 0,
rules: {
name: [{required: true, message: "请填写 设备名称"}]
}
}
},
created () {
this.selected.areaId = this.user.info.areaId
this.disabledLevel = this.user.info.areaList.length
},
methods: {
handleFullscreen() {
this.fullscreen = !this.fullscreen
this.$fullscreen(this.fullscreen)
},
handleSelectMonitor(monitor) {
let {id} = monitor,
index = this.monitors.findIndex(e => e.id == id)
if (index > -1) {
this.monitors.splice(index, 1)
this.monitors.map((e, i) => {
if (i > index) {
this.showMonitor(e, true)
}
})
} else if (this.monitors.length >= this.splitScreen && this.splitScreen > 1) {
this.$message.warning("可分屏监控已满,请先取消其他的监控")
} else {
this.showMonitor(monitor)
}
},
showMonitor(monitor, refresh = false) {
let {id: deviceId} = monitor
deviceId && this.instance.post("/app/appzyvideoequipment/getWebSdkUrl", null, {
params: {deviceId}
}).then(res => {
if (res?.data) {
let data = JSON.parse(res.data)
if (refresh) {
monitor.url = data.url
} else if (this.splitScreen == 1) {
this.monitors = [{...monitor, ...data}]
} else {
this.monitors.push({...monitor, ...data})
}
}
})
} else if (this.monitors.length >= this.splitScreen && this.splitScreen > 1) {
this.$message.warning("可分屏监控已满,请先取消其他的监控")
} else {
this.showMonitor(monitor)
}
},
showMonitor(monitor, refresh = false) {
let {id: deviceId} = monitor
deviceId && this.instance.post("/app/appzyvideoequipment/getWebSdkUrl", null, {
params: {deviceId}
}).then(res => {
if (res?.data) {
let data = JSON.parse(res.data)
if (refresh) {
monitor.url = data.url
} else if (this.splitScreen == 1) {
this.monitors = [{...monitor, ...data}]
} else {
this.monitors.push({...monitor, ...data})
}
}
})
},
renderTreeItem: function (h, {node, data}) {
let show = data.deviceStatus == 1 ? 'show' : ''
if (node.isLeaf) {
return (
},
renderTreeItem: function (h, {node, data}) {
let show = data.deviceStatus==1 ? 'show' : ''
if (node.isLeaf) {
return (
<div class="flexRow">
<i class={['iconfont', 'iconshipinjiankong', show]}/>
<div>{node.label}</div>
<el-dropdown class="menuBtn" onCommand={e => this.handleSliderOption(e, data)}>
<i class="iconfont iconMore"/>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="edit">修改名称</el-dropdown-item>
<el-dropdown-item command="area">行政地区</el-dropdown-item>
<el-dropdown-item command="locate">地图标绘</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
)
} else return (
<div class="flexRow">
<i class={['iconfont', 'iconshipinjiankong', show]}/>
<div>{node.label}</div>
<el-dropdown class="menuBtn" onCommand={e => this.handleSliderOption(e, data)}>
<i class="iconfont iconMore"/>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="edit">修改名称</el-dropdown-item>
<el-dropdown-item command="area">行政地区</el-dropdown-item>
<el-dropdown-item command="locate">地图标绘</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
{data.id != 'no_area' ? <div class="sta">
<p>{data.online || 0}</p>/{data.sum || 0}
</div>
: <div/>}
</div>
)
} else return (
<div class="flexRow">
<div>{node.label}</div>
{data.id != 'no_area' ? <div class="sta">
<p>{data.online || 0}</p>/{data.sum || 0}
</div>
: <div/>}
</div>
)
},
handleSliderOption(command, data) {
this.selected = JSON.parse(JSON.stringify({...data, command}))
if (command == "edit") {//修改名称
this.dialog = true
} else if (command == "area") {//绑定areaId
this.$refs.BindArea?.chooseArea()
} else if (command == "locate") {//地图标绘
this.latlng = data.lat && data.lng ? {
lat: data.lat,
lng: data.lng
} : ''
this.locate = true
}
},
handleSubmit(row) {
delete row.createTime
return this.instance.post("/app/appzyvideoequipment/addOrUpdate", {
...row
}).then(res => {
if (res?.code == 0) {
this.$message.success("提交成功!")
this.dialog = false
this.$refs.DeviceSlider?.getDevices()
},
handleSliderOption(command, data) {
this.selected = JSON.parse(JSON.stringify({...data, command}))
if (command == "edit") {//修改名称
this.dialog = true
} else if (command == "area") {//绑定areaId
this.$refs.BindArea?.chooseArea()
} else if (command == "locate") {//地图标绘
this.latlng = data.lat && data.lng ? {
lat: data.lat,
lng: data.lng
} : ''
this.locate = true
}
})
},
handleLocate(row, locate) {
if (locate) {
let {lat, lng} = locate.location
this.handleSubmit({...row, lat, lng}).then(() => {
this.locate = false
},
handleSubmit(row) {
delete row.createTime
return this.instance.post("/app/appzyvideoequipment/addOrUpdate", {
...row
}).then(res => {
if (res?.code == 0) {
this.$message.success("提交成功!")
this.dialog = false
this.$refs.DeviceSlider?.getDevices()
}
})
},
handleLocate(row, locate) {
if (locate) {
let {lat, lng} = locate.location
this.handleSubmit({...row, lat, lng}).then(() => {
this.locate = false
})
}
}
},
beforeDestroy() {
this.monitors = []
}
},
beforeDestroy() {
this.monitors = []
}
}
</script>
<style lang="scss" scoped>

View File

@@ -140,15 +140,15 @@
width="720px">
<div class="grid">
<el-tree
:data="treeObj.treeList"
:props="treeObj.defaultProps"
node-key="id"
ref="tree"
:check-strictly="true"
show-checkbox
:default-checked-keys="treeObj.checkedKeys"
default-expand-all
@check="onCheckChange">
:data="treeObj.treeList"
:props="treeObj.defaultProps"
node-key="id"
ref="tree"
:check-strictly="true"
show-checkbox
:default-checked-keys="treeObj.checkedKeys"
:default-expanded-keys="treeObj.checkedKeys"
@check="onCheckChange">
</el-tree>
</div>
</ai-dialog>
@@ -192,7 +192,7 @@ export default {
treeObj: {
treeList: [],
defaultProps: {
children: "girdList",
children: "children",
label: "girdName",
},
checkedKeys: [],
@@ -223,12 +223,20 @@ export default {
},
methods: {
getGridList() {
this.instance.post(`/app/appgirdinfo/listAll`).then((res) => {
this.instance.post(`/app/appgirdinfo/listAll3`).then((res) => {
if (res.code == 0) {
this.treeObj.treeList = this.format(res.data)
if (this.formData.girdId) {
this.$set(this.treeObj, 'checkedKeys', [this.formData.girdId])
}
this.treeObj.treeList = res.data.filter(e => !e.parentGirdId)
this.treeObj.treeList.map(p => this.addChild(p, res.data.map(v => {
return {
...v
}
}), {
parent: 'parentGirdId'
}))
}
})
},

View File

@@ -0,0 +1,299 @@
<template>
<div class="AppWxcpAppSet">
<ai-list>
<template slot="title">
<ai-title title="企微应用配置" :isShowBottomBorder="false" :isShowArea="false"></ai-title>
</template>
<template slot="content">
<ai-search-bar>
<template slot="left">
<el-button type="primary" icon="iconfont iconAdd" size="small" @click="add({}, '添加')">添加</el-button>
</template>
<template slot="right">
<el-input v-model="search.title" size="small" placeholder="搜索标题" clearabl v-throttle="() => {page.current = 1, getList()}"
@clear=";(page.current = 1), (search.title = ''), getList()" suffix-icon="iconfont iconSearch"/>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :colConfigs="colConfigs" :total="page.total" :current.sync="page.current"
:size.sync="page.size" @getList="getList" class="ai-table">
<el-table-column label="banner封面" align="left" width="150" slot="banner">
<template slot-scope="{ row }">
<img :src="row.banner" alt="" class="banner-img" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200" slot="option">
<template slot-scope="{ row }">
<el-button type="text" @click="add(row, '编辑')">编辑</el-button>
<el-button type="text" @click="remove(row.id)">删除</el-button>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<ai-dialog :title="dialog.title" :visible.sync="visible" @onCancel="visible = false" @onConfirm="addConfirm" width="800px">
<el-form ref="ruleForm" :model="dialogInfo" :rules="formRules" size="small" label-width="120px">
<el-form-item label="banner封面" prop="banner">
<ai-uploader v-model="dialogInfo.bannerList" @change="changeBanner" :instance="instance" :limit="1"></ai-uploader>
</el-form-item>
<el-form-item label="主页名称" prop="title">
<el-input placeholder="请输入主页名称" :maxlength="10" show-word-limit v-model="dialogInfo.title"></el-input>
</el-form-item>
</el-form>
<div class="dialog-title-flex">
<h3>应用列表</h3>
<span @click="addMiniApp({}, true, '')">添加</span>
</div>
<ai-table :tableData="dialogInfo.wxApplicationList" :colConfigs="miniAppColConfigs" :isShowPagination="false">
<el-table-column label="icon图片" width="80" slot="icon">
<template slot-scope="{ row }">
<img :src="row.icon" alt="" class="banner-img" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="250" slot="option">
<template slot-scope="{ row }">
<el-button type="text" @click="top(row)" v-if="row.showIndex > 1">上移</el-button>
<el-button type="text" @click="next(row)" v-if="row.showIndex < dialogInfo.wxApplicationList.length">下移</el-button>
<el-button type="text" @click="addMiniApp(row, false, row.$index)">编辑</el-button>
<el-button type="text" @click="removeMiniApp(row)">删除</el-button>
</template>
</el-table-column>
</ai-table>
</ai-dialog>
<ai-dialog title="应用" :visible.sync="miniAppDialog" width="800px" @onCancel="miniAppDialog = false" @onConfirm="addMiniAppConfirm" >
<el-form ref="miniApppRuleForm" :model="miniAppInfo" :rules="miniAppFormRules" size="small" label-width="120px">
<el-form-item label="icon图片" prop="icon">
<ai-uploader v-model="miniAppInfo.iconList" @change="changeIcon" :instance="instance" :limit="1"></ai-uploader>
</el-form-item>
<el-form-item label="应用名称" prop="title">
<el-input placeholder="请输入应用名称" :maxlength="10" show-word-limit v-model="miniAppInfo.title"></el-input>
</el-form-item>
<el-form-item label="页面路径" prop="accessPath">
<el-input placeholder="请输入页面路径" :maxlength="50" show-word-limit v-model="miniAppInfo.accessPath"></el-input>
</el-form-item>
</el-form>
</ai-dialog>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
label: '企微应用配置',
name: 'AppWxcpAppSet',
props: {
instance: Function,
dict: Object,
permissions: Function,
},
data() {
return {
search: {
title: '',
},
tableData: [],
page: {
size: 10,
current: 1,
total: 0,
},
visible: false,
dialog: {
title: '',
},
dialogInfo: {},
formRules: {
banner: [{required: true, message: '请添加banner封面', trigger: 'blur'}],
title: [{required: true, message: '请输入主页名称', trigger: 'blur'}],
},
colConfigs: [
{prop: 'banner', label: 'banner封面', slot: 'banner'},
{prop: 'title', label: '主页名称', width: 150},
{prop: 'id', label: '配置id', width: 300},
{prop: 'appNames', label: '应用列表'},
{slot: 'option', label: '操作', width: 200},
],
miniAppColConfigs: [
{prop: 'showIndex', label: '序号', width: 80, align: 'center'},
{prop: 'icon', label: 'icon图片', slot: 'icon', align: 'center'},
{prop: 'title', label: '应用名称',},
{prop: 'accessPath', label: '页面路径'},
{slot: 'option', label: '操作', align: 'center'},
],
miniAppFormRules: {
icon: [{required: true, message: '请添加icon图片', trigger: 'blur'}],
title: [{required: true, message: '请输入应用名称', trigger: 'blur'}],
accessPath: [{ required: true, message: '请输入页面路径', trigger: 'blur' }]
},
miniAppDialog: false,
miniAppInfo: {},
isAddMiniApp: false,
editMiniIndex: '',
}
},
computed: {
...mapState(['user']),
},
created() {
this.getList()
},
mounted() {
},
methods: {
add(row, title) {
this.dialog.title = title
this.visible = true
if(row && row.id) {
this.instance.post(`/app/appwxapplication/queryDetailById?id=${row.id}`).then(res => {
if (res?.code == 0) {
this.dialogInfo = res.data
this.dialogInfo.bannerList = [{url: this.dialogInfo.banner}]
this.dialogInfo.wxApplicationList = this.dialogInfo.wxApplicationList || []
}
})
}else {
this.dialogInfo = {}
this.dialogInfo.wxApplicationList = []
}
},
addConfirm() {
this.$refs.ruleForm.validate((valid) => {
if (valid) {
this.instance.post(`/app/appwxapplication/addOrUpdate`, {...this.dialogInfo}).then(res => {
if (res?.code == 0) {
this.$message.success('新增成功')
this.visible = false
this.getList()
}
})
}
})
},
remove(id) {
this.$confirm('删除后不可恢复,是否要删除该配置?', {
type: 'error',
}).then(() => {
this.instance.post(`/app/appwxapplication/delete?ids=${id}`).then((res) => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
addMiniApp(row, isAddMiniApp, index) {
this.miniAppInfo = row || {}
if(this.miniAppInfo.icon) {
this.miniAppInfo.iconList = [{url: this.miniAppInfo.icon}]
this.editMiniIndex = index
}
this.miniAppDialog = true
this.isAddMiniApp = isAddMiniApp
},
removeMiniApp(row) {
this.$confirm('删除后不可恢复,是否要删除该应用?', {
type: 'error',
}).then(() => {
var showIndex = row.showIndex
this.dialogInfo.wxApplicationList.map((item) => {
if(item.showIndex > showIndex) {
item.showIndex = item.showIndex - 1
}
})
this.dialogInfo.wxApplicationList.splice(row.showIndex-1, 1)
})
},
addMiniAppConfirm() {
this.$refs.miniApppRuleForm.validate((valid) => {
if (valid) {
if(this.isAddMiniApp) { //新增
this.miniAppInfo.showIndex = this.dialogInfo.wxApplicationList.length + 1
this.dialogInfo.wxApplicationList.push(this.miniAppInfo)
}else {
this.dialogInfo.wxApplicationList[this.editMiniIndex] = this.miniAppInfo
}
this.miniAppDialog = false
}
})
},
top(row) {
var list = JSON.parse(JSON.stringify(this.dialogInfo.wxApplicationList))
var oldRow = list[row.showIndex - 2]
oldRow.showIndex ++
row.showIndex --
list[row.showIndex - 1] = row
list[row.showIndex] = oldRow
this.$set(this.dialogInfo, 'wxApplicationList', list)
},
next(row) {
var list = JSON.parse(JSON.stringify(this.dialogInfo.wxApplicationList))
var oldRow = list[row.showIndex]
oldRow.showIndex --
row.showIndex ++
list[row.showIndex-1] = row
list[oldRow.showIndex-1] = oldRow
this.$set(this.dialogInfo, 'wxApplicationList', list)
},
changeBanner(e) {
this.dialogInfo.bannerList = e
this.dialogInfo.banner = e[0].url
},
changeIcon(e) {
this.miniAppInfo.iconList = e
this.miniAppInfo.icon = e[0].url
},
getList() {
this.instance.post(`/app/appwxapplication/list`, null, {
params: {
...this.search,
...this.page,
},
})
.then((res) => {
if (res.code == 0) {
res.data.records.map((item) => {
item.appNames = ''
item.wxApplicationList.map((app, index) => {
if(index == 0) {
item.appNames = app.title
}
if(index > 0) {
item.appNames = item.appNames + `,${app.title}`
}
})
})
this.tableData = res.data.records
this.page.total = res.data.total
}
})
.catch(() => {
})
},
},
}
</script>
<style lang="scss">
.AppWxcpAppSet {
height: 100%;
// padding: 15px 10px;
.banner-img {
width: 50px;
height: 50px;
}
.dialog-title-flex{
display: flex;
justify-content: space-between;
margin-bottom: 8px;
h3{
font-size: 16px;
color: #333;
}
span{
color: #26f;
cursor: pointer;
}
}
}
</style>

View File

@@ -37,17 +37,20 @@
</el-form-item>
</el-form>
</div>
<ai-dialog title="选择网格" :visible.sync="showGrid" :customFooter="true" :destroyOnClose="true"
@opened="beforeSelectTree" border width="720px">
<ai-dialog title="选择网格" :visible.sync="showGrid" :customFooter="true" :destroyOnClose="true" border width="720px">
<div class="grid">
<el-tree :data="treeObj.treeList" :props="treeObj.defaultProps" node-key="id" ref="tree"
:check-strictly="true" show-checkbox
:default-checked-keys="treeObj.checkedKeys" default-expand-all
@check="onCheckChange">
<template slot-scope="{node,data}">
<el-tooltip :content="node.label">
<div class="el-tree-node__label" v-text="node.label"/>
</el-tooltip>
<el-tree
:data="treeObj.treeList"
:props="treeObj.defaultProps"
node-key="id"
ref="tree"
:check-strictly="true"
show-checkbox
:default-checked-keys="treeObj.checkedKeys"
:default-expanded-keys="treeObj.checkedKeys"
@check="onCheckChange">
<template slot-scope="{node}">
<div class="el-tree-node__label" :title="node.label" v-text="node.label"/>
</template>
</el-tree>
</div>
@@ -98,7 +101,7 @@ export default {
treeObj: {
treeList: [],
defaultProps: {
children: "girdList",
children: "children",
label: "girdName",
},
checkedKeys: [],
@@ -115,6 +118,7 @@ export default {
},
created() {
this.beforeSelectTree()
this.dict.load('cardType', 'sex', 'nation').then(() => {
if (this.params && this.params.id) {
this.getInfo(this.params.id)
@@ -123,15 +127,23 @@ export default {
},
methods: {
beforeSelectTree() {
this.treeObj.checkedKeys = [];
this.instance.post(`/app/appgirdinfo/listAll`, null, null).then((res) => {
this.instance.post(`/app/appgirdinfo/listAll3`).then((res) => {
if (res.code == 0) {
this.treeObj.treeList = this.format(res.data)
this.treeObj.checkedKeys = []
if (this.form.girdInfoList.length) {
this.form.girdInfoList.map((e) => {
this.treeObj.checkedKeys.push(e.id);
});
}
this.treeObj.treeList = res.data.filter(e => !e.parentGirdId)
this.treeObj.treeList.map(p => this.addChild(p, res.data.map(v => {
return {
...v
}
}), {
parent: 'parentGirdId'
}))
}
});
},

View File

@@ -17,10 +17,10 @@
<el-input v-model="forms.girdName" placeholder="请输入…" :maxlength="50" show-word-limit clearable/>
</el-form-item>
<el-form-item label="网格长" prop="girdMemberManageList">
<ai-user-picker :instance="instance" v-model="forms.girdMemberManageList" :props="{label:'name', id: 'id'}"/>
<ai-user-selecter isShowUser :instance="instance" v-model="forms.girdMemberManageList" :props="{label:'name', id: 'id'}"/>
</el-form-item>
<el-form-item label="网格员" prop="girdMemberList">
<ai-user-picker :instance="instance" v-model="forms.girdMemberList" :props="{label:'name', id: 'id'}"/>
<ai-user-selecter isShowUser :instance="instance" v-model="forms.girdMemberList" :props="{label:'name', id: 'id'}"/>
</el-form-item>
</template>
</ai-card>
@@ -185,13 +185,15 @@ export default {
girdMemberManageList: res.data.girdMemberManageList ? res.data.girdMemberManageList.map(v => {
return {
...v,
id: v.wxUserId
id: v.wxUserId,
avatar: v.photo
}
}) : [],
girdMemberList: res.data.girdMemberList ? res.data.girdMemberList.map(v => {
return {
...v,
id: v.wxUserId
id: v.wxUserId,
avatar: v.photo
}
}) : []
};

View File

@@ -12,8 +12,9 @@
@node-click="handleNodeClick"
node-key="id"
ref="tree"
:expand-on-click-node="false"
:defaultExpandedKeys="treeObj.defaultExpandedKeys"
:filter-node-method="filterNode"
default-expand-all
highlight-current>
<template slot-scope="{node,data}">
<div v-text="node.label"/>
@@ -153,7 +154,7 @@ export default {
treeObj: {
treeList: [],
defaultProps: {
children: "girdList",
children: "children",
label: "girdName",
},
defaultExpandedKeys: [],
@@ -216,12 +217,26 @@ export default {
this.getList();
},
getTreeList() {
this.instance.post("/app/appgirdinfo/listAllByTop").then(res => {
this.instance.post(`/app/appgirdinfo/listAll3`).then((res) => {
if (res?.data) {
this.treeObj.treeList = [res.data];
this.info = res.data
this.treeObj.treeList = res.data.filter(e => !e.parentGirdId)
const parentGirdId = this.treeObj.treeList[0].id
this.treeObj.treeList.map(p => this.addChild(p, res.data.map(v => {
if (v.id === parentGirdId) {
this.treeObj.defaultExpandedKeys.push(v.id)
}
return {
...v
}
}), {
parent: 'parentGirdId'
}))
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(this.info.id)
this.info = this.treeObj.treeList[0]
this.$refs.tree.setCurrentKey(parentGirdId)
})
}
});

View File

@@ -13,19 +13,19 @@
</header>
<div class="tree-div">
<el-tree
:data="treeObj.treeList"
:props="treeObj.defaultProps"
@node-click="handleNodeClick"
node-key="id"
ref="tree"
:expand-on-click-node="false"
:filter-node-method="filterNode"
default-expand-all
highlight-current>
:data="treeObj.treeList"
:props="treeObj.defaultProps"
@node-click="handleNodeClick"
node-key="id"
ref="tree"
:expand-on-click-node="false"
:filter-node-method="filterNode"
:default-expanded-keys="treeObj.defaultExpandedKeys"
highlight-current>
<template slot-scope="{node,data}">
<el-tooltip :content="node.label">
<div class="el-tree-node__label" v-text="node.label"/>
</el-tooltip>
<!-- <el-tooltip :content="node.label"> -->
<div class="el-tree-node__label" :title="node.label" v-text="node.label"/>
<!-- </el-tooltip> -->
</template>
</el-tree>
</div>
@@ -61,7 +61,7 @@ export default {
treeObj: {
treeList: [],
defaultProps: {
children: "girdList",
children: "children",
label: "girdName",
},
defaultExpandedKeys: [],
@@ -88,9 +88,7 @@ export default {
},
created() {
this.dict.load("onlineStatus")
this.getTreeList().then(() => {
this.getLeafNodes()
})
this.getTreeList()
},
watch: {
filterText(val) {
@@ -103,15 +101,29 @@ export default {
return data.girdName.indexOf(value) !== -1;
},
getTreeList() {
return this.instance.post(`/app/appgirdinfo/listAll`).then((res) => {
if (res.code == 0) {
this.treeObj.treeList = res.data;
this.instance.post(`/app/appgirdinfo/listAll3`).then((res) => {
if (res?.data) {
this.$nextTick(() => {
res.data.length && this.$refs.tree.setCurrentKey(res.data[0].id)
this.$refs.tree.setCurrentKey(res.data.id)
})
this.treeObj.treeList = res.data.filter(e => !e.parentGirdId)
const parentGirdId = this.treeObj.treeList[0].id
this.treeObj.treeList.map(p => this.addChild(p, res.data.map(v => {
if (v.id === parentGirdId) {
this.treeObj.defaultExpandedKeys.push(v.id)
}
return {
...v
}
}), {
parent: 'parentGirdId'
}))
this.getLeafNodes()
}
})
});
},
onMapInit() {

View File

@@ -25,9 +25,9 @@
<el-form-item label="网格员姓名" prop="name">
<el-input v-model="forms.name" placeholder="请选择网格员" disabled>
<template #append>
<ai-wechat-selecter :isMultiple="false" refs="addTags" :instance="instance" v-model="users" @change="getSelectPerson">
<ai-user-selecter :isMultiple="false" refs="addTags" :instance="instance" v-model="users" @change="getSelectPerson">
<el-button size="small" type="primary"><span style="color: #fff">选择成员</span></el-button>
</ai-wechat-selecter>
</ai-user-selecter>
</template>
</el-input>
</el-form-item>
@@ -197,18 +197,18 @@
</template>
</ai-card>
</el-form>
<ai-dialog title="选择网格" :visible.sync="showGrid" :customFooter="true" :destroyOnClose="true" border width="720px">
<ai-dialog title="选择网格" :visible.sync="showGrid" :customFooter="true" border width="720px">
<div class="grid">
<el-tree
:data="treeObj.treeList"
:props="treeObj.defaultProps"
node-key="id"
ref="tree"
:check-strictly="true"
show-checkbox
:default-checked-keys="currCheckedKeys"
default-expand-all
@check="onCheckChange">
:data="treeList"
:props="defaultProps"
node-key="id"
ref="tree"
:check-strictly="true"
show-checkbox
:default-expanded-keys="currCheckedKeys"
:default-checked-keys="currCheckedKeys"
@check="onCheckChange">
</el-tree>
</div>
<div class="dialog-footer" slot="footer">
@@ -266,13 +266,14 @@ export default {
}],
showGrid: false,
treeObj: {
treeList: [],
defaultProps: {
children: "girdList",
label: "girdName",
},
checkedKeys: [],
},
treeList: [],
defaultProps: {
children: "children",
label: "girdName",
},
defaultExpandedKeys: [],
girdInfoStr: '',
photoList: [],
title: "添加网格员",
@@ -391,12 +392,26 @@ export default {
},
beforeSelectTree() {
this.treeObj.checkedKeys = [];
this.instance.post(`/app/appgirdinfo/listAll`, null, null).then((res) => {
this.instance.post(`/app/appgirdinfo/listAll3`, null, null).then((res) => {
if (res.code == 0) {
this.treeObj.treeList = res.data;
this.forms.girdInfoList.map((e) => {
this.treeObj.checkedKeys.push(e.id);
});
this.treeList = res.data.filter(e => !e.parentGirdId)
const parentGirdId = this.treeList[0].id
this.treeList.map(p => this.addChild(p, res.data.map(v => {
if (v.id === parentGirdId) {
this.defaultExpandedKeys.push(v.id)
}
return {
...v
}
}), {
parent: 'parentGirdId'
}))
}
});
},

View File

@@ -21,12 +21,12 @@
</template>
<template slot="right">
<el-input
v-model="searchObj.name"
v-model="searchObj.con"
size="small"
placeholder="网格员/责任网格"
v-throttle="() => {page.current = 1, getList()}"
clearable
@clear="(searchObj.name = '', page.current = 1), getList()"
@clear="(searchObj.con = '', page.current = 1), getList()"
suffix-icon="iconfont iconSearch"/>
</template>
</ai-search-bar>
@@ -65,7 +65,7 @@ export default {
data() {
return {
searchObj: {
name: "",
con: "",
selectionDate: "",
},
page: {

View File

@@ -11,16 +11,22 @@ const install = function (Vue, params) {
if (install.installed) return
// 遍历注册全局组件
let apps = []
let contexts = import.meta.glob('./**/App*.vue')
let contexts = require.context('.', true, /\.(\/.+)\/App[^\/]+\.vue$/)
if (contexts) {
Object.keys(contexts).map(path => {
if (/App[A-Z]\w+\.vue/.test(path)) {
return contexts?.[path]()?.then(file => {
apps.push(file.default)
return Vue.component(file.default.name, file.default)
})
contexts.keys().map(e => {
if (contexts(e).default) {
if (params?.apps) {
if (params?.apps.includes(contexts(e).default.name)) {
apps.push(contexts(e).default)
Vue.component(contexts(e).default.name, contexts(e).default)
}
} else {
apps.push(contexts(e).default)
Vue.component(contexts(e).default.name, contexts(e).default)
}
}
})
!!params?.showList && console.log(apps.map(e => e.name))
}
core?.map(app => {
apps.push(app.component)

View File

@@ -7,67 +7,63 @@
<template #content>
<ai-search-bar>
<template slot="left">
<el-button type="primary" icon="iconfont iconAdd" @click="handleAdd">添加</el-button>
<el-button type="primary" icon="iconfont iconAdd" @click="handleAdd"
>添加</el-button
>
</template>
<template slot="right">
<el-input
v-model="search.title"
class="search-input"
size="small"
placeholder="请输入课程主题"
clearable
v-throttle="
v-model="search.title"
class="search-input"
size="small"
placeholder="请输入课程主题"
clearable
v-throttle="
() => {
(search.current = 1), getList();
}
"
@clear="(search.current = 1), (search.title = ''), getList()"
suffix-icon="iconfont iconSearch"
@clear="(search.current = 1), (search.title = ''), getList()"
suffix-icon="iconfont iconSearch"
>
</el-input>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
stripe
:total="total"
:current.sync="page.current"
:size.sync="page.size"
style="margin-top: 10px"
@getList="getList"
:dict="dict"
:tableData="tableData"
:col-configs="colConfigs"
stripe
:total="total"
:current.sync="page.current"
:size.sync="page.size"
style="margin-top: 10px"
@getList="getList"
>
<el-table-column
slot="options"
label="操作"
align="center"
width="230px"
fixed="right"
slot="options"
label="操作"
align="center"
width="230px"
fixed="right"
>
<div slot-scope="{ row }" class="table-options">
<el-button
type="text"
:title="row.status == 0 ? '发布' : '取消发布'"
@click="handleChange(row)"
>{{ row.status == 0 ? "发布" : "取消发布" }}
</el-button
type="text"
:title="row.status == 0 ? '发布' : '取消发布'"
@click="handleChange(row)"
>{{ row.status == 0 ? "发布" : "取消发布" }}</el-button
>
<el-button type="text" title="添加" @click="handleAddSeries(row)"
>添加剧集
</el-button
>添加剧集</el-button
>
<el-button type="text" title="详情" @click="handleDetail(row)"
>详情
</el-button
>详情</el-button
>
<el-button type="text" title="编辑" @click="handleEdit(row)"
>编辑
</el-button
>编辑</el-button
>
<el-button type="text" title="删除" @click="handleDelete(row)"
>删除
</el-button
>删除</el-button
>
</div>
</el-table-column>
@@ -75,14 +71,14 @@
</template>
</ai-list>
<component
:is="comp"
v-else
:row="row"
:instance="instance"
:dict="dict"
:permissions="permissions"
@back="back"
:isEdit="isEdit"
:is="comp"
v-else
:row="row"
:instance="instance"
:dict="dict"
:permissions="permissions"
@back="back"
:isEdit="isEdit"
></component>
</section>
</template>
@@ -90,12 +86,12 @@
<script>
import partyClassAdd from "./components/partyClassAdd";
import seriesManage from "./components/seriesManage";
import {mapState} from "vuex";
import { mapState } from "vuex";
export default {
name: "AppPartyHistoryClass",
label: "党员学习",
components: {partyClassAdd, seriesManage},
components: { partyClassAdd, seriesManage },
props: {
instance: Function,
dict: Object,
@@ -125,21 +121,37 @@ export default {
...mapState(["user"]),
colConfigs() {
return [
{label: "课程主题", prop: "title"},
{label: "更新状态", dict: "classroomUpdateStatus", prop: "updateStatus"},
{label: "更新时间", prop: "updateDate"},
{label: "发布时间", prop: "createDate"},
{label: "发布状态", prop: "status", dict: "newsCenterStatus"},
{label: "发布组织", prop: "organizationName"},
{slot: "options"},
{ label: "课程主题", prop: "title" },
{
label: "更新状态",
render: (h, { row }) => [
<span>
{" "}
{this.dict.getLabel(
"classroomUpdateStatus",
row.updateStatus
)}{" "}
</span>,
],
},
{ label: "更新时间", prop: "updateDate" },
{ label: "发布时间", prop: "createDate" },
{
label: "发布状态",
render: (h, { row }) => [
<span> {this.dict.getLabel("newsCenterStatus", row.status)} </span>,
],
},
{ label: "发布组织", prop: "organizationName" },
{ slot: "options" },
];
},
},
methods: {
handleChange(row) {
this.$confirm(`是否确定要${row.status == 0 ? "发布" : "取消发布"}?`).then(
(_) => {
this.instance
(_) => {
this.instance
.post("/app/apppartyclassroom/addOrUpdate", {
id: row.id,
status: row.status == 0 ? 1 : 0,
@@ -147,12 +159,12 @@ export default {
.then((res) => {
if (res.code == 0) {
this.$message.success(
`${row.status == 0 ? "发布成功" : "取消发布成功"}`
`${row.status == 0 ? "发布成功" : "取消发布成功"}`
);
this.getList();
}
});
}
}
);
},
handleAddSeries(row) {
@@ -160,20 +172,20 @@ export default {
this.comp = "seriesManage";
this.row = row;
},
handleDelete({id}) {
handleDelete({ id }) {
this.$confirm("确定要删除吗?").then((_) => {
this.instance
.post("/app/apppartyclassroom/delete", null, {
params: {
ids: id,
},
})
.then((res) => {
if (res.code == 0) {
this.$message.success("删除成功");
this.getList();
}
});
.post("/app/apppartyclassroom/delete", null, {
params: {
ids: id,
},
})
.then((res) => {
if (res.code == 0) {
this.$message.success("删除成功");
this.getList();
}
});
});
},
handleEdit(row) {
@@ -216,18 +228,18 @@ export default {
},
getList() {
this.instance
.post("/app/apppartyclassroom/list", null, {
params: {
...this.page,
...this.search,
},
})
.then((res) => {
if (res?.data) {
this.tableData = res.data.records;
this.total = res.data.total;
}
});
.post("/app/apppartyclassroom/list", null, {
params: {
...this.page,
...this.search,
},
})
.then((res) => {
if (res?.data) {
this.tableData = res.data.records;
this.total = res.data.total;
}
});
},
},
created() {

View File

@@ -70,7 +70,7 @@ export default {
...mapState(["user"]),
colConfigs() {
return [
{label: "类型", render: (h, {row}) =>h('p',row.type == 1 ? '单选题' : '多选题')},
{label: "类型", render: (h, {row}) => [< span> {row.type == 1 ? '单选题' : '多选题'} < /span>]},
{label: "题目", prop: "title"},
{label: "创建时间", prop: "createDate"},
{slot: "options"}

View File

@@ -26,7 +26,6 @@
:total="total"
:current.sync="page.current"
:size.sync="page.size"
:dict="dict"
@getList="getList">
<el-table-column slot="option" label="操作" fixed="right" width="300" align="center">
<template slot-scope="{ row }">
@@ -81,7 +80,7 @@
{prop: 'viewTotal', label: '浏览数', align: 'center'},
{prop: 'publishUserName', label: '发布人'},
{prop: 'publishTime', label: '发布时间'},
{prop: 'status', label: '发布状态', align: 'center',dict:"newsCenterStatus"},
{prop: 'status', label: '发布状态', align: 'center',render:(h,{row})=>[<span>{this.$dict.getLabel('newsCenterStatus',row.status)}</span>]},
{slot: 'option'},
]
}

View File

@@ -15,14 +15,13 @@
</el-button>
</template>
</ai-search-bar>
<ai-table :tableData="tableData"
:col-configs="colConfigs"
:total="total"
ref="aitableex"
:current.sync="search.current"
:size.sync="search.size"
:dict="dict"
@getList="getAppLeaveMessage">
<ai-table :tableData="tableData"
:col-configs="colConfigs"
:total="total"
ref="aitableex"
:current.sync="search.current"
:size.sync="search.size"
@getList="getAppLeaveMessage">
<el-table-column label="是否公示" slot="isPublic" align="center" width="150">
<template v-slot="{row}">
<el-switch v-model="row.isPublic" @change="onChange(row)" active-value="0" inactive-value="1"
@@ -32,9 +31,7 @@
<el-table-column label="操作" slot="options" align="center" width="150">
<template v-slot="{row}">
<el-button type="text" title="详情" @click="toDetail(row.id)"
v-if="$permissions('app_appleavemessagereply_detail')">详情
</el-button>
<el-button type="text" title="详情" @click="toDetail(row.id)" v-if="$permissions('app_appleavemessagereply_detail')">详情</el-button>
</template>
</el-table-column>
</ai-table>
@@ -126,6 +123,7 @@ export default {
onChange({id, isPublic}) {
this.instance.post(`/app/appleavemessage/public?id=${id}`).then(res => {
if (res.code == 0) {
console.log(isPublic)
this.$message.success(isPublic == 1 ? "已公示" : "不公示")
this.getAppLeaveMessage()
}
@@ -215,14 +213,15 @@ export default {
...mapState(['user']),
colConfigs() {
return [
{prop: 'msgCode', label: '编号', align: 'center'},
{prop: 'title', label: '标题', align: 'center'},
{prop: 'type', label: '类型', align: 'center', dict: "leaveMessageType"},
{prop: 'leaveName', label: '留言人', align: 'center'},
{prop: 'createTime', label: '留言提交时间', align: 'center'},
{prop: 'lastReplyTime', label: '最后回复时间', align: 'center'},
{slot: 'isPublic'},
{slot: 'options'},
{ prop: 'msgCode', label: '编号', align: 'center' },
{ prop: 'title', label: '标题', align: 'center' },
{ prop: 'type', label: '类型', align: 'center',
render:(h,{row})=>[<span>{this.dict.getLabel('leaveMessageType', row.type)}</span>] },
{ prop: 'leaveName', label: '留言', align: 'center' },
{ prop: 'createTime', label: '留言提交时间', align: 'center' },
{ prop: 'lastReplyTime', label: '最后回复时间', align: 'center' },
{ slot: 'isPublic'},
{ slot: 'options'},
]
}
}

View File

@@ -7,62 +7,58 @@
<el-select v-model="search.department" placeholder="请选择所属部门" @change="page.current=1,getList()"
size="small" clearable>
<el-option
v-for="(item,i) in dict.getDict('hbDepartment')" :key="i"
:label="item.dictName"
:value="item.dictValue">
v-for="(item,i) in dict.getDict('hbDepartment')" :key="i"
:label="item.dictName"
:value="item.dictValue">
</el-option>
</el-select>
<el-select v-model="search.classificationId" placeholder="请选择所属分类" @change="page.current=1,getList()"
size="small" clearable>
<el-option
v-for="(item,i) in classList" :key="i"
:label="item.name"
:value="item.id">
v-for="(item,i) in classList" :key="i"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</template>
<template #right>
<el-input
v-model="search.processName"
size="small"
placeholder="事项名称/创建人"
@clear="search={},page.current=1,getList()"
v-throttle="() => {page.current = 1, getList()}"
clearable
suffix-icon="iconfont iconSearch"/>
v-model="search.processName"
size="small"
placeholder="事项名称/创建人"
@clear="search={},page.current=1,getList()"
v-throttle="() => {page.current = 1, getList()}"
clearable
suffix-icon="iconfont iconSearch"/>
</template>
</ai-search-bar>
<ai-search-bar>
<template #left>
<el-button type="primary" icon="iconfont iconAdd" @click="goPage(tab.value==0 ? 'addConfig':'guidance')">
添加{{ tab.value == 0 ? '事项' : '办事指南' }}
</el-button>
<el-button type="primary" icon="iconfont iconAdd" @click="goPage(tab.value==0 ? 'addConfig':'guidance')">添加{{tab.value==0?'事项':'办事指南'}}</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:header-cell-style="{fontWeight:'bold',color:'#333'}"
:total="page.total"
:current.sync="page.current"
:size.sync="page.size"
:dict="dict"
@getList="getList">
:tableData="tableData"
:col-configs="colConfigs"
:header-cell-style="{fontWeight:'bold',color:'#333'}"
:total="page.total"
:current.sync="page.current"
:size.sync="page.size"
@getList="getList">
<el-table-column label="是否启用" slot="processDefStatus" align="center" width="150">
<template v-slot="{row}">
<el-switch
v-model="row.processDefStatus"
@change="onChange(row)" active-value="1" inactive-value="0"
active-color="#5088FF"
inactive-color="#D0D4DC">
v-model="row.processDefStatus"
@change="onChange(row)" active-value="1" inactive-value="0"
active-color="#5088FF"
inactive-color="#D0D4DC">
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" slot="options" align="center" width="150">
<template v-slot="{row}">
<div class="table-options">
<el-button type="text" title="编辑" @click="goPage(tab.value==0 ? 'addConfig':'guidance',row)">编辑
</el-button>
<el-button type="text" title="编辑" @click="goPage(tab.value==0 ? 'addConfig':'guidance',row)">编辑</el-button>
<el-button type="text" title="删除" @click="delInfo(row)">删除</el-button>
</div>
</template>
@@ -74,141 +70,145 @@
</template>
<script>
import day from 'dayjs'
import day from 'dayjs'
export default {
name: "configList",
props: {
instance: Function,
dict: Object,
permissions: Function,
tab: Object,
},
data() {
return {
search: {
department: "",
classificationId: "",
processName: "",
export default {
name: "configList",
props: {
instance: Function,
dict: Object,
permissions: Function,
tab: Object,
},
data() {
return {
search: {
department: "",
classificationId: "",
processName: "",
},
page: {current: 1, size: 10},
total: 0,
row: {},
tableData: [],
classList: [],
}
},
computed: {
colConfigs() {
return [
{
prop: 'processName',
align: 'left',
label: '事项名称',
},
{
prop: 'department',
align: 'left',
label: '所属部门',
render: (h, {row}) => [ < span > {this.dict.getLabel('hbDepartment', row.department)} < /span>]
},
{
prop: 'classificationName',
align: 'center',
label: '所属分类',
},
{
prop: 'timeLimit',
align: 'center',
label: '办结时限(日)',
},
{
prop: 'createUserName',
align: 'center',
label: '创建人',
},
{
prop: 'createTime',
align: 'center',
label: '最后修改时间',
render: (h, {row}) => [ < span > {day(row.createTime
).
format("YYYY-MM-DD HH:mm")
}<
/span>]
},
{slot: 'processDefStatus', align:'center', label:'是否启用',},
{ slot: 'options',align:'center',label:'操作',},
].filter(e=>this.tab.value==0 ? true : (e.prop!="timeLimit"))
},
page: {current: 1, size: 10},
total: 0,
row: {},
tableData: [],
classList: [],
}
},
computed: {
colConfigs() {
return [
{
prop: 'processName',
align: 'left',
label: '事项名称',
},
{
prop: 'department',
align: 'left',
label: '所属部门',
dict: "hbDepartment"
},
{
prop: 'classificationName',
align: 'center',
label: '所属分类',
},
{
prop: 'timeLimit',
align: 'center',
label: '办结时限(日)',
},
{
prop: 'createUserName',
align: 'center',
label: '创建人',
},
{
prop: 'createTime',
align: 'center',
label: '最后修改时间',
render: (h, {row}) => h('p', day(row.createTime).format("YYYY-MM-DD HH:mm"))
},
{slot: 'processDefStatus', align: 'center', label: '是否启用',},
{slot: 'options', align: 'center', label: '操作',},
].filter(e => this.tab.value == 0 ? true : (e.prop != "timeLimit"))
},
},
methods: {
goPage(comp, row = {}) {
this.$emit("goPage", {comp, row})
},
/**
* 获取分类
*/
getClassification() {
this.instance.post(`/app/zwspapprovalclassification/list`, null, {
params: {
current: 1,
status: 1,
size: 9999
}
}).then(res => {
if (res && res.data) {
this.classList = res.data.records
}
})
},
/**
* 删除
* */
delInfo({id}) {
this.$confirm("是否删除").then(() => {
this.instance.post(`/app/approval-process-def/delete?id=${id}`).then(res => {
methods: {
goPage(comp, row = {}) {
this.$emit("goPage", {comp, row})
},
/**
* 获取分类
*/
getClassification() {
this.instance.post(`/app/zwspapprovalclassification/list`, null, {
params:{
current: 1,
status: 1,
size: 9999
}
}).then(res => {
if (res && res.data) {
this.classList = res.data.records
}
})
},
/**
* 删除
* */
delInfo({id}) {
this.$confirm("是否删除").then(() => {
this.instance.post(`/app/approval-process-def/delete?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success("删除成功")
this.getList()
}
})
})
},
/**
* 启用,停用
*/
onChange({id, processDefStatus}) {
this.instance.post(`/app/approval-process-def/enable-disable`, null, {
params: {id}
}).then(res => {
if (res.code == 0) {
this.$message.success("删除成功")
this.$message.success(processDefStatus == 0 ? "不启用" : "已启用")
this.getList()
}
})
})
},
/**
* 启用,停用
*/
onChange({id, processDefStatus}) {
this.instance.post(`/app/approval-process-def/enable-disable`, null, {
params: {id}
}).then(res => {
if (res.code == 0) {
this.$message.success(processDefStatus == 0 ? "不启用" : "已启用")
this.getList()
}
})
},
},
getList() {
this.instance.post(`/app/approval-process-def/list`, null, {
params: {
...this.page,
...this.search,
processType: this.tab.value
},
}).then(res => {
if (res?.data) {
this.tableData = res.data.records
this.page.total = res.data.total;
}
})
getList() {
this.instance.post(`/app/approval-process-def/list`, null, {
params: {
...this.page,
...this.search,
processType: this.tab.value
},
}).then(res => {
if (res?.data) {
this.tableData = res.data.records
this.page.total = res.data.total;
}
})
},
},
},
mounted() {
this.getList()
this.getClassification()
},
}
mounted() {
this.getList()
this.getClassification()
},
}
</script>
<style lang="scss" scoped>
.config-list {
height: 100%;
}
.config-list {
height: 100%;
}
</style>

View File

@@ -455,14 +455,8 @@ export default {
},
getWxGroups() {
this.instance.post(`/app/appmasssendingtask/queryWxGroups?sendScope=${this.form.sendScope}`, null, {
data: {
filterCriteria: this.form.filterCriteria.join(',')
},
headers: {'Content-Type': 'application/json;charset=utf-8'},
transformRequest: [function (data) {
return data.filterCriteria
}]
this.instance.post(`/app/appmasssendingtask/queryWxGroups?sendScope=${this.form.sendScope}`, {
filterCriteria: this.form.filterCriteria.join(',')
}).then(res => {
if (res.code === 0) {
this.form.wxGroups = res.data

View File

@@ -143,7 +143,7 @@
onUserChange (e) {
if (e.length) {
this.name = e[0].name
this.search.createUserId = e[0].id
this.search.createUserId = e[0].openId
} else {
this.search.createUserId = ''
this.name = ''

View File

@@ -0,0 +1,79 @@
<template>
<div class="AppAnnounceResident">
<!-- <keep-alive :include="['List']"> -->
<component ref="component" :is="component" @change="onChange" :params="params" :instance="instance" :dict="dict"></component>
<!-- </keep-alive> -->
</div>
</template>
<script>
import List from './components/List'
import Add from './components/Add'
import Detail from './components/Detail'
export default {
name: 'AppAnnounceResident',
label: '群发居民',
props: {
instance: Function,
dict: Object
},
data () {
return {
component: 'List',
params: {},
include: []
}
},
components: {
Add,
List,
Detail
},
mounted () {
if (this.$route.params.id) {
this.component = 'Detail'
this.params = {
id: this.$route.params.id
}
}
},
methods: {
onChange (data) {
if (data.type === 'Add') {
this.component = 'Add'
this.params = data.params
}
if (data.type === 'Detail') {
this.component = 'Detail'
this.params = data.params
}
if (data.type === 'list') {
this.component = 'List'
this.params = data.params
this.$nextTick(() => {
if (data.isRefresh) {
this.$refs.component.getList()
}
})
}
}
}
}
</script>
<style lang="scss">
.AppAnnounceResident {
height: 100%;
background: #F3F6F9;
overflow: auto;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,692 @@
<template>
<ai-detail class="AppAnnounceDetail">
<template slot="title">
<ai-title title="群发详情" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</template>
<template slot="content">
<ai-card title="基础信息">
<template #right>
<div class="right-tips" v-if="info.status === '4'">
<el-tooltip
placement="top"
content="任务开始后3天内15分钟更新1次3天后访问页面时触发更新1小时最多刷新1次">
<i class="iconfont iconDetails"></i>
</el-tooltip>
<span>数据更新于{{ info.dataUpdateTime }}</span>
</div>
</template>
<template #content>
<ai-wrapper>
<ai-info-item label="任务名称" isLine :value="info.taskTitle"></ai-info-item>
<ai-info-item label="任务状态" isLine>
<span :style="{ color: dict.getColor('mstStatus', info.status) }">{{ dict.getLabel('mstStatus', info.status) }}</span>
</ai-info-item>
<ai-info-item label="创建人" isLine>
<div class="user">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/user.png" />
<span>{{ info.createUserName }}</span>
<span>{{ info.createUserDeptName }}</span>
</div>
</ai-info-item>
<ai-info-item label="审批人" isLine v-if="info.enableExamine === '1'">
<div class="user-wrapper">
<div class="user" v-for="(item, index) in info.examines" :key="index">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/user.png" />
<span>{{ item.examineUserName }}</span>
</div>
</div>
</ai-info-item>
<ai-info-item label="创建时间" :value="info.createTime"></ai-info-item>
<ai-info-item label="群发时间" :value="info.choiceTime"></ai-info-item>
<ai-info-item label="群发范围" isLine>
<div class="text">
<span>{{ info.sendScope === '0' ? '全部' : '按条件筛选的' }}</span>
<i>{{ groups.length }}</i>
<span>个居民群</span>
<em @click="isShowGroups = true">详情</em>
</div>
</ai-info-item>
<ai-info-item label="消息内容" isLine>
<div class="msg">
<p>{{ content }}</p>
<div class="msg-bottom">
<div class="left" v-if="fileList.length">
<img :src="mapIcon(fileList[0].msgType)" />
<span>{{ mapType(fileList[0].msgType) }}{{ fileList[0].mpTitle || fileList[0].name || fileList[0].linkTitle }} </span>
<i>{{ fileList.length }}</i>
<span>个附件</span>
</div>
<div class="left" v-else>
<span>暂无附件</span>
</div>
<div class="right" @click="isShowPhone = true">预览消息</div>
</div>
</div>
</ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="成员统计">
<template #content>
<div class="content-item">
<div class="top">
<div class="top-item">
<div class="top-item__title">
<h3>计划执行成员</h3>
</div>
<p>{{ memberInfo.planCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>未执行成员</h3>
</div>
<p>{{ memberInfo.unExecutedCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>已执行成员</h3>
</div>
<p>{{ memberInfo.executedCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>无法执行成员</h3>
<el-tooltip
placement="top"
content="由于员工不在可见范围、离职、客户群接收已达到上限等原因,无法执行群发任务的成员总数">
<i class="iconfont iconDetails"></i>
</el-tooltip>
</div>
<p>{{ memberInfo.cannotExecuteCount || 0 }}</p>
</div>
</div>
<div class="bottom">
<div class="bottom-search">
<div class="left">
<el-radio-group v-model="search1.sendStatus" size="small" @change="search1.current = 1, getMemberInfo()">
<el-radio-button size="small" label="0">未执行</el-radio-button>
<el-radio-button size="small" label="1">已执行</el-radio-button>
<el-radio-button size="small" label="2">无法执行</el-radio-button>
</el-radio-group>
<ai-picker
dialogTitle="选择部门"
action="/app/wxcp/wxdepartment/departList"
:instance="instance"
@pick="e => onUserChange(e, 'search1')" :multiple="false" v-model="user1">
<div class="userSelcet">
<span style="color: #606266;" v-if="search1.deptartId">{{ name1 }}</span>
<span v-else>部门</span>
<i class="el-icon-arrow-up" v-if="!search1.deptartId"></i>
<i class="el-icon-circle-close" v-if="search1.deptartId" @click.stop="user1 = [], search1.deptartId = '', search1.current = 1, getMemberInfo()"></i>
</div>
</ai-picker>
</div>
<el-button :type="isDisabled ? '' : 'primary'" :disabled="isDisabled" @click="sendMsg(0)" v-if="info.status === '4'">{{ isDisabled ? min + '分钟后可再次提醒' : '提醒成员发送' }}</el-button>
</div>
<ai-table
:tableData="tableData1"
:col-configs="colConfigs1"
:total="total1"
border
tableSize="small"
:current.sync="search1.current"
:size.sync="search1.size"
@getList="getMemberInfo">
<el-table-column slot="user" label="成员" align="left">
<template slot-scope="{ row }">
<div class="userinfo">
<span>{{ row.groupOwnerName }}</span>
<span style="color: #999">{{ row.mainDepartmentName }}</span>
</div>
</template>
</el-table-column>
</ai-table>
</div>
</div>
</template>
</ai-card>
<ai-dialog
:visible.sync="isShowGroups"
width="890px"
title="群发范围"
@onConfirm="isShowGroups = false">
<ai-table
:tableData="info.wxGroups"
:col-configs="colConfigs3"
border
tableSize="small"
:isShowPagination="false"
@getList="() => {}">
</ai-table>
</ai-dialog>
<div class="detail-phone" v-if="isShowPhone">
<div class="mask"></div>
<Phone :avatar="user.info.avatar" @close="isShowPhone = false" :isShowClose="true" :content="content" :fileList="fileList"></Phone>
</div>
</template>
</ai-detail>
</template>
<script>
import { mapState } from 'vuex'
import Phone from './Phone'
export default {
name: 'Detail',
props: {
instance: Function,
dict: Object,
params: Object
},
components: {
Phone
},
data () {
return {
total1: 0,
isShowGroups: false,
isShowPhone: false,
total2: 0,
user1: [],
user2: [],
name1: '',
name2: '',
radio1: '未执行',
search1: {
current: 1,
size: 10,
deptartId: '',
type: 0,
sendStatus: '0'
},
search2: {
current: 1,
size: 10,
deptartId: '',
type: 1,
sendStatus: '0'
},
memberInfo: {},
groupInfo: {},
tableData1: [],
fileList: [],
tableData2: [],
info: {},
content: '',
currIndex: 0,
colConfigs3: [
{ prop: 'groupOwnerName', label: '群主' },
{ prop: 'groupNames', label: '群名称' }
],
colConfigs1: [
{ slot: 'user', label: '成员' },
{ prop: 'groupCount', label: '预计送达居民群', align: 'center' }
],
groups: [],
timer: null,
min: 60,
isDisabled: false,
rejecterId: ''
}
},
computed: {
...mapState(['user'])
},
created () {
this.getInfo(this.params.id)
this.getMemberInfo()
this.getGroupInfo()
},
destroyed () {
clearInterval(this.timer)
},
methods: {
getMemberInfo () {
this.instance.post(`/app/appmasssendingtask/detailStatistics`, null, {
params: {
...this.search1,
taskId: this.params.id
}
}).then(res => {
if (res.code === 0) {
this.tableData1 = res.data.executedList.records
this.total1 = res.data.executedList.total
this.memberInfo = res.data
}
})
},
onUserChange (e, search) {
if (e.length) {
search === 'search1' ? this.name1 = e[0].name : this.name2 = e[0].name
this[search].deptartId = e[0].id
} else {
this[search].deptartId = ''
search === 'search1' ? this.name1 = '' : this.name2 = ''
}
this[search].current = 1
if (search === 'search1') {
this.getMemberInfo()
} else {
this.getGroupInfo()
}
},
sendMsg () {
this.instance.post(`/app/appmasssendingtask/remindSend?id=${this.params.id}`).then(res => {
if (res.code === 0) {
this.$message.success('提醒成功')
this.getInfo(this.params.id)
}
})
},
getGroupInfo () {
this.instance.post(`/app/appmasssendingtask/detailStatistics`, null, {
params: {
...this.search2,
taskId: this.params.id
}
}).then(res => {
if (res.code === 0) {
this.tableData2 = res.data.executedList.records.map(v => {
return {
...v,
groupName: v.groupName || '未命名群聊'
}
})
this.total2 = res.data.executedList.total
this.groupInfo = res.data
}
})
},
countdown () {
this.timer = setInterval(() => {
const nowTime = this.$moment(new Date())
const min = nowTime.diff(this.info.remindTime, 'minute')
this.min = (60 - min)
if (this.min <= 0) {
this.isDisabled = false
clearInterval(this.timer)
} else {
this.isDisabled = true
}
}, 1000)
},
getInfo (id) {
this.instance.post(`/app/appmasssendingtask/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.info = res.data
if (res.data.status === '4' && res.data.remindTime) {
this.countdown()
}
const content = res.data.contents.filter(v => v.msgType === '0')
if (content.length) {
this.content = content[0].content
}
this.fileList = res.data.contents.filter(v => v.msgType !== '0').map(v => {
return {
...v,
...v.sysFile
}
})
this.info.wxGroups = res.data.wxGroups.map(v => {
this.groups.push(...v.groupIds.split(','))
return {
...v,
groupIds: v.groupIds.split(',')
}
})
if (res.data.examines && res.data.examines.length) {
const user = res.data.examines.filter(v => v.examineStatus === '2')
if (user.length) {
this.rejecterId = user[0].examineUserId
}
}
}
})
},
mapType (type) {
return {
1: '图片',
2: '视频',
3: '文件',
4: '网站',
5: '小程序'
}[type]
},
mapIcon (type) {
return {
1: 'https://cdn.cunwuyun.cn/dvcp/announce/img.png',
2: 'https://cdn.cunwuyun.cn/dvcp/announce/video.png',
3: 'https://cdn.cunwuyun.cn/dvcp/announce/folder.png',
4: 'https://cdn.cunwuyun.cn/dvcp/announce/site.png',
5: 'https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png'
}[type]
},
cancel (isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: !!isRefresh
})
}
}
}
</script>
<style scoped lang="scss">
.AppAnnounceDetail {
position: relative;
.user-wrapper {
display: flex;
flex-wrap: wrap;
}
.detail-phone {
position: fixed;
left: 0%;
top: 0%;
z-index: 11;
width: 100%;
height: 100%;
.mask {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 1;
background: rgba($color: #000000, $alpha: 0.6);
}
::v-deep .phone-container {
position: absolute;
left: 50%;
top: 50%;
z-index: 11;
transform: translate(-50%, -50%);
}
}
.userSelcet {
display: flex;
align-items: center;
justify-content: space-between;
width: 215px;
height: 32px;
line-height: 32px;
margin-left: 12px;
border-radius: 4px;
border: 1px solid #d0d4dc;
overflow: hidden;
cursor: pointer;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
border-color: #26f;
}
i {
display: flex;
position: relative;
align-items: center;
justify-content: center;
width: 30px;
height: 100%;
line-height: 32px;
font-size: 14px;
text-align: center;
color: #d0d4dc;
transform: rotateZ(180deg);
}
.el-icon-circle-close:hover {
opacity: 0.6;
}
span {
flex: 1;
padding: 0 15px;
font-size: 12px;
color: $placeholderColor;
}
}
.userinfo {
display: flex;
flex-direction: column;
justify-content: center;
line-height: 1;
span:first-child {
margin-bottom: 4px;
}
}
.user {
display: flex;
align-items: center;
line-height: 1;
margin-right: 8px;
img {
width: 16px;
height: 16px;
margin-right: 2px;
}
span {
position: relative;
top: 2px;
color: #222222;
font-size: 12px;
}
}
.text {
display: flex;
align-items: center;
i {
color: #2266FF;
font-style: normal;
}
em {
margin-left: 8px;
color: #2266FF;
font-size: 12px;
font-style: normal;
cursor: pointer;
transition: all ease 0.3s;
&:hover {
opacity: 0.6;
}
}
}
.msg {
background: #F9F9F9;
border-radius: 2px;
border: 1px solid #D0D4DC;
p {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
line-height: 38px;
padding: 0px 12px;
overflow: hidden;
}
.msg-bottom {
display: flex;
align-items: center;
justify-content: space-between;
height: 38px;
padding: 0 16px;
border-top: 1px solid #D0D4DC;
.left {
display: flex;
align-items: center;
img {
width: 16px;
height: 16px;
margin-right: 8px;
}
span {
color: #222222;
font-size: 14px;
}
i {
color: #2266FF;
font-size: 14px;
font-style: normal;
}
}
.right {
color: #2266FF;
font-size: 12px;
cursor: pointer;
&:hover {
opacity: 0.6;
}
}
}
}
::v-deep .AppAnnounceDetail-title {
display: flex;
align-items: center;
span {
height: 100%;
line-height: 56px;
margin-right: 32px;
color: #888888;
font-size: 16px;
font-weight: 600;
transition: all ease 0.3s;
border-bottom: 3px solid transparent;
cursor: pointer;
user-select: none;
&:hover {
color: #222;
}
&:last-child {
margin-right: 0;
}
&.active {
color: #222222;
border-bottom: 3px solid #2266FF;
}
}
}
.content-item {
.top {
display: flex;
align-items: center;
margin-bottom: 16px;
.top-item {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
height: 90px;
margin-right: 16px;
padding: 0 16px;
background: #F9F9F9;
border-radius: 2px;
&:last-child {
margin-right: 0;
}
.top-item__title {
display: flex;
align-items: center;
margin-bottom: 8px;
i {
margin-left: 4px;
color: #8899bb;
font-size: 16px;
}
}
h3 {
color: #222222;
font-size: 14px;
font-weight: 700;
}
p {
color: #2266FF;
font-size: 24px;
font-weight: 700;
}
}
}
.bottom-search {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.left {
display: flex;
align-items: center;
}
}
}
::v-deep .right-tips {
display: flex;
align-items: center;
i {
margin-right: 4px;
color: #8899bb;
font-size: 16px;
}
span {
color: #888888;
font-size: 12px;
}
}
}
</style>

View File

@@ -0,0 +1,295 @@
<template>
<ai-list class="AppAnnounce">
<template slot="title">
<ai-title title="群发居民" isShowBottomBorder>
<template #sub>
<span>管理员统一创建宣发任务选择要发送的居民群后通知群主发送群主确认后即可群发到居民群群主向同一个居民群每天最多可群发10条消息</span>
</template>
</ai-title>
</template>
<template slot="content">
<ai-search-bar class="search-bar">
<template #left>
<el-button size="small" type="primary" icon="iconfont iconAdd" @click="toAdd('')">创建宣发</el-button>
<ai-select
v-model="search.status"
@change="search.current = 1, getList()"
placeholder="任务状态"
:selectList="dict.getDict('mstStatus')">
</ai-select>
<el-date-picker
v-model="search.startTime"
type="date"
size="small"
value-format="yyyy-MM-dd"
@change="search.current = 1, getList()"
placeholder="选择群发开始日期">
</el-date-picker>
<el-date-picker
v-model="search.endTime"
type="date"
size="small"
value-format="yyyy-MM-dd"
@change="search.current = 1, getList()"
placeholder="选择群发结束日期">
</el-date-picker>
<ai-wechat-selecter :instance="instance" @change="onUserChange" :isMultiple="false" v-model="user">
<div class="userSelcet">
<span style="color: #606266;" v-if="search.createUserId">{{ name }}</span>
<span v-else>创建人</span>
<i class="el-icon-arrow-up" v-if="!search.createUserId"></i>
<i class="el-icon-circle-close" v-if="search.createUserId" @click.stop="user = [], search.createUserId = '', name = '', search.current = 1, getList()"></i>
</div>
</ai-wechat-selecter>
</template>
<template slot="right">
<el-input
v-model="search.taskTitle"
size="small"
v-throttle="() => { search.current = 1, getList() }"
placeholder="请输入任务名称"
clearable
@clear="search.current = 1, search.taskTitle = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
v-loading="loading"
style="margin-top: 6px; width: 100%;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="user" width="140px" label="创建人" align="center">
<template slot-scope="{ row }">
<div class="userinfo">
<span>{{ row.createUserName }}</span>
<span style="color: #999">{{ row.createUserDeptName }}</span>
</div>
</template>
</el-table-column>
<el-table-column slot="options" width="140px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="remindExamine(row.id)" v-if="['0'].includes(row.status)">催办</el-button>
<el-button type="text" @click="cancel(row.id)" v-if="['0'].includes(row.status)">撤回</el-button>
<el-button type="text" @click="toDetail(row.id)">详情</el-button>
<el-button type="text" @click="toAdd(row.id)" v-if="['1', '3'].includes(row.status)">编辑</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
export default {
name: 'List',
props: {
instance: Function,
dict: Object
},
data() {
return {
search: {
current: 1,
size: 10,
status: '',
createUserId: '',
taskTitle: '',
startTime: '',
endTime: ''
},
name: '',
user: [],
tableData: [],
loading: false,
total: 0,
colConfigs: [
{ prop: 'taskTitle', label: '任务名称' },
{ prop: 'typeName', label: '群发类型', align: 'center' },
{ slot: 'user', label: '创建人', openType: 'userName', align: 'center' },
{ prop: 'choiceTime', label: '群发时间', align: 'center' },
{
prop: 'status',
align: 'center',
label: '状态',
render: (h, {row}) => {
return h('span', {
style: {
color: this.dict.getColor('mstStatus', row.status)
}
}, this.dict.getLabel('mstStatus', row.status))
}
},
{ prop: 'completionRate', label: '任务完成率', align: 'center', formart: v => v ? v === '0.0' ? '0%' : `${v}%` : '-' }
]
}
},
created () {
this.dict.load('mstStatus', 'mstSendType').then(() => {
this.getList()
})
},
methods: {
onUserChange (e) {
if (e.length) {
this.name = e[0].name
this.search.createUserId = e[0].id
} else {
this.search.createUserId = ''
this.name = ''
}
this.search.current = 1
this.getList()
},
getList() {
this.loading = true
this.instance.post(`/app/appmasssendingtask/list`, null, {
params: {
...this.search,
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records.map(v => {
return {
...v,
typeName: '群发居民群'
}
})
this.total = res.data.total
this.$nextTick(() => {
this.loading = false
})
} else {
this.loading = false
}
}).catch(() => {
this.loading = false
})
},
remindExamine (id) {
this.$confirm('确认再次通知任务审核人员?').then(() => {
this.instance.post(`/app/appmasssendingtask/remindExamine?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('催办成功!')
this.getList()
}
})
})
},
cancel (id) {
this.$confirm('确认撤回该群发任务?').then(() => {
this.instance.post(`/app/appmasssendingtask/cancel?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('撤回成功!')
this.getList()
}
})
})
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appmasssendingtask/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toAdd(id) {
this.$emit('change', {
type: 'Add',
params: {
id
}
})
},
toDetail (id) {
this.$emit('change', {
type: 'Detail',
params: {
id
}
})
}
}
}
</script>
<style lang="scss" scoped>
.AppAnnounce {
height: 100%;
.userinfo {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
line-height: 1;
span:first-child {
margin-bottom: 4px;
}
}
.userSelcet {
display: flex;
align-items: center;
justify-content: space-between;
width: 215px;
height: 32px;
line-height: 32px;
border-radius: 4px;
border: 1px solid #d0d4dc;
overflow: hidden;
cursor: pointer;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
border-color: #26f;
}
i {
display: flex;
position: relative;
align-items: center;
justify-content: center;
width: 30px;
height: 100%;
line-height: 32px;
font-size: 14px;
text-align: center;
color: #d0d4dc;
transform: rotateZ(180deg);
}
.el-icon-circle-close:hover {
opacity: 0.6;
}
span {
flex: 1;
padding: 0 15px;
font-size: 12px;
color: $placeholderColor;
}
}
}
</style>

View File

@@ -0,0 +1,344 @@
<template>
<div class="phone-container">
<img class="close" @click="$emit('close')" v-if="isShowClose" src="https://cdn.cunwuyun.cn/dvcp/announce/close.png" />
<img class="phone" src="https://cdn.cunwuyun.cn/dvcp/announce/phone.png" />
<img class="phone-wrapper" src="https://cdn.cunwuyun.cn/dvcp/announce/phone-wrapper.png" />
<div class="right-content">
<div class="msg-list">
<div class="msg-item" v-if="content">
<div class="msg-item__left">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/avatar.png" />
</div>
<div class="msg-item__right">
<div class="msg-wrapper msg-text">
<p>{{ content }}</p>
</div>
</div>
</div>
<div class="msg-item" v-for="item in fileList" :key="item.id">
<div class="msg-item__left">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/avatar.png" />
</div>
<div class="msg-item__right" :class="[['1', '2'].indexOf(item.msgType) !== -1 ? 'left-border' : '']">
<div class="msg-wrapper msg-img" v-if="item.msgType === '1'">
<img :src="item.imgPicUrl" />
</div>
<div class="msg-wrapper msg-video" v-if="item.msgType === '2'">
<video controls :src="item.url"></video>
</div>
<div class="msg-wrapper msg-file" v-if="item.msgType === '3'">
<div class="msg-left">
<h2>{{ item.name }}</h2>
<p>{{ item.fileSizeStr }}</p>
</div>
<img :src="mapIcon(item.name)" />
</div>
<div class="msg-wrapper msg-link" v-if="item.msgType === '4'">
<h2>{{ item.linkTitle }}</h2>
<div class="msg-right">
<p>{{ item.linkDesc }}</p>
<img :src="item.linkPicUrl || 'https://cdn.cunwuyun.cn/dvcp/announce/html.png'" />
</div>
</div>
<div class="msg-wrapper msg-miniapp" v-if="item.msgType === '5'">
<h2>{{ item.mpTitle }}</h2>
<img :src="item.url" />
<div class="msg-bottom">
<i>小程序</i>
<img src="https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['fileList', 'avatar', 'content', 'isShowClose'],
watch: {
fileList (v) {
if (v.length) {
setTimeout(() => {
document.querySelector('.right-content').scrollTo(0, 999999)
}, 800)
}
}
},
methods: {
mapIcon (fileName) {
if (['.zip', '.rar'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/zip.png'
}
if (['.doc', '.docx'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/world.png'
}
if (['.xls', '.xlsx'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/xls.png'
}
if (['.txt'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/txt.png'
}
if (['.pdf'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/pdf.png'
}
if (['.ppt', '.pptx'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/ppt.png'
}
},
getExtension(name) {
return name.substring(name.lastIndexOf('.'))
}
}
}
</script>
<style lang="scss" scoped>
.phone-container {
width: 338px;
height: 675px;
padding: 80px 15px 100px 32px;
.phone {
position: absolute;
left: 13px;
top: 4px;
z-index: 1;
width: 314px;
height: 647px;
}
.close {
position: absolute;
top: 0;
right: 0;
z-index: 111;
width: 60px;
height: 60px;
cursor: pointer;
transition: all ease 0.5s;
transform: translate(100%, -50%);
&:hover {
opacity: 0.7;
}
}
.phone-wrapper {
position: absolute;
left: 0;
top: 0;
z-index: 2;
width: 338px;
height: 675px;
}
.right-content {
position: relative;
z-index: 11;
height: 100%;
overflow-y: auto;
.msg-item {
display: flex;
margin-bottom: 20px;
.msg-item__left {
width: 42px;
height: 42px;
margin-right: 16px;
border-radius: 4px;
flex-shrink: 1;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
.msg-item__right {
position: relative;
flex: 1;
&::after {
position: absolute;
top: 16px;
left: 0;
z-index: 1;
width: 0;
height: 0;
border-right: 6px solid #fff;
border-left: 6px solid transparent;
border-bottom: 6px solid transparent;
border-top: 6px solid transparent;
content: " ";
transform: translate(-100%, 0%);
}
&.left-border::after {
display: none;
}
.msg-img img {
max-width: 206px;
max-height: 200px;
}
.msg-video video {
max-width: 206px;
max-height: 200px;
}
.msg-text {
max-width: 206px;
width: max-content;
line-height: 1.3;
padding: 12px;
background: #FFFFFF;
border-radius: 5px;
word-break: break-all;
font-size: 14px;
color: #222222;
}
.msg-miniapp {
width: 206px;
padding: 0 12px;
text-align: justify;
font-size: 0;
background: #FFFFFF;
border-radius: 5px;
font-size: 14px;
color: #222222;
h2 {
line-height: 1.2;
padding: 8px 0;
border-bottom: 1px solid #eee;
color: #222222;
font-size: 14px;
}
& > img {
width: 100%;
height: 120px;
margin-bottom: 8px;
}
.msg-bottom {
display: flex;
align-items: center;
line-height: 1;
padding: 4px 0;
border-top: 1px solid #eee;
i {
margin-right: 4px;
font-size: 12px;
font-style: normal;
color: #999;
}
img {
width: 16px;
height: 16px;
border-radius: 50%;
}
}
}
.msg-file {
display: flex;
align-items: center;
width: 206px;
padding: 12px;
background: #FFFFFF;
border-radius: 5px;
.msg-left {
flex: 1;
margin-right: 18px;
h2 {
display: -webkit-box;
flex: 1;
line-height: 16px;
margin-bottom: 4px;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
text-overflow: ellipsis;
overflow: hidden;
color: #222222;
font-size: 14px;
width: 120px;
}
p {
color: #888888;
font-size: 12px;
}
}
img {
width: 44px;
height: 44px;
border-radius: 2px;
}
}
.msg-link {
width: 206px;
padding: 12px;
background: #FFFFFF;
border-radius: 5px;
h2 {
margin-bottom: 4px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #222222;
font-size: 14px;
font-weight: normal;
}
.msg-right {
display: flex;
align-items: center;
p {
display: -webkit-box;
flex: 1;
line-height: 16px;
margin-right: 10px;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
text-overflow: ellipsis;
overflow: hidden;
color: #888;
font-size: 12px;
}
img {
width: 50px;
height: 50px;
border-radius: 4px;
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,759 @@
<template>
<ai-list class="AppAnnounceStatistics">
<template slot="content">
<div class="statistics-content">
<ai-title title="宣发日历"></ai-title>
<div class="flex-content">
<div class="flex-left">
<div class="date-header">
<p>{{chooseYear}}{{chooseMonth}}</p>
<div>
<el-date-picker size="small"
v-model="searchMonth"
type="month" value-format="yyyy-MM"
placeholder="选择日期" @change="searchMonthChange">
</el-date-picker>
</div>
</div>
<el-calendar v-model="calendarDate">
<template
slot="dateCell"
slot-scope="{date, data}" >
<div class="flex-date">
<span>{{Number(data.day.substring(8, 10))}}</span>
<span class="tips" v-if="data.day.substring(5, 7) == chooseMonth && dateList[Number(data.day.substring(8, 10))] && dateList[Number(data.day.substring(8, 10))].taskList.length">{{dateList[Number(data.day.substring(8, 10))].taskList.length}}</span>
</div>
</template>
</el-calendar>
</div>
<div class="flex-right">
<div class="title">{{chooseMonth}}{{chooseDay}}日宣发内容</div>
<div class="list-content" v-if="taskList.length">
<el-timeline >
<el-timeline-item v-for="(item, index) in taskList" :key="index">
<el-card>
<div class="flex-between">
<p class="item-title">{{item.taskTitle}}</p>
<span class="item-time" v-if="item.choiceTime">{{item.choiceTime.substring(10, 16)}}</span>
</div>
<div class="item-info item-created">
<span class="label">创建人</span>
<ai-open-data type="userName" :openid="item.createUserId" class="name"></ai-open-data>
</div>
<div class="item-info item-dept">
<span class="label">创建部门</span>
<ai-open-data type="departmentName" :openid="item.createUserDept" class="name"></ai-open-data>
</div>
<div class="flex-between">
<!-- <div class="item-info">群发类型<span>{{$dict.getLabel('mstSendType', item.sendType) || ''}}</span></div> -->
<div class="item-info"><span class="label">群发类型</span><span>群发居民群</span></div>
<span class="item-btn" @click="$router.push({name: '357e228ba8e64008ace90d095a7a0dd7', params: { id: item.id }})">详情</span>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
<ai-empty v-if="!taskList.length" />
</div>
</div>
</div>
<div class="statistics-content">
<div class="flex-between mar-b16">
<ai-title title="宣发效果"></ai-title>
<div class="right-search">
<div class="time-select" :class="effectType == index ? 'active' : ''" v-for="(item, index) in dateTypeList" :key="index" @click="changeEffectType(index)">{{item}}</div>
<ai-picker :instance="instance" @pick="e => onUserChange(e)" :multiple="false" dialogTitle="选择部门" action="/app/wxcp/wxdepartment/departList">
<div class="time-select">
<span class="dept-name" style="color:#999;" v-if="deptList && !deptList.length">宣发部门</span>
<ai-open-data class="dept-name" type="departmentName" :openid="deptList[0].id" v-else/>
<i class="el-icon-arrow-down"></i>
</div>
</ai-picker>
</div>
</div>
<div class="line-content">
<div class="flex1">
<div class="header">
<p>累计创建宣发任务数</p>
<h2>{{effectData.createCount}}</h2>
</div>
<div class="chart-content">
<div class="chart-title">宣发任务数</div>
<div class="chart-box" id="createChart"></div>
</div>
</div>
<div class="flex1">
<div class="header">
<p>累计执行宣发次数</p>
<h2>{{effectData.executeCount}}</h2>
</div>
<div class="chart-content">
<div class="chart-title">宣发次数</div>
<div class="chart-box" id="executeChart"></div>
</div>
</div>
<div class="flex1 mar-r0">
<div class="header">
<p>累计触达人次</p>
<h2>{{effectData.receiveCount}}</h2>
</div>
<div class="chart-content">
<div class="chart-title">触达人次</div>
<div class="chart-box" id="receiveChart"></div>
</div>
</div>
</div>
</div>
<div class="statistics-content">
<div class="flex-between mar-b16">
<ai-title title="宣发明细"></ai-title>
<div class="right-search">
<div class="time-select" :class="departType == index ? 'active' : ''" v-for="(item, index) in dateTypeList" :key="index" @click="changeDepartType(index)">{{item}}</div>
</div>
</div>
<div id="departBarChart" v-if="isDepartData"></div>
<ai-empty v-if="!isDepartData"></ai-empty>
</div>
<ai-dialog :visible.sync="dialogDate" title="选择时间" width="500px" customFooter>
<el-date-picker v-model="timeList" size="small" type="daterange" value-format="yyyy-MM-dd"
range-separator="" start-placeholder="开始日期" end-placeholder="结束日期">
</el-date-picker>
<el-button slot="footer" @click="selectDete" type="primary">确认</el-button>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
import * as echarts from "echarts";
import { mapActions, mapState } from 'vuex';
export default {
name: 'AppAnnounceResidentStatistics',
label: '协同宣发居民统计',
props: {
instance: Function,
dict: Object,
permissions: Function
},
data () {
return {
calendarDate: new Date(),
dateList: {},
chooseYear: '',
chooseMonth: '',
chooseDay: '',
searchMonth: '',
taskList: [],
effectType: 0, // 宣发效果类型 0近七天、1近30天、2近一年、3自定义
effectData: {},
createChart: null,
executeChart: null,
receiveChart: null,
departType: 0, // 宣发明细类型 0近七天、1近30天、2近一年、3自定义
dateTypeList: ['近7天', '近30天', '近1年', '自定义'],
departData: {},
departBarChart: null,
dialogDate: false,
timeListEffect: '',
timeListDepart: '',
timeList: '',
isEffectTimeSelect: false,
deptList: [],
selectDeptName: '',
isDepartData: true,
departBarData: [],
type: '',
}
},
computed: {
...mapState(['user']),
},
watch: {
calendarDate: function() {
var year = '' , month = '', date = ''
if(this.calendarDate.length == 9) { // 月份选择器触发
year = this.calendarDate.substring(0, 4)
month = this.calendarDate.substring(5, 7)
date = this.calendarDate.substring(8, 10)
}else { // 日历点击
year = this.calendarDate.getFullYear();
month = this.calendarDate.getMonth() + 1;
date = this.calendarDate.getDate()
if (month >= 1 && month <= 9) {
month = "0" + month;
}
if(this.chooseMonth != month) { // 日历点击不同月
this.searchMonth = ''
}
}
this.chooseDay = date
if(this.chooseMonth != month || this.chooseYear != year) { // 不同年/不同月重新请求日历列表
this.getCalendarList(year, month)
} else {
this.getTaskList(date)
}
this.chooseMonth = month
this.chooseYear = year
}
},
created() {
var year = this.calendarDate.getFullYear();
var month = this.calendarDate.getMonth() + 1;
var date = this.calendarDate.getDate()
if (month >= 1 && month <= 9) {
month = "0" + month;
}
this.chooseMonth = month
this.chooseYear = year
this.chooseDay = date
this.getCalendarList(year, month)
this.getEffect()
this.getDepart()
this.dict.load('mstSendType')
},
methods: {
...mapActions(['initOpenData', 'transCanvas']),
onUserChange (e) {
this.deptList = e
this.getEffect()
},
selectDete() {
if(!this.timeList || !this.timeList.length) {
return this.$message.error('请选择自定义时间');
}
if(this.isEffectTimeSelect) { //宣发效果
this.timeListEffect = this.timeList
this.effectType = 3
this.getEffect()
} else { //宣发明细
this.timeListDepart = this.timeList
this.departType = 3
this.getDepart()
}
this.dialogDate = false
},
searchMonthChange() {
this.calendarDate = this.searchMonth + '-1'
},
getCalendarList(year, month){
this.instance.post(`/app/appmasssendingtask/statisticsCalendar?yyyyMM=${year}${month}`).then(res => {
if (res.code == 0) {
this.dateList = res.data
this.getTaskList(this.chooseDay)
}
})
},
getTaskList(day) {
this.taskList = this.dateList[day].taskList
},
changeEffectType(type) {
if(this.effectType != 3) {
this.timeList = []
}else {
this.timeList = this.timeListEffect
}
if(type == 3) {
this.isEffectTimeSelect = true
this.dialogDate = true
}else {
this.effectType = type
this.getEffect()
}
},
getEffect() {
var startTime = this.timeListEffect[0] || '' , endTime = this.timeListEffect[1] || '', departId = this.deptList[0] || ''
this.instance.post(`/app/appmasssendingtask/statisticsEffect?type=${this.effectType}&startTime=${startTime}&endTime=${endTime}&departId=${departId}`).then(res => {
if (res.code == 0) {
this.effectData = res.data
var xData = [], createData = [], executeData = [], receiveData = []
res.data.trend.map(e => {
if(this.effectType == 0 || this.effectType == 1) {
e.ymd = e.ymd.substring(5, 10)
}
xData.push(e.ymd)
createData.push(e.createCount)
executeData.push(e.executeCount)
receiveData.push(e.receiveCount)
})
this.setLineChart(xData, createData, 'createChart', ['#2891FF'])
this.setLineChart(xData, executeData, 'executeChart', ['#FFB865'])
this.setLineChart(xData, receiveData, 'receiveChart', ['#26D52B'])
}
})
},
setLineChart(xData, yData, id, colorList) {
this[id] = echarts.init(document.querySelector(`#${id}`))
var option = {
xAxis: {
type: 'category',
data: xData
},
yAxis: {
type: 'value'
},
grid: {
left: '10px',
right: '28px',
bottom: '14px',
top: '30px',
containLabel: true
},
tooltip: {
trigger: 'axis'
},
legend: {
type: "plain"
},
color: colorList,
series: [
{
data: yData,
type: 'line'
}
]
}
this[id].setOption(option)
},
changeDepartType(type) {
if(this.departType != 3) {
this.timeList = []
}else {
this.timeList = this.timeListDepart
}
if(type == 3) {
this.isEffectTimeSelect = false
this.dialogDate = true
}else {
this.departType = type
this.getDepart()
}
},
getDepart() {
var startTime = this.timeListDepart[0] || '' , endTime = this.timeListDepart[1] || ''
this.instance.post(`/app/appmasssendingtask/statisticsDepart?type=${this.departType}&startTime=${startTime}&endTime=${endTime}`).then(res => {
if (res.code == 0) {
if(res.data && res.data.length) {
this.isDepartData = true
var items = [], xData = [], yData = []
res.data.map((item) => {
this.departBarData.push(item)
var i = {type: 'departmentName', id: item.deptId, corpid: this.user.info.corpId}
items.push(i)
yData.push(item.taskCount)
})
this.initOpenData({canvas:true})
this.transCanvas(items).then((data) => {
xData = data.items.map((i) => {
return i.data
})
this.setBarChart(xData, yData)
})
}else {
this.isDepartData = false
}
}
})
},
setBarChart(xData, yData) {
this.departBarChart = echarts.init(document.querySelector(`#departBarChart`))
var option = {
color: ['#2891FF'],
grid: {
top: '10%',
left: '2%',
right: '2%',
bottom: 90,
containLabel: true
},
// toolbox: {
// feature: {
// dataZoom: {
// yAxisIndex: false
// },
// saveAsImage: {
// pixelRatio: 2
// }
// }
// },
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: (data) => {
var index = data[0].dataIndex
return `<ww-open-data type="departmentName" openid="${this.departBarData[index].deptId}"></ww-open-data><br/>宣发任务数:${data[0].value}`
}
},
dataZoom: [
{
type: 'inside'
},
{
type: 'slider'
}
],
xAxis: {
data: xData,
silent: false,
splitLine: {
show: false
},
splitArea: {
show: false
}
},
yAxis: {
splitArea: {
show: false
}
},
series: [
{
type: 'bar',
data: yData,
barWidth: 20,
barGap: '250%',
large: true
}
]
};
// {
// tooltip: {
// trigger: 'axis',
// axisPointer: {
// type: 'shadow'
// }
// },
// grid: {
// top: '10%',
// left: '2%',
// right: '2%',
// bottom: '2%',
// containLabel: true
// },
// color: ['#2891FF'],
// xAxis: {
// type: 'category',
// data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
// },
// yAxis: {
// type: 'value'
// },
// series: [
// {
// data: [120, 200, 150, 80, 70, 110, 130],
// type: 'bar',
// barWidth: 20,
// barGap: '250%',
// }
// ]
// };
this.departBarChart.setOption(option)
}
}
}
</script>
<style lang="scss" scoped>
.AppAnnounceStatistics {
height: 100%;
.flex-between{
display: flex;
justify-content: space-between;
}
.mar-b16{
margin-bottom: 16px;
}
.mar-r0{
margin-right: 0!important;
}
.statistics-content{
padding: 0 24px 24px;
background-color: #fff;
box-shadow: 0px 4px 6px -2px rgba(15,15,21,0.1500);
border-radius: 4px;
margin-bottom: 20px;
.flex-content{
width: 100%;
display: flex;
margin-top: 16px;
.flex-left{
width: 50%;
.date-header{
padding: 12px 16px;
border: 1px solid #eee;
display: flex;
justify-content: space-between;
p{
line-height: 32px;
}
}
.flex-date{
display: flex;
justify-content: space-between;
}
.tips{
display: inline-block;
padding: 0 4px;
height: 16px;
line-height: 16px;
border-radius: 8px;
background: #2891FF;
font-size: 12px;
font-family: ArialMT;
color: #FFF;
margin-top: 8px;
}
}
.flex-right{
width: 50%;
margin-left: 16px;
border: 1px solid #eee;
.title{
line-height: 56px;
border-bottom: 1px solid #EEE;
padding-left: 16px;
font-size: 16px;
font-family: MicrosoftYaHeiSemibold;
color: #333;
}
.list-content{
padding: 16px;
height: 339px;
box-sizing: border-box;
overflow-y: scroll;
background-color: #F9F9F9;
box-sizing: border-box;
.item-title{
width: calc(100% - 100px);
word-break: break-all;
margin-bottom: 8px;
font-size: 16px;
font-family: MicrosoftYaHeiSemibold;
color: #222;
line-height: 24px;
}
.item-time{
width: 100px;
text-align: right;
font-size: 16px;
font-family: ArialMT;
color: #888;
line-height: 24px;
}
.item-info{
display: inline-block;
font-size: 14px;
font-family: MicrosoftYaHei;
color: #222;
line-height: 22px;
span{
display: inline-block;
color: #222;
word-break: break-all;
// vertical-align: text-top;
}
.label{
color: #999;
}
}
.item-created{
width: 152px;
margin-bottom: 4px;
.label{
width: 56px;
}
.name{
width: calc(100% - 56px);
}
}
.item-dept{
width: calc(100% - 152px);
.label{
width: 70px;
}
.name{
width: calc(100% - 70px);
}
}
.item-btn{
color: #26f;
cursor: pointer;
}
}
}
}
.right-search{
margin-top: 10px;
div{
display: inline-block;
}
.time-select{
font-size: 14px;
font-family: MicrosoftYaHei;
color: #222;
line-height: 22px;
padding: 6px 12px;
border-radius: 2px;
border: 1px solid #D0D4DC;
margin-right: 8px;
box-sizing: border-box;
cursor: pointer;
.dept-name{
display: inline-block;
width: 200px;
height: 22px;
overflow:hidden;
white-space: nowrap;
text-overflow: ellipsis;
vertical-align: bottom;
}
.el-icon-arrow-down{
vertical-align: middle;
}
}
.active{
border: 1px solid #26f;
color: #26f;
}
}
.line-content{
display: flex;
.flex1{
flex: 1;
margin-right: 16px;
.header{
padding: 16px;
width: 100%;
height: 90px;
background: #F9F9F9;
border-radius: 2px;
box-sizing: border-box;
margin-bottom: 16px;
p{
font-size: 14px;
font-family: MicrosoftYaHeiSemibold;
color: #222;
line-height: 22px;
margin-bottom: 4px;
}
h2{
font-size: 24px;
font-family: DINAlternate-Bold, DINAlternate;
font-weight: bold;
color: #26F;
line-height: 32px;
}
}
.chart-content{
width: 100%;
padding: 16px;
background: #F9F9F9;
border-radius: 2px;
box-sizing: border-box;
.chart-title{
font-size: 16px;
font-family: MicrosoftYaHeiSemibold;
color: #333;
line-height: 24px;
}
.chart-box{
width: 100%;
height: 280px;
}
}
}
}
#departBarChart{
width: 100%;
height: 300px;
}
}
::v-deep .el-calendar-table:not(.is-range) td.next,
::v-deep .el-calendar-table:not(.is-range) td.prev {
color: #ccc;
}
::v-deep .el-calendar-table .el-calendar-day{
height: 48px;
line-height: 32px;
padding-left: 12px;
font-size: 14px;
font-family: ArialMT;
}
.el-calendar-table:not(.is-range) td .current{
color: #888;
}
::v-deep .el-calendar__header{
display: none;
}
::v-deep .el-calendar__body{
padding: 0;
}
::v-deep .el-calendar-table thead th:nth-of-type(1){
border-left: 1px solid #eee;
}
::v-deep .el-calendar-table thead th:nth-of-type(7){
border-right: 1px solid #eee;
}
::v-deep .el-calendar-table tr td:first-child {
border-left: 1px solid #eee;
}
::v-deep .el-calendar-table tr:first-child td {
border-top: 1px solid #eee;
}
::v-deep .el-calendar-table td {
border-bottom: 1px solid #eee;
border-right: 1px solid #eee;
}
::v-deep .el-timeline-item__timestamp.is-top{
margin-bottom: 0;
padding-top: 0;
}
::v-deep .el-timeline-item__node{
background-color: #26F;
width: 8px;
height: 8px;
border-radius: 50%;
left: 1px;
}
::v-deep .el-card{
border: none;
}
::v-deep .el-card__body{
padding: 8px;
}
}
::v-deep .ai-list__content {
padding: 0!important;
.ai-list__content--right-wrapper {
background: transparent!important;
box-shadow: none!important;
margin: 0!important;
padding: 0 0 0!important;
}
}
::v-deep .AiPicker{
display: inline-block;
}
</style>

View File

@@ -0,0 +1,79 @@
<template>
<div class="AppAnnounceWeChat">
<!-- <keep-alive :include="['List']"> -->
<component ref="component" :is="component" @change="onChange" :params="params" :instance="instance" :dict="dict"></component>
<!-- </keep-alive> -->
</div>
</template>
<script>
import List from './components/List'
import Add from './components/Add'
import Detail from './components/Detail'
export default {
name: 'AppAnnounceWeChat',
label: '群发朋友圈',
props: {
instance: Function,
dict: Object
},
data () {
return {
component: 'List',
params: {},
include: []
}
},
components: {
Add,
List,
Detail
},
mounted () {
if (this.$route.params.id) {
this.component = 'Detail'
this.params = {
id: this.$route.params.id
}
}
},
methods: {
onChange (data) {
if (data.type === 'Add') {
this.component = 'Add'
this.params = data.params
}
if (data.type === 'Detail') {
this.component = 'Detail'
this.params = data.params
}
if (data.type === 'list') {
this.component = 'List'
this.params = data.params
this.$nextTick(() => {
if (data.isRefresh) {
this.$refs.component.getList()
}
})
}
}
}
}
</script>
<style lang="scss">
.AppAnnounceWeChat {
height: 100%;
background: #F3F6F9;
overflow: auto;
}
</style>

View File

@@ -0,0 +1,879 @@
<template>
<ai-detail class="AppAnnounceAdd">
<template slot="title">
<ai-title :title="id ? '编辑群发朋友圈' : '添加群发朋友圈'" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</template>
<template slot="content">
<div class="AppAnnounceDetail-container">
<el-form ref="form" class="left" :model="form" label-width="110px" label-position="right">
<ai-card title="基本信息">
<template #content>
<div class="ai-form">
<el-form-item label="任务名称" prop="taskTitle" style="width: 100%;" :rules="[{ required: true, message: '请输入任务名称', trigger: 'blur' }]">
<el-input size="small" placeholder="请输入任务名称" v-model="form.taskTitle" :maxlength="15" show-word-limit></el-input>
</el-form-item>
<el-form-item label="发送范围" style="width: 100%;" prop="sendScope" :rules="[{ required: true, message: '请选择发送范围', trigger: 'change' }]">
<el-radio-group v-model="form.sendScope" @change="onScopeChange">
<el-radio label="0">全部居民群</el-radio>
<el-radio label="1">按部门选择</el-radio>
<el-radio label="2">按网格选择</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="选择群主" v-if="form.sendScope !== '0'" prop="wxGroupsName" style="width: 100%;" :rules="[{ required: true, message: '请选择选择群主', trigger: 'change' }]">
<ai-picker
:instance="instance"
multiple
:dialogTitle="form.sendScope === '2' ? '选择网格' : '选择部门'"
:ops="{label: form.sendScope === '2' ? 'girdName' : 'name'}"
:pageTitle="form.sendScope === '2' ? '网格' : '部门'"
:action="form.sendScope === '1' ? '/app/wxcp/wxdepartment/departList' : '/app/appgirdinfo/girdList'"
v-model="form.filterCriteria"
@pick="onPick"
@change="onSelcetChange">
<div class="AppAnnounceDetail-select">
<el-input size="small" class="AppAnnounceDetail-select__input" placeholder="请选择..." disabled v-model="form.wxGroupsName"></el-input>
<div class="select-left" v-if="form.wxGroups.length">
<span v-for="(item, index) in form.wxGroups" :key="index" v-if="index < 9">{{ item.groupOwnerName }}</span>
<em v-if="form.wxGroups.length > 9">{{ form.wxGroups.length }}</em>
</div>
<i v-if="!form.wxGroups.length">请选择</i>
<div class="select-right">{{ form.filterCriteria.length ? '重新选择' : '选择' }}</div>
</div>
</ai-picker>
<div class="tips">
<p>消息预计送达居民群数</p>
<span>{{ groupLen }}</span>
<el-tooltip
placement="top"
content="将由指定群主发送给TA作为群主的所有的群由于企业微信限制当超过1000个时将只发送到最近活跃的1000个群">
<i class="iconfont iconModal_Warning"></i>
</el-tooltip>
</div>
</el-form-item>
<el-form-item label="发送内容" prop="content" style="width: 100%;" :rules="[{ required: true, message: '请输入发送内容', trigger: 'blur' }]">
<el-input size="small" type="textarea" :rows="6" maxlength="1300" show-word-limit placeholder="请输入文本内容..." v-model="form.content"></el-input>
<div class="add">
<div class="fileList" v-if="fileList.length">
<div class="add-item" v-for="(item, index) in fileList" :key="index">
<div class="left">
<img :src="mapIcon(item.msgType)"/>
<span>{{ item.mpTitle || item.name || item.linkTitle }}</span>
</div>
<i @click="removeFile(index)">删除</i>
</div>
</div>
<el-popover
placement="top"
width="280"
offset="0"
trigger="hover">
<div class="add-item" slot="reference" style="width: max-content;">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/add.png"/>
<span style="color: #2266FF; font-size: 12px;">添加附件类型</span>
</div>
<div class="AppAnnounceDetail-content-wrapper">
<el-upload
ref="upload"
multiple
:file-list="fileList"
:show-file-list="false"
:before-upload="v => handleChange(v, 10, '.jpg,.png,.jpeg')"
:limit="9"
action="/app/wxcp/upload/uploadFile"
accept=".jpg,.png,.jpeg"
:on-exceed="onExceed"
:http-request="v => submitUpload(v, '1')">
<div class="content-item" trigger>
<img src="https://cdn.cunwuyun.cn/dvcp/announce/big-img.png"/>
<p>图片</p>
</div>
</el-upload>
<el-upload
ref="upload"
multiple
:file-list="fileList"
:show-file-list="false"
:before-upload="v => handleChange(v, 10, '.mp4')"
:limit="9"
action="/app/wxcp/upload/uploadFile"
accept=".mp4"
:on-exceed="onExceed"
:http-request="v => submitUpload(v, '2')">
<div class="content-item" trigger>
<img src="https://cdn.cunwuyun.cn/dvcp/announce/big-video.png"/>
<p>视频</p>
</div>
</el-upload>
<el-upload
ref="upload"
multiple
:file-list="fileList"
:show-file-list="false"
:before-upload="v => handleChange(v, 20, '.zip,.rar,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt')"
:limit="9"
:on-exceed="onExceed"
action="/app/wxcp/upload/uploadFile"
accept=".zip,.rar,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt"
:http-request="v => submitUpload(v, '3')">
<div class="content-item" trigger>
<img src="https://cdn.cunwuyun.cn/dvcp/announce/folder.png"/>
<p>文件</p>
</div>
</el-upload>
<div class="content-item" @click="isShowAddLink = true">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/site.png"/>
<p>网页</p>
</div>
</div>
</el-popover>
</div>
<div class="tips">
<em>从本地上传图片最大支持10MB支持JPG,PNG格式视频最大支持10MB支持MP4格式文件最大支持20MB</em>
</div>
</el-form-item>
<el-form-item label="宣发审批" prop="enableExamine" style="width: 100%;" :rules="[{ required: true, message: '请输入任务名称', trigger: 'blur' }]">
<el-switch
v-model="form.enableExamine"
active-value="1"
inactive-value="0"
active-text="开启后创建的群发任务需要审批人进行审批">
</el-switch>
</el-form-item>
<el-form-item v-if="form.enableExamine === '1'" label="审批人员" prop="examines" style="width: 100%;" :rules="[{ required: true, message: '请选择审批人员', trigger: 'change' }]">
<ai-wechat-selecter :instance="instance" v-model="form.examines" @change="onUserChange">
<div class="AppAnnounceDetail-select">
<el-input class="AppAnnounceDetail-select__input" size="small" placeholder="请选择..." v-model="form.examinesName"></el-input>
<div class="select-left" v-if="form.examines.length">
<span v-for="(item, index) in form.examines" :key="index">{{ item.name }}</span>
</div>
<i v-if="!form.examines.length">请选择</i>
<div class="select-right">{{ form.examines.length ? '重新选择' : '选择' }}</div>
</div>
</ai-wechat-selecter>
</el-form-item>
</div>
</template>
</ai-card>
</el-form>
<div class="right">
<Phone :avatar="user.info.avatar" @close="isShowPhone = false" :isShowClose="false" :content="form.content" :fileList="fileList"></Phone>
</div>
<ai-dialog
:visible.sync="isShowAddLink"
width="920px"
title="链接消息"
@close="onClose"
@onConfirm="onLinkConfirm">
<el-form ref="linkForm" :model="linkForm" label-width="110px" label-position="right">
<div class="ai-form">
<el-form-item label="标题" style="width: 100%;" prop="linkTitle" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入标题"
maxlength="42"
show-word-limit
v-model="linkForm.linkTitle">
</el-input>
</el-form-item>
<el-form-item label="链接" style="width: 100%;" prop="linkUrl" :rules="[{ required: true, message: '请输入链接', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入链接"
maxlength="682"
show-word-limit
v-model="linkForm.linkUrl">
</el-input>
</el-form-item>
<el-form-item label="描述" style="width: 100%;" prop="linkDesc">
<el-input
size="small"
placeholder="请输入描述"
maxlength="170"
show-word-limit
v-model="linkForm.linkDesc">
</el-input>
</el-form-item>
<el-form-item label="封面图" prop="linkPicUrl" style="width: 100%;">
<ai-uploader :instance="instance" v-model="linkForm.linkPicUrl" :limit="1"></ai-uploader>
</el-form-item>
</div>
</el-form>
</ai-dialog>
<ai-dialog
:visible.sync="isShowDate"
width="590px"
title="定时发送"
customFooter>
<el-form ref="dateForm" :model="dateForm" label-width="130px" label-position="right">
<div class="ai-form">
<el-form-item label="定时发送时间" style="width: 100%;" prop="choiceTime" :rules="[{ required: true, message: '请选择定时发送时间', trigger: 'change' }]">
<el-date-picker
style="width: 100%;"
v-model="dateForm.choiceTime"
type="datetime"
size="small"
:picker-options="pickerOptions"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择定时发送时间">
</el-date-picker>
</el-form-item>
</div>
</el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="onClose">取消</el-button>
<el-button @click="onDateForm" type="primary" :loading="isLoading2" style="width: 92px;">确认</el-button>
</div>
</ai-dialog>
</div>
</template>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="confirm(0)" :loading="isLoading1" style="width: 120px;">通知成员发送</el-button>
<el-button type="primary" @click="confirm(1)">定时发送</el-button>
</template>
</ai-detail>
</template>
<script>
import Phone from './Phone'
import {mapActions, mapState} from 'vuex'
export default {
name: 'Add',
props: {
instance: Function,
dict: Object,
params: Object
},
components: {
Phone
},
data() {
return {
info: {},
department: [],
isLoading1: false,
isLoading2: false,
fileList: [],
isShowAddLink: false,
isShowAddMiniapp: false,
isShowDate: false,
isLoading: false,
linkForm: {
linkPicUrl: [],
linkDesc: '',
linkTitle: '',
linkUrl: ''
},
dateForm: {
choiceTime: ''
},
form: {
content: '',
choiceTime: '',
contents: [],
enableExamine: '0',
examines: [],
wxGroups: [],
wxGroupsName: '',
sendScope: '0',
sendType: 0,
name: '',
filterCriteria: [],
taskTitle: '',
examinesName: ''
},
girdNames: '',
id: '',
tagsList: [],
pickerOptions: {
disabledDate: e => {
return e.getTime() < (Date.now() - 60 * 1000 * 60 * 24)
}
}
}
},
computed: {
...mapState(['user']),
groupLen() {
let i = 0
this.form.wxGroups.forEach(v => {
i = i + v.groupIds.split(',').length
})
return i
}
},
created() {
if (this.params && this.params.id) {
this.id = this.params.id
this.getInfo(this.params.id)
} else {
this.getWxGroups()
}
},
methods: {
...mapActions(['initOpenData', 'transCanvas']),
getInfo(id) {
this.instance.post(`/app/appmasssendingtask/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.form = {
...this.form,
...res.data,
wxGroupsName: '1',
filterCriteria: res.data.filterCriteria.split(',')
}
if (res.data.girdNames) {
this.girdNames = res.data.girdNames.split(',')
}
this.dateForm.choiceTime = ''
if (res.data.examines && res.data.examines.length) {
this.form.examines = res.data.examines.map(v => {
return {
...v,
wxOpenUserId: v.examineUserId,
id: v.examineUserId,
name: v.examineUserName
}
})
this.form.examinesName = '1'
}
const content = res.data.contents.filter(v => v.msgType === '0')
if (content.length) {
this.$set(this.form, 'content', content[0].content)
}
this.fileList = res.data.contents.filter(v => v.msgType !== '0').map(v => {
return {
...v,
...v.sysFile
}
})
}
})
},
onUserChange(e) {
if (e.length) {
this.form.examinesName = '1'
} else {
this.form.wxGroupsName = ''
}
},
onScopeChange(e) {
this.form.filterCriteria = []
this.form.wxGroups = []
this.girdNames = ''
if (e === '0') {
this.getWxGroups()
} else {
this.form.filterCriteria = []
}
},
onPick(e) {
if (this.form.sendScope === '2' && e.length) {
this.girdNames = e.map(v => v.girdName)
}
},
onSelcetChange(e) {
if (e.length) {
this.form.wxGroupsName = '1'
this.$nextTick(() => {
this.getWxGroups()
})
} else {
this.form.wxGroupsName = ''
this.form.wxGroups = []
}
},
getWxGroups() {
this.instance.post(`/app/appmasssendingtask/queryWxGroups?sendScope=${this.form.sendScope}`, null, {
data: {
filterCriteria: this.form.filterCriteria.join(',')
},
headers: {'Content-Type': 'application/json;charset=utf-8'},
transformRequest: [function (data) {
return data.filterCriteria
}]
}).then(res => {
if (res.code === 0) {
this.form.wxGroups = res.data
}
})
},
onLinkConfirm() {
this.$refs.linkForm.validate((valid) => {
if (valid) {
this.fileList.push({
...this.linkForm,
linkPicUrl: this.linkForm.linkPicUrl.length ? this.linkForm.linkPicUrl[0].url : '',
msgType: '4'
})
this.isShowAddLink = false
}
})
},
onClose() {
this.linkForm.linkPicUrl = []
this.linkForm.linkDesc = ''
this.linkForm.linkTitle = ''
this.linkForm.linkUrl = ''
this.dateForm.choiceTime = ''
this.isShowDate = false
},
removeFile(index) {
this.fileList.splice(index, 1)
},
mapIcon(type) {
return {
1: 'https://cdn.cunwuyun.cn/dvcp/announce/img.png',
2: 'https://cdn.cunwuyun.cn/dvcp/announce/video.png',
3: 'https://cdn.cunwuyun.cn/dvcp/announce/folder.png',
4: 'https://cdn.cunwuyun.cn/dvcp/announce/site.png',
5: 'https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png'
}[type]
},
onBeforeUpload(event) {
return this.onOverSize(event)
},
getExtension(name) {
return name.substring(name.lastIndexOf('.'))
},
handleChange(e, size, accept) {
const isLt10M = e.size / 1024 / 1024 < size
const suffixName = this.getExtension(e.name)
const suffixNameList = accept.split(',')
if (suffixNameList.indexOf(`${suffixName.toLowerCase()}`) === -1) {
this.$message.error(`不支持该格式`)
return false
}
if (!isLt10M) {
this.$message.error(`大小不超过${10}MB!`)
return false
}
return true
},
onExceed() {
this.$message.error(`最多上传9个附件`)
},
submitUpload(file, type) {
const fileType = {
'1': 'image',
'2': 'video',
'3': 'file'
}[type]
let formData = new FormData()
formData.append('file', file.file)
formData.append('type', fileType)
let loading = this.$loading()
this.instance.post(`/app/wxcp/upload/uploadFile`, formData, {
withCredentials: false
}).then(res => {
if (res.code == 0) {
this.fileList.push({
...res.data.file,
media: res.data.media,
msgType: type,
sysFileId: res.data.file.id,
imgPicUrl: res.data.file.url,
mediaId: res.data.media.mediaId
})
this.$message.success('上传成功')
}
}).finally(() => loading.close())
},
onDateForm() {
this.$refs.dateForm.validate((valid) => {
if (valid) {
if (new Date(this.dateForm.choiceTime).getTime() < Date.now()) {
return this.$message.error('定时发送时间不得早于当前时间')
} else {
this.confirm(1)
}
}
})
},
confirm(sendType) {
this.$refs.form.validate((valid) => {
if (valid) {
if (!this.form.wxGroups.length) {
return this.$message.error('居民群数量不能为0')
}
if (sendType === 1 && !this.dateForm.choiceTime) {
this.isShowDate = true
return false
}
const contents = [
{
content: this.form.content,
msgType: '0'
},
...this.fileList
]
if (sendType === 0) {
this.isLoading1 = true
} else {
this.isLoading2 = true
}
this.instance.post(`/app/appmasssendingtask/addOrUpdate`, {
...this.form,
id: this.params.id,
wxGroups: this.form.wxGroups,
contents,
sendType,
choiceTime: this.dateForm.choiceTime,
filterCriteria: this.form.filterCriteria.join(','),
examines: this.form.examines.length ? this.form.examines.map(v => {
return {
...v,
examineUserId: v.id,
examineUserName: v.name
}
}) : []
}).then(res => {
if (res.code == 0) {
this.$message.success('提交成功')
setTimeout(() => {
this.cancel(true)
}, 600)
} else {
this.isLoading1 = false
this.isLoading2 = false
}
}).catch(() => {
this.isLoading1 = false
this.isLoading2 = false
})
}
})
},
cancel(isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: !!isRefresh
})
}
}
}
</script>
<style lang="scss">
.el-tooltip__popper.is-dark {
max-width: 240px;
}
.tags {
.tag-item {
display: flex;
align-items: center;
padding-bottom: 30px;
padding-top: 30px;
border-bottom: 1px solid #EEEEEE;
&:first-child {
padding-top: 0;
}
.el-tag {
margin-right: 8px;
color: #222222;
}
h2 {
width: 88px;
margin-right: 40px;
text-align: right;
color: #888888;
font-size: 14px;
}
}
}
.AppAnnounceDetail-content-wrapper {
display: flex;
align-items: center;
.content-item {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 64px;
height: 64px;
line-height: 1;
margin-right: 4px;
text-align: center;
background: #F9F9F9;
border-radius: 2px;
cursor: pointer;
&:hover {
opacity: 0.6;
}
&:last-child {
margin-right: 0;
}
img {
width: 32px;
height: 32px;
margin-bottom: 4px;
}
p {
color: #222;
font-size: 12px;
}
}
}
.AppAnnounceAdd {
.ai-detail__content {
.ai-detail__content--wrapper {
position: relative;
max-width: 100%;
margin: 0;
height: 100%;
overflow: hidden;
}
}
.ai-form {
textarea {
border-radius: 4px 4px 0 0!important;
}
}
* {
box-sizing: border-box;
}
.add {
display: flex;
flex-direction: column;
padding: 14px 16px;
background: #F9F9F9;
border-radius: 0px 0px 4px 4px;
border: 1px solid #D0D4DC;
border-top: none;
.add-item {
display: flex;
align-items: center;
line-height: 1;
cursor: pointer;
&:hover {
opacity: 0.6;
}
img {
width: 20px;
height: 20px;
margin-right: 2px;
}
span {
color: #222;
font-size: 14px;
}
}
.fileList {
margin-bottom: 12px;
.add-item {
justify-content: space-between;
margin-bottom: 8px;
.left {
display: flex;
align-items: center;
}
i {
font-size: 14px;
cursor: pointer;
font-style: normal;
color: red;
&:hover {
opacity: 0.6;
}
}
&:last-child {
margin-bottom: 0;
}
}
}
}
.AppAnnounceDetail-container {
display: flex;
position: relative;
height: 100%;
padding: 0 20px;
overflow: auto;
overflow: overlay;
.left {
flex: 1;
margin-right: 20px;
}
.right {
position: sticky;
top: 0;
}
.AppAnnounceDetail-select {
display: flex;
align-items: center;
min-height: 32px;
line-height: 1;
background: #F5F5F5;
border-radius: 4px;
border: 1px solid #D0D4DC;
cursor: pointer;
overflow: hidden;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
border-color: #26f;
}
& > i {
flex: 1;
height: 100%;
line-height: 32px;
padding: 0 12px;
color: #888888;
font-size: 14px;
font-style: normal;
border-right: 1px solid #D0D4DC;
background: #fff;
}
.AppAnnounceDetail-select__input {
position: absolute;
left: 0;
top: 0;
z-index: -1;
opacity: 0;
height: 100%;
}
.select-right {
height: 100%;
padding: 0 12px;
color: #222222;
font-size: 12px;
cursor: pointer;
transition: all ease 0.3s;
&:hover {
opacity: 0.5;
}
}
.select-left {
display: flex;
flex-wrap: wrap;
flex: 1;
padding: 5px 0 0px 12px;
border-right: 1px solid #D0D4DC;
border-radius: 4px 0 0 4px;
background: #fff;
em {
height: 22px;
line-height: 22px;
margin: 0 4px 5px 0;
color: #222222;
font-size: 12px;
font-style: normal;
}
span {
height: 22px;
line-height: 22px;
margin: 0 4px 5px 0;
padding: 0 8px;
font-size: 12px;
color: #222222;
background: #F3F4F7;
border-radius: 2px;
border: 1px solid #D0D4DC;
}
}
}
}
.tips {
display: flex;
align-items: center;
font-size: 14px;
color: #222222;
span {
margin: 0 3px;
color: #2266FF;
}
i {
color: #8899bb;
}
em {
line-height: 20px;
margin-top: 8px;
color: #888888;
font-size: 12px;
font-style: normal;
}
}
}
</style>

View File

@@ -0,0 +1,655 @@
<template>
<ai-detail class="AppAnnounceDetail">
<template slot="title">
<ai-title title="群发详情" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</template>
<template slot="content">
<ai-card title="基础信息">
<template #right>
<div class="right-tips" v-if="info.status === '4'">
<el-tooltip
placement="top"
content="任务开始后3天内15分钟更新1次3天后访问页面时触发更新1小时最多刷新1次">
<i class="iconfont iconDetails"></i>
</el-tooltip>
<span>数据更新于{{ info.dataUpdateTime }}</span>
</div>
</template>
<template #content>
<ai-wrapper>
<ai-info-item label="任务名称" isLine :value="info.taskTitle"></ai-info-item>
<ai-info-item label="任务状态" isLine>
<span :style="{ color: dict.getColor('mstStatus', info.status) }">{{ dict.getLabel('mstStatus', info.status) }}</span>
</ai-info-item>
<ai-info-item label="创建人" isLine>
<div class="user">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/user.png" />
<span>{{ info.createUserName }}</span>
<span>({{ info.createUserDeptName }})</span>
</div>
</ai-info-item>
<ai-info-item label="审批人" isLine v-if="info.enableExamine === '1'">
<div class="user-wrapper">
<div class="user" v-for="(item, index) in info.examines" :key="index">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/user.png" />
<span>{{ item.examineUserName }}</span>
</div>
</div>
</ai-info-item>
<ai-info-item label="创建时间" :value="info.createTime"></ai-info-item>
<ai-info-item label="群发时间" :value="info.choiceTime"></ai-info-item>
<ai-info-item label="群发范围" isLine>
<div class="text">
<span>{{ info.sendScope === '0' ? '全部' : '按条件筛选的' }}</span>
<i>{{ groups.length }}</i>
<span>个居民群</span>
<em @click="isShowGroups = true">详情</em>
</div>
</ai-info-item>
<ai-info-item label="消息内容" isLine>
<div class="msg">
<p>{{ content }}</p>
<div class="msg-bottom">
<div class="left" v-if="fileList.length">
<img :src="mapIcon(fileList[0].msgType)" />
<span>{{ mapType(fileList[0].msgType) }}{{ fileList[0].mpTitle || fileList[0].name || fileList[0].linkTitle }} </span>
<i>{{ fileList.length }}</i>
<span>个附件</span>
</div>
<div class="left" v-else>
<span>暂无附件</span>
</div>
<div class="right" @click="isShowPhone = true">预览消息</div>
</div>
</div>
</ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="成员统计">
<template #content>
<div class="content-item">
<div class="top">
<div class="top-item">
<div class="top-item__title">
<h3>计划执行成员</h3>
</div>
<p>{{ memberInfo.planCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>未执行成员</h3>
</div>
<p>{{ memberInfo.unExecutedCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>已执行成员</h3>
</div>
<p>{{ memberInfo.executedCount || 0 }}</p>
</div>
<div class="top-item">
<div class="top-item__title">
<h3>无法执行成员</h3>
<el-tooltip
placement="top"
content="由于员工不在可见范围、离职、客户群接收已达到上限等原因,无法执行群发任务的成员总数">
<i class="iconfont iconDetails"></i>
</el-tooltip>
</div>
<p>{{ memberInfo.cannotExecuteCount || 0 }}</p>
</div>
</div>
<div class="bottom">
<div class="bottom-search">
<div class="left">
<el-radio-group v-model="search1.sendStatus" size="small" @change="search1.current = 1, getMemberInfo()">
<el-radio-button size="small" label="0">未执行</el-radio-button>
<el-radio-button size="small" label="1">已执行</el-radio-button>
<el-radio-button size="small" label="2">无法执行</el-radio-button>
</el-radio-group>
<ai-picker
dialogTitle="选择部门"
action="/app/wxcp/wxdepartment/departList"
:instance="instance"
@pick="e => onUserChange(e, 'search1')" :multiple="false" v-model="user1">
<div class="userSelcet">
<span style="color: #606266;" v-if="search1.deptartId"><ai-open-data type="departmentName" :openid="search1.deptartId"></ai-open-data></span>
<span v-else>部门</span>
<i class="el-icon-arrow-up" v-if="!search1.deptartId"></i>
<i class="el-icon-circle-close" v-if="search1.deptartId" @click.stop="user1 = [], search1.deptartId = '', search1.current = 1, getMemberInfo()"></i>
</div>
</ai-picker>
</div>
<el-button :type="isDisabled ? '' : 'primary'" :disabled="isDisabled" @click="sendMsg(0)" v-if="info.status === '4'">{{ isDisabled ? min + '分钟后可再次提醒' : '提醒成员发送' }}</el-button>
</div>
<ai-table
:tableData="tableData1"
:col-configs="colConfigs1"
:total="total1"
border
tableSize="small"
:current.sync="search1.current"
:size.sync="search1.size"
@getList="getMemberInfo">
<el-table-column slot="user" label="成员" align="left">
<template slot-scope="{ row }">
<div class="userinfo">
<span>{{ row.groupOwnerName }}</span>
<span style="color: #999">{{ row.mainDepartmentName }}</span>
</div>
</template>
</el-table-column>
</ai-table>
</div>
</div>
</template>
</ai-card>
<ai-dialog
:visible.sync="isShowGroups"
width="890px"
title="群发范围"
@onConfirm="isShowGroups = false">
<ai-table
:tableData="info.wxGroups"
:col-configs="colConfigs3"
border
tableSize="small"
:isShowPagination="false"
@getList="() => {}">
</ai-table>
</ai-dialog>
<div class="detail-phone" v-if="isShowPhone">
<div class="mask"></div>
<Phone :avatar="user.info.avatar" @close="isShowPhone = false" :isShowClose="true" :content="content" :fileList="fileList"></Phone>
</div>
</template>
</ai-detail>
</template>
<script>
import { mapState } from 'vuex'
import Phone from './Phone'
export default {
name: 'Detail',
props: {
instance: Function,
dict: Object,
params: Object
},
components: {
Phone
},
data () {
return {
total1: 0,
isShowGroups: false,
isShowPhone: false,
user1: [],
user2: [],
radio1: '未执行',
search1: {
current: 1,
size: 10,
deptartId: '',
type: 0,
sendStatus: '0'
},
memberInfo: {},
groupInfo: {},
tableData1: [],
fileList: [],
tableData2: [],
info: {},
content: '',
currIndex: 0,
colConfigs3: [
{ prop: 'groupOwnerName', label: '群主' },
{ prop: 'groupNames', label: '群名称' }
],
colConfigs1: [
{ slot: 'user', label: '成员' },
{ prop: 'groupCount', label: '预计送达居民群', align: 'center' }
],
groups: [],
timer: null,
min: 60,
isDisabled: false,
rejecterId: ''
}
},
computed: {
...mapState(['user'])
},
created () {
this.getInfo(this.params.id)
this.getMemberInfo()
},
destroyed () {
clearInterval(this.timer)
},
methods: {
getMemberInfo () {
this.instance.post(`/app/appmasssendingtask/detailStatistics`, null, {
params: {
...this.search1,
taskId: this.params.id
}
}).then(res => {
if (res.code === 0) {
this.tableData1 = res.data.executedList.records
this.total1 = res.data.executedList.total
this.memberInfo = res.data
}
})
},
onUserChange (e, search) {
if (e.length) {
this[search].deptartId = e[0].id
} else {
this[search].deptartId = ''
}
this[search].current = 1
this.getMemberInfo()
},
sendMsg () {
this.instance.post(`/app/appmasssendingtask/remindSend?id=${this.params.id}`).then(res => {
if (res.code === 0) {
this.$message.success('提醒成功')
this.getInfo(this.params.id)
}
})
},
countdown () {
this.timer = setInterval(() => {
const nowTime = this.$moment(new Date())
const min = nowTime.diff(this.info.remindTime, 'minute')
this.min = (60 - min)
if (this.min <= 0) {
this.isDisabled = false
clearInterval(this.timer)
} else {
this.isDisabled = true
}
}, 1000)
},
getInfo (id) {
this.instance.post(`/app/appmasssendingtask/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.info = res.data
if (res.data.status === '4' && res.data.remindTime) {
this.countdown()
}
const content = res.data.contents.filter(v => v.msgType === '0')
if (content.length) {
this.content = content[0].content
}
this.fileList = res.data.contents.filter(v => v.msgType !== '0').map(v => {
return {
...v,
...v.sysFile
}
})
this.info.wxGroups = res.data.wxGroups.map(v => {
this.groups.push(...v.groupIds.split(','))
return {
...v,
groupIds: v.groupIds.split(',')
}
})
if (res.data.examines && res.data.examines.length) {
const user = res.data.examines.filter(v => v.examineStatus === '2')
if (user.length) {
this.rejecterId = user[0].examineUserId
}
}
}
})
},
mapType (type) {
return {
1: '图片',
2: '视频',
3: '文件',
4: '网站',
5: '小程序'
}[type]
},
mapIcon (type) {
return {
1: 'https://cdn.cunwuyun.cn/dvcp/announce/img.png',
2: 'https://cdn.cunwuyun.cn/dvcp/announce/video.png',
3: 'https://cdn.cunwuyun.cn/dvcp/announce/folder.png',
4: 'https://cdn.cunwuyun.cn/dvcp/announce/site.png',
5: 'https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png'
}[type]
},
cancel (isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: !!isRefresh
})
}
}
}
</script>
<style scoped lang="scss">
.AppAnnounceDetail {
position: relative;
.user-wrapper {
display: flex;
flex-wrap: wrap;
}
.detail-phone {
position: fixed;
left: 0%;
top: 0%;
z-index: 11;
width: 100%;
height: 100%;
.mask {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 1;
background: rgba($color: #000000, $alpha: 0.6);
}
::v-deep .phone-container {
position: absolute;
left: 50%;
top: 50%;
z-index: 11;
transform: translate(-50%, -50%);
}
}
.userSelcet {
display: flex;
align-items: center;
justify-content: space-between;
width: 215px;
height: 32px;
line-height: 32px;
margin-left: 12px;
border-radius: 4px;
border: 1px solid #d0d4dc;
overflow: hidden;
cursor: pointer;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
border-color: #26f;
}
i {
display: flex;
position: relative;
align-items: center;
justify-content: center;
width: 30px;
height: 100%;
line-height: 32px;
font-size: 14px;
text-align: center;
color: #d0d4dc;
transform: rotateZ(180deg);
}
.el-icon-circle-close:hover {
opacity: 0.6;
}
span {
flex: 1;
padding: 0 15px;
font-size: 12px;
color: $placeholderColor;
}
}
.userinfo {
display: flex;
flex-direction: column;
justify-content: center;
line-height: 1;
span:first-child {
margin-bottom: 4px;
}
}
.user {
display: flex;
align-items: center;
line-height: 1;
margin-right: 8px;
img {
width: 16px;
height: 16px;
margin-right: 2px;
}
span {
position: relative;
top: 2px;
color: #222222;
font-size: 12px;
}
}
.text {
display: flex;
align-items: center;
i {
color: #2266FF;
font-style: normal;
}
em {
margin-left: 8px;
color: #2266FF;
font-size: 12px;
font-style: normal;
cursor: pointer;
transition: all ease 0.3s;
&:hover {
opacity: 0.6;
}
}
}
.msg {
background: #F9F9F9;
border-radius: 2px;
border: 1px solid #D0D4DC;
p {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
line-height: 38px;
padding: 0px 12px;
overflow: hidden;
}
.msg-bottom {
display: flex;
align-items: center;
justify-content: space-between;
height: 38px;
padding: 0 16px;
border-top: 1px solid #D0D4DC;
.left {
display: flex;
align-items: center;
img {
width: 16px;
height: 16px;
margin-right: 8px;
}
span {
color: #222222;
font-size: 14px;
}
i {
color: #2266FF;
font-size: 14px;
font-style: normal;
}
}
.right {
color: #2266FF;
font-size: 12px;
cursor: pointer;
&:hover {
opacity: 0.6;
}
}
}
}
::v-deep .AppAnnounceDetail-title {
display: flex;
align-items: center;
span {
height: 100%;
line-height: 56px;
margin-right: 32px;
color: #888888;
font-size: 16px;
font-weight: 600;
transition: all ease 0.3s;
border-bottom: 3px solid transparent;
cursor: pointer;
user-select: none;
&:hover {
color: #222;
}
&:last-child {
margin-right: 0;
}
&.active {
color: #222222;
border-bottom: 3px solid #2266FF;
}
}
}
.content-item {
.top {
display: flex;
align-items: center;
margin-bottom: 16px;
.top-item {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
height: 90px;
margin-right: 16px;
padding: 0 16px;
background: #F9F9F9;
border-radius: 2px;
&:last-child {
margin-right: 0;
}
.top-item__title {
display: flex;
align-items: center;
margin-bottom: 8px;
i {
margin-left: 4px;
color: #8899bb;
font-size: 16px;
}
}
h3 {
color: #222222;
font-size: 14px;
font-weight: 700;
}
p {
color: #2266FF;
font-size: 24px;
font-weight: 700;
}
}
}
.bottom-search {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.left {
display: flex;
align-items: center;
}
}
}
::v-deep .right-tips {
display: flex;
align-items: center;
i {
margin-right: 4px;
color: #8899bb;
font-size: 16px;
}
span {
color: #888888;
font-size: 12px;
}
}
}
</style>

View File

@@ -0,0 +1,294 @@
<template>
<ai-list class="AppAnnounce">
<template slot="title">
<ai-title title="群发居民朋友圈" isShowBottomBorder>
<template #sub>
<span>管理员统一创建宣发任务成员确认发布后将在所有已添加的居民朋友圈进行展示一个居民每天只能看到同个人发送的3条朋友圈</span>
</template>
</ai-title>
</template>
<template slot="content">
<ai-search-bar class="search-bar">
<template #left>
<el-button size="small" type="primary" icon="iconfont iconAdd" @click="toAdd('')">创建宣发</el-button>
<ai-select
v-model="search.status"
@change="search.current = 1, getList()"
placeholder="任务状态"
:selectList="dict.getDict('mstStatus')">
</ai-select>
<el-date-picker
v-model="search.startTime"
type="date"
size="small"
value-format="yyyy-MM-dd"
@change="search.current = 1, getList()"
placeholder="选择群发开始日期">
</el-date-picker>
<el-date-picker
v-model="search.endTime"
type="date"
size="small"
value-format="yyyy-MM-dd"
@change="search.current = 1, getList()"
placeholder="选择群发结束日期">
</el-date-picker>
<ai-wechat-selecter :instance="instance" @change="onUserChange" :isMultiple="false" v-model="user">
<div class="userSelcet">
<span style="color: #606266;" v-if="search.createUserId">{{ name }}</span>
<span v-else>创建人</span>
<i class="el-icon-arrow-up" v-if="!search.createUserId"></i>
<i class="el-icon-circle-close" v-if="search.createUserId" @click.stop="user = [], search.createUserId = '', name = '', search.current = 1, getList()"></i>
</div>
</ai-wechat-selecter>
</template>
<template slot="right">
<el-input
v-model="search.taskTitle"
size="small"
v-throttle="() => { search.current = 1, getList() }"
placeholder="请输入任务名称"
clearable
@clear="search.current = 1, search.taskTitle = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
v-loading="loading"
style="margin-top: 6px; width: 100%;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="user" width="140px" label="创建人" align="center">
<template slot-scope="{ row }">
<div class="userinfo">
<span>{{ row.createUserName }}</span>
<span style="color: #999">{{ row.createUserDeptName }}</span>
</div>
</template>
</el-table-column>
<el-table-column slot="options" width="140px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="remindExamine(row.id)" v-if="['0'].includes(row.status)">催办</el-button>
<el-button type="text" @click="cancel(row.id)" v-if="['0'].includes(row.status)">撤回</el-button>
<el-button type="text" @click="toDetail(row.id)">详情</el-button>
<el-button type="text" @click="toAdd(row.id)" v-if="['1', '3'].includes(row.status)">编辑</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
export default {
name: 'List',
props: {
instance: Function,
dict: Object
},
data() {
return {
search: {
current: 1,
size: 10,
status: '',
createUserId: '',
taskTitle: '',
startTime: '',
endTime: ''
},
user: [],
tableData: [],
loading: false,
total: 0,
colConfigs: [
{ prop: 'taskTitle', label: '任务名称' },
{ prop: 'typeName', label: '群发类型', align: 'center' },
{ slot: 'user', label: '创建人', openType: 'userName', align: 'center' },
{ prop: 'choiceTime', label: '群发时间', align: 'center' },
{
prop: 'status',
align: 'center',
label: '状态',
render: (h, {row}) => {
return h('span', {
style: {
color: this.dict.getColor('mstStatus', row.status)
}
}, this.dict.getLabel('mstStatus', row.status))
}
},
{ prop: 'completionRate', label: '任务完成率', align: 'center', formart: v => v ? v === '0.0' ? '0%' : `${v}%` : '-' }
]
}
},
created () {
this.dict.load('mstStatus', 'mstSendType').then(() => {
this.getList()
})
},
methods: {
onUserChange (e) {
if (e.length) {
this.name = e[0].name
this.search.createUserId = e[0].id
} else {
this.search.createUserId = ''
this.name = ''
}
this.search.current = 1
this.getList()
},
getList() {
this.loading = true
this.instance.post(`/app/appmasssendingtask/list`, null, {
params: {
...this.search,
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records.map(v => {
return {
...v,
typeName: '群发居民群'
}
})
this.total = res.data.total
this.$nextTick(() => {
this.loading = false
})
} else {
this.loading = false
}
}).catch(() => {
this.loading = false
})
},
remindExamine (id) {
this.$confirm('确认再次通知任务审核人员?').then(() => {
this.instance.post(`/app/appmasssendingtask/remindExamine?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('催办成功!')
this.getList()
}
})
})
},
cancel (id) {
this.$confirm('确认撤回该群发任务?').then(() => {
this.instance.post(`/app/appmasssendingtask/cancel?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('撤回成功!')
this.getList()
}
})
})
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appmasssendingtask/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toAdd(id) {
this.$emit('change', {
type: 'Add',
params: {
id
}
})
},
toDetail (id) {
this.$emit('change', {
type: 'Detail',
params: {
id
}
})
}
}
}
</script>
<style lang="scss" scoped>
.AppAnnounce {
height: 100%;
.userinfo {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
line-height: 1;
span:first-child {
margin-bottom: 4px;
}
}
.userSelcet {
display: flex;
align-items: center;
justify-content: space-between;
width: 215px;
height: 32px;
line-height: 32px;
border-radius: 4px;
border: 1px solid #d0d4dc;
overflow: hidden;
cursor: pointer;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:hover {
border-color: #26f;
}
i {
display: flex;
position: relative;
align-items: center;
justify-content: center;
width: 30px;
height: 100%;
line-height: 32px;
font-size: 14px;
text-align: center;
color: #d0d4dc;
transform: rotateZ(180deg);
}
.el-icon-circle-close:hover {
opacity: 0.6;
}
span {
flex: 1;
padding: 0 15px;
font-size: 12px;
color: $placeholderColor;
}
}
}
</style>

View File

@@ -0,0 +1,344 @@
<template>
<div class="phone-container">
<img class="close" @click="$emit('close')" v-if="isShowClose" src="https://cdn.cunwuyun.cn/dvcp/announce/close.png" />
<img class="phone" src="https://cdn.cunwuyun.cn/dvcp/announce/phone.png" />
<img class="phone-wrapper" src="https://cdn.cunwuyun.cn/dvcp/announce/phone-wrapper.png" />
<div class="right-content">
<div class="msg-list">
<div class="msg-item" v-if="content">
<div class="msg-item__left">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/avatar.png" />
</div>
<div class="msg-item__right">
<div class="msg-wrapper msg-text">
<p>{{ content }}</p>
</div>
</div>
</div>
<div class="msg-item" v-for="item in fileList" :key="item.id">
<div class="msg-item__left">
<img src="https://cdn.cunwuyun.cn/dvcp/announce/avatar.png" />
</div>
<div class="msg-item__right" :class="[['1', '2'].indexOf(item.msgType) !== -1 ? 'left-border' : '']">
<div class="msg-wrapper msg-img" v-if="item.msgType === '1'">
<img :src="item.imgPicUrl" />
</div>
<div class="msg-wrapper msg-video" v-if="item.msgType === '2'">
<video controls :src="item.url"></video>
</div>
<div class="msg-wrapper msg-file" v-if="item.msgType === '3'">
<div class="msg-left">
<h2>{{ item.name }}</h2>
<p>{{ item.fileSizeStr }}</p>
</div>
<img :src="mapIcon(item.name)" />
</div>
<div class="msg-wrapper msg-link" v-if="item.msgType === '4'">
<h2>{{ item.linkTitle }}</h2>
<div class="msg-right">
<p>{{ item.linkDesc }}</p>
<img :src="item.linkPicUrl || 'https://cdn.cunwuyun.cn/dvcp/announce/html.png'" />
</div>
</div>
<div class="msg-wrapper msg-miniapp" v-if="item.msgType === '5'">
<h2>{{ item.mpTitle }}</h2>
<img :src="item.url" />
<div class="msg-bottom">
<i>小程序</i>
<img src="https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['fileList', 'avatar', 'content', 'isShowClose'],
watch: {
fileList (v) {
if (v.length) {
setTimeout(() => {
document.querySelector('.right-content').scrollTo(0, 999999)
}, 800)
}
}
},
methods: {
mapIcon (fileName) {
if (['.zip', '.rar'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/zip.png'
}
if (['.doc', '.docx'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/world.png'
}
if (['.xls', '.xlsx'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/xls.png'
}
if (['.txt'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/txt.png'
}
if (['.pdf'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/pdf.png'
}
if (['.ppt', '.pptx'].indexOf(this.getExtension(fileName)) !== -1) {
return 'https://cdn.cunwuyun.cn/dvcp/announce/ppt.png'
}
},
getExtension(name) {
return name.substring(name.lastIndexOf('.'))
}
}
}
</script>
<style lang="scss" scoped>
.phone-container {
width: 338px;
height: 675px;
padding: 80px 15px 100px 32px;
.phone {
position: absolute;
left: 13px;
top: 4px;
z-index: 1;
width: 314px;
height: 647px;
}
.close {
position: absolute;
top: 0;
right: 0;
z-index: 111;
width: 60px;
height: 60px;
cursor: pointer;
transition: all ease 0.5s;
transform: translate(100%, -50%);
&:hover {
opacity: 0.7;
}
}
.phone-wrapper {
position: absolute;
left: 0;
top: 0;
z-index: 2;
width: 338px;
height: 675px;
}
.right-content {
position: relative;
z-index: 11;
height: 100%;
overflow-y: auto;
.msg-item {
display: flex;
margin-bottom: 20px;
.msg-item__left {
width: 42px;
height: 42px;
margin-right: 16px;
border-radius: 4px;
flex-shrink: 1;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
.msg-item__right {
position: relative;
flex: 1;
&::after {
position: absolute;
top: 16px;
left: 0;
z-index: 1;
width: 0;
height: 0;
border-right: 6px solid #fff;
border-left: 6px solid transparent;
border-bottom: 6px solid transparent;
border-top: 6px solid transparent;
content: " ";
transform: translate(-100%, 0%);
}
&.left-border::after {
display: none;
}
.msg-img img {
max-width: 206px;
max-height: 200px;
}
.msg-video video {
max-width: 206px;
max-height: 200px;
}
.msg-text {
max-width: 206px;
width: max-content;
line-height: 1.3;
padding: 12px;
background: #FFFFFF;
border-radius: 5px;
word-break: break-all;
font-size: 14px;
color: #222222;
}
.msg-miniapp {
width: 206px;
padding: 0 12px;
text-align: justify;
font-size: 0;
background: #FFFFFF;
border-radius: 5px;
font-size: 14px;
color: #222222;
h2 {
line-height: 1.2;
padding: 8px 0;
border-bottom: 1px solid #eee;
color: #222222;
font-size: 14px;
}
& > img {
width: 100%;
height: 120px;
margin-bottom: 8px;
}
.msg-bottom {
display: flex;
align-items: center;
line-height: 1;
padding: 4px 0;
border-top: 1px solid #eee;
i {
margin-right: 4px;
font-size: 12px;
font-style: normal;
color: #999;
}
img {
width: 16px;
height: 16px;
border-radius: 50%;
}
}
}
.msg-file {
display: flex;
align-items: center;
width: 206px;
padding: 12px;
background: #FFFFFF;
border-radius: 5px;
.msg-left {
flex: 1;
margin-right: 18px;
h2 {
display: -webkit-box;
flex: 1;
line-height: 16px;
margin-bottom: 4px;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
text-overflow: ellipsis;
overflow: hidden;
color: #222222;
font-size: 14px;
width: 120px;
}
p {
color: #888888;
font-size: 12px;
}
}
img {
width: 44px;
height: 44px;
border-radius: 2px;
}
}
.msg-link {
width: 206px;
padding: 12px;
background: #FFFFFF;
border-radius: 5px;
h2 {
margin-bottom: 4px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #222222;
font-size: 14px;
font-weight: normal;
}
.msg-right {
display: flex;
align-items: center;
p {
display: -webkit-box;
flex: 1;
line-height: 16px;
margin-right: 10px;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
text-overflow: ellipsis;
overflow: hidden;
color: #888;
font-size: 12px;
}
img {
width: 50px;
height: 50px;
border-radius: 4px;
}
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,759 @@
<template>
<ai-list class="AppAnnounceStatistics">
<template slot="content">
<div class="statistics-content">
<ai-title title="宣发日历"></ai-title>
<div class="flex-content">
<div class="flex-left">
<div class="date-header">
<p>{{chooseYear}}{{chooseMonth}}</p>
<div>
<el-date-picker size="small"
v-model="searchMonth"
type="month" value-format="yyyy-MM"
placeholder="选择日期" @change="searchMonthChange">
</el-date-picker>
</div>
</div>
<el-calendar v-model="calendarDate">
<template
slot="dateCell"
slot-scope="{date, data}" >
<div class="flex-date">
<span>{{Number(data.day.substring(8, 10))}}</span>
<span class="tips" v-if="data.day.substring(5, 7) == chooseMonth && dateList[Number(data.day.substring(8, 10))] && dateList[Number(data.day.substring(8, 10))].taskList.length">{{dateList[Number(data.day.substring(8, 10))].taskList.length}}</span>
</div>
</template>
</el-calendar>
</div>
<div class="flex-right">
<div class="title">{{chooseMonth}}{{chooseDay}}日宣发内容</div>
<div class="list-content" v-if="taskList.length">
<el-timeline >
<el-timeline-item v-for="(item, index) in taskList" :key="index">
<el-card>
<div class="flex-between">
<p class="item-title">{{item.taskTitle}}</p>
<span class="item-time" v-if="item.choiceTime">{{item.choiceTime.substring(10, 16)}}</span>
</div>
<div class="item-info item-created">
<span class="label">创建人</span>
<ai-open-data type="userName" :openid="item.createUserId" class="name"></ai-open-data>
</div>
<div class="item-info item-dept">
<span class="label">创建部门</span>
<ai-open-data type="departmentName" :openid="item.createUserDept" class="name"></ai-open-data>
</div>
<div class="flex-between">
<!-- <div class="item-info">群发类型<span>{{$dict.getLabel('mstSendType', item.sendType) || ''}}</span></div> -->
<div class="item-info"><span class="label">群发类型</span><span>群发居民群</span></div>
<span class="item-btn" @click="$router.push({name: '357e228ba8e64008ace90d095a7a0dd7', params: { id: item.id }})">详情</span>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
<ai-empty v-if="!taskList.length" />
</div>
</div>
</div>
<div class="statistics-content">
<div class="flex-between mar-b16">
<ai-title title="宣发效果"></ai-title>
<div class="right-search">
<div class="time-select" :class="effectType == index ? 'active' : ''" v-for="(item, index) in dateTypeList" :key="index" @click="changeEffectType(index)">{{item}}</div>
<ai-picker :instance="instance" @pick="e => onUserChange(e)" :multiple="false" dialogTitle="选择部门" action="/app/wxcp/wxdepartment/departList">
<div class="time-select">
<span class="dept-name" style="color:#999;" v-if="deptList && !deptList.length">宣发部门</span>
<ai-open-data class="dept-name" type="departmentName" :openid="deptList[0].id" v-else/>
<i class="el-icon-arrow-down"></i>
</div>
</ai-picker>
</div>
</div>
<div class="line-content">
<div class="flex1">
<div class="header">
<p>累计创建宣发任务数</p>
<h2>{{effectData.createCount}}</h2>
</div>
<div class="chart-content">
<div class="chart-title">宣发任务数</div>
<div class="chart-box" id="createChart"></div>
</div>
</div>
<div class="flex1">
<div class="header">
<p>累计执行宣发次数</p>
<h2>{{effectData.executeCount}}</h2>
</div>
<div class="chart-content">
<div class="chart-title">宣发次数</div>
<div class="chart-box" id="executeChart"></div>
</div>
</div>
<div class="flex1 mar-r0">
<div class="header">
<p>累计触达人次</p>
<h2>{{effectData.receiveCount}}</h2>
</div>
<div class="chart-content">
<div class="chart-title">触达人次</div>
<div class="chart-box" id="receiveChart"></div>
</div>
</div>
</div>
</div>
<div class="statistics-content">
<div class="flex-between mar-b16">
<ai-title title="宣发明细"></ai-title>
<div class="right-search">
<div class="time-select" :class="departType == index ? 'active' : ''" v-for="(item, index) in dateTypeList" :key="index" @click="changeDepartType(index)">{{item}}</div>
</div>
</div>
<div id="departBarChart" v-if="isDepartData"></div>
<ai-empty v-if="!isDepartData"></ai-empty>
</div>
<ai-dialog :visible.sync="dialogDate" title="选择时间" width="500px" customFooter>
<el-date-picker v-model="timeList" size="small" type="daterange" value-format="yyyy-MM-dd"
range-separator="" start-placeholder="开始日期" end-placeholder="结束日期">
</el-date-picker>
<el-button slot="footer" @click="selectDete" type="primary">确认</el-button>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
import * as echarts from "echarts";
import { mapActions, mapState } from 'vuex';
export default {
name: 'AppAnnounceWeChatStatistics',
label: '协同宣发朋友圈统计',
props: {
instance: Function,
dict: Object,
permissions: Function
},
data () {
return {
calendarDate: new Date(),
dateList: {},
chooseYear: '',
chooseMonth: '',
chooseDay: '',
searchMonth: '',
taskList: [],
effectType: 0, // 宣发效果类型 0近七天、1近30天、2近一年、3自定义
effectData: {},
createChart: null,
executeChart: null,
receiveChart: null,
departType: 0, // 宣发明细类型 0近七天、1近30天、2近一年、3自定义
dateTypeList: ['近7天', '近30天', '近1年', '自定义'],
departData: {},
departBarChart: null,
dialogDate: false,
timeListEffect: '',
timeListDepart: '',
timeList: '',
isEffectTimeSelect: false,
deptList: [],
selectDeptName: '',
isDepartData: true,
departBarData: [],
type: '',
}
},
computed: {
...mapState(['user']),
},
watch: {
calendarDate: function() {
var year = '' , month = '', date = ''
if(this.calendarDate.length == 9) { // 月份选择器触发
year = this.calendarDate.substring(0, 4)
month = this.calendarDate.substring(5, 7)
date = this.calendarDate.substring(8, 10)
}else { // 日历点击
year = this.calendarDate.getFullYear();
month = this.calendarDate.getMonth() + 1;
date = this.calendarDate.getDate()
if (month >= 1 && month <= 9) {
month = "0" + month;
}
if(this.chooseMonth != month) { // 日历点击不同月
this.searchMonth = ''
}
}
this.chooseDay = date
if(this.chooseMonth != month || this.chooseYear != year) { // 不同年/不同月重新请求日历列表
this.getCalendarList(year, month)
} else {
this.getTaskList(date)
}
this.chooseMonth = month
this.chooseYear = year
}
},
created() {
var year = this.calendarDate.getFullYear();
var month = this.calendarDate.getMonth() + 1;
var date = this.calendarDate.getDate()
if (month >= 1 && month <= 9) {
month = "0" + month;
}
this.chooseMonth = month
this.chooseYear = year
this.chooseDay = date
this.getCalendarList(year, month)
this.getEffect()
this.getDepart()
this.dict.load('mstSendType')
},
methods: {
...mapActions(['initOpenData', 'transCanvas']),
onUserChange (e) {
this.deptList = e
this.getEffect()
},
selectDete() {
if(!this.timeList || !this.timeList.length) {
return this.$message.error('请选择自定义时间');
}
if(this.isEffectTimeSelect) { //宣发效果
this.timeListEffect = this.timeList
this.effectType = 3
this.getEffect()
} else { //宣发明细
this.timeListDepart = this.timeList
this.departType = 3
this.getDepart()
}
this.dialogDate = false
},
searchMonthChange() {
this.calendarDate = this.searchMonth + '-1'
},
getCalendarList(year, month){
this.instance.post(`/app/appmasssendingtask/statisticsCalendar?yyyyMM=${year}${month}`).then(res => {
if (res.code == 0) {
this.dateList = res.data
this.getTaskList(this.chooseDay)
}
})
},
getTaskList(day) {
this.taskList = this.dateList[day].taskList
},
changeEffectType(type) {
if(this.effectType != 3) {
this.timeList = []
}else {
this.timeList = this.timeListEffect
}
if(type == 3) {
this.isEffectTimeSelect = true
this.dialogDate = true
}else {
this.effectType = type
this.getEffect()
}
},
getEffect() {
var startTime = this.timeListEffect[0] || '' , endTime = this.timeListEffect[1] || '', departId = this.deptList[0] || ''
this.instance.post(`/app/appmasssendingtask/statisticsEffect?type=${this.effectType}&startTime=${startTime}&endTime=${endTime}&departId=${departId}`).then(res => {
if (res.code == 0) {
this.effectData = res.data
var xData = [], createData = [], executeData = [], receiveData = []
res.data.trend.map(e => {
if(this.effectType == 0 || this.effectType == 1) {
e.ymd = e.ymd.substring(5, 10)
}
xData.push(e.ymd)
createData.push(e.createCount)
executeData.push(e.executeCount)
receiveData.push(e.receiveCount)
})
this.setLineChart(xData, createData, 'createChart', ['#2891FF'])
this.setLineChart(xData, executeData, 'executeChart', ['#FFB865'])
this.setLineChart(xData, receiveData, 'receiveChart', ['#26D52B'])
}
})
},
setLineChart(xData, yData, id, colorList) {
this[id] = echarts.init(document.querySelector(`#${id}`))
var option = {
xAxis: {
type: 'category',
data: xData
},
yAxis: {
type: 'value'
},
grid: {
left: '10px',
right: '28px',
bottom: '14px',
top: '30px',
containLabel: true
},
tooltip: {
trigger: 'axis'
},
legend: {
type: "plain"
},
color: colorList,
series: [
{
data: yData,
type: 'line'
}
]
}
this[id].setOption(option)
},
changeDepartType(type) {
if(this.departType != 3) {
this.timeList = []
}else {
this.timeList = this.timeListDepart
}
if(type == 3) {
this.isEffectTimeSelect = false
this.dialogDate = true
}else {
this.departType = type
this.getDepart()
}
},
getDepart() {
var startTime = this.timeListDepart[0] || '' , endTime = this.timeListDepart[1] || ''
this.instance.post(`/app/appmasssendingtask/statisticsDepart?type=${this.departType}&startTime=${startTime}&endTime=${endTime}`).then(res => {
if (res.code == 0) {
if(res.data && res.data.length) {
this.isDepartData = true
var items = [], xData = [], yData = []
res.data.map((item) => {
this.departBarData.push(item)
var i = {type: 'departmentName', id: item.deptId, corpid: this.user.info.corpId}
items.push(i)
yData.push(item.taskCount)
})
this.initOpenData({canvas:true})
this.transCanvas(items).then((data) => {
xData = data.items.map((i) => {
return i.data
})
this.setBarChart(xData, yData)
})
}else {
this.isDepartData = false
}
}
})
},
setBarChart(xData, yData) {
this.departBarChart = echarts.init(document.querySelector(`#departBarChart`))
var option = {
color: ['#2891FF'],
grid: {
top: '10%',
left: '2%',
right: '2%',
bottom: 90,
containLabel: true
},
// toolbox: {
// feature: {
// dataZoom: {
// yAxisIndex: false
// },
// saveAsImage: {
// pixelRatio: 2
// }
// }
// },
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: (data) => {
var index = data[0].dataIndex
return `<ww-open-data type="departmentName" openid="${this.departBarData[index].deptId}"></ww-open-data><br/>宣发任务数:${data[0].value}`
}
},
dataZoom: [
{
type: 'inside'
},
{
type: 'slider'
}
],
xAxis: {
data: xData,
silent: false,
splitLine: {
show: false
},
splitArea: {
show: false
}
},
yAxis: {
splitArea: {
show: false
}
},
series: [
{
type: 'bar',
data: yData,
barWidth: 20,
barGap: '250%',
large: true
}
]
};
// {
// tooltip: {
// trigger: 'axis',
// axisPointer: {
// type: 'shadow'
// }
// },
// grid: {
// top: '10%',
// left: '2%',
// right: '2%',
// bottom: '2%',
// containLabel: true
// },
// color: ['#2891FF'],
// xAxis: {
// type: 'category',
// data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
// },
// yAxis: {
// type: 'value'
// },
// series: [
// {
// data: [120, 200, 150, 80, 70, 110, 130],
// type: 'bar',
// barWidth: 20,
// barGap: '250%',
// }
// ]
// };
this.departBarChart.setOption(option)
}
}
}
</script>
<style lang="scss" scoped>
.AppAnnounceStatistics {
height: 100%;
.flex-between{
display: flex;
justify-content: space-between;
}
.mar-b16{
margin-bottom: 16px;
}
.mar-r0{
margin-right: 0!important;
}
.statistics-content{
padding: 0 24px 24px;
background-color: #fff;
box-shadow: 0px 4px 6px -2px rgba(15,15,21,0.1500);
border-radius: 4px;
margin-bottom: 20px;
.flex-content{
width: 100%;
display: flex;
margin-top: 16px;
.flex-left{
width: 50%;
.date-header{
padding: 12px 16px;
border: 1px solid #eee;
display: flex;
justify-content: space-between;
p{
line-height: 32px;
}
}
.flex-date{
display: flex;
justify-content: space-between;
}
.tips{
display: inline-block;
padding: 0 4px;
height: 16px;
line-height: 16px;
border-radius: 8px;
background: #2891FF;
font-size: 12px;
font-family: ArialMT;
color: #FFF;
margin-top: 8px;
}
}
.flex-right{
width: 50%;
margin-left: 16px;
border: 1px solid #eee;
.title{
line-height: 56px;
border-bottom: 1px solid #EEE;
padding-left: 16px;
font-size: 16px;
font-family: MicrosoftYaHeiSemibold;
color: #333;
}
.list-content{
padding: 16px;
height: 339px;
box-sizing: border-box;
overflow-y: scroll;
background-color: #F9F9F9;
box-sizing: border-box;
.item-title{
width: calc(100% - 100px);
word-break: break-all;
margin-bottom: 8px;
font-size: 16px;
font-family: MicrosoftYaHeiSemibold;
color: #222;
line-height: 24px;
}
.item-time{
width: 100px;
text-align: right;
font-size: 16px;
font-family: ArialMT;
color: #888;
line-height: 24px;
}
.item-info{
display: inline-block;
font-size: 14px;
font-family: MicrosoftYaHei;
color: #222;
line-height: 22px;
span{
display: inline-block;
color: #222;
word-break: break-all;
// vertical-align: text-top;
}
.label{
color: #999;
}
}
.item-created{
width: 152px;
margin-bottom: 4px;
.label{
width: 56px;
}
.name{
width: calc(100% - 56px);
}
}
.item-dept{
width: calc(100% - 152px);
.label{
width: 70px;
}
.name{
width: calc(100% - 70px);
}
}
.item-btn{
color: #26f;
cursor: pointer;
}
}
}
}
.right-search{
margin-top: 10px;
div{
display: inline-block;
}
.time-select{
font-size: 14px;
font-family: MicrosoftYaHei;
color: #222;
line-height: 22px;
padding: 6px 12px;
border-radius: 2px;
border: 1px solid #D0D4DC;
margin-right: 8px;
box-sizing: border-box;
cursor: pointer;
.dept-name{
display: inline-block;
width: 200px;
height: 22px;
overflow:hidden;
white-space: nowrap;
text-overflow: ellipsis;
vertical-align: bottom;
}
.el-icon-arrow-down{
vertical-align: middle;
}
}
.active{
border: 1px solid #26f;
color: #26f;
}
}
.line-content{
display: flex;
.flex1{
flex: 1;
margin-right: 16px;
.header{
padding: 16px;
width: 100%;
height: 90px;
background: #F9F9F9;
border-radius: 2px;
box-sizing: border-box;
margin-bottom: 16px;
p{
font-size: 14px;
font-family: MicrosoftYaHeiSemibold;
color: #222;
line-height: 22px;
margin-bottom: 4px;
}
h2{
font-size: 24px;
font-family: DINAlternate-Bold, DINAlternate;
font-weight: bold;
color: #26F;
line-height: 32px;
}
}
.chart-content{
width: 100%;
padding: 16px;
background: #F9F9F9;
border-radius: 2px;
box-sizing: border-box;
.chart-title{
font-size: 16px;
font-family: MicrosoftYaHeiSemibold;
color: #333;
line-height: 24px;
}
.chart-box{
width: 100%;
height: 280px;
}
}
}
}
#departBarChart{
width: 100%;
height: 300px;
}
}
::v-deep .el-calendar-table:not(.is-range) td.next,
::v-deep .el-calendar-table:not(.is-range) td.prev {
color: #ccc;
}
::v-deep .el-calendar-table .el-calendar-day{
height: 48px;
line-height: 32px;
padding-left: 12px;
font-size: 14px;
font-family: ArialMT;
}
.el-calendar-table:not(.is-range) td .current{
color: #888;
}
::v-deep .el-calendar__header{
display: none;
}
::v-deep .el-calendar__body{
padding: 0;
}
::v-deep .el-calendar-table thead th:nth-of-type(1){
border-left: 1px solid #eee;
}
::v-deep .el-calendar-table thead th:nth-of-type(7){
border-right: 1px solid #eee;
}
::v-deep .el-calendar-table tr td:first-child {
border-left: 1px solid #eee;
}
::v-deep .el-calendar-table tr:first-child td {
border-top: 1px solid #eee;
}
::v-deep .el-calendar-table td {
border-bottom: 1px solid #eee;
border-right: 1px solid #eee;
}
::v-deep .el-timeline-item__timestamp.is-top{
margin-bottom: 0;
padding-top: 0;
}
::v-deep .el-timeline-item__node{
background-color: #26F;
width: 8px;
height: 8px;
border-radius: 50%;
left: 1px;
}
::v-deep .el-card{
border: none;
}
::v-deep .el-card__body{
padding: 8px;
}
}
::v-deep .ai-list__content {
padding: 0!important;
.ai-list__content--right-wrapper {
background: transparent!important;
box-shadow: none!important;
margin: 0!important;
padding: 0 0 0!important;
}
}
::v-deep .AiPicker{
display: inline-block;
}
</style>

View File

@@ -53,7 +53,6 @@
:total="total"
:current.sync="search.current"
:size.sync="search.size"
:dict="dict"
@getList="getList">
<el-table-column slot="readUserName" label="姓名" align="center">
<template slot-scope="{ row }">
@@ -104,8 +103,9 @@ export default {
{slot: "unitName"},
{
prop: "readStatus", label: "查阅状态", align: "center",
render: (h, {row}) => h('p', {color: this.dict.getColor("announcementReadStatus", row.readStatus)}, this.dict.getLabel("announcementReadStatus", row.readStatus))
}
render: (h, {row}) => [<span
style={{color: this.dict.getColor("announcementReadStatus", row.readStatus)}}>{this.dict.getLabel("announcementReadStatus", row.readStatus)}</span>]
},
];
}
},

View File

@@ -40,7 +40,6 @@
:total="total"
:current.sync="search.current"
:size.sync="search.size"
:dict="dict"
@getList="getList">
<el-table-column slot="releaseUserName" label="发布人" align="center">
<template slot-scope="{ row }">
@@ -124,20 +123,17 @@ export default {
{prop: 'title', label: '标题'},
{
prop: 'readNum', label: '查询状态', align: 'center',
render: (h, {row}) => h('p', {
class: 'status', on: {
click() {
this.showDialog(row)
}
}
}, `${row.readNum || 0}人已读,${row.unReadNum}人未读`)
render: (h, {row}) => [<span class='status'
onClick={this.showDialog.bind(this, row)}>{row.readNum}人已读</span>,
<span class='status' onClick={this.showDialog.bind(this, row)}>{row.unReadNum}人未读</span>]
},
{slot: 'releaseUserName'},
{slot: 'unitName'},
{prop: 'releaseTime', label: '发布时间', align: 'center'},
{
prop: 'status', label: '发布状态', align: 'center',
render: (h, {row}) => h('p', {color: this.dict.getColor("announcementStatus", row.status)}, this.dict.getLabel("announcementStatus", row.status))
render: (h, {row}) => [<span
style={{color: this.dict.getColor("announcementStatus", row.status)}}>{this.dict.getLabel("announcementStatus", row.status)}</span>]
},
{slot: 'options'},
];

View File

@@ -13,13 +13,13 @@
</div>
</div>
<div class="header-right">
<!-- <div class="header-right__item">-->
<!-- <span>成员总数</span>-->
<!-- <h3>{{ chartData.groupSum}}</h3>-->
<!-- </div>-->
<!-- <div class="header-right__item">-->
<!-- <span>成员总数</span>-->
<!-- <h3>{{ chartData.groupSum}}</h3>-->
<!-- </div>-->
<div class="header-right__item">
<span>成员总数</span>
<h3>{{ chartData.today && chartData.today.total }}</h3>
<h3>{{chartData.today && chartData.today.total }}</h3>
</div>
<div class="header-right__item">
<span>今日新增</span>
@@ -27,13 +27,13 @@
</div>
<div class="header-right__item">
<span>今日流失</span>
<h3>{{ chartData.today && chartData.today.decrease }}</h3>
<h3>{{chartData.today && chartData.today.decrease}}</h3>
</div>
</div>
</div>
<div class="detail-top__content">
<ai-wrapper
label-width="80px">
label-width="80px">
<ai-info-item label="群主" :value="info.ownerName"></ai-info-item>
<ai-info-item label="群公告" :value="info.notice" isLine></ai-info-item>
<ai-info-item label="群聊标签" isLine>
@@ -53,25 +53,14 @@
<ai-card title="成员列表">
<template slot="content">
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
border
ref="aitableex"
@getList="getDynamicInfo"
:current.sync="search.current"
:size.sync="search.size"
:dict="dict">
<el-table-column slot="member" label="成员">
<template slot-scope="{row}">
<el-row type="flex" align="middle">
<img class="avatar" :src='row.avatar || "https://cdn.cunwuyun.cn/dvcp/h5/defaultAvatar.png"'/>
<div v-text="row.memberName"/>
<span :style="{color: row.customerType == 1 ? '#2EA222' : '#3C7FC8', marginLeft: '8px'}"
v-text="(row.customerType == 1 ? '@微信' : '@' + row.corpName)|| ''"/>
</el-row>
</template>
</el-table-column>
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
border
ref="aitableex"
@getList="getDynamicInfo"
:current.sync="search.current"
:size.sync="search.size">
<el-table-column slot="options" label="操作" width="100" align="center">
<template slot-scope="{ row }">
<el-button type="text" v-if="row.type==2 && row.avatar" @click="toDetail(row)">居民详情</el-button>
@@ -85,300 +74,304 @@
</template>
<script>
import * as echarts from 'echarts'
import * as echarts from 'echarts'
export default {
name: 'Detail',
export default {
name: 'Detail',
props: {
instance: Function,
dict: Object,
params: Object
},
props: {
instance: Function,
dict: Object,
params: Object
},
computed: {
colConfigs() {
return [
{slot: "member"},
{prop: 'type', label: '类型', dict: "wxGroupMemberType"},
{prop: 'joinTime', label: '入群时间'},
{prop: 'joinScene', label: '入群方式', dict: "wxGroupMemberJoinScene"},
{slot: "options"},
]
}
},
data() {
return {
isShow: false,
info: {},
search: {
current: 1,
size: 10
computed: {
colConfigs() {
return [
{
prop: 'memberName', label: '成员',render:(h,{row})=>[<img class="avatar" src={row.avatar || "https://cdn.cunwuyun.cn/dvcp/h5/defaultAvatar.png"} />,
<span>{row.memberName}</span>,
<span style={{color:row.customerType==1 ? '#2EA222' : '#3C7FC8',marginLeft:'8px'}}>{ row.customerType?(row.customerType==1 ? '@微信' : '@' + row.corpName):'' }</span>],
},
total: 0,
tableData: [],
chart: null,
chartData: {},
}
},
created() {
this.dict.load("wxGroupMemberJoinScene", "wxGroupMemberType")
},
mounted() {
if (this.params && this.params.id) {
this.getInfo()
this.getDynamicInfo()
this.getChart()
}
},
methods: {
getChart() {
this.instance.post(`/app/wxcp/wxgroup/groupStatistic`, null, {
params: {
id: this.params.id
}
}).then(res => {
if (res && res.data) {
this.chartData = res.data
this.initChart()
}
})
},
initChart() {
this.chart = echarts.init(document.getElementById('lineChart'))
this.setOptions()
},
setOptions() {
const x = Object.keys(this.chartData.list)
const y = Object.values(this.chartData.list)
this.chart.setOption({
tooltip: {
trigger: 'axis'
},
legend: {
type: "plain"
},
grid: {
left: '20px',
right: '38px',
bottom: '14px',
top: '30px',
containLabel: true
},
color: ['#2266FF', '#22AA99', '#F8B425'],
xAxis: {
type: 'category',
axisLabel: {
align: 'center',
padding: [2, 0, 0, 0],
interval: 0,
fontSize: 14,
color: '#666666'
},
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#E1E5EF'
}
},
data: x
},
yAxis: {
axisTick: {
length: 0,
show: false
},
splitLine: {
show: true,
lineStyle: {
color: ['#E1E5EF'],
width: 1,
type: 'solid'
}
},
nameTextStyle: {
color: '#666666',
align: 'left'
},
axisLine: {
show: false
},
axisLabel: {
color: '#666666'
},
type: 'value'
},
series: [
{
name: '成员总数',
type: 'line',
data: y.map(v => v.total)
},
{
name: '新增成员数',
type: 'line',
data: y.map(v => v.increase)
},
{
name: '流失成员数',
type: 'line',
data: y.map(v => v.decrease)
}
{prop: 'type', label: '类型',render:(h,{row})=>[<span>{this.dict.getLabel("wxGroupMemberType",row.type)}</span>]},
{prop: 'joinTime', label: '入群时间'},
{prop: 'joinScene', label: '入群方式',render:(h,{row})=>[<span>{this.dict.getLabel("wxGroupMemberJoinScene",row.joinScene)}</span>]},
{slot: "options"},
]
})
}
},
getInfo() {
this.instance.post(`/app/wxcp/wxgroup/getDetail?id=${this.params.id}`).then(res => {
if (res && res.data) {
this.info = res.data
}
})
data() {
return {
isShow: false,
info: {},
search: {
current: 1,
size: 10
},
total: 0,
tableData: [],
chart: null,
chartData: {},
}
},
getDynamicInfo() {
this.instance.post(`/app/wxcp/wxgroup/listMember?groupId=${this.params.id}`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code === 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
created() {
this.dict.load("wxGroupMemberJoinScene", "wxGroupMemberType")
},
toDetail(row) {
this.$router.push({
name: '68',
query: {
id: row.userId,
type: 'Detail'
}
})
mounted() {
if (this.params && this.params.id) {
this.getInfo()
this.getDynamicInfo()
this.getChart()
}
},
cancel(isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: isRefresh ? true : false
})
methods: {
getChart() {
this.instance.post(`/app/wxcp/wxgroup/groupStatistic`, null, {
params: {
id: this.params.id
}
}).then(res => {
if (res && res.data) {
this.chartData = res.data
this.initChart()
}
})
},
initChart() {
this.chart = echarts.init(document.getElementById('lineChart'))
this.setOptions()
},
setOptions() {
const x = Object.keys(this.chartData.list)
const y = Object.values(this.chartData.list)
this.chart.setOption({
tooltip: {
trigger: 'axis'
},
legend: {
type: "plain"
},
grid: {
left: '20px',
right: '38px',
bottom: '14px',
top: '30px',
containLabel: true
},
color: ['#2266FF', '#22AA99', '#F8B425'],
xAxis: {
type: 'category',
axisLabel: {
align: 'center',
padding: [2, 0, 0, 0],
interval: 0,
fontSize: 14,
color: '#666666'
},
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#E1E5EF'
}
},
data: x
},
yAxis: {
axisTick: {
length: 0,
show: false
},
splitLine: {
show: true,
lineStyle: {
color: ['#E1E5EF'],
width: 1,
type: 'solid'
}
},
nameTextStyle: {
color: '#666666',
align: 'left'
},
axisLine: {
show: false
},
axisLabel: {
color: '#666666'
},
type: 'value'
},
series: [
{
name: '成员总数',
type: 'line',
data: y.map(v => v.total)
},
{
name: '新增成员数',
type: 'line',
data: y.map(v => v.increase)
},
{
name: '流失成员数',
type: 'line',
data: y.map(v => v.decrease)
}
]
})
},
getInfo() {
this.instance.post(`/app/wxcp/wxgroup/getDetail?id=${this.params.id}`).then(res => {
if (res && res.data) {
this.info = res.data
}
})
},
getDynamicInfo() {
this.instance.post(`/app/wxcp/wxgroup/listMember?groupId=${this.params.id}`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code === 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
toDetail(row) {
this.$router.push({
name: '68',
query: {
id: row.userId,
type: 'Detail'
}
})
},
cancel(isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: isRefresh ? true : false
})
}
}
}
}
</script>
<style scoped lang="scss">
.AppResidentManage {
::v-deep .ai-detail__content--wrapper {
max-width: 100% !important;
padding: 20px;
}
h2, h3 {
margin: 0;
}
.detail-top {
padding: 30px 40px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 2px;
margin-bottom: 20px;
.detail-top__content {
padding-top: 32px;
.AppResidentManage {
::v-deep .ai-detail__content--wrapper {
max-width: 100% !important;
padding: 20px;
}
.detail-top__header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 32px;
border-bottom: 1px solid #EEEEEE;
h2, h3 {
margin: 0;
}
.header-right {
.header-right__item {
width: 120px;
margin-right: 8px;
text-align: center;
}
.detail-top {
padding: 30px 40px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 2px;
margin-bottom: 20px;
div {
text-align: center;
&:last-child {
margin-right: 0;
}
span {
display: block;
margin-bottom: 10px;
color: #888888;
}
}
.el-button {
height: 28px;
margin-left: 8px;
border-radius: 14px;
font-size: 12px;
padding: 7px 15px;
}
.detail-top__content {
padding-top: 32px;
}
.header-left, .header-right {
.detail-top__header {
display: flex;
justify-content: space-between;
align-items: center;
}
padding-bottom: 32px;
border-bottom: 1px solid #EEEEEE;
.header-left {
img {
width: 64px;
height: 64px;
margin-right: 16px;
.header-right {
.header-right__item {
width: 120px;
margin-right: 8px;
text-align: center;
}
div {
text-align: center;
&:last-child {
margin-right: 0;
}
span {
display: block;
margin-bottom: 10px;
color: #888888;
}
}
.el-button {
height: 28px;
margin-left: 8px;
border-radius: 14px;
font-size: 12px;
padding: 7px 15px;
}
}
h2 {
margin-bottom: 6px;
color: #222222;
font-size: 16px;
.header-left, .header-right {
display: flex;
align-items: center;
}
p {
color: #2EA222;
font-size: 14px;
.header-left {
img {
width: 64px;
height: 64px;
margin-right: 16px;
}
h2 {
margin-bottom: 6px;
color: #222222;
font-size: 16px;
}
p {
color: #2EA222;
font-size: 14px;
}
}
}
}
}
#lineChart {
width: 100%;
height: 336px;
}
#lineChart {
width: 100%;
height: 336px;
}
.table-tags {
.el-tag {
.table-tags {
.el-tag {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
}
::v-deep .avatar {
width: 40px;
height: 40px;
vertical-align: middle;
margin-right: 8px;
&:last-child {
margin-right: 0;
}
object-fit: fill;
}
}
::v-deep .avatar {
width: 40px;
height: 40px;
vertical-align: middle;
margin-right: 8px;
object-fit: fill;
}
}
</style>

View File

@@ -0,0 +1,4 @@
{
"AppGigscreenViewer": "大屏预览组件",
"AppDesigner": "大屏设计"
}

View File

@@ -0,0 +1,133 @@
<template>
<section class="AppCentralTaskDV">
<!-- <ai-dv-background :src="bgImage"/>-->
<div class="coreTask">
<div class="leftBox">
<div class="boxTitle">{{ coreTaskData.titleText }}</div>
<dv-scroll-board v-if="refresh" :config="coreTaskData"/>
</div>
</div>
</section>
</template>
<script>
import {scrollBoard} from '@jiaminghi/data-view'
import bgImage from '../assets/centralTask/bg.png'
import Vue from "vue";
Vue.use(scrollBoard)
export default {
name: "AppCentralTaskDV",
label: "数据大屏-重点工作",
props: {
nav: {default: () => ({})}
},
inject: {
dv: {default: ""}
},
computed: {
coreTaskData() {
return {
headerHeight: 52,
header: ['基层组织建设', "社区治理", "便民服务效能", "城市管理"],
headerBGC: 'rgba(0, 113, 255, 0.5)',
oddRowBGC: "rgba(5, 65, 139, 0.5)",
evenRowBGC: "rgba(5, 65, 139, 0.5)",
waitTime: 6000,
align: ["center", "start", "center", "center"],
rowNum: 5,
...this.nav.data,
}
},
},
data() {
return {bgImage, refresh: true}
},
watch: {
nav: {
deep: true,
handler() {
this.refresh = false
this.$nextTick(() => this.refresh = true)
}
}
}
}
</script>
<style lang="scss" scoped>
.AppCentralTaskDV {
height: 100%;
padding: 0 0 60px;
box-sizing: border-box;
display: flex;
flex-direction: column;
.coreTask {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
gap: 53px;
margin-top: 56px;
.leftBox {
width: 100%;
height: 100%;
background-image: url("../assets/centralTask/box.png");
background-size: 100% 100%;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
align-items: center;
padding: 0 40px 42px;
box-sizing: border-box;
}
}
.boxTitle {
height: 68px;
width: 534px;
background-image: url("../assets/centralTask/titleBox.png");
text-align: center;
font-size: 22px;
font-weight: 400;
color: #71F8FF;
transform: translateY(-32px);
line-height: 68px;
}
::v-deep .dv-scroll-board {
width: 100%;
flex: 1;
min-height: 0;
border-radius: 6px;
overflow: hidden;
.header {
color: #9FDBFB;
font-size: 20px;
.header-item {
text-align: center;
border: 1px solid #054596;
}
}
.row-item {
color: #68F0FC;
font-size: 18px;
overflow: hidden;
.ceil {
border: 1px solid #054596;
border-top: none;
position: relative;
line-height: 30px;
white-space: normal;
padding: 20px;
}
}
}
}
</style>

View File

@@ -0,0 +1,479 @@
<template>
<section class="AppGovInteractionDV">
<el-row type="flex" justify="space-between" align="bottom">
<div flex class="framePane column top">
<div class="titlePane" v-text="'事件内容'"/>
<div class="fill">
<dv-scroll-board :config="topLeftData"/>
</div>
</div>
<div class="centerTopPane" id="centerTopPane">
<b class="title gradientFont">事件统计</b>
<el-row type="flex" justify="space-between" align="middle"
v-for="(row,i) in topCenterData" :key="i">
<div class="dataPane" v-for="(op,j) in row" :key="j">
<span class="gradientFont" v-text="op.label"/>
<dv-digital-flop class="gradientFont" :config="op.v1"/>
</div>
</el-row>
</div>
<div flex class="framePane column top">
<div class="titlePane" v-text="'政务微信群'"/>
<div class="totalPane" flex>
<div class="dataPanel fill" flex v-for="(op,i) in rightTopData.total" :key="i">
<span v-text="op.label"/>
<b v-text="op.v1"/>
</div>
</div>
<div class="fill">
<ai-echart class="chart" :data="rightTopData.list" :ops="rightTopData.ops"/>
</div>
</div>
</el-row>
<div flex class="gap fill">
<div flex class="framePane fill column" v-for="c in charts" :key="c.id">
<div class="titlePane" v-text="c.label"/>
<div class="fill">
<ai-echart class="chart" :data="chartData[c.id]" :ops="c.ops"/>
</div>
</div>
</div>
</section>
</template>
<script>
import {digitalFlop, scrollBoard} from '@jiaminghi/data-view'
import Vue from "vue";
Vue.use(digitalFlop)
Vue.use(scrollBoard)
export default {
name: "AppGovInteractionDV",
label: "数据大屏-政民互动",
props: {
instance: Function
},
computed: {
topCenterData() {
let meta = [
[{label: "待受理", name: 'pending'}, {label: "累计上报", name: 'total_case'}],
[{label: "办理中", name: 'processing'}, {label: "累计受理", name: 'total_accepted'}],
[{label: "今日上报", name: "added_today"}, {label: "累计办结", name: 'total_solved'}],
[{label: "今日办结", name: 'solved_today'}, {label: "累计办结率", name: 'total_solved_percent'}],
]
return meta.map(e => e.map(s => {
let v1 = {
number: [this.meta.residentCategoryReport?.[s.name] || 0],
style: {
fontFamily: 'dineng',
fontWeight: 'bold',
fontSize: 40,
gradientType: 'linear',
gradientColor: ['#fff', '#fff', '#6BC7FF'],
gradientParams: [0, 0, 0, 40],
gradientWith: 'fill',
gradientStops: [0, .18, 1]
}
}
if (s.name == 'total_solved_percent') {
v1 = {
...v1,
number: [v1.number * 100], content: '{nt}%'
}
}
return {...s, v1}
}))
},
topLeftData() {
let statusColor = {
0: 'doing',
1: 'done',
2: 'pending',
},
statusLabel = {
0: '处理中',
1: '已处理'
},
list = this.meta.residentOrderList?.map(e => {
let status = e.process_list.slice(-1)?.[0]?.status
return {
...e, status, statusLabel: statusLabel[status]
}
})
return {
oddRowBGC: 'transparent',
evenRowBGC: 'transparent',
rowNum: 10,
data: list?.map(e => [`
<div flex class="eventItem">
<span class="tag ${statusColor[e.status]}">${e.statusLabel}</span>
<div class="fill">${e.desc}</div>
</div>`]) || []
}
},
rightTopData() {
let obj = this.meta.groupMap?.list || {},
list = Object.keys(obj).map(e => {
let {total, increase, decrease} = obj?.[e],
time = this.$moment(e).format('MM-DD')
return {time, total, increase, decrease}
})
return {
total: [
{label: '群聊总数', v1: this.meta.groupMap?.groupSum || 0},
{label: '群成员数', v1: this.meta.groupMap?.today?.total || 0},
],
ops: {
color: ['rgba(54, 165, 255, 0.2)', 'rgba(28, 212, 68, 0.2)', 'rgba(255, 215, 109, 0.2)'],
legend: {
itemWidth: 16,
itemHeight: 16,
textStyle: {color: '#82C5FF', padding: [0, 0, 0, 8], fontSize: 14},
icon: 'rect',
itemGap: 40
},
tooltip: {},
xAxis: {
type: 'category', axisTick: false,
nameGap: 20,
axisLabel: {color: '#fff'},
axisLine: {lineStyle: {color: '#263763'}}
},
// 声明一个 Y 轴,数值轴。
yAxis: {
nameGap: 30, minInterval: 1,
splitLine: {lineStyle: {color: '#263763'}},
axisLabel: {color: 'rgba(255,255,255,.8)'}
},
series: [
{
type: 'line', name: "群成员数", symbol: 'none', lineStyle: {color: '#36A5FF', borderWidth: 1},
itemStyle: {borderColor: '#36A5FF', borderWidth: 1},
},
{
type: 'line', name: "新增人数", symbol: 'none', lineStyle: {color: '#1CD444', borderWidth: 1},
itemStyle: {borderColor: '#1CD444', borderWidth: 1},
},
{
type: 'line', name: "退群人数", symbol: 'none', lineStyle: {color: '#FFD76D', borderWidth: 1},
itemStyle: {borderColor: '#FFD76D', borderWidth: 1},
}
],
grid: {left: 60, bottom: 58, right: 20}
},
list
}
},
chartData() {
return {
EventType: this.meta.residentCategoryReportList?.map(e => {
let {category_name: name, total_case, total_solved} = e
return {name, total_case, total_solved}
}) || [],
EventSource: this.meta.unitReportList?.map(e => {
let {grid_name: name, total_case, total_solved} = e
return {name, total_case, total_solved}
}) || []
}
},
},
data() {
return {
charts: [
{
label: "事件类型", id: "EventType", ops: {
color: ['rgba(54, 165, 255, 0.2)', 'rgba(28, 212, 68, 0.2)'],
legend: {
itemWidth: 16,
itemHeight: 16,
textStyle: {color: '#82C5FF', padding: [0, 0, 0, 8], fontSize: 14},
icon: 'rect',
itemGap: 40
},
tooltip: {},
xAxis: {
type: 'category', nameGap: 20, axisTick: false,
axisLabel: {color: '#fff'},
axisLine: {lineStyle: {color: '#263763'}}
},
// 声明一个 Y 轴,数值轴。
yAxis: {
nameGap: 23, minInterval: 1,
splitLine: {lineStyle: {color: '#263763'}},
axisLabel: {color: 'rgba(255,255,255,.8)'}
},
series: [
{
type: 'bar',
name: "累计受理",
barWidth: 10,
barGap: '20%',
itemStyle: {borderColor: '#36A5FF', borderWidth: 1}
},
{
type: 'bar',
name: "累计办结",
barWidth: 10,
barGap: '20%',
itemStyle: {borderColor: '#1CD444', borderWidth: 1}
}
],
grid: {left: 40, bottom: 58, right: 20}
}
},
{
label: "上报来源", id: "EventSource", ops: {
color: ['rgba(54, 165, 255, 0.2)', 'rgba(28, 212, 68, 0.2)'],
legend: {
itemWidth: 16,
itemHeight: 16,
textStyle: {color: '#82C5FF', padding: [0, 0, 0, 8], fontSize: 14},
icon: 'rect',
itemGap: 40
},
tooltip: {},
xAxis: {
type: 'category', axisTick: false,
nameGap: 20,
axisLabel: {color: '#fff'},
axisLine: {lineStyle: {color: '#263763'}}
},
// 声明一个 Y 轴,数值轴。
yAxis: {
nameGap: 30, minInterval: 1,
splitLine: {lineStyle: {color: '#263763'}},
axisLabel: {color: 'rgba(255,255,255,.8)'}
},
series: [
{
type: 'line', name: "事件数", symbol: 'none', lineStyle: {color: '#36A5FF', borderWidth: 1},
itemStyle: {borderColor: '#36A5FF', borderWidth: 1, show: false},
areaStyle: {
color: {
type: 'linear', x2: 0, y2: 1, colorStops: [
{offset: 0, color: 'rgba(37, 161, 255, 0.5)'}, {offset: 1, color: 'rgba(37, 161, 255, 0)'}]
}
}
},
{
type: 'line', name: "办理数", symbol: 'none', lineStyle: {color: '#1CD444', borderWidth: 1},
itemStyle: {borderColor: '#1CD444', borderWidth: 1, show: false},
areaStyle: {
color: {
type: 'linear', x2: 0, y2: 1, colorStops: [
{offset: 0, color: 'rgba(37, 206, 55, 0.5)'}, {offset: 1, color: 'rgba(37, 206, 55, 0)'}]
}
}
}
],
grid: {left: 40, bottom: 58, right: 20}
}
},
],
meta: {},
}
},
methods: {
getData() {
this.instance.post("/app/statistics/governmentPeople/queryResidentReport").then(res => {
if (res?.data) this.meta = res.data
})
},
},
created() {
this.getData()
}
}
</script>
<style lang="scss" scoped>
.AppGovInteractionDV {
height: 100%;
padding: 6px 0 10px;
gap: 20px;
font-size: 16px;
color: #82C5FF;
display: flex;
flex-direction: column;
::v-deep .eventItem {
width: 100%;
color: #82C5FF;
& > .fill {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
::v-deep .communityEvent {
list-style-type: circle;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: 40px;
line-height: 40px;
padding-left: 1px;
color: #82C5FF;
}
::v-deep .tag {
padding: 0 10px;
border-radius: 4px;
margin-right: 10px;
font-size: 14px;
line-height: 28px;
color: #fff;
box-sizing: border-box;
&.doing {
background-image: radial-gradient(rgba(#1B1BD6, .4), #208FFF);
}
&.pending {
background-image: radial-gradient(rgba(#FF9333, .4), #FFE959);
}
&.done {
background-image: radial-gradient(rgba(#1BD622, .4), #2CFF7C);
}
}
.centerTopPane {
background-image: url("../assets/govInteraction/globe_map.png");
background-repeat: no-repeat;
background-size: 100% 100%;
height: 540px;
width: 940px;
flex-shrink: 0;
position: relative;
padding: 30px 50px 50px;
box-sizing: border-box;
.title {
font-size: 60px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
::v-deep .gradientFont {
background-image: linear-gradient(180deg, #FFFFFF 0%, #FFFFFF 18%, #6BC7FF 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.el-row {
&:first-of-type, &:last-of-type {
margin: 0 110px;
}
}
::v-deep .dataPane {
width: 122px;
height: 114px;
background-image: url("../assets/govInteraction/kuaikuai.png");
background-repeat: no-repeat;
background-size: 100% 80px;
background-position: bottom center;
display: flex;
flex-direction: column;
align-items: center;
font-weight: bold;
& > b {
font-size: 50px;
line-height: 50px;
span {
font-weight: normal;
font-size: 32px;
}
}
& > span {
font-size: 18px;
line-height: 18px;
}
}
}
.framePane {
width: 100%;
height: 100%;
background: rgba(7, 11, 35, 0.4);
border: 1px solid #14345F;
display: flex;
flex-direction: column;
&.top {
width: 440px;
height: 520px;
}
& > .fill {
width: 100%;
height: 100%;
padding: 20px;
box-sizing: border-box;
overflow-y: auto;
}
}
.titlePane {
width: 100%;
background-image: url("../assets/govInteraction/title.png");
background-repeat: no-repeat;
background-size: 309px 100%;
height: 60px;
padding-left: 30px;
color: #fff;
font-weight: bold;
font-size: 20px;
line-height: 48px;
}
.chart {
width: 100%;
}
.el-image {
width: 100%;
height: 100%;
}
.totalPane {
width: 100%;
.dataPanel {
height: 60px;
background: linear-gradient(270deg, rgba(119, 169, 255, 0.1) 0%, rgba(66, 112, 255, 0.25) 100%);
border-radius: 4px;
padding: 0 10px;
font-size: 16px;
justify-content: space-between;
margin-left: 20px;
& > span {
white-space: nowrap;
}
& > b {
font-size: 24px;
color: #fff;
font-family: Arial-BoldMT, Arial, serif;
}
&:last-of-type {
margin-right: 20px;
}
}
}
}
</style>

View File

@@ -0,0 +1,852 @@
<template>
<div class="griddv">
<div class="left">
<div class="griddv-title">
<h2>网格列表</h2>
</div>
<div class="griddv-tree">
<el-tree
:data="treeList"
:props="defaultProps"
@node-click="handleNodeClick"
node-key="id"
ref="tree"
default-expand-all
:expand-on-click-node="false"
highlight-current>
</el-tree>
</div>
</div>
<div class="middle" :class="[girdLevel == '2' ? 'middle-active' : '']" ref="container" v-loading="isLoading"
element-loading-background="rgba(0, 0, 0, 0.5)">
<div
ref="middleTree"
id="tree"
class="tree"
:style="{left: x, top: y, transform: `scale(${scale}) translate(-50%, -50%) `, 'transform-origin': `${0} ${0}`}">
<ai-okr-tree ref="VueOkrTree" v-if="chartData.length"
:data="chartData"
node-key="id"
show-collapsable
aniamte
animate-name="okr-fade-in-linear"
:render-content="renderContent"
default-expand-all/>
</div>
</div>
<div class="right">
<div class="right-top">
<div class="griddv-title">
<h2>网格内人员情况</h2>
</div>
<div class="right-chart">
<ai-echart
style="height: 100%; width: 100%;"
:data="userInfo"
:ops="barChart1">
</ai-echart>
</div>
</div>
<div class="right-bottom">
<div class="griddv-title">
<h2>事件上报情况</h2>
</div>
<div class="right-chart">
<ai-echart
style="height: 100%; width: 100%;"
:data="eventInfo"
:ops="pieChart">
</ai-echart>
</div>
</div>
</div>
<el-dialog :visible.sync="isShowInfo" width="640px" :close-on-click-modal="false" :modal-append-to-body="false">
<template slot="title">
<h2>家庭信息</h2>
<img src="../assets/grid/close.png" @click="isShowInfo = false">
</template>
<div class="grid-info">
<div class="grid-info__title">
<h2>家庭地址</h2>
<span>{{ residentInfo.currentAreaName }}</span>
</div>
<ai-table
style="width: 558px"
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:isShowPagination="false"
:current.sync="search.current"
:size.sync="search.size"
@getList="() => {}">
</ai-table>
</div>
</el-dialog>
</div>
</template>
<script>
import pieChart from 'dvcp-dv-ui/components/AiEchart/template/pie/pieChart2'
import barChart1 from 'dvcp-dv-ui/components/AiEchart/template/bar/barChart1'
import AiOkrTree from "dvcp-dv-ui/components/AiOkrTree/AiOkrTree"
export default {
name: 'AppGridDV',
label: '网格数据大屏',
components: {AiOkrTree},
props: {
instance: Function,
dict: Object,
},
data() {
return {
isLoading: false,
treeList: [],
search: {
size: 100,
current: 1
},
barChart1,
userInfo: [],
eventInfo: [],
pieChart,
total: 0,
isShowInfo: false,
defaultProps: {
children: 'girdList',
label: 'girdName',
},
colConfigs: [
{prop: 'name', label: '姓名', align: 'center', width: 120},
{
prop: 'householdRelation',
label: '与户主关系',
align: 'center',
render: (h, {row}) => {
return h('span', {
style: {
color: row.householdName === '1' ? '#1DE94D' : '#A8D7F3'
}
}, row.householdName === '1' ? '户主' : (this.dict.getLabel('householdRelation', row.householdRelation) || '-'))
},
formart: v => this.dict.getLabel('householdRelation', v)
},
{
prop: 'idNumber',
label: '身份证号',
align: 'center',
width: 220,
formart: v => v ? v.replace(/^(\d{10})\d{4}(.{4}$)/g, `$1${Array(5).join('*')}$2`) : '-'
},
{prop: 'phone', label: '联系方式', align: 'center'}
],
girdId: '',
residentInfo: {},
tableData: [],
chartData: [],
girdLevel: '0',
scale: 1,
x: '50%',
y: '50%',
offsetX: 0,
offsetY: 0,
defaultUrl: 'https://cdn.cunwuyun.cn/dvcp/dv/avatar.png'
}
},
created() {
this.dict.load('householdRelation')
this.getTreeList()
this.getGirdInfo()
},
mounted() {
this.bindEvent()
},
destroyed() {
document.querySelector('body').removeEventListener('mousewheel', this.onMousewheel)
document.querySelector('body').removeEventListener('mouseup', this.onMouseUp)
document.querySelector('body').removeEventListener('mousedown', this.onMousedown)
document.querySelector('body').removeEventListener('mousemove', this.onMouseMove)
},
methods: {
bindEvent() {
document.querySelector('body').addEventListener('mousewheel', this.onMousewheel, true)
document.querySelector('body').addEventListener('mouseup', this.onMouseUp, true)
document.querySelector('body').addEventListener('mousedown', this.onMousedown, true)
document.querySelector('body').addEventListener('mousemove', this.onMouseMove, true)
},
onMousewheel(event) {
if (!event) return false
const elClass = event.target.className
if (elClass === 'tree' || elClass === 'middle' || (elClass && (elClass.indexOf('chart') > -1 || elClass.indexOf('user') > -1))) {
var dir = event.deltaY > 0 ? 'Up' : 'Down'
if (dir === 'Up') {
this.scale = this.scale - 0.2 <= 0.1 ? 0.1 : this.scale - 0.2
} else {
this.scale = this.scale + 0.2
}
}
return false
},
onMousedown(e) {
const elClass = e.target.className
if ((elClass && (elClass.indexOf('chart') > -1 || elClass.indexOf('user') > -1))) {
const left = document.querySelector('#tree').offsetLeft
const top = document.querySelector('#tree').offsetTop
this.isMove = true
this.offsetX = e.clientX - left
this.offsetY = e.clientY - top
}
},
onMouseMove(e) {
if (!this.isMove) return
this.x = (e.clientX - this.offsetX) + 'px'
this.y = (e.clientY - this.offsetY) + 'px'
},
onMouseUp() {
this.isMove = false
},
debounce(func, wait = 1000) {
let timeout
return function (event) {
clearTimeout(timeout)
timeout = setTimeout(() => {
func.call(this, event)
}, wait)
}
},
handleNodeClick(e) {
this.girdLevel = e.girdLevel
this.isLoading = true
this.getGirdInfo(e.id, e.girdLevel)
this.getStatisticsInfo(e.id)
},
getStatisticsInfo(id) {
this.instance.post(`/app/appgirdmemberinfo/girdMemberAndResidentStatistic?girdId=${id}`).then((res) => {
if (res.code == 0) {
this.userInfo = [
{
'name': '网格长',
'人数': res.data['网格长'] || 0
},
{
'name': '网格员',
'人数': res.data['网格员'] || 0
},
{
'name': '责任家庭数',
'人数': res.data['责任家庭数'] || 0
}
]
}
})
this.instance.post(`/app/appclapeventinfo/clapEventStatistic?girdId=${id}`).then((res) => {
if (res.code == 0) {
this.eventInfo = Object.keys(res.data).map(v => {
return {
'事件类型': v,
v1: res.data[v]
}
})
}
})
},
renderContent(h, node) {
return h('div', {
class: 'userlist-container'
}, [h('div', {
class: `userlist ${node.data.label === '子节点' ? 'last-level' : ''} ${node.data.girdLevel > 1 ? 'userlist-wrapper' : ''} userlist-${node.data.girdLevel}`
}, node.data.userList.map(v => {
return h('div', {
class: `user-item user-item-${v.girdLevel}`
}, [h('img', {
class: 'user-img',
attrs: {
src: v.photo || this.defaultUrl
},
on: {
dragstart: e => {
e.preventDefault()
e.stopPropagation()
return false
}
}
}), h('p', {
class: 'user-p',
attrs: {
title: v.label,
'data-id': v.id
},
on: {
click: () => {
if (node.data.label === '子节点') {
this.getResidentInfo(v.id)
}
}
}
}, v.label), h('span', {
class: 'user-span',
style: {
display: v.girdLevel === '2' ? 'block' : 'none',
fontSize: v.girdLevel === '2' ? '12px' : ''
}
}, v.checkType ? (v.checkType === '1' ? '网格员' : '网格长') : '-')])
})), h('div', {
class: 'user-gridName',
style: {
display: node.data.label === '子节点' ? 'none' : 'block',
color: 'rgba(255, 255, 255, 0.8)',
fontSize: '12px',
margin: '4px 0'
}
}, node.data.label)])
},
getResidentInfo(id) {
this.isLoading = true
this.instance.post(`/app/appresident/detail?id=${id}`).then((res) => {
if (res.code == 0) {
this.residentInfo.resident = res.data
this.tableData = res.data.family || []
this.isShowInfo = true
}
this.isLoading = false
})
},
autoScale() {
const treeWidth = this.$refs.middleTree.offsetWidth
const containerWidth = this.$refs.container.offsetWidth
this.scale = treeWidth < containerWidth ? 1 : containerWidth / treeWidth
this.x = '50%'
this.y = '50%'
},
getGirdInfo(id) {
this.instance.post(`/app/appgirdinfo/listAllGirdAndMemberByTop?id=${id || ''}`).then((res) => {
if (res.code == 0) {
const chartData = this.formatList([res.data])
this.chartData = chartData
this.$nextTick(() => {
if (id) {
this.getUserList(id, chartData[0].id)
} else {
this.isLoading = false
this.autoScale()
}
})
}
})
},
getUserList(id, parentId) {
this.instance.post(`/app/appgirdmemberresident/listByGirdMember`, null, {
params: {
size: 1000,
girdId: id
}
}).then(res => {
if (res.code == 0) {
const userList = res.data.records.map(v => {
return {
...v,
isLast: true,
label: v.name
}
})
this.isLoading = false
if (!userList.length) {
this.autoScale()
return false
}
const node = this.$refs.VueOkrTree.getNode(parentId)
this.$refs.VueOkrTree.append({
id: new Date().getTime(),
label: '子节点',
userList: userList || []
}, node)
this.$nextTick(() => {
this.autoScale()
})
}
})
},
formatList(list) {
return list.map(item => {
let userList = []
const girdMemberManageList = item.girdMemberManageList ? item.girdMemberManageList.map(v => {
return {
...v,
label: v.name,
id: v.id,
checkType: '2',
girdName: item.girdName,
girdLevel: item.girdLevel,
isUser: true
}
}) : []
const girdMemberList = item.girdMemberList ? item.girdMemberList.map(v => {
return {
...v,
label: v.name,
id: v.id,
checkType: '1',
girdName: item.girdName,
girdLevel: item.girdLevel,
isUser: true
}
}) : []
if (this.girdLevel === '2' && item.girdLevel === '2' && girdMemberList.length) {
userList = girdMemberManageList
item.girdList = [{
girdLevel: '2',
id: item.id,
isUser: false,
userList: girdMemberList,
label: item.girdName,
children: []
}]
} else {
userList = [...girdMemberManageList, ...girdMemberList]
}
if (!userList.length) {
userList = [{
label: '-',
id: item.id,
girdLevel: item.girdLevel,
girdName: item.girdName
}]
}
const obj = {
label: item.girdName,
id: `${new Date().getTime()}-${item.id}`,
girdLevel: item.girdLevel,
isUser: false,
userList: userList,
children: item.girdList || []
}
if (obj.children && obj.children.length && this.girdLevel !== '2') {
obj.children = this.formatList(obj.children)
}
return obj
})
},
getTreeList() {
this.instance.post('/app/appgirdinfo/listAll').then((res) => {
if (res.code == 0) {
this.treeList = [...res.data]
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(res.data[0].id)
this.getStatisticsInfo(res.data[0].id)
})
}
})
}
}
}
</script>
<style lang="scss" scoped>
.griddv {
display: flex;
align-items: center;
height: 100%;
user-select: none;
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
border-radius: 5px;
background: rgba(93, 163, 255, 0.1);
}
::-webkit-scrollbar-thumb {
border-radius: 5px;
background: rgba(173, 208, 255, 0.5);
}
.grid-info {
width: 100%;
.grid-info__title {
display: flex;
align-items: center;
margin-bottom: 20px;
h2, span {
color: #fff;
font-size: 14px;
font-weight: 500;
}
}
}
::v-deep .el-dialog__body {
padding: 10px 40px 30px;
.el-table {
background-color: transparent;
}
.el-table__body tr td:first-child .cell, .ai-table .el-table__header tr th:first-child .cell {
padding-left: 0 !important;
}
.el-table th, .el-table tr {
color: #fff;
font-size: 14px;
background-color: rgba(28, 39, 65, 0.9);
}
.el-table__row--striped, .el-table--striped .el-table__body tr.el-table__row--striped td {
background-color: transparent !important;
}
.el-table__header-wrapper {
display: none;
}
.el-table--enable-row-hover .el-table__body tr:hover > td, .el-table-filter {
background-color: transparent;
}
}
::v-deep .el-dialog {
position: absolute;
top: 50%;
left: 50%;
margin: 0 !important;
transform: translate(-50%, -50%);
background: rgba(2, 13, 43, 0.9);
box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.5), inset 0px 0px 10px 0px #2C7CFF;
border: 1px solid #2D65C9;
.el-dialog__header {
display: flex;
align-items: center;
justify-content: space-between;
background: transparent;
box-shadow: none;
h2 {
color: #fff;
font-size: 18px;
}
img {
cursor: pointer;
width: 16px;
height: 16px;
&:hover {
opacity: 0.6;
}
}
}
.el-dialog__headerbtn {
display: none;
}
}
::v-deep .userlist-container {
.userlist {
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
background: rgba(76, 166, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
.user-item {
margin-right: 10px;
color: #fff;
font-size: 0;
&:last-child {
margin-right: 0;
}
img {
width: 58px;
height: 80px;
object-fit: cover;
user-select: none;
}
p {
max-width: 120px;
margin: 4px 0 0 0;
font-size: 19px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
span {
display: block;
max-width: 120px;
font-size: 17px;
color: #9DD3FF;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.user-item-0 {
img {
width: 86px;
height: 120px;
}
}
&.user-item-2 {
p {
line-height: 14px;
font-size: 12px;
margin-bottom: 4px;
}
span {
line-height: 14px;
}
img {
width: 40px;
height: 56px;
}
}
}
&.userlist-2 {
justify-content: space-between;
max-width: 130px;
flex-wrap: wrap;
padding-bottom: 0;
width: fit-content;
margin: 0 auto;
.user-item {
width: 48px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 10px;
&:nth-of-type(2n) {
margin-right: 0;
}
}
}
&.last-level {
flex-wrap: wrap;
max-width: 690px;
font-size: 0;
background: rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
padding: 12px 12px 0 12px;
box-sizing: border-box;
.user-item {
margin-bottom: 12px;
}
img {
display: none;
}
p {
padding: 8px;
font-size: 12px;
background: #0B477D;
border-radius: 4px;
}
span {
display: none !important;
}
}
}
}
* {
box-sizing: border-box;
}
.griddv-title {
display: flex;
width: 320px;
height: 62px;
background-image: url(../assets/grid/title-bg.png);
background-size: 100% 100%;
h2 {
width: 100%;
height: 50px;
line-height: 50px;
padding-left: 24px;
font-weight: 600;
font-size: 20px;
letter-spacing: 1px;
color: #fff;
}
}
& > div {
height: 100%;
}
.right {
display: flex;
justify-content: space-between;
flex-direction: column;
width: 440px;
margin-left: 20px;
& > div {
flex: 1;
width: 100%;
padding-bottom: 20px;
background: rgba(7, 11, 35, 0.4);
border: 1px solid #2D50B5;
box-sizing: border-box;
.right-chart {
height: calc(100% - 82px);
padding: 0 20px;
}
&:last-child {
margin-top: 20px;
}
}
}
.middle {
position: relative;
flex: 1;
margin-left: 20px;
background: rgba(7, 11, 35, 0.4);
border: 1px solid #2D50B5;
overflow: hidden;
#tree {
display: flex;
position: absolute;
align-items: center;
justify-content: center;
left: 50%;
top: 50%;
padding: 20px;
overflow: hidden;
width: max-content;
height: 300%;
}
::v-deep .org-chart-container {
display: flex;
justify-content: center;
.org-chart-node-children:before, .org-chart-node:after, .org-chart-node:last-child:before,
.org-chart-node.is-leaf:before {
border-radius: 0;
border-color: #9CD7FF !important;
}
.vertical .org-chart-node:after, .vertical .org-chart-node:before {
border-radius: 0;
border-color: #9CD7FF !important;
}
.org-chart-node-label-inner {
padding: 0 !important;
}
.org-chart-node-btn {
margin-left: 0;
transform: translateX(-50%);
}
.org-chart-node {
// max-width: 500px;
}
}
::v-deep .org-chart-node-children {
display: flex;
justify-content: center;
}
}
.left {
width: 320px;
background: rgba(7, 11, 35, 0.4);
border: 1px solid #2D50B5;
.griddv-tree {
height: calc(100% - 62px);
overflow-y: auto;
margin: 0 8px;
padding-bottom: 8px;
}
::v-deep .el-tree {
background: transparent;
.el-tree-node__expand-icon {
color: #eaeff9;
}
.el-tree-node__expand-icon.is-leaf {
color: transparent;
}
.el-tree-node__content {
height: 32px;
color: #eaeff9;
font-size: 14px;
user-select: none;
font-weight: normal !important;
background: transparent;
}
.is-current > .el-tree-node__content, .el-tree-node__content:hover {
background: linear-gradient(270deg, #4895D9 0%, #2D52CA 100%);
}
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -272,6 +272,7 @@
},
mounted () {
document.title = '微网实格 · 服务矩阵'
this.$nextTick(() => {
if (document.querySelector('.AiDvWrapper .viewPanel')) {
document.querySelector('.AiDvWrapper .viewPanel').style.backgroundImage = 'url(https://cdn.cunwuyun.cn/dvcp/dv/pddv/bg.png)'
@@ -321,7 +322,7 @@
this.initLineChart('.chart2', {
x: res.data['群成员数'].map(v => v.month),
value: res.data['群成员数'].map(v => v.totalNumber),
name: '居民'
name: '居民'
})
}
})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,522 @@
<template>
<section class="AppVideoMonitoringDV">
<el-row type="flex" justify="space-between" class="body">
<div class="left-wrap column" flex>
<div class="left-top fill">
<label class="label">设备统计</label>
<dv-scroll-board :header="config.header" :config="config" class="table"/>
</div>
<div class="left-bottom">
<label class="label">电子地图</label>
<ai-map :areaId="user.info.areaId"/>
</div>
</div>
<div class="center-wrap column" flex>
<div flex style="width: 100%">
<div :span="6" class="card fill" v-for="(item,index) in cardList" :key="index">
<span>{{ item.label }}</span>
<span>{{ item.value }}</span>
</div>
</div>
<div class="center-bottom fill">
<label class="label">视频监控</label>
<div class="video-wrap">
<div class="item" v-for="(item,index) in videoData.slice(0,2)"
:key="index">
<template v-if="flag">
<iframe class="video" :src="item.url" allow="autoplay *; microphone *; fullscreen *"
allowfullscreen allowtransparency="true" allowusermedia="true" frameBorder="no"></iframe>
</template>
<template v-else>
<div class="video"></div>
</template>
<div class="info">
<span>{{ item.name }}</span>
</div>
</div>
</div>
<div class="video-wrap">
<div class="item" v-for="(item,index) in videoData.slice(2,4)"
:key="index">
<template v-if="flag">
<iframe class="video" :src="item.url" allow="autoplay *; microphone *; fullscreen *"
allowfullscreen allowtransparency="true" allowusermedia="true" frameBorder="no"></iframe>
</template>
<template v-else>
<div class="video"></div>
</template>
<div class="info">
<span>{{ item.name }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="right-wrap column" flex>
<div class="right-top fill">
<label class="label">人员统计</label>
<label class="total">人员总数
<span>0</span>
<span></span>
</label>
<dv-scroll-board :config="config1" class="total-table"/>
</div>
<div class="right-middle">
<label class="label">动作告警统计</label>
<div class="tag">
<span></span>
</div>
<ai-echart :ops="getOpt()"/>
<div class="info">
今日
<span>0</span>
<span></span>
本月
<span>0</span>
<span></span>
</div>
</div>
<div class="right-bottom">
<label class="label">声音告警统计</label>
<div class="tag">
<span></span>
</div>
<ai-echart :ops="getOpt('1')"/>
<div class="info">
今日
<span style="color:#00FFDF">0</span>
<span></span>
本月
<span style="color:#00FFDF">0</span>
<span></span>
</div>
</div>
</div>
</el-row>
</section>
</template>
<script>
import {mapState} from "vuex";
import Vue from "vue";
import {scrollBoard} from "@jiaminghi/data-view";
Vue.use(scrollBoard)
export default {
name: "AppVideoMonitoringDV",
label: "数据大屏-平安小区",
inject: {
dv: {default: ""}
},
props: {
instance: Function
},
computed: {
...mapState(['user']),
cardList() {
return [
{label: "总设备数", value: "0"},
{label: "在线设备", value: "0"},
{label: "离线设备", value: "0"},
{label: "设备在线率", value: "0%"},
]
},
},
data() {
return {
meta: {},
config: {
data: [
],
columnWidth: [60, 140],
header: ["机构", "设备在线率"],
headerBGC: '',
headerHeight: 41,
oddRowBGC: "",
evenRowBGC: "",
align: ["center", "center", "center"],
rowNum: 10,
index: true,
indexHeader: "排名"
},
config1: {
data: [
['普通用户', '-'],
['村级管理员', '-'],
['超级管理员', '-'],
['测试', '-'],
],
headerHeight: 41,
oddRowBGC: "",
evenRowBGC: "",
align: ["left", "left", "right"],
index: true,
rowNum: 6,
columnWidth: [50]
},
start: 0,
end: 40,
start1: 0,
end1: 40,
interval: null,
videoData: [],
flag: false,
}
},
methods: {
jsonObj(val) {
return JSON.parse(val || "{}")?.url
},
getOpt(type = "0") {
let opt = {
"0": {
xData: ['2020/7/1', "2020/7/2", "2020/7/3", "2020/7/4", "2020/7/5", "2020/7/6", "2020/7/7", "2020/7/8", "2020/7/9", "2020/7/10", "2020/7/11"],
yData: Array(11).fill(0),
color: "#FFEA2F",
areaStyle: 'rgba(255,234,47,0.2)',
unit: '',
},
"1": {
xData: ["1:00", "2:00", "3:00", "4:00", "5:00", "6:00", "7:00", "8:00", "9:00", "10:00"],
yData: Array(10).fill(0),
color: "#13B5B1",
areaStyle: 'rgba(255,234,47,0.2)',
unit: '次',
},
}
return {
grid: {
top: "5%",
left: "16%",
right: "3%",
bottom: "12%"
},
xAxis: {
show: true,
boundaryGap: false,
type: 'category',
axisLabel: {
show: true,
textStyle: {
color: "#5E9CEA",
fontSize: 12
}
},
axisLine: {
show: false
},
axisTick: {
show: false
},
data: opt[type]["xData"]
},
yAxis: {
type: 'value',
show: true,
axisTick: {
show: false
},
axisLine: {
show: false
},
splitLine: {
show: true,
lineStyle: {
color: ['#103066'],
}
},
axisLabel: {
show: true,
formatter: (val) => `${val}${opt[type]["unit"]}`,
textStyle: {
color: "#5E9CEA",
fontSize: 12
}
},
},
series: [{
symbol: "none",
data: opt[type]["yData"],
type: 'line',
itemStyle: {
normal: {
color: opt[type]["color"],
areaStyle: {
color: {
type: 'linear', x2: 0, y2: 1, colorStops: [
{offset: 0, color: opt[type]["areaStyle"]}, {offset: 1, color: '#000000'}]
}
}
}
}
}]
}
},
getWebSdkUrlForScreen() {
this.instance.post(`/app/appzyvideoequipment/getWebSdkUrlForTianQiaoScreen`).then(res => {
if (res.code == 0) {
this.videoData = res.data
this.flag = true
}
})
}
},
beforeDestroy() {
this.interval && clearInterval(this.interval);
},
mounted() {
// this.getWebSdkUrlForScreen()
},
}
</script>
<style lang="scss" scoped>
.AppVideoMonitoringDV {
height: 100%;
padding: 6px 0 10px;
display: flex;
flex-direction: column;
gap: 24px;
.body {
height: 100%;
.left-wrap {
width: 307px;
height: 100%;
.left-top {
width: 100%;
background-image: url("../assets/videoMonitor/box1.png");
background-size: 100% 100%;
position: relative;
overflow: hidden;
.table {
box-sizing: border-box;
padding: 63px 17px 17px;
}
}
.left-bottom {
width: 100%;
height: 311px;
background-image: url("../assets/videoMonitor/box2.png");
background-size: 100% 100%;
margin-top: 7px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
.AiMap {
width: 274px;
height: 247px;
margin-top: 40px;
}
}
}
.center-wrap {
width: 1183px;
height: 100%;
.card {
height: 93.3px;
display: flex;
align-items: center;
justify-content: center;
background-image: url("../assets/videoMonitor/card.png");
background-size: 100% 100%;
margin-right: 24px;
&:last-child {
margin-right: 0;
}
& > span:first-child {
font-size: 20px;
font-weight: 400;
color: #FFFFFF;
}
& > span:last-child {
font-size: 38px;
font-family: dineng, serif;
font-weight: bold;
color: #00EDFF;
margin-left: 13px;
}
}
.center-bottom {
width: 100%;
height: 789px;
background-image: url("../assets/videoMonitor/middlebox.png");
background-size: 100% 100%;
margin-top: 40px;
position: relative;
box-sizing: border-box;
padding: 61px;
.video-wrap {
width: 100%;
height: 344px;
display: flex;
justify-content: space-between;
margin-bottom: 12px;
.item {
.video {
width: 522px;
height: 296px;
object-fit: fill;
vertical-align: bottom;
border: 2px solid #122C7D;
}
.info {
height: 48px;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: 0 12px;
background-color: #071153;
font-size: 14px;
color: #FFFFFF;
}
}
}
}
}
.right-wrap {
width: 297px;
height: 100%;
.right-top {
width: 100%;
height: 333px;
background-image: url("../assets/videoMonitor/box3.png");
background-size: 100% 100%;
position: relative;
overflow: hidden;
.total {
font-size: 16px;
color: #979AB7;
display: inline-block;
margin: 65px 0 16px 23px;
& > span:nth-child(1) {
font-size: 28px;
font-family: dineng, serif;
font-weight: bold;
color: #01CAFF;
}
& > span:nth-child(2) {
font-size: 16px;
color: #01CAFF;
}
}
.total-table {
height: 200px;
box-sizing: border-box;
padding: 0 23px;
}
}
.right-middle, .right-bottom {
width: 100%;
height: 276px;
background-image: url("../assets/videoMonitor/box4.png");
background-size: 100% 100%;
margin: 19px 0;
position: relative;
overflow: hidden;
.tag {
margin: 28px 18px 0 0;
display: flex;
justify-content: flex-end;
font-size: 12px;
color: #5E9CEA;
& > span {
font-size: 12px;
color: #FFFFFF;
display: inline-block;
width: 34px;
height: 17px;
text-align: center;
line-height: 18px;
margin-left: 14px;
}
}
::v-deep .AiEchart {
width: 251px;
height: 175px;
margin: 11px auto 0;
}
.info {
font-size: 12px;
color: #FFFFFF;
line-height: 24px;
box-sizing: border-box;
padding: 15px 19px 0;
& > span:nth-child(2n+1) {
font-size: 18px;
font-weight: 400;
line-height: 24px;
color: #FFE930;
}
& > span:nth-child(2n) {
font-size: 14px;
font-weight: 400;
color: #FFE930;
line-height: 24px;
margin: 0 5px;
}
& > span:nth-child(2) {
margin-right: 22px;
}
}
}
.right-bottom {
margin: 0;
}
}
}
.label {
position: absolute;
left: 24px;
top: 23px;
font-size: 16px;
font-weight: 600;
color: #FFFFFF;
}
::v-deep .index {
background-color: transparent !important;
}
::v-deep .header {
background: url("../assets/videoMonitor/titlebox.png");
}
::v-deep .row-item div:nth-child(3) {
font-size: 13px;
color: #00CDFF;
}
}
</style>

1262
project/dv/apps/AppXyDv.vue Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,490 @@
<template>
<div class="pdgrid">
<div class="pdgrid-title">
<h2>{{ currGird }}</h2>
</div>
<!-- <div class="pdgrid-grid__title" @click="isShowGrid2 = true">
<h2 :title="girdName2">{{ girdName2 }}</h2>
</div> -->
<div class="pdgrid-body">
<div class="pdgrid-body__item" @click="isShowGrid3 = true">
<h2>{{ girdNum3 || 0 }}</h2>
<div class="bottom">
<i></i>
<p>{{ girdName3 }}</p>
<i class="right"></i>
</div>
</div>
<div class="pdgrid-body__item" @click.stop="isShowGrid4 = true">
<h2>{{ girdNum4 || 0 }}</h2>
<div class="bottom">
<i></i>
<p>{{ girdName4 }}</p>
<i class="right"></i>
</div>
</div>
<div class="pdgrid-body__item" @click="isShowGrid5 = true">
<h2>{{ girdNum5 || 0 }}</h2>
<div class="bottom">
<i></i>
<p>{{ girdName5 }}</p>
<i class="right"></i>
</div>
</div>
</div>
<transition name="fade">
<div class="grid-dialog" v-show="isShowGrid2">
<div class="mask" @click="isShowGrid2 = false"></div>
<div class="grid-container">
<h2 :title="girdName2">{{ girdName2 }}</h2>
<div class="grid-list">
<div
:class="[currIndex2 === index ? 'grid-active' : '']"
v-for="(item, index) in girdInfoList2"
:key="index"
:title="item.girdName"
@click.stop="onGrid2Click(item, index)">
{{ item.girdName }}
</div>
</div>
</div>
</div>
</transition>
<transition name="fade">
<div class="grid-dialog" v-show="isShowGrid3">
<div class="mask" @click="isShowGrid3 = false"></div>
<div class="grid-container">
<h2 :title="girdName3">{{ girdName3 }}</h2>
<div class="grid-list">
<div
:class="[currIndex3 === index ? 'grid-active' : '']"
v-for="(item, index) in girdInfoList3"
:key="index"
:title="item.girdName"
@click.stop="onGrid3Click(item, index)">
{{ item.girdName }}
</div>
</div>
</div>
</div>
</transition>
<transition name="fade">
<div class="grid-dialog" v-show="isShowGrid4">
<div class="mask" @click="isShowGrid4 = false"></div>
<div class="grid-container">
<h2 :title="girdName4">{{ girdName4 }}</h2>
<div class="grid-list">
<div
:class="[currIndex4 === index ? 'grid-active' : '']"
v-for="(item, index) in girdInfoList4"
:key="index"
:title="item.girdName"
@click.stop="onGrid4Click(item, index)">
{{ item.girdName }}
</div>
</div>
</div>
</div>
</transition>
<transition name="fade">
<div class="grid-dialog" v-show="isShowGrid5">
<div class="mask" @click="isShowGrid5 = false"></div>
<div class="grid-container">
<h2 :title="girdName5">{{ girdName5 }}</h2>
<div class="grid-list">
<div
:class="[currIndex5 === index ? 'grid-active' : '']"
v-for="(item, index) in girdInfoList5"
:key="index"
:title="item.girdName"
@click.stop="onGrid5Click(item, index)">
{{ item.girdName }}
</div>
</div>
</div>
</div>
</transition>
</div>
</template>
<script>
export default {
name: 'XyGrid',
props: ['instance'],
data () {
return {
isShowGrid2: false,
isShowGrid3: false,
isShowGrid4: false,
isShowGrid5: false,
currIndex2: 0,
currIndex3: 0,
currIndex4: 0,
currIndex5: 0,
girdInfoList2: [],
girdInfoList3: [],
girdInfoList4: [],
girdInfoList5: [],
girdName2: '',
girdName3: '',
girdName4: '',
girdName5: '',
girdNum3: 0,
girdNum4: 0,
girdNum5: 0,
currGird: ''
}
},
mounted () {
this.$nextTick(() => {
document.addEventListener('keydown', this.onKeyDown)
})
this.getInfo()
},
destroyed () {
document.removeEventListener('keydown', this.onKeyDown)
},
methods: {
onKeyDown (e) {
if (e.keyCode == 27) {
this.isShowGrid2 = false
this.isShowGrid3 = false
this.isShowGrid4 = false
this.isShowGrid5 = false
}
},
onGrid2Click (item, index) {
this.currIndex2 = index
this.girdName2 = item.girdName
this.currIndex3 = -1
this.currIndex4 = -1
this.currIndex5 = -1
this.isShowGrid2 = false
this.girdInfoList3 = []
this.girdInfoList4 = []
this.girdInfoList5 = []
this.$emit('nodeClick', item.id)
this.currGird = item.girdName
this.getInfo(item.id)
},
onGrid3Click (item, index) {
this.currIndex3 = index
this.girdName3 = item.girdName
this.currIndex4 = -1
this.currIndex5 = -1
this.girdNum3 = 1
this.isShowGrid3 = false
this.$emit('nodeClick', {
id: item.id,
level: 2
})
this.girdInfoList4 = []
this.girdInfoList5 = []
this.currGird = item.girdName
this.getInfo(item.id)
},
onGrid4Click (item, index) {
this.currIndex4 = index
this.girdName4 = item.girdName
this.currIndex5 = -1
this.girdNum4 = 1
this.isShowGrid4 = false
this.$emit('nodeClick', {
id: item.id,
level: 3
})
this.girdInfoList5 = []
this.currGird = item.girdName
this.getInfo(item.id)
},
onGrid5Click (item, index) {
this.currIndex5 = index
this.girdName5 = item.girdName
this.isShowGrid5 = false
this.girdNum5 = 1
this.$emit('nodeClick', {
id: item.id,
level: 4
})
this.currGird = item.girdName
this.getInfo(item.id)
},
getInfo (id) {
this.instance.post(`/app/qxn_appgirdinfo/queryGirdInfo?id=${id || ''}`).then(res => {
if (res.code === 0) {
res.data.girdInfoList2 && (this.girdInfoList3 = res.data.girdInfoList2)
res.data.girdInfoList3 && (this.girdInfoList4 = res.data.girdInfoList3)
res.data.girdInfoList4 && (this.girdInfoList5 = res.data.girdInfoList4)
res.data.girdName1 && (this.girdName2 = res.data.girdName1)
res.data.girdName2 && (this.girdName3 = res.data.girdName2)
res.data.girdName3 && (this.girdName4 = res.data.girdName3)
res.data.girdName4 && (this.girdName5 = res.data.girdName4)
res.data.girdNum2 != null && (this.girdNum3 = res.data.girdNum2)
res.data.girdNum3 != null && (this.girdNum4 = res.data.girdNum3)
res.data.girdNum4 != null && (this.girdNum5 = res.data.girdNum4)
if (!id) {
this.currGird = res.data.girdName1
}
}
})
}
}
}
</script>
<style lang="scss">
.pdgrid {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
box-sizing: border-box;
background: url(https://cdn.cunwuyun.cn/dvcp/dv/pddv/middle-bg.png) no-repeat center;
background-size: contain;
.fade-enter-active, .fade-leave-active {
transition: opacity .3s ease-in-out;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
* {
box-sizing: border-box;
}
.pdgrid-grid__title {
position: absolute;
top: 40px;
left: 50%;
width: 271px;
height: 53px;
line-height: 53px;
text-align: center;
background: url(https://cdn.cunwuyun.cn/dvcp/dv/pddv/grid-title-sbg.png) no-repeat center;
background-size: 100% 100%;
cursor: pointer;
transform: translateX(-50%);
transition: opacity ease 0.3s;
&:hover {
opacity: 0.8;
}
h2 {
width: 182px;
margin: 0 auto;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #FFFFFF;
font-size: 21px;
text-shadow: 0px 0px 13px rgb(59 182 255 / 80%);
background: #fff;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
}
.pdgrid-title {
position: absolute;
top: 200px;
left: 50%;
min-width: 640px;
height: 80px;
line-height: 80px;
text-align: center;
background: url(https://cdn.cunwuyun.cn/dvcp/dv/pddv/middle-titlebg.png) no-repeat center;
background-size: 100% 100%;
transform: translateX(-50%);
h2 {
color: #FFFFFF;
font-size: 22px;
white-space: nowrap;
text-shadow: 0px 0px 13px rgb(59 182 255 / 80%);
background: #fff;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
}
.pdgrid-body {
display: flex;
position: absolute;
justify-content: space-between;
bottom: 200px;
left: 0;
width: 100%;
padding: 0 112px;
.pdgrid-body__item {
display: flex;
flex-direction: column;
width: 200px;
height: 187px;
align-items: center;
padding-top: 71px;
cursor: pointer;
background: url(https://cdn.cunwuyun.cn/dvcp/dv/pddv/item-bg.png) no-repeat center;
background-size: 100% 100%;
transition: opacity ease 0.3s;
&:hover {
opacity: 0.8;
}
&:nth-of-type(2) {
position: relative;
top: 67px;
}
h2 {
font-size: 36px;
color: #FFFFFF;
text-shadow: 0px 0px 13px rgb(59 182 255 / 80%);
background: #fff;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
p {
max-width: 164px;
margin-top: 4px;
padding: 0 16px;
font-size: 16px;
color: #FFFFFF;
text-shadow: 0px 0px 13px rgb(59 182 255 / 80%);
background: #fff;
-webkit-background-clip: text;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
-webkit-text-fill-color: transparent;
}
.bottom {
display: flex;
align-items: center;
i {
width: 0px;
height: 0px;
border: 6px solid transparent;
border-top-color: transparent;
border-bottom-color: transparent;
border-left-color: transparent;
border-right-color: #FFCB42;
&.right {
width: 0px;
height: 0px;
border: 6px solid transparent;
border-top-color: transparent;
border-bottom-color: transparent;
border-left-color: #FFCB42;
border-right-color: transparent;
}
}
}
}
}
.grid-dialog {
position: fixed;
top: 0;
left: 0;
z-index: 111;
width: 100%;
height: 100%;
& > .mask {
position: absolute;
left: 0;
top: 0;
z-index: 1;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
}
.grid-container {
display: flex;
position: absolute;
flex-direction: column;
left: 50%;
top: 50%;
z-index: 2;
width: 640px;
height: 640px;
background: rgba(7,13,41,0.9);
border: 1px solid #144662;
transform: translate(-50%, -50%);
& > h2 {
width: 100%;
height: 67px;
line-height: 67px;
padding: 0 20px;
text-align: center;
color: #FFFFFF;
font-size: 24px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-shadow: 0px 0px 13px rgb(59 182 255 / 80%);
background: url(https://cdn.cunwuyun.cn/dvcp/dv/pddv/grid-title-bg.png) no-repeat center;
background-size: 100% 100%;
}
.grid-list {
flex: 1;
overflow-y: auto;
& > div {
height: 67px;
line-height: 67px;
padding: 0 20px;
text-align: center;
color: #FFFFFF;
font-size: 27px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: all ease 0.5s;
&.grid-active {
background: linear-gradient(270deg, rgba(0,48,124,0) 0%, #00307C 16%, rgba(0,99,255,0.9100) 50%, rgba(0,48,124,0.8200) 87%, rgba(0,48,124,0) 100%);
box-shadow: inset 0px -1px 0px 0px rgba(16,34,54,1);
text-shadow: 0px 3px 5px rgba(0,0,0,0.5000);
}
&:hover {
background: linear-gradient(270deg, rgba(0,48,124,0) 0%, #00307C 16%, rgba(0,99,255,0.9100) 50%, rgba(0,48,124,0.8200) 87%, rgba(0,48,124,0) 100%);
box-shadow: inset 0px -1px 0px 0px rgba(16,34,54,1);
text-shadow: 0px 3px 5px rgba(0,0,0,0.5000);
}
}
}
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

12
project/dv/package.json Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "dvcp-dv",
"description": "大屏应用",
"version": "1.0.0",
"main": "dist/dvcp-dv.common.js",
"publishConfig": {
"registry": "http://192.168.1.87:4873/"
},
"dependencies": {
"dvcp-dv-ui": "^2.0.0"
}
}

View File

@@ -0,0 +1,91 @@
export default {
legend: {
right: 0,
itemHeight: 5,
itemWidth: 16,
textStyle: { color: '#fff' }
},
yAxis: {
nameGap: 23,
minInterval: 1,
splitLine: { lineStyle: { color: 'rgba(255,255,255,.2)', type: 'dashed' } },
axisLabel: { color: '#fff' },
axisPointer: { show: false }
},
axisPointer: {
type: 'shadow',
triggerTooltip: false,
shadowStyle: { color: 'rgba(46, 153, 255, .2)' }
},
color: [
{
type: 'linear',
x: 0,
x2: 0,
y: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(66, 187, 255, 1)' },
{ offset: 1, color: 'rgba(37, 143, 255, 0.2)' }
]
},
{
type: 'linear',
x: 0,
x2: 0,
y: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(66, 255, 254, 1)' },
{ offset: 1, color: 'rgba(37, 255, 246, 0.2)' }
]
},
{
type: 'linear',
x: 0,
x2: 0,
y: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(97, 253, 185, 1)' },
{ offset: 1, color: 'rgba(97, 253, 185, 0.2)' }
]
},
{
type: 'linear',
x: 0,
x2: 0,
y: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(253, 108, 57, 1)' },
{ offset: 1, color: 'rgba(253, 108, 57, 0.2)' }
]
},
{
type: 'linear',
x: 0,
x2: 0,
y: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(248, 187, 25, 1)' },
{ offset: 1, color: 'rgba(55, 39, 5, 1)' }
]
}
],
daemon: {
type: 'bar',
label: {
show: true,
position: 'top',
color: '#fff',
formatter: e => {
return e.data[e.seriesName] || ''
}
},
barWidth: 16,
barCategoryGap: 40,
itemStyle: {}
}
}

View File

@@ -0,0 +1,24 @@
export default {
legend: {
right: 0, itemGap: 16,
textStyle: {color: '#fff', padding: [0, 0, 0, 8], fontSize: 14},
itemWidth: 16, itemHeight: 5
},
grid: {
left: 50, right: 0
},
tooltip: {
trigger: 'axis', backgroundColor: 'rgba(14, 51, 111, 0.9)', borderColor: '#1A6ABC',
textStyle: {color: '#fff'}
},
yAxis: {
nameGap: 23, minInterval: 1,
splitLine: {lineStyle: {color: 'rgba(255,255,255,.2)', type: 'dashed'}},
axisLabel: {color: '#fff'}, axisPointer: {show: false}
},
axisPointer: {
type: 'shadow', show: true,
shadowStyle: {color: 'rgba(46, 153, 255, .2)'}
},
daemon: {type: 'bar', barWidth: 2, barGap: 4}
}

View File

@@ -0,0 +1,33 @@
export default {
legend: {show: false},
yAxis: {
nameGap: 23, minInterval: 1,
splitLine: {lineStyle: {color: 'rgba(255,255,255,.2)', type: 'dashed'}},
axisLabel: {color: '#fff'}, axisPointer: {show: false}
},
axisPointer: {
type: 'none', show: true, triggerTooltip: false,
},
daemon: {
type: 'bar', label: {show: true, position: 'insideBottom', color: '#fff'},
barWidth: 24,
showBackground: true,
backgroundStyle: {
color: 'rgba(123, 165, 255, .2)'
},
itemStyle: {
color: {
type: 'linear', x: 0, x2: 0, y: 0, y2: 1,
colorStops: [{offset: 0, color: '#42BBFF'}, {offset: 1, color: 'rgba(37, 143, 255, 0.2)'}]
}
},
emphasis: {
itemStyle: {
color: {
type: 'linear', x: 0, x2: 0, y: 0, y2: 1,
colorStops: [{offset: 0, color: '#42FFFE'}, {offset: 1, color: 'rgba(37, 255, 246, 0.2)'}]
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
export default {
legend: {
right: 0, itemGap: 16,
textStyle: {color: '#fff', padding: [0, 0, 0, 8], fontSize: 14},
itemWidth: 16, itemHeight: 5
},
grid: {
left: 50, right: 0
},
tooltip: {
trigger: 'axis', backgroundColor: 'rgba(14, 51, 111, 0.9)', borderColor: '#1A6ABC',
textStyle: {color: '#fff'}
},
yAxis: {
nameGap: 23, minInterval: 1,
splitLine: {lineStyle: {color: 'rgba(255,255,255,.2)', type: 'dashed'}},
axisLabel: {color: '#fff'}, axisPointer: {show: false}
},
axisPointer: {
type: 'none', show: true, triggerTooltip: false,
},
daemon: {type: 'bar', barWidth: 12, stack: 'stack'}
}

View File

@@ -0,0 +1,45 @@
export default {
legend: {show: false},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(14, 51, 111, 0.9)', borderColor: '#1A6ABC', textStyle: {color: '#fff'},
axisPointer: {
type: 'shadow', shadowStyle: {color: 'rgba(46, 153, 255, .2)'},
label: {show: true, backgroundColor: 'transparent', fontSize: 14, margin: 1, color: '#28FBFF'}
}
},
yAxis: {
type: 'category',
axisLabel: {color: '#fff', fontSize: 14},
axisTick: {show: false},
axisLine: {show: false},
},
xAxis: {
nameGap: 23, minInterval: 1,
splitLine: {lineStyle: {color: 'rgba(255,255,255,.2)', type: 'dashed'}},
axisLabel: {color: '#fff', fontSize: 14},
},
daemon: {
type: 'bar', barWidth: 10, barGap: '40%',
label: {show: true, position: 'insideRight', color: '#fff', fontSize: 14},
showBackground: true,
backgroundStyle: {
color: 'rgba(123, 165, 255, .2)'
},
itemStyle: {
color: {
type: 'linear', x: 0, x2: 1, y: 0, y2: 0,
colorStops: [{offset: 0, color: 'rgba(37, 143, 255,.5)'}, {offset: 1, color: 'rgba(43, 199, 255, 1)'}]
}
},
emphasis: {
label: {color: '#28FBFF', position: 'right'},
itemStyle: {
color: {
type: 'linear', x: 0, x2: 1, y: 0, y2: 0,
colorStops: [{offset: 0, color: 'rgba(37, 255, 246, 0.5)'}, {offset: 1, color: '#28FBFF'}]
}
}
}
}
}

View File

@@ -0,0 +1,36 @@
import tools from "../tools";
export default {
legend: {
right: 0, itemGap: 16,
textStyle: {color: '#fff', padding: [0, 0, 0, 8], fontSize: 14},
itemWidth: 16, itemHeight: 5
},
grid: {
left: 50, right: 0
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(14, 51, 111, 0.9)', borderColor: '#1A6ABC', textStyle: {color: '#fff'}
},
yAxis: {
type: 'category',
axisLabel: {color: '#fff', fontSize: 14},
axisLine: {lineStyle: {color: 'rgba(255,255,255,.5)'}},
axisPointer: {show: false},
},
xAxis: {
nameGap: 23, minInterval: 1,
splitLine: {lineStyle: {color: 'rgba(255,255,255,.2)', type: 'dashed'}},
axisLabel: {color: '#fff', fontSize: 14},
},
daemon: {
type: 'bar', barWidth: 10,
itemStyle: {
color: ({color}) => ({
type: 'linear', x: 0, x2: 1, y: 0, y2: 0,
colorStops: [{offset: 0, color: tools.$colorUtils.Hex2RGBA(color, .5)}, {offset: 1, color}]
})
},
}
}

View File

@@ -0,0 +1,29 @@
export default {
legend: {
right: 0, itemGap: 16,
textStyle: {color: '#fff', padding: [0, 0, 0, 8], fontSize: 14},
itemWidth: 16, itemHeight: 5
},
grid: {
left: 80, right: 0
},
tooltip: {
backgroundColor: 'rgba(14, 51, 111, 0.9)', borderColor: '#1A6ABC', textStyle: {color: '#fff'},
},
yAxis: {
type: 'category',
axisLabel: {color: '#fff', fontSize: 14, margin: 23},
axisTick: {show: false},
axisLine: {show: false},
},
xAxis: {
nameGap: 23, minInterval: 1,
splitLine: {lineStyle: {color: 'rgba(255,255,255,.2)', type: 'dashed'}},
axisLabel: {color: '#fff', fontSize: 14},
},
daemon: {
type: 'bar', barWidth: 10, barGap: '40%', stack: 'stack',
showBackground: true,
backgroundStyle: {color: 'rgba(123, 165, 255, .2)'}
}
}

View File

@@ -0,0 +1,40 @@
import tools from '../tools';
export default {
legend: { show: false },
grid: {
left: 50,
right: 0
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(14, 51, 111, 0.9)',
borderColor: '#1A6ABC',
textStyle: { color: '#fff' },
axisPointer: { type: 'cross' }
},
yAxis: {
nameGap: 23,
minInterval: 1,
splitLine: { lineStyle: { color: 'rgba(255,255,255,.2)', type: 'dashed' } },
axisLabel: { color: '#fff' },
axisPointer: { snap: true }
},
daemon: (color) => ({
showSymbol: false,
lineStyle: { shadowBlur: 4, shadowOffsetY: 2 },
areaStyle: {
color: {
type: 'linear',
x: 0,
x2: 0,
y: 0,
y2: 1,
colorStops: [
{ offset: 0, color: tools.$colorUtils.Hex2RGBA(color, 0.5) },
{ offset: 1, color: tools.$colorUtils.Hex2RGBA(color, 0.5) }
]
}
}
})
};

View File

@@ -0,0 +1,27 @@
import tools from "../tools";
export default {
legend: {
right: 0, itemGap: 16,
textStyle: {color: '#fff', padding: [0, 0, 0, 8], fontSize: 14},
},
grid: {
left: 50, right: 0
},
tooltip: {
trigger: 'axis', backgroundColor: 'rgba(14, 51, 111, 0.9)', borderColor: '#1A6ABC',
textStyle: {color: '#fff'}
},
daemon: color => ({
showSymbol: false,
lineStyle: {shadowBlur: 4, shadowOffsetY: 2},
areaStyle: {
color: {
type: 'linear', x: 0, x2: 0, y: 0, y2: 1, colorStops: [
{offset: 0, color: tools.$colorUtils.Hex2RGBA(color, .5)},
{offset: 1, color: tools.$colorUtils.Hex2RGBA(color, 0)},
]
}
}
})
}

View File

@@ -0,0 +1,16 @@
export default {
legend: {
right: 0, itemGap: 16,
textStyle: {color: '#fff', padding: [0, 0, 0, 8], fontSize: 14},
},
grid: {
left: 50, right: 0
},
tooltip: {
trigger: 'axis', backgroundColor: 'rgba(14, 51, 111, 0.9)', borderColor: '#1A6ABC',
textStyle: {color: '#fff'}
},
daemon: {
lineStyle: {shadowBlur: 4, shadowOffsetY: 2},
}
}

View File

@@ -0,0 +1,19 @@
export default {
legend: {
right: 0, itemGap: 16,
textStyle: {color: '#fff', padding: [0, 0, 0, 8], fontSize: 14},
},
grid: {
left: 50, right: 0
},
tooltip: {
trigger: 'axis', backgroundColor: 'rgba(14, 51, 111, 0.9)', borderColor: '#1A6ABC',
textStyle: {color: '#fff'}
},
daemon: {
lineStyle: {shadowBlur: 4, shadowOffsetY: 2},
emphasis: {
focus: 'self'
}
}
}

View File

@@ -0,0 +1,31 @@
import tools from "../tools";
export default {
legend: {show: false},
grid: {
left: 50, right: 0
},
tooltip: {
trigger: 'axis', backgroundColor: 'rgba(14, 51, 111, 0.9)', borderColor: '#1A6ABC',
textStyle: {color: '#fff'},
axisPointer: {type: 'cross'}
},
yAxis: {
nameGap: 23, minInterval: 1,
splitLine: {lineStyle: {color: 'rgba(255,255,255,.2)', type: 'dashed'}},
axisLabel: {color: '#fff'},
axisPointer: {snap: true}
},
daemon: color=> ({
showSymbol: false, smooth: true,
lineStyle: {shadowBlur: 4, shadowOffsetY: 2},
areaStyle: {
color: {
type: 'linear', x: 0, x2: 0, y: 0, y2: 1, colorStops: [
{offset: 0, color: tools.$colorUtils.Hex2RGBA(color, .5)},
{offset: 1, color: tools.$colorUtils.Hex2RGBA(color, 0)},
]
}
}
})
}

View File

@@ -0,0 +1,96 @@
export default {
legend: {
bottom: 0,
itemGap: 14,
itemWidth: 16,
itemHeight: 5,
textStyle: {color: "#fff", fontSize: 14}
},
grid: {
height: 260
},
xAxis: {show: false},
yAxis: {show: false},
tooltip: {
backgroundColor: "rgba(14, 51, 111, 0.9)",
borderColor: "#1A6ABC",
textStyle: {color: "#fff"}
},
series: {
type: "pie",
minShowLabelAngle: 10,
radius: [70, 81],
itemStyle: {
borderColor: "#fff",
borderWidth: 2
},
label: {
color: "#A8D7F3",
fontSize: 14,
formatter: "{name|{b}}\n{v|{d}%}",
minMargin: 5,
edgeDistance: 10,
lineHeight: 22,
rich: {
v: {
color: "#fff"
}
}
},
labelLine: {},
labelLayout: function (params) {
let points = params.labelLinePoints,
isLeft = points[2][0] < points[1][0]
points[2][0] =
points[2][0] + (params.labelRect.width + 4) * (isLeft ? -1 : 1)
return {
labelLinePoints: points
}
}
},
render: (h, params) => {
const formatNum = num => {
if (num >= 10000000) {
return num / 10000000 + "千万"
}
if (num >= 10000) {
return num / 10000 + "万"
}
return parseFloat(num.toFixed(2))
}
let total = params.data.reduce((t, e) => {
return t + Number(Object.values(e)?.[1] || 0)
}, 0)
return h(
"div",
{
style: {
height: "162px",
width: "162px",
color: "#8BCCFF",
left: "50%",
top: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
position: "absolute",
transform: "translate(-50%,-50%)",
backgroundImage: `url('https://cdn.cunwuyun.cn/dvcp/dv/tpl/pie2Circle.png')`,
backgroundPosition: "center"
}
},
[
h(
"span",
{style: {fontSize: "28px", color: "#fff", fontFamily: "DIN"}},
formatNum(total)
),
h("span", null, "总量")
]
)
}
}

View File

@@ -0,0 +1,24 @@
export default {
$colorUtils: {
Hex2RGBA(color, alpha = 1) {
let hex = 0;
if (color.charAt(0) == "#") {
if (color.length == 4) {
//检测诸如#FFF简写格式
color = "#" + color.charAt(1).repeat(2) +
color.charAt(2).repeat(2) +
color.charAt(3).repeat(2);
}
hex = parseInt(color.slice(1), 16);
}
let r = hex >> 16 & 0xFF;
let g = hex >> 8 & 0xFF;
let b = hex & 0xFF;
return `rgba(${r},${g},${b},${alpha})`;
},
RGBtoHex(r, g, b) {
let hex = r << 16 | g << 8 | b;
return "#" + hex.toString(16);
}
}
}

View File

@@ -0,0 +1,48 @@
<template>
<section class="AiMonitor">
<template v-if="type=='cmcc'">
<iframe :src="src" allow="autoplay *; microphone *; fullscreen *" allowfullscreen allowtransparency
allowusermedia frameBorder="no"/>
</template>
<hikversion-monitor v-else-if="type=='hik'" :src="src"/>
<dhVideo v-else-if="type=='dahua'" :src="src"/>
<slwVideo v-else-if="type=='slw'" :src="src"/>
</section>
</template>
<script>
import HikversionMonitor from "./hikversionMonitor";
import dhVideo from "./dhVideo";
import slwVideo from "./slwVideo";
export default {
name: "AiMonitor",
components: {HikversionMonitor, dhVideo, slwVideo},
props: {
/**
* 视频源
*/
src: {default: ""},
/**
* 组件类型
* cmcc 中移物联,hik 海康威视
* @values cmcc,hik
*/
type: {default: "cmcc"},
}
}
</script>
<style lang="scss" scoped>
.AiMonitor {
width: 100%;
height: 100%;
min-width: 100px;
min-height: 60px;
iframe {
width: 100%;
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<div class="dh-video" style="height: 100%;">
<video :id="id" autoplay class="video-js vjs-default-skin" style="width: 100%; height: 100%;" controls>
<source :src="src">
</video>
</div>
</template>
<script>
import videojs from 'video.js'
import 'videojs-contrib-hls'
import 'video.js/dist/video-js.css'
export default {
props: ['src'],
data () {
return {
isInit: false,
id: `video-${Math.ceil(Math.random() * 1000000)}`
}
},
watch: {
src: {
handler(val) {
if (val) {
this.$nextTick(() => {
videojs(this.id, {
autoplay: true
}, function () { console.log('videojs播放器初始化成功') })
})
}
},
immediate: true,
deep: true
}
},
mounted () {
if (this.src) {
this.$nextTick(() => {
videojs(this.id, {
autoplay: true
}, function () {
this.isInit = true
console.log('videojs播放器初始化成功')
})
})
}
}
}
</script>
<style lang="scss" scoped>
.dh-video {
width: 100%;
height: 100%;
.video-js {
width: 100%!important;
height: 100%!important;
}
::v-deep .video-js {
width: 100%!important;
height: 100%!important;
.vjs-big-play-button {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
& > div {
width: 100%!important;
height: 100%!important;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More