8 Commits

Author SHA1 Message Date
aixianling
17bf8599d1 提交一波 2024-10-10 10:38:50 +08:00
aixianling
45017af56d 接入nuxt 2024-09-30 18:02:54 +08:00
aixianling
2f93572701 Merge remote-tracking branch 'origin/build' into release 2024-09-29 15:25:35 +08:00
aixianling
64f08839e2 Merge branch 'refs/heads/build' into release 2024-08-19 17:43:32 +08:00
aixianling
d52cbffed7 Merge branch 'build' into release 2024-08-19 16:20:41 +08:00
aixianling
0e7cb83bd4 Merge branch 'build' into release 2024-08-14 09:18:29 +08:00
aixianling
bc8b161020 整合工程 2024-08-06 17:57:34 +08:00
aixianling
d2e63c2588 整合工程 2024-08-06 17:53:52 +08:00
81 changed files with 2110 additions and 9216 deletions

29
.gitignore vendored
View File

@@ -27,3 +27,32 @@ yarn-error.log*
/project/*/dist
/ui/package-lock.json
/examples/modules.json
/src/apps/
/src/config.json
/.nuxt/components/nuxt.js
/.nuxt/components/nuxt-build-indicator.vue
/.nuxt/components/nuxt-child.js
/.nuxt/components/nuxt-error.vue
/.nuxt/components/nuxt-link.client.js
/.nuxt/components/nuxt-link.server.js
/.nuxt/components/nuxt-loading.vue
/.nuxt/layouts/default.vue
/.nuxt/mixins/fetch.client.js
/.nuxt/mixins/fetch.server.js
/.nuxt/views/app.template.html
/.nuxt/views/error.html
/.nuxt/App.js
/.nuxt/client.js
/.nuxt/empty.js
/.nuxt/index.js
/.nuxt/jsonp.js
/.nuxt/loading.html
/.nuxt/middleware.js
/.nuxt/router.js
/.nuxt/router.scrollBehavior.js
/.nuxt/routes.json
/.nuxt/server.js
/.nuxt/store.js
/.nuxt/utils.js

5
.npmrc
View File

@@ -1,5 +1,4 @@
registry=http://192.168.1.87:4873/
email=aixianling@sinoecare.com
always-auth=true
package-lock=false
//192.168.1.87:4873/:_auth="YWRtaW46YWRtaW4xMjM="
registry=http://registry.npmmirror.com
legacy-peer-deps=true

View File

@@ -102,7 +102,7 @@ export default {
localStorage.setItem("searchApp", this.searchApp)
},
},
created() {
mounted() {
this.searchApp = localStorage.getItem("searchApp") || ""
}
}

View File

@@ -1,18 +1,14 @@
import Vue from 'vue';
import App from './App.vue';
import ui from 'element-ui';
import router from './router/router';
import axios from './router/axios';
import utils from './utils';
import dui from 'dui';
import store from './store';
import dataV from '@jiaminghi/data-view';
import dvui from '@dui/dv'
Vue.use(dataV)
Vue.use(ui);
Vue.use(dui);
Vue.use(dvui);
//富文本编辑器配置
Vue.config.productionTip = false;
Object.keys(utils).map((e) => (Vue.prototype[e] = utils[e]));
@@ -28,7 +24,6 @@ store.dispatch('getSystem').then(res => {
Vue.prototype.$theme = theme?.web || "blue"
return import(`dui/lib/styles/theme.${theme?.web}.scss`).catch(() => 0)
}).finally(() => {
Vue.prototype.$vm = app
import(`dui/lib/styles/common.scss`).finally(() => app.$mount('#app'))
})

21
examples/nuxt.config.js Normal file
View File

@@ -0,0 +1,21 @@
import {resolve} from 'path'
export default {
css: ['ui/lib/styles/common.scss'],
dev: process.env.NODE_ENV !== 'production',
alias: {
'style': resolve(__dirname, './assets/style'),
'dui': resolve(__dirname, '../ui')
},
srcDir: "examples",
dir: {
pages: 'views',
},
build: {
postcss: null,
transpile: [/^ui/]
},
plugins: [
'~plugins/ui.js'
],
}

7
examples/plugins/ui.js Normal file
View File

@@ -0,0 +1,7 @@
import Vue from 'vue';
import ElementUI from 'element-ui';
import dui from 'dui/packages';
import 'dui/lib/styles/common.scss';
Vue.use(ElementUI);
Vue.use(dui)

View File

@@ -1,26 +1,26 @@
import Vue from 'vue'
import Vuex from 'vuex'
import preState from 'vuex-persistedstate'
import * as modules from "dui/lib/js/modules"
import xsActions from "../../project/xiushan/actions"
Vue.use(Vuex)
export default new Vuex.Store({
state: {
export const state = () => ({
apps: []
},
mutations: {
})
export const mutations = {
addApp(state, app) {
state.apps.push(app)
},
cleanApps(state) {
state.apps = []
},
},
actions: {
}
}
const actions = {
...xsActions
},
}
export default {
state,
mutations,
actions,
modules,
plugins: [preState()]
})
}

View File

@@ -6,9 +6,7 @@
</template>
<script>
import {mapState} from "vuex";
import Vue from "vue";
export default {
name: "appEntry",
@@ -20,14 +18,6 @@ export default {
return app.esm ?? ""
}
},
mounted() {
this.$vm.$on("mock", v => {
if (!!this.$refs.currentPage.$children?.[0]?.form) {
this.$refs.currentPage.$children[0].form = v
this.$refs.currentPage.$children[0].$forceUpdate()
}
})
}
}
</script>

View File

@@ -25,16 +25,15 @@
</template>
<script>
import SliderNav from "./components/sliderNav";
import MainContent from "./components/mainContent";
import HeaderNav from "./components/headerNav";
import SliderNav from "../components/sliderNav";
import MainContent from "../components/mainContent";
import HeaderNav from "../components/headerNav";
import {mapActions, mapMutations, mapState} from "vuex";
import Mock from "./components/mock";
import AiDvWrapper from "@dui/dv/layout/AiDvWrapper/AiDvWrapper.vue";
import Mock from "../components/mock";
export default {
name: 'app',
components: {AiDvWrapper, Mock, HeaderNav, MainContent, SliderNav},
components: {Mock, HeaderNav, MainContent, SliderNav},
computed: {
...mapState(['user']),
login() {
@@ -75,8 +74,9 @@ export default {
handleMock() {
}
},
created() {
wx = jWeixin
mounted() {
const {jWeixin} = window
window.wx = jWeixin
if (this.user.token) this.getUserInfo().finally(() => {
if (/^\/project\/xiushan/.test(location.pathname)) {
this.getFinanceUser()

View File

@@ -1,38 +1,28 @@
{
"name": "dvcp-web-apps",
"version": "3.0.0",
"name": "dvcp-web",
"version": "4.0.0",
"private": false,
"author": "kubbo",
"scripts": {
"dev": "vue-cli-service serve examples/main.js",
"dev": "nuxt -c examples/nuxt.config.js",
"build": "vue-cli-service build",
"dev:ai": "vue-cli-service serve examples/main.js --mode ai",
"dev:oms": "vue-cli-service serve examples/main.js --mode oms",
"dev:biaopin": "vue-cli-service serve examples/main.js --mode biaopin",
"dev:dv": "vue-cli-service serve examples/main.js --mode dv",
"dev:fengdu": "vue-cli-service serve examples/main.js --mode fengdu",
"lib": "npm publish||(npm unpublish -f&&npm publish)",
"preui": "npm publish -ws||(npm unpublish -f -ws&&npm publish -ws)",
"ui": "npm i dui@latest @dui/dv@latest",
"sync": "node bin/appsSync.js",
"preview": "vue-cli-service serve"
"preview": "vue-cli-service serve",
"prebuild": ""
},
"workspaces": [
"ui",
"ui/dv"
],
"files": [
"packages",
"project"
],
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@dui/dv": "^1.0.0",
"@ckeditor/ckeditor5-vue2": "^3.0.1",
"@jiaminghi/data-view": "^2.10.0",
"@logicflow/core": "^1.2.1",
"bin-ace-editor": "^3.2.0",
"crypto-js": "^4.1.1",
"dayjs": "^1.8.35",
"dui": "^2.0.0",
"echarts-wordcloud": "^2.0.0",
"hash.js": "^1.1.7",
"html2canvas": "^1.4.1",
@@ -41,7 +31,9 @@
"serialize-javascript": "^6.0.0",
"sortablejs": "^1.12.0",
"vue-carousel": "^0.18.0",
"vue-cropper": "^0.6.5",
"vue-draggable-resizable": "^2.3.0",
"vue-qr": "^4.0.9",
"vue-ruler-tool": "^1.2.4",
"vue-style-loader": "^4.1.3",
"vuedraggable": "^2.24.3"
@@ -63,6 +55,7 @@
"inquirer": "^6.5.2",
"mockjs": "^1.1.0",
"node-ipc": "^9.2.1",
"nuxt": "^2.18.1",
"readline": "^1.3.0",
"sass": "~1.32.6",
"sass-loader": "^7.3.1",
@@ -73,14 +66,6 @@
"vuex": "^3.5.1",
"vuex-persistedstate": "^2.7.1"
},
"vetur": {
"attributes": "./attributes.json"
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",

View File

@@ -1,62 +0,0 @@
<template>
<div class="AppHealthReport">
<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.vue'
import Detail from './components/Detail.vue'
export default {
name: 'AppHealthReport',
label: '健康上报',
components: {
List,
Detail
},
props: {
instance: Function,
dict: Object,
permissions: Function
},
data () {
return {
component: 'List',
params: {}
}
},
methods: {
onChange (data) {
if (data.type === 'Detail') {
this.component = 'Detail'
this.isShowDetail = true
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" scoped>
.AppHealthReport {
height: 100%;
}
</style>

View File

@@ -1,316 +0,0 @@
<template>
<ai-detail isHasSidebar>
<template slot="title">
<ai-title title="详情" isShowBack isShowBottomBorder @onBackClick="cancel(true)">
</ai-title>
</template>
<template slot="content">
<AiSidebar :tabTitle="tabList" v-model="currIndex"></AiSidebar>
<div v-show="currIndex === 0">
<ai-card title="基本信息" v-show="currIndex === 0">
<template #content>
<ai-wrapper
label-width="120px">
<ai-info-item label="姓名" :value="info.name"></ai-info-item>
<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.areaName"></ai-info-item>
<ai-info-item label="详细地址" isLine :value="info.address"></ai-info-item>
</ai-wrapper>
</template>
</ai-card>
</div>
<ai-card title="每日上报" v-show="currIndex === 1">
<template #content>
<ai-table
class="detail-table__table"
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<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="toDetail(row.id)">详情</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<ai-dialog
:visible.sync="isShow"
width="890px"
customFooter
title="上报详情">
<ai-bar title="健康状况"></ai-bar>
<ai-wrapper
label-width="120px">
<ai-info-item label="当前体温">
<span :style="{color: reportInfo.temperature > 37.3 ? '#FF4466' : '#42D784'}">{{ reportInfo.temperature }}</span>
</ai-info-item>
<ai-info-item label="14天内是否接触新冠确诊或疑似患者">
<span :style="{color: reportInfo.touchInFourteen === '0' ? '#42D784' : '#FF4466'}">{{ $dict.getLabel('epidemicTouchInFourteen', reportInfo.touchInFourteen) }}</span>
</ai-info-item>
<ai-info-item label="当前健康状况" isLine>
<span :style="{color: !reportInfo.isHealth ? '#42D784' : '#FF4466'}">{{ reportInfo.healthName }}</span>
</ai-info-item>
</ai-wrapper>
<ai-bar title="核酸检测信息"></ai-bar>
<ai-wrapper
label-width="120px">
<ai-info-item label="检测日期">
<span>{{ reportInfo.checkTime && reportInfo.checkTime.split(' ')[0] }}</span>
</ai-info-item>
<ai-info-item label="检测结果">
<span :style="{color: reportInfo.checkResult === '0' ? '#42D784' : '#FF4466'}">{{ $dict.getLabel('epidemicRecentTestResult', reportInfo.checkResult) }}</span>
</ai-info-item>
<ai-info-item label="健康码状态">
<span :style="{color: (reportInfo.healthCode === '0' || reportInfo.healthCode === '1') ? '#42D784' : '#FF4466'}">{{ $dict.getLabel('epidemicHealthCode', reportInfo.healthCode) }}</span>
</ai-info-item>
<ai-info-item label="已接种疫苗次数">
<span>{{ $dict.getLabel('epidemicVaccineTime', reportInfo.vaccine) }}</span>
</ai-info-item>
<ai-info-item label="本人健康码截图" isLine>
<ai-uploader
:instance="instance"
v-model="reportInfo.checkPhoto"
disabled
:limit="9">
</ai-uploader>
</ai-info-item>
</ai-wrapper>
<div class="dialog-footer" slot="footer">
<el-button @click="isShow = false">关闭</el-button>
</div>
</ai-dialog>
</template>
</ai-card>
<div v-show="currIndex === 2">
<ai-card title="异常处理">
<template #right>
<el-button type="primary" v-if="info.status === '0'" @click="release">解除异常</el-button>
</template>
<template #content>
<ai-wrapper
label-width="120px">
<ai-info-item label="姓名" :value="info.name"></ai-info-item>
<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>
<span :style="{color: info.unusual ? 'red' : '#333'}">{{ info.unusual || '-' }}</span>
</ai-info-item>
<ai-info-item label="异常解除人" v-if="info.releaseName && info.status === '1'" :value="info.releaseName"></ai-info-item>
<ai-info-item label="异常解除时间" v-if="info.releaseTime && info.status === '1'" :value="info.releaseTime"></ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="异常情况记录">
<template #right>
<el-button type="primary" v-if="info.status === '0'" @click="isShowAdd = true">添加记录</el-button>
</template>
<template #content>
<ai-table
:tableData="recordList"
:col-configs="recordConfigs"
:total="recordTotal"
:current.sync="recordSerch.current"
:size.sync="recordSerch.size"
@getList="getRecordList">
<el-table-column slot="options" width="120px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-card>
<ai-dialog
:visible.sync="isShowAdd"
width="800px"
@close="form.content = ''"
title="添加异常记录"
@onConfirm="onConfirm">
<el-form class="ai-form" label-width="120px" :model="form" ref="form">
<el-form-item label="异常记录" prop="content" style="width: 100%;" :rules="[{ required: true, message: '请输入异常记录' }]">
<el-input type="textarea" :rows="5" :maxlength="500" v-model="form.content" clearable placeholder="请输入异常记录" show-word-limit></el-input>
</el-form-item>
</el-form>
</ai-dialog>
</div>
</template>
</ai-detail>
</template>
<script>
export default {
name: 'Detail',
props: {
instance: Function,
dict: Object,
params: Object
},
data () {
return {
total: 0,
info: {},
id: '',
isShowAdd: false,
recordTotal: 0,
recordSerch: {
current: 1,
size: 10
},
search: {
current: 1,
size: 10
},
form: {
content: ''
},
recordConfigs: [
{prop: 'content', label: '异常记录', align: 'center' },
{prop: 'createTime', label: '创建时间', align: 'center'},
{prop: 'createUserName', label: '记录人', align: 'center' }
],
reportInfo: {},
isShow: false,
currIndex: 0,
tableData: [],
recordList: [],
colConfigs: [
{prop: 'createTime', label: '上报日期', align: 'center', dateFormat: 'YYYY-MM-DD'},
{prop: 'status', label: '健康状态', align: 'center', format: v => v === '0' ? '异常' : '正常' }
],
tabList: ['基本信息', '每日上报', '异常处理']
}
},
created () {
if (this.params && this.params.id) {
this.id = this.params.id
this.dict.load(['epidemicRecentHealth', 'epidemicRecentTravel', 'epidemicTouchInFourteen', 'epidemicMemberType', 'epidemicRecentTestResult']).then(() => {
this.getInfo(this.params.id)
this.getList()
this.getRecordList()
})
}
},
methods: {
getInfo (id) {
this.instance.post(`/app/appepidemicreportmember/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.info = res.data
this.currIndex = 0
}
})
},
getRecordList () {
this.instance.post(`/app/appepidemicunusuallog/list`, null, {
params: {
...this.search,
recordId: this.params.id
}
}).then(res => {
if (res.code == 0) {
this.recordList = res.data.records
this.recordTotal = res.data.total
}
})
},
onConfirm() {
this.$refs.form.validate(v => {
if (v) {
this.instance.post('/app/appepidemicunusuallog/addOrUpdate', {
...this.form,
recordId: this.params.id
}).then(res => {
if (res?.code == 0) {
this.isShowAdd = false
this.getRecordList(this.params.id)
this.$message.success('添加成功!')
}
})
}
})
},
release () {
this.$confirm('确定解除异常?').then(() => {
this.instance.post(`/app/appepidemicreportmember/release`, {
id: this.params.id
}).then(res => {
if (res.code == 0) {
this.$message.success('解除异常成功!')
this.currIndex = 0
this.getInfo(this.params.id)
}
})
})
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appepidemicunusuallog/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getRecordList(this.params.id)
}
})
})
},
toDetail (id) {
this.instance.post(`/app/appepidemichealthreport/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.reportInfo = res.data
this.reportInfo.checkPhoto = JSON.parse(res.data.checkPhoto)
let healthName = ''
this.reportInfo.isHealth = false
res.data.health.split(',').forEach(v => {
if (v > 0) {
this.reportInfo.isHealth = true
}
healthName = healthName + this.dict.getLabel('epidemicRecentHealth', v)
})
this.reportInfo.healthName = healthName
this.isShow = true
}
})
},
getList () {
this.instance.post(`/app/appepidemichealthreport/list`, null, {
params: {
...this.search,
memberId: this.params.id
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
cancel (isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: !!isRefresh
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -1,293 +0,0 @@
<template>
<ai-list class="list">
<ai-title slot="title" title="健康上报" v-if="search.areaId" isShowBottomBorder :instance="instance" :disabledLevel="disabledLevel" isShowArea v-model="search.areaId" @change="changeArea"></ai-title>
<template slot="content">
<div class="statistics-top">
<div class="statistics-top__item">
<span>上报人数</span>
<h2 style="color: #2266FF;">{{ info.total }}</h2>
</div>
<div class="statistics-top__item">
<span>今日上报</span>
<h2 style="color: #22AA99;">{{ info.today }}</h2>
</div>
<div class="statistics-top__item">
<span>今日未报</span>
<h2 style="color: #F8B425">{{ info.unReport }}</h2>
</div>
<div class="statistics-top__item">
<span>今日异常</span>
<h2 style="color: red">{{ info.unusual }}</h2>
</div>
</div>
<div class="content">
<ai-search-bar bottomBorder>
<template #left>
<ai-select
v-model="search.checkResult"
clearable
placeholder="请选择检查结果"
:selectList="dict.getDict('epidemicRecentTestResult')"
@change="search.current = 1, getList()">
</ai-select>
<ai-select
v-model="search.today"
clearable
placeholder="今日是否上报"
:selectList="dictList"
@change="search.current = 1, getList()">
</ai-select>
<ai-download :instance="instance" url="/app/appepidemicreportmember/export" :params="search" fileName="健康上报" :disabled="tableData.length == 0">
<el-button icon="iconfont iconExported" :disabled="tableData.length == 0">导出</el-button>
</ai-download>
</template>
<template #right>
<el-input
v-model="search.name"
size="small"
placeholder="请输入姓名"
clearable
v-throttle="() => {search.current = 1, getList()}"
@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"
v-loading="loading"
style="margin-top: 16px;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="healthCode" align="center" label="健康码">
<template slot-scope="{ row }">
<ai-uploader
v-if="row.healthCode"
:instance="instance"
:value="JSON.parse(row.healthCode)"
disabled
:limit="1">
</ai-uploader>
</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="toDetail(row.id)">详情</el-button>
<el-button type="text" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</div>
</template>
</ai-list>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'List',
props: {
instance: Function,
dict: Object
},
data () {
return {
search: {
current: 1,
size: 10,
name: '',
checkResult: '',
areaId: '',
today: ''
},
dictList: [{
dictName: '否',
dictValue: '0'
}, {
dictName: '是',
dictValue: '1'
}],
info: {},
colConfigs: [
{ prop: 'name', label: '姓名' },
{ prop: 'phone', align: 'center', label: '手机号码' },
{ prop: 'areaName', align: 'center', label: '所属地区', width: '200px' },
{ prop: 'reportTime', align: 'center', label: '上报时间', width: '200px' },
{
prop: 'vaccine',
align: 'center',
label: '已接种情况',
render: (h, {row}) => {
return h('span', {
style: {
}
}, row.today === '0' ? '-' : (row.vaccine || 0 + '次'))
}
},
{ prop: 'healthCode', align: 'center', label: '健康码', format: v => v ? this.dict.getLabel('epidemicHealthCode', v) : '-' },
{ prop: 'checkTime', align: 'center', label: '核酸日期', format: v => v ? v.split(' ')[0] : '-' },
{ prop: 'checkResult', align: 'center', label: '检测结果', format: v => v ? this.dict.getLabel('epidemicRecentTestResult', v) : '-' },
{
prop: 'status',
align: 'center',
label: '健康状态',
render: (h, {row}) => {
return h('span', {
style: {
color: row.status === '0' ? 'red' : '#333'
}
}, row.today === '0' ? '-' : (row.status === '0' ? '异常' : '正常'))
}
},
{ prop: 'today', align: 'center', label: '今日上报', format: v => v === '0' ? '未上报' : '已上报' },
],
tableData: [],
total: 0,
loading: false,
disabledLevel: 0
}
},
computed: {
...mapState(['user']),
param () {
return {
}
}
},
created () {
this.disabledLevel = this.user.info.areaList.length - 1
this.search.areaId = this.user.info.areaId
this.loading = true
this.dict.load(['epidemicTouchInFourteen', 'epidemicRecentHealth', 'epidemicRecentTestResult', 'epidemicHealthCode', 'epidemicVaccineTime']).then(() => {
this.getList()
})
},
methods: {
getList () {
this.instance.post(`/app/appepidemicreportmember/list`, 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
})
this.getTotalInfo()
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appepidemicreportmember/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getTotalInfo()
this.getList()
}
})
})
},
toDetail (id) {
this.$emit('change', {
type: 'Detail',
params: {
id: id || ''
}
})
},
changeArea () {
this.search.current = 1
this.$nextTick(() => {
this.getList()
this.getTotalInfo()
})
},
getTotalInfo () {
this.instance.post(`/app/appepidemicreportmember/statistic`, null, {
params: {
areaId: this.search.areaId
}
}).then(res => {
if (res.code == 0) {
this.info = res.data
}
})
}
}
}
</script>
<style scoped lang="scss">
.list {
:deep( .ai-list__content ){
padding: 0!important;
.ai-list__content--right-wrapper {
background: transparent!important;
box-shadow: none!important;
margin: 0!important;
padding: 12px 16px 12px!important;
}
}
.statistics-top {
display: flex;
align-items: center;
margin-bottom: 20px;
& > div {
flex: 1;
height: 96px;
line-height: 1;
margin-right: 20px;
padding: 16px 24px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 4px;
&:last-child {
margin-right: 0;
}
h3 {
font-size: 24px;
}
span {
display: block;
margin-bottom: 16px;
color: #888888;
font-size: 16px;
}
}
}
.content {
padding: 16px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
}
}
</style>

View File

@@ -1,105 +0,0 @@
<template>
<ai-list class="AppReturnHomeRegister" v-if="!isShowDetail">
<template slot="title">
<ai-title title="返乡登记" :isShowBottomBorder="false" :fullname.sync="areaName" v-model="areaId" :instance="instance" @change="onAreaChange"></ai-title>
</template>
<template slot="tabs">
<el-tabs v-model="currIndex">
<el-tab-pane v-for="(tab,i) in tabs" :key="i" :label="tab.label">
<component :areaId="areaId" :ref="String(i)" v-if="currIndex == i" :is="tab.comp" @change="onChange" lazy :instance="instance" :dict="dict" :permissions="permissions"/>
</el-tab-pane>
</el-tabs>
</template>
</ai-list>
<Detail v-else-if="component === 'Detail'" :params="params" :instance="instance" :dict="dict" :permissions="permissions" @change="onChange"></Detail>
</template>
<script>
import List from './components/List.vue'
import Detail from './components/Detail.vue'
import AbnormalList from './components/AbnormalList.vue'
import { mapState } from 'vuex'
export default {
name: 'AppReturnHomeRegister',
label: '返乡登记',
components: {
List,
Detail,
AbnormalList
},
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
...mapState(['user']),
tabs () {
const tabList = [
{label: '返乡登记', name: 'List', comp: List, permission: ''},
// {label: '历史异常人员', name: 'AbnormalList', comp: AbnormalList, permission: ''}
]
return tabList
}
},
data () {
return {
activeName: 'JoinEvent',
currIndex: '0',
component: 'List',
params: {},
areaId: '',
isShowDetail: false,
areaName: ''
}
},
created () {
this.areaId = this.user.info.areaId
},
methods: {
onChange (data) {
if (data.type === 'Detail') {
this.component = 'Detail'
this.isShowDetail = true
this.params = data.params
}
if (data.type === 'AbnormalList') {
this.component = 'AbnormalList'
this.isShowDetail = false
this.params = data.params
}
if (data.type === 'List') {
this.component = 'List'
this.isShowDetail = false
this.params = data.params
this.$nextTick(() => {
if (data.isRefresh) {
this.$refs.component.getList()
}
})
}
},
onAreaChange () {
this.$refs[this.currIndex][0].changeArea()
}
}
}
</script>
<style lang="scss" scoped>
.AppReturnHomeRegister {
height: 100%;
}
</style>

View File

@@ -1,200 +0,0 @@
<template>
<ai-list isTabs>
<template slot="content">
<ai-search-bar bottomBorder>
<template #left>
<ai-select
v-model="search.status"
clearable
placeholder="请选择健康状态"
:selectList="dictList"
@change="search.current = 1, getList()">
</ai-select>
<ai-download :instance="instance" url="/app/appepidemicbackhomerecord/export" :params="param" fileName="返乡登记" :disabled="tableData.length == 0">
<el-button icon="iconfont iconExported" :disabled="tableData.length == 0">导出</el-button>
</ai-download>
</template>
<template #right>
<el-input
v-model="search.name"
size="small"
placeholder="请输入姓名"
clearable
v-throttle="() => {search.current = 1, getList()}"
@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"
v-loading="loading"
style="margin-top: 16px;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<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="toDetail(row.id)">详情</el-button>
<el-button type="text" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'AbnormalList',
props: {
instance: Function,
dict: Object
},
data () {
return {
search: {
current: 1,
size: 10,
name: '',
arriveAreaId: '',
status: ''
},
dictList: [{
dictName: '异常',
dictValue: '0'
}, {
dictName: '正常',
dictValue: '1'
}],
info: {},
colConfigs: [
{ prop: 'name', label: '姓名' },
{ prop: 'phone', align: 'center', label: '手机号码' },
{ prop: 'startTime', align: 'center', label: '出发时间', format: v => v.substr(0, v.length - 3) },
{
prop: 'startAreaName',
align: 'center',
label: '出发地区'
},
{ prop: 'arriveTime', align: 'center', label: '到达时间', format: v => v.substr(0, v.length - 3) },
{
prop: 'arriveAreaName',
align: 'center',
label: '到达地区'
},
{ prop: 'checkTime', align: 'center', label: '核酸日期', format: v => v.split(' ')[0] },
{
prop: 'status',
align: 'center',
label: '健康状态',
render: (h, {row}) => {
return h('span', {
style: {
color: row.status === '0' ? 'red' : '#333'
}
}, row.status === '0' ? '异常' : '正常')
}
}
],
ids: [],
tableData: [],
total: 0,
loading: false,
disabledLevel: 0
}
},
computed: {
...mapState(['user']),
param () {
return this.search
}
},
created () {
this.disabledLevel = this.user.info.areaList.length - 1
this.search.arriveAreaId = this.user.info.areaId
this.loading = true
this.dict.load(['marriageType', 'marriagePersonType', 'modeType']).then(() => {
this.getList()
})
},
methods: {
getList () {
this.instance.post(`/app/appepidemicbackhomerecord/list`, 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
})
this.getTotalInfo()
},
toDetail (id) {
this.$emit('change', {
type: 'Detail',
params: {
id: id || ''
}
})
},
changeArea () {
this.search.current = 1
this.$nextTick(() => {
this.getList()
this.getTotalInfo()
})
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appepidemicbackhomerecord/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getTotalInfo()
this.getList()
}
})
})
},
getTotalInfo () {
this.instance.post(`/app/appepidemicbackhomerecord/statistic`, null, {
params: {
areaId: this.search.arriveAreaId
}
}).then(res => {
if (res.code == 0) {
this.info = res.data
}
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -1,279 +0,0 @@
<template>
<ai-detail isHasSidebar>
<template slot="title">
<ai-title title="返乡登记详情" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</template>
<template slot="content">
<AiSidebar :tabTitle="tabList" v-model="currIndex"></AiSidebar>
<div v-show="currIndex === 0">
<ai-card title="基本信息" v-show="currIndex === 0">
<template #content>
<ai-wrapper
label-width="120px">
<ai-info-item label="姓名" :value="info.name"></ai-info-item>
<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>
<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>
</ai-card>
<ai-card title="行程信息">
<template #content>
<ai-wrapper
label-width="120px">
<ai-info-item label="出发时间" :value="info.startTime"></ai-info-item>
<ai-info-item label="出发地区" >
<span :style="{color: info.denger == 1 ? '#FF4466' : '#333'}">{{info.startAreaName}} </span>
</ai-info-item>
<ai-info-item label="出发地址" isLine :value="info.startAddress"></ai-info-item>
<ai-info-item label="出行方式" :value="dict.getLabel('epidemicRecentTravel', info.travelType)"></ai-info-item>
<ai-info-item label="行程描述" isLine :value="info.description"></ai-info-item>
<ai-info-item label="到达时间" :value="info.arriveTime"></ai-info-item>
<ai-info-item label="到达地区" :value="info.arriveAreaName"></ai-info-item>
<ai-info-item label="返乡地址" isLine :value="info.arriveAddress"></ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="健康状况">
<template #content>
<ai-wrapper
label-width="120px">
<ai-info-item label="当前体温">
<span :style="info.temperature >= 37.3 ? 'color:#f46;' : ''">{{ info.temperature + '℃' }}</span>
</ai-info-item>
<ai-info-item label="14天内是否接触新冠确诊或疑似患者">
<span :class="'color-'+info.touchInFourteen">{{$dict.getLabel('epidemicTouchInFourteen', info.touchInFourteen)}}</span>
</ai-info-item>
<ai-info-item label="当前健康状况">
<span></span>
<span v-for="(item, index) in info.health" :key="index" :style="item != 0 ? 'color:#FF4466;' : ''"><span v-if="index>0">;</span>{{$dict.getLabel('epidemicRecentHealth', item)}}</span>
</ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="核酸检测">
<template #content>
<ai-wrapper
label-width="120px">
<ai-info-item label="检测日期" :value="info.checkTime && info.checkTime.split(' ')[0]"></ai-info-item>
<ai-info-item label="检测结果">
<span :style="info.checkResult == 1 ? 'color:#f46;' : 'color:#42D784;'">{{$dict.getLabel('epidemicRecentTestResult', info.checkResult)}}</span>
</ai-info-item>
<ai-info-item label="本人健康码截图" isLine>
<ai-uploader
:instance="instance"
v-model="info.checkPhoto"
disabled
:limit="9">
</ai-uploader>
</ai-info-item>
</ai-wrapper>
</template>
</ai-card>
</div>
<div v-show="currIndex === 1">
<ai-card title="异常处理">
<template #right>
<el-button type="primary" v-if="info.status === '0'" @click="release">解除异常</el-button>
</template>
<template #content>
<ai-wrapper
label-width="120px">
<ai-info-item label="姓名" :value="info.name"></ai-info-item>
<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('epidemicRecentPersonType', info.type)"></ai-info-item>
<ai-info-item label="异常状况" isLine>
<span :style="{color: info.unusual ? 'red' : '#333'}">{{ info.unusual || '-' }}</span>
</ai-info-item>
<ai-info-item label="异常解除人" v-if="info.releaseName && info.status === '1'" :value="info.releaseName"></ai-info-item>
<ai-info-item label="异常解除时间" v-if="info.releaseTime && info.status === '1'" :value="info.releaseTime"></ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="异常情况记录">
<template #right>
<el-button type="primary" v-if="info.status === '0'" @click="isShow = true">添加记录</el-button>
</template>
<template #content>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="options" width="120px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<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="800px"
@close="form.content = ''"
title="添加异常记录"
@onConfirm="onConfirm">
<el-form class="ai-form" label-width="120px" :model="form" ref="form">
<el-form-item label="异常记录" prop="content" style="width: 100%;" :rules="[{ required: true, message: '请输入异常记录' }]">
<el-input type="textarea" :rows="5" :maxlength="500" v-model="form.content" clearable placeholder="请输入异常记录" show-word-limit></el-input>
</el-form-item>
</el-form>
</ai-dialog>
</div>
</template>
</ai-detail>
</template>
<script>
export default {
name: 'Detail',
props: {
instance: Function,
dict: Object,
params: Object
},
data () {
return {
total: 0,
info: {},
id: '',
search: {
current: 1,
size: 10
},
form: {
content: ''
},
isShow: false,
currIndex: 0,
tableData: [],
colConfigs: [
{prop: 'content', label: '异常记录', align: 'center' },
{prop: 'createTime', label: '创建时间', align: 'center'},
{prop: 'createUserName', label: '记录人', align: 'center' }
],
tabList: ['基本信息', '异常处理']
}
},
created () {
if (this.params && this.params.id) {
this.id = this.params.id
this.$dict.load(['epidemicRecentHealth', 'epidemicRecentTravel', 'epidemicTouchInFourteen', 'epidemicRecentPersonType', 'epidemicRecentTestResult']).then(() => {
this.getInfo(this.params.id)
})
this.getList()
}
},
methods: {
getInfo (id) {
this.instance.post(`/app/appepidemicbackhomerecord/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.info = res.data
this.info.checkPhoto = res.data.checkPhoto ? JSON.parse(res.data.checkPhoto) : []
let healthName = ''
this.info.isHealth = false
res.data.health.split(',').forEach(v => {
if (v > 0) {
this.info.isHealth = true
}
healthName = healthName + this.$dict.getLabel('epidemicRecentHealth', v)
})
this.info.healthName = healthName
this.info.health = this.info.health.split(',')
}
})
},
release () {
this.$confirm('确定解除异常?').then(() => {
this.instance.post(`/app/appepidemicbackhomerecord/release?recordId=${this.params.id}`, {
id: this.params.id
}).then(res => {
if (res.code == 0) {
this.$message.success('解除异常成功!')
this.currIndex = 0
this.getInfo(this.params.id)
}
})
})
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appepidemicunusuallog/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
onConfirm() {
this.$refs.form.validate(v => {
if (v) {
this.instance.post('/app/appepidemicunusuallog/addOrUpdate', {
...this.form,
recordId: this.params.id
}).then(res => {
if (res?.code == 0) {
this.isShow = false
this.getList()
this.$message.success('添加成功!')
}
})
}
})
},
getList () {
this.instance.post(`/app/appepidemicunusuallog/list`, null, {
params: {
...this.search,
recordId: this.params.id
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
cancel () {
this.$emit('change', {
type: 'List',
isRefresh: true
})
}
}
}
</script>
<style scoped lang="scss">
.color-0{
color: #42D784;
}
.color-1{
color: #f46;
}
.color-2{
color: #1365DD;
}
</style>

View File

@@ -1,273 +0,0 @@
<template>
<ai-list class="list" isTabs>
<template slot="content">
<div class="statistics-top">
<div class="statistics-top__item">
<span>返乡人数</span>
<h2 style="color: #2266FF;">{{ info.total }}</h2>
</div>
<div class="statistics-top__item">
<span>今日新增返乡</span>
<h2 style="color: #22AA99;">{{ info.today }}</h2>
</div>
<div class="statistics-top__item">
<span>异常人数</span>
<h2 style="color: #F8B425">{{ info.unusual }}</h2>
</div>
<div class="statistics-top__item">
<span>今日异常人数</span>
<h2 style="color: red">{{ info.todayUnusual }}</h2>
</div>
<div class="statistics-top__item">
<span>异常处理</span>
<h2 style="color: red">{{ info.release }}</h2>
</div>
</div>
<div class="content">
<ai-search-bar bottomBorder>
<template #left>
<ai-select
v-model="search.status"
clearable
placeholder="请选择健康状态"
:selectList="dictList"
@change="search.current = 1, getList()">
</ai-select>
<ai-download :instance="instance" url="/app/appepidemicbackhomerecord/export" :params="param" fileName="返乡登记" :disabled="tableData.length == 0">
<el-button icon="iconfont iconExported" :disabled="tableData.length == 0">导出</el-button>
</ai-download>
</template>
<template #right>
<el-input
v-model="search.name"
size="small"
placeholder="请输入姓名"
clearable
v-throttle="() => {search.current = 1, getList()}"
@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"
v-loading="loading"
style="margin-top: 16px;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<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="toDetail(row.id)">详情</el-button>
<el-button type="text" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</div>
</template>
</ai-list>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'List',
props: {
instance: Function,
dict: Object,
areaId: String
},
data () {
return {
search: {
current: 1,
size: 10,
name: '',
status: ''
},
dictList: [{
dictName: '异常',
dictValue: '0'
}, {
dictName: '正常',
dictValue: '1'
}],
info: {},
colConfigs: [
{ prop: 'name', label: '姓名' },
{ prop: 'phone', align: 'center', label: '手机号码' },
{ prop: 'startTime', align: 'center', label: '出发时间', format: v => v.substr(0, v.length - 3) },
{
prop: 'startAreaName',
align: 'center',
label: '出发地区'
},
{ prop: 'arriveTime', align: 'center', label: '到达时间', format: v => v.substr(0, v.length - 3) },
{
prop: 'arriveAreaName',
align: 'center',
label: '到达地区'
},
{ prop: 'checkTime', align: 'center', label: '核酸日期', format: v => v.split(' ')[0] },
{
prop: 'status',
align: 'center',
label: '健康状态',
render: (h, {row}) => {
return h('span', {
style: {
color: row.status === '0' ? 'red' : '#333'
}
}, row.status === '0' ? '异常' : '正常')
}
}
],
ids: [],
tableData: [],
total: 0,
loading: false,
disabledLevel: 0
}
},
computed: {
...mapState(['user']),
param () {
return this.search
}
},
created () {
this.disabledLevel = this.user.info.areaList.length - 1
this.loading = true
this.dict.load(['marriageType', 'marriagePersonType', 'modeType']).then(() => {
this.getList()
})
},
methods: {
getList () {
this.instance.post(`/app/appepidemicbackhomerecord/list`, null, {
params: {
...this.search,
arriveAreaId: this.areaId
}
}).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
})
this.getTotalInfo()
},
toDetail (id) {
this.$emit('change', {
type: 'Detail',
params: {
id: id || ''
}
})
},
changeArea () {
this.search.current = 1
this.$nextTick(() => {
this.getList()
this.getTotalInfo()
})
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appepidemicbackhomerecord/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getTotalInfo()
this.getList()
}
})
})
},
getTotalInfo () {
this.instance.post(`/app/appepidemicbackhomerecord/statistic`, null, {
params: {
areaId: this.search.arriveAreaId
}
}).then(res => {
if (res.code == 0) {
this.info = res.data
}
})
}
}
}
</script>
<style scoped lang="scss">
.list {
: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;
}
}
.statistics-top {
display: flex;
align-items: center;
margin-bottom: 20px;
& > div {
flex: 1;
height: 96px;
line-height: 1;
margin-right: 20px;
padding: 16px 24px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 4px;
&:last-child {
margin-right: 0;
}
h3 {
font-size: 24px;
}
span {
display: block;
margin-bottom: 16px;
color: #888888;
font-size: 16px;
}
}
}
.content {
padding: 16px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
}
}
</style>

View File

@@ -1,65 +0,0 @@
<template>
<div class="doc-circulation ailist-wrapper">
<keep-alive :include="['List']">
<component ref="component" :moduleName="moduleName" :moduleId="moduleId" :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'
export default {
name: 'AppRiskArea',
label: '风险配置',
props: {
instance: Function,
dict: Object
},
data () {
return {
component: 'List',
params: {},
moduleId: '',
include: [],
moduleName: ''
}
},
components: {
Add,
List
},
methods: {
onChange (data) {
if (data.type === 'Add') {
this.component = 'Add'
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">
.doc-circulation {
height: 100%;
background: #F3F6F9;
overflow: auto;
}
</style>

View File

@@ -1,154 +0,0 @@
<template>
<ai-detail class="content-add">
<template slot="title">
<ai-title :title="params.id ? '编辑风险区域' : '添加风险区域'" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</template>
<template slot="content">
<ai-card title="基本信息">
<template #content>
<el-form class="ai-form" :model="form" label-width="120px" ref="form">
<el-form-item prop="areaId" style="width: 100%;" label="选择地区" :rules="[{required: true, message: '请选择地区', trigger: 'change'}]">
<ai-area-select clearable @fullname="v => form.areaName = v" always-show :instance="instance" v-model="form.areaId"></ai-area-select>
</el-form-item>
<el-form-item label="风险等级" style="width: 100%;" prop="level" :rules="[{required: true, message: '请选择风险等级', trigger: 'change'}]">
<ai-select
v-model="form.level"
clearable
placeholder="请选择风险等级"
:selectList="dict.getDict('epidemicDangerousAreaLevel')">
</ai-select>
</el-form-item>
</el-form>
</template>
</ai-card>
</template>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="confirm">提交</el-button>
</template>
</ai-detail>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'Add',
props: {
instance: Function,
dict: Object,
params: Object,
moduleName: String
},
data () {
return {
info: {},
form: {
level: '',
areaId: '',
areaName: ''
},
id: ''
}
},
computed: {
...mapState(['user'])
},
created () {
this.dict.load('epidemicDangerousAreaLevel').then(() => {
if (this.params && this.params.id) {
this.id = this.params.id
this.getInfo(this.params.id)
}
})
},
methods: {
getInfo (id) {
this.instance.post(`/app/appepidemicdangerousarea/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.form = res.data
}
})
},
confirm () {
this.$refs.form.validate((valid) => {
if (valid) {
this.instance.post(`/app/appepidemicdangerousarea/addOrUpdate`, {
...this.form
}).then(res => {
if (res.code == 0) {
this.$message.success('提交成功')
setTimeout(() => {
this.cancel(true)
}, 600)
}
})
}
})
},
cancel (isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: !!isRefresh
})
}
}
}
</script>
<style scoped lang="scss">
.content-add {
.video {
width: 640px;
height: 360px;
border-radius: 4px;
border: 1px dashed #D0D4DC;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
span:nth-child(2) {
display: inline-block;
font-size: 16px;
color: #333333;
line-height: 30px;
}
.iconfont {
display: inline-block;
font-size: 40px;
color: #2266FF;
}
}
.tips {
display: inline-block;
font-size: 12px;
color: #999999;
line-height: 26px;
}
}
.video-com {
width: 640px;
height: 360px;
background: rgba(0, 0, 0, 0.5);
border-radius: 2px;
border: 1px solid #D0D4DC;
margin-top: -40px;
}
}
</style>

View File

@@ -1,141 +0,0 @@
<template>
<ai-list class="notice">
<template slot="title">
<ai-title title="风险区域配置" isShowBottomBorder></ai-title>
</template>
<template slot="content">
<ai-search-bar class="search-bar">
<template #left>
<ai-select
v-model="search.level"
clearable
placeholder="请选择风险等级"
:selectList="dict.getDict('epidemicDangerousAreaLevel')"
@change="search.current = 1, getList()">
</ai-select>
<el-button size="small" type="primary" icon="iconfont iconAdd" @click="toAdd('')">添加</el-button>
</template>
<template #right>
<el-input
v-model="search.province"
class="search-input"
size="small"
v-throttle="() => {search.current = 1, getList()}"
placeholder="省级名称/市级名称/区级名称"
clearable
@clear="search.current = 1, search.province = '', getList()"
suffix-icon="iconfont iconSearch">
</el-input>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 6px;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<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="toAdd(row.id)">编辑</el-button>
<el-button type="text" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'List',
props: {
instance: Function,
dict: Object,
moduleName: String
},
data() {
return {
search: {
current: 1,
size: 10,
level: '',
province: ''
},
currIndex: -1,
areaList: [],
total: 10,
colConfigs: [
{ prop: 'province', label: '省级', align: 'left', width: '200px' },
{ prop: 'city', label: '市级', align: 'center' },
{ prop: 'district', label: '区级', align: 'center' },
{ prop: 'town', label: '镇级', align: 'center' },
{ prop: 'village', label: '村级', align: 'center' },
{ prop: 'level', label: '等级', align: 'center', format: v => this.dict.getLabel('epidemicDangerousAreaLevel', v) },
{ prop: 'createTime', label: '设置时间', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ slot: 'options', label: '操作', align: 'center' }
],
areaName: '',
unitName: '',
tableData: []
}
},
computed: {
...mapState(['user'])
},
created() {
this.dict.load('epidemicDangerousAreaLevel').then(() => {
this.getList()
})
},
methods: {
getList() {
this.instance.post(`/app/appepidemicdangerousarea/list`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appepidemicdangerousarea/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toAdd(id) {
this.$emit('change', {
type: 'Add',
params: {
id: id || ''
}
})
}
}
}
</script>
<style lang="scss" scoped>
.notice {
}
</style>

View File

@@ -1,206 +0,0 @@
<template>
<section class="AppVaccination">
<ai-list v-if="!showDetail">
<ai-title slot="title" title="疫苗接种" isShowBottomBorder isShowArea v-model="areaId" :instance="instance"
@change="page.current=1,getTableData()"/>
<template #blank>
<el-row type="flex">
<div class="dataPane" v-for="(op,i) in dataPanes" :key="i">
{{ [op.label, op.v].join(" ") }}
</div>
</el-row>
<div class="mainPane">
<ai-search-bar>
<template #left>
<ai-select placeholder="接种状况" v-model="search.inoculationType" @change="page.current=1,getTableData()"
:selectList="dict.getDict('vaccineInoculationType')"/>
</template>
<template #right>
<el-input placeholder="姓名/身份证/联系方式"
v-model="search.name"
size="small"
clearable
@clear="page.current = 1,search.name = '', getTableData()"
v-throttle="() => {page.current = 1, getTableData()}"
suffix-icon="iconfont iconSearch"/>
</template>
</ai-search-bar>
<ai-search-bar>
<template #left>
<el-button type="primary" icon="iconfont iconAdd" @click="$router.push({hash:'#add'})">添加</el-button>
<el-button icon="iconfont iconDelete" :disabled="!ids.length" @click="handleDelete(ids)">删除</el-button>
</template>
<template #right>
<ai-import :instance="instance" :dict="dict" name="疫苗接种" suffixName="xlsx"
type="appvaccineinoculationuser" @onSuccess="resetSearch"/>
<ai-download url="/app/appvaccineinoculationuser/export" :params="{...search,areaId,ids:ids.toString()}"
:instance="instance" fileName="疫苗接种导出文件"/>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :colConfigs="colConfigs" :total="page.total" :current.sync="page.current"
:size.sync="page.size" @getList="getTableData" :dict="dict"
@selection-change="v=>ids=v.map(e=>e.id)">
<el-table-column slot="vaccinationDate" label="接种日期" align="center" class-name="vaccinationDate">
<el-table-column label="第一次" align="center" prop="firstDate"/>
<el-table-column label="第二次" align="center" prop="secondDate"/>
<el-table-column label="第三次" align="center" prop="thirdDate"/>
</el-table-column>
<el-table-column slot="options" label="操作" align="center" fixed="right">
<template slot-scope="{row:{id}}">
<el-button type="text" @click="$router.push({hash:'#add',query:{id}})">编辑</el-button>
<el-button type="text" @click="handleDelete(id)">删除</el-button>
</template>
</el-table-column>
</ai-table>
</div>
</template>
</ai-list>
<add-vaccination v-else :dict="dict" :instance="instance"/>
</section>
</template>
<script>
import {mapState} from "vuex";
import AddVaccination from "./addVaccination";
import {ID} from "dui/lib/js/utils";
export default {
name: "AppVaccination",
components: {AddVaccination},
label: "疫苗接种",
provide() {
return {
top: this
}
},
props: {
instance: Function,
dict: Object,
permissions: Function,
},
data() {
return {
areaId: "",
page: {current: 1, size: 10, total: 0},
search: {inoculationType: "", name: ""},
ids: [],
tableData: [],
staData: {}
}
},
computed: {
...mapState(['user']),
showDetail() {
return this.$route.hash == "#add"
},
dataPanes() {
return [
{label: "总接种人数", v: this.staData.zjzrs || 0},
{label: "已接种第一针人数", v: this.staData.yjzdyzrs || 0},
{label: "已接种第二针人数", v: this.staData.yjzdezrs || 0},
{label: "已接种第三针人数", v: this.staData.yjzdszrs || 0},
]
},
colConfigs() {
return [
{type: "selection", align: 'center'},
{label: "姓名", prop: "name", align: 'center'},
{label: "性别", prop: "sex", dict: 'sex', align: 'center'},
{label: "出生日期", prop: "birthday", align: 'center'},
{
label: "身份证号", width: "160px", align: 'center',
render: (h, {row}) => h('span', null, ID.hideId(row.idNumber))
},
{label: "所属地区", prop: "areaName", align: 'center'},
{label: "住址", prop: "address", width: "200px", align: 'center'},
{label: "联系方式", prop: "phone", align: 'center'},
{slot: 'vaccinationDate'},
{slot: "options"},
]
}
},
created() {
this.areaId = JSON.parse(JSON.stringify(this.user.info.areaId))
this.dict.load('sex', 'vaccineInoculationType')
this.getTableData()
},
methods: {
getStaData() {
this.instance.post(`/app/appvaccineinoculationuser/countByAreaId`, null, {
params: {areaId: this.areaId}
}).then(res => {
if (res?.data) {
this.staData = res.data
}
})
},
getTableData() {
this.page.current == 1 && this.getStaData()
this.instance.post(`/app/appvaccineinoculationuser/list`, null, {
params: {...this.search, ...this.page, areaId: this.areaId}
}).then(res => {
if (res?.data) {
this.tableData = res.data.records
this.page.total = res.data.total
}
})
},
handleDelete(ids) {
ids = ids?.toString()
this.$confirm("确定要删除该条数据吗?").then(() => {
this.instance.post(`/app/appvaccineinoculationuser/delete`, null, {
params: {ids}
}).then(res => {
if (res?.code == 0) {
this.$message.success("删除成功!");
this.getTableData();
}
})
}).catch(() => 0)
},
resetSearch() {
this.page.current = 1
this.search = {}
this.getTableData()
}
}
}
</script>
<style lang="scss" scoped>
.AppVaccination {
height: 100%;
:deep( .dataPane ){
flex: 1;
min-width: 0;
background: #FFFFFF;
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 2px;
margin-bottom: 16px;
margin-right: 16px;
display: flex;
align-items: center;
justify-content: center;
height: 60px;
&:last-of-type {
margin-right: 0;
}
}
:deep( .mainPane ){
background: #fff;
padding: 12px 16px;
box-shadow: 0 4px 6px -2px rgba(15, 15, 21, 0.15);
.vaccinationDate {
border-bottom: 1px solid #D0D4DC;
}
.ai-table__header {
padding: 2px 0;
}
}
}
</style>

View File

@@ -1,236 +0,0 @@
<template>
<section class="addVaccination">
<ai-detail>
<ai-title slot="title" :title="addTitle" isShowBottomBorder
isShowBack @onBackClick="back"/>
<template #content>
<el-form size="small" :model="form" ref="vaccinationForm" :rules="rules" label-width="100px">
<ai-card title="基本信息">
<template #content>
<el-form-item label="受种人姓名" prop="name">
<el-row type="flex" align="middle">
<el-input placeholder="请输入" v-model="form.name" :disabled="isEdit" clearable
style="margin-right: 8px"/>
<ai-person-select v-if="!isEdit" :instance="instance" @selectPerson="handleSelectPerson"/>
</el-row>
</el-form-item>
<el-form-item label="身份证号码" prop="idNumber">
<ai-id v-model="form.idNumber" @change="getInfoByIdNumber" :disabled="isEdit"/>
</el-form-item>
<el-form-item label="性别" prop="sex">
<ai-select disabled v-model="form.sex" :selectList="dict.getDict('sex')"/>
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker v-model="form.birthday" type="date" disabled/>
</el-form-item>
<el-form-item label="联系方式" prop="phone">
<el-input v-model="form.phone" placeholder="请输入"/>
</el-form-item>
<el-form-item label="所属地区" isLine prop="areaId">
<ai-area-select :instance="instance" v-model="form.areaId" always-show @name="v=>form.areaName=v"/>
</el-form-item>
<el-form-item label="住址" isLine prop="address">
<el-input v-model="form.address" placeholder="请输入"/>
</el-form-item>
</template>
</ai-card>
<ai-card title="接种情况">
<template #right>
<el-button icon="iconfont iconAdd" type="text" @click="dialog=true">添加</el-button>
</template>
<template #content>
<el-form-item isLine label-width="0">
<ai-table :tableData="form.detailList" :colConfigs="colConfigs" :isShowPagination="false" :dict="dict">
<el-table-column slot="options" label="操作" align="center" fixed="right">
<template v-slot="{row,$index}">
<el-button type="text" @click="handleEdit(row,$index)">编辑</el-button>
<el-button type="text" @click="handleDelete($index)">删除</el-button>
</template>
</el-table-column>
</ai-table>
</el-form-item>
</template>
</ai-card>
</el-form>
</template>
<template #footer>
<el-button @click="back">取消</el-button>
<el-button type="primary" @click="handleSubmit">提交</el-button>
</template>
</ai-detail>
<ai-dialog :visible.sync="dialog" title="接种情况" @closed="dialogForm={}" @onConfirm="handleConfirm">
<el-form ref="appvaccineinoculationuser" size="small" :model="dialogForm" label-width="100px" :rules="rules">
<el-form-item label="接种次数" prop="type">
<ai-select v-model="dialogForm.type" :selectList="dict.getDict('vaccineType')"/>
</el-form-item>
<el-form-item label="接种日期" prop="vaccinateDate">
<el-date-picker v-model="dialogForm.vaccinateDate" value-format="yyyy-MM-dd"/>
</el-form-item>
<el-form-item label="接种人员" prop="vaccinatePerson">
<el-input v-model="dialogForm.vaccinatePerson"/>
</el-form-item>
<el-form-item label="生产企业" prop="createCompany">
<el-input v-model="dialogForm.createCompany"/>
</el-form-item>
<el-form-item label="接种单位" prop="vaccinateUnit">
<el-input v-model="dialogForm.vaccinateUnit"/>
</el-form-item>
</el-form>
</ai-dialog>
</section>
</template>
<script>
import {mapState} from "vuex";
import {ID} from "dui/lib/js/utils";
export default {
name: "addVaccination",
inject: ['top'],
props: {
instance: Function,
dict: Object,
},
data() {
return {
form: {detailList: []},
dialog: false,
dialogForm: {}
}
},
computed: {
...mapState(['user']),
isEdit() {
return !!this.$route.query.id
},
addTitle() {
return this.isEdit ? '编辑疫苗接种人员' : '新增疫苗接种人员'
},
rules() {
return {
vaccinateDate: {required: true, message: "请选择 接种日期"},
type: {required: true, message: "请选择 接种次数",},
areaId: [
{required: true, message: "请选择 所属地区"},
{trigger:'blur',validator: (r, v, cb) => /0{3}$/g.test(v) ? cb('请选择到村/社区') : cb()}
],
name: {required: true, message: "请填写 受种人姓名"},
idNumber: {required: true, message: "请填写 身份号码"},
sex: {required: true, message: "请填写 性别"},
birthday: {required: true, message: "请填写 出生日期"},
phone: {required: true, message: "请填写 联系方式"},
}
},
colConfigs() {
return [
{label: "类型", align: 'center', prop: "type", dict: 'vaccineType'},
{label: "接种日期", align: 'center', prop: "vaccinateDate"},
{label: "接种人员", align: 'center', prop: "vaccinatePerson"},
{label: "生产企业", align: 'center', prop: "createCompany"},
{label: "接种单位", align: 'center', prop: "vaccinateUnit"},
{slot: "options"}
]
}
},
methods: {
back() {
this.$router.push({})
},
getDetail() {
let {id} = this.$route.query
if (id) {
this.instance.post("/app/appvaccineinoculationuser/queryDetailById", null, {
params: {id}
}).then(res => {
if (res?.data) {
this.form = res.data
}
})
} else {
this.$set(this.form,'areaId',JSON.parse(JSON.stringify(this.top.areaId)))
}
},
handleSubmit() {
this.$refs.vaccinationForm?.validate(v => {
if (v) {
this.instance.post("/app/appvaccineinoculationuser/addOrUpdate", this.form).then(res => {
if (res?.code == 0) {
this.$message.success("提交成功!")
this.top.resetSearch()
this.back()
}
})
}
})
},
handleEdit(row, index) {
this.dialogForm = JSON.parse(JSON.stringify({...row, index}))
this.dialog = true
},
handleConfirm() {
this.$refs.appvaccineinoculationuser.validate(v => {
if (v) {
if (this.dialogForm.index > -1) {
this.form.detailList.splice(this.dialogForm.index, 1, this.dialogForm)
} else {
this.form.detailList.push(this.dialogForm)
}
this.dialog = false
}
})
},
handleSelectPerson(v) {
let {name, idNumber, phone, currentAreaId:areaId, currentAddress:address} = v
this.form = {...this.form, name, idNumber, phone, areaId, address}
},
getInfoByIdNumber(code) {
if (ID.check(code)) {
let info = new ID(code)
this.form.sex = info.sex
this.form.birthday = info.birthday
this.$forceUpdate()
}
},
handleDelete(index) {
this.$confirm("是否要删除该条数据?").then(() => this.form.detailList.splice(index, 1)).catch(() => 0)
}
},
created() {
this.dict.load('vaccineType')
this.getDetail()
}
}
</script>
<style lang="scss" scoped>
.addVaccination {
height: 100%;
:deep( .ai-card__body), .el-form {
display: flex;
flex-wrap: wrap;
}
.ai-card {
width: 100%;
}
.el-form-item {
width: 50%;
&[isLine] {
width: 100%;
}
.el-date-editor {
width: 100%;
}
}
:deep( .el-button ){
.iconfont {
color: inherit
}
}
}
</style>

View File

@@ -1,66 +0,0 @@
<template>
<div class="doc-circulation ailist-wrapper">
<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'
export default {
name: 'AppAddressBook',
label: '通讯录管理',
props: {
instance: Function,
dict: Object
},
data () {
return {
component: 'List',
params: {},
include: []
}
},
components: {
Add,
List
},
mounted () {
},
methods: {
onChange (data) {
if (data.type === 'Add') {
this.component = 'Add'
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">
.doc-circulation {
height: 100%;
background: #F3F6F9;
overflow: auto;
}
</style>

View File

@@ -1,203 +0,0 @@
<template>
<ai-detail>
<template slot="title">
<ai-title :title="id ? '编辑成员' : '添加成员'" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</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" placeholder="请输入姓名" show-word-limit v-model="form.name" :maxlength="10"></el-input>
</el-form-item>
<el-form-item label="账号" prop="id" :rules="[{ required: true, message: '请输入账号', trigger: 'blur' }]">
<el-input size="small" :disabled="!!id" show-word-limit :maxlength="30" placeholder="成员唯一标识,设定以后不支持修改" v-model="form.id"></el-input>
</el-form-item>
<el-form-item label="手机号" prop="mobile" :rules="[{ required: true, validator: validatorPhone, trigger: 'blur' }]">
<el-input size="small" placeholder="请输入手机号" show-word-limit :maxlength="11" v-model="form.mobile"></el-input>
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="form.gender">
<el-radio label="1"></el-radio>
<el-radio label="2"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="座机" prop="telephone">
<el-input size="small" placeholder="请输入座机" v-model="form.telephone"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input size="small" placeholder="请输入邮箱" v-model="form.email"></el-input>
</el-form-item>
<el-form-item label="地址" style="width: 100%;" prop="address">
<el-input size="small" style="width: 100%;" show-word-limit :maxlength="30" placeholder="请输入地址" v-model="form.address"></el-input>
</el-form-item>
</div>
</template>
</ai-card>
<ai-card title="组织信息">
<template #content>
<el-form-item label="部门" prop="departmentName" style="width: 100%;" :rules="[{ required: true, message: '请选择部门', trigger: 'change' }]">
<el-input size="small" placeholder="请选择..." disabled v-model="form.departmentName">
<ai-wechat-selecter slot="append" isStrictly :instance="instance" @change="onChange" v-model="department" isChooseUnit>
<el-button type="info">选择</el-button>
</ai-wechat-selecter>
</el-input>
</el-form-item>
<el-form-item label="标签" style="width: 100%;" prop="tags">
<el-select size="small" v-model="form.tagIds" multiple placeholder="请选择标签">
<el-option
v-for="item in tagsList"
:key="item.id"
:label="item.tagname"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="职务" prop="position">
<el-input size="small" placeholder="请输入职务" v-model="form.position"></el-input>
</el-form-item>
</template>
</ai-card>
</el-form>
</template>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="confirm">提交</el-button>
</template>
</ai-detail>
</template>
<script>
export default {
name: 'Add',
props: {
instance: Function,
dict: Object,
params: Object
},
data () {
const validatorPhone = function (rule, value, callback) {
if (value === '') {
callback(new Error('请输入手机号'))
} else if (!/^1\d{10}$/.test(value)) {
callback(new Error('手机号格式错误'))
} else {
callback()
}
}
return {
info: {},
department: [],
validatorPhone: validatorPhone,
form: {
position: '',
name: '',
email: '',
telephone: '',
gender: '',
mobile: '',
departmentName: '',
departmentIds: [],
tagIds: [],
id: ''
},
id: '',
tagsList: []
}
},
created () {
this.getTags()
if (this.params && this.params.departmentId && !this.params.id) {
this.department = [{
id: String(this.params.departmentId),
name: this.params.departmentName
}]
this.form.departmentIds = [this.params.departmentId]
this.form.departmentName = this.params.departmentName
}
if (this.params && this.params.id) {
this.id = this.params.id
this.getInfo(this.params.id)
}
},
methods: {
getInfo (id) {
this.instance.post(`/app/wxcp/wxuser/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
const departmentNames = res.data.departmentNames.split(',')
this.department = res.data.departmentIdsStr.split(',').map((item, index) => {
return {
name: departmentNames[index],
id: item
}
})
this.form = {
...res.data,
departmentName: res.data.departmentNames,
tagIds: res.data.tags.map(v => v.id),
departmentIds: res.data.departmentIdsStr.split(',')
}
}
})
},
onChange (e) {
if (e.length) {
this.form.departmentIds = e.map(v => v.id)
this.form.departmentName = e.map(v => v.name).join(',')
} else {
this.form.departmentIds = ''
this.form.departmentName = ''
}
},
getTags () {
this.instance.post(`/app/wxcp/wxtag/listAll`).then(res => {
if (res.code == 0) {
this.tagsList = res.data
}
})
},
onClose () {
this.form.explain = ''
},
confirm () {
this.$refs.form.validate((valid) => {
if (valid) {
const api = this.id ? '/app/wxcp/wxuser/update' : '/app/wxcp/wxuser/add'
this.instance.post(api, {
...this.form
}).then(res => {
if (res.code == 0) {
this.$message.success('提交成功')
setTimeout(() => {
this.cancel(true)
}, 600)
}
})
}
})
},
cancel (isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: !!isRefresh
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -1,995 +0,0 @@
<template>
<ai-list class="addressBook">
<template slot="title">
<ai-title title="内部通讯录" isShowBottomBorder></ai-title>
</template>
<template #left>
<div class="addressBook-left">
<div class="addressBook-left__title">
<h2 @click="tabIndex = 0, search.current = 1, getList()" :class="[tabIndex === 0 ? 'tab-active' : '']">
组织架构</h2>
<h2 @click="tabIndex = 1, search.current = 1, getList()" :class="[tabIndex === 1 ? 'tab-active' : '']">标签</h2>
</div>
<div class="addressBook-left__list--title" v-if="tabIndex === 0">
<el-input
size="mini"
placeholder="请输入部门名称"
v-model="unitName"
clearable
suffix-icon="iconfont iconSearch">
</el-input>
</div>
<div class="addressBook-left__list--title" v-if="tabIndex === 1">
<el-button size="mini" icon="iconfont iconAdd" @click="isShowTags = true">添加标签</el-button>
<el-input
class="addressBook-left__list--search"
size="mini"
clearable
style="width: 154px;"
placeholder="请输入标签名称"
v-model="tagName"
suffix-icon="iconfont iconSearch">
</el-input>
</div>
<div class="addressBook-left__list--wrapper">
<div class="addressBook-left__list" v-show="tabIndex === 0">
<el-tree
:filter-node-method="filterNode"
ref="tree"
:props="defaultProps"
node-key="id"
:data="unitList"
highlight-current
@node-contextmenu="nodeContextmenu"
:current-node-key="search.departmentId"
:default-expanded-keys="defaultExpanded"
:default-checked-keys="defaultChecked"
@current-change="onTreeChange">
</el-tree>
<ul
v-if="isShowMenu"
class="el-dropdown-menu el-popper"
:style="{top: menuInfo.y + 'px', left: menuInfo.x + 'px', position: 'fixed', zIndex: 2023}"
x-placement="top-end">
<li class="el-dropdown-menu__item" @click="handleTreeCommand('add', menuInfo.node)">添加子部门</li>
<li class="el-dropdown-menu__item" @click="handleTreeCommand('edit', menuInfo.node)">修改名称</li>
<li class="el-dropdown-menu__item" @click="handleTreeCommand('remove', menuInfo.node)">删除</li>
<li class="el-dropdown-menu__item" :class="[!menuInfo.node.i ? 'is-disabled' : '']"
@click="handleTreeCommand('top', menuInfo.node)">上移
</li>
<li
class="el-dropdown-menu__item"
:class="[(menuInfo.node.i === menuInfo.node.len - 1) || (!menuInfo.node.i && menuInfo.node.i !== 0) ? 'is-disabled' : '']"
@click="handleTreeCommand('bottom', menuInfo.node)">下移
</li>
</ul>
</div>
<div class="addressBook-left__list" v-show="tabIndex === 1">
<div class="addressBook-left__tags">
<div
@click="changeTag(index)"
class="addressBook-left__tags--item"
:class="[currIndex === index ? 'addressBook-left__tags--item-active' : '']"
v-for="(item, index) in tagsList" :key="index">
<span>{{ item.tagname }}</span>
<el-dropdown @command="e => handleCommand(e, item)">
<i class="iconfont iconmore"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="edit">编辑</el-dropdown-item>
<el-dropdown-item command="remove">删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</div>
</div>
</div>
</template>
<template slot="content">
<ai-search-bar class="search-bar">
<template #left>
<el-button size="small" type="primary" icon="iconfont iconAdd" v-if="tabIndex === 0" @click="toAdd('')">添加成员
</el-button>
<ai-import :instance="instance" :dict="dict" v-if="tabIndex === 0" type="wxcp/wxuser" name="内部通讯录"
:importParams="{departmentId:search.departmentId}" @success="getList"/>
<el-button size="small" icon="iconfont iconUpdate_Files" v-if="tabIndex === 0" :loading="btnLoading" @click="syncMembers">同步部门</el-button>
<el-button size="small" icon="iconfont iconUpdate_Files" v-if="tabIndex === 0" :loading="btnLoading" @click="syncUser">同步成员</el-button>
<ai-wechat-selecter refs="addTags" :instance="instance" v-model="users" @change="onChooseUser"
:disabled="currIndex < 0" v-if="tabIndex === 1">
<el-button size="small" :disabled="currIndex < 0" type="primary" icon="iconfont iconAdd">添加成员</el-button>
</ai-wechat-selecter>
</template>
<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"
v-loading="loading"
style="margin-top: 6px;"
:current.sync="search.current"
:size.sync="search.size"
@handleSelectionChange="handleSelectionChange"
@getList="getList">
<el-table-column slot="avatar" label="" align="right" width="100px">
<template slot-scope="{ row }">
<img class="table-avatar" :src="row.avatar || 'https://cdn.cunwuyun.cn/dvcp/h5/defaultAvatar.png'">
</template>
</el-table-column>
<el-table-column slot="tags" label="标签" align="left">
<template slot-scope="{ row }">
<div class="table-tags" v-if="row.tagNames">
<el-tag type="info" v-for="(item, index) in row.tagNames.split('、')" size="small" :key="index">{{
item
}}
</el-tag>
</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="toAdd(row.id)" v-show="tabIndex === 0">编辑</el-button>
<el-button type="text" @click="remove(row.id)" v-show="tabIndex === 0">删除</el-button>
<el-button type="text" @click="removeTags(row.id)" v-show="tabIndex === 1">移除</el-button>
</div>
</template>
</el-table-column>
<div slot="paginationBtns" class="addressBook-table__btns">
<span style="margin-right: 8px;" @click="removeAll">{{ tabIndex === 0 ? '批量删除' : '批量移除' }}</span>
<ai-wechat-selecter :instance="instance" v-model="department" isChooseUnit @change="onDepartment"
v-if="tabIndex === 0">
<span>批量导出</span>
</ai-wechat-selecter>
</div>
</ai-table>
<ai-dialog
:visible.sync="isShowTags"
width="590px"
:title="tagId ? '编辑标签' : '添加标签'"
@close="onClose"
@onConfirm="onFormConfirm">
<el-form ref="tagForm" :model="tagForm" label-width="110px" label-position="right">
<el-form-item label="标签名称" prop="tagname" :rules="[{ required: true, message: '请输入标签名称', trigger: 'blur' }]">
<el-input size="small" placeholder="请输入标签名称" show-word-limit :maxlength="10" v-model="tagForm.tagname"></el-input>
</el-form-item>
</el-form>
</ai-dialog>
<ai-dialog
:visible.sync="isShowDepart"
width="590px"
:title="departId ? '修改名称' : '添加部门'"
@close="onClose"
@onConfirm="onDepartConfirm">
<el-form ref="departForm" :model="departForm" label-width="110px" label-position="right">
<el-form-item label="部门名称" prop="name" :rules="[{ required: true, message: '请输入部门名称', trigger: 'blur' }]">
<el-input size="small" placeholder="请输入部门名称" show-word-limit :maxlength="30" v-model="departForm.name"></el-input>
</el-form-item>
</el-form>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'List',
props: {
instance: Function,
dict: Object
},
data() {
return {
users: [],
isShowMenu: false,
department: [],
btnLoading: false,
menuInfo: {
x: '',
y: '',
node: {}
},
search: {
current: 1,
size: 10,
title: '',
tagname: '',
name: '',
tagIds: '',
departmentId: ''
},
tagForm: {
tagname: ''
},
isShowDepart: false,
departForm: {
name: ''
},
loading: false,
isShowTags: false,
defaultChecked: [],
defaultExpanded: [],
tabIndex: 0,
currIndex: -1,
areaList: [],
total: 0,
colConfigs: [
{type: 'selection', label: ''},
{slot: 'avatar', label: ''},
{prop: 'name', label: '姓名'},
{prop: 'position', label: '职务'},
{prop: 'departmentNames', label: '部门'},
{prop: 'mobile', label: '手机号'},
{slot: 'tags', label: '标签'},
{prop: 'status', label: '账号状态', align: 'center', format: v => v == 1 ? '已激活' : '未激活'}
],
defaultProps: {
children: 'children',
label: 'name'
},
unitName: '',
rootId: '',
unitList: [],
tagsList: [],
tagName: '',
sourceTagList: [],
tableData: [],
tagId: '',
departmentName: '',
departId: '',
ids: ''
}
},
computed: {
...mapState(['user'])
},
watch: {
unitName(val) {
this.$refs.tree.filter(val)
},
tagName(val) {
if (!val) {
this.tagsList = this.sourceTagList
}
this.tagsList = this.sourceTagList.filter(v => v.tagname.indexOf(val) > -1)
}
},
mounted() {
this.getTree()
this.getList()
this.getTags()
document.querySelector('html').addEventListener('click', this.bindEvent)
this.$nextTick(() => {
})
},
methods: {
changeTag(index) {
this.currIndex = index
this.search.current = 1
this.$nextTick(() => {
this.getList()
})
},
bindEvent() {
this.isShowMenu = false
},
nodeContextmenu(e, node) {
this.isShowMenu = true
let y = e.y + 6
if (y + 202 > document.body.clientHeight) {
y = y - 202
}
this.menuInfo = {
x: e.x + 16, y,
node
}
},
removeTags(id) {
if (this.currIndex < 0) {
return this.$message.error('请选择标签')
}
this.$confirm('确定移除该成员?').then(() => {
this.instance.post(`/app/wxcp/wxtag/removeTag?userIds=${id}&tagId=${this.tagsList[this.currIndex].id}`).then(res => {
if (res.code == 0) {
this.$message.success('移除成功!')
this.search.current = 1
this.getList()
}
})
})
},
onDepartment(e) {
if (!e.length) {
return this.$message.error('请选择部门')
}
const ids = e.map(v => v.id).join(',')
this.department = []
this.instance.post(`/app/wxcp/wxuser/export?departmentId=${ids}`, null, {
responseType: 'blob'
}).then(res => {
if (res?.type == "application/json") {
let reader = new FileReader()
reader.readAsText(res, "utf-8")
reader.onload = e => {
if (e.target.readyState === 2) {
let ret = JSON.parse(e.target.result)
if (ret?.code == 0) {
this.$message.success(ret.msg)
} else this.$message.error(ret.msg)
}
}
} else {
const link = document.createElement('a')
let blob = new Blob([res], {type: res.type})
link.style.display = 'none'
link.href = URL.createObjectURL(blob)
link.setAttribute('download', `${e[0].name}.xls`)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
this.$message.success('导出成功!')
}
})
},
onChooseUser(e) {
if (!e.length) {
return this.$message.error('请选择成员')
}
this.instance.post(`/app/wxcp/wxtag/markTag`, null, {
params: {
tagId: this.tagsList[this.currIndex].id,
userIds: e.map(v => v.id).join(',')
}
}).then(res => {
if (res.code == 0) {
this.getList()
this.users = []
this.search.current = 1
this.$refs.addTags.reset()
} else {
this.users = []
}
}).catch(() => {
this.users = []
})
},
handleTreeCommand(e, item) {
this.isShowMenu = false
if (e === 'add') {
this.departForm.id = ''
this.departForm.parentid = item.id
this.departId = ''
this.isShowDepart = true
} else if (e === 'edit') {
this.departForm = {
...item
}
this.departId = item.id
this.isShowDepart = true
} else if (e === 'top') {
if (!item.i) {
return false
}
this.moveDepart(item.id, 0)
} else if (e === 'bottom') {
if ((item.i === item.len - 1) || (!item.i && item.i !== 0)) {
return false
}
this.moveDepart(item.id, 1)
} else if (e === 'remove') {
this.removeDepart(item.id, item.parentid)
}
},
removeDepart(id, parentid) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/wxcp/wxdepartment/delete?id=${id}`).then(res => {
if (res.code == 0) {
this.defaultChecked = [parentid]
this.$message.success('删除成功!')
this.getTree()
}
})
})
},
moveDepart(id, type) {
this.instance.post(`/app/wxcp/wxdepartment/move?id=${id}&type=${type}`).then(res => {
if (res.code == 0) {
this.defaultChecked = [id]
this.getTree()
this.$message.success(type === 0 ? '上移成功' : '下移成功')
}
})
},
onDepartConfirm() {
this.$refs.departForm.validate((valid) => {
if (valid) {
this.instance.post(`/app/wxcp/wxdepartment/addOrUpdate`, {
...this.departForm,
departId: this.departId
}).then(res => {
if (res.code == 0) {
this.defaultChecked = [this.departForm.parentid]
this.isShowDepart = false
this.getTree()
this.$message.success(this.departId ? '编辑成功' : '新增成功')
}
})
}
})
},
onFormConfirm() {
this.$refs.tagForm.validate((valid) => {
if (valid) {
this.instance.post(`/app/wxcp/wxtag/addOrUpdate`, {
...this.tagForm
}).then(res => {
if (res.code == 0) {
this.isShowTags = false
this.getTags()
this.$message.success(this.tagId ? '编辑成功' : '新增成功')
}
})
}
})
},
handleCommand(e, item) {
if (e === 'edit') {
this.tagId = item.id
this.tagForm = {
...item
}
this.isShowTags = true
} else {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/wxcp/wxtag/delete?id=${item.id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getTags()
this.getList()
}
})
})
}
},
onClose() {
this.tagId = ''
this.tagForm.tagname = ''
this.departForm.name = ''
this.departId = ''
this.departForm.nameEn = ''
this.departForm.parentid = ''
this.departForm.showIndex = ''
},
syncMembers() {
this.btnLoading = true
this.instance.post(`/app/wxcp/wxdepartment/syncDepart`).then(res => {
if (res.code == 0) {
this.$message.success('同步成功')
this.getList()
this.getTree()
}
this.btnLoading = false
}).catch(() => {
this.btnLoading = false
})
},
syncUser() {
let departId = this.search.departmentId;
if (!departId) departId = 1;
this.btnLoading = true
this.instance.post(`/app/wxcp/wxdepartment/syncUser?departmentId=${departId}`, null, {
timeout: 1000000
}).then(res => {
if (res.code == 0) {
this.$message.success('同步成功')
this.getList()
this.getTree()
}
this.btnLoading = false
}).catch(() => {
this.btnLoading = false
})
},
getTags() {
this.instance.post(`/app/wxcp/wxtag/listAll`).then(res => {
if (res.code == 0) {
this.sourceTagList = res.data.length ? JSON.parse(JSON.stringify(res.data)) : []
this.tagsList = res.data
}
})
},
onSwitchChange(id) {
this.instance.post(`/app/wxcp/wxuser/enable?id=${id}`).then(res => {
if (res.code == 0) {
this.getList()
}
})
},
onTreeChange(e) {
this.departmentName = e.name
this.search.departmentId = e.id || ''
this.search.current = 1
this.isShowMenu = false
this.$nextTick(() => {
this.getList()
})
},
getList() {
this.loading = true
this.instance.post(`/app/wxcp/wxuser/list`, null, {
params: {
...this.search,
departmentId: this.tabIndex === 0 ? this.search.departmentId : '',
tagIds: this.tabIndex === 1 ? (this.currIndex >= 0 ? this.tagsList[this.currIndex].id : '') : '',
listType: this.tabIndex
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
this.$nextTick(() => {
this.loading = false
})
} else {
this.loading = false
}
}).catch(() => {
this.loading = false
})
},
removeAll() {
if (!this.ids) return
if (this.tabIndex === 1) {
this.removeTags(this.ids)
} else {
this.remove(this.ids)
}
},
handleSelectionChange(e) {
this.ids = e.map(v => v.id).join(',')
},
filterNode(value, data) {
if (!value) return true
return data.name.indexOf(value) !== -1
},
changeTab(id, index) {
this.currIndex = index
this.search.areaId = id
this.$nextTick(() => {
this.getList()
})
},
getTree() {
this.instance.post(`/app/wxcp/wxdepartment/listAll?unitName=${this.unitName}`).then(res => {
if (res.code === 0) {
let parent = res.data.map(v => {
v.label = v.name
v.children = []
return v
}).filter(e => !e.parentid)[0]
this.defaultExpanded = [parent.id]
this.defaultChecked = [parent.id]
this.search.departmentId = parent.id
this.departmentName = parent.name
this.addChild(parent, res.data)
this.unitList = [parent]
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(parent.id)
})
}
})
},
addChild(parent, list) {
for (let i = 0; i < list.length; i++) {
if (list[i].parentid === parent.id) {
list[i].i = parent.children.length
parent.children.push(list[i])
}
}
if (parent.children.length) {
parent.children.forEach(v => {
v.len = parent.children.length
})
}
if (list.length > 0) {
parent['children'].map(v => this.addChild(v, list))
}
},
formatList(list) {
var arr = []
for (let item of list) {
if (item.childrenUser && item.childrenUser.length) {
delete item.childrenUser
}
if (item.childrenDept && item.childrenDept.length) {
this.formatList(item.childrenDept)
}
arr.push(item)
}
return arr
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/wxcp/wxuser/delete?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toAdd(id) {
this.$emit('change', {
type: 'Add',
params: {
id: id || '',
departmentId: this.search.departmentId || '',
departmentName: this.departmentName || ''
}
})
}
}
}
</script>
<style lang="scss" scoped>
.addressBook {
.addressBook-table__btns {
display: flex;
align-items: center;
}
.table-tags {
.el-tag {
margin-right: 8px;
margin-bottom: 8px;
&:last-child {
margin-right: 0;
}
}
}
.import-wrapper {
& > h2 {
margin-bottom: 8px;
font-size: 16px;
color: #222222;
font-weight: Bold;
}
.import-wrapper__tips {
line-height: 1;
margin-bottom: 24px;
div {
display: flex;
margin-bottom: 8px;
color: #222222;
font-size: 14px;
span {
cursor: pointer;
color: #2266FF;
&:hover {
opacity: 0.8;
text-decoration: underline;
}
}
}
}
.import-files {
i {
display: block;
margin-top: 8px;
}
}
i {
color: #999999;
font-size: 12px;
font-style: normal;
}
}
.tree-container {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 100%;
.tree-name {
padding-right: 30px;
}
i {
position: absolute;
top: 50%;
right: 8px;
transform: translateY(-50%);
padding-right: 8px;
font-weight: normal;
color: #fff;
}
}
.el-tag {
margin-right: 8px;
border: 1px solid #D0D4DC;
background: #F3F4F7;
border-radius: 4px;
font-size: 13px;
color: #222222;
&:last-child {
margin-right: 0;
}
}
.table-avatar {
width: 40px;
height: 40px;
margin-top: 3px;
border-radius: 2px;
border: 1px solid #CCCCCC;
}
.el-button--mini, .el-button--mini.is-round {
height: 28px;
line-height: 28px;
padding: 0;
font-size: 12px;
:deep( span ){
margin-left: 0;
}
}
.addressBook-left__list--title {
display: flex;
align-items: center;
margin: 8px 8px 0;
.addressBook-left__list--search {
flex: 1;
:deep( input ){
width: 100%;
}
}
.el-button {
width: 84px;
flex-shrink: 1;
margin-right: 8px;
}
}
.addressBook-left {
width: 100%;
height: auto;
background: #FAFAFB;
.addressBook-left__title {
display: flex;
align-items: center;
width: 100%;
height: 40px;
background: #ffffff;
h2 {
flex: 1;
height: 100%;
line-height: 40px;
color: #222;
font-size: 14px;
text-align: center;
cursor: pointer;
border-bottom: 2px solid transparent;
&.tab-active {
color: #2266FF;
border-bottom: 2px solid #2266FF;
}
}
}
// ::-webkit-scrollbar {
// width: 1px;
// }
.addressBook-left__list--wrapper {
height: calc(100% - 68px);
padding: 8px;
}
.addressBook-left__list {
width: 100%;
height: 100%;
overflow: auto;
:deep( .el-tree ){
width: fit-content;
min-width: 100%;
}
:deep( .el-scrollbar__wrap ){
margin-bottom: 0 !important;
overflow-x: hidden;
.el-scrollbar__view {
width: fit-content;
min-width: 100%;
}
}
.addressBook-left__tags--item {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
padding: 0 8px 0 16px;
cursor: pointer;
color: #222222;
&.addressBook-left__tags--item-active, &:hover {
background: #E8EFFF;
color: #2266FF;
i, span {
color: #2266FF;
}
}
span {
font-size: 14px;
}
i {
cursor: pointer;
color: #8e9ebf;
font-size: 16px;
}
}
span {
color: #222222;
font-size: 14px;
}
:deep( .el-tree ){
background: transparent;
.el-tree-node__expand-icon.is-leaf {
color: transparent !important;
}
.el-tree-node__content > .el-tree-node__expand-icon {
padding: 4px;
}
.el-tree-node__content {
height: 32px;
}
.el-tree__empty-text {
color: #222;
font-size: 14px;
}
.el-tree-node__children .el-tree-node__content {
height: 32px;
}
.el-tree-node__content:hover {
background: #E8EFFF;
color: #222222;
border-radius: 2px;
}
.is-current > .el-tree-node__content {
&:hover {
background: #2266FF;
color: #fff;
}
background: #2266FF;
span {
color: #fff;
}
}
}
}
}
:deep( .ai-list__content--right ){
flex: 1;
min-width: 0;
margin-left: 1px;
box-shadow: none;
.ai-list__content--right-wrapper {
width: 100%;
}
}
}
</style>

View File

@@ -1,354 +0,0 @@
<template>
<ai-list class="AppPartyPayment">
<template slot="title">
<ai-title title="党费缴纳" isShowBottomBorder :instance="instance"></ai-title>
</template>
<template #content>
<div class="statistics-top">
<div class="statistics-top__item">
<span>本月已缴纳党费金额</span>
<h2 style="color: #2266FF;">{{ topTotal['本月缴纳党费'] || 0 }}</h2>
</div>
<div class="statistics-top__item">
<span>本月已缴纳人数</span>
<h2 style="color: #22AA99;">{{ topTotal['本月已缴纳人数'] || 0 }}</h2>
</div>
<div class="statistics-top__item">
<span>本月未缴纳党费金额</span>
<h2 style="color: #F8B425">{{ topTotal['本月未缴纳党费'] || 0 }}</h2>
</div>
<div class="statistics-top__item">
<span>本月未缴纳人数</span>
<h2 style="color: #999;">{{ topTotal['本月未缴纳人数'] || 0 }}</h2>
</div>
</div>
<div class="content">
<ai-search-bar>
<template #left>
<ai-party :instance="instance" v-model="search.partyOrgId" :topOrgId="topOrgId" :name.sync="search.partyOrgName"
style="display:inline-block" @origin="handlePartyOrgSelect" customClicker
url="/app/partyOrganization/queryAllChildren">
<el-input size="small" v-model="search.partyOrgName" readonly placeholder="选择党组织"></el-input>
</ai-party>
<el-date-picker v-model="search.ymd" type="month" placeholder="选择日期" size="small" value-format="yyyy-MM" @change="getListInit"></el-date-picker>
<ai-select
v-model="search.status"
@change="getListInit()"
placeholder="状态"
:selectList="dict.getDict('zhishengPartyFeeRecordStatus')">
</ai-select>
</template>
<template #right>
<el-input size="small" v-model="search.name" placeholder="姓名/身份证"
suffix-icon="iconfont iconSearch" v-throttle="() => {getListInit()}" clearable @clear="search.name = '', getListInit()"/>
</template>
</ai-search-bar>
<ai-search-bar>
<template #left>
<el-button size="small" type="primary" icon="iconfont iconAdd" @click="toAdd('')">新增</el-button>
</template>
<template #right>
<ai-import :instance="instance" :dict="dict" type="appconvenientaddressbook" name="党费缴纳"
@success="getListInit()" importUrl="/app/appdfjnzhisheng/import" url="/app/appdfjnzhisheng/downloadTemplate">
<el-button icon="iconfont iconImport">导入</el-button>
</ai-import>
<ai-download :instance="instance" url="/app/appdfjnzhisheng/export" :params="search" fileName="党费缴纳"
:disabled="tableData.length == 0">
<el-button icon="iconfont iconExported" :disabled="tableData.length == 0">导出</el-button>
</ai-download>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="option" label="操作" align="center" width="160px" fixed="right">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" title="编辑" @click="edit(row)">编辑</el-button>
<el-button type="text" title="删除" @click="remove(row)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</div>
<ai-dialog :visible.sync="dialog" :title="dialogTitle" @closed="$refs.form.resetFields()" @onConfirm="handlePayment">
<el-form ref="form" :rules="rules" size="small" :model="form" label-width="80px">
<el-form-item label="党员姓名" prop="name">
<el-autocomplete ref="poiInput" v-model="form.name" size="small" clearable :fetch-suggestions="handleSearch"
placeholder="请输入党员姓名" @select="handleSelect" :trigger-on-focus="false" style="width:100%;">
<template slot-scope="{item}">
<span style="direction: rtl" v-text="`${item.name}-${item.partyOrgName}(${item.idNumber})`"/>
</template>
</el-autocomplete>
</el-form-item>
<el-form-item label="身份证号" prop="idNumber">
<el-input placeholder="自动带入" v-model="form.idNumber" :disabled="true" size="small"></el-input>
</el-form-item>
<el-form-item label="缴纳状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio label="1">已缴纳</el-radio>
<el-radio label="0">未缴纳</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="缴纳月份" prop="ymd">
<el-date-picker v-model="form.ymd" type="month" placeholder="选择年月" size="small" value-format="yyyy-MM"></el-date-picker>
</el-form-item>
<el-form-item label="金额(元)" prop="amount">
<el-input-number :precision="2" size="small" type="input" v-model="form.amount" clearable placeholder="2位小数"></el-input-number>
</el-form-item>
</el-form>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'AppPartyPayment',
label: '党费缴纳',
props: {
instance: Function,
dict: Object
},
data () {
return {
search: {
current: 1,
size: 10,
partyOrgId: '',
partyOrgName: '',
name: '',
status: '',
ymd: ''
},
total: 0,
tableData: [],
colConfigs: [
{ prop: 'orgName', label: '党组织' },
{ prop: 'name', label: '党员姓名', align: 'center', },
{ prop: 'idNumber', label: '身份证', align: 'center' },
{ prop: 'ymd', label: '缴纳月份', align: 'center' },
{ prop: 'amount', label: '缴纳党费', align: 'center' },
{ prop: 'status', label: '状态', align: 'center',format: v => this.dict.getLabel('zhishengPartyFeeRecordStatus', v)},
{ slot: 'option'}
],
topOrgId: '',
dialog: false,
dialogTitle: '',
form: {
name: '',
idNumber: '',
status: '0',
ymd: '',
amount: '',
orgId: '',
orgName: '',
partyId: ''
},
topTotal: {}
}
},
computed: {
params () {
return {
...this.search,
startTime: this.search.type === '3' ? this.date[0] : '',
endTime: this.search.type === '3' ? this.date[1] : ''
}
},
rules() {
return {
name: [{ required: true, message: '请输入党员姓名', trigger: 'change'}],
idNumber: [{ required: true, message: '请选择党员', trigger: 'change'}],
status: [{ required: true, message: '请选择缴费状态', trigger: 'change'}],
ymd: [{ required: true, message: '请选择缴纳月份', trigger: 'change'}],
amount: [{ required: true, message: '请输入金额', trigger: 'blur'}],
}
},
...mapState(['user']),
},
created () {
this.topOrgId = this.user.info.organizationId
this.dict.load('zhishengPartyFeeRecordStatus').then(() => {
this.getList()
})
this.getTotal()
},
methods: {
toAdd() {
this.form = {
name: '',
idNumber: '',
status: '0',
ymd: '',
amount: '',
orgId: '',
orgName: '',
partyId: ''
}
this.dialogTitle = '新增党费信息'
this.dialog = true
},
edit(row) {
this.form = row
this.dialog = true
this.dialogTitle = '编辑党费信息'
},
handleSearch(e, cb) {
this.instance.post(`/app/appparty/fuzzyList?name=${e}&size=50`).then((res) => {
if (res.code == 0) {
cb(res.data)
}
});
},
handleSelect(e) {
this.form.name = e.name
this.form.idNumber = e.idNumber
this.form.orgId = e.partyOrgId
this.form.orgName = e.partyOrgName
this.form.partyId = e.id
},
handlePayment() {
this.$refs.form.validate((valid) => {
if (valid) {
this.instance.post(`/app/appdfjnzhisheng/addOrUpdate`, {...this.form}).then((res) => {
if (res.code == 0) {
this.$message.success(this.dialogTitle == '编辑党费信息' ? "编辑成功" : "新增成功");
this.$refs.form.resetFields()
this.dialog = false
this.getListInit()
this.getTotal()
}
});
}
});
},
handlePartyOrgSelect(e) {
let {isLeaf, name, id} = e?.[0] || {};
if (isLeaf == 1) {
this.search.partyOrgName = name;
this.search.partyOrgId = id;
}
this.getListInit()
},
getListInit() {
this.search.current = 1
this.getList()
},
getList () {
this.instance.post(`/app/appdfjnzhisheng/list`, null, {
params: {
...this.search,
orgId: this.search.partyOrgId
}
}).then(res => {
if (res.code == 0) {
res.data.records.map((item) => {
item.ymd = item.ymd.substring(0, 7)
})
this.tableData = res.data.records
this.total = res.data.total
}
})
},
remove(row) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appdfjnzhisheng/delete?ids=${row.id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
getTotal() {
this.instance.post(`/app/appdfjnzhisheng/statistics`).then(res => {
if (res.code == 0) {
this.topTotal = res.data
}
})
}
}
}
</script>
<style lang="scss" scoped>
.AppPartyPayment {
.bottom {
display: flex;
align-items: center;
& > .ai-card {
flex: 1;
&:last-child {
margin-left: 20px;
}
}
}
:deep( .ai-list__content ){
padding: 0!important;
.ai-list__content--right-wrapper {
background: transparent!important;
box-shadow: none!important;
margin: 0!important;
padding: 12px 16px 12px!important;
}
}
:deep( .ai-card) {
.ai-card__body {
padding: 12px 16px;
}
}
.statistics-top {
display: flex;
align-items: center;
margin-bottom: 20px;
& > div {
flex: 1;
height: 96px;
line-height: 1;
margin-right: 20px;
padding: 16px 24px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 4px;
&:last-child {
margin-right: 0;
}
h3 {
font-size: 24px;
}
span {
display: block;
margin-bottom: 16px;
color: #888888;
font-size: 16px;
}
}
}
.content {
padding: 16px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
}
}
</style>

View File

@@ -1,35 +0,0 @@
<template>
<section class="AppPartyScore">
<component :is="currentPage" v-bind="$props"/>
</section>
</template>
<script>
import PsList from "./psList";
import PsDetail from "./psDetail";
export default {
name: "AppPartyScore",
components: {PsDetail, PsList},
label: "党员积分",
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
currentPage() {
return this.$route.query.id ? PsDetail : PsList
}
},
created() {
this.dict.load("partyIntegralType")
}
}
</script>
<style lang="scss" scoped>
.AppPartyScore {
height: 100%;
}
</style>

View File

@@ -1,154 +0,0 @@
<template>
<section class="psDetail">
<ai-detail>
<ai-title slot="title" title="积分详情" isShowBottomBorder isShowBack @onBackClick="back"/>
<template #content>
<el-row type="flex">
<ai-card hideTitle class="staCard fill">
<template #content>
<div class="color-999" v-text="`姓名`"/>
<b v-text="detail.name"/>
</template>
</ai-card>
<ai-card hideTitle class="staCard fill">
<template #content>
<div class="color-999" v-text="`学习强国`"/>
<el-button type="text" @click="handleEditLearningIntergral(detail.id)">编辑</el-button>
<b class="color-26f" v-text="detail.learningIntegral||0"/>
</template>
</ai-card>
<ai-card hideTitle class="staCard fill">
<template slot="content">
<div class="color-999" v-text="`个人积分`"/>
<b class="color-26f" v-text="detail.integral||0"/>
</template>
</ai-card>
<ai-card hideTitle class="staCard fill">
<template #content>
<div class="color-999" v-text="`家庭积分`"/>
<b class="color-26f" v-text="detail.familySurplusIntegral||0"/>
</template>
</ai-card>
</el-row>
<ai-card title="余额变动明细">
<template #content>
<ai-table :tableData="detail.integralInfoList" :isShowPagination="false" :col-configs="colConfigs"
:dict="dict"/>
</template>
</ai-card>
</template>
</ai-detail>
<ai-dialog :visible.sync="dialog" title="学习强国设置" width="500px" @close="form={}" @onConfirm="submit">
<el-form :model="form" size="small" ref="DialogForm" :rules="rules" label-width="110px">
<el-form-item label="学习强国积分" prop="learningIntegral">
<el-input v-model.number="form.learningIntegral" placeholder="请输入正整数" clearable/>
</el-form-item>
</el-form>
</ai-dialog>
</section>
</template>
<script>
export default {
name: "psDetail",
props: {
instance: Function,
dict: Object,
},
data() {
return {
detail: {},
colConfigs: [
{label: "时间", prop: "createTime"},
{label: "类型", prop: "integralType", align: 'center', dict: "partyIntegralType"},
{label: "变动积分", prop: "integral", align: 'center'},
{label: "剩余积分", prop: "residualIntegral", align: 'center'},
{label: "调整说明", prop: "remark"},
],
dialog: false,
form: {},
rules: {
learningIntegral: [
{required: true, message: "请输入学习强国积分"},
{pattern: /^\d+$/g, message: "请输入正整数"}
]
}
}
},
methods: {
getDetail() {
let {id} = this.$route.query
this.instance.post("/app/appparty/getPartyIntegralDetail", null, {
params: {id}
}).then(res => {
if (res?.data) {
this.detail = res.data
}
})
},
back() {
this.$router.push({})
},
submit() {
this.$refs.DialogForm.validate(v => {
if (v) {
this.instance.post("/app/appparty/editLearningIntegral", null,{
params:this.form
}).then(res => {
if (res?.code == 0) {
this.$message.success("提交成功!")
this.dialog = false
this.getDetail()
}
})
}
})
},
handleEditLearningIntergral(partyMemberId) {
this.dialog = true
this.form = {partyMemberId}
}
},
created() {
this.getDetail()
}
}
</script>
<style lang="scss" scoped>
.psDetail {
height: 100%;
.color-999 {
color: #999;
}
.color-26f {
color: #26f;
}
:deep(.staCard ){
font-size: 14px;
b {
font-size: 24px;
line-height: 40px;
}
& + .staCard {
margin-left: 16px;
}
.ai-card__body {
position: relative;
.el-button {
position: absolute;
right: 32px;
top: 6px;
}
}
}
}
</style>

View File

@@ -1,116 +0,0 @@
<template>
<section class="psList">
<ai-list>
<ai-title slot="title" title="党员积分" isShowBottomBorder/>
<template #content>
<ai-search-bar>
<template #right>
<ai-import :instance="instance" name="党员积分" title="导入党员积分"
suffixName="xlsx"
url="/app/apppartyintegralinfo/downloadTemplate"
importUrl="/app/apppartyintegralinfo/import"
@onSuccess="page.current=1,getTableData()"/>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size"
@getList="getTableData" :col-configs="colConfigs" :dict="dict">
<el-table-column slot="options" label="操作" fixed="right" align="center">
<template slot-scope="{row}">
<el-button type="text" @click="getFamilyByPartyId(row.idNumber)">家庭成员</el-button>
<el-button type="text" @click="showDetail(row.id)">详情</el-button>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<ai-dialog :visible.sync="dialog" title="家庭成员" :customFooter="true" width="780px" @close="familyList=[]">
<ai-table :tableData="familyList" :isShowPagination="false" :col-configs="familyCols" :dict="dict"/>
<div class="dialog-footer" slot="footer">
<el-button @click="dialog=false"> </el-button>
</div>
</ai-dialog>
</section>
</template>
<script>
import {mapState} from "vuex";
import {ID} from "dui/lib/js/utils";
export default {
name: "psList",
props: {
instance: Function,
dict: Object,
},
computed: {
...mapState(['user']),
colConfigs() {
return [
{label: "姓名", prop: "name"},
{label: "个人积分", prop: "integral", align: "center"},
{label: "家庭积分", prop: "familySurplusIntegral", align: "center"},
{label: "学习强国", prop: "learningIntegral", align: "center"},
{slot: "options"}
]
},
familyCols() {
return [
{
label: '与户主关系', prop: 'householdRelation', align: 'center', width: 165,
render: (h, {row}) => h('p', this.dict.getLabel('householdRelation', row.householdRelation || "户主"))
},
{label: '类型', prop: 'residentType', align: 'center', dict: "residentType"},
{label: '姓名', prop: 'name', align: 'center'},
{label: '身份证号', render: (h, {row}) => h('p', ID.hideId(row.idNumber)), width: 165},
{label: '联系电话', prop: 'phone', align: 'center', width: 120}
]
}
},
data() {
return {
search: {},
page: {current: 1, size: 10, total: 0},
tableData: [],
dialog: false,
familyList: [],
}
},
methods: {
back() {
this.$router.push({})
},
getTableData() {
this.instance.post("/app/appparty/listByPartyIntegral", null, {
params: {...this.page, ...this.search}
}).then(res => {
if (res?.data) {
this.tableData = res.data?.records
this.page.total = res.data.total
}
})
},
showDetail(id) {
this.$router.push({query: {id}})
},
getFamilyByPartyId(idNumber) {
this.instance.post("/app/appresident/queryHomeMember", null, {
params: {idNumber}
}).then(res => {
if (res?.data) {
this.familyList = res.data?.family || []
this.dialog = true
}
})
}
},
created() {
this.getTableData()
}
}
</script>
<style lang="scss" scoped>
.psList {
height: 100%;
}
</style>

View File

@@ -1,34 +0,0 @@
<template>
<section class="AppPartyScoreFlow">
<component :is="currentPage" v-bind="$props"/>
</section>
</template>
<script>
import PsfList from "./psfList";
export default {
name: "AppPartyScoreFlow",
components: {PsfList},
label: "党员积分明细",
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
currentPage() {
return PsfList
}
},
created() {
this.dict.load("partyIntegralType")
}
}
</script>
<style lang="scss" scoped>
.AppPartyScoreFlow {
height: 100%;
}
</style>

View File

@@ -1,150 +0,0 @@
<template>
<section class="psfList">
<ai-list>
<ai-title slot="title" title="党员积分明细" isShowBottomBorder/>
<template #content>
<ai-search-bar>
<template #left>
<el-button type="primary" icon="iconfont iconAdd" @click="dialog=true">添加</el-button>
<el-date-picker type="daterange" placeholder="日期" size="small" clearable v-model="createTime"
@change="handleSearchTime" start-placeholder="开始时间" end-placeholder="结束时间"
value-format="yyyy-MM-dd HH:mm:ss" :default-time="['00:00:00','23:59:59']"/>
</template>
<template #right>
<el-input size="small" placeholder="搜索党员" v-model="search.partyName" clearable
@change="page.current=1,getTableData()" suffix-icon="iconfont iconSearch"/>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size"
@getList="getTableData" :col-configs="colConfigs" :dict="dict">
<el-table-column slot="options" label="操作" fixed="right" align="center">
<template slot-scope="{row}">
<el-button type="text" @click="showDetail(row)">详情</el-button>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<ai-dialog :visible.sync="dialog" title="积分对象" width="600px" @close="form={}" @onConfirm="submit"
:customFooter="!isAdd">
<el-form v-if="isAdd" :model="form" size="small" ref="DialogForm" :rules="rules" label-width="80px">
<el-form-item label="选择人员" prop="partyId">
<ai-select v-model="form.partyId" action="/app/appparty/list" :instance="instance"
:prop="{label:'name'}"/>
</el-form-item>
<el-form-item label="调整说明" prop="remark">
<el-input type="textarea" placeholder="请输入" v-model="form.remark" maxlength="100" show-word-limit rows="3"
clearable/>
</el-form-item>
<el-form-item label="类型" prop="integralType">
<ai-select v-model="form.integralType" :selectList="dict.getDict('partyIntegralType')"/>
</el-form-item>
<el-form-item label="积分" prop="integral">
<el-input v-model.number="form.integral" placeholder="请输入正整数" clearable/>
</el-form-item>
</el-form>
<ai-wrapper v-else>
<ai-info-item label="对象" :value="form.partyName"/>
<ai-info-item label="调整说明" :value="form.remark" isLine/>
<ai-info-item label="类型" :value="dict.getLabel('partyIntegralType',form.integralType)"/>
<ai-info-item label="积分" :value="form.integral"/>
</ai-wrapper>
</ai-dialog>
</section>
</template>
<script>
import {mapState} from "vuex";
export default {
name: "psfList",
props: {
instance: Function,
dict: Object,
},
computed: {
...mapState(['user']),
colConfigs() {
return [
{label: "对象", prop: "partyName"},
{label: "调整说明", prop: "remark", align: "center"},
{label: "时间", prop: "createTime"},
{label: "类型", prop: "integralType", align: "center", dict: "partyIntegralType"},
{label: "积分", prop: "integral", align: "center"},
{slot: "options"}
]
},
isAdd() {
return !this.form.id
}
},
data() {
return {
search: {},
page: {current: 1, size: 10, total: 0},
tableData: [],
dialog: false,
form: {},
rules: {
partyId: {required: true, message: "请选择人员"},
remark: {required: true, message: "请输入调整说明"},
integralType: {required: true, message: "请选择类型"},
integral: [
{required: true, message: "请输入分数"},
{pattern: /^\d+$/g, message: "请输入正整数"}
],
},
createTime: ""
}
},
methods: {
getTableData() {
this.instance.post("/app/apppartyintegralinfo/list", null, {
params: {...this.page, ...this.search}
}).then(res => {
if (res?.data) {
this.tableData = res.data?.records?.map(e => ({
...e,
integral: (e.integralType == 0 ? "-" : '+') + e.integral
}))
this.page.total = res.data.total
}
})
},
showDetail(row) {
this.form = JSON.parse(JSON.stringify(row))
this.dialog = true
},
submit() {
this.$refs.DialogForm.validate(v => {
if (v) {
let loading = this.$loading({text: "提交中..."})
this.instance.post("/app/apppartyintegralinfo/addOrUpdate", this.form).then(res => {
loading.close()
if (res?.code == 0) {
this.$message.success("提交成功!")
this.dialog = false
this.getTableData()
}
}).catch(() => loading.close())
}
})
},
handleSearchTime(v) {
this.page.current = 1
this.search.startTime = v?.[0]
this.search.endTime = v?.[1]
this.getTableData()
}
},
created() {
this.getTableData()
}
}
</script>
<style lang="scss" scoped>
.psfList {
height: 100%;
}
</style>

View File

@@ -1,101 +0,0 @@
<template>
<section class="conference">
<ai-list v-if="!showDetail">
<template slot="title">
<ai-title title="会议管理"></ai-title>
</template>
<template slot="tabs">
<el-tabs v-model="currIndex" @tab-click="handleClick">
<el-tab-pane v-for="(tab,i) in tabs" :key="i" :label="tab.label" :name="String(i)">
<component :is="tab.comp" v-if="currIndex==i" :ref="currIndex" :instance="instance" :dict="dict"
:permissions="permissions" @goPage="goPage" :listType="listType" />
</el-tab-pane>
</el-tabs>
</template>
</ai-list>
<component v-else :is="currentComp" :instance="instance" :dict="dict" :detail="detailRow" :listType="listType" @gotoEdit="gotoAdd" ></component>
</section>
</template>
<script>
import addMeeting from './addMeeting';
import detail from './detail'
import list from './list'
export default {
name: 'AppConference',
label: "会议管理",
components: {addMeeting, detail, list},
provide() {
return {
top: this
}
},
props: {
instance: Function,
dict: Object,
permissions: Function,
},
data() {
return {
//会议状态0、草稿1、未开始2、进行中3、已取消4、已结束
//参会状态0、未确认1、已确认2、缺席
currIndex: "0",
currentComp: "",
showDetail: false,
detailRow: {},
listType: '1',
}
},
computed:{
tabs() {
return [
{label: "我参与的会议", name: "addMeeting", comp: list, detail: detail, permission: ""},
{label: "我发起的会议", name: "addMeeting", comp: list, detail: detail, permission: ""},
]
},
},
methods: {
goPage(params) {
this.detailRow = params.row
this.currentComp = params.comp
if(params.comp == 'detail' || params.comp == 'addMeeting') {
this.showDetail = true
}
},
handleClick() {
if (this.currIndex == 0) {
this.listType = '1'
} else {
this.listType = '0'
}
},
goBack() {
this.showDetail = false;
if (this.currIndex == '0') {
this.listType = '1'
} else {
this.listType = '0'
}
},
gotoAdd(obj) {
this.showDetail = true
this.detailRow = obj
this.currentComp = 'addMeeting'
},
},
}
</script>
<style lang="scss" scoped>
.conference {
height: 100%;
:deep( .ai-list__content--right-wrapper ){
background-color: transparent !important;
box-shadow: none !important;
}
}
</style>

View File

@@ -1,244 +0,0 @@
<template>
<ai-detail class="addMeeting">
<ai-title slot="title" title="发起会议" isShowBack isShowBottomBorder @onBackClick="$parent.goBack"/>
<template #content>
<ai-card title="会议信息">
<template #content>
<el-form :model="saveData" status-icon ref="ruleForm" :rules="rules" label-width="100px"
label-position="right">
<el-form-item label="会议标题:" prop="title">
<el-input v-model="saveData.title" size="small" placeholder="请输入..."
:maxlength="30" show-word-limit></el-input>
</el-form-item>
<el-row type="flex" justify="space-between">
<el-form-item label="开始时间:" prop="startTime">
<el-date-picker
:editable="false"
value-format="yyyy-MM-dd HH:mm:ss"
v-model="saveData.startTime"
:picker-options="pickerOptions"
type="datetime"
size="small"
placeholder="选择开始时间">
</el-date-picker>
</el-form-item>
<el-form-item label="结束时间:" prop="endTime">
<el-date-picker
:editable="false"
value-format="yyyy-MM-dd HH:mm:ss"
v-model="saveData.endTime"
:picker-options="pickerOptions"
type="datetime"
:disabled="!Boolean(saveData.startTime)"
size="small"
placeholder="选择结束时间">
</el-date-picker>
</el-form-item>
</el-row>
<el-form-item label="会议地点:" prop="address">
<el-input v-model="saveData.address" size="small" placeholder="请输入..."
:maxlength="30" show-word-limit></el-input>
</el-form-item>
<el-form-item label="会议内容:" prop="content">
<el-input v-model="saveData.content" size="small" placeholder="请输入..."
type="textarea" :rows="8" :maxlength="500" show-word-limit></el-input>
</el-form-item>
<el-row type="flex" justify="space-between">
<el-form-item label="参会提醒:" prop="noticeBefore">
<ai-select v-model="saveData.noticeBefore"
placeholder="请选择"
:selectList="dict.getDict('meetingNoticeBefore')"
></ai-select>
</el-form-item>
<el-form-item label="确认提醒:" prop="noticeAfter">
<ai-select v-model="saveData.noticeAfter"
placeholder="请选择"
:selectList="dict.getDict('meetingNoticeAfter')"
></ai-select>
</el-form-item>
</el-row>
<el-form-item label="会议资料:" prop="files">
<ai-uploader :instance="instance" @change="handleChange" isShowTip fileType="file"
v-model="saveData.fileList"
acceptType=".zip,.rar,.doc,.docx,.xls,.ppt,.pptx,.pdf,.txt,.jpg,.png,.xlsx"
:limit="9"></ai-uploader>
</el-form-item>
</el-form>
</template>
</ai-card>
<ai-card title="参会人员信息">
<template #right>
<ai-wechat-selecter slot="append" :instance="instance" v-model="saveData.attendees">
<el-button type="text" icon="iconfont iconAdd">选择参会人员</el-button>
</ai-wechat-selecter>
</template>
<template #content>
<ai-table
border
:tableData="saveData.attendees"
:colConfigs="colConfigs"
:isShowPagination="false">
<el-table-column label="操作" slot="option" align="center">
<template v-slot="{row}">
<el-button type="text" title="删除" @click="deletePer(row)">删除</el-button>
</template>
</el-table-column>
</ai-table>
</template>
</ai-card>
</template>
<template #footer>
<el-button @click="$parent.goBack">取消</el-button>
<el-button type="primary" @click="saveFrom(0)">保存草稿</el-button>
<el-button type="primary" @click="saveFrom(1)">保存并发布</el-button>
</template>
</ai-detail>
</template>
<script>
import {mapState} from "vuex";
import moment from 'dayjs'
export default {
name: "addMeeting",
inject: ['top'],
props: {
instance: Function,
dict: Object,
permissions: Function,
detail: Object
},
data() {
let endTimePass = (rule, value, callback) => {
if (value) {
if (moment(value).unix() - moment(this.saveData.startTime).unix() > 0) {
callback()
} else {
callback(new Error('结束时间要大于开始时间'));
}
} else {
callback(new Error('请选择结束时间'));
}
}
return {
saveData: {
noticeBefore: '4',
noticeAfter: '0',
attendees: [],
fileList: []
},
rules: {
title: [{required: true, message: '请填写会议标题', trigger: 'blur'}],
startTime: [{required: true, message: '请选择开始时间', trigger: 'blur'}],
endTime: [{required: true, validator: endTimePass, trigger: 'change'}],
address: [{required: true, message: '请填写会议地点', trigger: 'blur'}],
// content: [{required: true, message: '请填写会议内容', trigger: 'blur'}],
noticeBefore: [{required: true, message: '请选择参会提醒', trigger: 'blur'}],
noticeAfter: [{required: true, message: '请选择确认提醒', trigger: 'blur'}],
},
pickerOptions: {
disabledDate(time) {
return time.getTime() < Date.now() - 8.64e7;
}
},
showEdit: false,
total: 0,
}
},
methods: {
handleChange(e) {
this.saveData.fileList = e
},
deletePer(scope) {
this.$confirm('确认删除此参会人?')
.then(_ => {
if (this.detail.id) { //编辑
} else { //新增
this.saveData.attendees.map((item, index) => {
if (item.id == scope.id) {
this.saveData.attendees.splice(index, 1)
}
})
}
})
},
saveFrom(status) {
this.$refs["ruleForm"].validate((valid) => {
if (this.saveData.attendees.length == 0) {
return this.$message.error("参会人不能为空!")
}
if (moment(this.saveData.startTime).unix() - moment(Date.now()).unix() < 0) {
return this.$message.error("会议开始时间已过期,请重新选择!");
}
if (valid) {
this.saveData.files = []
this.saveData.fileList.map((item) => {
this.saveData.files.push(item.id)
})
this.instance.post(`/app/appmeetinginfo/add-update`, {
...this.saveData,
status: status,
}).then(res => {
if (res.code == 0) {
if (status != 1) {
this.$message.success("保存草稿成功")
} else {
this.$message.success("发布成功")
}
this.$parent.goBack();
}
});
}
});
},
getDetail() {
this.instance.post(`/app/appmeetinginfo/info-id?id=${this.detail.id}`).then(res => {
if (res?.data) {
this.saveData = {
...res.data,
};
this.saveData.fileList = res.data.files || []
}
});
}
},
created() {
this.dict.load("meetingNoticeAfter", "meetingNoticeBefore").then(
this.$nextTick(() => {
if (JSON.stringify(this.detail) == '{}') {
this.showEdit = false;
} else {
this.showEdit = true;
// this.saveData = {...this.detail};
// this.compereList = this.saveData.hosts;
// this.saveData.attendees = this.saveData.attendees || [];
this.getDetail()
}
})
)
},
computed: {
...mapState(['user']),
headerTitle() {
return this.showEdit ? '修改会议' : '发起会议'
},
colConfigs() {
return [
{prop: 'name', align: 'center', label: '姓名'},
{prop: 'departName', align: 'center', label: '所属单位'},
{slot: 'option'}
]
},
}
}
</script>
<style lang="scss" scoped>
.addMeeting {
:deep( .el-button--text ){
.iconfont {
color: inherit;
}
}
}
</style>

View File

@@ -1,595 +0,0 @@
<template>
<section class="meetingDetail detail-content">
<ai-detail>
<template #title>
<ai-title title="会议详情" isShowBottomBorder isShowBack @onBackClick="$parent.goBack">
<template #rightBtn>
<p class="conference_top_area" v-if="listType==0">
<!-- <el-button type="primary" v-if="detail.status==0" @click="changeMeeting('是否立即发布会议?')">立即发布</el-button> -->
<el-button type="primary" v-if="detail.status==1" @click="noticeMetting()">参会提醒</el-button>
<el-button type="primary" v-if="detail.status==0" @click="editMeeting()">修改会议</el-button>
<el-button class="del-btn-list" v-if="detail.status==1" @click="changeMeeting('是否取消会议?')">取消会议</el-button>
<el-button class="iconfont iconDelete del-btn-list" v-if="detail.status==0||detail.status==3"
@click="changeMeeting('是否删除会议?')">删除会议
</el-button>
</p>
<!-- v-if="detail.status==0||detail.status==3" -->
<p class="conference_top_area" v-if="listType==1&&info.status==1">
<el-button @click="toDo" style="width:80px;" class="del-btn-list">待定</el-button>
<el-button @click="innerVisible=true" v-if="info.joinStatus!=2" style="width:80px;" class="del-btn-list">
请假
</el-button>
<el-button type="primary" @click="changeMeeting('是否确认参会?')" v-if="info.joinStatus!=1" style="width:80px;">
确认参会
</el-button>
</p>
</template>
</ai-title>
</template>
<template #content>
<ai-dialog
title="确认请假"
:visible.sync="innerVisible"
@onConfirm="queMeeting('writeInfo')"
@onCancel="innerVisible=false;" @close="$refs.writeInfo.resetFields()"
width="520px">
<div class="addother_main" style="width:400px;margin:auto;">
<el-form :model="writeInfo" status-icon ref="writeInfo" label-width="100px" class="demo-ruleForm">
<el-form-item label="请假原因:" prop="reason" autocomplete="off"
:rules="{
required: true, message: '请假原因不能为空', trigger: 'blur'
}"
>
<el-input v-model.trim="writeInfo.reason" autocomplete="off" size="mini" placeholder="请输入..."
type="textarea" :rows="4" :maxlength="100" show-word-limit></el-input>
</el-form-item>
</el-form>
</div>
</ai-dialog>
<ai-card title="会议说明">
<template slot="content">
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="1">
<ai-info-item label="会议标题:"><span>{{ info.title }}</span></ai-info-item>
</ai-wrapper>
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="2">
<ai-info-item label="发起单位:" :value="info.unitName">
</ai-info-item>
<ai-info-item label="发起人:" :value="info.userName">
</ai-info-item>
</ai-wrapper>
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="1">
<!-- <ai-info-item label="会议状态:" v-if="xq.joinStatus==1&&listType==1"><span>{{ xq.joinStatus }}</span>
</ai-info-item> -->
<ai-info-item label="发起时间:"><span>{{ info.createTime }}</span></ai-info-item>
</ai-wrapper>
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="2">
<ai-info-item label="开始时间:"><span>{{ info.startTime }}</span></ai-info-item>
<ai-info-item label="结束时间:"><span>{{ info.endTime }}</span></ai-info-item>
</ai-wrapper>
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="2">
<ai-info-item label="参会提醒:"><span>{{
dict.getLabel("meetingNoticeBefore", info.noticeBefore) || "-"
}}</span></ai-info-item>
<ai-info-item label="确认提醒:"><span>{{
dict.getLabel("meetingNoticeAfter", info.noticeAfter) || "-"
}}</span>
</ai-info-item>
</ai-wrapper>
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="1">
<ai-info-item label="会议地点:"><span>{{ info.address }}</span></ai-info-item>
</ai-wrapper>
<ai-wrapper class="mar-t16" label-width="70px" :columnsNumber="1">
<ai-info-item label="会议内容:"><span v-html="info.content"></span></ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="会议资料">
<template slot="content">
<ai-file-list :fileList="info.files" :fileOps="{name: 'name', size: 'fileSizeStr'}"></ai-file-list>
</template>
</ai-card>
<ai-card title="参会名单">
<template slot="content">
<ai-search-bar bottomBorder>
<template #left>
<el-select v-model="search.joinStatus" placeholder="确认状态" size="small" clearable class="vc-input-160" @change="searchMeetinguser">
<el-option
v-for="(item,k) in confirmStatus"
:key="k"
:label="item.label"
:value="k">
</el-option>
</el-select>
</template>
<template #right>
<!-- <ai-download :instance="instance" url="/app/appepidemicbackhomerecord/export" fileName="参会名单">
<el-button icon="iconfont iconExported">导出</el-button>
</ai-download> -->
</template>
</ai-search-bar>
<ai-table
:tableData="info.attendees"
:colConfigs="colConfigs"
style="margin-top: 12px;"
:isShowPagination="false">
<el-table-column slot="meetingUserName"
label="姓名"
align="center"
show-overflow-tooltip>
<div slot-scope="{row}">
<span>{{ row.meetingUserName }}</span>
</div>
</el-table-column>
<el-table-column slot="meetingUnitName"
label="所属部门"
align="center"
show-overflow-tooltip>
<div slot-scope="{row}">
<span>{{ row.meetingUnitName }}</span>
</div>
</el-table-column>
<el-table-column slot="joinStatus"
prop="joinStatus"
label="确认状态"
align="center"
show-overflow-tooltip>
<div slot-scope="{row}">
<p style="color:rgba(255,68,102,1);display:flex;justify-content:center;" v-if="row.joinStatus==2">
请假<i class="el-icon-warning" :title="row.absence" style="cursor: pointer;"></i>
</p>
<span v-else :style="{color:confirmStatus[row.joinStatus].color}"
v-text="confirmStatus[row.joinStatus].label"/>
</div>
</el-table-column>
<el-table-column slot="option"
label="操作"
align="center"
v-if="listType==0"
show-overflow-tooltip>
<div slot-scope="{row}" v-if="row.joinStatus==0">
<span class="el-icon-message-solid" style="cursor: pointer;" title="参会提醒"
@click="noticeMetting(row.meetingUserId)" v-if="detail.status==1"></span>
</div>
</el-table-column>
</ai-table>
</template>
</ai-card>
</template>
</ai-detail>
</section>
</template>
<script>
import {mapState} from "vuex";
export default {
name: 'meetingDetail',
inject: ['top'],
props: {
instance: Function,
dict: Object,
permissions: Function,
detail: Object,
listType: String
},
data() {
return {
navId: '1',
info: {},
total: 0,
writeInfo: {reason: ''},
meetingUserList: [],
innerVisible: false,
search: {joinStatus: ''}
}
},
created() {
this.dict.load("meetingNoticeAfter", "meetingNoticeBefore").then(() => this.searchDetail(this.detail.id))
},
computed: {
...mapState(['user']),
colConfigs() {
return [
{
slot: 'meetingUserName'
},
{
slot: 'meetingUnitName'
},
{
slot: 'joinStatus',
},
{prop: 'signInStatus', align: 'center', label: '签到', format: v => v === '1' ? '已签到' : '未签到'},
{
slot: 'option',
}
]
},
confirmStatus() {
return {
0: {label: '未确认', color: "rgba(255,136,34,1)"},
1: {label: '已确认', color: "rgba(34,102,255,1)"},
2: {label: '请假', color: "rgba(255,68,102,1)"},
3: {label: '待定', color: "rgba(255,136,34,1)"},
}
}
},
methods: {
toDo() {
this.$confirm("是否确认待定会议?").then(_ => {
this.instance.post("/app/appmeetinginfo/tobeConfirm", null, {
params: {
meetingId: this.detail.id,
}
}).then(res => {
if (res.code == 0) {
this.$message.success("会议待定");
setTimeout(_ => {
this.$parent.goBack();
}, 800)
}
})
})
},
searchMeetinguser() {
this.instance.post(`/app/appmeetinguser/list`, null, {
params: {...this.search, size: 999, meetingId: this.info.id}
}).then(res => {
if (res?.data) {
this.info.attendees = res.data.records;
this.total = res.data.total;
}
});
},
searchDetail(id) {
this.instance.post(`/app/appmeetinginfo/info-id`, null, {
params: {id}
}).then(res => {
if (res?.data) {
let {files = [], content} = res.data
content = content.replace(/(\r\n)|(\n)/g, "<br>")
this.info = {...res.data, content, files};
this.searchMeetinguser()
}
});
},
changeMeeting(title, id) {
this.$confirm(title, {
type: 'warning'
}).then(() => {
if (title == '是否立即发布会议?') {
this.fbMeeting();
} else if (title == '是否删除会议?') {
this.sdMeeting();
} else if (title == '是否取消会议?') {
this.qxMeeting();
} else if (title == '是否确认参会?') {
this.qrMeeting();
} else if (title == '是否删除此参会人员?') {
this.deleteMeetingPer(id)
}
})
},
deleteMeetingPer(id) {
this.instance.post(`/app/appmeetinguser/delete`, null,
{
params: {
id
}
}
).then(res => {
if (res && res.code == 0) {
this.$message.success("删除成功!");
this.searchMeetinguser();
}
});
},
queMeeting(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.instance.post(`/app/appmeetinginfo/absent`, null,
{
params: {
meetingId: this.detail.id,
reason: this.writeInfo.reason
}
}
).then(res => {
if (res && res.code == 0) {
this.innerVisible = false;
this.$parent.goBack();
}
});
} else {
return false;
}
});
},
qrMeeting() {
this.instance.post(`/app/appmeetinginfo/confirm`, null,
{
params: {
meetingId: this.detail.id
}
}
).then(res => {
if (res && res.code == 0) {
this.$message.success("确认参会成功!")
this.$parent.goBack();
}
});
},
noticeMetting(meetingUserId) {
this.instance.post(`/app/appmeetinginfo/notice`, null, {
params: {
meetingId: this.detail.id, noticeALL: !meetingUserId, meetingUserId
}
}).then(res => {
if (res && res.code == 0) {
this.$message.success("提醒成功!")
}
});
},
fbMeeting() {
this.instance.post(`/app/appmeetinginfo/release`, null,
{
params: {
meetingId: this.detail.id
}
}
).then(res => {
if (res && res.code == 0) {
this.$message.success("发布成功!")
this.$parent.goBack();
}
});
},
sdMeeting() {
this.instance.post(`/app/appmeetinginfo/delete`, null,
{
params: {
meetingId: this.detail.id
}
}
).then(res => {
if (res && res.code == 0) {
this.$message.success("删除成功!");
this.$parent.goBack();
}
});
},
qxMeeting() {
this.instance.post(`/app/appmeetinginfo/cancel`, null,
{
params: {
meetingId: this.detail.id
}
}
).then(res => {
if (res && res.code == 0) {
this.$message.success("取消会议成功!");
this.$parent.goBack();
}
});
},
editMeeting() {
this.$parent.goBack();
this.$emit('gotoEdit', this.info)
},
}
}
</script>
<style lang="scss" scoped>
.meetingDetail {
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 1);
.conference_top_area {
display: flex;
align-items: center;
}
.user-search {
width: 100%;
height: 48px;
display: flex;
justify-content: space-between;
align-items: center;
.float-right {
width: calc(100% - 200px);
text-align: right;
}
.input-162 {
display: inline-block;
width: 162px;
}
}
.content {
width: 1000px;
height: calc(100% - 50px);
overflow: hidden;
margin: auto;
display: flex;
justify-content: space-around;
.content-left {
width: 160px;
height: 100%;
.content-left-nav {
width: 158px;
background-color: #ffffff;
border-radius: 4px;
border: solid 1px #eeeeee;
margin-top: 56px;
overflow: hidden;
li {
height: 48px;
line-height: 48px;
padding-left: 24px;
font-size: 14px;
font-weight: normal;
font-stretch: normal;
letter-spacing: 0;
color: #666666;
cursor: pointer;
border-left: 3px solid transparent;
&:hover {
border-left: 3px solid #5088ff;
}
a {
display: block;
}
}
.navActive {
border-left: 3px solid #5088ff;
color: #5088ff;
}
}
}
.content-right {
width: 780px;
height: calc(100% - 80px);
overflow-y: auto;
margin-left: 40px;
box-sizing: border-box;
overflow-x: hidden;
.content-right-title {
width: 780px;
height: 56px;
margin-bottom: 16px;
box-shadow: inset 0px -1px 0px 0px #dad5d5;
display: flex;
justify-content: space-between;
align-items: center;
span {
display: block;
width: 150px;
height: 56px;
line-height: 56px;
color: #333333;
font-weight: bold;
&:nth-of-type(2) {
text-align: right;
width: 200px;
}
}
}
.flie {
width: 100%;
height: 40px;
line-height: 40px;
padding: 0 8px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
color: rgba(51, 51, 51, 1);
background: rgba(255, 255, 255, 1);
border-radius: 4px;
border: 1px solid rgba(208, 212, 220, 1);
margin-bottom: 16px;
cursor: pointer;
p {
display: flex;
justify-content: flex-start;
align-items: center
}
}
.meeting_name {
width: 100%;
padding-bottom: 25px;
font-size: 16px;
box-shadow: inset 0px -1px 0px 0px #dad5d5;
position: relative;
overflow: hidden;
.title {
color: #333333;
height: 28px;
line-height: 28px;
margin-left: 0;
font-weight: bold;
margin-top: 14px;
}
ul {
overflow: hidden;
li {
width: 33.3%;
float: left;
line-height: 28px;
font-size: 14px;
span {
width: 70px;
display: block;
float: left;
color: rgba(153, 153, 153, 1)
}
p {
width: calc(100% - 70px);
float: left;
color: rgba(51, 51, 51, 1);
}
;
}
}
.svg {
width: 88px;
height: 88px;
position: absolute;
right: -20px;
bottom: -20px;
}
}
}
}
}
</style>

View File

@@ -1,259 +0,0 @@
<template>
<ai-list isTabs>
<template #content>
<ai-search-bar>
<template #left>
<el-button type="primary" @click="addMetting()">发起会议</el-button>
<ai-select
v-model="search.meetingStatus"
@change="getTableData()"
placeholder="会议状态"
:selectList="dict.getDict($parent.name==0 ? 'meetingStatusSelect' : 'meetingStatus')"
></ai-select>
<ai-select
v-if="$parent.name==0"
v-model="search.confirmStatus"
@change="getTableData()"
placeholder="确认状态"
:selectList="dict.getDict('confirmStatus')"
></ai-select>
</template>
<template #right>
<el-input
v-model="search.param"
v-throttle="() => {search.current=1,getTableData()}"
@keyup.enter.native="search.current=1,getTableData()"
placeholder="会议标题/地点"
size="small" suffix-icon="iconfont iconSearch" clearable @clear="getTableData()"></el-input>
</template>
</ai-search-bar>
<div class="list_main">
<div class="no-data" style="height:160px;" v-if="tableData.length==0"></div>
<ul v-if="tableData.length>0">
<li v-for="(item,index) in tableData" :key="index" @click="goDetail(item)">
<p>
<span class="conference_title">{{ item.title }}</span>
<span class="time" v-if="item.status==1">{{ item.expirationTime }}</span>
</p>
<p style="width:80%;">
<el-row type="flex" align="middle" class="unit">发起人{{ item.userName }}
</el-row>
<el-row type="flex" align="middle" class="unit">发起单位{{ item.unitName }}
</el-row>
</p>
<p style="width:80%;">
<span class="unit">会议时间{{ item.startTime.substring(0, 16) + '至' + item.endTime.substring(0, 16) }}</span>
<el-tooltip :content="item.address" placement="top-start" effect="light">
<span class="unit" v-if="item.address.length>12">会议地点{{
item.address.substring(0, 12) + '....'
}}</span>
<span class="unit" v-else>会议地点{{ item.address }}</span>
</el-tooltip>
</p>
<!-- <p style="width:80%;">
<span
class="unit">会议时间{{
item.startTime.substring(0, 16) + '至' + item.endTime.substring(0, 16)
}}</span>
<el-tooltip :content="item.address" placement="top-start" effect="light">
<span class="unit address"
v-if="item.address.length>12">会议地点{{ item.address.substring(0, 12) + '....' }}</span>
<span class="unit address" v-else>会议地点{{ item.address }}</span>
</el-tooltip>
</p> -->
<h5 :class="{color0:item.status==0,color1:item.status==1,color2:item.status==2,color3:item.status==3,color4:item.status==4}">
<span v-if="item.status==0">草稿箱</span>
<span v-if="item.status==1">未开始</span>
<span v-if="item.status==2">进行中</span>
<span v-if="item.status==3">已取消</span>
<span v-if="item.status==4">已结束</span>
</h5>
<ai-icon class="svg" v-if="item.joinStatus==0" type="svg" icon="iconunidentified"/>
<ai-icon class="svg" v-else-if="item.joinStatus==1" type="svg" icon="iconidentified"/>
<ai-icon class="svg" v-else-if="item.joinStatus==2" type="svg" icon="iconyiqingjia"/>
<ai-icon class="svg" v-else-if="item.joinStatus==3" type="svg" icon="icondaiding"/>
</li>
</ul>
</div>
<div class="pagination" v-if="tableData.length>0">
<el-pagination
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
background
:current-page.sync="search.current"
:page-sizes="[5, 10, 50, 100,200]"
:page-size="search.size"
layout="total,prev, pager, next,sizes,jumper"
:total="total">
</el-pagination>
</div>
</template>
</ai-list>
</template>
<script>
export default {
name: "list",
props: {
instance: Function,
dict: Object,
permissions: Function,
listType: String
},
data() {
return {
search: {
meetingStatus: '',
confirmStatus: '',
param: '',
current: 1,
size: 10,
listType: ''
},
tableData: [],
total: 0,
}
},
methods: {
goDetail(item) {
this.$emit('goPage', {
row: item,
comp: 'detail'
});
},
addMetting() {
this.$emit('goPage', {
row: {},
comp: 'addMeeting'
});
},
getTableData() {
this.instance.post(`/app/appmeetinginfo/list`, null, {
params: {
...this.search,
listType: this.listType
}
}).then(res => {
if (res && res.data) {
this.tableData = res.data.records;
this.total = res.data.total;
}
});
},
handleCurrentChange(val) {
this.search.current = val;
this.getTableData();
},
handleSizeChange(val) {
this.search.size = val;
this.getTableData();
},
},
created() {
this.dict.load("confirmStatus", "meetingStatus", "meetingStatusSelect").then(_ => this.getTableData())
}
}
</script>
<style lang="scss" scoped>
.list_main {
width: 100%;
ul {
overflow: hidden;
padding: 0;
margin: 0;
li {
width: 100%;
height: 107px;
background: rgba(255, 255, 255, 1);
border-radius: 4px;
border: 1px solid rgba(216, 224, 232, 1);
box-sizing: border-box;
padding: 16px 16px 16px 50px;
margin-top: 8px;
cursor: pointer;
position: relative;
overflow: hidden;
p {
width: 100%;
height: 25px;
display: flex;
justify-content: space-between;
align-items: center;
.conference_title {
color: rgba(51, 51, 51, 1);
font-size: 16px;
font-weight: bold;
}
.time {
font-size: 14px;
color: #2266FF;
}
.unit {
font-size: 14px;
width: 50%;
}
}
h5 {
width: 100px;
height: 20px;
line-height: 20px;
text-align: center;
position: absolute;
left: -22px;
top: 10px;
box-sizing: border-box;
padding-right: 8px;
font-size: 12px;
transform: rotate(-45deg);
background: #FFF3E8;
color: rgba(255, 136, 34, 1);
box-shadow: -1px 1px 0px 0px rgba(216, 224, 232, 1), 1px -1px 0px 0px rgba(216, 224, 232, 1);
margin: 0;
}
.color0 {
color: #2244FF;
background: #EFF6FF;
}
.color1 {
background: #FFF3E8;
color: rgba(255, 136, 34, 1);
}
.color2 {
background: #EFF6FF;
color: #2266FF;
}
.color3 {
background-color: #D8E0E8;
color: #999999;
}
.color4 {
color: #2EA222;
background-color: #D8E0E8;
}
.svg {
width: 88px;
height: 88px;
position: absolute;
right: -20px;
bottom: -20px;
}
}
}
}
</style>

View File

@@ -1,186 +0,0 @@
<template>
<section class="AppCorporateSeal">
<ai-list v-if="showList">
<template slot="title">
<ai-title title="企业印章" :isShowBottomBorder="true"></ai-title>
</template>
<template slot="content">
<div class="signaturePane">
<div class="signatureCard" v-for="(op,i) in signatures" :key="i">
<div class="default" v-if="op.isDefault==1">默认</div>
<div class="body">
<el-image :src="`data:image/png;base64,${op.signSealData}`"/>
</div>
<div class="footer">
<el-button type="text" :disabled="op.isDefault==1" @click.stop="handleSetDefault(op.id)">设为默认</el-button>
<hr/>
<el-button type="text" :disabled="op.isDefault==1||op.signType==1" @click.stop="handleDelete(op.id)">删除
</el-button>
</div>
</div>
<div class="signatureCard add" @click="showList=false">
<ai-icon icon="iconAdd" size="32px"/>
<span>点击添加印章</span>
</div>
</div>
</template>
</ai-list>
<seal-detail v-else/>
</section>
</template>
<script>
import SealDetail from "./sealDetail";
export default {
name: "AppCorporateSeal",
label: "企业印章",
components: {SealDetail},
provide() {
return {
seal: this
}
},
props: {
instance: Function,
dict: Object,
permissions: Function
},
data() {
return {
signatures: [],
showList: true
}
},
created() {
this.getSignatures()
},
methods: {
getSignatures() {
this.instance.post("/app/syssignaccount/list", null, {
params: {
signType: 2,
size: 999
}
}).then(res => {
if (res?.data) {
this.signatures = res.data.records
}
})
},
handleSetDefault(id) {
this.$confirm("是否设置该印章为默认印章?").then(() => {
this.instance.post("/app/syssignaccount/default", null, {params: {id, listType: 1}}).then(res => {
if (res?.code == 0) {
this.$message.success("设置成功!")
this.getSignatures()
}
}).catch(() => 0)
})
},
handleDelete(ids) {
this.$confirm("是否删除该印章?").then(() => {
this.instance.post("/app/syssignaccount/delete", null, {params: {ids}}).then(res => {
if (res?.code == 0) {
this.$message.success("删除成功!")
this.getSignatures()
}
}).catch(() => 0)
})
},
}
}
</script>
<style lang="scss" scoped>
.AppCorporateSeal {
height: 100%;
background: #f3f6f9;
:deep( .signaturePane ){
display: flex;
gap: 16px;
flex-wrap: wrap;
padding: 16px;
.signatureCard {
width: 290px;
height: 258px;
background: #FFFFFF;
border-radius: 4px;
position: relative;
display: flex;
flex-direction: column;
overflow: hidden;
&.add {
justify-content: center;
align-items: center;
color: #666666;
cursor: pointer;
.AiIcon {
width: 32px;
height: 32px;
font-size: 32px;
}
& > span {
font-size: 12px;
line-height: 16px;
}
}
.default {
position: absolute;
width: 56px;
height: 24px;
background: #3573FF;
border-radius: 0 0 4px 0;
top: 0;
left: 0;
text-align: center;
line-height: 24px;
font-size: 12px;
color: #FFF;
}
.body {
min-height: 0;
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 50px;
cursor: pointer;
}
.footer {
flex-shrink: 0;
height: 40px;
background: rgba(#30426F, .5);
display: flex;
align-items: center;
padding: 8px 0;
box-sizing: border-box;
hr {
height: 100%;
border-color: rgba(#fff, .5);
}
& > .el-button {
flex: 1;
color: #fff;
&[disabled] {
color: rgba(#fff, .5);
}
}
}
}
}
}
</style>

View File

@@ -1,235 +0,0 @@
<template>
<section class="sealDetail">
<ai-detail>
<template #title>
<ai-title title="添加企业印章" isShowBack isShowBottomBorder @onBackClick="back"/>
</template>
<template #content>
<el-form size="small" ref="SealForm" :model="form" :rules="rules" label-suffix="" label-width="160px">
<ai-title title="单位信息" isShowBottomBorder/>
<el-form-item label="单位名称" prop="organizeName">
<el-input clearable v-model="form.organizeName" placeholder="请输入..." maxlength="25" show-word-limit/>
</el-form-item>
<el-form-item label="单位类型" prop="organizeType">
<el-select clearable v-model="form.organizeType" placeholder="请输入...">
<el-option v-for="(op,i) in $dict.getDict('organizeType')" :key="i" :value="op.dictValue"
:label="op.dictName"/>
</el-select>
</el-form-item>
<div class="flexFillRow">
<el-form-item label="企业注册类型" prop="organRegType">
<el-select clearable v-model="form.organRegType" placeholder="请输入...">
<el-option v-for="(op,i) in $dict.getDict('organRegType')" :key="i" :value="op.dictValue"
:label="op.dictName"/>
</el-select>
</el-form-item>
<el-form-item v-if="!!form.organRegType" :label="$dict.getLabel('organRegType',form.organRegType)"
prop="organCode">
<el-input clearable v-model="form.organCode" placeholder="请输入..."/>
</el-form-item>
<div v-else/>
</div>
<div class="flexFillRow">
<el-form-item label="注册类型" prop="registerType">
<el-select clearable v-model="form.registerType" placeholder="请输入...">
<el-option v-for="(op,i) in $dict.getDict('registerType')" :key="i" :value="op.dictValue"
:label="op.dictName"/>
</el-select>
</el-form-item>
<div/>
</div>
<template v-if="form.registerType==1">
<div class="flexFillRow">
<el-form-item label="代理人姓名" prop="agentName">
<el-input clearable v-model="form.agentName" placeholder="请输入..."/>
</el-form-item>
<el-form-item label="代理人身份证号" prop="agentIdNumber">
<el-input clearable v-model="form.agentIdNumber" placeholder="请输入..."/>
</el-form-item>
</div>
<div class="flexFillRow">
<el-form-item label="代理人手机号" prop="signPhone">
<el-input clearable v-model="form.signPhone" placeholder="请输入..."/>
</el-form-item>
<div/>
</div>
</template>
<template v-if="form.registerType==2">
<div class="flexFillRow">
<el-form-item label="法人姓名" prop="legalName">
<el-input clearable v-model="form.legalName" placeholder="请输入..."/>
</el-form-item>
<el-form-item label="法人身份证号" prop="legalIdNumber">
<el-input clearable v-model="form.legalIdNumber" placeholder="请输入..."/>
</el-form-item>
</div>
<div class="flexFillRow">
<el-form-item label="法人手机号" prop="signPhone">
<el-input clearable v-model="form.signPhone" placeholder="请输入..."/>
</el-form-item>
<div/>
</div>
</template>
<ai-title title="印章信息" isShowBottomBorder/>
<el-form-item class="sealImageTypes" label="生成印章类型" prop="organizeTemplateType">
<div v-for="(op,i) in sealImageTypes" :key="i" class="item" @click="form.organizeTemplateType=op.value">
<el-image :src="op.image" fit="contain"/>
<el-radio :label="op.value" v-model="form.organizeTemplateType">{{ op.name }}</el-radio>
</div>
</el-form-item>
<el-form-item label="横向文内容" prop="htext">
<el-input clearable v-model="form.htext" placeholder="0-8个字如合同专用章财务专用章等" maxlength="8" show-word-limit/>
</el-form-item>
<el-form-item label="下弦文内容" prop="qtext">
<el-input clearable v-model="form.qtext" placeholder="0-20个字下弦文是指的贵司公章底部一串防伪数字" maxlength="20"
show-word-limit/>
</el-form-item>
</el-form>
</template>
<template #footer>
<el-button @click="back">取消</el-button>
<el-button type="primary" @click="handleSubmit" v-loading="loading">提交</el-button>
</template>
</ai-detail>
</section>
</template>
<script>
import {ID} from "dui/lib/js/utils";
export default {
name: "sealDetail",
inject: ['seal'],
data() {
return {
form: {
organizeTemplateType: "STAR",
organizeType: "4",
organRegType: "NORMAL",
registerType: "1"
},
loading: false
}
},
computed: {
sealImageTypes() {
return this.$dict.getDict("organizeTemplateType")?.map(e => ({
image: e.dictColor,
value: e.dictValue,
name: e.dictName
})) || []
},
rules() {
return {
organizeName: [{required: true, message: "请填写单位名称"}],
organCode: [{required: true, message: `请填写${this.$dict.getLabel('organRegType', this.form.organRegType)}`}],
legalName: [{required: true, message: "请填写法人姓名"}],
agentName: [{required: true, message: "请填写代理人姓名"}],
organizeTemplateType: [{required: true}],
htext: [{required: true, message: "请填写横向文内容"}],
qtext: [{required: true, message: "请填写下弦文内容"}],
organizeType: [{required: true, message: "请选择单位类型"}],
organRegType: [{required: true, message: "请选择企业注册类型"}],
registerType: [{required: true, message: "请选择注册类型"}],
signPhone: [
{required: true, message: "请填写手机号码"},
{pattern: /^1[3456789]\d{9}$/, message: "手机号码格式有误"}
],
legalIdNumber: [
{required: true, message: "请填写法人身份证号码"},
{validator: (r, v, cb) => cb(ID.check(v) ? undefined : "身份证号码格式有误")}
],
agentIdNumber: [
{required: true, message: "请填写代理人身份证号码"},
{validator: (r, v, cb) => cb(ID.check(v) ? undefined : "身份证号码格式有误")}
],
}
}
},
created() {
this.$dict.load("registerType", "organRegType", "organizeType", "organizeTemplateType")
},
methods: {
back() {
this.seal.showList = true
this.seal.getSignatures()
},
handleSubmit() {
this.$refs.SealForm.validate(v => {
if (v) {
this.loading = true
this.seal.instance.post("/app/syssignaccount/register", {
userType: 0,
signType: 2,
signPhone: this.form.signPhone,
registerInfo: {...this.form, legalArea: 0},
style: {...this.form, sealColor: "RED"}
}).then(res => {
this.loading = false
if (res?.code == 0) {
this.$message.success("添加成功!")
this.back()
}
}).catch(() => this.loading = false)
}
})
}
}
}
</script>
<style lang="scss" scoped>
.sealDetail {
height: inherit;
:deep( .ai-detail__content--wrapper ){
.el-form {
display: flex;
flex-direction: column;
gap: 24px;
}
.el-form-item {
margin-bottom: 0;
.el-select {
width: 100%;
}
&.sealImageTypes > .el-form-item__content {
display: flex;
gap: 40px;
.item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
cursor: pointer;
.el-image {
width: 80px;
height: 80px;
}
}
}
}
.flexFillRow {
display: flex;
align-items: center;
& > * {
flex: 1;
min-width: 0;
}
}
}
:deep( .ai-detail__footer > .el-button ){
width: 92px;
height: 32px;
}
}
</style>

View File

@@ -1,365 +0,0 @@
<template>
<section class="personal-signature">
<ai-list v-if="!showPhonePage">
<ai-title slot="title" title="个人签名" :isShowBottomBorder="false"/>
<template #custom>
<div class="signaturePane">
<div class="signatureCard" v-for="(op,i) in signatures" :key="i">
<div class="default" v-if="op.isDefault==1">默认</div>
<div class="body">
<el-image :src="`data:image/png;base64,${op.signSealData}`"/>
</div>
<div class="footer">
<el-button type="text" :disabled="op.isDefault==1" @click.stop="handleSetDefault(op.id)">设为默认</el-button>
<hr/>
<el-button type="text" :disabled="op.isDefault==1||op.signType>0" @click.stop="handleDelete(op.id)">删除
</el-button>
</div>
</div>
<div class="signatureCard add" @click="dialog=true">
<ai-icon icon="iconAdd" size="32px"/>
<span>点击添加签名</span>
</div>
</div>
</template>
</ai-list>
<ai-dialog :visible.sync="dialog" v-bind="dialogConf" @onConfirm="handleSubmit"
@closed="form={},drawPlaceholder=true,qrCode=null,showQRCode=false,getSignatures()">
<ai-drawer v-if="hasAuthed" :seal.sync="sealData" ref="aiDrawer">
<template #tools>
<el-popover trigger="manual" v-model="showQRCode">
<el-image :src="qrCode"/>
<div class="writeInPhone" slot="reference" @click.stop="showQR">
<ai-icon icon="iconEwm"/>
<span>手机签名</span>
</div>
</el-popover>
</template>
</ai-drawer>
<el-form size="small" :model="form" ref="authForm" :rules="rules" class="authZone" v-else label-suffix=""
label-width="100px">
<el-alert type="warning" title="第一次添加个人签名,需先进行实名认证" show-icon :closable="false"/>
<el-form-item label="姓名" prop="personName">
<el-input v-model="form.personName" clearable placeholder="姓名"/>
</el-form-item>
<el-form-item label="身份证号" prop="idNumber">
<el-input v-model="form.idNumber" clearable placeholder="身份证号"/>
</el-form-item>
<el-form-item label="手机号码" prop="signPhone">
<el-input v-model="form.signPhone" clearable placeholder="手机号码"/>
</el-form-item>
</el-form>
</ai-dialog>
<draw-in-phone v-if="showPhonePage"/>
</section>
</template>
<script>
import {mapState} from "vuex";
import DrawInPhone from "./drawInPhone";
export default {
name: "AppPersonalSignature",
label: "个人签名",
components: {DrawInPhone},
provide() {
return {
signature: this
}
},
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
...mapState(['user']),
hasAuthed() {
return this.signatures.length > 0
},
dialogConf() {
return this.hasAuthed ? {
title: "手写签名",
width: '720px'
} : {
title: "实名认证",
width: '520px'
}
},
rules() {
return {
personName: [{required: true, message: "请填写姓名"}],
signPhone: [
{required: true, message: "请填写手机号码"},
{pattern: /^1[3456789]\d{9}$/, message: "手机号码格式有误"}
],
idNumber: [
{required: true, message: "请填写身份证号码"},
{validator: (r, v, cb) => cb(ID.check(v) ? undefined : "身份证号码格式有误")}
],
}
}
},
data() {
return {
signatures: [],
dialog: false,
form: {},
sealData: null,
qrCode: null,
showQRCode: false,
showPhonePage: false,
loading: false,
}
},
created() {
if (this.$route.query.userId && this.$route.hash == "#phone") {
this.showPhonePage = true
} else {
this.getSignatures()
}
},
methods: {
getSignatures() {
this.instance.post("/app/syssignaccount/list", null, {
params: {
size: 999
}
}).then(res => {
if (res?.data) {
this.signatures = res.data.records
}
})
},
handleSubmit() {
if (this.loading) return
if (this.hasAuthed && this.$refs['aiDrawer'].drawPlaceholder) return this.$message.error("请签名")
this.loading = true
if (this.hasAuthed) {
let sealData = this.sealData?.replace(/data:image\/png;base64,/, '')
sealData && this.instance({
url: '/app/syssignaccount/upload-sealdata',
headers: {"Content-Type": "application/json"},
method: 'post',
params: {userId: this.user.info.id},
data: sealData
}).then(res => {
this.loading = false
if (res?.code == 0) {
this.dialog = false
this.$message.success("添加成功!")
this.getSignatures()
}
}).catch(() => {
this.loading = false
})
} else {
this.$refs.authForm.validate(v => {
if (v) {
this.instance.post("/app/syssignaccount/register", {
signPhone: this.form.signPhone,
signType: 1,
userType: 0,
registerInfo: {...this.form},
style: {personTemplateType: 'RECTANGLE', sealColor: 'RED'}
}).then(res => {
this.loading = false
if (res?.code == 0) {
this.dialog = false
this.$message.success("认证成功!")
this.getSignatures()
}
}).catch(() => {
this.loading = false
})
}
})
}
},
handleSetDefault(id) {
this.$confirm("是否设置该签名为默认签名?").then(() => {
this.instance.post("/app/syssignaccount/default", null, {params: {id, listType: 0}}).then(res => {
if (res?.code == 0) {
this.$message.success("设置成功!")
this.getSignatures()
}
}).catch(() => 0)
})
},
handleDelete(ids) {
this.$confirm("是否删除该签名?").then(() => {
this.instance.post("/app/syssignaccount/delete", null, {params: {ids}}).then(res => {
if (res?.code == 0) {
this.$message.success("删除成功!")
this.getSignatures()
}
}).catch(() => 0)
})
},
showQR() {
if (!this.qrCode) {
let url = `${location.href}?userId=${this.user.info.id}#phone`
this.instance.post("/app/syssignaccount/draw-qrcode", null, {
params: {url}
}).then(res => {
if (res?.data) {
this.showQRCode = true
this.qrCode = res.data
}
})
} else {
this.showQRCode = !this.showQRCode
}
}
}
}
</script>
<style lang="scss" scoped>
.personal-signature {
height: 100%;
background: #f3f6f9;
overflow: auto;
:deep( .signaturePane ){
display: flex;
gap: 16px;
flex-wrap: wrap;
padding: 16px;
.signatureCard {
width: 290px;
height: 258px;
background: #FFFFFF;
border-radius: 4px;
position: relative;
display: flex;
flex-direction: column;
overflow: hidden;
&.add {
justify-content: center;
align-items: center;
color: #666666;
cursor: pointer;
.AiIcon {
width: 32px;
height: 32px;
font-size: 32px;
}
& > span {
font-size: 12px;
line-height: 16px;
}
}
.default {
position: absolute;
width: 56px;
height: 24px;
background: #3573FF;
border-radius: 0 0 4px 0;
top: 0;
left: 0;
text-align: center;
line-height: 24px;
font-size: 12px;
color: #FFF;
}
.body {
min-height: 0;
flex: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 25px;
pointer-events: none;
.el-image {
width: 100%;
height: 100%
}
}
.footer {
flex-shrink: 0;
height: 40px;
background: rgba(#30426F, .5);
display: flex;
align-items: center;
padding: 8px 0;
box-sizing: border-box;
hr {
height: 100%;
border-color: rgba(#fff, .5);
}
& > .el-button {
flex: 1;
color: #fff;
&[disabled] {
color: rgba(#fff, .5);
}
}
}
}
}
:deep( .writeInPhone ){
position: absolute;
width: 100px;
height: 32px;
background: rgba(#000, .5);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
color: rgba(#fff, .6);
cursor: pointer;
left: 16px;
top: 16px;
.AiIcon {
width: auto;
height: auto;
}
&:hover {
color: #fff;
}
}
:deep( .ai-dialog__wrapper ){
.ai-dialog__content--wrapper {
padding-right: 0 !important;
}
.el-dialog__body {
padding: 24px 0;
}
.authZone {
padding: 0 16px 24px;
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 24px;
.el-alert {
border: 1px solid #FF8822;
}
.el-form-item {
margin-bottom: 0;
}
}
}
}
</style>

View File

@@ -1,110 +0,0 @@
<template>
<section class="drawInPhone" @touchmove.prevent>
<div class="endPage" v-if="finished">操作结束请关闭页面</div>
<ai-drawer :seal.sync="sealData" placeholder="请签名" :width="device.width" :height="device.height">
<template #tools>
<div class="writeInPhone" slot="reference" @click.stop="handleSubmit">
<ai-icon icon="iconPublish"/>
<span>提交</span>
</div>
</template>
</ai-drawer>
</section>
</template>
<script>
export default {
name: "drawInPhone",
inject: ['signature'],
data() {
return {
sealData: null,
device: {width: 0, height: 0},
finished: false
}
},
created() {
this.device.width = document.body.clientWidth
this.device.height = document.body.clientHeight
window.onresize = () => {
this.device.width = document.body.clientWidth
this.device.height = document.body.clientHeight
}
},
methods: {
handleSubmit() {
let sealData = this.sealData?.replace(/data:image\/png;base64,/, ''),
{userId} = this.$route.query
if (!userId) return alert("缺少必要参数")
sealData && this.signature.instance({
url: '/app/syssignaccount/upload-sealdata',
headers: {"Content-Type": "application/json"},
method: 'post',
params: {userId},
data: sealData,
withoutToken: true
}).then(res => {
if (res?.code == 0) {
alert("添加成功!")
this.finished = true
}
})
},
}
}
</script>
<style lang="scss" scoped>
.drawInPhone {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 20210205932;
.endPage {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 20210205933;
background: rgba(#000, .8);
color: #999;
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
}
.AiDrawer {
margin: 0;
}
.writeInPhone {
position: absolute;
width: 72px;
height: 32px;
background: rgba(#000, .5);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
color: rgba(#fff, .6);
cursor: pointer;
left: 16px;
top: 16px;
.AiIcon {
width: auto;
height: auto;
}
&:hover {
color: #fff;
}
}
}
</style>

View File

@@ -1,66 +0,0 @@
<template>
<div class="doc-circulation ailist-wrapper">
<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'
export default {
name: 'AppBuddyMessage',
label: '好友欢迎语',
props: {
instance: Function,
dict: Object
},
data () {
return {
component: 'List',
params: {},
include: []
}
},
components: {
Add,
List
},
mounted () {
},
methods: {
onChange (data) {
if (data.type === 'Add') {
this.component = 'Add'
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">
.doc-circulation {
height: 100%;
background: #F3F6F9;
overflow: auto;
}
</style>

View File

@@ -1,380 +0,0 @@
<template>
<ai-detail>
<template slot="title">
<ai-title title="新建欢迎语" isShowBack isShowBottomBorder @onBackClick="cancel(false)"></ai-title>
</template>
<template slot="content">
<ai-card>
<template #title>
<div class="ai-card__title">
<h2>基本信息</h2>
<span>*一个成员如果被设置了多个欢迎语将会使用最新设置或修改的欢迎语</span>
</div>
</template>
<template #content>
<el-form class="ai-form" :rules="rules" ref="userForm" :model="form" label-width="100px" label-position="right">
<el-form-item label="使用成员" prop="users" style="width: 100%">
<el-input v-model="users" placeholder="请选择使用成员" disabled style="width: 100%">
<template #append>
<ai-user-selecter refs="addTags" :instance="instance" v-model="form.users">
<el-button size="small">选择</el-button>
</ai-user-selecter>
</template>
</el-input>
</el-form-item>
</el-form>
</template>
</ai-card>
<ai-card title="发送欢迎语">
<template #content>
<el-form class="ai-form" ref="form" :model="form" label-width="110px" label-position="right">
<el-form-item class="el-form-item__textarea" label="文本内容" prop="explain" style="width: 100%">
<span @click="insertNickname" class="el-form-item__btn" type="text">[插入居民昵称]</span>
<el-input type="textarea" placeholder="请输入…" v-model="form.content" maxlength="1000" :rows="5" show-word-limit></el-input>
</el-form-item>
<el-form-item label="其他类型" prop="explain" style="width: 100%">
<el-radio-group v-model="form.type" @change="onTypeChange">
<el-radio label="image">图片</el-radio>
<el-radio label="link">链接</el-radio>
<el-radio label="video">视频</el-radio>
<el-radio label="miniapp">小程序</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="图片" prop="files" style="width: 100%" v-if="form.type === 'image'">
<ai-uploader :instance="instance" isWechat v-model="form.files" :limit="1" url="/app/wxcp/upload/uploadFile?type=image"></ai-uploader>
</el-form-item>
<el-form-item label="链接" prop="linkUrl" style="width: 100%" v-if="form.type === 'link'">
<el-input placeholder="链接地址请以http或https开头" :rows="2" type="textarea" v-model="form.linkUrl"></el-input>
</el-form-item>
<el-form-item label="链接图片地址" prop="picUrl" style="width: 100%" v-if="form.type === 'link'">
<ai-uploader :instance="instance" v-model="form.picUrl" :limit="1"></ai-uploader>
</el-form-item>
<el-form-item label="链接标题" prop="title" style="width: 100%" v-if="form.type === 'link'">
<el-input placeholder="请输入链接标题" v-model="form.title"></el-input>
</el-form-item>
<el-form-item label="视频" prop="files" style="width: 100%" v-if="form.type === 'video'">
<ai-uploader :instance="instance" fileType="file" isWechat acceptType=".mp4" v-model="form.files" :limit="1" url="/app/wxcp/upload/uploadFile?type=video"></ai-uploader>
</el-form-item>
<!-- <el-form-item label="小程序" prop="applets" style="width: 100%" v-if="form.type === 'miniapp'">
<el-radio-group v-model="form.applets">
<div class="appletss">
<div class="applets-item" @click="form.applets = '0'" :class="[form.applets === '0' ? 'applets-active' : '']">
<el-radio label="0"></el-radio>
<img src="http://www.9665.com/uploadfile/2018/0607/20180607042142312.png">
<span>小程序</span>
</div>
<div class="applets-item" @click="form.applets = '1'" :class="[form.applets === '1' ? 'applets-active' : '']">
<el-radio label="1"></el-radio>
<img src="http://www.9665.com/uploadfile/2018/0607/20180607042142312.png">
<span>小程序</span>
</div>
<div class="applets-item" @click="form.applets = '2'" :class="[form.applets === '2' ? 'applets-active' : '']">
<el-radio label="2"></el-radio>
<img src="http://www.9665.com/uploadfile/2018/0607/20180607042142312.png">
<span>小程序</span>
</div>
</div>
</el-radio-group>
</el-form-item> -->
<el-form-item label="小程序标题" prop="title" style="width: 100%" v-if="form.type === 'miniapp'">
<el-input placeholder="请输入小程序标题" v-model="form.title"></el-input>
</el-form-item>
<el-form-item label="小程序APPID" prop="appid" style="width: 100%" v-if="form.type === 'miniapp'">
<el-input placeholder="请输入小程序APPID" v-model="form.appid"></el-input>
</el-form-item>
<el-form-item label="小程序跳转页面" prop="page" style="width: 100%" v-if="form.type === 'miniapp'">
<el-input placeholder="如pages/home/Home" v-model="form.page"></el-input>
</el-form-item>
<el-form-item label="小程序图片" prop="files" style="width: 100%" v-if="form.type === 'miniapp'">
<ai-uploader :instance="instance" v-model="form.files" isWechat :limit="1" url="/app/wxcp/upload/uploadFile?type=image"></ai-uploader>
</el-form-item>
</el-form>
</template>
</ai-card>
</template>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="confirm">提交</el-button>
</template>
</ai-detail>
</template>
<script>
const validateUser = (rule, value, callback) => {
if (!value.length) {
callback(new Error('请选择使用成员'))
} else {
callback()
}
}
export default {
name: 'Add',
props: {
instance: Function,
dict: Object,
params: Object
},
data () {
return {
isShow: false,
info: {},
form: {
users: [],
appId: '',
page: '',
title: '',
miniappImg: [],
picUrl: [],
content: '',
files: [],
linkUrl: '',
isRemind: true,
type: 'text'
},
rules: {
users: [
{ required: true, message: '请选择使用成员', trigger: 'change' },
{ validator: validateUser, trigger: 'change' }
]
}
}
},
computed: {
users () {
return this.form.users.map(v => v.name).join(',')
}
},
created () {
},
methods: {
getInfo (id) {
this.instance.post(`/app/appleavemessage/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.info = res.data
this.info.appLeaveMessageReplyList = res.data.appLeaveMessageReplyList.map(item => {
item.images = JSON.parse(item.images).map(item => {
return {
...item,
url: item.accessUrl
}
})
return item
})
}
})
},
validateUser (rule, value, callback) {
if (!value.length) {
callback(new Error('请选择使用成员'))
} else {
callback()
}
},
onClose () {
this.form.explain = ''
},
onTypeChange () {
this.form.files = []
this.form.picUrl = []
this.form.appId = ''
this.form.page = ''
this.form.title = ''
this.form.miniappImg = []
this.form.linkUrl = ''
},
insertNickname () {
this.form.content = this.form.content + '[用户昵称]'
},
onChange () {
},
confirm () {
this.$refs.userForm.validate((valid) => {
if (valid) {
if (this.form.type === 'text' && !this.form.content) {
return this.$message.error('请输入消息内容')
}
if (this.form.type === 'image' && !this.form.files.length) {
return this.$message.error('请上传图片')
}
// if (this.form.type === 'file' && !this.form.files.length) {
// return this.$message.error('请上传附件')
// }
if (this.form.type === 'video' && !this.form.files.length) {
return this.$message.error('请上传视频')
}
if (this.form.type === 'link' && !this.form.linkUrl) {
return this.$message.error('请输入链接')
}
if (this.form.type === 'link' && !this.form.picUrl.length) {
return this.$message.error('请输入链接图片')
}
if (this.form.type === 'link' && !this.form.title) {
return this.$message.error('请输入链接标题')
}
if (this.form.type === 'miniapp' && !this.form.title) {
return this.$message.error('请输入小程序标题')
}
if (this.form.type === 'miniapp' && !this.form.appid) {
return this.$message.error('请输入小程序appid')
}
if (this.form.type === 'miniapp' && !this.form.page) {
return this.$message.error('请输入小程序page')
}
if (this.form.type === 'miniapp' && !this.form.files.length) {
return this.$message.error('请上传小程序图片')
}
this.instance.post(`/app/wxcp/wxwelcomeword/add`, {
type: '1',
content: this.form.content || '',
isNotify: '0',
media: {
createdAt: this.form.files.length ? this.form.files[0].media.createdAt : '',
file: this.form.files.length ? this.form.files[0] : {},
mediaId: this.form.files.length ? this.form.files[0].media.mediaId : '',
sysFileId: this.form.files.length ? this.form.files[0].id : '',
type: this.form.type,
linkUrl: this.form.type === 'link' && this.form.linkUrl ? this.form.linkUrl : '',
title: this.form.title,
appId: this.form.appid,
page: this.form.page,
picUrl: this.form.type === 'link' ? this.form.picUrl[0].url : ''
},
users: this.form.users.map(item => {
return {
type: 0,
objectId: item.id,
objectName: item.name
}
})
}).then(res => {
if (res.code == 0) {
this.$message.success('提交成功')
setTimeout(() => {
this.cancel(true)
}, 600)
}
})
}
})
},
cancel (isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: isRefresh ? true : false
})
}
}
}
</script>
<style scoped lang="scss">
.ai-card__title {
display: flex;
align-items: center;
h2 {
margin-right: 20px;
color: #222222;
font-size: 16px;
font-weight: 700;
}
span {
color: #888888;
font-size: 14px;
}
}
.appletss {
display: flex;
flex-wrap: wrap;
.applets-item {
display: flex;
align-items: center;
width: 400px;
height: 60px;
margin-right: 8px;
margin-bottom: 8px;
padding: 0 17px;
background: #FFFFFF;
border-radius: 2px;
border: 1px solid #D0D4DC;
cursor: pointer;
&:hover {
border-color: #2266FF;
}
:deep( ){
.el-radio__label {
display: none;
}
}
img {
width: 40px;
height: 40px;
margin: 0 8px 0 13px;
}
span {
color: #222222;
font-size: 12px;
}
.el-radio {
margin: 0;
}
&.applets-active {
border-color: #2266FF;
}
}
}
.tips {
position: relative;
top: 2px;
padding-left: 8px;
color: #222222;
font-size: 14px;
}
.el-form-item-item__textarea {
position: relative;
}
.el-form-item__btn {
position: absolute;
bottom: 12px;
left: 12px;
line-height: 1;
z-index: 1;
color: #2266FF;
font-size: 14px;
user-select: none;
background: #fff;
cursor: pointer;
}
</style>

View File

@@ -1,235 +0,0 @@
<template>
<ai-list class="message">
<template slot="title">
<ai-title title="好友欢迎语" isShowBottomBorder></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>
</template>
<template slot="right">
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 6px;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="type" width="240px" label="消息内容" align="center">
<template slot-scope="{ row }">
<el-popover
placement="bottom"
width="400"
:visible-arrow="false"
popper-class="wechat-message__container"
trigger="hover">
<div class="count" slot="reference">{{ row.count }}</div>
<div class="message-info">
<h2 v-if="row.content" :style="{marginBottom: row.media ? '16px' : '0'}">{{ row.content }}</h2>
<div class="message-info__wrapper" v-if="row.media && (row.media.file || row.media.type === 'link')">
<img v-if="row.media.type === 'image' || row.media.type === 'miniapp'" :src="row.media.file.url">
<video v-if="row.media.type === 'video'" :src="row.media.file.url"></video>
<img v-if="row.media.type === 'link'" :src="row.media.picUrl">
<div class="message-info__wrapper--right">
<h3 v-if="row.media.type === 'miniapp'">{{ row.media.title }}</h3>
<h3 v-if="row.media.type === 'image'">{{ row.media.file.name }}</h3>
<h3 v-if="row.media.type === 'link'">{{ row.media.linkUrl }}</h3>
<h3 v-if="row.media.type === 'video'">{{ row.media.file.name }}</h3>
<p v-if="row.media.type === 'image'">{{ row.media.file.fileSizeStr }}</p>
<p v-if="row.media.type === 'link'">{{ row.media.title }}</p>
<p v-if="row.media.type === 'video'">{{ row.media.file.fileSizeStr }}</p>
</div>
</div>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column slot="options" width="120px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<!-- <el-button type="text" disabled @click="toAdd(row.id)" title="编辑">编辑</el-button> -->
<el-button type="text" @click="remove(row.id)" title="删除">删除</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,
content: ''
},
currIndex: 0,
total: 10,
colConfigs: [
{ prop: 'type', label: '类型', align: 'left', width: '160',
render: (h, params) => {
let str = ''
if (params.row.content) {
str = '文字'
}
if (params.row.media && params.row.media.type === 'link') {
str += this.dict.getLabel('wxMsgType', params.row.media.type) ? (params.row.content ? '+' : '') + this.dict.getLabel('wxMsgType', params.row.media.type) : ''
}
if (params.row.media && params.row.media.file) {
str += this.dict.getLabel('wxMsgType', params.row.media.type) ? (params.row.content ? '+' : '') + this.dict.getLabel('wxMsgType', params.row.media.type) : ''
}
return h('span', {
style: {
}
}, str)
}
},
{ slot: 'type' },
{ prop: 'users', label: '使用成员', align: 'left', format: v => v.join(';') },
{ prop: 'createUser', label: '创建人' },
{ prop: 'createTime', label: '编辑时间' },
{ slot: 'options', label: '操作' }
],
tableData: []
}
},
mounted () {
this.dict.load(['wxMsgType']).then(() => {
this.getList()
})
},
methods: {
getList() {
this.instance.post(`/app/wxcp/wxwelcomeword/list`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records.map(item => {
let count = 0
if (item.content) {
count = count + 1
}
if (item.media && item.media.type !== 'text') {
count = count + 1
}
item.count = count
return item
})
this.total = res.data.total
}
})
},
remove (id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/wxcp/wxwelcomeword/delete?id=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toAdd (id) {
this.$emit('change', {
type: 'Add',
params: {
id
}
})
}
}
}
</script>
<style lang="scss" scoped>
.message {
.count {
cursor: pointer;
color: #2266FF;
font-size: 14px;
&:hover {
opacity: 0.6;
}
}
}
.message-info {
padding: 8px;
min-height: 116px;
h2 {
color: #222222;
font-weight: 500;
font-size: 14px;
}
.message-info__wrapper {
display: flex;
align-items: center;
width: 368px;
height: 60px;
padding: 10px;
background: #FFFFFF;
border-radius: 2px;
border: 1px solid #D0D4DC;
.message-info__wrapper--right {
flex: 1;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
h3 {
width: 100%;
color: #222222;
font-size: 14px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
font-weight: normal;
}
img, video {
width: 40px;
height: 40px;
margin-right: 10px;
object-fit: cover;
}
p {
margin-top: 6px;
font-size: 14px;
color: #888888;
}
}
}
</style>

View File

@@ -1,47 +0,0 @@
<template>
<div class="doc-circulation ailist-wrapper">
<keep-alive :include="['List']">
<component ref="component" :is="component" :instance="instance" :dict="dict"></component>
</keep-alive>
</div>
</template>
<script>
import List from './components/List'
export default {
name: 'AppMaterialLibrary',
label: '素材库',
props: {
instance: Function,
dict: Object
},
data () {
return {
component: 'List',
params: {},
include: []
}
},
components: {
List
},
mounted () {
},
methods: {
}
}
</script>
<style lang="scss">
.doc-circulation {
height: 100%;
background: #F3F6F9;
overflow: auto;
}
</style>

View File

@@ -1,212 +0,0 @@
<template>
<div id="ChooseMaterial">
<ai-dialog
:visible.sync="isShow"
width="890px"
@onConfirm="onConfirm"
title="选择素材">
<div class="AppMaterialLibrary-title">
<span
v-for="(item, index) in typeList"
:key="index"
:class="[currIndex === index ? 'active' : '']" @click="currIndex = index, search.current = 1, getList()">
{{ item }}
</span>
</div>
<ai-search-bar class="search-bar">
<template #left>
</template>
<template slot="right">
<el-input
v-model="search.title"
size="small"
v-throttle="() => { search.current = 1, getList() }"
placeholder="请输入标题、话术内容、添加人"
clearable
@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"
style="margin-top: 6px; width: 100%;"
:current.sync="search.current"
:size.sync="search.size"
@selection-change="v=> ids = v.map((e) => e.id)"
@getList="getList">
</ai-table>
</ai-dialog>
</div>
</template>
<script>
export default {
name: 'ChooseMaterial',
props: {
instance: Function
},
data() {
return {
search: {
current: 1,
size: 10,
title: '',
},
ids: [],
isShow: false,
id: '',
typeList: ['话术', '图片', '小程序', '文件', '视频', '网页'],
currIndex: 0,
tableData: [],
total: 0
}
},
computed: {
mpTitle () {
return {
'0': '话术标题',
'1': '图片名称',
'2': '小程序标题',
'3': '文件名称',
'4': '视频名称',
'5': '网页名称'
}[this.currIndex]
},
colConfigs () {
if (this.currIndex === 0) {
return [
{ type: 'selection' },
{ prop: 'title', label: this.mpTitle },
{ prop: 'content', label: '话术内容', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ prop: 'createTime', label: '添加时间', align: 'center' }
]
}
if (this.currIndex === 2) {
return [
{ type: 'selection' },
{ prop: 'title', label: this.mpTitle },
{ prop: 'appId', label: '小程序APPID', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ prop: 'createTime', label: '添加时间', align: 'center' }
]
}
if (this.currIndex === 5) {
return [
{ type: 'selection' },
{ prop: 'title', label: this.mpTitle },
{ prop: 'pagePath', label: '外链网页', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ prop: 'createTime', label: '添加时间', align: 'center' }
]
}
return [
{ type: 'selection' },
{ prop: 'title', label: this.mpTitle },
{ prop: 'fileSizeStr', label: '文件大小', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ prop: 'createTime', label: '添加时间', align: 'center' }
]
}
},
created () {
this.getList()
},
methods: {
getList() {
this.instance.post(`/app/appmaterialinfo/list`, null, {
params: {
...this.search,
type: this.currIndex
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
onConfirm () {
if (!this.ids.length) {
return this.$message.error('请选择素材')
}
if (this.ids.length > 1) {
return this.$message.error('素材不能多选')
}
},
open () {
this.isShow = true
},
close () {
this.isShow = false
}
}
}
</script>
<style lang="scss" scoped>
#ChooseMaterial {
:deep( .ai-list__content--right-wrapper ) {
padding: 0 20px!important;
}
:deep( .el-dialog__header ) {
display: none;
}
:deep( .el-dialog__body ) {
padding-top: 0!important;
}
.AppMaterialLibrary-title {
display: flex;
align-items: center;
margin-bottom: 20px;
border-bottom: 1px solid #eee;
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;
}
}
}
}
</style>

View File

@@ -1,394 +0,0 @@
<template>
<ai-list id="AppMaterialLibrary">
<template slot="title">
<ai-title title="素材管理" isShowBottomBorder>
<template #sub>
<span>可在聊天素材宣发工具中选择素材发送给居民提升服务效率其中文件视频和网页素材支持记录居民查看行为</span>
</template>
</ai-title>
</template>
<template slot="content">
<div class="AppMaterialLibrary-title">
<span
v-for="(item, index) in typeList"
:key="index"
:class="[currIndex === index ? 'active' : '']" @click="currIndex = index, search.current = 1, getList()">
{{ item }}
</span>
</div>
<ai-search-bar class="search-bar">
<template #left>
<el-button size="small" type="primary" icon="iconfont iconAdd" @click="isShow = true">添加{{ typeList[currIndex] }}</el-button>
</template>
<template slot="right">
<el-input
v-model="search.title"
size="small"
v-throttle="() => { search.current = 1, getList() }"
placeholder="请输入标题、话术内容、添加人"
clearable
@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"
style="margin-top: 6px; width: 100%;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="options" width="120px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="id = row.id, toAdd(row)">编辑</el-button>
<el-button type="text" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
<ai-dialog
:visible.sync="isShow"
width="920px"
:title="(id ? '编辑' : '添加') + typeList[currIndex]"
@close="onClose"
:close-on-click-modal="false"
destroy-on-close
@onConfirm="confirm">
<el-form ref="form" v-if="isShow" :model="form" label-width="130px" label-position="right">
<div class="ai-form">
<el-form-item v-if="currIndex === 0" label="话术标题" style="width: 100%;" prop="title" :rules="[{ required: true, message: '请输入话术标题', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入话术标题"
maxlength="42"
show-word-limit
v-model="form.title">
</el-input>
</el-form-item>
<el-form-item v-if="currIndex === 0" label="话术内容" style="width: 100%;" prop="content" :rules="[{ required: true, message: '请输入话术内容', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入话术内容"
maxlength="682"
show-word-limit
v-model="form.content">
</el-input>
</el-form-item>
<el-form-item v-if="currIndex === 1" label="图片名称" style="width: 100%;" prop="title" :rules="[{ required: true, message: '请输入图片名称', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入图片名称"
maxlength="42"
show-word-limit
v-model="form.title">
</el-input>
</el-form-item>
<el-form-item v-if="currIndex === 1" label="上传图片" style="width: 100%;" prop="fileUrl" :rules="[{ required: true, message: '请上传', trigger: 'change' }]">
<ai-uploader :instance="instance" url="/admin/file/add3?type=image" v-model="form.fileUrl" :limit="1"></ai-uploader>
</el-form-item>
<el-form-item v-if="currIndex === 3" label="文件名称" style="width: 100%;" prop="title" :rules="[{ required: true, message: '请输入文件名称', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入图片名称"
maxlength="42"
show-word-limit
v-model="form.title">
</el-input>
</el-form-item>
<el-form-item v-if="currIndex === 3" label="上传文件" style="width: 100%;" prop="fileUrl" :rules="[{ required: true, message: '请上传', trigger: 'change' }]">
<ai-uploader fileType="file" url="/admin/file/add3?type=file" :instance="instance" v-model="form.fileUrl" :limit="1"></ai-uploader>
</el-form-item>
<el-form-item v-if="currIndex === 4" label="视频标题" style="width: 100%;" prop="title" :rules="[{ required: true, message: '请输入视频标题', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入视频标题"
maxlength="42"
show-word-limit
v-model="form.title">
</el-input>
</el-form-item>
<el-form-item v-if="currIndex === 4" label="上传视频" style="width: 100%;" prop="fileUrl" :rules="[{ required: true, message: '请上传', trigger: 'change' }]">
<ai-uploader :instance="instance" url="/admin/file/add3?type=video" fileType="file" v-model="form.fileUrl" :limit="1"></ai-uploader>
</el-form-item>
<el-form-item v-if="currIndex === 4" label="视频封面图" prop="pictureUrl" style="width: 100%;" :rules="[{ required: true, message: '请上传视频封面图', trigger: 'change' }]">
<ai-uploader :instance="instance" url="/admin/file/add3?type=image" v-model="form.pictureUrl" :limit="1"></ai-uploader>
</el-form-item>
<el-form-item v-if="currIndex === 4" label="视频描述" style="width: 100%;" prop="content" :rules="[{ required: true, message: '请输入视频描述', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入视频描述"
maxlength="682"
show-word-limit
v-model="form.content">
</el-input>
</el-form-item>
<el-form-item v-if="currIndex === 5" label="网页标题" style="width: 100%;" prop="title" :rules="[{ required: true, message: '请输入网页标题', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入网页标题"
maxlength="42"
show-word-limit
v-model="form.title">
</el-input>
</el-form-item>
<el-form-item v-if="currIndex === 5" label="网页链接" style="width: 100%;" prop="pagePath" :rules="[{ required: true, message: '请输入网页链接链接', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入网页链接链接"
maxlength="682"
show-word-limit
v-model="form.pagePath">
</el-input>
</el-form-item>
<el-form-item v-if="form.currIndex === 5" label="网页描述" style="width: 100%;" prop="content">
<el-input
size="small"
placeholder="请输入网页描述"
maxlength="170"
show-word-limit
v-model="form.content">
</el-input>
</el-form-item>
<el-form-item v-if="currIndex === 5" label="网页封面图" prop="pictureUrl" style="width: 100%;" :rules="[{ required: true, message: '请上传封面图', trigger: 'change' }]">
<ai-uploader :instance="instance" url="/admin/file/add3?type=image" v-model="form.pictureUrl" :limit="1"></ai-uploader>
</el-form-item>
<el-form-item v-if="currIndex === 2" label="小程序标题" style="width: 100%;" prop="title" :rules="[{ required: true, message: '请输入小程序标题', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入小程序标题"
maxlength="20"
show-word-limit
v-model="form.title">
</el-input>
</el-form-item>
<el-form-item v-if="currIndex === 2" label="小程序appid" style="width: 100%;" prop="appId" :rules="[{ required: true, message: '小程序appid', trigger: 'blur' }]">
<el-input
size="small"
placeholder="小程序appid"
v-model="form.appId">
</el-input>
</el-form-item>
<el-form-item v-if="currIndex === 2" label="小程序路径" style="width: 100%;" prop="pagePath" :rules="[{ required: true, message: '请输入小程序路径', trigger: 'blur' }]">
<el-input
size="small"
placeholder="请输入小程序page路径"
v-model="form.pagePath">
</el-input>
</el-form-item>
<el-form-item v-if="currIndex === 2" label="小程序封面图" prop="pictureUrl" style="width: 100%;" :rules="[{ required: true, message: '请上传封面图', trigger: 'change' }]">
<ai-uploader :instance="instance" url="/admin/file/add3?type=image" v-model="form.pictureUrl" :limit="1"></ai-uploader>
</el-form-item>
</div>
</el-form>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
export default {
name: 'List',
props: {
instance: Function,
dict: Object
},
data() {
return {
search: {
current: 1,
size: 10,
title: '',
},
isShow: false,
form: {
appId: '',
content: '',
fileUrl: [],
pagePath: '',
pictureUrl: [],
title: ''
},
id: '',
typeList: ['话术', '图片', '小程序', '文件', '视频', '网页'],
currIndex: 0,
tableData: [],
total: 0
}
},
computed: {
mpTitle () {
return {
'0': '话术标题',
'1': '图片名称',
'2': '小程序标题',
'3': '文件名称',
'4': '视频名称',
'5': '网页名称'
}[this.currIndex]
},
colConfigs () {
if (this.currIndex === 0) {
return [
{ prop: 'title', label: this.mpTitle },
{ prop: 'content', label: '话术内容', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ prop: 'createTime', label: '添加时间', align: 'center' }
]
}
if (this.currIndex === 2) {
return [
{ prop: 'title', label: this.mpTitle },
{ prop: 'appId', label: '小程序APPID', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ prop: 'createTime', label: '添加时间', align: 'center' }
]
}
if (this.currIndex === 5) {
return [
{ prop: 'title', label: this.mpTitle },
{ prop: 'pagePath', label: '外链网页', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ prop: 'createTime', label: '添加时间', align: 'center' }
]
}
return [
{ prop: 'title', label: this.mpTitle },
{ prop: 'fileSizeStr', label: '文件大小', align: 'center' },
{ prop: 'createUserName', label: '添加人', align: 'center' },
{ prop: 'createTime', label: '添加时间', align: 'center' }
]
}
},
created () {
this.getList()
},
methods: {
getList() {
this.instance.post(`/app/appmaterialinfo/list`, null, {
params: {
...this.search,
type: this.currIndex
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
toAdd (e) {
this.form = {
...this.form,
...e,
fileUrl: e.fileUrl ? [{
url: e.fileUrl
}] : '',
pictureUrl: e.pictureUrl ? [{
url: e.pictureUrl
}] : ''
}
this.isShow = true
},
onClose () {
this.form.content = ''
this.form.appId = ''
this.form.fileUrl = []
this.form.pagePath = ''
this.form.pictureUrl = []
this.form.title = ''
this.id = ''
},
confirm () {
this.$refs.form.validate((valid) => {
if (valid) {
this.instance.post(`/app/appmaterialinfo/addOrUpdate`, {
...this.form,
type: this.currIndex,
fileUrl: this.form.fileUrl.length ? this.form.fileUrl[0].url : '',
fileId: this.form.fileUrl.length ? this.form.fileUrl[0].id : '',
pictureId: this.form.pictureUrl.length ? this.form.pictureUrl[0].id : '',
pictureUrl: this.form.pictureUrl.length ? this.form.pictureUrl[0].url : '',
fileSize: this.form.fileUrl.length ? this.form.fileUrl[0].fileSize : '',
}).then(res => {
if (res.code == 0) {
this.$message.success('提交成功')
this.isShow = false
this.getList()
}
})
}
})
},
remove (id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appmaterialinfo/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
}
}
}
</script>
<style lang="scss" scoped>
#AppMaterialLibrary {
:deep( .ai-list__content--right-wrapper ) {
padding: 0 20px!important;
}
.AppMaterialLibrary-title {
display: flex;
align-items: center;
margin-bottom: 20px;
border-bottom: 1px solid #eee;
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;
}
}
}
}
</style>

View File

@@ -1,617 +0,0 @@
<template>
<section style="height: 100%;">
<ai-list>
<template slot="title">
<ai-title title="话术库" :isShowBottomBorder="true"></ai-title>
</template>
<template #left>
<div class="left-tree">
<div class="tree-title">
<label>分组</label>
<el-button icon="iconfont iconAdd" style="height: 28px;line-height: 0;" @click="groupDialog = true">添加分组
</el-button>
</div>
<el-scrollbar style="height: calc(100% - 40px)">
<el-menu style="height: 100%;" default-active="0" active-text-color="#26f" text-color="#222"
mode="vertical">
<el-menu-item v-for="(item, index) in sortList" :key="index" class="menu-item" @click="currentMenu(item)"
:index="index.toString()">
<label class="item-title">{{ item.name }}</label>
<el-popover style="width: 30px" placement="left" trigger="click">
<div style="display: flex;flex-direction: column;align-items: center;">
<div style="cursor: pointer;" @click="editGroup(item, index)">编辑</div>
<div style="cursor: pointer;" @click="delteGroup(item, index)">删除</div>
</div>
<span class="iconfont iconMore" slot="reference"></span>
</el-popover>
</el-menu-item>
</el-menu>
</el-scrollbar>
</div>
</template>
<template slot="content">
<ai-search-bar>
<template slot="left">
<el-select v-model="search.contentType" placeholder="类型" size="small" clearable @change="getList()">
<el-option v-for="(item, index) in types" :key="index" :label="item.name" :value="item.value"></el-option>
</el-select>
</template>
<template slot="right">
<el-input v-model="search.title" class="search-input" size="small"
@keyup.enter.native=";(search.current = 1), getList()" placeholder="请输入标题或话术内容或创建人" clearable
@clear=";(search.current = 1), (search.title = ''), getList()" suffix-icon="iconfont iconSearch"/>
</template>
</ai-search-bar>
<ai-search-bar>
<template slot="left">
<el-button icon="iconfont iconAdd" type="primary" @click="add">添加话术</el-button>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :col-configs="colConfigs" :stripe="true" :total="total" ref="aitableex"
style="margin-top: 8px;" :current.sync="search.current" :size.sync="search.size" @getList="getList">\
<el-table-column slot="content" label="话术内容" align="left" width="280">
<template slot-scope="{ row }">
<div class="table-left__wrapper">
<video :src="row.file && row.file.url" v-if="row.file && row.contentType == 'video'"
class="media"></video>
<img :src="row.file && row.file.url" v-if="row.file && row.contentType == 'image'" class="media"/>
<audio :src="row.file && row.file.url" v-if="row.file && row.contentType == 'voice'"
class="media"></audio>
<div class="table-left__wrapper--right">
<el-tooltip class="item" effect="dark" :content="row.content" placement="top">
<div class="table-left__wrapper--text" v-if="row.contentType == 'text'">{{ row.content }}</div>
</el-tooltip>
<div class="ellipsis" v-if="row.contentType !== 'text' && row.file && row.file.name">{{ row.file.name }}</div>
<div v-if="row.contentType !== 'text' && row.file && row.file.size">{{ row.file.size | size }}KB</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" width="160px" align="center" fixed="right">
<template slot-scope="{ row }">
<span class="table-btn" title="编辑" @click="editTrick(row)">编辑</span>
<span class="table-btn" title="删除" @click="deleteTrick(row)">删除</span>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<ai-dialog :visible.sync="groupDialog" @closed="sortForm = {},sortForm.type=1" width="800px" title="添加分组"
@onConfirm="onConfirm">
<el-form size="small" label-width="100px" ref="groupForm" :model="sortForm" :rules="sortRules">
<el-form-item label="分组名称" prop="name">
<el-input clearable v-model.trim="sortForm.name" placeholder="请输入..." :maxlength="10" show-word-limit/>
</el-form-item>
<!-- <el-form-item label="可见范围" prop="type">-->
<!-- <el-radio-group v-model="sortForm.type">-->
<!-- <el-radio label="0">个人话术</el-radio>-->
<!-- <el-radio label="1">公共话术</el-radio>-->
<!-- </el-radio-group>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="可用部门" prop="documentName">-->
<!-- <el-row type="flex">-->
<!-- <div class="input"></div>-->
<!-- <ai-person-select :instance="instance" url="/app/appvillagecadres/list" :isMultiple="true"-->
<!-- btnText="选择" dialogTitle="选择">-->
<!-- <template name="option" v-slot:option="{ item }">-->
<!-- <span class="iconfont iconProlife">{{ item.name }}</span>-->
<!-- <ai-id mode="show" :show-eyes="false" :value="item.idNumber"/>-->
<!-- </template>-->
<!-- </ai-person-select>-->
<!-- </el-row>-->
<!-- </el-form-item>-->
</el-form>
</ai-dialog>
<ai-dialog :visible.sync="trickDialog" @close="onClose" @onConfirm="trickConfirm" width="800px" title="添加话术">
<el-form size="small" label-width="100px" ref="trickFom" :model="trickForm" :rules="trickRules">
<!-- <el-form-item label="分组" prop="handleResult">-->
<!-- <ai-select-->
<!-- v-model="search.type"-->
<!-- @change="search.current = 1, getList()"-->
<!-- placeholder="类型"-->
<!-- :selectList="sortList"-->
<!-- ></ai-select>-->
<!-- </el-form-item>-->
<el-form-item label="标题" prop="title">
<el-input clearable placeholder="请输入..." v-model="trickForm.title" show-word-limit :maxlength="20"/>
</el-form-item>
<el-form-item label="话术类型" prop="contentType">
<el-radio-group v-model="trickForm.contentType" @change="onChange">
<el-radio :label="item.value" v-for="(item, index) in types" :key="index">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="文本内容" class="el-form-item__textarea" prop="content" v-if="trickForm.contentType == 'text'">
<span @click="insertNickname" class="el-form-item__btn" type="text">[插入用户昵称]</span>
<span @click="insertRemark" class="el-form-item__remark" type="text">[插入备注名]</span>
<el-input type="textarea" v-model="trickForm.content" placeholder="请输入..." :rows="5" show-word-limit
:maxlength="255"/>
</el-form-item>
<el-form-item :label="compLabel" prop="fileId" v-else>
<ai-uploader :instance="instance" v-model="fileList" :acceptType="acceptType"
:url="'/app/wxcp/upload/uploadFile?type=' + trickForm.contentType" isWechat :fileType="fileType"
:limit="1" @change="change"></ai-uploader>
</el-form-item>
</el-form>
</ai-dialog>
</section>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'AppVerbalTrick',
label: '话术库',
props: {
instance: Function,
dict: Object,
permissions: Function,
},
data() {
return {
groupDialog: false,
trickDialog: false,
search: {
current: 1,
size: 10,
contentType: '',
},
sortForm: {
name: '',
type: '1',
},
trickForm: {
title: '',
contentType: 'text',
content: '',
fileId: '',
mediaId: '',
},
row: {},
current: {},
sortList: [],
total: 0,
tableData: [],
fileList: [],
}
},
computed: {
...mapState(['user']),
acceptType() {
return {
"image": ".jpg,.png,.jpeg",
"video": ".mp4"
}[this.trickForm.contentType]
},
compLabel() {
return this.types.find((e) => e.value == this.trickForm.contentType)?.name
},
fileType() {
return this.trickForm.contentType == 'image' ? 'img' : 'file'
},
types() {
return [
{name: '文本', value: 'text'},
{name: '图片', value: 'image'},
{name: '视频', value: 'video'},
// // {name: "附件", value: "file"},
// {name: "音频", value: "voice"},
]
},
trickRules() {
return {
title: [{required: true, message: '请输入标题', trigger: 'blur'}],
contentType: [{required: true, message: '请选择话术类型', trigger: 'change'}],
content: [{required: true, validator: (rule, value, callback)=>{
if(this.trickForm.contentType == 'text' && !value){
return callback("请输入文本内容")
}else {
callback()
}
}}],
fileId: [{required: true, validator: (rule, value, callback)=>{
if(this.trickForm.contentType != 'text' && !value){
return callback("请上传文件")
}else {
callback()
}
}}],
}
},
// message() {
// return (() => {
// if (this.trickForm.contentType == 'text') {
// return '请输入文本内容'
// } else if (this.trickForm.contentType == 'image') {
// return '请上传图片'
// } else if (this.trickForm.contentType == 'file') {
// return '请上传文件'
// } else if (this.trickForm.contentType == 'video') {
// return '请上传视频'
// } else if (this.trickForm.contentType == 'voice') {
// return '请上传音频'
// }
// })()
// },
sortRules() {
return {
name: [{required: true, message: '请输入分组名称', trigger: 'blur'}],
type: [{required: true, message: '请选择可见范围', trigger: 'change'}],
}
},
colConfigs() {
return [
{slot: 'type'},
{slot: 'content'},
{prop: 'title', label: '标题'},
{
prop: 'contentType',
label: '类型',
align: 'center',
render: (h, {row}) => {
return h('span', {}, this.types.find((e) => row.contentType == e.value)?.name)
},
},
{prop: 'userName', label: '创建人', align: 'center'},
{prop: 'createTime', label: '创建时间', align: 'center'},
{slot: 'options', label: '操作', align: 'center'},
]
},
},
created() {
this.getSortList()
},
filters: {
size(size) {
return (size / 1024).toFixed(1)
},
},
methods: {
insertNickname() {
this.trickForm.content += '[用户昵称]'
},
insertRemark() {
this.trickForm.content += '[备注名]'
},
onClose() {
this.trickForm.title = ''
this.trickForm.content = ''
this.trickForm.fileId = ''
this.trickForm.mediaId = ''
this.trickForm.createdAt = ''
this.trickForm.contentType = 'text'
this.fileList = []
},
onChange() {
this.trickForm.fileId = ''
this.trickForm.content = ''
this.trickForm.mediaId = ''
this.trickForm.createdAt = ''
this.fileList = []
},
add() {
if (this.sortList.length == 0) return this.$message.error('请先添加分组')
this.$nextTick(() => {
this.trickDialog = true
})
},
change(e) {
if(e.length){
this.trickForm.fileId = e[0].id
this.trickForm.mediaId = e[0].media.mediaId
this.trickForm.createdAt = e[0].media.createdAt
}else {
this.trickForm.fileId =""
this.trickForm.mediaId = ""
this.trickForm.createdAt = ""
}
},
editTrick(row) {
this.row = row
this.trickForm = {...row}
row.file && (this.trickForm.fileId = row.file.id)
this.$nextTick(() => {
this.trickDialog = true
})
this.fileList.push(row.file)
},
deleteTrick(row) {
this.$confirm('确认要删除吗?').then(() => {
this.instance
.post(`/app/wxcp/wxspeechtechnique/delete`, null, {
params: {
id: row.id,
},
})
.then((res) => {
if (res.code == 0) {
this.$message.success('删除成功')
this.getList()
}
})
})
},
trickConfirm() {
this.$refs['trickFom'].validate((valid) => {
if (valid) {
this.instance
.post(`/app/wxcp/wxspeechtechnique/addOrUpdate`, {
...this.trickForm,
category: this.current.id,
userId: this.user.info.id,
})
.then((res) => {
if (res.code == 0) {
this.trickDialog = false
this.$message.success(this.trickForm.id ? '编辑成功' : '添加成功')
this.getList()
}
})
}
})
},
currentMenu(item) {
this.current = item
this.getList()
},
delteGroup(item, index) {
this.$confirm('是否要删除?').then(() => {
this.instance
.post(`/app/wxcp/wxspeechtechniquecategory/delete`, null, {
params: {
id: item.id,
},
})
.then((res) => {
if (res.code == 0) {
this.$message.success('删除成功')
this.getSortList()
}
})
})
},
editGroup(item, index) {
this.sortForm = {...item}
this.groupDialog = true
},
onConfirm() {
this.$refs['groupForm'].validate((valid) => {
if (valid) {
this.instance
.post(`/app/wxcp/wxspeechtechniquecategory/addOrUpdate`, {
...this.sortForm,
})
.then((res) => {
if (res.code == 0) {
this.groupDialog = false
this.$message.success(this.sortForm.id ? '编辑成功' : '添加成功')
this.getSortList()
}
})
}
})
},
getSortList() {
this.instance
.post(`/app/wxcp/wxspeechtechniquecategory/listAll`, null, {
params: {
// type: 1,
},
})
.then((res) => {
if (res && res.data.length) {
this.sortList = res.data
this.current = res.data[0]
this.getList()
}
})
},
getList(item) {
this.instance
.post(`/app/wxcp/wxspeechtechnique/list`, null, {
params: {
category: this.current.id,
...this.search,
status: 1,
},
})
.then((res) => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
},
}
</script>
<style lang="scss" scoped>
.left-tree {
width: 264px;
background: #fafafb;
box-shadow: -1px 0px 0px 0px #e5e5e5;
border-radius: 2px 0px 0px 2px;
.tree-title {
height: 40px;
box-sizing: border-box;
padding: 0 8px 0 16px;
background: #eeeff1;
display: flex;
align-items: center;
justify-content: space-between;
& > label {
font-size: 14px;
font-weight: bold;
color: #222222;
}
}
.tree-item {
box-sizing: border-box;
padding: 8px 16px 8px 24px;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
border-right: 2px solid transparent;
}
.menu-item {
height: 40px;
align-items: center;
display: flex;
justify-content: space-between;
.item-title {
display: inline-block;
width: 160px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
color: #222222;
}
.iconMore {
cursor: pointer;
}
}
:deep( .el-scrollbar__wrap ){
overflow-x: hidden;
}
}
:deep( .ai-list__content--right ){
.ai-list__content--right-wrapper {
min-height: 100%;
}
}
:deep( .has-gutter tr ){
height: 40px;
}
:deep( .el-table__row td ){
height: 68px;
}
// :deep( .el-table__row td .cell ){
// width: 240px;
// height: 44px;
// }
.table-btn {
font-size: 14px;
color: #2266ff;
cursor: pointer;
&:nth-child(1) {
margin-right: 16px;
}
}
.input {
width: 100%;
min-height: 32px;
line-height: 32px;
border-radius: 4px;
border: 1px solid #d0d4dc;
color: #666;
display: inline-block;
font-size: inherit;
cursor: pointer;
&:after {
content: '请选择...';
color: #888888;
padding-left: 10px;
}
}
:deep( .media ){
object-fit: fill;
width: 40px;
height: 40px;
vertical-align: middle;
box-sizing: content-box;
}
:deep( .AiPersonSelect ){
& > button {
background: #f5f5f5;
border-radius: 0px 2px 2px 0px;
border: 1px solid #d0d4dc;
color: #222222;
}
}
:deep( .is-active ){
border-right: 2px solid #2266ff;
}
</style>
<style scoped lang="scss">
.el-popper {
min-width: 76px !important;
}
.table-left__wrapper {
display: flex;
align-items: center;
.table-left__wrapper--text {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.ellipsis {
width: 170px;
height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
img,
video {
margin-right: 8px;
}
}
.el-form-item__textarea {
position: relative;
.el-form-item__btn, .el-form-item__remark {
position: absolute;
bottom: 12px;
left: 12px;
line-height: 1;
z-index: 1;
color: #2266FF;
font-size: 14px;
user-select: none;
background: #fff;
cursor: pointer;
}
.el-form-item__remark {
left: 108px;
}
}
</style>

View File

@@ -1,66 +0,0 @@
<template>
<div class="doc-circulation ailist-wrapper">
<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'
export default {
name: 'AppVillageCode',
label: '一村一码',
props: {
instance: Function,
dict: Object
},
data () {
return {
component: 'List',
params: {},
include: []
}
},
components: {
Add,
List
},
mounted () {
},
methods: {
onChange (data) {
if (data.type === 'Add') {
this.component = 'Add'
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">
.doc-circulation {
height: 100%;
background: #F3F6F9;
overflow: auto;
}
</style>

View File

@@ -1,119 +0,0 @@
<template>
<ai-detail>
<template slot="title">
<ai-title title="添加二维码" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
</ai-title>
</template>
<template slot="content">
<ai-card title="基本信息">
<template #content>
<el-form class="ai-form" ref="form" :model="form" label-width="110px" label-position="right">
<el-form-item label="地区" style="width: 100%;" prop="codeName">
<span style="color: #666;">{{ form.areaName }}</span>
</el-form-item>
<el-form-item label="二维码名称" prop="codeName" :rules="[{ required: true, message: '请输入二维码名称', trigger: 'blur' }]">
<el-input size="small" maxlength="30" show-word-limit placeholder="请输入二维码名称" style="width: 328px;" v-model="form.codeName"></el-input>
</el-form-item>
<el-form-item style="width: 100%;" label="二维码类型" prop="type" :rules="[{ required: true, message: '请选择二维码类型', trigger: 'change' }]">
<el-radio-group v-model="form.type">
<el-radio label="0">群二维码</el-radio>
<el-radio label="1">个人二维码</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="上传二维码" prop="codeUrl" style="width: 100%;" :rules="[{ required: true, message: '请上传二维码', trigger: 'change' }]">
<ai-uploader :instance="instance" v-model="form.codeUrl" :limit="1"></ai-uploader>
</el-form-item>
</el-form>
</template>
</ai-card>
</template>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="confirm">提交</el-button>
</template>
</ai-detail>
</template>
<script>
export default {
name: 'Add',
props: {
instance: Function,
dict: Object,
params: Object
},
data () {
return {
info: {},
form: {
areaId: '',
codeName: '',
areaName: '',
code: '',
codeUrl: [],
type: '',
},
id: ''
}
},
created () {
if (this.params && this.params.areaId && !this.params.id) {
this.form.areaId = this.params.areaId
this.form.areaName = this.params.areaName
}
if (this.params && this.params.id) {
this.id = this.params.id
this.getInfo(this.params.id)
}
},
methods: {
getInfo (id) {
this.instance.post(`/app/appeveryvillagecode/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.form = res.data
this.form.codeUrl = [{
url: res.data.codeUrl
}]
}
})
},
onClose () {
this.form.explain = ''
},
confirm () {
this.$refs.form.validate((valid) => {
if (valid) {
this.instance.post(`/app/appeveryvillagecode/addOrUpdate`, {
...this.form,
codeUrl: this.form.codeUrl[0].url
}).then(res => {
if (res.code == 0) {
this.$message.success('提交成功')
setTimeout(() => {
this.cancel(true)
}, 600)
}
})
}
})
},
cancel (isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: !!isRefresh
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -1,413 +0,0 @@
<template>
<ai-list class="villagecode">
<template slot="title">
<ai-title title="一村一码" isShowBottomBorder></ai-title>
</template>
<template #left>
<div class="villagecode-left">
<div class="villagecode-left__title">
<h2>村列表</h2>
</div>
<div class="addressBook-left__list">
<div class="addressBook-left__list--title">
<el-input
class="addressBook-left__list--search"
size="mini"
clearable
placeholder="请输入地区名称"
v-model="unitName"
suffix-icon="iconfont iconSearch">
</el-input>
</div>
<el-tree
:filter-node-method="filterNode"
ref="tree"
:props="defaultProps"
node-key="id"
:data="areaTree"
highlight-current
:current-node-key="search.areaId"
:default-expanded-keys="defaultExpanded"
:default-checked-keys="defaultChecked"
@current-change="onTreeChange">
</el-tree>
</div>
</div>
</template>
<template slot="content">
<ai-search-bar class="search-bar">
<template #left>
<el-button size="small" type="primary" :disabled="isShowAdd" icon="iconfont iconAdd" @click="toAdd('')">添加活码</el-button>
</template>
</ai-search-bar>
<ai-table
:tableData="tableData"
:col-configs="colConfigs"
:total="total"
style="margin-top: 6px;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="tags" label="标签">
<template slot-scope="{ row }">
<div class="table-tags">
<el-tag type="info" v-for="(item, index) in row.tags" size="small" :key="index">{{ item }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column slot="options" width="180px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-popover
placement="bottom"
width="160"
:visible-arrow="false"
popper-class="wechat-message__container"
trigger="hover">
<el-button type="text" slot="reference">二维码</el-button>
<div style="font-size: 0;">
<img class="message-info__img" :src="row.codeUrl">
</div>
</el-popover>
<el-button type="text" @click="toAdd(row.id)">编辑</el-button>
<el-button type="text" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'List',
props: {
instance: Function,
dict: Object
},
data() {
return {
search: {
current: 1,
size: 10,
status: 0,
title: '',
areaId: ''
},
defaultExpanded: [],
defaultChecked: [],
areaTree: [],
defaultProps: {
children: 'children',
label: 'name'
},
currIndex: -1,
total: 10,
colConfigs: [
{prop: 'codeName', label: '名称', align: 'left'},
{prop: 'type', label: '二维码类型', align: 'left', format: v => v === '0' ? '群二维码' : '个人二维码'},
{prop: 'createUserName', label: '创建人'},
{prop: 'createTime', label: '创建时间'},
{slot: 'options', label: '操作'}
],
areaName: '',
unitName: '',
tableData: []
}
},
computed: {
...mapState(['user']),
isShowAdd () {
const str = this.search.areaId.substr(this.search.areaId.length - 3)
return str === '000'
}
},
watch: {
unitName (val) {
this.$refs.tree.filter(val)
}
},
mounted() {
this.search.areaId = this.user.info.areaId
this.areaName = this.user.info.areaName
this.getTree()
this.getList()
this.$nextTick(() => {
})
},
methods: {
getList() {
this.instance.post(`/app/appeveryvillagecode/list`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
}
})
},
filterNode(value, data) {
if (!value) return true
return data.name.indexOf(value) !== -1
},
onTreeChange (e) {
this.search.areaId = e.id
this.areaName = e.name
this.search.current = 1
this.$nextTick(() => {
this.getList()
})
},
getTree () {
this.instance.post(`/admin/area/queryAllArea?id=${this.user.info.areaId}`).then(res => {
if (res.code === 0) {
let parent = res.data.map(v => {
v.label = v.name
v.children = []
return v
}).filter(e => !e.parentid)[0]
this.defaultExpanded = [parent.id]
this.defaultChecked = [parent.id]
this.search.areaId = parent.id
this.addChild(parent, res.data)
this.areaTree = [parent]
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(parent.id)
})
}
})
},
addChild (parent, list) {
for (let i = 0; i < list.length; i++) {
if (list[i].parentId === parent.id) {
parent.children.push(list[i])
}
}
if (list.length > 0) {
parent['children'].map(v => this.addChild(v, list))
}
},
remove(id) {
this.$confirm('确定删除该数据?').then(() => {
this.instance.post(`/app/appeveryvillagecode/delete?ids=${id}`).then(res => {
if (res.code == 0) {
this.$message.success('删除成功!')
this.getList()
}
})
})
},
toAdd(id) {
this.$emit('change', {
type: 'Add',
params: {
areaName: this.areaName,
id: id || '',
areaId: this.search.areaId
}
})
}
}
}
</script>
<style lang="scss" scoped>
.villagecode {
.table-tags {
.el-tag {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
}
.addressBook-left__list {
height: calc(100% - 40px);
padding: 8px 8px;
overflow: auto;
.addressBook-left__tags--item {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
padding: 0 8px 0 16px;
color: #222222;
&.addressBook-left__tags--item-active, &:hover {
background: #E8EFFF;
color: #2266FF;
i, span {
color: #2266FF;
}
}
span {
font-size: 14px;
}
i {
cursor: pointer;
color: #8e9ebf;
font-size: 16px;
}
}
.addressBook-left__list--title {
display: flex;
align-items: center;
margin-bottom: 8px;
.addressBook-left__list--search {
flex: 1;
:deep( input ){
width: 100%;
}
}
.el-button {
width: 84px;
flex-shrink: 1;
margin-right: 8px;
}
}
span {
color: #222222;
font-size: 14px;
}
:deep( .el-tree ){
background: transparent;
.el-tree-node__expand-icon.is-leaf {
color: transparent!important;
}
.el-tree-node__content > .el-tree-node__expand-icon {
padding: 4px;
}
.el-tree-node__content {
height: 32px;
}
.el-tree__empty-text {
color: #222;
font-size: 14px;
}
.el-tree-node__children .el-tree-node__content {
height: 32px;
}
.el-tree-node__content:hover {
background: #E8EFFF;
color: #222222;
border-radius: 2px;
}
.is-current > .el-tree-node__content {
&:hover {
background: #2266FF;
color: #fff;
}
background: #2266FF;
span {
color: #fff;
}
}
}
}
.villagecode-left {
width: 100%;
height: auto;
background: #FAFAFB;
.villagecode-left__title {
display: flex;
align-items: center;
height: 40px;
padding: 0 16px;
background: #E5E5E5;
h2 {
color: #222;
font-size: 14px;
}
}
.villagecode-left__list {
height: calc(100% - 40px);
padding: 8px 0;
overflow: auto;
span {
display: block;
height: 40px;
line-height: 40px;
padding: 0 24px;
color: #222222;
font-size: 14px;
cursor: pointer;
border-right: 2px solid transparent;
background: transparent;
&:hover {
color: #2266FF;
background: #E8EFFF;
}
&.left-active {
color: #2266FF;
border-color: #2266FF;
background: #E8EFFF;
}
}
}
}
:deep( .ai-list__content--right ){
.ai-list__content--right-wrapper {
min-height: 100%;
}
}
}
.message-info__img {
font-size: 0;
width: 144px;
height: 144px;
}
</style>

70
src/App.vue Normal file
View File

@@ -0,0 +1,70 @@
<template>
<div id="app" :class="{greyFilter,[`theme-${$theme}`]:true}">
<router-view/>
</div>
</template>
<script>
import {mapState} from "vuex";
import customConfig from "./config.json";
export default {
name: 'app',
computed: {
...mapState(['sys']),
greyFilter: v => v.sys?.theme?.enableGreyFilter == 1,
},
methods: {
initFavicon(icon) {
const linkList = document.head.querySelectorAll('link') || {},
url = `${this.$cdn}/favicon/${icon || "favicon"}.ico`
if (Object.values(linkList).findIndex(e => e.href == url) == -1) {
let favicon = document.createElement("link")
favicon.rel = "icon"
favicon.href = url
document.head.appendChild(favicon)
}
}
},
created() {
this.initFavicon(customConfig.sysInfo?.favicon)
document.title = this.sys.info.fullTitle
customConfig?.hmt && this.$injectLib("https://hm.baidu.com/hm.js?4e5dd7c5512e5da68c025c3b956fbd5d")
}
}
</script>
<style lang="scss">
html, body {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, input, p, blockquote, th, td {
margin: 0;
padding: 0;
}
#app {
font-family: Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow: hidden;
width: 100%;
height: 100%;
}
li {
list-style-type: none;
}
.greyFilter {
filter: grayscale(100%);
}
.amap-logo, .amap-copyright {
display: none !important;
}
</style>

BIN
src/assets/bg_prolife.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
src/assets/building.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
src/assets/loginLeft.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
src/assets/nav_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View File

@@ -0,0 +1,125 @@
<template>
<section class="AiAreaPicker">
<div @click="handleOpenDialog">
<slot v-if="$scopedSlots.default" :selected="selected"/>
<el-button v-else type="text">选择地区</el-button>
</div>
<ai-dialog :visible.sync="dialog" title="选择地区" @onConfirm="submit" @close="selecting=[],init()" destroy-on-close>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item v-for="(item,i) in path" :key="item.id">
<el-button type="text" @click.native="handlePathClick(i)">{{ item.name }}</el-button>
</el-breadcrumb-item>
</el-breadcrumb>
<ai-table-select class="mar-t16" v-model="selecting" :instance="instance" :action="action" :isShowPagination="false" extra="hidden" search-key="name"
multiple valueObj>
<template slot="extra" slot-scope="{row}">
<el-button v-if="row.id!=root" type="text" icon="el-icon-arrow-right" @click.stop="getChildren(row)"/>
</template>
</ai-table-select>
</ai-dialog>
</section>
</template>
<script>
export default {
name: "AiAreaPicker",
model: {
prop: "value",
event: "change"
},
props: {
value: {default: ""},
meta: {default: null},
root: {default: ""},
instance: {type: Function, required: true},
multiple: Boolean
},
computed: {
action() {
let currentParent = this.path.slice(-1)?.[0]?.id
return !!currentParent && /[^0]0{0,2}$/.test(currentParent) ? `/admin/appresident/queryAreaIdGroup?currentAreaId=${currentParent}` :
`/admin/area/queryAreaByParentIdSelf?self=${currentParent == this.root ? 1 : ''}&id=${currentParent}`
}
},
watch: {
value(v) {
this.dispatch('ElFormItem', 'el.form.change', [v]);
},
selected(v) {
this.$emit("update:meta", v)
}
},
data() {
return {
dialog: false,
options: [],
selecting: [],
path: [],
selected: []
}
},
methods: {
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root;
let name = parent.$options.componentName;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
getChildren(row) {
let area = this.path.find(e => e.id == row.id)
if (!area) this.path.push(row)
},
handlePathClick(index) {
this.path.splice(index + 1, 10)
},
submit() {
this.$emit("change", this.selecting.map(e => e.id))
this.selected = this.$copy(this.selecting)
this.$emit("select", this.selecting)
this.dialog = false
},
init() {
this.path = [{id: this.root, name: "可选范围"}]
},
handleOpenDialog() {
let areas = this.value.filter(e => /^\d{12}$/.test(e))
this.instance.post("/admin/area/getAreaNameByids", null, {
params: {ids: areas?.toString()}
}).then(res => {
if (res?.data) {
this.selecting = this.value.map(id => ({id, name: res.data?.[id] || id}))
this.dialog = true
}
})
},
getAreaById(id) {
return this.instance.post("/admin/area/queryAreaByAreaid", null, {
params: {id}
}).then(res => {
if (res?.data) {
return res.data
}
})
}
},
created() {
this.init()
}
}
</script>
<style lang="scss" scoped>
.AiAreaPicker {
.mar-t16 {
margin-top: 16px;
}
}
</style>

View File

@@ -1,7 +1,6 @@
<template>
<section class="AppLicence">
<section class="AppLicence" v-if="showLicence">
<ai-list>
<ai-title slot="title" title="产品许可" isShowBottomBorder :instance="instance"></ai-title>
<template #content>
<div class="licence-content">
<img class="left-img" src="https://cdn.cunwuyun.cn/dvcp/key.png" alt="" />
@@ -30,13 +29,14 @@
<span class="value">{{info.ip}}</span>
</div>
<el-upload
class="upload-demo"
class="upload-demo mar-r16"
action
multiple
accept=".lic"
:http-request="uploadFile">
<div class="btn">上传许可</div>
</el-upload>
<div class="btn" @click="showLicence = false">返回登录</div>
</div>
</div>
</template>
@@ -50,19 +50,25 @@ export default {
label: "产品许可",
props: {
instance: Function,
dict: Object,
permissions: Function
},
data() {
return {
files: [],
info: {}
info: {},
showLicence: false
}
},
mounted() {
this.getDetail()
},
methods: {
show() {
this.showLicence = true
this.getDetail()
},
hide() {
this.showLicence = false
},
getDetail() {
this.instance.post(`/admin/license/detail`).then(res => {
if (res?.data) {
@@ -78,7 +84,9 @@ export default {
this.$message.success("证书上传成功!");
this.getDetail()
}
});
}).catch((err) => {
this.$message.error(err);
})
},
},
@@ -88,6 +96,10 @@ export default {
.AppLicence {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 99;
:deep( .ai-list){
background-color: #F5F6F9;
}
@@ -102,7 +114,8 @@ export default {
}
.licence-content{
display: flex;
margin-top: 30px;
width: 1000px;
margin: 200px auto 0;
.left-img{
width: 200px;
height: 200px;
@@ -111,7 +124,6 @@ export default {
width: 800px;
.title{
font-size: 24px;
font-family: MicrosoftYaHei-Bold, MicrosoftYaHei;
font-weight: bold;
color: #222;
line-height: 24px;
@@ -119,14 +131,12 @@ export default {
}
.mini-title{
font-size: 14px;
font-family: MicrosoftYaHei;
color: #555;
line-height: 22px;
margin-bottom: 20px;
}
.info{
font-size: 14px;
font-family: MicrosoftYaHei;
line-height: 22px;
margin-bottom: 8px;
display: flex;
@@ -147,7 +157,11 @@ export default {
.mar-b32{
margin-bottom: 32px;
}
.upload-demo{
display: inline-block;
}
.btn{
display: inline-block;
width: 88px;
height: 32px;
line-height: 32px;
@@ -158,6 +172,9 @@ export default {
color: #fff;
font-size: 14px;
}
.mar-r16{
margin-right: 16px;
}
}
}
}

View File

@@ -0,0 +1,255 @@
<template>
<div class="headerNav navBg">
<div style="position: relative">
<ai-icon type="logo" :icon="system.logo||'iconcunwei'"/>
<ai-icon type="logo" :icon="system.logo||'iconcunwei'" class="textShadow"/>
</div>
<span class="headerTitle">{{ system.fullTitle }}<div class="textShadow" v-html="system.fullTitle"/></span>
<el-row type="flex" align="middle" class="toolbar">
<!--下载中心-->
<downloan-center-btn v-if="extra.downloadCenter"/>
<!--大屏按钮-->
<dv-btn v-if="extra.dv"/>
<div class="btn" v-if="extra.showTool" @click="home.showTool=false">隐藏工具栏</div>
<div class="btn" v-if="extra.helpDoc" @click="openHelp">帮助文档</div>
<app-qrcode v-if="extra.appQRCode"/>
<div class="btn" v-if="extra.customerService" @click.native="openAiService">
<div class="iconfont iconCustomer_Service"></div>
<div>智能客服</div>
</div>
<!--推荐链接-->
<link-btn/>
</el-row>
<el-dropdown @visible-change="v=>isClick=v" @command="doMenu" class="rightDropdown">
<el-row type="flex" align="middle">
<el-avatar :src="user.info.avatar">
{{ defaultAvatar }}
</el-avatar>
<span>{{ [user.info.name, user.info.roleName].join(" - ") }}</span>
<i :class="dropdownIcon"/>
</el-row>
<el-dropdown-menu>
<el-dropdown-item command="user">用户中心</el-dropdown-item>
<el-dropdown-item command="signOut">退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
import {mapState} from "vuex";
import DvBtn from "./headerTools/dvBtn";
import DownloanCenterBtn from "./headerTools/downloanCenterBtn";
import extra from "../config.json"
import AppQrcode from "./headerTools/appQrcode";
import LinkBtn from "./headerTools/linkBtn";
export default {
name: 'headerNav',
components: {LinkBtn, AppQrcode, DownloanCenterBtn, DvBtn},
data() {
return {
extra,
isClick: false,
}
},
computed: {
...mapState(['user', 'sys']),
dropdownIcon() {
return this.isClick ? 'el-icon-caret-top' : 'el-icon-caret-bottom'
},
defaultAvatar() {
return this.user.info.name?.slice(-2) || "游客"
},
system: v => v.sys?.info || {}
},
created() {
this.$dict.load('fileFrom');
},
methods: {
// 获取最新的安卓、ios下载二维码
doMenu(comm) {
switch (comm) {
case 'signOut':
//登出
this.$confirm("是否要登出?", {type: "warning"}).then(() => {
this.$store.commit("signOut")
}).catch(() => {
})
break;
case 'user':
this.$router.push({name: "个人中心"})
break;
}
},
openAiService() {
window.open('http://v3.faqrobot.org/robot/chat1.html?sysNum=153543696570625098')
},
openHelp() {
window.open('https://www.yuque.com/books/share/eeaaa5e3-a528-42eb-872e-20d661f3d0e2')
},
changeStatus(id) {
this.$request.post(`/app/sysuserdownload/addOrUpdate`, {
id, status: "2"
}).then(res => {
if (res?.code == 0) {
this.getFiles();
}
});
},
}
}
</script>
<style lang="scss" scoped>
.headerNav {
display: flex;
align-items: center;
width: 100%;
background-repeat: no-repeat;
background-size: 100% 48px;
position: fixed;
z-index: 99;
height: 48px;
padding-left: 24px;
box-sizing: border-box;
top: 0;
color: white;
font-size: 14px;
.AiIcon {
font-size: 38px;
width: auto;
height: auto;
background: linear-gradient(180deg, #FFFFFF 0%, #CCDBF6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
&:hover {
color: white;
}
}
.headerTitle {
flex: 1;
min-width: 0;
font-size: 24px;
color: #FFF;
line-height: 28px;
background: linear-gradient(180deg, #FFFFFF 0%, #CCDBF6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: bold;
margin-left: 8px;
position: relative;
}
:deep(.toolbar) {
gap: 12px;
margin-right: 32px;
.btn {
padding: 0 12px;
color: white;
&:hover {
cursor: pointer;
color: rgba(#fff, .8);
}
}
}
.el-dropdown {
height: 48px;
line-height: 48px;
color: #fff;
padding: 0 12px;
&:hover {
background-color: rgba(46, 51, 68, .15);
color: white;
}
}
.el-image {
margin: 12px 0 12px 16px;
}
.download-wrapper {
position: relative;
&:hover .download {
display: flex;
}
.download {
display: none;
position: absolute;
top: 100%;
left: 12px;
transform: translateX(-90%);
width: auto;
height: auto;
padding: 12px;
background: #fff;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.1);
border-radius: 2px;
box-sizing: border-box;
z-index: 999;
}
.download-item {
text-align: center;
&:first-child {
margin-right: 13px;
}
& > img {
width: 105px;
height: 105px;
margin-bottom: 7px;
}
p {
margin-top: 5px;
font-size: 13px;
color: #333;
text-align: center;
}
.download-item__middle {
img {
width: 13px;
height: 16px;
vertical-align: sub;
}
span {
padding-left: 8px;
font-size: 13px;
color: #333;
}
}
}
}
:deep(.rightDropdown) {
font-size: 12px;
padding: 0 16px;
height: 48px;
background: rgba(#fff, .1);
.el-avatar > img {
width: 100%;
}
.el-row {
gap: 4px;
}
}
}
</style>

View File

@@ -0,0 +1,121 @@
<template>
<section class="appQrcode">
<div class="btn">手机APP</div>
<div class="download">
<div class="download-item" v-if="!androidQRcode&&!iosQRcode"><p class="nowarp-text" v-text="`暂未发布`"/></div>
<template v-else>
<div class="download-item" v-if='iosQRcode'>
<img :src="iosQRcode" alt=""/>
<div class="download-item__middle">
<span class="iconfont iconIOS"></span>
<span>iPhone</span>
</div>
<p>手机扫码下载APP</p>
</div>
<div class="download-item" v-if='androidQRcode'>
<img :src="androidQRcode" alt=""/>
<div class="download-item__middle">
<span class="iconfont iconAndroid"></span>
<span>Android</span>
</div>
<p>手机扫码下载APP</p>
</div>
</template>
</div>
</section>
</template>
<script>
export default {
name: "appQrcode",
data() {
return {
iosQRcode: null,
androidQRcode: null
}
},
methods: {
getAppQRcode() {
this.$request.post(`/admin/sysversion/getLatestIosVersion`, null, {
params: {type: 3}
}).then(res => {
if (res?.data) {
this.iosQRcode = res.data.qrCodeUrl
}
})
this.$request.post(`/admin/sysversion/getLatestVersion`, null, {
params: {type: 1}
}).then(res => {
if (res?.data) {
this.androidQRcode = res.data.qrCodeUrl
}
})
},
},
created() {
this.getAppQRcode()
}
}
</script>
<style lang="scss" scoped>
.appQrcode {
position: relative;
&:hover .download {
display: flex;
}
.download {
display: none;
position: absolute;
top: 100%;
left: 12px;
transform: translateX(-90%);
width: auto;
height: auto;
padding: 12px;
background: #fff;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.1);
border-radius: 2px;
box-sizing: border-box;
z-index: 999;
}
.download-item {
text-align: center;
&:first-child {
margin-right: 13px;
}
& > img {
width: 105px;
height: 105px;
margin-bottom: 7px;
}
p {
margin-top: 5px;
font-size: 13px;
color: #333;
text-align: center;
}
.download-item__middle {
img {
width: 13px;
height: 16px;
vertical-align: sub;
}
span {
padding-left: 8px;
font-size: 13px;
color: #333;
}
}
}
}
</style>

View File

@@ -0,0 +1,221 @@
<template>
<section class="downloanCenterBtn">
<el-badge :value="badgeNum" :hidden='badgeNum==0'>
<div class="btn" @click="openDrawer()">下载中心</div>
</el-badge>
<el-drawer title="下载中心" :visible.sync="drawer" :modal-append-to-body="false" size="520">
<div class="downLoad_main">
<div class="search_top ">
<p style="color:#999999;">仅显示最近90天的记录</p>
<el-input size="mini" v-model="fileName" placeholder="文件名" clearable prefix-icon="iconfont iconSearch"
style="width:240px;" @change="getFiles()"/>
</div>
<ul class="infinite-list">
<li v-for="(item,i) in filesList" class="infinite-list-item " :key="i">
<div class="left">
<svg class="svg" aria-hidden="true">
<use xlink:href="#iconZip"/>
</svg>
</div>
<div class="middle">
<p class="fileName">{{ item.fileName }}密码{{ item.pwd }}</p>
<p>
<span>来源:</span>
<span>{{ $dict.getLabel('fileFrom', item.fileFrom) }}</span>
<span>{{ (item.size / 1000).toFixed(2) + "KB" }}</span>
<span>{{ item.createTime }}</span>
</p>
</div>
<div class="right">
<span class="iconfont iconResetting" v-if="item.status==0">处理中</span>
<span v-if="item.status==2">已下载</span>
<i class="iconfont iconDownload" @click="downFile(item)" v-if="item.status!=0">下载</i>
</div>
</li>
</ul>
</div>
</el-drawer>
</section>
</template>
<script>
import {mapState} from "vuex";
export default {
name: "downloanCenterBtn",
data() {
return {
badgeNum: 0,
drawer: false,//抽屉
filesList: [],
fileName: '',
}
},
computed: {
...mapState(['user'])
},
methods: {
openDrawer() {
this.drawer = true;
this.getFiles();
},
getFiles() {
this.$request.post(`/app/sysuserdownload/list`, null, {
params: {
userId: this.user.info.id,
fileName: this.fileName,
current: 1,
size: 1000,
}
}).then(res => {
if (res?.data) {
this.filesList = res.data.records;
this.searchNum()
}
});
},
//查询未完成数量
searchNum() {
this.$request.post(`/app/sysuserdownload/queryCountByUserId`, null, {
params: {
userId: this.user.info.id
}
}).then(res => {
if (res?.data) {
this.badgeNum = res.data;
}
})
},
downFile(item) {
this.changeStatus(item.id);
// window.open(item.accessUrl);
let elemIF = document.createElement('iframe');
elemIF.src = item.accessUrl;
elemIF.style.display = 'none';
document.body.appendChild(elemIF);
}
},
created() {
this.searchNum()
}
}
</script>
<style lang="scss" scoped>
.downloanCenterBtn {
.downLoad_main {
width: 100%;
height: 100%;
padding: 16px;
box-sizing: border-box;
.search_top {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 8px;
}
.infinite-list {
width: 100%;
height: 100%;
.infinite-list-item {
width: 100%;
padding: 8px;
box-sizing: border-box;
background: rgba(255, 255, 255, 1);
border-radius: 4px;
border: 1px solid rgba(208, 212, 220, 1);
margin-bottom: 8px;
display: flex;
justify-content: space-between;
.left {
display: flex;
justify-content: center;
align-items: center;
width: 30px;
.svg {
width: 24px;
height: 24px;
vertical-align: middle;
}
}
.middle {
flex: 1;
.fileName {
color: #333333;
font-size: 14px;
}
p:nth-child(2) {
color: #999999;
font-size: 12px;
span {
padding: 0 4px;
}
span:nth-child(2) {
border-right: solid 1px #999999;
}
span:nth-child(3) {
border-right: solid 1px #999999;
}
}
}
.right {
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
width: 90px;
text-align: center;
span {
color: #999999;
}
i {
display: block;
width: 50px;
color: #5088FF;
font-size: 12px;
cursor: pointer;
}
}
}
}
::-webkit-scrollbar {
width: 4px;
background-color: #eee;
}
::-webkit-scrollbar-thumb {
background-color: #8888;
}
}
}
:deep(.el-drawer__wrapper) {
position: fixed;
width: 100%;
top: 0;
bottom: 0;
right: 0;
.el-drawer__header > span:focus {
outline: 0
}
}
</style>

View File

@@ -0,0 +1,52 @@
<template>
<section class="dvBtn">
<el-popover title="数据大屏" width="500" trigger="click">
<div flex class="wrap">
<div class="el-button--text pad-r8 pad-b8" style="width: 50%" v-for="op in dvOptions" :key="op.id" v-text="op.name||'无名大屏'"
@click="handleOpenDV(op.id)"/>
</div>
<div slot="reference" class="btn">数据大屏</div>
</el-popover>
</section>
</template>
<script>
import extra from "../../config.json"
export default {
name: "dvBtn",
data() {
return {
dvOptions: []
}
},
methods: {
getDvList() {
this.$request.post("/app/appdiylargescreen/allLargeScreenProjectByPage", null, {
params: {size: 9999, status: 1}
}).then(res => {
if (res?.data) {
this.dvOptions = res.data.records
}
})
},
handleOpenDV(id) {
window.open(`${location.origin}${extra.base || ""}/dv?id=${id}#dv`)
},
},
created() {
this.getDvList()
}
}
</script>
<style lang="scss" scoped>
.dvBtn {
}
:deep(.el-button--text) {
cursor: pointer;
user-select: none;
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<section class="linkBtn">
<el-dropdown v-if="links.length > 0" @command="handleOpenLink">
<div class="btn">友情链接</div>
<el-dropdown-menu>
<el-dropdown-item v-for="op in links" :key="op.id" :command="op.url">
{{ op.title }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</section>
</template>
<script>
export default {
name: "linkBtn",
label: "友情链接",
data() {
return {
links: []
}
},
methods: {
getLinks() {
this.$request.post("/app/appwebnavurl/list", null, {
params: {size: 9999, status: 1}
}).then(res => {
if (res?.data) {
this.links = res.data.records
}
})
},
handleOpenLink(url) {
window.open(url)
},
},
created() {
this.getLinks()
}
}
</script>
<style lang="scss" scoped>
.linkBtn {
}
</style>

View File

@@ -0,0 +1,40 @@
<template>
<section class="mainContent">
<ai-nav-tab :fixed="homePage" :routes="routes"/>
<router-view v-if="refresh"/>
</section>
</template>
<script>
import {mapState} from "vuex";
export default {
name: "mainContent",
computed: {
...mapState(['user', 'homePage']),
routes: v => v.user.info?.menuSet?.map(e => ({...e, label: e.name, name: e.id}))
},
watch: {
$route(v, old) {
if (v.meta == old.meta && v.fullPath != old.fullPath) {
this.refresh = false
this.$nextTick(() => this.refresh = true)
}
}
},
data() {
return {
refresh: true
}
}
}
</script>
<style lang="scss" scoped>
.mainContent {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
}
</style>

View File

@@ -0,0 +1,296 @@
<template>
<section class="sliderNav">
<el-input class="searchApp" size="small" v-model="searchApp" placeholder="搜索应用" clearable
prefix-icon="iconfont iconSearch" @change="handleSearchApp"/>
<el-scrollbar class="ai-menu">
<div v-for="(item,i) in navs" :key="i">
<div class="rootMenu" :class="{isActive:menuPath.includes(item.id)}" @click.stop="openKidMenu(item)">
<i :class="item.style||'iconfont iconloudongmoxing'"/>
<span class="fill mar-l8" v-text="item.name"/>
<i v-if="hasChildren(item.children)" class="iconfont mar-l8" :class="arrowIcon(item.showChildren)"/>
</div>
<div class="kidMenu" v-if="hasChildren(item.children)&&item.showChildren" @click.stop>
<div v-for="menu in item.children" :key="menu.id">
<div class="submenu wrap pad-l16 pad-r16" flex v-if="hasChildren(menu.children)">
<b v-text="menu.name" :class="{menuBtn:menu.type==1,current:menuPath.includes(menu.id)}"
@click="handleSelect(menu)"/>
<div class="menuBtn" v-for="kid in menu.children" :key="kid.id" v-text="kid.name" :title="kid.name"
@click="handleSelect(kid)" :class="{current:menuPath.includes(kid.id)}"/>
</div>
<div v-else class="lv2Btn" v-text="menu.name" @click="handleSelect(menu)" :class="{current:menuPath.includes(menu.id)}"/>
</div>
</div>
</div>
<div class="divider"/>
</el-scrollbar>
</section>
</template>
<script>
import {mapGetters} from "vuex";
import qs from "querystring";
export default {
name: "sliderNav",
data() {
return {
menuList: [],
searchApp: "",
}
},
computed: {
...mapGetters(['mods']),
navs: v => v.sortList(v.menuList),
menuPath() {
let paths = [], current = this.mods?.find(e => e.route == this.$route.name)
const findParent = id => {
let menu = this.mods?.find(e => e.id == id)
if (menu) {
paths.push(menu.id)
if (!!menu.parentId) findParent(menu.parentId)
}
}
if (current) {
findParent(current.id)
}
return paths
},
modList: v => v.mods.filter(e => e.isMenu == 1 || e.type == 0 || (e.level > 1 && e.type == 1))
},
methods: {
initMenu(menus = this.modList) {
//isMenu 旧版本判断是否为菜单 type<2 新版本判断是否是菜单或应用
if (menus?.length > 0) {
this.menuList = this.$arr2tree(menus)
this.menuList = this.menuList.map(e => ({
...e,
showChildren: this.menuPath.includes(e.id) || !!this.searchApp
}))
}
},
openKidMenu(parent) {
if (this.hasChildren(parent.children)) {
parent.showChildren = !parent.showChildren
} else {
this.handleSelect(parent)
}
},
handleSelect(item) {
if (!item.path) return
if (item.route == this.$route.name) {
//避免同一路由跳转的BUG vue-router官方BUG
} else {
let {route: name, path} = item
if (!name) {
this.$message.warning("暂无应用")
} else {
this.goto({name, query: qs.parse(path.split("?")?.[1])})
}
}
},
goto(item) {
return this.$router.push(item)
},
sortList(list) {
return list?.sort((a, b) => a.showIndex - b.showIndex) || []
},
handleSearchApp() {
let {searchApp} = this
if (searchApp) {
let list = this.modList.filter(e => e.name?.indexOf(searchApp) > -1), map = {}
const findParent = e => {
map[e.id] = e
if (!!e.parentId) {
let parent = this.modList.find(m => m.id == e.parentId)
map[parent.id] = parent
if (!!parent.parentId) {
findParent(parent)
}
}
}
list.forEach(e => findParent(e))
console.log(map, list)
this.initMenu(Object.values(map))
} else {
this.initMenu()
}
},
arrowIcon(v) {
return v ? "iconArrow_Down" : "iconArrow_Right"
},
hasChildren(arr) {
return arr?.length > 0
}
},
created() {
this.initMenu()
}
}
</script>
<style lang="scss" scoped>
.sliderNav {
width: 200px;
height: 100%;
transition: width .1s;
display: flex;
justify-content: space-between;
flex-direction: column;
border-right: 1px solid #e5e5e5;
flex-shrink: 0;
box-sizing: border-box;
background: #EFF1F4;
color: #222;
position: relative;
user-select: none;
.kidMenu {
font-size: 13px;
.rootName {
font-size: 20px;
color: #333;
cursor: default;
}
.submenu {
margin-top: 8px;
width: 100%;
color: #aaa;
& > b {
width: 100%;
line-height: 28px;
}
& > * {
cursor: default;
}
}
.menuBtn {
display: block;
width: 50%;
cursor: pointer;
line-height: 32px;
color: #333;
flex-shrink: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.line {
width: 100%;
}
&:hover {
color: #26f;
}
&.current {
color: #26f;
}
}
}
.rootMenu {
padding: 0 16px;
display: flex;
align-items: center;
height: 44px;
cursor: pointer;
box-shadow: 0px -1px 0px 0px #D8DCE3 inset, 0px 1px 0px 0px #FFF inset, -1px 0px 0px 0px #E5E5E5 inset;
font-size: 13px;
.iconfont {
color: #89B;
font-size: 20px;
}
&.isActive {
color: #26f;
.iconfont {
color: #26f !important;
}
}
&:hover {
color: #26f;
}
}
:deep(.ai-menu ){
padding-left: 0;
flex: 1;
min-height: 0;
.el-scrollbar__wrap {
overflow-x: auto;
}
&::-webkit-scrollbar {
display: none;
}
}
:deep(.searchApp ){
display: flex;
align-items: center;
height: 44px;
padding: 0 16px;
box-shadow: 0px -1px 0px 0px #E5E5E5 inset;
.el-input__inner {
border: none;
background: inherit;
padding: 0 28px;
}
.el-input__prefix {
left: 16px;
.iconSearch {
font-size: 20px;
width: fit-content;
color: #89B;
line-height: 44px;
}
}
}
.divider {
color: #aaa;
border-top: 1px solid #ddd;
position: relative;
font-size: 12px;
margin: 16px 16px 32px;
&:before {
content: "到达底部";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 0 16px;
background: #EFF1F4;
white-space: nowrap;
}
}
.lv2Btn {
height: 44px;
display: flex;
align-items: center;
color: #222;
padding-left: 44px;
cursor: pointer;
&.current {
background: linear-gradient(90deg, #298BFF 0%, #0C61FF 100%);
box-shadow: inset -1px 0 0 0 #E5E5E5, inset 0 2px 8px 0 #1E4599;
color: #fff;
}
}
}
</style>

33
src/main.js Normal file
View File

@@ -0,0 +1,33 @@
import Vue from 'vue';
import App from './App.vue';
import ui from 'element-ui';
import router from './utils/router';
import utils from './utils';
import vcUI from 'dui';
import appComp from '@dui/dv';
import store from './utils/store';
import autoRoutes from "./utils/autoRoutes";
import extra from "./config.json"
import axios from "./utils/axios";
//import ob from "dui/lib/js/observer"
//备注底座信息,勿删
console.log("欢迎使用%s", extra.sysInfo?.name || "构建版本")
//new ob()
window.Vue = Vue
Vue.use(ui);
Vue.use(vcUI);
Vue.use(appComp);
Vue.config.productionTip = false;
Vue.prototype.$cdn = "https://cdn.cunwuyun.cn"
Vue.prototype.$request = axios
Object.keys(utils).map((e) => (Vue.prototype[e] = utils[e]));
const loadPage = () => autoRoutes.init().finally(() => new Vue({router, store, render: h => h(App)}).$mount("#app"))
let theme = null
store.dispatch('getSystem', extra.sysInfo).then(res => {
theme = JSON.parse(res?.colorScheme || null)
return import(`dui/lib/styles/theme.${theme?.web}.scss`).catch(() => 0)
}).finally(() => {
Vue.prototype.$theme = theme?.web || "blue"
!!theme?.web && theme?.web != "blue" ? loadPage() : import(`dui/lib/styles/common.scss`).finally(loadPage)
})

107
src/utils/autoRoutes.js Normal file
View File

@@ -0,0 +1,107 @@
import {waiting} from "./index";
import router from "./router";
import store from "./store";
import {Message} from "element-ui";
import Vue from "vue";
import extra from "../config.json"
let {state: {user}, commit, dispatch} = store
const signOut = () => commit("signOut"),
getUserInfo = () => dispatch("getUserInfo"),
existRoute = route => {
return router.getRoutes()?.find(e => e.name == route?.name || e.path == route?.path)
},
goto = (route, next) => {
const exist = !!existRoute(route)
return exist ? route.name ? next() : router.replace(route) :
!route.name && route.path == "/" ? router.replace({name: "Home"}).catch(() => 0) :
Message.error("无法找到路由,请联系系统管理员!")
}
const loadApps = () => {
//新App的自动化格式
waiting.init({innerHTML: '应用加载中..'})
let apps = require.context('../../apps', true, /\.(\/.+)\/App[A-Z][^\/]+\.vue$/, "lazy")
return Promise.all(apps.keys().map(path => apps(path).then(file => {
if (file.default) {
let {name} = file.default
waiting.setContent(`加载${name}...`)
Vue.component(name, file.default)
} else return 0
}))).then(() => {
waiting.setContent(`正在进入系统...`)
setTimeout(() => waiting.close(), 1000)
})
}
const addHome = homePage => {
const component = extra?.homePage || homePage.path
if (extra?.homePage && Vue.component(component)) {
homePage = {...homePage, path: component, component: () => import('../views/mainEntry'), meta: component}
}
router.addRoute('Home', homePage)
router.options.routes[2].children.unshift(homePage)
commit("setHomePage", {
...homePage,
label: homePage.name,
id: `/v/${component}`,
isMenu: 1,
route: homePage.name,
component,
path: component,
})
}
const generateRoutes = (to, from, next) => {
if (router.options.routes[2].children.length > 0) {
goto(to, next)
} else {
Promise.all([getUserInfo(), loadApps()]).then(() => {
//初始化默认工作台
let homePage = {name: "工作台", path: "console", style: "iconfont iconNav_Dashborad", component: () => import('../views/console')}
addHome(homePage)
const mods = user.info.menuSet?.filter(e => !!e.component)?.map(e => ({route: e.id, ...e}))
mods?.map(({route: name, path, component}) => {
if (!!Vue.component(component) && path && !existRoute({name})) {
let search = path.split("?")
path = search?.[0] || path
const route = {name, path, component: () => import('../views/mainEntry'), meta: component}
router.addRoute('Home', route)
router.options.routes[2].children.push(route)
}
})
to.name == "Home" ? next({name: homePage.name, replace: true}) : next({...to, replace: true})
}).then(() => commit("setRoutes", router.options.routes[2].children))
}
}
export const routes = [
{path: "/login", name: "登录", component: () => import('../views/sign')},
{path: '/dv', name: '数据大屏入口', component: () => import('../views/dvIndex')},
{path: '/v', name: 'Home', component: () => import('../views/home'), children: []},
{path: '/', name: "init"},
]
export default {
init: () => {
router.beforeEach((to, from, next) => {
console.log('%s=>%s', from.name, to.name)
if (to.hash == "#pddv") {
const {query} = to
dispatch("getToken", {
username: "18971406276",
password: "admin321!"
}).then(() => next({name: "数据大屏入口", query, hash: "#dv"}))
} else if (["数据大屏入口", "登录"].includes(to.name)) {
next()
} else if (to.hash == "#dv") {
//数据大屏进行的独立页面跳转
let {query, hash} = to
next({name: "数据大屏入口", query, hash})
} else if (user.token) {
to.name == "init" ? next({name: "Home"}) : generateRoutes(to, from, next)
} else {
signOut()
}
})
router.onError(err => {
console.error(err)
})
return Promise.resolve()
}
}

26
src/utils/axios.js Normal file
View File

@@ -0,0 +1,26 @@
import instance from 'dui/lib/js/request'
import {Message} from 'element-ui'
import extra from "../config.json";
import store from "./store"
let baseURLs = {
production: extra.base || "/",
development: extra.baseURL || '/lan',
}
instance.defaults.baseURL = baseURLs[process.env.NODE_ENV]
instance.interceptors.request.use(config => {
config.timeout = 300000
if (extra?.isSingleService) {
config.url = config.url.replace(/(app|auth|admin)\//, "api/")
}
if (config.url.startsWith("/node")) {
config.baseURL = "/ns"
}
return config
}, error => Message.error(error))
instance.interceptors.response.use(res => res, err => {
if (err?.code == 401) {
store.commit('signOut', 1)
}
})
export default instance

100
src/utils/index.js Normal file
View File

@@ -0,0 +1,100 @@
import {MessageBox} from 'element-ui'
import tools from 'dui/lib/js/utils'
import store from "./store";
let {state: {user}} = store
const addChildParty = (parent, pending) => {
let doBeforeCount = pending.length
parent["children"] = parent["children"] || []
pending.map((e, index, arr) => {
if (e.partyOrgParentId == parent.partyOrgId) {
parent.children.push(e)
arr.splice(index, 1)
addChildParty(parent, arr)
}
})
if (parent.children.length == 0) {
delete parent.children
}
if (pending.length > 0 && doBeforeCount > pending.length) {
parent.children.map(c => addChildParty(c, pending))
}
}
/**
* 封装提示框
*/
const $confirm = (content, options) => {
return MessageBox.confirm(content, {
type: "warning",
confirmButtonText: "确认",
center: true,
title: "提示",
dangerouslyUseHTMLString: true,
...options
})
}
/**
* 封装权限判断方法
*/
const $permissions = flag => {
const buttons = user?.info?.buttons
if (buttons) return buttons.some(b => b.id == flag || b.permission == flag)
else return false
}
const $decimalCalc = (...arr) => {
//确认提升精度
let decimalLengthes = arr.map(e => {
let index = ("" + e).indexOf(".")
return ("" + e).length - index
})
let maxDecimal = Math.max(...decimalLengthes), precision = Math.pow(10, maxDecimal)
//计算
let intArr = arr.map(e => (Number(e) || 0) * precision)
//返回计算值
return intArr.reduce((t, a) => t + a) / precision
}
export const waiting = {
init(ops, count) {
if (document.body) {
let div = document.createElement('div')
div.id = "ai-waiting"
div.innerHTML = "信息正在加载中..."
div.className = "el-loading-mask is-fullscreen"
div.style.zIndex = '202204271710'
div.style.textAlign = 'center'
div.style.lineHeight = '100vh'
div.style.background = 'rgba(255,255,255,.8)'
div.style.backdropFilter = 'blur(6px)'
document.body.appendChild(div)
} else if (count < 10) {
setTimeout(() => this.init(ops, ++count), 500)
}
},
getDom() {
return document.querySelector('#ai-waiting')
},
setContent(html) {
let div = this.getDom()
div.innerHTML = html
},
close() {
let div = this.getDom()
div.parentElement.removeChild(div)
}
}
export default {
...tools,
addChildParty,
$confirm,
$permissions,
$decimalCalc,
$waiting: waiting
}

19
src/utils/router.js Normal file
View File

@@ -0,0 +1,19 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import {routes} from "./autoRoutes"
import config from "../config.json"
Vue.use(VueRouter)
export default new VueRouter({
base: config.base || '/',
mode: 'history',
hashbang: false,
routes,
scrollBehavior(to) {
if (to.hash) {
return {
selector: to.hash
}
}
}
})

44
src/utils/store.js Normal file
View File

@@ -0,0 +1,44 @@
import Vue from 'vue'
import Vuex from 'vuex'
import preState from 'vuex-persistedstate'
import * as modules from "dui/lib/js/modules"
import axios from "./axios";
import extra from "../config.json"
Vue.use(Vuex)
export default new Vuex.Store({
state: {
homePage: {}
},
mutations: {
setHomePage(state, home) {
state.homePage = home
},
signOut(state, flag) {
const base = extra.base || ""
if (flag) {
state.user.token = null;
state.user.info = {}
sessionStorage.clear();
location.href = base + '/login' + location.hash;
} else {
axios.delete('/auth/token/logout').then(() => {
state.user.token = null;
sessionStorage.clear();
state.user.info = {}
location.href = base + '/login';
});
}
},
},
getters: {
//后台数据库中的应用集合,在本工程中不一定存在
mods: state => [
state.homePage,
state.user.info?.menuSet?.map(e => ({route: e.id, ...e, label: e.name}))
].flat().filter(Boolean)
},
modules,
plugins: [preState()]
})

31
src/views/building.vue Normal file
View File

@@ -0,0 +1,31 @@
<template>
<section class="building">
<div class="title">功能开发中,敬请期待...</div>
</section>
</template>
<script>
export default {
name: "building"
}
</script>
<style lang="scss" scoped>
.building {
position: relative;
height: 100%;
background-image: url("../assets/building.png");
background-size: 400px 300px;
background-repeat: no-repeat;
background-position: center, center;
.title {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin-top: 150px;
font-weight: bold;
}
}
</style>

42
src/views/console.vue Normal file
View File

@@ -0,0 +1,42 @@
<template>
<section class="console">
<div class="consoleBg" v-text="`欢迎使用${system.fullTitle}`"/>
</section>
</template>
<script>
import {mapState} from "vuex";
export default {
name: "console",
label: "工作台",
computed: {
...mapState(['sys']),
system: v => v.sys.info || {}
},
}
</script>
<style lang="scss" scoped>
.console {
height: 100%;
.consoleBg {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background-image: url("https://cdn.cunwuyun.cn/dvcp/consoleBg.png");
background-size: 600px 362px;
background-repeat: no-repeat;
background-position: center top;
padding-top: 402px;
font-size: 32px;
font-family: MicrosoftYaHei-Bold, MicrosoftYaHei;
font-weight: bold;
color: #95A1B0;
min-width: 600px;
text-align: center;
}
}
</style>

83
src/views/dvIndex.vue Normal file
View File

@@ -0,0 +1,83 @@
<template>
<section class="dvIndex">
<ai-dv-wrapper v-model="activeTab" :views="views" :title="title" :theme="theme" v-if="views.length" :background="bgImg" :type="currentStyle" :titleSize="titleSize">
<ai-dv-viewer urlPrefix="/app" :instance="instance" :dict="dict" :id="currentView.id"/>
</ai-dv-wrapper>
</section>
</template>
<script>
import Vue from "vue";
import {waiting} from "../utils";
export default {
name: "dvIndex",
provide() {
return {
dv: this
}
},
computed: {
currentView: v => v.views.find(e => e.id == v.activeTab) || v.views?.[0] || {},
background: v => JSON.parse(v.currentView.config || null)?.dashboard?.backgroundImage?.[0]?.url || "",
bgImg: v => v.theme == 1 ? 'https://cdn.cunwuyun.cn/dvcp/dv/img/dj-bg.png' : v.background,
theme() {
if (!this.currentView) return '0'
if (!this.currentView.config) return '0'
const config = JSON.parse(this.currentView.config)
if (config.custom) {
return '0'
}
return config.dashboard.theme
},
currentStyle: v => JSON.parse(v.currentView.config || null)?.dashboard?.style || "black",
titleSize: v => JSON.parse(v.currentView.config || "{}").dashboard?.titleSize
},
data() {
return {
instance: this.$request,
dict: this.$dict,
activeTab: 0,
views: [],
title: "",
}
},
methods: {
getDvOptions() {
let {id} = this.$route.query
return id ? this.instance.post("/app/appdiylargescreen/queryLargeScreenProjectDetailById", null, {
params: {id, status: 1}
}).then(res => {
if (res?.data) {
this.title = res.data.name
this.views = res.data.lsList?.map(e => ({...e, label: e.title}))
}
}) : Promise.reject()
},
loadDvs() {
//新App的自动化格式
waiting.init({innerHTML: '应用加载中..'})
let apps = require.context('../../apps', true, /\.(\/.+)\/App[A-Z][^\/]+D[Vv]\.vue$/, "lazy")
return Promise.all(apps.keys().map(path => apps(path).then(file => {
if (file.default) {
let {name} = file.default
waiting.setContent(`加载${name}...`)
Vue.component(name, file.default)
} else return 0
}))).then(() => {
waiting.setContent(`正在进入系统...`)
setTimeout(() => waiting.close(), 1000)
})
}
},
created() {
this.loadDvs().then(() => this.getDvOptions())
}
}
</script>
<style lang="scss" scoped>
.dvIndex {
height: 100%;
}
</style>

42
src/views/home.vue Normal file
View File

@@ -0,0 +1,42 @@
<template>
<section class="home">
<header-nav/>
<el-row class="fill" type="flex">
<slider-nav/>
<main-content class="fill"/>
</el-row>
<ai-copilot v-if="useCopilot" :http="$request"/>
</section>
</template>
<script>
import SliderNav from "../components/sliderNav";
import MainContent from "../components/mainContent";
import HeaderNav from "../components/headerNav";
import configExtra from "../config.json"
export default {
name: 'app',
components: {HeaderNav, MainContent, SliderNav},
computed: {
useCopilot: () => !!configExtra?.copilot
},
created() {
import("../../apps/actions").then(extra => {
const actions = extra?.default || {}
this.$store.hotUpdate({actions})
Object.keys(actions)?.map(action => this.$store.dispatch(action))
}).catch(() => 0)
},
}
</script>
<style lang="scss" scoped>
.home {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
padding-top: 48px;
box-sizing: border-box;
}
</style>

55
src/views/mainEntry.vue Normal file
View File

@@ -0,0 +1,55 @@
<template>
<section class="mainEntry fill">
<ai-detail list v-if="hasIntro">
<template #content>
<ai-intro :id="currentApp.guideId" :instance="$request" @start="handleStartUse"/>
</template>
</ai-detail>
<component v-else :is="app" :instance="$request" :dict="$dict" :permissions="$permissions" :menuName="currentApp.name"/>
</section>
</template>
<script>
import Building from "./building";
import Vue from "vue";
import {mapGetters, mapMutations, mapState} from "vuex";
export default {
name: "mainEntry",
components: {Building},
computed: {
...mapState(['logs']),
...mapGetters(['mods']),
currentApp() {
const {fullPath, name} = this.$route
return this.mods.find(e => !name ? fullPath.indexOf(e.path) > -1 : name == e.route) || Building
},
app() {
const {currentApp} = this
return Vue.component(currentApp?.component) ? currentApp.component : Building
},
hasIntro() {
const {app, currentApp, logs} = this
return !!currentApp.guideId && !logs?.closeIntro?.includes(app)
}
},
methods: {
...mapMutations(['addCloseIntro']),
handleStartUse() {
this.addCloseIntro(this.app)
}
}
}
</script>
<style lang="scss" scoped>
.mainEntry {
width: 100%;
height: 100%;
& > * {
height: 100%;
}
}
</style>

172
src/views/sign.vue Normal file
View File

@@ -0,0 +1,172 @@
<template>
<section class="sign">
<div class="left signLeftBg">
<el-row type="flex" align="middle">
<img class="AiIcon" v-if="/[\\\/]/.test(logo.icon)" :src="logo.icon" alt=""/>
<ai-icon v-else type="logo" :icon="logo.icon"/>
<div v-if="logo.text" class="logoText mar-l8" v-text="logo.text"/>
</el-row>
<div class="signLeftContent">
<div class="titlePane">
<b v-text="system.name"/>
<div v-text="system.title"/>
</div>
<div class="subTitle" v-for="(t,i) in subTitles" :key="i" v-html="t"/>
</div>
</div>
<div class="right">
<div class="projectName mar-b48" :title="system.fullTitle">{{ system.fullTitle }}</div>
<ai-sign v-if="system.edition=='saas'" @login="login" :instance="instance" visible :tps="['wxwork']" :sassLogin="!isDev"/>
<ai-sign v-else isSignIn @login="login" :instance="instance" visible :showScanLogin="system.edition=='standard'||!system.edition"/>
<el-row type="flex" align="middle" class="bottomRecord">
<div v-if="system.recordDesc" v-text="system.recordDesc"/>
<el-link v-if="system.recordNo" v-text="system.recordNo" :href="system.recordURL"/>
<div v-if="system.ssl" v-html="system.ssl"/>
</el-row>
</div>
<app-licence :instance="instance" ref="licence"/>
</section>
</template>
<script>
import {mapMutations, mapState} from 'vuex'
import AppLicence from "../components/AppLicence";
export default {
name: "sign",
components: {AppLicence},
computed: {
...mapState(['user', 'sys']),
instance: v => v.$request,
system: v => v.sys?.info || {},
subTitles() {
let list = [
"构建全域数字大脑,助力政府科学决策",
"全域统一应用入口,移动办公高效协同",
"直接触达居民微信,政民互动“零距离”"
]
return (typeof this.system.desc == "object" ? this.system.desc : JSON.parse(this.system.desc || null)) || list
},
logo: v => !!v.system.loginLogo ? {icon: v.system.loginLogo, text: v.system.loginLogoText} : {icon: v.system.logo, text: v.system.logoText},
isDev: () => process.env.NODE_ENV == "development"
},
created() {
if (this.user.token) {
this.handleGotoHome()
} else {
const {code, auth_code} = this.$route.query
if (code) {
this.toLogin(code)
} else if (auth_code) {
this.tpLogin(auth_code)
}
}
},
methods: {
...mapMutations(['setToken']),
login(data) {
if (data.data == '999') {
return this.$refs.licence.show()
}
if (data?.access_token) {
this.setToken([data.token_type, data.access_token].join(" "))
this.handleGotoHome()
}
},
handleGotoHome() {
this.$message.success("登录成功!")
if (this.$route.hash == "#dv") {
this.$router.push({name: "数据大屏入口", hash: "#dv"})
} else {
this.$router.push({name: "Home"})
}
},
toLogin(code) {
this.instance.post(`/auth/wechatcp-qr/token`, {
code: code,
type: 'cpuser'
}, {
auth: {
username: 'villcloud',
password: "villcloud"
},
params: {
grant_type: 'password',
scope: 'server'
}
}).then(this.login)
},
tpLogin(code) {
this.instance.post("/auth/wechatcp-qr/token", {code}, {
auth: {
username: 'villcloud',
password: "villcloud"
},
params: {
grant_type: 'password',
scope: 'server'
}
}).then(this.login).catch(() => this.$router.push({}))
}
},
}
</script>
<style lang="scss" scoped>
.sign {
display: flex;
box-sizing: border-box;
height: 100%;
.AiIcon {
font-size: 40px;
height: 40px;
}
.logoText {
font-size: 20px;
}
:deep(.left ) {
width: 480px;
flex-shrink: 0;
background-size: 100% 100%;
background-repeat: no-repeat;
padding-left: 64px;
padding-top: 40px;
box-sizing: border-box;
color: #fff;
font-size: 16px;
.iconcunwei1 {
font-size: 36px;
}
}
:deep(.right ) {
flex: 1;
min-width: 0;
background-color: #F6F8FB;
background-image: url("../assets/loginRightTop.png"), url("../assets/loginRightBottom.png");
background-repeat: no-repeat;
background-position: calc(100% - 80px) 0, calc(100% - 40px) 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.bottomRecord {
font-size: 12px;
color: #999;
gap: 16px;
position: fixed;
bottom: 20px;
.el-link {
font-size: inherit;
}
}
}
}
</style>

View File

@@ -217,10 +217,10 @@ export default {
&.AiDvWrapper1 {
:deep(.primary) {
.content {
background-image: url("assets/headerThemePrimaryBG_lb.png"), url("assets/headerThemePrimaryBG_rb.png");
background-image: url("./assets/headerThemePrimaryBG_lb.png"), url("./assets/headerThemePrimaryBG_rb.png");
.item {
background-image: url("assets/themeTimeIcon.png");
background-image: url("./assets/themeTimeIcon.png");
}
}

View File

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

View File

@@ -11,8 +11,8 @@ $--color-success: $successColor;
$--color-warning: $warnColor;
$--color-danger: $errorColor;
$--color-info: $infoColor;
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index";
$--font-path: 'element-ui/lib/theme-chalk/fonts';
@import "element-ui/packages/theme-chalk/src/index";
/**
常用内外边距样式

View File

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