Merge remote-tracking branch 'origin/build' into release
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
VUE_APP_SCOPE=biaopin
|
||||
#VUE_APP_API=https://web.fdfengshou.cn/
|
||||
VUE_APP_API=http://192.168.1.87:9000/
|
||||
#VUE_APP_API=http://192.168.1.87:9000/
|
||||
VUE_APP_API=https://www.wyzzb.com
|
||||
#VUE_APP_API=http://test87web.cunwuyun.cn/
|
||||
|
||||
@@ -3,11 +3,15 @@
|
||||
<header-nav v-if="showTools" title="web端产品库">
|
||||
<template #right>
|
||||
<mock/>
|
||||
<div @click="showTools=false,dvDev=true">大屏开发</div>
|
||||
<div @click="showTools=false">隐藏工具栏</div>
|
||||
<div @click="handleLogin">点此登录</div>
|
||||
</template>
|
||||
</header-nav>
|
||||
<el-row v-if="showTools" class="fill mar-t48" type="flex">
|
||||
<ai-dv-wrapper class="fill" v-if="dvDev">
|
||||
<router-view/>
|
||||
</ai-dv-wrapper>
|
||||
<el-row v-else-if="showTools" class="fill mar-t48" type="flex">
|
||||
<slider-nav/>
|
||||
<main-content class="fill"/>
|
||||
</el-row>
|
||||
@@ -26,10 +30,11 @@ 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";
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
components: {Mock, HeaderNav, MainContent, SliderNav},
|
||||
components: {AiDvWrapper, Mock, HeaderNav, MainContent, SliderNav},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
login() {
|
||||
@@ -42,6 +47,12 @@ export default {
|
||||
return {
|
||||
dialog: false,
|
||||
showTools: true,
|
||||
dvDev: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showTools(v) {
|
||||
v && (this.dvDev = false)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -102,6 +113,7 @@ html, body {
|
||||
top: 0;
|
||||
right: 60px;
|
||||
opacity: 0;
|
||||
z-index: 999;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
|
||||
@@ -27,6 +27,7 @@ export default {
|
||||
break
|
||||
case 'ai':
|
||||
this.esm = {
|
||||
biaopin: require.context('../../project/biaopin/AppCopilotConfig', true, /\.\/App[A-Z][^\/]+\.vue$/, 'lazy'),
|
||||
project: require.context('../../project/ai', true, /\.(\/.+)\/App[A-Z][^\/]+\.vue$/, 'lazy')
|
||||
}
|
||||
break
|
||||
|
||||
@@ -9,6 +9,8 @@ instance.defaults.baseURL = baseURLs[process.env.NODE_ENV]
|
||||
instance.interceptors.request.use(config => {
|
||||
if (config.url.startsWith("/node")) {
|
||||
config.baseURL = "/ns"
|
||||
} else if (config.url.startsWith("/sse")) {
|
||||
config.baseURL = "/"
|
||||
} else if (/\/project\/activeAnalysis/.test(location.pathname)) {
|
||||
config.baseURL = "/analysis"
|
||||
} else if (/\/project\/beta/.test(location.pathname)) {
|
||||
|
||||
@@ -70,7 +70,7 @@ export const waiting = {
|
||||
div.style.lineHeight = '100vh'
|
||||
div.style.color = '#26f'
|
||||
div.style.fontSize = '20px'
|
||||
div.style.background = 'rgba(255,255,255,.8)'
|
||||
div.style.background = 'rgba(255,255,255,.6)'
|
||||
div.style.backdropFilter = 'blur(6px)'
|
||||
document.body.appendChild(div)
|
||||
} else if (count < 10) {
|
||||
|
||||
72
project/biaopin/AppCodeGeneration/AppCodeGeneration.vue
Normal file
72
project/biaopin/AppCodeGeneration/AppCodeGeneration.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<component
|
||||
v-if="hasApp && configLoaded"
|
||||
:is="componentName"
|
||||
:params="params"
|
||||
:backType="backType"
|
||||
:instance="instance"
|
||||
:dict="dict"
|
||||
:appType="appType"
|
||||
:appId="appId"
|
||||
:configs="configs"
|
||||
@change="changePage" />
|
||||
<ai-empty v-else-if="hasApp">应用配置加载中...</ai-empty>
|
||||
<ai-empty v-else>读取应用失败</ai-empty>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Add from './components/Add.vue'
|
||||
import Detail from './components/Detail.vue'
|
||||
import List from './components/List.vue'
|
||||
|
||||
export default {
|
||||
label: '代码生成',
|
||||
name: 'AppCodeGeneration',
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object
|
||||
},
|
||||
components: {Add, Detail, List},
|
||||
computed: {
|
||||
appId() {
|
||||
return this.$route.query.app
|
||||
},
|
||||
hasApp() {
|
||||
return !!this.appId
|
||||
},
|
||||
configLoaded() {
|
||||
return !!this.configs.id
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
componentName: 'list',
|
||||
params: {},
|
||||
backType: '',
|
||||
configs: {},
|
||||
appType: '',
|
||||
// appId: '4fbba8b80576499985ca2f7b332f303a'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changePage(data) {
|
||||
this.componentName = data.type
|
||||
this.params = data.params
|
||||
this.backType = data.backType || ''
|
||||
},
|
||||
getConfigs() {
|
||||
this.instance.post(`/app/appapplicationinfo/queryApplicationInfo`, null, {
|
||||
params: {appId: this.appId}
|
||||
}).then((res) => {
|
||||
if (res?.data) {
|
||||
this.appType = res.data.appType
|
||||
this.configs = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getConfigs()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
230
project/biaopin/AppCodeGeneration/app/visit/Visit.vue
Normal file
230
project/biaopin/AppCodeGeneration/app/visit/Visit.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<ai-card title="走访记录" class="visit" v-loading="loading">
|
||||
<el-button slot="right" icon="iconfont iconAdd" type="text" @click="isShow = true">添加走访记录</el-button>
|
||||
<template #content>
|
||||
<div class="visit-list">
|
||||
<div class="visit-item" v-for="(item, index) in list" :key="index">
|
||||
<div class="visit-item__top">
|
||||
<div class="left">
|
||||
<div class="avatar">{{ item.name.substr(item.name.length - 2) }}</div>
|
||||
<h2>{{ item.name }}</h2>
|
||||
</div>
|
||||
<span>{{ item.visitTime }}</span>
|
||||
</div>
|
||||
<b>{{ item.title }}</b>
|
||||
<p>{{ item.description }}</p>
|
||||
<div class="visit-imgs" v-if="item.images.length">
|
||||
<ai-uploader v-model="item.images" :instance="instance" :limit="9" disabled/>
|
||||
</div>
|
||||
<div class="visit-status">
|
||||
<span>现实状态:</span>
|
||||
<i>{{ dict.getLabel('visitCondolenceReality', item.reality) }}</i>
|
||||
</div>
|
||||
</div>
|
||||
<ai-empty v-if="!list.length"></ai-empty>
|
||||
</div>
|
||||
<ai-dialog
|
||||
:visible.sync="isShow"
|
||||
width="1000px"
|
||||
height="500px"
|
||||
title="添加走访记录"
|
||||
@close="onClose"
|
||||
@onConfirm="onConfirm">
|
||||
<el-form ref="form" :model="form" label-width="110px" label-position="right" size="small">
|
||||
<ai-bar title="走访记录"></ai-bar>
|
||||
<div class="ai-form" :model="form" label-width="110px" label-position="right">
|
||||
<el-form-item label="走访时间" prop="visitTime"
|
||||
:rules="[{ required: true, message: '请选择走访时间', trigger: 'change' }]">
|
||||
<el-date-picker
|
||||
v-model="form.visitTime"
|
||||
type="datetime"
|
||||
style="width: 100%;"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择走访时间">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="现实状态" prop="reality"
|
||||
:rules="[{ required: true, message: '请选择现实状态', trigger: 'change' }]">
|
||||
<ai-select
|
||||
v-model="form.reality"
|
||||
:selectList="dict.getDict('visitCondolenceReality')"
|
||||
laceholder="请选择现实状态">
|
||||
</ai-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="入户走访事项" prop="title" style="width: 100%;"
|
||||
:rules="[{ required: true, message: '请输入 入户走访事项'}]">
|
||||
<el-input placeholder="请输入 入户走访事项" v-model="form.title" maxlength="30" show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item label="入户走访内容" prop="description" style="width: 100%;">
|
||||
<el-input type="textarea" placeholder="请输入 入户走访内容" v-model="form.description" :rows="4" maxlength="500"
|
||||
show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item label="图片" prop="images" style="width: 100%;">
|
||||
<ai-uploader v-model="form.images" :instance="instance" :limit="9" isShowTip/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</ai-dialog>
|
||||
</template>
|
||||
</ai-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'visit',
|
||||
|
||||
props: ['id', 'dict', 'instance', 'appId', 'name', 'areaId'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
appList: [],
|
||||
list: [],
|
||||
loading: false,
|
||||
isShow: false,
|
||||
form: {
|
||||
visitTime: '',
|
||||
reality: '',
|
||||
images: [],
|
||||
description: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['user'])
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.dict.load('visitCondolenceReality').then(() => {
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true
|
||||
this.instance.post(`/app/appvisitvondolence/list?optionId=${this.id}&size=10000`).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.list = res.data.records.map(item => {
|
||||
return {
|
||||
...item,
|
||||
images: item.images ? JSON.parse(item.images) : []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
onConfirm() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
this.instance.post(`/app/appvisitvondolence/addOrUpdate`, {
|
||||
...this.form,
|
||||
optionId: this.id,
|
||||
images: JSON.stringify(this.form.images),
|
||||
applicationId: this.appId,
|
||||
name: this.name,
|
||||
areaId: this.areaId
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success('添加成功')
|
||||
this.isShow = false
|
||||
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onClose() {
|
||||
this.form = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.visit {
|
||||
.visit-list {
|
||||
.visit-item {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.visit-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
|
||||
span {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #999;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
||||
& > p {
|
||||
line-height: 1.4;
|
||||
margin-bottom: 4px;
|
||||
text-align: justify;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
b {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.visit-item__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
img, .avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
margin-right: 10px;
|
||||
border-radius: 50%;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
background: #26f;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
540
project/biaopin/AppCodeGeneration/components/Add.vue
Normal file
540
project/biaopin/AppCodeGeneration/components/Add.vue
Normal file
@@ -0,0 +1,540 @@
|
||||
<template>
|
||||
<ai-detail v-if="pageShow" class="add-form">
|
||||
<template #title>
|
||||
<ai-title :title="params.id ? '编辑'+colData.applicationName : '新增' + colData.applicationName" isShowBottomBorder
|
||||
isShowBack @onBackClick="onBack(true)"></ai-title>
|
||||
</template>
|
||||
<template #content>
|
||||
<el-form ref="formData" class="ai-form" :rules="rules" :model="formData" label-width="110px" size="small">
|
||||
<ai-card :title="items[0].groupName" v-for="(items, indexs) in formDataList" :key="indexs" v-if="items.length">
|
||||
<template slot="content">
|
||||
<div v-for="(item, index) in items" :key="index" :style="item.grid == 1 ? 'width: 100%;' : 'width: 50%;'"
|
||||
class="form-div">
|
||||
<el-form-item :label="item.fieldName" :prop="item.fieldDbName" style="width: 100%">
|
||||
<!-- 字典下拉选择 -->
|
||||
<template v-if="item.type == 'dict'">
|
||||
<ai-select v-model="formData[item.fieldDbName]" :placeholder="item.fieldName" :selectList="dict.getDict(item.dict)"
|
||||
:disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)"/>
|
||||
</template>
|
||||
<!-- 单选radio -->
|
||||
<template v-else-if="item.type == 'radio'">
|
||||
<el-radio-group v-model="formData[item.fieldDbName]" :disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)">
|
||||
<el-radio :label="item.dictValue" v-for="(item, i) in dict.getDict(item.dict)" :key="i">{{ item.dictName }}</el-radio>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<!-- 开关onOff -->
|
||||
<template v-else-if="item.type == 'onOff'">
|
||||
<el-switch v-model="formData[item.fieldDbName]" active-color="#26f" inactive-color="#ddd" active-value="1" inactive-value="0"
|
||||
:disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)"></el-switch>
|
||||
</template>
|
||||
<!-- 多选checkbox -->
|
||||
<template v-else-if="item.type == 'checkbox'">
|
||||
<el-checkbox-group v-model="formData[item.fieldDbName]" :disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)">
|
||||
<el-checkbox v-for="(item, i) in dict.getDict(item.dict)" :label="item.dictValue" :key="i">
|
||||
{{ item.dictName }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</template>
|
||||
<!-- 网格 -->
|
||||
<template v-else-if="item.type === 'gird'">
|
||||
<el-input disabled :value="girdName" size="small" placeholder="请选择网格">
|
||||
<template slot="append">
|
||||
<el-button size="small"
|
||||
@click="showGrid = true, treeObj.checkedKeys = formData[item.fieldDbName] ? [formData[item.fieldDbName]] : [], gridFieldName = item.fieldDbName">
|
||||
选择网格
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
<template v-else-if="item.type === 'resident'">
|
||||
<el-input
|
||||
v-model="formData.name"
|
||||
:placeholder="'请选择'+item.fieldName">
|
||||
<template slot="append">
|
||||
<ai-person-select
|
||||
:instance="instance"
|
||||
:disabled="!!params.id"
|
||||
:url="'/app/appresident/list?auditType=1&areaId=' + user.info.areaId"
|
||||
:isMultiple="false" dialogTitle="选择" @selectPerson="onChange">
|
||||
<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>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
<template v-else-if="item.type == 'idNumber'">
|
||||
<ai-id v-model="formData[item.fieldDbName]" :disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)"/>
|
||||
</template>
|
||||
<!-- input输入框 -->
|
||||
<template v-else-if="item.type == 'input' || item.type == 'name' || item.type == 'phone'">
|
||||
<el-input v-model="formData[item.fieldDbName]" :placeholder="'请输入'+item.fieldName" clearable
|
||||
:disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)"
|
||||
:maxlength="item.maxLength" show-word-limit></el-input>
|
||||
</template>
|
||||
<!-- number 输入框 -->
|
||||
<template v-else-if="item.type == 'number'">
|
||||
<el-input-number v-model="formData[item.fieldDbName]" :label="'请输入'+item.fieldName"
|
||||
:disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)" :precision="item.decimalPlaces" :max="item.maxValue"
|
||||
:min="item.minValue"></el-input-number>
|
||||
</template>
|
||||
<!-- textarea输入框 -->
|
||||
<template v-else-if="item.type == 'textarea' || item.type == 'text'">
|
||||
<el-input v-model="formData[item.fieldDbName]" :placeholder="'请输入'+item.fieldName" clearable
|
||||
:disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)"
|
||||
:maxlength="item.maxLength" type="textarea" show-word-limit :rows="3"></el-input>
|
||||
</template>
|
||||
<!-- 日期选择 -->
|
||||
<template v-else-if="item.type == 'date'">
|
||||
<el-date-picker style="width: 100%;" v-model="formData[item.fieldDbName]" type="date" placeholder="请选择"
|
||||
:disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)"
|
||||
:value-format="item.timePattern"></el-date-picker>
|
||||
</template>
|
||||
<!-- 日期带时分秒选择 -->
|
||||
<template v-else-if="item.type == 'datetime'">
|
||||
<el-date-picker v-model="formData[item.fieldDbName]" type="datetime" placeholder="选择日期时间"
|
||||
:disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)"
|
||||
:value-format="item.timePattern"></el-date-picker>
|
||||
</template>
|
||||
<!-- 时间-时分秒选择 -->
|
||||
<template v-else-if="item.type == 'time'">
|
||||
<el-time-picker v-model="formData[item.fieldDbName]" placeholder="请选择"
|
||||
:disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)"
|
||||
:value-format="item.timePattern"></el-time-picker>
|
||||
</template>
|
||||
<!-- 附件 -->
|
||||
<template v-else-if="item.type == 'upload'">
|
||||
<ai-uploader :instance="instance" isShowTip fileType="file" v-model="formData[item.fieldDbName]" :disabled="item.disable == 1"
|
||||
acceptType=".zip,.rar,.doc,.docx,.xls,.ppt,.pptx,.pdf,.txt,.jpg,.png,.xlsx"
|
||||
:limit="item.fileMaxCount" :maxSize="item.fileChoseSize"></ai-uploader>
|
||||
</template>
|
||||
<!-- 富文本 -->
|
||||
<template v-else-if="item.type == 'rtf'">
|
||||
<ai-editor v-model="formData[item.fieldDbName]" :instance="instance"
|
||||
:disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)"/>
|
||||
</template>
|
||||
<!-- 地区选择 -->
|
||||
<template v-else-if="item.type == 'area'">
|
||||
<ai-area-get :instance="instance" v-model="formData[item.fieldDbName]" :name.sync="formData[item.fieldDbName +'_name']"
|
||||
:disabled="item.disable == 1 || !!(formData.resident_id && item.isInit)"/>
|
||||
</template>
|
||||
<!-- 人员选择 -->
|
||||
<div class="especial" v-else-if="item.type == 'user'">
|
||||
<span class="icon"> </span>
|
||||
<span class="people">{{ item.fieldName }}:</span>
|
||||
<ai-wechat-selecter slot="append" isShowUser :instance="instance"
|
||||
v-model="formData[item.fieldDbName]"></ai-wechat-selecter>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
</ai-card>
|
||||
</el-form>
|
||||
<ai-dialog
|
||||
title="选择网格"
|
||||
:visible.sync="showGrid"
|
||||
:destroyOnClose="true"
|
||||
@open="getGridList"
|
||||
@close="showGrid = false"
|
||||
@onConfirm="getCheckedTree"
|
||||
width="720px">
|
||||
<div class="grid">
|
||||
<el-tree
|
||||
:data="treeObj.treeList"
|
||||
:props="treeObj.defaultProps"
|
||||
node-key="id"
|
||||
ref="tree"
|
||||
:check-strictly="true"
|
||||
show-checkbox
|
||||
:default-checked-keys="treeObj.checkedKeys"
|
||||
:default-expanded-keys="treeObj.checkedKeys"
|
||||
@check="onCheckChange">
|
||||
</el-tree>
|
||||
</div>
|
||||
</ai-dialog>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button class="delete-btn footer-btn" @click="onBack">取消</el-button>
|
||||
<el-button class="footer-btn" type="primary" :loading="isLoading" @click="submit('formData')">提交</el-button>
|
||||
</template>
|
||||
</ai-detail>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'Add',
|
||||
props: {
|
||||
instance: Function,
|
||||
params: Object,
|
||||
dict: Object,
|
||||
appId: String,
|
||||
backType: String,
|
||||
configs: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
age: 2
|
||||
},
|
||||
pickerOptions0: {
|
||||
disabledDate(time) {
|
||||
return time.getTime() < Date.now() - 8.64e7;
|
||||
}
|
||||
},
|
||||
isLoading: false,
|
||||
girdName: '',
|
||||
gridFieldName: '',
|
||||
showGrid: false,
|
||||
formDataList: [],
|
||||
pageShow: true,
|
||||
treeObj: {
|
||||
treeList: [],
|
||||
defaultProps: {
|
||||
children: "children",
|
||||
label: "girdName",
|
||||
},
|
||||
checkedKeys: [],
|
||||
},
|
||||
colData: {},
|
||||
areaId: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
rules() {
|
||||
let rules = {}
|
||||
this.colData?.tableInfos?.map(e => {
|
||||
rules[e.fieldDbName] = []
|
||||
if (e.mustFill == 1) {//是否必填
|
||||
rules[e.fieldDbName]?.push({required: true, message: e.fieldTips})
|
||||
}
|
||||
})
|
||||
return rules
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getFormData()
|
||||
if (this.params.id) {
|
||||
this.getDetail()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getGridList() {
|
||||
this.instance.post(`/app/appgirdinfo/listAll3`).then((res) => {
|
||||
if (res?.data) {
|
||||
if (this.formData.girdId) {
|
||||
this.$set(this.treeObj, 'checkedKeys', [this.formData.girdId])
|
||||
}
|
||||
this.treeObj.treeList = this.$arr2tree(res.data, {parent: "parentGirdId"})
|
||||
}
|
||||
})
|
||||
},
|
||||
onChange(e) {
|
||||
this.formData.resident_id = e.id
|
||||
Object.keys(this.formData).forEach(item => {
|
||||
for (var p in e) {
|
||||
if (item === p) {
|
||||
this.$set(this.formData, item, e[item])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const idNumberInfo = new ID(e.idNumber)
|
||||
this.$set(this.formData, 'birthDate', idNumberInfo.birthday)
|
||||
this.formData.photo = e.photo ? [{
|
||||
url: e.photo
|
||||
}] : []
|
||||
},
|
||||
getCheckedTree() {
|
||||
const gird = this.$refs.tree.getCheckedNodes()
|
||||
|
||||
if (gird.length) {
|
||||
this.girdName = gird[0].girdName
|
||||
this.formData[this.gridFieldName] = gird[0].id
|
||||
} else {
|
||||
this.girdName = ''
|
||||
this.formData[this.gridFieldName] = ''
|
||||
}
|
||||
|
||||
this.showGrid = false
|
||||
},
|
||||
|
||||
onCheckChange(e) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.tree.getCheckedKeys().forEach(v => {
|
||||
this.$refs.tree.setChecked(v, false)
|
||||
})
|
||||
this.$refs.tree.setChecked(e.id, true)
|
||||
})
|
||||
},
|
||||
|
||||
format(list) {
|
||||
return list.map(item => {
|
||||
if (item.girdList && item.girdList.length) {
|
||||
item.girdList = this.format(item.girdList)
|
||||
}
|
||||
return item
|
||||
})
|
||||
},
|
||||
|
||||
initForm(data) {
|
||||
this.colData = data
|
||||
let dictList = []
|
||||
let formList = {}
|
||||
data.tableInfos.map((item) => {
|
||||
let colItem
|
||||
if (item.dictionaryCode) {
|
||||
dictList.push(item.dictionaryCode)
|
||||
}
|
||||
if (item.dictionaryCode && item.type != 'radio' && item.type != 'checkbox' && item.type != 'onOff') {
|
||||
colItem = {
|
||||
...item,
|
||||
type: 'dict',
|
||||
dict: item.dictionaryCode
|
||||
}
|
||||
if (!this.params.id) {
|
||||
colItem.fieldValue = item.defaultValue || ''
|
||||
}
|
||||
} else if (item.type == 'radio') {
|
||||
colItem = {
|
||||
...item,
|
||||
dict: item.dictionaryCode,
|
||||
}
|
||||
if (!this.params.id) {
|
||||
colItem.fieldValue = item.defaultValue || ''
|
||||
}
|
||||
} else if (item.type == 'checkbox') {
|
||||
colItem = {
|
||||
...item,
|
||||
dict: item.dictionaryCode,
|
||||
fieldValue: []
|
||||
}
|
||||
if (!this.params.id && item.defaultValue) {
|
||||
colItem.fieldValue = item.defaultValue?.split('`')
|
||||
}
|
||||
|
||||
} else if (item.type === 'upload') {
|
||||
colItem = {
|
||||
...item,
|
||||
fieldValue: []
|
||||
}
|
||||
} else if (item.type == 'onOff') {
|
||||
colItem = {
|
||||
...item,
|
||||
}
|
||||
if (!this.params.id) {
|
||||
colItem.fieldValue = item.defaultValue || 0
|
||||
}
|
||||
} else if (item.type == 'number') {
|
||||
colItem = {
|
||||
...item,
|
||||
type: item.type,
|
||||
min: item.minValue || 1,
|
||||
max: item.maxValue || 1000000000,
|
||||
minValue: item.minValue || 1,
|
||||
maxValue: item.maxValue || 1000000000
|
||||
}
|
||||
if (!this.params.id) {
|
||||
colItem.fieldValue = Number(item.defaultValue) || 0
|
||||
}
|
||||
} else {
|
||||
if (item.type == 'date' && !item.timePattern) {
|
||||
item.timePattern = 'yyyy-MM-dd'
|
||||
}
|
||||
if (item.type == 'datetime' && !item.timePattern) {
|
||||
item.timePattern = 'yyyy-MM-dd HH:mm:ss'
|
||||
}
|
||||
if (item.type == 'time' && !item.timePattern) {
|
||||
item.timePattern = 'HH:mm:ss'
|
||||
}
|
||||
|
||||
colItem = {
|
||||
...item,
|
||||
type: item.type,
|
||||
}
|
||||
if (!this.params.id) {
|
||||
colItem.fieldValue = item.defaultValue || ''
|
||||
}
|
||||
}
|
||||
// console.log(colItem)
|
||||
formList[item.groupIndex]?.push(colItem) || (formList[item.groupIndex] = [colItem])
|
||||
if (item.type === 'number') {
|
||||
this.$set(this.formData, item.fieldDbName, 1)
|
||||
} else {
|
||||
this.$set(this.formData, item.fieldDbName, colItem.fieldValue || '')
|
||||
}
|
||||
})
|
||||
this.formDataList = Object.values(formList)
|
||||
|
||||
this.$forceUpdate()
|
||||
|
||||
if (dictList.length) {
|
||||
this.getDictList(dictList)
|
||||
} else {
|
||||
this.pageShow = true
|
||||
}
|
||||
},
|
||||
getFormData() {
|
||||
this.initForm(this.configs)
|
||||
},
|
||||
getDetail() {
|
||||
this.instance.post(`/app/appapplicationinfo/queryDetailById?appId=${this.appId}&id=${this.params.id}`).then((res) => {
|
||||
if (res?.data) {
|
||||
this.configs.tableInfos.map((item) => {
|
||||
this.formData[item.fieldDbName] = res.data[item.fieldDbName] || ''
|
||||
if (item.type == 'checkbox') {
|
||||
this.formData[item.fieldDbName] = this.formData[item.fieldDbName]?.split(',')?.filter(e => !!e)
|
||||
}
|
||||
|
||||
if (item.type === 'gird' && this.formData[item.fieldDbName]) {
|
||||
this.girdName = res.data[`${item.fieldDbName}_name`]
|
||||
}
|
||||
|
||||
if (item.type === 'upload' && !this.formData[item.fieldDbName]) {
|
||||
this.formData[item.fieldDbName] = []
|
||||
}
|
||||
|
||||
if (item.type === 'upload' && this.formData[item.fieldDbName]) {
|
||||
this.formData[item.fieldDbName] = this.formData[item.fieldDbName].split(',').map(v => {
|
||||
return {
|
||||
url: v
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getDictList(listName) {
|
||||
this.dict.load(listName.join(',')).then(() => {
|
||||
this.pageShow = true
|
||||
})
|
||||
},
|
||||
submit() {
|
||||
this.$refs.formData?.validate((valid) => {
|
||||
if (valid) {
|
||||
this.isLoading = true
|
||||
this.formDataList.map((item) => {
|
||||
if (item.length) {
|
||||
item.map((items) => {
|
||||
if (items.type == 'checkbox') { //多选
|
||||
this.formData[items.fieldDbName] = this.formData[items.fieldDbName]?.filter(e => !!e)?.toString()
|
||||
}
|
||||
if (items.type == 'upload') {
|
||||
this.formData[items.fieldDbName] = this.formData[items.fieldDbName].map(v => v.url).join(',')
|
||||
}
|
||||
|
||||
if (items.type == 'gird' && this.formData[items.fieldDbName]) {
|
||||
this.formData[items.fieldDbName] = this.formData[items.fieldDbName] + '_' + this.girdName
|
||||
}
|
||||
if (items.type == 'area' && this.formData[items.fieldDbName]) {
|
||||
var area = []
|
||||
area.push(this.formData[items.fieldDbName])
|
||||
area.push(this.formData[items.fieldDbName + '_name'])
|
||||
this.formData[items.fieldDbName] = area.join('_')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
this.instance.post(`/app/appapplicationinfo/addOrUpdate?appId=${this.appId}`, {
|
||||
...this.formData,
|
||||
id: this.params.id || ''
|
||||
}).then((res) => {
|
||||
this.isLoading = false
|
||||
if (res.code == 0) {
|
||||
this.$message.success('提交成功')
|
||||
setTimeout(() => {
|
||||
this.onBack(true)
|
||||
}, 600)
|
||||
}
|
||||
}).catch(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
onBack(isRefresh) {
|
||||
this.$emit('change', {
|
||||
type: this.backType == 'Detail' ? 'detail' : 'list',
|
||||
params: this.params,
|
||||
isRefresh: !!isRefresh,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep( .ai-card ){
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-div {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.add-form {
|
||||
:deep( .AiPersonSelect ){
|
||||
.el-button {
|
||||
border-color: transparent;
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
border-top: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.especial {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.icon {
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
padding-top: 5px;
|
||||
margin-left: 20px;
|
||||
color: #f46;
|
||||
}
|
||||
|
||||
.people {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
padding-right: 12px;
|
||||
vertical-align: top;
|
||||
width: 64px;
|
||||
word-break: break-all;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.AiWechatSelecter {
|
||||
display: inline-block;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.mar-r40 {
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.w80 {
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
|
||||
.AiWechatSelecter {
|
||||
width: calc(100% - 120px);
|
||||
}
|
||||
</style>
|
||||
257
project/biaopin/AppCodeGeneration/components/Detail.vue
Normal file
257
project/biaopin/AppCodeGeneration/components/Detail.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<ai-detail v-loading="pageShow" isHasSidebar>
|
||||
<template #title>
|
||||
<ai-title :title="colData.applicationName+'详情'" isShowBottomBorder isShowBack @onBackClick="onBack(true)"></ai-title>
|
||||
</template>
|
||||
<template #content>
|
||||
<AiSidebar :tabTitle="tabTitle" v-model="currIndex" v-if="appType"></AiSidebar>
|
||||
<ai-card v-show="currIndex === 0" :title="items[0].groupName" v-for="(items, indexs) in formDataList" :key="indexs">
|
||||
<template slot="content">
|
||||
<ai-wrapper>
|
||||
<ai-info-item :label="item.fieldName" v-show="item.type !== 'resident'" v-for="(item, index) in items" :key="index" :isLine="item.grid == 1">
|
||||
<span v-if="item.dict && item.type != 'checkbox'">{{$dict.getLabel(item.dict, formData[item.fieldDbName]) || '-'}}</span>
|
||||
<span v-else-if="item.type == 'checkbox'">{{formData[item.fieldDbName]}}</span>
|
||||
<span v-else-if="item.type == 'rtf'" v-html="formData[item.fieldDbName]"></span>
|
||||
<ai-file-list :fileList="formData[item.fieldDbName]" v-else-if="item.type == 'upload'" :fileOps="{name: 'name', size: 'fileSizeStr'}"></ai-file-list>
|
||||
<span v-else-if="item.type == 'area'">{{ formData[item.fieldDbName + '_name'] }}</span>
|
||||
<span v-else-if="item.type == 'gird'">{{ formData[item.fieldDbName + '_name'] }}</span>
|
||||
<span v-else>{{ formData[item.fieldDbName] || '-' }}</span>
|
||||
</ai-info-item>
|
||||
</ai-wrapper>
|
||||
</template>
|
||||
</ai-card>
|
||||
<component
|
||||
:is="component"
|
||||
:name="params.name || params.name00"
|
||||
:areaId="formData.area"
|
||||
:id="params.id"
|
||||
:appId="appId"
|
||||
:dict="dict"
|
||||
:instance="instance"
|
||||
v-if="currIndex === 1">
|
||||
</component>
|
||||
</template>
|
||||
</ai-detail>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import Visit from '../app/visit/Visit'
|
||||
|
||||
export default {
|
||||
name: 'Detail',
|
||||
|
||||
props: {
|
||||
dict: Object,
|
||||
appId: String,
|
||||
params: Object,
|
||||
configs: Object,
|
||||
appType: String,
|
||||
instance: Function
|
||||
},
|
||||
|
||||
components: {
|
||||
Visit
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
formData: {},
|
||||
colData: {},
|
||||
formDataList: [],
|
||||
tabTitle: ['人员信息'],
|
||||
pageShow: false,
|
||||
currIndex: 0,
|
||||
component: ''
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
},
|
||||
|
||||
created () {
|
||||
if (!this.appType) return
|
||||
|
||||
if (this.appType === '0') {
|
||||
this.component = 'Visit'
|
||||
}
|
||||
this.dict.load('diyAppType').then(() => {
|
||||
this.tabTitle.push(this.dict.getLabel('diyAppType', this.appType))
|
||||
})
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.pageShow = true
|
||||
this.getDetail()
|
||||
},
|
||||
|
||||
methods: {
|
||||
initForm (data) {
|
||||
this.colData = data
|
||||
let dictList = []
|
||||
let formList = {}
|
||||
data.tableInfos.map((item) => {
|
||||
let colItem
|
||||
if (item.dictionaryCode) {
|
||||
dictList.push(item.dictionaryCode)
|
||||
}
|
||||
if (item.dictionaryCode && item.type != 'radio' && item.type != 'checkbox') {
|
||||
colItem = {
|
||||
...item,
|
||||
type: 'dict',
|
||||
dict: item.dictionaryCode
|
||||
}
|
||||
} else if (item.type == 'radio') {
|
||||
colItem = {
|
||||
...item,
|
||||
dict: item.dictionaryCode,
|
||||
}
|
||||
} else if (item.type == 'checkbox') {
|
||||
colItem = {
|
||||
...item,
|
||||
dict: item.dictionaryCode,
|
||||
fieldValue: item.fieldValue?.split(',') || []
|
||||
}
|
||||
} else if (item.type == 'onOff') {
|
||||
colItem = {
|
||||
...item,
|
||||
fieldValue: 0
|
||||
}
|
||||
} else {
|
||||
if (item.type == 'date' && !item.timePattern) {
|
||||
item.timePattern = 'yyyy-MM-dd'
|
||||
}
|
||||
if (item.type == 'datetime' && !item.timePattern) {
|
||||
item.timePattern = 'HH:mm:ss yyyy-MM-dd'
|
||||
}
|
||||
if (item.type == 'time' && !item.timePattern) {
|
||||
item.timePattern = 'HH:mm:ss'
|
||||
}
|
||||
|
||||
colItem = {
|
||||
...item,
|
||||
type: item.type,
|
||||
}
|
||||
}
|
||||
formList[item.groupIndex]?.push(colItem) || (formList[item.groupIndex] = [colItem])
|
||||
if (item.type === 'upload') {
|
||||
this.$set(this.formData, colItem.fieldDbName, this.formData[colItem.fieldDbName] ? this.formData[colItem.fieldDbName].split(',').map(v => {
|
||||
return {
|
||||
url: v
|
||||
}
|
||||
}) : [])
|
||||
} else {
|
||||
this.$set(this.formData, colItem.fieldDbName, this.formData[colItem.fieldDbName] || "")
|
||||
}
|
||||
})
|
||||
this.formDataList = Object.values(formList)
|
||||
|
||||
this.$forceUpdate()
|
||||
|
||||
if (dictList.length) {
|
||||
this.getDictList(dictList)
|
||||
} else {
|
||||
this.pageShow = true
|
||||
}
|
||||
},
|
||||
|
||||
getFormData () {
|
||||
this.initForm(this.configs)
|
||||
},
|
||||
|
||||
getDetail () {
|
||||
this.instance.post(`/app/appapplicationinfo/queryDetailById?appId=${this.appId}&id=${this.params.id}`).then((res) => {
|
||||
if (res?.data) {
|
||||
this.formData = res.data
|
||||
this.getFormData()
|
||||
|
||||
this.pageShow = false
|
||||
}
|
||||
}).catch(() => {
|
||||
this.pageShow = false
|
||||
})
|
||||
},
|
||||
|
||||
getDictList (listName) {
|
||||
this.dict.load(listName.join(',')).then(() => {
|
||||
|
||||
})
|
||||
},
|
||||
|
||||
onBack (isRefresh) {
|
||||
this.$emit('change', {
|
||||
type: 'list',
|
||||
isRefresh: !!isRefresh,
|
||||
})
|
||||
},
|
||||
|
||||
toEdit () {
|
||||
this.$emit('change', {
|
||||
type: 'Add',
|
||||
params: this.params,
|
||||
backType: 'Detail'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.form-div{
|
||||
display: inline-block;
|
||||
}
|
||||
.especial {
|
||||
margin-bottom: 12px;
|
||||
.icon {
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
padding-top: 5px;
|
||||
margin-left: 20px;
|
||||
color: #f46;
|
||||
}
|
||||
.people {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-right: 16px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.AiWechatSelecter {
|
||||
display: inline-block;
|
||||
margin-left: 3px;
|
||||
}
|
||||
.hint {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-left: 16px;
|
||||
}
|
||||
.mar-r40{
|
||||
margin-right: 40px;
|
||||
}
|
||||
.w80{
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
.add-icon{
|
||||
text-align: right;
|
||||
cursor: pointer;
|
||||
i{
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
.color1{
|
||||
color:#4B87FE;
|
||||
}
|
||||
.color2{
|
||||
color:#2EA222;
|
||||
}
|
||||
.color3{
|
||||
color:#999999;
|
||||
}
|
||||
.AiWechatSelecter{
|
||||
width: calc(100% - 150px);
|
||||
}
|
||||
</style>
|
||||
323
project/biaopin/AppCodeGeneration/components/List.vue
Normal file
323
project/biaopin/AppCodeGeneration/components/List.vue
Normal file
@@ -0,0 +1,323 @@
|
||||
<template>
|
||||
<ai-list v-if="pageShow">
|
||||
<template slot="title">
|
||||
<ai-title :title="configs.applicationName" isShowBottomBorder :instance="instance" :disabledLevel="disabledLevel" :isShowArea="appType == 0" v-model="areaId" @change="changeArea"></ai-title>
|
||||
</template>
|
||||
<template slot="content">
|
||||
<ai-search-bar v-if="searchList.length" bottomBorder style="margin-bottom: 12px;">
|
||||
<template #left>
|
||||
<div v-for="(item, index) in searchList" :key="index">
|
||||
<ai-select multiple
|
||||
v-model="search[item.searchValue]"
|
||||
:placeholder="'请选择'+item.label" clearable
|
||||
@change=" $forceUpdate();(page.current = 1), getList()"
|
||||
:selectList="dict.getDict(item.dict)"
|
||||
v-if="item.type == 'dict'">
|
||||
</ai-select>
|
||||
<ai-search v-else-if="item.type == 'date'" :label="item.label">
|
||||
<el-date-picker size="small" v-model="search[item.searchValue]" type="daterange" range-separator="至"
|
||||
start-placeholder="开始日期" end-placeholder="结束日期"
|
||||
@change="$forceUpdate();(page.current = 1), getList()"
|
||||
value-format="yyyy-MM-dd"/>
|
||||
</ai-search>
|
||||
<ai-search v-else-if="item.type == 'time'" :label="item.label">
|
||||
<el-time-picker is-range size="small" v-model="search[item.searchValue]" range-separator="至"
|
||||
start-placeholder="开始时间" end-placeholder="结束时间" placeholder="选择时间范围"
|
||||
@change="$forceUpdate();(page.current = 1), getList()"
|
||||
value-format="HH:mm:ss"></el-time-picker>
|
||||
</ai-search>
|
||||
<ai-search v-else-if="item.type == 'area'" :label="item.label">
|
||||
<ai-area-get :instance="instance" v-model="search[item.searchValue]"/>
|
||||
</ai-search>
|
||||
</div>
|
||||
</template>
|
||||
<template #right v-if="showRightInput">
|
||||
<el-input :placeholder="placeholder" v-model="search.searchParam" size="small"
|
||||
@keyup.enter.native="$forceUpdate();(page.current = 1), getList()"
|
||||
@clear="$forceUpdate();(page.current = 1), getList()"
|
||||
@change="$forceUpdate();(page.current = 1), getList()" clearable prefix-icon="iconfont iconSearch"/>
|
||||
</template>
|
||||
</ai-search-bar>
|
||||
<ai-search-bar>
|
||||
<template #left>
|
||||
<el-button type="primary" icon="iconfont iconAdd" size="small"
|
||||
v-if="configs.insertEnable == 1" @click="toAdd('', 'Add')">添加
|
||||
</el-button>
|
||||
<el-button icon="iconfont iconDelete" size="small" :disabled="ids.length == 0"
|
||||
v-if="configs.batchDelEnable == 1" @click="delAll()">删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #right>
|
||||
<ai-import :instance="instance" v-if="configs.importEnable == 1" type="appapplicationinfo"
|
||||
:importParams="{appId}" :tplParams="{appId}" :name="configs.applicationName" @success="getList()">
|
||||
<el-button icon="iconfont iconImport">导入</el-button>
|
||||
</ai-import>
|
||||
<el-button icon="iconfont iconExported" size="small" v-if="configs.exportEnalbe == 1" @click="down()"
|
||||
>导出
|
||||
</el-button>
|
||||
</template>
|
||||
</ai-search-bar>
|
||||
<ai-table class="ai-table" :tableData="tableData" :col-configs="colConfigs" :total="page.total"
|
||||
:current.sync="page.current" :size.sync="page.size" @getList="getList" :dict="dict"
|
||||
@selection-change="(v) => (ids = v.map((e) => e.id))">
|
||||
<el-table-column v-for="(item, indexs) in colConfigs" :key="indexs" :slot="item.slot" :label="item.label"
|
||||
show-overflow-tooltip
|
||||
align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<div v-if="item.type != 'checkbox' && item.dict">
|
||||
{{ $dict.getLabel(item.dict, row[item.fieldDbName]) || '-' }}
|
||||
</div>
|
||||
<div v-if="item.type == 'rtf'" v-html="row[item.fieldDbName]"></div>
|
||||
<div v-if="item.type == 'checkbox'">{{ row[item.fieldDbName] }}</div>
|
||||
<div v-if="item.type == 'gird'">{{ row[item.fieldDbName + '_name'] }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="options" label="操作" fixed="right" align="center" width="160">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="table-options">
|
||||
<el-button type="text" @click="toDetail(row, 'Detail')">详情</el-button>
|
||||
<el-button type="text" @click="toDetail(row, 'Add')" v-if="configs.editEnable == 1">编辑</el-button>
|
||||
<el-button type="text" @click="del(row.id)" v-if="configs.deleteEnable == 1">删除</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,
|
||||
params: Object,
|
||||
appId: String,
|
||||
configs: Object,
|
||||
appType: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pageShow: false,
|
||||
tableData: [],
|
||||
colConfigs: [],
|
||||
page: {
|
||||
size: 10,
|
||||
current: 1,
|
||||
total: 0,
|
||||
},
|
||||
search: {
|
||||
searchParam: '',
|
||||
},
|
||||
searchList: [],
|
||||
ids: [],
|
||||
showRightInput: false,
|
||||
placeholder: '请输入',
|
||||
disabledLevel: 0,
|
||||
areaId: ''
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
},
|
||||
|
||||
created() {
|
||||
this.areaId = this.user.info.areaId
|
||||
this.disabledLevel = this.user.info.areaList.length - 1
|
||||
this.initConfigs()
|
||||
},
|
||||
methods: {
|
||||
initConfigs() {
|
||||
var dictList = []
|
||||
var colList = []
|
||||
var searchList = []
|
||||
var placeholderList = []
|
||||
this.configs.showListFields.map((item) => {
|
||||
var colItem
|
||||
if (item.dictionaryCode) {
|
||||
dictList.push(item.dictionaryCode)
|
||||
colItem = {
|
||||
slot: item.fieldDbName,
|
||||
label: item.fieldName,
|
||||
dict: item.dictionaryCode,
|
||||
fieldDbName: item.fieldDbName,
|
||||
type: item.type
|
||||
}
|
||||
} else if (item.type == 'rtf') {
|
||||
colItem = {label: item.fieldName, type: item.type, slot: item.fieldDbName, fieldDbName: item.fieldDbName}
|
||||
} else if (item.type == 'area') {
|
||||
colItem = {prop: item.fieldDbName + '_name', label: item.fieldName, align: "center"}
|
||||
} else if (item.type == 'gird') {
|
||||
colItem = {prop: item.fieldDbName + '_name', label: item.fieldName, align: "center"}
|
||||
}else {
|
||||
colItem = {prop: item.fieldDbName, label: item.fieldName, align: "center"}
|
||||
}
|
||||
colList.push(colItem)
|
||||
})
|
||||
this.configs.fuzzyQueryFields.map((item) => {
|
||||
var searchItem = {}
|
||||
if (item.dictionaryCode) {
|
||||
searchItem = {
|
||||
type: 'dict',
|
||||
label: item.fieldName,
|
||||
dict: item.dictionaryCode,
|
||||
searchValue: item.fieldDbName
|
||||
}
|
||||
}
|
||||
|
||||
if (item.type == 'input' || item.type == 'name' || item.type == 'idNumber' || item.type == 'phone') {
|
||||
placeholderList.push(item.fieldName)
|
||||
this.showRightInput = true
|
||||
}
|
||||
|
||||
if (item.type == 'date') {
|
||||
searchItem = {
|
||||
type: 'date',
|
||||
label: item.fieldName,
|
||||
searchValue: item.fieldDbName
|
||||
}
|
||||
}
|
||||
|
||||
if (item.type == 'time') {
|
||||
searchItem = {
|
||||
type: 'time',
|
||||
label: item.fieldName,
|
||||
searchValue: item.fieldDbName
|
||||
}
|
||||
}
|
||||
|
||||
if (item.type == 'area') {
|
||||
searchItem = {
|
||||
type: 'area',
|
||||
label: item.fieldName,
|
||||
searchValue: item.fieldDbName
|
||||
}
|
||||
}
|
||||
|
||||
this.$set(this.search, item.fieldDbName, '')
|
||||
searchList.push(searchItem)
|
||||
})
|
||||
|
||||
this.colConfigs = colList
|
||||
this.searchList = searchList
|
||||
this.$forceUpdate()
|
||||
|
||||
var text = placeholderList.join('/')
|
||||
this.placeholder = this.placeholder + text
|
||||
|
||||
if (this.configs.batchDelEnable == 1) {
|
||||
this.colConfigs.unshift({type: 'selection', width: 100})
|
||||
}
|
||||
if (dictList.length) {
|
||||
this.getDictList(dictList)
|
||||
} else {
|
||||
this.pageShow = true
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
getDictList(listName) {
|
||||
this.dict.load(listName.join(',')).then(() => {
|
||||
this.pageShow = true
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
getList() {
|
||||
this.instance.post(`/app/appapplicationinfo/list?appId=${this.appId}¤t=${this.page.current}&size=${this.page.size}&areaId=${this.areaId}`, {...this.search}).then((res) => {
|
||||
if (res?.data) {
|
||||
this.tableData = res.data.records
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
toDetail(item, type) {
|
||||
this.$emit('change', {
|
||||
type: type,
|
||||
params: item,
|
||||
backType: 'List'
|
||||
})
|
||||
},
|
||||
toAdd() {
|
||||
this.$emit('change', {
|
||||
type: 'Add',
|
||||
params: {
|
||||
type: 'Add',
|
||||
}
|
||||
})
|
||||
},
|
||||
del(id) {
|
||||
this.$confirm("删除后不可恢复,是否要删除?", {
|
||||
type: 'error'
|
||||
}).then(() => {
|
||||
this.instance.post(`/app/appapplicationinfo/delete?appId=${this.appId}&ids=${id}`).then((res) => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success("删除成功!");
|
||||
this.getList();
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
delAll() {
|
||||
if (this.ids.length > 100) {
|
||||
return this.$message.error("删除的数据最多不能超过100条!");
|
||||
}
|
||||
var id = this.ids.join(',')
|
||||
this.del(id)
|
||||
},
|
||||
|
||||
reset() {
|
||||
Object.keys(this.search).forEach((e) => {
|
||||
this.search[e] = ''
|
||||
})
|
||||
this.getList()
|
||||
},
|
||||
down() {
|
||||
var id = ''
|
||||
if (this.ids.length) {
|
||||
id = this.ids.join(',')
|
||||
}
|
||||
this.instance.post(`/app/appapplicationinfo/export?appId=${this.appId}&ids=${id}`, this.search, {
|
||||
responseType: 'blob',
|
||||
timeout: 100000
|
||||
}).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', this.configs.applicationName + '.xls')
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.$error.success(err)
|
||||
})
|
||||
},
|
||||
|
||||
changeArea() {
|
||||
this.page.current = 1
|
||||
this.getList()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mar-b10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -5,7 +5,7 @@
|
||||
<template slot="content">
|
||||
<ai-search-bar>
|
||||
<template #left>
|
||||
<el-button type="primary" icon="iconfont iconAdd" @click="dialog = true"> 添加</el-button>
|
||||
<el-button type="primary" icon="iconfont iconAdd" @click="add"> 添加</el-button>
|
||||
<el-cascader size="small" v-model="systemRuleIdList" :options="rulesOps" placeholder="请选择事件/类型" clearable :props="rulesProps"
|
||||
@change="handleTypeSearch" ref="eventTypeSearch"/>
|
||||
<ai-select v-model="search.status" @change="(page.current = 1), getList()" placeholder="请选择状态" :selectList="$dict.getDict('integralRuleStatus')">
|
||||
@@ -14,12 +14,20 @@
|
||||
</ai-search-bar>
|
||||
<ai-table :tableData="tableData" :col-configs="colConfigs" :total="page.total" :dict="dict" :current.sync="page.current" :size.sync="page.size"
|
||||
@getList="getList()">
|
||||
<el-table-column slot="scoringCycle" label="周期范围" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<span v-if="row.parentRuleName == '工单处理'">-</span>
|
||||
<span v-else>{{row.numberLimit.length ? $dict.getLabel("integralRuleScoringCycle", row.scoringCycle)
|
||||
: $dict.getLabel("integralRuleScoringCycle", row.scoringCycle) + row.numberLimit + "次"}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="integral" label="分值" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<!-- <span v-if="row.integralValueType == 1">
|
||||
{{ row.integralStart > 0 ? "+" + row.integralStart : row.integralStart }}~{{ row.integralEnd > 0 ? "+" + row.integralEnd : row.integralEnd }}
|
||||
</span> -->
|
||||
<span>{{ row.integral > 0 ? "+" : "" }}{{ row.integral }}</span>
|
||||
<span v-if="row.integral > 0">{{ row.integral > 0 ? "+" : "" }}{{ row.integral }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="options" label="操作" align="center" fixed="right" width="200">
|
||||
@@ -47,7 +55,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="规则">
|
||||
<div>常规</div>
|
||||
<div>{{form.systemRuleId != '自定义' ? form.ruleType == 1 ? '阶梯' : '常规' : '常规'}}</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- <el-form-item label="规则" prop="ruleType" v-if="form.ruleType>-1" required>
|
||||
@@ -77,18 +85,50 @@
|
||||
</el-table>
|
||||
</el-form-item> -->
|
||||
|
||||
<el-form-item label="周期范围" prop="scoringCycle">
|
||||
<el-form-item label="周期范围" prop="scoringCycle" v-if="!isWorkOrder">
|
||||
<ai-select v-model="form.scoringCycle" :selectList="$dict.getDict('integralRuleScoringCycle')" :disabled="isOneAndTen"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="奖励次数">
|
||||
<el-form-item label="奖励次数" v-if="!isWorkOrder">
|
||||
<el-input type="number" placeholder="请输入,周期范围内,不填写表示不限制" v-model.number="form.numberLimit" clearable :disabled="isOneAndTen"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="积分分值" prop="integral">
|
||||
|
||||
<el-form-item label="积分分值" prop="integral" v-if="!isWorkOrder">
|
||||
<el-input placeholder="请输入" v-model="form.integral" clearable/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 工单 -->
|
||||
<el-form-item label="规则明细" prop="ladderRule" v-if="isWorkOrder">
|
||||
<el-row type="flex" justify="space-between">
|
||||
<div>从小到大顺序配置</div>
|
||||
<el-button type="text" icon="iconfont iconAdd" @click="addIntegral">添加</el-button>
|
||||
</el-row>
|
||||
<el-table :data="form.ladderRuleList" size="mini" border stripe>
|
||||
<el-table-column label="工作日区间" align="center" width="400">
|
||||
<template slot-scope="{row}">
|
||||
大于等于
|
||||
<!-- <el-input-number class="tableInput" v-model="row.ge" :min="0" label="请输入" style="margin-right:8px;"></el-input-number> -->
|
||||
<el-input placeholder="请输入" v-model="row.ge" clearable style="margin-right:8px;width:100px;" @keyup.native="row.ge=checkGe(row.ge)"/>
|
||||
小于
|
||||
<el-input placeholder="请输入" v-model="row.lt" clearable @keyup.native="row.lt=checkGe(row.lt)" style="width:100px;"/>
|
||||
<!-- <el-input-number class="tableInput" v-model="row.lt" :min="0" label="请输入"></el-input-number> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="积分" align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-input class="tableInput" v-model="row.integral" clearable placeholder="请输入" type="number"
|
||||
@blur="row.integral=tableIntegral(row.integral)"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="" align="center">
|
||||
<template slot-scope="{ $index }">
|
||||
<el-button type="text" @click="handleDeleteRule($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="有效范围" prop="validRangeType" required>
|
||||
<el-radio-group v-model="form.validRangeType">
|
||||
<el-radio label="0">全局</el-radio>
|
||||
@@ -145,7 +185,7 @@ export default {
|
||||
} else {
|
||||
callback(new Error('请输入积分分值'));
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
search: {
|
||||
status: "",
|
||||
@@ -163,20 +203,8 @@ export default {
|
||||
{prop: "ruleName", label: "事件", dict: "integralRuleEvent"},
|
||||
{prop: "ruleType", label: "规则", dict: "integralRuleRuleType"},
|
||||
{
|
||||
prop: "scoringCycle",
|
||||
slot: "scoringCycle",
|
||||
label: "周期范围",
|
||||
dict: "integralRuleScoringCycle",
|
||||
render: (h, {row}) => {
|
||||
return h(
|
||||
"span",
|
||||
{},
|
||||
row.numberLimit.length
|
||||
? $dict.getLabel("integralRuleScoringCycle", row.scoringCycle)
|
||||
: $dict.getLabel("integralRuleScoringCycle", row.scoringCycle) +
|
||||
row.numberLimit +
|
||||
"次"
|
||||
);
|
||||
},
|
||||
},
|
||||
{slot: "integral", label: "积分分值", align: "center"},
|
||||
{
|
||||
@@ -204,6 +232,7 @@ export default {
|
||||
integral: "",
|
||||
validRangeType: "0",
|
||||
validRangeData: "",
|
||||
ladderRuleList: []
|
||||
},
|
||||
formRules: {
|
||||
systemRuleId: [
|
||||
@@ -215,10 +244,13 @@ export default {
|
||||
scoringCycle: [
|
||||
{required: true, message: "请选择周期范围", trigger: "change"},
|
||||
],
|
||||
integral: [{required: true, validator: validcode, trigger: "blur"},],
|
||||
integral: [{required: true, validator: validcode, trigger: "blur"}],
|
||||
validRangeType: [
|
||||
{required: true, message: "请选择有效范围", trigger: "change"},
|
||||
],
|
||||
ladderRuleList: [
|
||||
{required: true, message: "请输入积分值", trigger: "change"},
|
||||
],
|
||||
},
|
||||
rulesOps: [],
|
||||
rulesProps: {
|
||||
@@ -242,11 +274,13 @@ export default {
|
||||
girdNameList: [],
|
||||
list: [],
|
||||
isOneAndTen: false,
|
||||
isWorkOrder: false,
|
||||
typeList: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$dict.load("integralRuleStatus", "integralRuleRuleType", "integralRuleScoringCycle",
|
||||
"integralRuleEvent", "integralRuleEventType").then(() => {
|
||||
"integralRuleEvent", "integralRuleEventType", "qjEACondition").then(() => {
|
||||
this.getList();
|
||||
this.getRulesList();
|
||||
});
|
||||
@@ -290,6 +324,11 @@ export default {
|
||||
this.$nextTick(() => {
|
||||
this.dialog = true;
|
||||
});
|
||||
this.isWorkOrder = false
|
||||
if(row.parentRuleName == '工单处理') {
|
||||
this.isWorkOrder = true
|
||||
this.form.ladderRuleList = JSON.parse(this.form.ladderRule)
|
||||
}
|
||||
},
|
||||
remove(id) {
|
||||
this.$confirm("删除后不可恢复,是否要删除该规则?", {
|
||||
@@ -319,10 +358,20 @@ export default {
|
||||
});
|
||||
},
|
||||
onConfirm() {
|
||||
var isWork = true
|
||||
this.form.ladderRuleList.map((item) => {
|
||||
if(!item.integral) {
|
||||
isWork = false
|
||||
}
|
||||
})
|
||||
if(!isWork) {
|
||||
return this.$message.error('请输入分值')
|
||||
}
|
||||
this.$refs.DialogForm.validate((valid) => {
|
||||
if (valid) {
|
||||
let formData = this.$copy(this.form);
|
||||
// formData.ladderRule = JSON.stringify(formData.ladderRule)
|
||||
formData.ladderRule = JSON.stringify(formData.ladderRuleList)
|
||||
console.log(formData.ladderRule)
|
||||
formData.integral = formData.integral || 0;
|
||||
this.instance
|
||||
.post(`/app/appintegralrule/addOrUpdate`, formData)
|
||||
@@ -332,10 +381,12 @@ export default {
|
||||
`${this.isEdit ? "编辑成功" : "添加成功"}`
|
||||
);
|
||||
this.dialog = false;
|
||||
this.isWorkOrder = false
|
||||
this.getList();
|
||||
this.closed();
|
||||
this.girdInfoList = []
|
||||
this.girdNameList = []
|
||||
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -343,6 +394,21 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
add() {
|
||||
this.form = {
|
||||
ruleType: "0",
|
||||
systemRuleId: "",
|
||||
ruleName: "",
|
||||
scoringCycle: "",
|
||||
numberLimit: "",
|
||||
integral: "",
|
||||
validRangeType: "0",
|
||||
validRangeData: "",
|
||||
ladderRuleList: []
|
||||
}
|
||||
this.isWorkOrder = false
|
||||
this.dialog = true
|
||||
},
|
||||
handleTypeSearch(v) {
|
||||
this.systemRuleIdList = v
|
||||
this.search.systemRuleId = v?.[v.length - 1];
|
||||
@@ -352,34 +418,62 @@ export default {
|
||||
this.getList();
|
||||
},
|
||||
handleTypeForm(v) {
|
||||
console.log(v)
|
||||
if (this.dialog) {
|
||||
if(v[0] == '1' || v[0]== '10') {
|
||||
this.form.scoringCycle = '0'
|
||||
this.form.numberLimit = '1'
|
||||
this.isOneAndTen = true
|
||||
this.isWorkOrder = false
|
||||
this.form.ladderRuleList = []
|
||||
} else if(v[0] == '20') { //工单处理
|
||||
this.isWorkOrder = true
|
||||
this.form.ladderRuleList = [{ ge: null, lt: null, integral: null }]
|
||||
} else {
|
||||
this.form.scoringCycle = ''
|
||||
this.form.numberLimit = ''
|
||||
this.isOneAndTen = false
|
||||
this.isWorkOrder = false
|
||||
this.form.ladderRuleList = []
|
||||
}
|
||||
this.form.systemRuleId = v?.[v.length - 1];
|
||||
if(v != '自定义') {
|
||||
this.typeList.map((item) => {
|
||||
if(item.id == v[1]) {
|
||||
this.form.ruleType = item.ruleType
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
handleDelete(i) {
|
||||
this.$confirm("是否要删除该规则?")
|
||||
.then(() => {
|
||||
this.form.ladderRule.splice(i, 1);
|
||||
this.form.ladderRuleList.splice(i, 1);
|
||||
})
|
||||
.catch(() => 0);
|
||||
},
|
||||
checkIntegral(v) {
|
||||
return /\.\d{2,}$/.test(v) ? Math.abs(v).toFixed(1) : Math.abs(v);
|
||||
return /^\d+$/.test(v) ? Math.abs(v).toFixed(1) : Math.abs(v);
|
||||
},
|
||||
tableIntegral(v) {
|
||||
if (!/^([+-]?([1-9]{1}\d*)|(0{1}))(\.\d{1,2})?$/.test(v)) {
|
||||
this.$message.error("积分分值可输入正数、负数、最多保留两位小数");
|
||||
var num = Number(v).toFixed(2)
|
||||
return num
|
||||
}else {
|
||||
return v
|
||||
}
|
||||
},
|
||||
checkGe(v) {
|
||||
return /\.\d{2,}$/.test(v) ? v : Math.floor(v);
|
||||
},
|
||||
getRulesList() {
|
||||
this.instance
|
||||
.post(`/app/appintegralsystemrule/list?current=1&size=3000`)
|
||||
.then((res) => {
|
||||
if (res?.data) {
|
||||
this.typeList = res.data.records
|
||||
this.rulesOps = this.toTree(res.data.records);
|
||||
this.rulesOps.push({
|
||||
ruleName: "自定义",
|
||||
@@ -417,7 +511,7 @@ export default {
|
||||
this.girdInfoList = selected.map((item) => {
|
||||
return {...item, checkType: true};
|
||||
});
|
||||
let validRangeData = selected.map((e) => ({id: e.id, girdName: e.girdName}))
|
||||
let validRangeData = selected.map((e) => ({id: e.id, girdName: e.girdName, girdCode: e.girdCode}))
|
||||
this.girdNameList = validRangeData.map(e => e.girdName)
|
||||
this.form.validRangeData = JSON.stringify(validRangeData)
|
||||
},
|
||||
@@ -443,7 +537,15 @@ export default {
|
||||
} else {
|
||||
delete this.treeSelected[data.id]
|
||||
}
|
||||
}
|
||||
},
|
||||
addIntegral() {
|
||||
this.form.ladderRuleList?.push({ ge: null, lt: null, integral: null })
|
||||
},
|
||||
handleDeleteRule(i) {
|
||||
this.$confirm("是否要删除该规则?").then(() => {
|
||||
this.form.ladderRuleList.splice(i, 1)
|
||||
}).catch(() => 0)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isEdit() {
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<ai-list v-if="!isShowDetail">
|
||||
<template slot="title">
|
||||
<ai-title title="居民群管理" :instance="instance" :disabledLevel="disabledLevel" isShowArea v-model="areaId" @change="changeArea"></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 :ref="tab.name" v-if="currIndex == i" :is="tab.comp" @change="onChange" lazy :instance="instance" :dict="dict" :permissions="permissions" :areaId="areaId"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</ai-list>
|
||||
<Detail v-else-if="componentName === 'Detail'" :params="params" :instance="instance" :dict="dict" :permissions="permissions" @change="onChange"></Detail>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import List from './components/List.vue'
|
||||
import Statistics from './components/Statistics'
|
||||
import Tags from './components/Tags'
|
||||
import Detail from './components/Detail'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'AppResidentGroupManage',
|
||||
label: '居民群管理',
|
||||
|
||||
components: {
|
||||
List,
|
||||
Tags,
|
||||
Detail,
|
||||
Statistics
|
||||
},
|
||||
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object,
|
||||
permissions: Function
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
|
||||
tabs () {
|
||||
const tabList = [
|
||||
{label: '居民群列表', name: 'List', comp: List, permission: ''},
|
||||
{label: '居民群统计', name: 'Statistics', comp: Statistics, permission: ''},
|
||||
{label: '居民群标签', name: 'Tags', comp: Tags, permission: ''}
|
||||
].filter(item => {
|
||||
return true
|
||||
})
|
||||
|
||||
return tabList
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
activeName: 'JoinEvent',
|
||||
currIndex: '0',
|
||||
componentName: '',
|
||||
params: {},
|
||||
isShowDetail: false,
|
||||
disabledLevel: 0,
|
||||
areaId: ''
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.areaId = this.user.info.areaId
|
||||
this.disabledLevel = this.user.info.areaList.length - 1
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange (data) {
|
||||
if (data.type === 'list') {
|
||||
this.componentName = 'List'
|
||||
this.isShowDetail = false
|
||||
this.params = data.params
|
||||
}
|
||||
|
||||
if (data.type === 'detail') {
|
||||
this.componentName = 'Detail'
|
||||
this.isShowDetail = true
|
||||
this.params = data.params
|
||||
}
|
||||
},
|
||||
changeArea() {
|
||||
if(this.componentName != 'Detail') {
|
||||
var component = this.tabs[this.currIndex].name
|
||||
this.$nextTick(() => {
|
||||
this.$refs[component][0].getListInit()
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
377
project/biaopin/AppResidentGroupManage/components/Detail.vue
Normal file
377
project/biaopin/AppResidentGroupManage/components/Detail.vue
Normal file
@@ -0,0 +1,377 @@
|
||||
<template>
|
||||
<ai-detail class="AppResidentManage">
|
||||
<template slot="title">
|
||||
<ai-title title="居民群详情" isShowBack isShowBottomBorder @onBackClick="cancel(false)"></ai-title>
|
||||
</template>
|
||||
<template slot="content">
|
||||
<div class="detail-top">
|
||||
<div class="detail-top__header">
|
||||
<div class="header-left">
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/h5/groupAvatar.png">
|
||||
<div class="header-left__right">
|
||||
<h2>{{ info.name }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<!-- <div class="header-right__item">-->
|
||||
<!-- <span>成员总数</span>-->
|
||||
<!-- <h3>{{ chartData.groupSum}}</h3>-->
|
||||
<!-- </div>-->
|
||||
<div class="header-right__item">
|
||||
<span>成员总数</span>
|
||||
<h3>{{chartData.today && chartData.today.total }}</h3>
|
||||
</div>
|
||||
<div class="header-right__item">
|
||||
<span>今日新增</span>
|
||||
<h3>{{ chartData.today && chartData.today.increase }}</h3>
|
||||
</div>
|
||||
<div class="header-right__item">
|
||||
<span>今日流失</span>
|
||||
<h3>{{chartData.today && chartData.today.decrease}}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-top__content">
|
||||
<ai-wrapper
|
||||
label-width="80px">
|
||||
<ai-info-item label="群主" :value="info.ownerName"></ai-info-item>
|
||||
<ai-info-item label="群公告" :value="info.notice" isLine></ai-info-item>
|
||||
<ai-info-item label="群聊标签" isLine>
|
||||
<div class="table-tags">
|
||||
<el-tag type="info" v-for="(item, index) in info.tagList" size="small" :key="index">{{ item.tagName }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</ai-info-item>
|
||||
</ai-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
<ai-card title="图表数据">
|
||||
<template slot="content">
|
||||
<div id="lineChart"></div>
|
||||
</template>
|
||||
</ai-card>
|
||||
<ai-card title="成员列表">
|
||||
<template slot="content">
|
||||
<ai-table
|
||||
:tableData="tableData"
|
||||
:col-configs="colConfigs"
|
||||
:total="total"
|
||||
border
|
||||
ref="aitableex"
|
||||
@getList="getDynamicInfo"
|
||||
:current.sync="search.current"
|
||||
:size.sync="search.size">
|
||||
<el-table-column slot="options" label="操作" width="100" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" v-if="row.type==2 && row.avatar" @click="toDetail(row)">居民详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ai-table>
|
||||
</template>
|
||||
</ai-card>
|
||||
</template>
|
||||
</ai-detail>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
export default {
|
||||
name: 'Detail',
|
||||
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object,
|
||||
params: Object
|
||||
},
|
||||
|
||||
computed: {
|
||||
colConfigs() {
|
||||
return [
|
||||
{
|
||||
prop: 'memberName', label: '成员',render:(h,{row})=>[<img class="avatar" src={row.avatar || "https://cdn.cunwuyun.cn/dvcp/h5/defaultAvatar.png"} />,
|
||||
<span>{row.memberName}</span>,
|
||||
<span style={{color:row.customerType==1 ? '#2EA222' : '#3C7FC8',marginLeft:'8px'}}>{ row.customerType?(row.customerType==1 ? '@微信' : '@' + row.corpName):'' }</span>],
|
||||
},
|
||||
{prop: 'type', label: '类型',render:(h,{row})=>[<span>{this.dict.getLabel("wxGroupMemberType",row.type)}</span>]},
|
||||
{prop: 'joinTime', label: '入群时间'},
|
||||
{prop: 'joinScene', label: '入群方式',render:(h,{row})=>[<span>{this.dict.getLabel("wxGroupMemberJoinScene",row.joinScene)}</span>]},
|
||||
{slot: "options"},
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
info: {},
|
||||
search: {
|
||||
current: 1,
|
||||
size: 10
|
||||
},
|
||||
total: 0,
|
||||
tableData: [],
|
||||
chart: null,
|
||||
chartData: {},
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.dict.load("wxGroupMemberJoinScene", "wxGroupMemberType")
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.params && this.params.id) {
|
||||
this.getInfo()
|
||||
this.getDynamicInfo()
|
||||
this.getChart()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getChart() {
|
||||
this.instance.post(`/app/wxcp/wxgroup/groupStatistic`, null, {
|
||||
params: {
|
||||
id: this.params.id
|
||||
}
|
||||
}).then(res => {
|
||||
if (res && res.data) {
|
||||
this.chartData = res.data
|
||||
this.initChart()
|
||||
}
|
||||
})
|
||||
},
|
||||
initChart() {
|
||||
this.chart = echarts.init(document.getElementById('lineChart'))
|
||||
this.setOptions()
|
||||
},
|
||||
setOptions() {
|
||||
const x = Object.keys(this.chartData.list)
|
||||
const y = Object.values(this.chartData.list)
|
||||
this.chart.setOption({
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
type: "plain"
|
||||
},
|
||||
grid: {
|
||||
left: '20px',
|
||||
right: '38px',
|
||||
bottom: '14px',
|
||||
top: '30px',
|
||||
containLabel: true
|
||||
},
|
||||
color: ['#2266FF', '#22AA99', '#F8B425'],
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
axisLabel: {
|
||||
align: 'center',
|
||||
padding: [2, 0, 0, 0],
|
||||
interval: 0,
|
||||
fontSize: 14,
|
||||
color: '#666666'
|
||||
},
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#E1E5EF'
|
||||
}
|
||||
},
|
||||
data: x
|
||||
},
|
||||
yAxis: {
|
||||
axisTick: {
|
||||
length: 0,
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: ['#E1E5EF'],
|
||||
width: 1,
|
||||
type: 'solid'
|
||||
}
|
||||
},
|
||||
nameTextStyle: {
|
||||
color: '#666666',
|
||||
align: 'left'
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666666'
|
||||
},
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '成员总数',
|
||||
type: 'line',
|
||||
data: y.map(v => v.total)
|
||||
},
|
||||
{
|
||||
name: '新增成员数',
|
||||
type: 'line',
|
||||
data: y.map(v => v.increase)
|
||||
},
|
||||
{
|
||||
name: '流失成员数',
|
||||
type: 'line',
|
||||
data: y.map(v => v.decrease)
|
||||
}
|
||||
]
|
||||
})
|
||||
},
|
||||
|
||||
getInfo() {
|
||||
this.instance.post(`/app/wxcp/wxgroup/getDetail?id=${this.params.id}`).then(res => {
|
||||
if (res && res.data) {
|
||||
this.info = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getDynamicInfo() {
|
||||
this.instance.post(`/app/wxcp/wxgroup/listMember?groupId=${this.params.id}`, null, {
|
||||
params: {
|
||||
...this.search
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data.records
|
||||
this.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
toDetail(row) {
|
||||
this.$router.push({
|
||||
name: '68',
|
||||
query: {
|
||||
id: row.userId,
|
||||
type: 'Detail'
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
cancel(isRefresh) {
|
||||
this.$emit('change', {
|
||||
type: 'list',
|
||||
isRefresh: isRefresh ? true : false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.AppResidentManage {
|
||||
:deep( .ai-detail__content--wrapper ){
|
||||
max-width: 100% !important;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h2, h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.detail-top {
|
||||
padding: 30px 40px;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
|
||||
border-radius: 2px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.detail-top__content {
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.detail-top__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 32px;
|
||||
border-bottom: 1px solid #EEEEEE;
|
||||
|
||||
.header-right {
|
||||
.header-right__item {
|
||||
width: 120px;
|
||||
margin-right: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div {
|
||||
text-align: center;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
color: #888888;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
height: 28px;
|
||||
margin-left: 8px;
|
||||
border-radius: 14px;
|
||||
font-size: 12px;
|
||||
padding: 7px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-left, .header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
img {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 6px;
|
||||
color: #222222;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #2EA222;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#lineChart {
|
||||
width: 100%;
|
||||
height: 336px;
|
||||
}
|
||||
|
||||
.table-tags {
|
||||
.el-tag {
|
||||
margin-right: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep( .avatar ){
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
vertical-align: middle;
|
||||
margin-right: 8px;
|
||||
object-fit: fill;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
404
project/biaopin/AppResidentGroupManage/components/List.vue
Normal file
404
project/biaopin/AppResidentGroupManage/components/List.vue
Normal file
@@ -0,0 +1,404 @@
|
||||
<template>
|
||||
<ai-list class="AppPetitionManage" isTabs>
|
||||
<template slot="content">
|
||||
<ai-search-bar class="search-bar">
|
||||
<template slot="left">
|
||||
<!-- <ai-select-->
|
||||
<!-- v-model="search.owner"-->
|
||||
<!-- filterable-->
|
||||
<!-- @change="search.current = 1, getList()"-->
|
||||
<!-- placeholder="群主"-->
|
||||
<!-- :selectList="userList">-->
|
||||
<!-- </ai-select>-->
|
||||
<ai-select
|
||||
v-model="search.tagId"
|
||||
@change="search.current = 1, getList()"
|
||||
placeholder="选择标签"
|
||||
:selectList="subTags">
|
||||
</ai-select>
|
||||
</template>
|
||||
<template slot="right">
|
||||
<el-input
|
||||
v-model="search.name"
|
||||
class="search-input"
|
||||
size="small"
|
||||
v-throttle="() => {search.current = 1, getList()}"
|
||||
placeholder="请输入群主/群名称"
|
||||
clearable
|
||||
@change="getList"
|
||||
@clear="search.current = 1, search.name = '', getList()"
|
||||
suffix-icon="iconfont iconSearch">
|
||||
</el-input>
|
||||
</template>
|
||||
</ai-search-bar>
|
||||
<ai-search-bar>
|
||||
<template slot="left">
|
||||
<el-button type="primary" icon="iconfont iconAdd" @click="isShow = true" :disabled="!ids.length">批量打群标签
|
||||
</el-button>
|
||||
</template>
|
||||
<template slot="left">
|
||||
<el-button type="primary" icon="iconfont iconDelete" @click="onConfirm(1)" :disabled="!ids.length">批量移除群标签
|
||||
</el-button>
|
||||
</template>
|
||||
<template slot="right">
|
||||
<el-button type="primary" icon="iconfont iconResetting" @click="update">更新数据</el-button>
|
||||
</template>
|
||||
</ai-search-bar>
|
||||
<ai-table
|
||||
:tableData="tableData"
|
||||
:col-configs="colConfigs"
|
||||
:total="total"
|
||||
ref="aitableex"
|
||||
:current.sync="search.current"
|
||||
@selection-change="handleSelectionChange"
|
||||
:size.sync="search.size"
|
||||
v-loading="isLoading"
|
||||
@getList="getList">
|
||||
<el-table-column slot="avatar" label="" width="80" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="avatar" style="text-align: right;justify-content: end;">
|
||||
<img :src="row.avatar">
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="userinfo" label="群名称" show-overflow-tooltip width="300px" align="left">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="userinfo">
|
||||
<div class="userinfo-right ellipsis">
|
||||
<div class="userinfo-right__top">
|
||||
<h3>{{ row.name || "群聊" }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="tags" label="群标签" align="left">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="table-tags">
|
||||
<el-tag type="info" v-for="(item, index) in row.tagList" size="medium" :key="index">{{ item.tagName }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="options" label="操作" width="100" fixed="right" 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="800px"
|
||||
title="批量打标签"
|
||||
@close="chooseTags = []"
|
||||
@onConfirm="onConfirm">
|
||||
<div class="tags">
|
||||
<div class="tag-item" v-for="(item, index) in tags" :key="index">
|
||||
<h2>{{ item.name }}</h2>
|
||||
<div class="tag-item__right">
|
||||
<el-button
|
||||
:type="chooseTags.indexOf(item.id) === -1 ? '' : 'primary'"
|
||||
v-for="(item, index) in item.tagList"
|
||||
@click="choose(item.id)"
|
||||
:key="index">
|
||||
{{ item.name }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ai-dialog>
|
||||
</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: '',
|
||||
// tagId: '',
|
||||
owner: ''
|
||||
},
|
||||
isLoading: false,
|
||||
isShow: false,
|
||||
ids: [],
|
||||
total: 10,
|
||||
chooseTags: [],
|
||||
tags: [],
|
||||
colConfigs: [
|
||||
{type: 'selection'},
|
||||
// {slot: 'avatar'},
|
||||
{slot: 'userinfo'},
|
||||
{prop: 'ownerName', label: '群主', align: 'center'},
|
||||
{prop: 'departmentName', label: '部门', align: 'center'},
|
||||
{prop: 'areaName', label: '地区', align: 'center'},
|
||||
{slot: 'tags'},
|
||||
{prop: 'personCount', label: '群人数', align: 'center'},
|
||||
{prop: 'increase', label: '当日入群人数', align: 'center'},
|
||||
{prop: 'decrease', label: '当日退群人数', align: 'center'},
|
||||
{prop: 'createTime', label: '创建时间', align: 'left'},
|
||||
{slot: 'options', label: '操作', align: 'center'}],
|
||||
tableData: [],
|
||||
subTags: [],
|
||||
userList: []
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['user'])
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getTags()
|
||||
this.getSubTags()
|
||||
// this.getWxUserList()
|
||||
this.getList()
|
||||
this.isLoading = true
|
||||
},
|
||||
|
||||
methods: {
|
||||
getWxUserList() {
|
||||
this.instance.post(`/app/wxcp/wxuser/listByDepartId`, {}).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.userList = res.data.map(item => {
|
||||
item.dictName = item.name
|
||||
item.dictValue = item.id
|
||||
|
||||
return item
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
update() {
|
||||
this.instance.post(`/app/wxcp/wxgroup/sync`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success('更新成功')
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getSubTags() {
|
||||
this.instance.post(`/app/wxcp/wxgroupchattag/listAllTag`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.subTags = res.data?.map(item => {
|
||||
return {
|
||||
dictName: item.name,
|
||||
dictValue: item.id
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getList() {
|
||||
this.instance.post(`/app/wxcp/wxgroup/list`, null, {
|
||||
params: {
|
||||
...this.search,
|
||||
areaId: this.areaId
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.tableData = res.data.records
|
||||
this.total = res.data.total
|
||||
}
|
||||
this.isLoading = false
|
||||
}).catch(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
choose(id) {
|
||||
const index = this.chooseTags.indexOf(id)
|
||||
if (index === -1) {
|
||||
this.chooseTags.push(id)
|
||||
} else {
|
||||
this.chooseTags.splice(index, 1)
|
||||
}
|
||||
},
|
||||
|
||||
onConfirm(type = 0) {
|
||||
if (type == 0 && !this.chooseTags.length) {
|
||||
return this.$message.error('请选择标签')
|
||||
}
|
||||
this.instance.post(`/app/wxcp/wxgroupchattag/markTagForWeb`, {
|
||||
tagIds: this.chooseTags, groupIds: this.ids.map(v => v.id), type,
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
type == 0 ? (this.isShow = false) : false
|
||||
this.$message.success(type == 0 ? "添加成功" : "删除成功")
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getTags() {
|
||||
this.instance.post(`/app/wxcp/wxgroupchattag/listAll?size=999`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.tags = res.data.records
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onAdd() {
|
||||
this.$emit('change', {
|
||||
type: 'add'
|
||||
})
|
||||
},
|
||||
|
||||
handleSelectionChange(e) {
|
||||
this.ids = e
|
||||
},
|
||||
|
||||
removeAll() {
|
||||
this.remove(this.ids.map(v => v.id).join(','))
|
||||
},
|
||||
|
||||
remove(id) {
|
||||
this.$confirm('确定删除该数据?').then(() => {
|
||||
this.instance.post(`/app/apppetition/delete?ids=${id}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success('删除成功!')
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
toDetail(id) {
|
||||
this.$emit('change', {
|
||||
type: 'detail', params: {
|
||||
id: id
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onAdd() {
|
||||
this.$emit('change', {
|
||||
type: 'add'
|
||||
})
|
||||
},
|
||||
|
||||
getListInit() {
|
||||
this.search.current = 1
|
||||
this.getList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-tags {
|
||||
.el-tag {
|
||||
margin-right: 8px;
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid #D0D4DC;
|
||||
background: #F3F4F7;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
color: #222222;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tags {
|
||||
.tag-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 30px;
|
||||
padding-top: 30px;
|
||||
border-bottom: 1px solid #EEEEEE;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
margin-right: 8px;
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
h2 {
|
||||
width: 88px;
|
||||
margin-right: 40px;
|
||||
text-align: right;
|
||||
color: #888888;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
text-align: right;
|
||||
|
||||
img {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #CCCCCC;
|
||||
}
|
||||
}
|
||||
|
||||
.userinfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
|
||||
.userinfo-right__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.userinfo-right__bottom {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
font-style: normal;
|
||||
color: #888888;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-right: 8px;
|
||||
color: #222222;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #2EA222;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
212
project/biaopin/AppResidentGroupManage/components/Statistics.vue
Normal file
212
project/biaopin/AppResidentGroupManage/components/Statistics.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<ai-list class="statistics" isTabs style="width: 100%">
|
||||
<template slot="content" v-loading="loading">
|
||||
<div class="statistics-top">
|
||||
<div class="statistics-top__item">
|
||||
<span>群聊总数</span>
|
||||
<h2 style="color: #2266FF;">{{ groupSum }}</h2>
|
||||
</div>
|
||||
<div class="statistics-top__item">
|
||||
<span>群成员总人数</span>
|
||||
<h2>{{ info.total }}</h2>
|
||||
</div>
|
||||
<div class="statistics-top__item">
|
||||
<span>今日入群人数</span>
|
||||
<h2 style="color: #22AA99;">{{ info.increase }}</h2>
|
||||
</div>
|
||||
<div class="statistics-top__item">
|
||||
<span>今日退群人数</span>
|
||||
<h2 style="color: #F8B425">{{ info.decrease }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<ai-card title="趋势图">
|
||||
<template #content>
|
||||
<div class="chart" style="height: 340px; width: 100%;"></div>
|
||||
</template>
|
||||
</ai-card>
|
||||
</template>
|
||||
</ai-list>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
export default {
|
||||
name: 'Statistics',
|
||||
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object,
|
||||
areaId: String
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
info: {},
|
||||
chartWidth: '',
|
||||
groupSum: "",
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loading = true
|
||||
this.$nextTick(() => {
|
||||
this.chart = echarts.init(document.querySelector('.chart'))
|
||||
window.addEventListener('resize', this.onResize)
|
||||
this.getInfo()
|
||||
})
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
window.removeEventListener('resize', this.onResize)
|
||||
},
|
||||
|
||||
methods: {
|
||||
onResize() {
|
||||
this.chart.resize()
|
||||
},
|
||||
|
||||
getListInit() {
|
||||
this.getInfo()
|
||||
},
|
||||
|
||||
getInfo() {
|
||||
this.instance.post(`/app/wxcp/wxgroup/groupStatistic?areaId=${this.areaId}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.info = res.data.today
|
||||
this.groupSum = res.data.groupSum
|
||||
this.initChart(res.data.list)
|
||||
this.loading = false
|
||||
} else {
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
initChart(data) {
|
||||
const x = Object.keys(data)
|
||||
const y = Object.values(data)
|
||||
let option = {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
type: "plain"
|
||||
},
|
||||
grid: {
|
||||
left: '20px',
|
||||
right: '38px',
|
||||
bottom: '14px',
|
||||
top: '30px',
|
||||
containLabel: true
|
||||
},
|
||||
color: ['#2266FF', '#22AA99', '#F8B425'],
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
axisLabel: {
|
||||
align: 'center',
|
||||
padding: [2, 0, 0, 0],
|
||||
interval: 0,
|
||||
fontSize: 14,
|
||||
color: '#666666'
|
||||
},
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#E1E5EF'
|
||||
}
|
||||
},
|
||||
data: x
|
||||
},
|
||||
yAxis: {
|
||||
axisTick: {
|
||||
length: 0,
|
||||
show: false
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: ['#E1E5EF'],
|
||||
width: 1,
|
||||
type: 'solid'
|
||||
}
|
||||
},
|
||||
nameTextStyle: {
|
||||
color: '#666666',
|
||||
align: 'left'
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666666'
|
||||
},
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '群成员总数',
|
||||
type: 'line',
|
||||
data: y.map(v => v.total)
|
||||
},
|
||||
{
|
||||
name: '新增入群人数',
|
||||
type: 'line',
|
||||
data: y.map(v => v.increase)
|
||||
},
|
||||
{
|
||||
name: '退群人数',
|
||||
type: 'line',
|
||||
data: y.map(v => v.decrease)
|
||||
}
|
||||
]
|
||||
}
|
||||
this.chart.setOption(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.statistics {
|
||||
:deep( .ai-list__content--right-wrapper ){
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
padding: 12px 0 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
208
project/biaopin/AppResidentGroupManage/components/Tags.vue
Normal file
208
project/biaopin/AppResidentGroupManage/components/Tags.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<ai-list class="AppPetitionManage" isTabs>
|
||||
<template slot="content">
|
||||
<ai-search-bar>
|
||||
<template slot="left">
|
||||
<el-button type="primary" icon="iconfont iconAdd" @click="id = '', form.name = '', form.tagList = [], isShow = true">添加群标签组</el-button>
|
||||
</template>
|
||||
</ai-search-bar>
|
||||
<ai-table
|
||||
:tableData="tableData"
|
||||
:col-configs="colConfigs"
|
||||
:total="total"
|
||||
ref="aitableex"
|
||||
:current.sync="search.current"
|
||||
:size.sync="search.size"
|
||||
@getList="getList">
|
||||
<el-table-column slot="tags" label="群标签" align="left">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="table-tags">
|
||||
<el-tag type="info" v-for="(item, index) in row.tagList" size="small" :key="index">{{ item.name }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="options" label="操作" width="180" fixed="right" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="table-options">
|
||||
<!-- <el-button type="text" @click="edit(row)">添加标签</el-button> -->
|
||||
<el-button type="text" @click="edit(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="800px"
|
||||
title="添加群标签组"
|
||||
@onConfirm="onConfirm"
|
||||
@onCancel="onCancel">
|
||||
<el-form class="ai-form" ref="form" label-width="120px" :model="form">
|
||||
<el-form-item style="width: 100%" label="群标签组名称" prop="name" :rules="[{ required: true, message: '请输入群标签组名称', trigger: 'blur' }]">
|
||||
<el-input size="small" v-model.trim="form.name" :maxlength="15" show-word-limit placeholder="请输入群标签组名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 100%" label="群标签" prop="tagList" :rules="[{ required: true, message: '请输入群标签组名称', trigger: 'blur' }]">
|
||||
<div class="table-tags">
|
||||
<el-tag type="info" color="#fff" closable @close="onClose(index)" v-for="(item, index) in form.tagList" :key="index">{{ item.name }}</el-tag>
|
||||
<el-input
|
||||
v-if="inputVisible"
|
||||
v-model.trim="tagName"
|
||||
size="small"
|
||||
style="width: 100px;"
|
||||
maxlength="30"
|
||||
clearable
|
||||
@keyup.enter.native="handleInputConfirm"
|
||||
@blur="handleInputConfirm">
|
||||
</el-input>
|
||||
<el-button v-else size="small" icon="iconfont iconAdd" @click="inputVisible = true">添加</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ai-dialog>
|
||||
</template>
|
||||
</ai-list>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Tags',
|
||||
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object,
|
||||
areaId: String
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
search: {
|
||||
current: 1,
|
||||
size: 10
|
||||
},
|
||||
tagName: '',
|
||||
form: {
|
||||
name: '',
|
||||
tagList: []
|
||||
},
|
||||
inputVisible: false,
|
||||
isShow: false,
|
||||
ids: [],
|
||||
total: 10,
|
||||
colConfigs: [
|
||||
{ prop: 'name', label: '群标签组名称', align: 'left', width: 160 },
|
||||
{ slot: 'tags' },
|
||||
{ slot: 'options', label: '操作', align: 'center' }
|
||||
],
|
||||
id: '',
|
||||
tableData: [],
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.getList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
getListInit() {
|
||||
this.search.current = 1
|
||||
this.getList()
|
||||
},
|
||||
|
||||
getList() {
|
||||
this.instance.post(`/app/wxcp/wxgroupchattag/listAll`, null, {
|
||||
params: {
|
||||
...this.search,
|
||||
areaId: this.areaId
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.tableData = res.data.records
|
||||
this.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onCancel () {
|
||||
this.form.name = ''
|
||||
this.form.tagList = []
|
||||
this.id = ''
|
||||
this.inputVisible = ''
|
||||
this.isShow = false
|
||||
},
|
||||
|
||||
edit (e) {
|
||||
this.id = e.id
|
||||
this.form = JSON.parse(JSON.stringify(e))
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.isShow = true
|
||||
})
|
||||
},
|
||||
|
||||
handleInputConfirm () {
|
||||
if (!this.tagName) {
|
||||
return
|
||||
}
|
||||
|
||||
this.form.tagList.push({
|
||||
name: this.tagName
|
||||
})
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.tagName = ''
|
||||
this.inputVisible = false
|
||||
})
|
||||
},
|
||||
|
||||
onClose (index) {
|
||||
this.form.tagList.splice(index, 1)
|
||||
},
|
||||
|
||||
onConfirm() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
this.instance.post(this.id ? '/app/wxcp/wxgroupchattag/update' : `/app/wxcp/wxgroupchattag/add`, {
|
||||
name: this.form.name,
|
||||
id: this.id || null,
|
||||
tagList: this.form.tagList
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.getList()
|
||||
this.isShow = false
|
||||
this.$message.success(`${this.id}` ? '编辑成功' : '提交成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
remove (id) {
|
||||
this.$confirm('确定删除该数据?').then(() => {
|
||||
this.instance.post(`/app/wxcp/wxgroupchattag/delete?id=${id}&type=0`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success('删除成功!')
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-tags {
|
||||
.el-tag {
|
||||
margin-right: 8px;
|
||||
border: 1px solid #D0D4DC;
|
||||
background: #F3F4F7;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
color: #222222;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
100
project/biaopin/AppSeatManagementXbot/AppSeatManagementXbot.vue
Normal file
100
project/biaopin/AppSeatManagementXbot/AppSeatManagementXbot.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div class="AppSeatManagement">
|
||||
<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 GroupList from "./components/GroupList";
|
||||
import AnnounceList from './components/AnnounceList'
|
||||
import Add from './components/Add'
|
||||
import Detail from './components/Detail'
|
||||
|
||||
export default {
|
||||
name: "AppSeatManagement",
|
||||
label: "席位管理",
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
component: "List",
|
||||
params: {},
|
||||
include: [],
|
||||
|
||||
};
|
||||
},
|
||||
components: {
|
||||
GroupList,
|
||||
List,
|
||||
AnnounceList,
|
||||
Add,
|
||||
Detail
|
||||
},
|
||||
mounted() {
|
||||
if (this.$route.params.id) {
|
||||
this.component = 'Detail'
|
||||
this.params = {
|
||||
id: this.$route.params.id
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(data) {
|
||||
if (data.type === "GroupList") {
|
||||
this.component = "GroupList";
|
||||
this.params = data.params;
|
||||
}
|
||||
if (data.type === "List") {
|
||||
this.component = "List";
|
||||
this.params = data.params;
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (data.isRefresh) {
|
||||
this.$refs.component.getTableData();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (data.type === 'Add') {
|
||||
this.component = 'Add'
|
||||
this.params = data.params
|
||||
}
|
||||
|
||||
if (data.type === 'Detail') {
|
||||
this.component = 'Detail'
|
||||
this.params = data.params
|
||||
}
|
||||
|
||||
if (data.type === 'AnnounceList') {
|
||||
this.component = 'AnnounceList'
|
||||
this.params = data.params
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (data.isRefresh) {
|
||||
this.$refs.component.getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.AppSeatManagement {
|
||||
height: 100%;
|
||||
background: #f3f6f9;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
990
project/biaopin/AppSeatManagementXbot/components/Add.vue
Normal file
990
project/biaopin/AppSeatManagementXbot/components/Add.vue
Normal file
@@ -0,0 +1,990 @@
|
||||
<template>
|
||||
<ai-detail class="AppAnnounceAdd">
|
||||
<template slot="title">
|
||||
<ai-title :title="id ? '编辑任务' : '添加任务'" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
|
||||
</ai-title>
|
||||
</template>
|
||||
<template slot="content">
|
||||
<div class="AppAnnounceDetail-container">
|
||||
<el-form ref="form" class="left" :model="form" label-width="110px" label-position="right">
|
||||
<ai-card title="基本信息">
|
||||
<template #content>
|
||||
<div class="ai-form">
|
||||
<el-form-item label="任务名称" prop="taskTitle" style="width: 100%;" :rules="[{ required: true, message: '请输入任务名称', trigger: 'blur' }]">
|
||||
<el-input size="small" placeholder="请输入任务名称" v-model="form.taskTitle" :maxlength="15" show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="发送范围" style="width: 100%;" prop="sendScope" :rules="[{ required: true, message: '请选择发送范围', trigger: 'change' }]">
|
||||
<el-radio-group v-model="form.sendScope" @change="onScopeChange">
|
||||
<el-radio label="0">全部居民群</el-radio>
|
||||
<el-radio label="1">按部门选择</el-radio>
|
||||
<el-radio label="2">按网格选择</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择群主" v-if="form.sendScope !== '0'" prop="wxGroupsName" style="width: 100%;" :rules="[{ required: true, message: '请选择选择群主', trigger: 'change' }]" >
|
||||
<ai-picker
|
||||
:instance="instance"
|
||||
multiple
|
||||
:dialogTitle="form.sendScope === '2' ? '选择网格' : '选择部门'"
|
||||
:ops="{label: form.sendScope === '2' ? 'girdName' : 'name'}"
|
||||
:pageTitle="form.sendScope === '2' ? '网格' : '部门'"
|
||||
:action="form.sendScope === '1' ? `/app/wxcp/wxdepartment/departList` : '/app/appgirdinfo/girdList'"
|
||||
v-model="form.filterCriteria"
|
||||
@pick="onPick"
|
||||
@change="onSelcetChange">
|
||||
<div class="AppAnnounceDetail-select">
|
||||
<el-input size="small" class="AppAnnounceDetail-select__input" placeholder="请选择..." disabled v-model="form.wxGroupsName"></el-input>
|
||||
<div class="select-left" v-if="form.wxGroups && form.wxGroups.length">
|
||||
<span v-for="(item, index) in form.wxGroups" :key="index" v-if="index < 9">{{ item.groupOwnerName }}</span>
|
||||
<em v-if="form.wxGroups.length > 9">等{{ form.wxGroups.length }}个</em>
|
||||
</div>
|
||||
<i v-else>请选择</i>
|
||||
<div class="select-right">{{ form.filterCriteria.length ? '重新选择' : '选择' }}</div>
|
||||
</div>
|
||||
</ai-picker>
|
||||
<div class="tips">
|
||||
<p>消息预计送达居民群数:</p>
|
||||
<span>{{ groupLen }}</span>
|
||||
<!-- <el-tooltip
|
||||
placement="top"
|
||||
content="将由指定群主发送给TA作为群主的所有的群,由于企业微信限制,当超过1000个时将只发送到最近活跃的1000个群">
|
||||
<i class="iconfont iconModal_Warning"></i>
|
||||
</el-tooltip> -->
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="发送内容" prop="content" style="width: 100%;" :rules="[{ required: true, message: '请输入发送内容', trigger: 'blur' }]">
|
||||
<el-input size="small" type="textarea" :rows="6" maxlength="1300" show-word-limit placeholder="请输入文本内容..." v-model="form.content"></el-input>
|
||||
<div class="add">
|
||||
<div class="fileList" v-if="fileList && fileList.length">
|
||||
<div class="add-item" v-for="(item, index) in fileList" :key="index">
|
||||
<div class="left">
|
||||
<img :src="mapIcon(item.msgType)"/>
|
||||
<span>{{ item.mpTitle || item.name || item.linkTitle }}</span>
|
||||
</div>
|
||||
<i @click="removeFile(index)">删除</i>
|
||||
</div>
|
||||
</div>
|
||||
<el-popover
|
||||
placement="top"
|
||||
width="340"
|
||||
offset="0"
|
||||
trigger="hover">
|
||||
<div class="add-item" slot="reference" style="width: max-content;">
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/announce/add.png"/>
|
||||
<span style="color: #2266FF; font-size: 12px;">添加附件类型</span>
|
||||
</div>
|
||||
<div class="AppAnnounceDetail-content-wrapper">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
multiple
|
||||
:file-list="fileList"
|
||||
:show-file-list="false"
|
||||
:before-upload="v => handleChange(v, 10, '.jpg,.png,.jpeg')"
|
||||
:limit="9"
|
||||
action="/app/wxcp/upload/uploadFile"
|
||||
accept=".jpg,.png,.jpeg"
|
||||
:on-exceed="onExceed"
|
||||
:http-request="v => submitUpload(v, '1')">
|
||||
<div class="content-item" trigger>
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/announce/big-img.png"/>
|
||||
<p>图片</p>
|
||||
</div>
|
||||
</el-upload>
|
||||
<!-- <el-upload
|
||||
ref="upload"
|
||||
multiple
|
||||
:file-list="fileList"
|
||||
:show-file-list="false"
|
||||
:before-upload="v => handleChange(v, 10, '.mp4')"
|
||||
:limit="9"
|
||||
action="/app/wxcp/upload/uploadFile"
|
||||
accept=".mp4"
|
||||
:on-exceed="onExceed"
|
||||
:http-request="v => submitUpload(v, '2')">
|
||||
<div class="content-item" trigger>
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/announce/big-video.png"/>
|
||||
<p>视频</p>
|
||||
</div>
|
||||
</el-upload> -->
|
||||
<!-- <el-upload
|
||||
ref="upload"
|
||||
multiple
|
||||
:file-list="fileList"
|
||||
:show-file-list="false"
|
||||
:before-upload="v => handleChange(v, 20, '.zip,.rar,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt')"
|
||||
:limit="9"
|
||||
:on-exceed="onExceed"
|
||||
action="/app/wxcp/upload/uploadFile"
|
||||
accept=".zip,.rar,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt"
|
||||
:http-request="v => submitUpload(v, '3')">
|
||||
<div class="content-item" trigger>
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/announce/folder.png"/>
|
||||
<p>文件</p>
|
||||
</div>
|
||||
</el-upload> -->
|
||||
<div class="content-item" @click="isShowAddLink = true">
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/announce/site.png"/>
|
||||
<p>网页</p>
|
||||
</div>
|
||||
<!-- <div class="content-item" @click="isShowAddMiniapp = true">
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png"/>
|
||||
<p>小程序</p>
|
||||
</div> -->
|
||||
</div>
|
||||
</el-popover>
|
||||
<!-- <div class="add-material add-item" @click="$refs.ChooseMaterial.open()">
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/announce/add.png"/>
|
||||
<span style="color: #2266FF; font-size: 12px;">从素材库选择</span>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="tips">
|
||||
<em>从本地上传,图片最大支持10MB,支持JPG,PNG格式;视频最大支持10MB,支持MP4格式;文件最大支持20MB</em>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="标签" style="width: 100%;" prop="markTag">
|
||||
<el-checkbox-group v-model="form.markTag">
|
||||
<el-checkbox
|
||||
v-for="(item, index) in dict.getDict('mstTag')"
|
||||
:key="index"
|
||||
:label="item.dictValue">
|
||||
{{ item.dictName }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="结束时间" style="width: 100%;" prop="taskEndTime" :rules="[{ required: true, message: '请选择结束时间', trigger: 'change' }]">
|
||||
<el-date-picker
|
||||
style="width: 100%;"
|
||||
v-model="form.taskEndTime"
|
||||
type="datetime"
|
||||
size="small"
|
||||
:picker-options="pickerOptions"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择结束时间">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="宣发审批" prop="enableExamine" style="width: 100%;" :rules="[{ required: true, message: '请输入任务名称', trigger: 'blur' }]">
|
||||
<el-switch
|
||||
v-model="form.enableExamine"
|
||||
active-value="1"
|
||||
inactive-value="0"
|
||||
active-text="开启后,创建的群发任务需要审批人进行审批">
|
||||
</el-switch>
|
||||
</el-form-item> -->
|
||||
<el-form-item v-if="form.enableExamine === '1'" label="审批人员" prop="examines" style="width: 100%;" :rules="[{ required: true, message: '请选择审批人员', trigger: 'change' }]">
|
||||
<ai-user-selecter :instance="instance" v-model="form.examines" @change="onUserChange">
|
||||
<div class="AppAnnounceDetail-select">
|
||||
<el-input class="AppAnnounceDetail-select__input" size="small" placeholder="请选择..." v-model="form.examinesName"></el-input>
|
||||
<div class="select-left" v-if="form.examines && form.examines.length">
|
||||
<span v-for="(item, index) in form.examines" :key="index">{{ item.name }}</span>
|
||||
</div>
|
||||
<i v-if="!form.examines.length">请选择</i>
|
||||
<div class="select-right">{{ form.examines.length ? '重新选择' : '选择' }}</div>
|
||||
</div>
|
||||
</ai-user-selecter>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
</ai-card>
|
||||
</el-form>
|
||||
<div class="right">
|
||||
<Phone :avatar="user.info.avatar" @close="isShowPhone = false" :isShowClose="false" :content="form.content" :fileList="fileList"></Phone>
|
||||
</div>
|
||||
<ai-dialog
|
||||
:visible.sync="isShowAddLink"
|
||||
width="920px"
|
||||
title="链接消息"
|
||||
@close="onClose"
|
||||
@onConfirm="onLinkConfirm">
|
||||
<el-form ref="linkForm" :model="linkForm" label-width="110px" label-position="right">
|
||||
<div class="ai-form">
|
||||
<el-form-item label="标题" style="width: 100%;" prop="linkTitle" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
|
||||
<el-input
|
||||
size="small"
|
||||
placeholder="请输入标题"
|
||||
maxlength="42"
|
||||
show-word-limit
|
||||
v-model="linkForm.linkTitle">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="链接" style="width: 100%;" prop="linkUrl" :rules="[{ required: true, message: '请输入链接', trigger: 'blur' }]">
|
||||
<el-input
|
||||
size="small"
|
||||
placeholder="请输入链接"
|
||||
maxlength="682"
|
||||
show-word-limit
|
||||
v-model="linkForm.linkUrl">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" style="width: 100%;" prop="linkDesc">
|
||||
<el-input
|
||||
size="small"
|
||||
placeholder="请输入描述"
|
||||
maxlength="170"
|
||||
show-word-limit
|
||||
v-model="linkForm.linkDesc">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="封面图" prop="linkPicUrl" style="width: 100%;">
|
||||
<ai-uploader :instance="instance" v-model="linkForm.linkPicUrl" :limit="1"></ai-uploader>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</ai-dialog>
|
||||
<ai-dialog
|
||||
:visible.sync="isShowAddMiniapp"
|
||||
width="920px"
|
||||
title="小程序消息"
|
||||
@close="onClose"
|
||||
@onConfirm="onMiniAppForm">
|
||||
<el-form ref="miniAppForm" :model="miniAppForm" label-width="130px" label-position="right">
|
||||
<div class="ai-form">
|
||||
<el-form-item label="小程序appid" style="width: 100%;" prop="mpAppid" :rules="[{ required: true, message: '小程序appid', trigger: 'blur' }]">
|
||||
<el-input
|
||||
size="small"
|
||||
placeholder="小程序appid"
|
||||
v-model="miniAppForm.mpAppid">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="小程序page路径" style="width: 100%;" prop="mpPage" :rules="[{ required: true, message: '请输入小程序page路径', trigger: 'blur' }]">
|
||||
<el-input
|
||||
size="small"
|
||||
placeholder="请输入小程序page路径"
|
||||
v-model="miniAppForm.mpPage">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题" style="width: 100%;" prop="mpTitle" :rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]">
|
||||
<el-input
|
||||
size="small"
|
||||
placeholder="请输入标题"
|
||||
maxlength="20"
|
||||
show-word-limit
|
||||
v-model="miniAppForm.mpTitle">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="封面图" prop="media" style="width: 100%;" :rules="[{ required: true, message: '请上传封面图', trigger: 'change' }]">
|
||||
<ai-uploader url="/app/wxcp/upload/uploadFile?type=image" :instance="instance" isWechat v-model="miniAppForm.media" :limit="1"></ai-uploader>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</ai-dialog>
|
||||
<ai-dialog
|
||||
:visible.sync="isShowDate"
|
||||
width="590px"
|
||||
title="定时发送"
|
||||
customFooter>
|
||||
<el-form ref="dateForm" :model="dateForm" label-width="130px" label-position="right">
|
||||
<div class="ai-form">
|
||||
<el-form-item label="定时发送时间" style="width: 100%;" prop="choiceTime" :rules="[{ required: true, message: '请选择定时发送时间', trigger: 'change' }]">
|
||||
<el-date-picker
|
||||
style="width: 100%;"
|
||||
v-model="dateForm.choiceTime"
|
||||
type="datetime"
|
||||
size="small"
|
||||
:picker-options="pickerOptions"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="请选择定时发送时间">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<div class="dialog-footer" slot="footer">
|
||||
<el-button @click="onClose">取消</el-button>
|
||||
<el-button @click="onDateForm" type="primary" :loading="isLoading2" style="width: 92px;">确认</el-button>
|
||||
</div>
|
||||
</ai-dialog>
|
||||
<!-- <ChooseMaterial ref="ChooseMaterial" :instance="instance" @change="onChooseChange"></ChooseMaterial> -->
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="cancel">取消</el-button>
|
||||
<el-button type="primary" @click="confirm(0)" :loading="isLoading1" style="width: 120px;">立即执行</el-button>
|
||||
<el-button type="primary" @click="confirm(1)">定时任务</el-button>
|
||||
</template>
|
||||
</ai-detail>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Phone from './Phone'
|
||||
import ChooseMaterial from './ChooseMaterial.vue'
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Add',
|
||||
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object,
|
||||
params: Object
|
||||
},
|
||||
|
||||
components: {
|
||||
Phone,
|
||||
ChooseMaterial
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
info: {},
|
||||
department: [],
|
||||
isShowChoose: false,
|
||||
isLoading1: false,
|
||||
isLoading2: false,
|
||||
fileList: [],
|
||||
isShowAddLink: false,
|
||||
isShowAddMiniapp: false,
|
||||
isShowDate: false,
|
||||
isLoading: false,
|
||||
linkForm: {
|
||||
linkPicUrl: [],
|
||||
linkDesc: '',
|
||||
linkTitle: '',
|
||||
linkUrl: ''
|
||||
},
|
||||
dateForm: {
|
||||
choiceTime: ''
|
||||
},
|
||||
miniAppForm: {
|
||||
mpAppid: '',
|
||||
mpPage: '',
|
||||
mpTitle: '',
|
||||
media: []
|
||||
},
|
||||
form: {
|
||||
content: '',
|
||||
choiceTime: '',
|
||||
contents: [],
|
||||
enableExamine: '0',
|
||||
taskEndTime: '',
|
||||
examines: [],
|
||||
wxGroups: [],
|
||||
markTag: [],
|
||||
wxGroupsName: '',
|
||||
sendScope: '1',
|
||||
sendType: 0,
|
||||
name: '',
|
||||
filterCriteria: [],
|
||||
taskTitle: '',
|
||||
examinesName: ''
|
||||
},
|
||||
girdNames: '',
|
||||
id: '',
|
||||
tagsList: [],
|
||||
pickerOptions: {
|
||||
disabledDate: e => {
|
||||
return e.getTime() < (Date.now() - 60 * 1000 * 60 * 24)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
|
||||
groupLen() {
|
||||
let i = 0
|
||||
if(this.form.wxGroups && this.form.wxGroups.length) {
|
||||
this.form.wxGroups.forEach(v => {
|
||||
i = i + v.groupIds.split(',').length
|
||||
})
|
||||
}
|
||||
return i
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.params && this.params.id) {
|
||||
this.id = this.params.id
|
||||
this.getInfo(this.params.id)
|
||||
this.dict.load('mstTag')
|
||||
} else {
|
||||
this.getWxGroups()
|
||||
this.dict.load('mstTag')
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions(['initOpenData', 'transCanvas']),
|
||||
|
||||
onChooseChange (e) {
|
||||
this.form.content = e.filter(v => v.msgType === '0').map(v => v.content).join(' ')
|
||||
|
||||
this.fileList = this.fileList.concat(e.filter(v => v.msgType !== '0'))
|
||||
},
|
||||
|
||||
getInfo(id) {
|
||||
this.instance.post(`/app/appmasssendingtaskxbot/queryDetailById?id=${id}`).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.form = {
|
||||
...this.form,
|
||||
...res.data,
|
||||
wxGroupsName: '1',
|
||||
filterCriteria: res.data.filterCriteria.split(','),
|
||||
// markTag: res.data.markTag.split(',')
|
||||
}
|
||||
|
||||
if (res.data.girdNames) {
|
||||
this.girdNames = res.data.girdNames.split(',')
|
||||
}
|
||||
|
||||
this.dateForm.choiceTime = ''
|
||||
|
||||
if (res.data.examines && res.data.examines.length) {
|
||||
this.form.examines = res.data.examines.map(v => {
|
||||
return {
|
||||
...v,
|
||||
wxOpenUserId: v.examineUserId,
|
||||
id: v.examineUserId,
|
||||
name: v.examineUserName
|
||||
}
|
||||
})
|
||||
this.form.examinesName = '1'
|
||||
}
|
||||
|
||||
|
||||
const content = res.data.contents.filter(v => v.msgType === '0')
|
||||
|
||||
if (content.length) {
|
||||
this.$set(this.form, 'content', content[0].content)
|
||||
}
|
||||
|
||||
this.fileList = res.data.contents.filter(v => v.msgType !== '0').map(v => {
|
||||
return {
|
||||
...v,
|
||||
...v.sysFile
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onUserChange(e) {
|
||||
if (e.length) {
|
||||
this.form.examinesName = '1'
|
||||
} else {
|
||||
this.form.wxGroupsName = ''
|
||||
}
|
||||
},
|
||||
|
||||
onScopeChange(e) {
|
||||
this.form.filterCriteria = []
|
||||
this.form.wxGroups = []
|
||||
this.girdNames = ''
|
||||
|
||||
if (e === '0') {
|
||||
this.getWxGroups()
|
||||
} else {
|
||||
this.form.filterCriteria = []
|
||||
}
|
||||
},
|
||||
|
||||
onPick(e) {
|
||||
if (this.form.sendScope === '2' && e.length) {
|
||||
this.girdNames = e.map(v => v.girdName)
|
||||
}
|
||||
},
|
||||
|
||||
onSelcetChange(e) {
|
||||
if (e.length) {
|
||||
this.form.wxGroupsName = '1'
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.getWxGroups()
|
||||
})
|
||||
} else {
|
||||
this.form.wxGroupsName = ''
|
||||
this.form.wxGroups = []
|
||||
}
|
||||
},
|
||||
|
||||
getWxGroups() {
|
||||
this.instance.post(`/app/appmasssendingtaskxbot/queryWxGroups?sendScope=${this.form.sendScope}&clientInfoId=${this.params.clientInfoId}`, {
|
||||
filterCriteria: this.form.filterCriteria.join(',')
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.form.wxGroups = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onLinkConfirm() {
|
||||
this.$refs.linkForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.fileList.push({
|
||||
...this.linkForm,
|
||||
linkPicUrl: this.linkForm.linkPicUrl.length ? this.linkForm.linkPicUrl[0].url : '',
|
||||
msgType: '4'
|
||||
})
|
||||
|
||||
this.isShowAddLink = false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onMiniAppForm() {
|
||||
this.$refs.miniAppForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.fileList.push({
|
||||
...this.miniAppForm,
|
||||
msgType: '5',
|
||||
...this.miniAppForm.media[0],
|
||||
mediaId: this.miniAppForm.media[0].media.mediaId,
|
||||
sysFileId: this.miniAppForm.media[0].id
|
||||
})
|
||||
|
||||
this.isShowAddMiniapp = false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onClose() {
|
||||
this.linkForm.linkPicUrl = []
|
||||
this.linkForm.linkDesc = ''
|
||||
this.linkForm.linkTitle = ''
|
||||
this.linkForm.linkUrl = ''
|
||||
this.miniAppForm.mpAppid = ''
|
||||
this.miniAppForm.mpPage = ''
|
||||
this.miniAppForm.mpTitle = ''
|
||||
this.dateForm.choiceTime = ''
|
||||
|
||||
this.isShowDate = false
|
||||
},
|
||||
|
||||
removeFile(index) {
|
||||
this.fileList.splice(index, 1)
|
||||
},
|
||||
|
||||
mapIcon(type) {
|
||||
return {
|
||||
1: 'https://cdn.cunwuyun.cn/dvcp/announce/img.png',
|
||||
2: 'https://cdn.cunwuyun.cn/dvcp/announce/video.png',
|
||||
3: 'https://cdn.cunwuyun.cn/dvcp/announce/folder.png',
|
||||
4: 'https://cdn.cunwuyun.cn/dvcp/announce/site.png',
|
||||
5: 'https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png'
|
||||
}[type]
|
||||
},
|
||||
|
||||
onBeforeUpload(event) {
|
||||
return this.onOverSize(event)
|
||||
},
|
||||
|
||||
getExtension(name) {
|
||||
return name.substring(name.lastIndexOf('.'))
|
||||
},
|
||||
|
||||
handleChange(e, size, accept) {
|
||||
const isLt10M = e.size / 1024 / 1024 < size
|
||||
const suffixName = this.getExtension(e.name)
|
||||
const suffixNameList = accept.split(',')
|
||||
|
||||
if (suffixNameList.indexOf(`${suffixName.toLowerCase()}`) === -1) {
|
||||
this.$message.error(`不支持该格式`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isLt10M) {
|
||||
this.$message.error(`大小不超过${10}MB!`)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
onExceed() {
|
||||
this.$message.error(`最多上传9个附件`)
|
||||
},
|
||||
|
||||
submitUpload(file, type) {
|
||||
const fileType = {
|
||||
'1': 'image',
|
||||
'2': 'video',
|
||||
'3': 'file'
|
||||
}[type]
|
||||
let formData = new FormData()
|
||||
formData.append('file', file.file)
|
||||
formData.append('type', fileType)
|
||||
let loading = this.$loading()
|
||||
this.instance.post(`/app/wxcp/upload/uploadFile`, formData, {
|
||||
withCredentials: false
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.fileList.push({
|
||||
...res.data.file,
|
||||
media: res.data.media,
|
||||
msgType: type,
|
||||
sysFileId: res.data.file.id,
|
||||
imgPicUrl: res.data.file.url,
|
||||
mediaId: res.data.media.mediaId
|
||||
})
|
||||
|
||||
this.$message.success('上传成功')
|
||||
}
|
||||
}).finally(() => loading.close())
|
||||
},
|
||||
|
||||
onDateForm() {
|
||||
this.$refs.dateForm.validate((valid) => {
|
||||
if (valid) {
|
||||
if (new Date(this.dateForm.choiceTime).getTime() < Date.now()) {
|
||||
return this.$message.error('定时发送时间不得早于当前时间')
|
||||
}
|
||||
|
||||
if (this.params.sendChannel === '1' && new Date(this.dateForm.choiceTime).getTime() > new Date(this.form.taskEndTime).getTime()) {
|
||||
return this.$message.error('定时发送时间不得晚于结束时间')
|
||||
}
|
||||
|
||||
this.confirm(1)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
confirm(sendType) {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
if (!this.form.wxGroups.length) {
|
||||
return this.$message.error('居民群数量不能为0')
|
||||
}
|
||||
|
||||
if (this.params.sendChannel === '1' && new Date(this.form.taskEndTime).getTime() < Date.now()) {
|
||||
return this.$message.error('结束时间不得早于当前时间')
|
||||
}
|
||||
|
||||
if (sendType === 1 && !this.dateForm.choiceTime) {
|
||||
this.isShowDate = true
|
||||
return false
|
||||
}
|
||||
|
||||
const contents = [
|
||||
{
|
||||
content: this.form.content,
|
||||
msgType: '0'
|
||||
},
|
||||
...this.fileList
|
||||
]
|
||||
|
||||
if (sendType === 0) {
|
||||
this.isLoading1 = true
|
||||
} else {
|
||||
this.isLoading2 = true
|
||||
}
|
||||
this.instance.post(`/app/appmasssendingtaskxbot/addOrUpdate`, {
|
||||
clientInfoId: this.params.clientInfoId,
|
||||
...this.form,
|
||||
id: this.params.id,
|
||||
wxGroups: this.form.wxGroups,
|
||||
contents,
|
||||
sendType,
|
||||
// markTag: this.form.markTag.join(','),
|
||||
sendChannel: this.params.sendChannel,
|
||||
choiceTime: this.dateForm.choiceTime,
|
||||
filterCriteria: this.form.filterCriteria.join(','),
|
||||
examines: this.form.examines.length ? this.form.examines.map(v => {
|
||||
return {
|
||||
...v,
|
||||
examineUserId: v.id,
|
||||
examineUserName: v.name
|
||||
}
|
||||
}) : [],
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success('提交成功')
|
||||
setTimeout(() => {
|
||||
this.cancel(true)
|
||||
}, 600)
|
||||
} else {
|
||||
this.isLoading1 = false
|
||||
this.isLoading2 = false
|
||||
}
|
||||
|
||||
}).catch(() => {
|
||||
this.isLoading1 = false
|
||||
this.isLoading2 = false
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
selectDept() {
|
||||
return this.$message.error(`您未在系统中关联‘主部门’,请联系管理员处理`)
|
||||
},
|
||||
|
||||
cancel(isRefresh) {
|
||||
this.$emit('change', {
|
||||
type: 'AnnounceList',
|
||||
isRefresh: !!isRefresh,
|
||||
params: {id: this.params.clientInfoId, status: this.params.status}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.el-tooltip__popper.is-dark {
|
||||
max-width: 240px;
|
||||
}
|
||||
.AppAnnounceDetail-content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.content-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
line-height: 1;
|
||||
margin-right: 4px;
|
||||
text-align: center;
|
||||
background: #F9F9F9;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #222;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.AppAnnounceAdd {
|
||||
.ai-detail__content {
|
||||
.ai-detail__content--wrapper {
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-form {
|
||||
textarea {
|
||||
border-radius: 4px 4px 0 0!important;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.add {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
padding: 14px 16px;
|
||||
background: #F9F9F9;
|
||||
border-radius: 0px 0px 4px 4px;
|
||||
border: 1px solid #D0D4DC;
|
||||
border-top: none;
|
||||
|
||||
.add-material {
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
left: 16px;
|
||||
transform: translateX(120%);
|
||||
}
|
||||
|
||||
.add-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #222;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.fileList {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.add-item {
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
font-style: normal;
|
||||
color: red;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.AppAnnounceDetail-container {
|
||||
display: flex;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
padding: 0 20px;
|
||||
overflow: auto;
|
||||
overflow: overlay;
|
||||
|
||||
.left {
|
||||
flex: 1;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.right {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.AppAnnounceDetail-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 32px;
|
||||
line-height: 1;
|
||||
background: #F5F5F5;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #D0D4DC;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
|
||||
&:hover {
|
||||
border-color: #26f;
|
||||
}
|
||||
|
||||
& > i {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
line-height: 32px;
|
||||
padding: 0 12px;
|
||||
color: #888888;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
border-right: 1px solid #D0D4DC;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.AppAnnounceDetail-select__input {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.select-right {
|
||||
height: 100%;
|
||||
padding: 0 12px;
|
||||
color: #222222;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all ease 0.3s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.select-left {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
padding: 5px 0 0px 12px;
|
||||
border-right: 1px solid #D0D4DC;
|
||||
border-radius: 4px 0 0 4px;
|
||||
background: #fff;
|
||||
|
||||
em {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
margin: 0 4px 5px 0;
|
||||
color: #222222;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
span {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
margin: 0 4px 5px 0;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
color: #222222;
|
||||
background: #F3F4F7;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #D0D4DC;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
color: #222222;
|
||||
|
||||
span {
|
||||
margin: 0 3px;
|
||||
color: #2266FF;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #8899bb;
|
||||
}
|
||||
|
||||
em {
|
||||
line-height: 20px;
|
||||
margin-top: 8px;
|
||||
color: #888888;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
.select-div {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
padding: 0 15px;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,438 @@
|
||||
<template>
|
||||
<ai-list class="AppAnnounce">
|
||||
<template slot="title">
|
||||
<ai-title title="推送任务" isShowBack isShowBottomBorder @onBackClick="cancel(true)">
|
||||
<template #sub>
|
||||
<span>设置居民助手的推送任务,可定时发送,也可立即发送</span>
|
||||
</template>
|
||||
</ai-title>
|
||||
</template>
|
||||
<template slot="content">
|
||||
<ai-search-bar class="search-bar">
|
||||
<template #left>
|
||||
<el-button size="small" type="primary" icon="iconfont iconAdd" @click="toAdd('')" :disabled="params.status != 1">创建任务</el-button>
|
||||
<ai-select
|
||||
v-model="search.status"
|
||||
@change="search.current = 1, getList()"
|
||||
placeholder="任务状态"
|
||||
:selectList="dict.getDict('mstXbotStatus')">
|
||||
</ai-select>
|
||||
<el-date-picker
|
||||
v-model="search.startTime"
|
||||
type="date"
|
||||
size="small"
|
||||
value-format="yyyy-MM-dd"
|
||||
@change="search.current = 1, getList()"
|
||||
placeholder="选择群发开始日期">
|
||||
</el-date-picker>
|
||||
<el-date-picker
|
||||
v-model="search.endTime"
|
||||
type="date"
|
||||
size="small"
|
||||
value-format="yyyy-MM-dd"
|
||||
@change="search.current = 1, getList()"
|
||||
placeholder="选择群发结束日期">
|
||||
</el-date-picker>
|
||||
<ai-user-selecter :instance="instance" @change="onUserChange" :isMultiple="false" v-model="userList" :actions="{tree: `/app/wxcp/wxdepartment/listAll?wxMainDepartmentId=${user.info.wxMainDepartmentId}`}" >
|
||||
<div class="userSelcet">
|
||||
<span style="color: #606266;" v-if="search.createUserId">{{ name }}</span>
|
||||
<span v-else>创建人</span>
|
||||
<i class="el-icon-arrow-up" v-if="!search.createUserId"></i>
|
||||
<i class="el-icon-circle-close" v-if="search.createUserId" @click.stop="userList = [], search.createUserId = '', name = '', search.current = 1, getList()"></i>
|
||||
</div>
|
||||
</ai-user-selecter>
|
||||
</template>
|
||||
<template slot="right">
|
||||
<el-input
|
||||
v-model="search.taskTitle"
|
||||
size="small"
|
||||
v-throttle="() => { search.current = 1, getList() }"
|
||||
placeholder="请输入任务名称"
|
||||
clearable
|
||||
@clear="search.current = 1, search.taskTitle = '', getList()"
|
||||
suffix-icon="iconfont iconSearch">
|
||||
</el-input>
|
||||
</template>
|
||||
</ai-search-bar>
|
||||
<ai-table
|
||||
:tableData="tableData"
|
||||
:col-configs="colConfigs"
|
||||
:total="total"
|
||||
v-loading="loading"
|
||||
style="margin-top: 6px; width: 100%;"
|
||||
:current.sync="search.current"
|
||||
:size.sync="search.size"
|
||||
@getList="getList">
|
||||
<el-table-column slot="user" width="140px" label="创建人" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="userinfo">
|
||||
<span>{{ row.createUserName }}</span>
|
||||
<span style="color: #999">{{ row.createUserDeptName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="options" width="140px" fixed="right" label="操作" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="table-options">
|
||||
<!-- <el-button type="text" @click="remindExamine(row.id)" v-if="['0'].includes(row.status)">催办</el-button> -->
|
||||
<el-button type="text" @click="remove(row.id)" v-if="['0'].includes(row.status)">删除</el-button>
|
||||
<el-button type="text" @click="close(row.id)" v-if="['0'].includes(row.status)">关闭</el-button>
|
||||
<!-- <el-button type="text" @click="cancel(row.id)" v-if="['0'].includes(row.status)">撤回</el-button> -->
|
||||
<el-button type="text" @click="toDetail(row.id)">详情</el-button>
|
||||
<el-button type="text" @click="toAdd(row.id)" v-if="['1', '3'].includes(row.status)">编辑</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ai-table>
|
||||
</template>
|
||||
</ai-list>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
name: 'List',
|
||||
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object,
|
||||
params: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
search: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
status: '',
|
||||
createUserId: '',
|
||||
taskTitle: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
markTag: ''
|
||||
},
|
||||
currIndex: '1',
|
||||
name: '',
|
||||
isShow: false,
|
||||
userList: [],
|
||||
tableData: [],
|
||||
loading: false,
|
||||
total: 0,
|
||||
colConfigs: [
|
||||
{ prop: 'taskTitle', label: '任务名称' },
|
||||
// { prop: 'typeName', label: '群发类型', align: 'center' },
|
||||
{ slot: 'user', label: '创建人', openType: 'userName', align: 'center' },
|
||||
{ prop: 'choiceTime', label: '群发时间', align: 'center' },
|
||||
{ prop: 'taskEndTime', label: '群发结束时间', align: 'center' },
|
||||
{
|
||||
prop: 'status',
|
||||
align: 'center',
|
||||
label: '状态',
|
||||
render: (h, {row}) => {
|
||||
return h('span', {
|
||||
style: {
|
||||
color: this.dict.getColor('mstXbotStatus', row.status)
|
||||
}
|
||||
}, this.dict.getLabel('mstXbotStatus', row.status))
|
||||
}
|
||||
},
|
||||
{ prop: 'completionRate', label: '任务进度', align: 'center', format: v => v ? v === '0.0' ? '0%' : `${v}%` : '-' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.dict.load('mstXbotStatus', 'mstSendType', 'mstTag').then(() => {
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
onUserChange (e) {
|
||||
if (e.length) {
|
||||
this.name = e[0].name
|
||||
this.search.createUserId = e[0].id
|
||||
} else {
|
||||
this.search.createUserId = ''
|
||||
this.name = ''
|
||||
}
|
||||
|
||||
this.search.current = 1
|
||||
this.getList()
|
||||
},
|
||||
|
||||
getList() {
|
||||
this.loading = true
|
||||
this.instance.post(`/app/appmasssendingtaskxbot/list`, null, {
|
||||
params: {
|
||||
...this.search,
|
||||
clientInfoId: this.params.id,
|
||||
markTag: this.search.markTag ? this.search.markTag.join(',') : ''
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.tableData = res.data.records.map(v => {
|
||||
return {
|
||||
...v,
|
||||
typeName: '群发居民群'
|
||||
}
|
||||
})
|
||||
this.total = res.data.total
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
this.loading = false
|
||||
}
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
remindExamine (id) {
|
||||
this.$confirm('确认再次通知任务审核人员?').then(() => {
|
||||
this.instance.post(`/app/appmasssendingtaskxbot/remindExamine?id=${id}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success('催办成功!')
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
close (id) {
|
||||
this.$confirm('确认关闭该群发任务?').then(() => {
|
||||
this.instance.post(`/app/appmasssendingtaskxbot/closeTask?id=${id}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success('关闭成功!')
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
cancel (id) {
|
||||
this.$confirm('确认撤回该群发任务?').then(() => {
|
||||
this.instance.post(`/app/appmasssendingtaskxbot/cancel?id=${id}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success('撤回成功!')
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
remove(id) {
|
||||
this.$confirm('确定删除该数据?').then(() => {
|
||||
this.instance.post(`/app/appmasssendingtaskxbot/delete?id=${id}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success('删除成功!')
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
toAdd(id) {
|
||||
this.$emit('change', {
|
||||
type: 'Add',
|
||||
params: {
|
||||
id: id || '',
|
||||
clientInfoId: this.params.id,
|
||||
sendChannel: 0,
|
||||
status: this.params.status
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
toDetail (id) {
|
||||
this.$emit('change', {
|
||||
type: 'Detail',
|
||||
params: {
|
||||
id,
|
||||
status: this.params.status
|
||||
}
|
||||
})
|
||||
},
|
||||
cancel(isRefresh) {
|
||||
this.$emit('change', {
|
||||
type: 'List',
|
||||
isRefresh: !!isRefresh
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AppAnnounce {
|
||||
height: 100%;
|
||||
|
||||
.dialog {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 111;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.dialog-wrapper {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
z-index: 2;
|
||||
width: 600px;
|
||||
height: 532px;
|
||||
padding: 40px 64px;
|
||||
box-sizing: border-box;
|
||||
background: #FFFFFF;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
& > img {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
z-index: 2;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 12px;
|
||||
font-size: 24px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
& > p {
|
||||
margin-bottom: 40px;
|
||||
font-size: 12px;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.dialog-list {
|
||||
// display: flex;
|
||||
// justify-content: space-between;
|
||||
margin-bottom: 40px;
|
||||
|
||||
& > div {
|
||||
// flex: 1;
|
||||
width: 216px;
|
||||
margin-left: 120px;
|
||||
height: 280px;
|
||||
padding: 24px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #D0D4DC;
|
||||
border-radius: 8px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: all ease 0.3s;
|
||||
|
||||
&.active, &:hover {
|
||||
background: #F7FAFF;
|
||||
border: 1px solid #2266FF;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 22px;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 8px;
|
||||
font-size: 18px;
|
||||
color: #222;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-right: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.userinfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
|
||||
span:first-child {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.userSelcet {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 215px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #d0d4dc;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
|
||||
&:hover {
|
||||
border-color: $placeholderColor;
|
||||
}
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: #d0d4dc;
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
|
||||
.el-icon-circle-close:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
padding: 0 15px;
|
||||
font-size: 12px;
|
||||
color: $placeholderColor;
|
||||
}
|
||||
}
|
||||
:deep .AiTitle .iconBack_Large {
|
||||
line-height: 22px!important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,221 @@
|
||||
<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
|
||||
v-if="isShow"
|
||||
:tableData="tableData"
|
||||
:col-configs="colConfigs"
|
||||
:total="total"
|
||||
style="margin-top: 6px; width: 100%;"
|
||||
:current.sync="search.current"
|
||||
:size.sync="search.size"
|
||||
@selection-change="onSelectChange"
|
||||
@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,
|
||||
value: []
|
||||
}
|
||||
},
|
||||
|
||||
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: 'mpTitle', label: this.mpTitle },
|
||||
{ prop: 'mpAppid', label: '小程序APPID', align: 'center' },
|
||||
{ prop: 'createUserName', label: '添加人', align: 'center' },
|
||||
{ prop: 'createTime', label: '添加时间', align: 'center' }
|
||||
]
|
||||
}
|
||||
|
||||
if (this.currIndex === 5) {
|
||||
return [
|
||||
{ type: 'selection' },
|
||||
{ prop: 'linkTitle', label: this.mpTitle },
|
||||
{ prop: 'linkUrl', 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/listByMST`, null, {
|
||||
params: {
|
||||
...this.search,
|
||||
mstType: 0,
|
||||
type: this.currIndex
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.tableData = res.data.records
|
||||
this.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onSelectChange (e) {
|
||||
this.value = e
|
||||
},
|
||||
|
||||
onConfirm () {
|
||||
if (!this.value.length) {
|
||||
return this.$message.error('请选择素材')
|
||||
}
|
||||
|
||||
if (this.value.length > 9) {
|
||||
return this.$message.error('素材不能超过9个')
|
||||
}
|
||||
|
||||
this.$emit('change', this.value)
|
||||
this.isShow = false
|
||||
},
|
||||
|
||||
open () {
|
||||
this.value = []
|
||||
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>
|
||||
784
project/biaopin/AppSeatManagementXbot/components/Detail.vue
Normal file
784
project/biaopin/AppSeatManagementXbot/components/Detail.vue
Normal file
@@ -0,0 +1,784 @@
|
||||
<template>
|
||||
<ai-detail class="AppAnnounceDetail">
|
||||
<template slot="title">
|
||||
<ai-title title="任务详情" isShowBack isShowBottomBorder @onBackClick="cancel(false)">
|
||||
</ai-title>
|
||||
</template>
|
||||
<template slot="content">
|
||||
<ai-card title="基础信息">
|
||||
<template #right>
|
||||
<div class="right-tips" v-if="info.status === '4'">
|
||||
<el-tooltip
|
||||
placement="top"
|
||||
content="任务开始后,3天内15分钟更新1次,3天后访问页面时触发更新,1小时最多刷新1次">
|
||||
<i class="iconfont iconDetails"></i>
|
||||
</el-tooltip>
|
||||
<span>数据更新于{{ info.dataUpdateTime }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<ai-wrapper>
|
||||
<ai-info-item label="任务名称" isLine :value="info.taskTitle"></ai-info-item>
|
||||
<ai-info-item label="任务状态" isLine>
|
||||
<span :style="{ color: dict.getColor('mstXbotStatus', info.status) }">{{ dict.getLabel('mstXbotStatus', info.status) }}</span>
|
||||
</ai-info-item>
|
||||
<ai-info-item label="创建人" isLine>
|
||||
<div class="user">
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/announce/user.png" />
|
||||
<span>{{ info.createUserName }}</span>
|
||||
<!-- (<span>{{ info.createUserDeptName }}</span>) -->
|
||||
</div>
|
||||
</ai-info-item>
|
||||
<ai-info-item label="审批人" isLine v-if="info.enableExamine === '1'">
|
||||
<div class="user-wrapper">
|
||||
<div class="user" v-for="(item, index) in info.examines" :key="index">
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/announce/user.png" />
|
||||
<span>{{ item.examineUserName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ai-info-item>
|
||||
<!-- <ai-info-item label="发送方式">
|
||||
<span>{{ info.sendChannel === '1' ? '通知员工转发' : '成员一键群发' }}</span>
|
||||
</ai-info-item> -->
|
||||
<!-- <ai-info-item label="标签" :value="info.markTag">
|
||||
</ai-info-item> -->
|
||||
<ai-info-item label="创建时间" :value="info.createTime"></ai-info-item>
|
||||
<ai-info-item label="群发时间" :value="info.choiceTime"></ai-info-item>
|
||||
<ai-info-item label="结束时间" :value="info.taskEndTime" v-if="info.sendChannel === '1'"></ai-info-item>
|
||||
<ai-info-item label="群发范围" isLine>
|
||||
<div class="text">
|
||||
<span>{{ info.sendScope === '0' ? '全部' : '按条件筛选的' }}</span>
|
||||
<i>{{ groups.length }}</i>
|
||||
<span>个居民群</span>
|
||||
<em @click="isShowGroups = true">详情</em>
|
||||
</div>
|
||||
</ai-info-item>
|
||||
<ai-info-item label="消息内容" isLine>
|
||||
<div class="msg">
|
||||
<p>{{ content }}</p>
|
||||
<div class="msg-bottom">
|
||||
<div class="left" v-if="fileList.length">
|
||||
<img :src="mapIcon(fileList[0].msgType)" />
|
||||
<span>{{ mapType(fileList[0].msgType) }}{{ fileList[0].mpTitle || fileList[0].name || fileList[0].linkTitle }} 等</span>
|
||||
<i>{{ fileList.length }}</i>
|
||||
<span>个附件</span>
|
||||
</div>
|
||||
<div class="left" v-else>
|
||||
<span>暂无附件</span>
|
||||
</div>
|
||||
<div class="right" @click="isShowPhone = true">预览消息</div>
|
||||
</div>
|
||||
</div>
|
||||
</ai-info-item>
|
||||
</ai-wrapper>
|
||||
</template>
|
||||
</ai-card>
|
||||
<!-- <ai-card>
|
||||
<template #title>
|
||||
<div class="AppAnnounceDetail-title">
|
||||
<span :class="[currIndex === 0 ? 'active' : '']" @click="currIndex = 0">成员统计</span>
|
||||
<span :class="[currIndex === 1 ? 'active' : '']" @click="currIndex = 1">居民群统计</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="content-item" v-if="currIndex === 0">
|
||||
<div class="top">
|
||||
<div class="top-item">
|
||||
<div class="top-item__title">
|
||||
<h3>计划执行成员</h3>
|
||||
</div>
|
||||
<p>{{ memberInfo.planCount || 0 }}</p>
|
||||
</div>
|
||||
<div class="top-item">
|
||||
<div class="top-item__title">
|
||||
<h3>未执行成员</h3>
|
||||
</div>
|
||||
<p>{{ memberInfo.unExecutedCount || 0 }}</p>
|
||||
</div>
|
||||
<div class="top-item">
|
||||
<div class="top-item__title">
|
||||
<h3>已执行成员</h3>
|
||||
</div>
|
||||
<p>{{ memberInfo.executedCount || 0 }}</p>
|
||||
</div>
|
||||
<div class="top-item" v-if="info.sendChannel === '0'">
|
||||
<div class="top-item__title">
|
||||
<h3>无法执行成员</h3>
|
||||
<el-tooltip
|
||||
placement="top"
|
||||
content="由于员工不在可见范围、离职、客户群接收已达到上限等原因,无法执行群发任务的成员总数">
|
||||
<i class="iconfont iconDetails"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<p>{{ memberInfo.cannotExecuteCount || 0 }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="bottom-search">
|
||||
<div class="left">
|
||||
<el-radio-group v-model="search1.sendStatus" size="small" @change="search1.current = 1, getMemberInfo()">
|
||||
<el-radio-button size="small" label="0">未执行</el-radio-button>
|
||||
<el-radio-button size="small" label="1">已执行</el-radio-button>
|
||||
<el-radio-button size="small" label="2" v-if="info.sendChannel === '0'">无法执行</el-radio-button>
|
||||
</el-radio-group>
|
||||
<ai-picker
|
||||
dialogTitle="选择部门"
|
||||
action="/app/wxcp/wxdepartment/departList"
|
||||
:instance="instance"
|
||||
@pick="e => onUserChange(e, 'search1')" :multiple="false" v-model="user1">
|
||||
<div class="userSelcet">
|
||||
<span style="color: #606266;" v-if="search1.deptartId">{{ name1 }}</span>
|
||||
<span v-else>部门</span>
|
||||
<i class="el-icon-arrow-up" v-if="!search1.deptartId"></i>
|
||||
<i class="el-icon-circle-close" v-if="search1.deptartId" @click.stop="user1 = [], search1.deptartId = '', search1.current = 1, getMemberInfo()"></i>
|
||||
</div>
|
||||
</ai-picker>
|
||||
</div>
|
||||
<el-button :type="isDisabled ? '' : 'primary'" :disabled="isDisabled" @click="sendMsg(0)" v-if="info.status === '4'">{{ isDisabled ? min + '分钟后可再次提醒' : '提醒成员发送' }}</el-button>
|
||||
</div>
|
||||
<ai-table
|
||||
:tableData="tableData1"
|
||||
:col-configs="colConfigs1"
|
||||
:total="total1"
|
||||
border
|
||||
tableSize="small"
|
||||
:current.sync="search1.current"
|
||||
:size.sync="search1.size"
|
||||
@getList="getMemberInfo">
|
||||
<el-table-column slot="user" label="成员" align="left">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="userinfo">
|
||||
<span>{{ row.groupOwnerName }}</span>
|
||||
<span style="color: #999">{{ row.mainDepartmentName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ai-table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-item" v-if="currIndex === 1">
|
||||
<div class="top">
|
||||
<div class="top-item">
|
||||
<div class="top-item__title">
|
||||
<h3>计划送达居民群</h3>
|
||||
</div>
|
||||
<p>{{ groupInfo.planCount || 0 }}</p>
|
||||
</div>
|
||||
<div class="top-item">
|
||||
<div class="top-item__title">
|
||||
<h3>未送达居民群</h3>
|
||||
</div>
|
||||
<p>{{ groupInfo.unExecutedCount || 0 }}</p>
|
||||
</div>
|
||||
<div class="top-item">
|
||||
<div class="top-item__title">
|
||||
<h3>已送达居民群</h3>
|
||||
</div>
|
||||
<p>{{ groupInfo.executedCount || 0 }}</p>
|
||||
</div>
|
||||
<div class="top-item" v-if="info.sendChannel === '0'">
|
||||
<div class="top-item__title">
|
||||
<h3>无法送达居民群</h3>
|
||||
</div>
|
||||
<p>{{ groupInfo.cannotExecuteCount || 0 }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="bottom-search">
|
||||
<div class="left">
|
||||
<el-radio-group v-model="search2.sendStatus" size="small" @change="search2.current = 1, getGroupInfo()">
|
||||
<el-radio-button size="small" label="0">未送达</el-radio-button>
|
||||
<el-radio-button size="small" label="1">已送达</el-radio-button>
|
||||
<el-radio-button size="small" label="2" v-if="info.sendChannel === '0'">无法送达</el-radio-button>
|
||||
</el-radio-group>
|
||||
<ai-picker
|
||||
dialogTitle="选择部门"
|
||||
action="/app/wxcp/wxdepartment/departList"
|
||||
:instance="instance"
|
||||
@pick="e => onUserChange(e, 'search2')" :multiple="false" v-model="user2">
|
||||
<div class="userSelcet">
|
||||
<span style="color: #606266;" v-if="search2.deptartId">{{ name2 }}</span>
|
||||
<span v-else>部门</span>
|
||||
<i class="el-icon-arrow-up" v-if="!search2.deptartId"></i>
|
||||
<i class="el-icon-circle-close" v-if="search2.deptartId" @click.stop="user1 = [], search2.deptartId = '', search2.current = 1, getGroupInfo()"></i>
|
||||
</div>
|
||||
</ai-picker>
|
||||
</div>
|
||||
<el-button :type="isDisabled ? '' : 'primary'" :disabled="isDisabled" @click="sendMsg(1)" v-if="info.status === '4'">{{ isDisabled ? min + '分钟后可再次提醒' : '提醒成员发送' }}</el-button>
|
||||
</div>
|
||||
<ai-table
|
||||
:tableData="tableData2"
|
||||
:col-configs="colConfigs2"
|
||||
:total="total2"
|
||||
border
|
||||
tableSize="small"
|
||||
:current.sync="search2.current"
|
||||
:size.sync="search2.size"
|
||||
@getList="getGroupInfo">
|
||||
<el-table-column slot="user" label="群主" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="userinfo">
|
||||
<span>{{ row.groupOwnerName }}</span>
|
||||
<span style="color: #999">{{ row.mainDepartmentName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ai-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ai-card> -->
|
||||
<ai-dialog
|
||||
:visible.sync="isShowGroups"
|
||||
width="890px"
|
||||
title="群发范围"
|
||||
customFooter
|
||||
@onConfirm="isShowGroups = false">
|
||||
<ai-table
|
||||
:tableData="info.wxGroups"
|
||||
:col-configs="colConfigs3"
|
||||
border
|
||||
tableSize="small"
|
||||
:isShowPagination="false"
|
||||
@getList="() => {}">
|
||||
</ai-table>
|
||||
<div class="dialog-footer" slot="footer">
|
||||
<el-button @click="isShowGroups = false">关闭</el-button>
|
||||
</div>
|
||||
</ai-dialog>
|
||||
<div class="detail-phone" v-if="isShowPhone">
|
||||
<div class="mask"></div>
|
||||
<Phone :avatar="user.info.avatar" @close="isShowPhone = false" :isShowClose="true" :content="content" :fileList="fileList"></Phone>
|
||||
</div>
|
||||
</template>
|
||||
</ai-detail>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import Phone from './Phone'
|
||||
export default {
|
||||
name: 'Detail',
|
||||
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object,
|
||||
params: Object
|
||||
},
|
||||
|
||||
components: {
|
||||
Phone
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
total1: 0,
|
||||
isShowGroups: false,
|
||||
isShowPhone: false,
|
||||
total2: 0,
|
||||
user1: [],
|
||||
user2: [],
|
||||
name1: '',
|
||||
name2: '',
|
||||
radio1: '未执行',
|
||||
search1: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
deptartId: '',
|
||||
type: 0,
|
||||
sendStatus: '0'
|
||||
},
|
||||
search2: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
deptartId: '',
|
||||
type: 1,
|
||||
sendStatus: '0'
|
||||
},
|
||||
memberInfo: {},
|
||||
groupInfo: {},
|
||||
tableData1: [],
|
||||
fileList: [],
|
||||
tableData2: [],
|
||||
info: {},
|
||||
content: '',
|
||||
currIndex: 0,
|
||||
colConfigs3: [
|
||||
{ prop: 'groupOwnerName', label: '群主' },
|
||||
{ prop: 'groupNames', label: '群名称' }
|
||||
],
|
||||
colConfigs1: [
|
||||
{ slot: 'user', label: '成员' },
|
||||
{ prop: 'groupCount', label: '预计送达居民群', align: 'center' }
|
||||
],
|
||||
colConfigs2: [
|
||||
{ prop: 'groupName', label: '居民群' },
|
||||
{ prop: 'memberCount', label: '群人数', align: 'center' },
|
||||
{ slot: 'user', label: '群主', align: 'center' },
|
||||
],
|
||||
groups: [],
|
||||
timer: null,
|
||||
min: 60,
|
||||
isDisabled: false,
|
||||
rejecterId: ''
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['user'])
|
||||
},
|
||||
|
||||
created () {
|
||||
this.getInfo(this.params.id)
|
||||
// this.getMemberInfo()
|
||||
// this.getGroupInfo()
|
||||
},
|
||||
|
||||
destroyed () {
|
||||
clearInterval(this.timer)
|
||||
},
|
||||
|
||||
methods: {
|
||||
getMemberInfo () {
|
||||
this.instance.post(`/app/appmasssendingtaskxbot/detailStatistics`, null, {
|
||||
params: {
|
||||
...this.search1,
|
||||
taskId: this.params.id
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.tableData1 = res.data.executedList.records
|
||||
this.total1 = res.data.executedList.total
|
||||
this.memberInfo = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onUserChange (e, search) {
|
||||
if (e.length) {
|
||||
search === 'search1' ? this.name1 = e[0].name : this.name2 = e[0].name
|
||||
this[search].deptartId = e[0].id
|
||||
} else {
|
||||
this[search].deptartId = ''
|
||||
search === 'search1' ? this.name1 = '' : this.name2 = ''
|
||||
}
|
||||
|
||||
this[search].current = 1
|
||||
if (search === 'search1') {
|
||||
this.getMemberInfo()
|
||||
} else {
|
||||
this.getGroupInfo()
|
||||
}
|
||||
},
|
||||
|
||||
sendMsg () {
|
||||
this.instance.post(`/app/appmasssendingtaskxbot/remindSend?id=${this.params.id}`).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success('提醒成功')
|
||||
this.getInfo(this.params.id)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getGroupInfo () {
|
||||
this.instance.post(`/app/appmasssendingtaskxbot/detailStatistics`, null, {
|
||||
params: {
|
||||
...this.search2,
|
||||
taskId: this.params.id
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.tableData2 = res.data.executedList.records.map(v => {
|
||||
return {
|
||||
...v,
|
||||
groupName: v.groupName || '未命名群聊'
|
||||
}
|
||||
})
|
||||
this.total2 = res.data.executedList.total
|
||||
this.groupInfo = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
countdown () {
|
||||
this.timer = setInterval(() => {
|
||||
const nowTime = this.$moment(new Date())
|
||||
const min = nowTime.diff(this.info.remindTime, 'minute')
|
||||
this.min = (60 - min)
|
||||
|
||||
if (this.min <= 0) {
|
||||
this.isDisabled = false
|
||||
clearInterval(this.timer)
|
||||
} else {
|
||||
this.isDisabled = true
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
getInfo (id) {
|
||||
this.instance.post(`/app/appmasssendingtaskxbot/queryDetailById?id=${id}`).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.info = res.data
|
||||
if (res.data.status === '4' && res.data.remindTime) {
|
||||
this.countdown()
|
||||
}
|
||||
|
||||
const content = res.data.contents.filter(v => v.msgType === '0')
|
||||
|
||||
if (content.length) {
|
||||
this.content = content[0].content
|
||||
}
|
||||
|
||||
this.fileList = res.data.contents.filter(v => v.msgType !== '0').map(v => {
|
||||
return {
|
||||
...v,
|
||||
...v.sysFile
|
||||
}
|
||||
})
|
||||
|
||||
this.info.wxGroups = res.data.wxGroups.map(v => {
|
||||
this.groups.push(...v.groupIds.split(','))
|
||||
|
||||
return {
|
||||
...v,
|
||||
groupIds: v.groupIds.split(',')
|
||||
}
|
||||
})
|
||||
|
||||
if (res.data.examines && res.data.examines.length) {
|
||||
const user = res.data.examines.filter(v => v.examineStatus === '2')
|
||||
|
||||
if (user.length) {
|
||||
this.rejecterId = user[0].examineUserId
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
mapType (type) {
|
||||
return {
|
||||
1: '图片',
|
||||
2: '视频',
|
||||
3: '文件',
|
||||
4: '网站',
|
||||
5: '小程序'
|
||||
}[type]
|
||||
},
|
||||
|
||||
mapIcon (type) {
|
||||
return {
|
||||
1: 'https://cdn.cunwuyun.cn/dvcp/announce/img.png',
|
||||
2: 'https://cdn.cunwuyun.cn/dvcp/announce/video.png',
|
||||
3: 'https://cdn.cunwuyun.cn/dvcp/announce/folder.png',
|
||||
4: 'https://cdn.cunwuyun.cn/dvcp/announce/site.png',
|
||||
5: 'https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png'
|
||||
}[type]
|
||||
},
|
||||
|
||||
cancel (isRefresh) {
|
||||
this.$emit('change', {
|
||||
type: 'AnnounceList',
|
||||
isRefresh: !!isRefresh,
|
||||
params: {id: this.params.clientInfoId, status: this.params.status}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.AppAnnounceDetail {
|
||||
position: relative;
|
||||
.user-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.detail-phone {
|
||||
position: fixed;
|
||||
left: 0%;
|
||||
top: 0%;
|
||||
z-index: 11;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background: rgba($color: #000000, $alpha: 0.6);
|
||||
}
|
||||
|
||||
:deep( .phone-container ){
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
z-index: 11;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
.userSelcet {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 215px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin-left: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #d0d4dc;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
|
||||
&:hover {
|
||||
border-color: #26f;
|
||||
}
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: #d0d4dc;
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
|
||||
.el-icon-circle-close:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
padding: 0 15px;
|
||||
font-size: 12px;
|
||||
color: $placeholderColor;
|
||||
}
|
||||
}
|
||||
|
||||
.userinfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
|
||||
span:first-child {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
.user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
margin-right: 8px;
|
||||
|
||||
img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
span {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
color: #222222;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
color: #2266FF;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
em {
|
||||
margin-left: 8px;
|
||||
color: #2266FF;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
cursor: pointer;
|
||||
transition: all ease 0.3s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.msg {
|
||||
background: #F9F9F9;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #D0D4DC;
|
||||
|
||||
p {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
line-height: 38px;
|
||||
padding: 0px 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.msg-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 38px;
|
||||
padding: 0 16px;
|
||||
border-top: 1px solid #D0D4DC;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #222222;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #2266FF;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
color: #2266FF;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep( .AppAnnounceDetail-title ){
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
height: 100%;
|
||||
line-height: 56px;
|
||||
margin-right: 32px;
|
||||
color: #888888;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
transition: all ease 0.3s;
|
||||
border-bottom: 3px solid transparent;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
color: #222;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #222222;
|
||||
border-bottom: 3px solid #2266FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-item {
|
||||
.top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.top-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 90px;
|
||||
margin-right: 16px;
|
||||
padding: 0 16px;
|
||||
background: #F9F9F9;
|
||||
border-radius: 2px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.top-item__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
i {
|
||||
margin-left: 4px;
|
||||
color: #8899bb;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #222222;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #2266FF;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep( .right-tips ){
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
margin-right: 4px;
|
||||
color: #8899bb;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #888888;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
364
project/biaopin/AppSeatManagementXbot/components/GroupList.vue
Normal file
364
project/biaopin/AppSeatManagementXbot/components/GroupList.vue
Normal file
@@ -0,0 +1,364 @@
|
||||
<template>
|
||||
<section class="GroupList">
|
||||
<ai-list>
|
||||
<template slot="title">
|
||||
<ai-title title="群匹配" isShowBottomBorder isShowBack @onBackClick="cancel(false)"></ai-title>
|
||||
</template>
|
||||
<template #content>
|
||||
<ai-search-bar>
|
||||
<template #left>
|
||||
<ai-select v-model="search.matchStatus" :selectList="dict.getDict('xbotGroupMatchStatus')" placeholder="请选择是否匹配" @change="search.current = 1, getTableData()"/>
|
||||
<ai-select v-model="search.status" :selectList="dict.getDict('xbotGroupStatus')" placeholder="请选择群状态" @change="search.current = 1, getTableData()"/>
|
||||
<el-button type="primary" @click="match">自动匹配</el-button>
|
||||
</template>
|
||||
<template #right>
|
||||
<el-input
|
||||
v-model="search.nickname"
|
||||
size="small"
|
||||
placeholder="群名称"
|
||||
clearable
|
||||
v-throttle="() => {search.current = 1, getTableData()}"
|
||||
@clear="search.current = 1, search.nickname = '', getTableData()"
|
||||
suffix-icon="iconfont iconSearch">
|
||||
</el-input>
|
||||
</template>
|
||||
</ai-search-bar>
|
||||
<ai-table :tableData="tableData" :total="total" :current.sync="search.current" :size.sync="search.size"
|
||||
@getList="getTableData()" :col-configs="colConfigs">
|
||||
<el-table-column slot="avatar" label="群聊头像" align="left">
|
||||
<template slot-scope="{ row }">
|
||||
<img :src="row.avatar ? row.avatar : 'https://cdn.cunwuyun.cn/dvcp/group-img.png'" alt="" class="group-avatar">
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="areaName" label="所属地区" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<div @click="changeArea(row)" style="cursor: pointer;color:#26f">{{row.areaName || '请选择'}}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="villageName" label="所属小区" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<div @click="changeVillage(row)" style="cursor: pointer;color:#26f">{{row.residentialQuarters || '请选择'}}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="option" label="操作" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<div class="table-options">
|
||||
<el-button type="text" @click="edit(row)">编辑</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ai-table>
|
||||
</template>
|
||||
</ai-list>
|
||||
<ai-dialog
|
||||
:visible.sync="dialog"
|
||||
width="800px"
|
||||
@close="dialog=false"
|
||||
title="群关联信息"
|
||||
@onConfirm="onConfirm">
|
||||
<el-form class="ai-form" label-width="120px" :model="editInfo" ref="editInfo">
|
||||
<el-form-item label="群名称" style="width: 100%;">
|
||||
<div>{{editInfo.nickname}}</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="群主ID" style="width: 100%;">
|
||||
<div>{{editInfo.managerWxid}}</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="人员关联" prop="managerTpUserName" style="width: 100%;" :rules="[{ required: true, message: '请选择人员' }]">
|
||||
<!-- <el-input disabled size="small" v-model="editInfo.managerTpUserName" clearable placeholder="请选择人员">
|
||||
<template slot="append">
|
||||
<ai-user-selecter refs="addTags" :isMultiple="false" :instance="instance" v-model="editInfo.user" @change="onChooseUser">
|
||||
<el-button size="small">选择人员</el-button>
|
||||
</ai-user-selecter>
|
||||
</template>
|
||||
</el-input> -->
|
||||
<ai-person-select :instance="instance" :customClicker="true" :chooseUserList="chooseUserList"
|
||||
url="/app/appgirdmemberinfo/list" headerTitle="网格员列表"
|
||||
:isMultiple="false" dialogTitle="选择" @selectPerson="selectPerson" class="aipersonselect">
|
||||
<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-form-item>
|
||||
</el-form>
|
||||
</ai-dialog>
|
||||
<ai-dialog
|
||||
title="所属地区设置"
|
||||
:visible.sync="showArea"
|
||||
:destroyOnClose="true"
|
||||
width="720px"
|
||||
@onConfirm="areaConfirm"
|
||||
>
|
||||
<el-form class="ai-form" :model="areaInfo" label-width="120px" ref="form">
|
||||
<el-form-item prop="areaId" style="width: 100%;" label="所属地区" :rules="[{required: true, message: '请选择所属地区', trigger: 'blur'}]">
|
||||
<ai-area-get style="width: 400px;" placeholder="所属地区" :instance="instance" v-model="areaInfo.areaId" :name.sync="areaInfo.areaName" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ai-dialog>
|
||||
<ai-dialog
|
||||
title="所属小区设置"
|
||||
:visible.sync="showVillage"
|
||||
:destroyOnClose="true"
|
||||
width="720px"
|
||||
@onConfirm="villageConfirm"
|
||||
>
|
||||
<el-form class="ai-form" :model="villageInfo" label-width="120px" ref="villageForm">
|
||||
<el-form-item prop="residentialQuarters" style="width: 100%;" label="所属小区" :rules="[{required: true, message: '请选择所属小区', trigger: 'blur'}]">
|
||||
<!-- <ai-area-get style="width: 400px;" placeholder="所属小区" :instance="instance" v-model="villageInfo.villageId" :name.sync="villageInfo.villageName" /> -->
|
||||
<el-select size="small"
|
||||
v-model="villageInfo.residentialQuarters"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
placeholder="所属小区">
|
||||
<el-option
|
||||
v-for="item in villageOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ai-dialog>
|
||||
</section>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
export default {
|
||||
name: "GroupList",
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object,
|
||||
params: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
search: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
matchStatus: '',
|
||||
status: '',
|
||||
nickname: ''
|
||||
},
|
||||
tableData: [],
|
||||
total: 0,
|
||||
dialog: false,
|
||||
editInfo: {},
|
||||
showArea: false,
|
||||
areaInfo: {},
|
||||
chooseUserList: [],
|
||||
showVillage: false,
|
||||
villageInfo: {residentialQuarters: ''},
|
||||
villageOptions: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.dict.load('xbotGroupMatchStatus', 'xbotGroupStatus').then(() => {
|
||||
this.getTableData()
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
colConfigs() {
|
||||
return [
|
||||
{ slot: "avatar", label: '群聊头像'},
|
||||
{ prop: "nickname", label: '群名称', align: 'center'},
|
||||
{ prop: "managerNickname", label: '群主昵称', align: 'center'},
|
||||
{ prop: "status", label: '群状态', align: 'center', dict: 'xbotGroupStatus'},
|
||||
{ prop: "managerTpUserName", label: '关联人员', align: 'center'},
|
||||
{ prop: "managerTpUserDeptName", label: '主部门', align: 'center'},
|
||||
{ slot: "areaName", label: '所属地区', align: 'center'},
|
||||
{ slot: "villageName", label: '所属小区', align: 'center'},
|
||||
{ slot: "option"},
|
||||
// { prop: "status", label: '状态'},
|
||||
]
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getTableData() {
|
||||
this.instance.post(`/app/xbotCallback/groupMatchList`,null,{
|
||||
params: {
|
||||
...this.search,
|
||||
managerWxid: this.params.loginUserId
|
||||
}
|
||||
}).then(res => {
|
||||
if(res?.data) {
|
||||
this.tableData = res.data.records
|
||||
this.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
showDialog(row) {
|
||||
this.dialog = true
|
||||
this.groupInfo = row
|
||||
this.getTableDataGroup()
|
||||
},
|
||||
getTableDataGroup() {
|
||||
this.instance.post(`/app/xbotCallback/groupList`,null,{
|
||||
params: {
|
||||
// ...this.searchGroup,
|
||||
current: 1,
|
||||
managerWxid: this.groupInfo.loginUserId
|
||||
}
|
||||
}).then(res => {
|
||||
if(res?.data) {
|
||||
this.tableDataGroup = JSON.parse(res.data)
|
||||
this.tableDataGroup.map((item) => {
|
||||
item.num = item.member_list.length
|
||||
})
|
||||
// this.totalGroup = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
cancel(isRefresh) {
|
||||
this.$emit('change', {
|
||||
type: 'List',
|
||||
isRefresh: !!isRefresh
|
||||
})
|
||||
},
|
||||
match() {
|
||||
this.instance.post(`/app/xbotCallback/automaticMatch`,null,{
|
||||
params: {
|
||||
managerWxid: this.params.loginUserId
|
||||
}
|
||||
}).then(res => {
|
||||
if(res.code == 0) {
|
||||
this.$message.success('匹配成功!')
|
||||
this.getTableData()
|
||||
}
|
||||
})
|
||||
},
|
||||
edit(row) {
|
||||
this.dialog = true
|
||||
this.editInfo = {...row}
|
||||
this.chooseUserList = [{id: row.managerTpwxid, name: row.managerTpUserName}]
|
||||
},
|
||||
onChooseUser(v) {
|
||||
this.editInfo.wxUserId = v[0].id
|
||||
this.editInfo.managerTpUserName = v[0].name
|
||||
this.editInfo.user = v
|
||||
},
|
||||
onConfirm() {
|
||||
this.$refs.editInfo.validate(v => {
|
||||
if (v) {
|
||||
this.instance.post('/app/xbotCallback/manualMatch', null, {
|
||||
params: {
|
||||
wxUserId: this.editInfo.wxUserId,
|
||||
wxid: this.editInfo.wxid
|
||||
}
|
||||
}).then(res => {
|
||||
if (res?.code == 0) {
|
||||
this.dialog = false
|
||||
this.getTableData()
|
||||
this.$message.success('编辑成功!')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
changeArea(row) {
|
||||
this.showArea = true
|
||||
this.areaInfo = {...row}
|
||||
this.$refs['form'].clearValidate();
|
||||
},
|
||||
areaConfirm() {
|
||||
this.$refs['form'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.instance.post(`app/xbotCallback/updateGroupArea?wxid=${this.areaInfo.wxid}&areaId=${this.areaInfo.areaId}&areaName=${this.areaInfo.areaName}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success('所属地区设置成功')
|
||||
this.showArea = false
|
||||
this.getTableData()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
},
|
||||
changeVillage(row) {
|
||||
if(!row.areaId) return this.$message.error('请先选所属地区')
|
||||
this.instance.post(`app/xbotCallback/residentialQuarters?areaId=${row.areaId}&size=1000`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.villageOptions = res.data
|
||||
}
|
||||
})
|
||||
this.showVillage = true
|
||||
this.villageInfo = {...row}
|
||||
this.$refs['villageForm'].clearValidate();
|
||||
},
|
||||
villageConfirm() {
|
||||
this.$refs['villageForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.instance.post(`app/xbotCallback/updateGroupResidentialQuarters?wxid=${this.villageInfo.wxid}&residentialQuarters=${this.villageInfo.residentialQuarters}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success('所属小区设置成功')
|
||||
this.showVillage = false
|
||||
this.getTableData()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
selectPerson(val) {
|
||||
if (val) {
|
||||
this.editInfo.managerTpUserName = val.name
|
||||
this.editInfo.wxUserId = val.wxUserId
|
||||
} else {
|
||||
this.editInfo.managerTpUserName = ''
|
||||
this.editInfo.wxUserId = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.GroupList {
|
||||
height: 100%;
|
||||
|
||||
.time-select {
|
||||
padding: 0 16px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border: 1px solid #d0d4dc;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
.el-icon-arrow-down {
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep .is-error {
|
||||
.time-select {
|
||||
border: 1px solid #f46!important;
|
||||
}
|
||||
}
|
||||
.tips {
|
||||
display: inline-block;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
.group-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
:deep .ai-dialog__content--wrapper {
|
||||
// height: 1000px;
|
||||
// overflow-y: scroll;
|
||||
.ai-table {
|
||||
// height: 1000px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
329
project/biaopin/AppSeatManagementXbot/components/List.vue
Normal file
329
project/biaopin/AppSeatManagementXbot/components/List.vue
Normal file
@@ -0,0 +1,329 @@
|
||||
<template>
|
||||
<section class="List">
|
||||
<ai-list>
|
||||
<template slot="title">
|
||||
<ai-title title="席位管理" isShowBottomBorder></ai-title>
|
||||
</template>
|
||||
<template #content>
|
||||
<ai-search-bar>
|
||||
<template #left>
|
||||
<el-select v-model="configInfo.id" filterable placeholder="请选择机位" size="small" @change="configChange" clearable>
|
||||
<el-option
|
||||
v-for="item in configList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<ai-area-get
|
||||
style="width: 180px;"
|
||||
placeholder="所属地区"
|
||||
:instance="instance"
|
||||
v-model="search.areaId"
|
||||
@change="getListInit"/>
|
||||
<span class="tips">当前机位1有效期为:{{configInfo.validity}} ip地址为:{{configInfo.xbotIp}}</span>
|
||||
</template>
|
||||
<template #right>
|
||||
<el-button size="small" v-if="configInfo.validity" :type="configInfo.status != 1 ? 'info' : 'primary'" @click="openWechat()" :disabled="configInfo.status != 1">{{configInfo.status != 1 ? '已启动' : '启动'}}</el-button>
|
||||
</template>
|
||||
</ai-search-bar>
|
||||
<ai-table :tableData="tableData" :total="total" :current.sync="search.current" :size.sync="search.size"
|
||||
@getList="getTableData()" :col-configs="colConfigs">
|
||||
<el-table-column slot="avatar" label="头像 " align="left" width="100">
|
||||
<template slot-scope="{ row }">
|
||||
<img :src="row.avatar ? row.avatar : 'https://cdn.cunwuyun.cn/dvcp/group-img.png'" alt="" class="group-avatar">
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="loginUserName" label="姓名" align="left" width="180" show-overflow-tooltip>
|
||||
<template slot-scope="{ row }">
|
||||
<p>{{row.loginUserName}} ({{row.loginUserId}})</p>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="groupCount" label="监控群聊" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<div @click="showDialog(row)" style="cursor: pointer;color:#26f">{{row.monitorGroupCount}}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="areaName" label="所属地区" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<div @click="changeArea(row)" style="cursor: pointer;color:#26f">{{row.areaName || '请选择'}}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="num" label="群匹配" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<div>{{row.matchGroupCount}}/{{row.groupCount}}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column slot="options" label="操作" align="center" width="150px">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" @click="toGroup(row)">群匹配</el-button>
|
||||
<!-- <el-button type="text" @click="toAnnouce(row)">推送任务</el-button> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ai-table>
|
||||
</template>
|
||||
</ai-list>
|
||||
<ai-dialog
|
||||
title="监控群聊"
|
||||
:visible.sync="dialog"
|
||||
:destroyOnClose="true"
|
||||
width="720px"
|
||||
:customFooter="true">
|
||||
<ai-table :tableData="tableDataGroup" :isShowPagination="false"
|
||||
@getList="getTableDataGroup()" :col-configs="colConfigsGroup">
|
||||
<el-table-column slot="avatar" label="群聊头像" align="left">
|
||||
<template slot-scope="{ row }">
|
||||
<img :src="row.avatar ? row.avatar : 'https://cdn.cunwuyun.cn/dvcp/group-img.png'" alt="" class="group-avatar">
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ai-table>
|
||||
<div class="dialog-footer" slot="footer">
|
||||
<el-button @click="dialog=false">关闭</el-button>
|
||||
</div>
|
||||
</ai-dialog>
|
||||
<ai-dialog
|
||||
title="所属地区设置"
|
||||
:visible.sync="showArea"
|
||||
:destroyOnClose="true"
|
||||
width="720px"
|
||||
@onConfirm="areaConfirm"
|
||||
>
|
||||
<el-form class="ai-form" :model="areaInfo" label-width="120px" ref="form">
|
||||
<el-form-item prop="areaId" style="width: 100%;" label="所属地区" :rules="[{required: true, message: '请选择所属地区地区', trigger: 'change'}]">
|
||||
<ai-area-get style="width: 400px;" placeholder="所属地区" :instance="instance" v-model="areaInfo.areaId" :name.sync="areaInfo.areaName" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ai-dialog>
|
||||
</section>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
export default {
|
||||
name: "List",
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
search: {
|
||||
areaId: '',
|
||||
current: 1,
|
||||
size: 10,
|
||||
},
|
||||
tableData: [],
|
||||
total: 0,
|
||||
dialog: false,
|
||||
configList: [],
|
||||
configId: '',
|
||||
configInfo: {id: ''},
|
||||
groupInfo: {},
|
||||
searchGroup: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
},
|
||||
tableDataGroup: [],
|
||||
totalGroup: 0,
|
||||
showArea: false,
|
||||
areaInfo: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.dict.load('yesOrNo', 'deviceStatus', 'xbotType').then(() => {
|
||||
this.getConfigList()
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
colConfigs() {
|
||||
return [
|
||||
{ prop: "type", label: '类型', dict: 'xbotType', width: 150},
|
||||
{ slot: "avatar"},
|
||||
{ prop: "xbotName", label: '机位'},
|
||||
// { slot: "loginUserId", label: '用户id'},
|
||||
{ slot: "loginUserName"},
|
||||
{ prop: "phone", label: '手机号'},
|
||||
// { prop: "girdNames", label: '管辖区域'},
|
||||
{ slot: "groupCount", label: '监控群聊'},
|
||||
{ slot: "areaName"},
|
||||
{ slot: "num", label: '群匹配'},
|
||||
{ prop: "status", label: '状态', dict: 'deviceStatus', align: "center"},
|
||||
{ slot: "option"},
|
||||
]
|
||||
},
|
||||
colConfigsGroup() {
|
||||
return [
|
||||
{ slot: "avatar", label: '群聊头像'},
|
||||
{ prop: "nickname", label: '群聊名称', align: 'center'},
|
||||
{ prop: "is_manager", label: '是否管理员', dict: 'yesOrNo', align: 'center'},
|
||||
{ prop: "total", label: '群聊成员(人数)', align: 'center'},
|
||||
// { prop: "phone", label: '群主'},
|
||||
]
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getConfigList() {
|
||||
this.instance.post(`/app/appxbotconfig/list?size=1000`).then(res => {
|
||||
if(res?.data) {
|
||||
res.data.records.map((item) => {
|
||||
item.label = item.xbotName
|
||||
item.value = item.id
|
||||
})
|
||||
this.configList = res.data.records
|
||||
// this.configInfo = this.configList[0]
|
||||
this.getTableData()
|
||||
}
|
||||
})
|
||||
},
|
||||
configChange(e) {
|
||||
if(e) {
|
||||
this.configList.map((item) => {
|
||||
if(item.id == e) {
|
||||
this.configInfo = {...item}
|
||||
}
|
||||
})
|
||||
}else {
|
||||
this.configInfo = {id: ''}
|
||||
}
|
||||
this.search.current = 1
|
||||
this.getTableData()
|
||||
},
|
||||
openWechat() {
|
||||
this.instance.post(`/app/appxbotconfig/openWechat?id=${this.configInfo.id}`).then(res => {
|
||||
if(res.code === 0) {
|
||||
this.configInfo.status = 0
|
||||
this.$message.success('启动成功!')
|
||||
}
|
||||
})
|
||||
},
|
||||
getListInit() {
|
||||
this.search.current = 1
|
||||
this.getTableData()
|
||||
},
|
||||
getTableData() {
|
||||
this.instance.post(`/app/xbotCallback/list`,null,{
|
||||
params: {
|
||||
...this.search,
|
||||
xbotId: this.configInfo.id ? this.configInfo.id : ''
|
||||
}
|
||||
}).then(res => {
|
||||
if(res?.data) {
|
||||
this.tableData = res.data.records
|
||||
this.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
showDialog(row) {
|
||||
if(row.status == 1) {
|
||||
this.dialog = true
|
||||
this.groupInfo = row
|
||||
this.getTableDataGroup()
|
||||
}
|
||||
},
|
||||
changeArea(row) {
|
||||
this.showArea = true
|
||||
this.areaInfo = {...row}
|
||||
this.$refs['form'].clearValidate();
|
||||
},
|
||||
areaConfirm() {
|
||||
this.$refs['form'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.instance.post(`app/xbotCallback/updateArea?id=${this.areaInfo.id}&areaId=${this.areaInfo.areaId}&areaName=${this.areaInfo.areaName}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$message.success('所属地区设置成功')
|
||||
this.showArea = false
|
||||
this.getListInit()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
},
|
||||
getTableDataGroup() {
|
||||
this.tableDataGroup = []
|
||||
this.instance.post(`/app/xbotCallback/groupList`,null,{
|
||||
params: {
|
||||
// ...this.searchGroup,
|
||||
current: 1,
|
||||
managerWxid: this.groupInfo.loginUserId,
|
||||
xbotId: this.groupInfo.xbotId
|
||||
}
|
||||
}).then(res => {
|
||||
if(res?.data) {
|
||||
var tableDataGroup = JSON.parse(res.data)
|
||||
console.log(tableDataGroup)
|
||||
tableDataGroup.map((item) => { // 0企微 1个微
|
||||
if(this.groupInfo.type == 1 && item.total_member > 2) {
|
||||
this.tableDataGroup.push(item)
|
||||
}
|
||||
if(this.groupInfo.type == 0) {
|
||||
item.is_manager = item.is_admin
|
||||
this.tableDataGroup.push(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
toGroup(row) {
|
||||
this.$emit('change', {
|
||||
type: 'GroupList',
|
||||
params:row
|
||||
})
|
||||
},
|
||||
toAnnouce(row) {
|
||||
this.$emit('change', {
|
||||
type: 'AnnounceList',
|
||||
params: row,
|
||||
isRefresh: true
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.List {
|
||||
height: 100%;
|
||||
|
||||
.time-select {
|
||||
padding: 0 16px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border: 1px solid #d0d4dc;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
.el-icon-arrow-down {
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep .is-error {
|
||||
.time-select {
|
||||
border: 1px solid #f46!important;
|
||||
}
|
||||
}
|
||||
.tips {
|
||||
display: inline-block;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
.group-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
:deep .ai-dialog__content--wrapper {
|
||||
// height: 1000px;
|
||||
// overflow-y: scroll;
|
||||
.ai-table {
|
||||
// height: 1000px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
344
project/biaopin/AppSeatManagementXbot/components/Phone.vue
Normal file
344
project/biaopin/AppSeatManagementXbot/components/Phone.vue
Normal file
@@ -0,0 +1,344 @@
|
||||
<template>
|
||||
<div class="phone-container">
|
||||
<img class="close" @click="$emit('close')" v-if="isShowClose" src="https://cdn.cunwuyun.cn/dvcp/announce/close.png" />
|
||||
<img class="phone" src="https://cdn.cunwuyun.cn/dvcp/announce/phone.png" />
|
||||
<img class="phone-wrapper" src="https://cdn.cunwuyun.cn/dvcp/announce/phone-wrapper.png" />
|
||||
<div class="right-content">
|
||||
<div class="msg-list">
|
||||
<div class="msg-item" v-if="content">
|
||||
<div class="msg-item__left">
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/announce/avatar.png" />
|
||||
</div>
|
||||
<div class="msg-item__right">
|
||||
<div class="msg-wrapper msg-text">
|
||||
<p>{{ content }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg-item" v-for="item in fileList" :key="item.id">
|
||||
<div class="msg-item__left">
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/announce/avatar.png" />
|
||||
</div>
|
||||
<div class="msg-item__right" :class="[['1', '2'].indexOf(item.msgType) !== -1 ? 'left-border' : '']">
|
||||
<div class="msg-wrapper msg-img" v-if="item.msgType === '1'">
|
||||
<img :src="item.imgPicUrl" />
|
||||
</div>
|
||||
<div class="msg-wrapper msg-video" v-if="item.msgType === '2'">
|
||||
<video controls :src="item.url || item.fileUrl"></video>
|
||||
</div>
|
||||
<div class="msg-wrapper msg-file" v-if="item.msgType === '3'">
|
||||
<div class="msg-left">
|
||||
<h2>{{ item.name || item.title }}</h2>
|
||||
<p>{{ item.fileSizeStr }}</p>
|
||||
</div>
|
||||
<img :src="mapIcon(item.name || item.fileUrl)" />
|
||||
</div>
|
||||
<div class="msg-wrapper msg-link" v-if="item.msgType === '4'">
|
||||
<h2>{{ item.linkTitle }}</h2>
|
||||
<div class="msg-right">
|
||||
<p>{{ item.linkDesc }}</p>
|
||||
<img :src="item.linkPicUrl || 'https://cdn.cunwuyun.cn/dvcp/announce/html.png'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg-wrapper msg-miniapp" v-if="item.msgType === '5'">
|
||||
<h2>{{ item.mpTitle }}</h2>
|
||||
<img :src="item.url || item.pictureUrl" />
|
||||
<div class="msg-bottom">
|
||||
<i>小程序</i>
|
||||
<img src="https://cdn.cunwuyun.cn/dvcp/announce/miniapp.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['fileList', 'avatar', 'content', 'isShowClose'],
|
||||
|
||||
watch: {
|
||||
fileList (v) {
|
||||
if (v.length) {
|
||||
setTimeout(() => {
|
||||
document.querySelector('.right-content').scrollTo(0, 999999)
|
||||
}, 800)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
mapIcon (fileName) {
|
||||
if (['.zip', '.rar'].indexOf(this.getExtension(fileName)) !== -1) {
|
||||
return 'https://cdn.cunwuyun.cn/dvcp/announce/zip.png'
|
||||
}
|
||||
|
||||
if (['.doc', '.docx'].indexOf(this.getExtension(fileName)) !== -1) {
|
||||
return 'https://cdn.cunwuyun.cn/dvcp/announce/world.png'
|
||||
}
|
||||
|
||||
if (['.xls', '.xlsx'].indexOf(this.getExtension(fileName)) !== -1) {
|
||||
return 'https://cdn.cunwuyun.cn/dvcp/announce/xls.png'
|
||||
}
|
||||
|
||||
if (['.txt'].indexOf(this.getExtension(fileName)) !== -1) {
|
||||
return 'https://cdn.cunwuyun.cn/dvcp/announce/txt.png'
|
||||
}
|
||||
|
||||
if (['.pdf'].indexOf(this.getExtension(fileName)) !== -1) {
|
||||
return 'https://cdn.cunwuyun.cn/dvcp/announce/pdf.png'
|
||||
}
|
||||
|
||||
if (['.ppt', '.pptx'].indexOf(this.getExtension(fileName)) !== -1) {
|
||||
return 'https://cdn.cunwuyun.cn/dvcp/announce/ppt.png'
|
||||
}
|
||||
},
|
||||
|
||||
getExtension(name) {
|
||||
return name.substring(name.lastIndexOf('.'))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.phone-container {
|
||||
width: 338px;
|
||||
height: 675px;
|
||||
padding: 80px 15px 100px 32px;
|
||||
|
||||
.phone {
|
||||
position: absolute;
|
||||
left: 13px;
|
||||
top: 4px;
|
||||
z-index: 1;
|
||||
width: 314px;
|
||||
height: 647px;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 111;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
cursor: pointer;
|
||||
transition: all ease 0.5s;
|
||||
transform: translate(100%, -50%);
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.phone-wrapper {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
width: 338px;
|
||||
height: 675px;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
position: relative;
|
||||
z-index: 11;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.msg-item {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.msg-item__left {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
margin-right: 16px;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-item__right {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-right: 6px solid #fff;
|
||||
border-left: 6px solid transparent;
|
||||
border-bottom: 6px solid transparent;
|
||||
border-top: 6px solid transparent;
|
||||
content: " ";
|
||||
transform: translate(-100%, 0%);
|
||||
}
|
||||
|
||||
&.left-border::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.msg-img img {
|
||||
max-width: 206px;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.msg-video video {
|
||||
max-width: 206px;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.msg-text {
|
||||
max-width: 206px;
|
||||
width: max-content;
|
||||
line-height: 1.3;
|
||||
padding: 12px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 5px;
|
||||
word-break: break-all;
|
||||
font-size: 14px;
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
.msg-miniapp {
|
||||
width: 206px;
|
||||
padding: 0 12px;
|
||||
text-align: justify;
|
||||
font-size: 0;
|
||||
background: #FFFFFF;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
color: #222222;
|
||||
|
||||
h2 {
|
||||
line-height: 1.2;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
color: #222222;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
& > img {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.msg-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
padding: 4px 0;
|
||||
border-top: 1px solid #eee;
|
||||
|
||||
i {
|
||||
margin-right: 4px;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.msg-file {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 206px;
|
||||
padding: 12px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 5px;
|
||||
|
||||
.msg-left {
|
||||
flex: 1;
|
||||
margin-right: 18px;
|
||||
|
||||
h2 {
|
||||
display: -webkit-box;
|
||||
flex: 1;
|
||||
line-height: 16px;
|
||||
margin-bottom: 4px;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
color: #222222;
|
||||
font-size: 14px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #888888;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-link {
|
||||
width: 206px;
|
||||
padding: 12px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 5px;
|
||||
|
||||
h2 {
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: #222222;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.msg-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
display: -webkit-box;
|
||||
flex: 1;
|
||||
line-height: 16px;
|
||||
margin-right: 10px;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1090
project/biaopin/dv/weiyang/AppWeiyangDv.vue
Normal file
1090
project/biaopin/dv/weiyang/AppWeiyangDv.vue
Normal file
File diff suppressed because it is too large
Load Diff
95
project/biaopin/dv/weiyang/comps/chargingPercent.vue
Normal file
95
project/biaopin/dv/weiyang/comps/chargingPercent.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<script>
|
||||
import ValueUnit from "./valueUnit.vue";
|
||||
|
||||
export default {
|
||||
name: "chargingPercent",
|
||||
components: {ValueUnit},
|
||||
props: ["label", "value"],
|
||||
computed: {
|
||||
renderPosition() {
|
||||
const offset = Math.round(120 * this.value / 100)
|
||||
return {
|
||||
'--offset-percent': `${offset}px`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="chargingPercent">
|
||||
{{ label }}
|
||||
<value-unit :value="value" unit="%" color="#fff"/>
|
||||
<div class="wave mar-8" :style="renderPosition"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.chargingPercent {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
|
||||
&:before {
|
||||
display: block;
|
||||
content: " ";
|
||||
border: 3px solid #2CD4C8;
|
||||
border-radius: 50%;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 2px 2px 8px 0 #2CD4C833, -2px -2px 8px 0 #2CD4C833, 2px -2px 8px 0 #2CD4C833, -2px 2px 8px 0 #2CD4C833;
|
||||
}
|
||||
|
||||
.wave {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
z-index: -1;
|
||||
--offset-percent: 0;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 360px;
|
||||
height: 360px;
|
||||
left: 50%;
|
||||
bottom: calc(-620px + var(--offset-percent));
|
||||
background-color: rgba(255, 255, 255, .1);
|
||||
border-radius: 45%;
|
||||
transform: translate(-50%, -70%) rotate(0);
|
||||
animation: rotate 6s linear infinite;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border-radius: 47%;
|
||||
background: radial-gradient(circle at center, rgba(44, 212, 200, 0.9) 50%, #ffffff00 100%);
|
||||
transform: translate(-50%, -70%) rotate(0);
|
||||
animation: rotate 10s linear -5s infinite;
|
||||
z-index: 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
50% {
|
||||
transform: translate(-50%, -73%) rotate(180deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -70%) rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
40
project/biaopin/dv/weiyang/comps/iconSmallPanel.vue
Normal file
40
project/biaopin/dv/weiyang/comps/iconSmallPanel.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<script>
|
||||
import ValueUnit from "./valueUnit.vue";
|
||||
|
||||
export default {
|
||||
name: "iconSmallPanel",
|
||||
components: {ValueUnit},
|
||||
props: ["icon", "label", "value", "unit", "vertical"]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="iconSmallPanel" :class="{vertical}">
|
||||
<img v-if="icon" class="icon" :src="icon">
|
||||
<div class="fill" v-text="label"/>
|
||||
<value-unit :value="value" :unit="unit"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.iconSmallPanel {
|
||||
font-weight: 400;
|
||||
font-size: 15px;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 13px;
|
||||
|
||||
&.vertical {
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
60
project/biaopin/dv/weiyang/comps/iconStaPanel.vue
Normal file
60
project/biaopin/dv/weiyang/comps/iconStaPanel.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script>
|
||||
import ValueUnit from "./valueUnit.vue";
|
||||
|
||||
export default {
|
||||
name: "iconStaPanel",
|
||||
components: {ValueUnit},
|
||||
props: ["icon", "label", "value", "unit"],
|
||||
computed: {
|
||||
valueSize: v => v.$attrs.size ?? 'large'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="iconStaPanel" :class="[$attrs.size]">
|
||||
<div class="block">
|
||||
<img class="icon" :src="icon">
|
||||
<div v-text="label"/>
|
||||
<value-unit :value="value" :unit="unit" :size="valueSize"/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.iconStaPanel {
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
color: #FFFFFF;
|
||||
|
||||
.block {
|
||||
padding-left: 96px;
|
||||
position: relative;
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: -16px;
|
||||
left: -32px;
|
||||
width: 144px;
|
||||
}
|
||||
}
|
||||
|
||||
&.small {
|
||||
font-size: 14px;
|
||||
|
||||
.block {
|
||||
padding-left: 90px;
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 16px;
|
||||
transform: translateY(-50%);
|
||||
width: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
62
project/biaopin/dv/weiyang/comps/navTabs.vue
Normal file
62
project/biaopin/dv/weiyang/comps/navTabs.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<script>
|
||||
export default {
|
||||
name: "navTabs",
|
||||
props: {
|
||||
list: {default: []}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick(e, i) {
|
||||
this.active = i
|
||||
this.$emit("click", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="navTabs">
|
||||
<div class="item" v-for="(e, i) in list" :key="i" v-text="e.label" @click="handleClick(e,i)" :class="{active:i==active}"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.navTabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: #01FFFF;
|
||||
gap: 4px;
|
||||
|
||||
.item {
|
||||
opacity: .7;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
|
||||
&:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
transform: translate(-50%, 8px);
|
||||
background: #01FFFF;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
69
project/biaopin/dv/weiyang/comps/subHeader.vue
Normal file
69
project/biaopin/dv/weiyang/comps/subHeader.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<section class="subHeader">
|
||||
<div class="subHeader-title" :class="{right:!!$scopedSlots.right}">
|
||||
<h2 class="fill" v-text="title"/>
|
||||
<slot name="right"/>
|
||||
</div>
|
||||
<div class="divider"/>
|
||||
<div class="slot">
|
||||
<slot/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'subHeader',
|
||||
props: ['title']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.subHeader {
|
||||
//background: #7583900f;
|
||||
|
||||
.subHeader-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 51px;
|
||||
padding: 0 8px 0 46px;
|
||||
box-sizing: border-box;
|
||||
background-image: linear-gradient(90deg, #00d4ff00 0%, rgba(0, 212, 255, 0.3) 12%, #0080ff00 100%), url("https://cdn.sinoecare.com/i/2024/09/03/66d6d5b006572.png");
|
||||
background-size: 100% 6px, 54px 42px;
|
||||
background-position: center bottom, left center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&.right {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 7px;
|
||||
background-image: linear-gradient(110deg, transparent 0% 14%, #1F979A 15% 29%, transparent 30% 44%, #1F979ABF 45% 59%, transparent 60% 74%, #1F979A82 75% 89%, transparent 90% 100%),
|
||||
linear-gradient(90deg, #243132 80%, #02FEFF 99%);
|
||||
background-size: 21px 7px, 100% 3px;
|
||||
background-position: calc(100% - 92px) top, left center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.slot {
|
||||
height: calc(100% - 60px);
|
||||
padding-top: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
44
project/biaopin/dv/weiyang/comps/valueUnit.vue
Normal file
44
project/biaopin/dv/weiyang/comps/valueUnit.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script>
|
||||
export default {
|
||||
name: "valueUnit",
|
||||
props: ["value", "unit"]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="valueUnit" :class="[$attrs.size]" :style="{color:$attrs.color}">
|
||||
{{ value || 0 }}<p v-text="unit"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.valueUnit {
|
||||
font-family: DINAlternate,serif;
|
||||
font-weight: 700;
|
||||
font-size: 22px;
|
||||
color: #02FEFF;
|
||||
display: flex;
|
||||
letter-spacing: 0;
|
||||
line-height: 36px;
|
||||
align-items: baseline;
|
||||
|
||||
& > p {
|
||||
margin-left: 2px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&.mini {
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
|
||||
& > p {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
19
project/biaopin/dv/weiyang/weiyang.json
Normal file
19
project/biaopin/dv/weiyang/weiyang.json
Normal file
File diff suppressed because one or more lines are too long
60
ui/lib/cdn/turf.min.js
vendored
Normal file
60
ui/lib/cdn/turf.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -23,7 +23,7 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
}
|
||||
}
|
||||
|
||||
@each $v in (8, 10, 12, 14, 16, 20, 24, 27, 32, 48, 56, 64, 80) {
|
||||
@each $v in (6, 8, 10, 12, 14, 16, 20, 24, 27, 32, 40, 48, 56, 64, 80) {
|
||||
//gap
|
||||
.gap-#{$v} {
|
||||
gap: #{$v}px !important;
|
||||
@@ -668,7 +668,11 @@ h1, h2, h3, p {
|
||||
}
|
||||
|
||||
#ai-waiting {
|
||||
color: $primaryColor
|
||||
color: $primaryColor;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.color-primary {
|
||||
|
||||
@@ -4,6 +4,8 @@ import ThinkingBar from "./components/thinkingBar.vue";
|
||||
import {mapState} from "vuex";
|
||||
import AiDrag from "../basic/AiDrag.vue";
|
||||
import AiLocateDialog from "../tools/AiLocateDialog.vue";
|
||||
import AiUploader from "../basic/AiUploader.vue";
|
||||
import {$checkJson} from "../../lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "AiCopilot",
|
||||
@@ -24,24 +26,19 @@ export default {
|
||||
currentConversation: null,
|
||||
app: {},
|
||||
locate: false,
|
||||
latlng: ""
|
||||
latlng: "",
|
||||
files: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(["user"]),
|
||||
profile: v => v.user.info || {},
|
||||
expandBtn: v => v.expand ? "收起" : "展开",
|
||||
isNeedPosition: v => ["1"].includes(v.app.ability),
|
||||
btns: v => [
|
||||
{
|
||||
label: "文件", icon: "https://cdn.sinoecare.com/i/2024/07/04/668663436e46e.png", click: () => {
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "位置", icon: "https://cdn.sinoecare.com/i/2024/08/19/66c2f907bd444.png", click: () => {
|
||||
v.locate = true
|
||||
}
|
||||
}
|
||||
],
|
||||
{label: "文件", icon: "https://cdn.sinoecare.com/i/2024/07/04/668663436e46e.png", click: v.handleUpload},
|
||||
{label: "位置", icon: "https://cdn.sinoecare.com/i/2024/08/19/66c2f907bd444.png", hide: !v.isNeedPosition, click: () => v.locate = true}
|
||||
].filter(e => e.hide !== true),
|
||||
rowBtns: v => [
|
||||
{icon: "https://cdn.sinoecare.com/i/2024/07/04/66866edc2910a.png", label: "置顶", click: row => 0},
|
||||
{icon: "https://cdn.sinoecare.com/i/2024/07/04/66866ed734540.png", label: "编辑", click: row => 0},
|
||||
@@ -49,7 +46,7 @@ export default {
|
||||
],
|
||||
dialogWidth: v => v.expand ? 960 : 468
|
||||
},
|
||||
components: {AiLocateDialog, AiDrag, ThinkingBar, ChatContent},
|
||||
components: {AiUploader, AiLocateDialog, AiDrag, ThinkingBar, ChatContent},
|
||||
methods: {
|
||||
getHistory(params) {
|
||||
this.http.post("/app/appaicopilotinfo/list", null, {params}).then(res => {
|
||||
@@ -85,23 +82,15 @@ export default {
|
||||
},
|
||||
handleSend() {
|
||||
if (!this.prompt.trim()) return this.$message.error("无法发送空白信息")
|
||||
const concatenateStr = (content, i = 0, target = this.history.at(-1)) => {
|
||||
target.content += content.slice(i, i + 1)
|
||||
if (++i < content.length) setTimeout(() => concatenateStr(content, i, target), 50)
|
||||
}
|
||||
else if (this.isNeedPosition && !this.latlng) return this.$message.error("请先选择您所在的位置")
|
||||
this.$debounce(() => {
|
||||
const {currentConversation: conversationId, app, prompt: content, latlng} = this.$data
|
||||
const message = {appType: "2", userType: 0, content, conversationId, ...app, supplementContent: latlng}
|
||||
const {currentConversation: conversationId, app, prompt: content, latlng, files} = this.$data
|
||||
const {id: fileId, url: sdkFileUrl, name: fileName} = files[0] || {}
|
||||
const message = {appType: "2", userType: 0, content, conversationId, ...app, supplementContent: latlng, fileId, sdkFileUrl, fileName}
|
||||
this.history.push(message)
|
||||
this.loading = true
|
||||
this.prompt = ""
|
||||
this.http.post("/app/appaicopilotinfo/add", message).then(res => {
|
||||
if (res?.data?.length >= 2) {
|
||||
const last = res.data.at(-1)
|
||||
this.history.push({...last, content: ""})
|
||||
concatenateStr(last.content)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.clearCache()
|
||||
this.sendMessage(message).finally(() => {
|
||||
this.loading = false
|
||||
this.getConversations()
|
||||
})
|
||||
@@ -118,13 +107,14 @@ export default {
|
||||
}).catch(() => 0)
|
||||
},
|
||||
handleChangeApp(item) {
|
||||
const {appId, id: aiConfigId} = item
|
||||
this.handleChangeConversation({appId, aiConfigId})
|
||||
const {appId, id: aiConfigId, ability} = item
|
||||
this.handleChangeConversation({appId, aiConfigId, ability})
|
||||
this.history = [{userType: 2, content: `当前应用已切换至【${item.appName}】`}]
|
||||
this.clearCache()
|
||||
},
|
||||
handleChangeConversation({conversationId, appId, aiConfigId} = {}) {
|
||||
handleChangeConversation({conversationId, appId, aiConfigId, ability} = {}) {
|
||||
this.currentConversation = conversationId
|
||||
this.app = {appId, aiConfigId}
|
||||
this.app = {appId, aiConfigId, ability}
|
||||
},
|
||||
getIcon(item = {}) {
|
||||
const icon = item.appIconUrl || "https://cdn.sinoecare.com/i/2024/07/04/66864da1684ad.png"
|
||||
@@ -136,26 +126,96 @@ export default {
|
||||
const {lat, lng} = v.location
|
||||
this.latlng = [lat, lng].join(",")
|
||||
this.locate = false
|
||||
},
|
||||
clearCache() {
|
||||
//清理发送消息缓存
|
||||
this.prompt = ""
|
||||
this.files = []
|
||||
this.$refs.uploader?.handleClear()
|
||||
},
|
||||
handleUpload() {
|
||||
if (this.$refs.uploader.checkUpload()) this.$refs.uploadTrigger.click()
|
||||
else this.$message.error("最多上传一个文件")
|
||||
},
|
||||
sendMessage(message, isSSE = false) {
|
||||
if (isSSE || localStorage.getItem("isSSE")) {
|
||||
const {content, sdkFileUrl, appId} = message
|
||||
const body = {
|
||||
inputs: {},
|
||||
query: content,
|
||||
response_mode: "streaming",
|
||||
user: "kubbo",
|
||||
}
|
||||
if (sdkFileUrl) body.files = [{type: 'image', url: sdkFileUrl, transfer_method: "remote_url"}]
|
||||
return new Promise(resolve => this.http.post("/sse/chat-messages", body, {
|
||||
withoutToken: 1, headers: {
|
||||
Accept: 'text/event-stream',
|
||||
Authorization: `Bearer ${appId}`
|
||||
},
|
||||
onDownloadProgress: evt => {
|
||||
evt.target.responseText.split(/(data|event):/).forEach(data => {
|
||||
if ($checkJson(data.trim())) {
|
||||
const res = JSON.parse(data.trim())
|
||||
if (this.history.at(-1).userType != 1) {
|
||||
this.history.push({userType: 1, content: "", workflow: []})
|
||||
}
|
||||
const last = this.history.at(-1)
|
||||
if (res.event == "node_started") {
|
||||
last.workflow = ["el-icon-loading", res.data.title]
|
||||
} else if (res.event == "node_finished") {
|
||||
last.workflow = ["el-icon-finished", res.data.title]
|
||||
}
|
||||
const chunk = res.answer?.trim()
|
||||
if (!!chunk) {
|
||||
if (!last.content?.includes(chunk)) last.content += chunk
|
||||
}
|
||||
if (res.event == "message_end") {
|
||||
last.workflow = ""
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
const concatenateStr = (content, i = 0, target = this.history.at(-1)) => {
|
||||
target.content += content.slice(i, i + 1)
|
||||
if (++i < content.length) setTimeout(() => concatenateStr(content, i, target), 50)
|
||||
}
|
||||
return this.http.post("/app/appaicopilotinfo/add", message).then(res => {
|
||||
if (res?.data?.length >= 2) {
|
||||
const last = res.data.at(-1)
|
||||
this.history.push({...last, content: ""})
|
||||
return concatenateStr(last.content)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentConversation(v) {
|
||||
v && this.getHistory({conversationId: v})
|
||||
},
|
||||
loading(v) {
|
||||
!v && this.$nextTick(() => this.$refs.sendInput.focus())
|
||||
},
|
||||
dialogWidth(v) {
|
||||
const dom = this.$el.querySelector(".vdr")
|
||||
dom.style.minWidth = `${v}px`
|
||||
}
|
||||
},
|
||||
created() {
|
||||
Promise.all([this.getApps(), this.getConversations()]).then(() => {
|
||||
const {appId, id: aiConfigId} = this.apps.at(0)
|
||||
this.handleChangeConversation(this.conversations.at(0) || {appId, aiConfigId})
|
||||
const {appId, id: aiConfigId, ability} = this.apps.at(0)
|
||||
this.handleChangeConversation(this.conversations.at(0) || {appId, aiConfigId, ability})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="AiCopilot">
|
||||
<ai-drag class="copilot" v-if="show" :w="dialogWidth" :h="600" :minHeight="600" :minWidth="dialogWidth" :x="-dialogWidth-12" :y="-542">
|
||||
<ai-drag class="copilot" v-if="show" :w="dialogWidth" :h="600" :minHeight="600" :minWidth="dialogWidth" :x="-dialogWidth-12" :y="-542" dragHandle=".header">
|
||||
<div class="flex header">
|
||||
<b class="fill" v-text="title"/>
|
||||
<div class="expandBtn pointer" v-text="expandBtn" @click="expand=!expand"/>
|
||||
@@ -163,24 +223,26 @@ export default {
|
||||
</div>
|
||||
<thinking-bar v-show="loading"/>
|
||||
<div class="flex content">
|
||||
<div class="left" :class="{expand}" v-loading="loading" element-loading-text="小助手正在思考中.."
|
||||
<div class="left" :class="{expand}" v-loading="loading" element-loading-text="小助理正在思考中.."
|
||||
element-loading-spinner="el-icon-loading" element-loading-background="rgba(255,255,255,0.8)">
|
||||
<div class="profile">
|
||||
<div v-text="profile.name"/>
|
||||
<span v-text="profile.girdName"/>
|
||||
</div>
|
||||
<div class="apps">
|
||||
<div v-for="(item,i) in apps" :key="i" class="app pointer" :class="{current:item.id==app.aiConfigId}" :style="getIcon(item)" v-text="item.appName" @click="handleChangeApp(item)"/>
|
||||
</div>
|
||||
<div class="conversation">
|
||||
<el-input class="search" v-model="filter" placeholder="搜索历史对话记录" size="small" suffix-icon="el-icon-search" clearable @change="getConversations"/>
|
||||
<div class="item pointer" v-for="item in conversations" :key="item.id" @click="currentConversation=item.conversationId" :class="{current:item.conversationId==currentConversation}">
|
||||
{{ item.content }}
|
||||
<div class="operation flex">
|
||||
<div v-for="(btn,i) in rowBtns" :key="i" class="pointer" :style="{backgroundImage: `url(${btn.icon})`}" @click.stop="btn.click(item)"/>
|
||||
<el-scrollbar>
|
||||
<div class="apps">
|
||||
<div v-for="(item,i) in apps" :key="i" class="app pointer" :class="{current:item.id==app.aiConfigId}" :style="getIcon(item)" v-text="item.appName" @click="handleChangeApp(item)"/>
|
||||
</div>
|
||||
<div class="conversation">
|
||||
<el-input class="search" v-model="filter" placeholder="搜索历史对话记录" size="small" suffix-icon="el-icon-search" clearable @change="getConversations"/>
|
||||
<div class="item pointer" v-for="item in conversations" :key="item.id" @click="currentConversation=item.conversationId" :class="{current:item.conversationId==currentConversation}">
|
||||
{{ item.content }}
|
||||
<div class="operation flex">
|
||||
<div v-for="(btn,i) in rowBtns" :key="i" class="pointer" :style="{backgroundImage: `url(${btn.icon})`}" @click.stop="btn.click(item)"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<div class="right flex column gap-14">
|
||||
<chat-content class="fill" :list="history"/>
|
||||
@@ -188,17 +250,20 @@ export default {
|
||||
<div class="topBar flex">
|
||||
<div v-for="(btn,i) in btns" :key="i" class="btn pointer" :style="{backgroundImage: `url(${btn.icon})`}" v-text="btn.label" @click="btn.click"/>
|
||||
</div>
|
||||
<ai-uploader ref="uploader" v-model="files" :instance="http" :limit="1" show-loading fileType="file" accept-type=".doc,.docx,.pdf,.txt">
|
||||
<div slot="trigger" ref="uploadTrigger"/>
|
||||
</ai-uploader>
|
||||
<div class="flex end">
|
||||
<el-input type="textarea" class="fill input" autosize resize="none" v-model="prompt" placeholder="请输入..." :rows="5"
|
||||
@keydown.native="handleHotkey" :disabled="loading" :placeholder="loading?'正在思考中...':'请输入'"/>
|
||||
@keydown.native="handleHotkey" :disabled="loading" :placeholder="loading?'正在思考中...':'请输入'" ref="sendInput"/>
|
||||
<div class="sendBtn" @click="handleSend"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ai-drag>
|
||||
<ai-locate-dialog v-model="locate" :ins="http" @confirm="v=>handleLocate(v)" :modal="false"/>
|
||||
<img class="icon" src="https://cdn.sinoecare.com/i/2024/06/04/665ec6f5ef213.png" @click="show=!show"/>
|
||||
<ai-locate-dialog dialogTitle="选择当前位置" v-model="locate" :ins="http" @confirm="v=>handleLocate(v)" :modal="false"/>
|
||||
<img class="icon" src="https://cdn.sinoecare.com/i/2024/08/22/66c6dfdc3766a.png" @click="show=!show"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -216,9 +281,11 @@ export default {
|
||||
:deep(.vdr) {
|
||||
border-radius: 0 0 8px 8px;
|
||||
height: 600px;
|
||||
background: #FFFFFF;
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0 0 20px 1px #0a255c1a;
|
||||
border: none;
|
||||
background-image: url("https://cdn.sinoecare.com/i/2024/08/22/66c6f2d71b185.png");
|
||||
background-size: 100% 100%;
|
||||
|
||||
.handle {
|
||||
opacity: 0;
|
||||
@@ -266,6 +333,7 @@ export default {
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
height: calc(100% - 50px);
|
||||
float: right;
|
||||
|
||||
@@ -273,8 +341,7 @@ export default {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
padding: 14px 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
& > * {
|
||||
@@ -293,7 +360,7 @@ export default {
|
||||
.profile {
|
||||
padding: 18px 14px;
|
||||
height: 88px;
|
||||
background: url("https://cdn.sinoecare.com/i/2024/06/04/665ed2bc580fa.png") no-repeat;
|
||||
background: url("https://cdn.sinoecare.com/i/2024/08/22/66c6e78fb918c.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
font-size: 14px;
|
||||
color: #222222;
|
||||
@@ -304,6 +371,9 @@ export default {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
background: linear-gradient(180deg, #F57A00 0%, #FFAA00 100%);
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +436,7 @@ export default {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
margin-bottom: 2px;
|
||||
|
||||
.operation {
|
||||
position: absolute;
|
||||
@@ -399,6 +470,14 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-scrollbar) {
|
||||
height: calc(100% - 88px);
|
||||
|
||||
.el-scrollbar__wrap {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
@@ -450,6 +529,7 @@ export default {
|
||||
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
color: #0052F5;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -463,6 +543,43 @@ export default {
|
||||
background: transparent;
|
||||
min-height: 73px !important;
|
||||
}
|
||||
|
||||
.el-upload {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-upload-list {
|
||||
display: flex;
|
||||
|
||||
li {
|
||||
width: auto;
|
||||
padding: 6px 12px;
|
||||
background: #0152f50f;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
line-height: normal;
|
||||
margin-top: 0;
|
||||
|
||||
.el-upload-list__item-name {
|
||||
margin-right: 0;
|
||||
padding-left: 0;
|
||||
color: #525F7A;
|
||||
}
|
||||
|
||||
.el-icon-close {
|
||||
right: 0;
|
||||
top: 0;
|
||||
transform: translate(2px, -2px);
|
||||
background-color: #A4ADC2;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
|
||||
&:hover {
|
||||
background-color: #2970FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,14 @@
|
||||
<div class="chat-wrapper" v-for="item in list" :key="item.id">
|
||||
<div class="chat-text" :class="{right:item.userType == '0',system:item.userType == '2'}">
|
||||
<img v-if="item.userType!=2" class="avatar" :src="avatar(item)" alt=""/>
|
||||
<div class="content" v-html="markdown(item.content)"/>
|
||||
<div style="max-width: max(calc(100% - 140px), 220px)">
|
||||
<div class="content" v-html="markdown(item.content)"/>
|
||||
<div v-if="item.workflow&&item.workflow.length>0" class="process flex">
|
||||
<i :class="item.workflow[0]"/>
|
||||
<div v-html="item.workflow[1]"/>
|
||||
</div>
|
||||
<div class="attachments pointer el-icon-paperclip" v-if="item.fileName" v-text="item.fileName" @click="handleDownload(item)"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
@@ -41,15 +48,19 @@ export default {
|
||||
},
|
||||
avatar(item) {
|
||||
return item.avatar || (item.userType == '0' ? 'https://cdn.sinoecare.com/i/2024/06/17/666fdb275be82.png' :
|
||||
'https://cdn.sinoecare.com/i/2024/06/04/665ec6f5ef213.png')
|
||||
'https://cdn.sinoecare.com/i/2024/08/22/66c6dfdc3766a.png')
|
||||
},
|
||||
markdown(v) {
|
||||
return this.md?.render(v) || v
|
||||
return v ? this.md?.render(v) : v
|
||||
},
|
||||
handleDownload(item) {
|
||||
window.open(item.sdkFileUrl)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const {hljs, markdownit} = window
|
||||
this.md = markdownit({
|
||||
breaks: true, linkify: true, html: true,
|
||||
highlight: function (str, lang) {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
@@ -69,6 +80,10 @@ export default {
|
||||
.chatContent {
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
overflow-x: hidden;
|
||||
background-image: url("https://cdn.sinoecare.com/i/2024/08/22/66c6dfdd76407.png");
|
||||
background-position: center center;
|
||||
background-size: 153px 182px;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.content {
|
||||
pre {
|
||||
@@ -96,7 +111,6 @@ export default {
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
max-width: max(calc(100% - 140px), 220px);
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: #F3F5F7;
|
||||
@@ -147,6 +161,44 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attachments {
|
||||
font-size: 12px;
|
||||
background: #F0F0F0;
|
||||
border-radius: 4px;
|
||||
color: #888;
|
||||
padding: 3px 6px;
|
||||
width: fit-content;
|
||||
margin-top: 4px;
|
||||
|
||||
&:hover, &:active {
|
||||
color: $primaryColor;
|
||||
}
|
||||
|
||||
&:before {
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.process) {
|
||||
font-size: 12px;
|
||||
gap: 4px;
|
||||
|
||||
& > i {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
&:before {
|
||||
font-size: 16px;
|
||||
color: $primaryColor;
|
||||
|
||||
&.el-icon-finished {
|
||||
color: $successColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
|
||||
@@ -86,7 +86,6 @@
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {VueCropper} from 'vue-cropper'
|
||||
|
||||
@@ -290,7 +289,6 @@ export default {
|
||||
getExtension(name) {
|
||||
return name.substring(name.lastIndexOf('.'))
|
||||
},
|
||||
|
||||
onOverSize(e) {
|
||||
const isLt10M = e.size / 1024 / 1024 < this.maxSize
|
||||
const suffixName = this.getExtension(e.name)
|
||||
@@ -308,22 +306,19 @@ export default {
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
onBeforeUpload(event) {
|
||||
return this.onOverSize(event)
|
||||
},
|
||||
|
||||
onClose() {
|
||||
this.fileList = []
|
||||
this.dialog = false
|
||||
},
|
||||
|
||||
submitUpload(file) {
|
||||
let formData = new FormData()
|
||||
formData.append('file', file.file)
|
||||
let loading
|
||||
if (this.showLoading) {
|
||||
loading = this.$loading({lock: true, text: '上传中...', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)'})
|
||||
loading = this.$loading({lock: true, text: '上传中...', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)', customClass: 'uploader-loading'})
|
||||
}
|
||||
this.instance.post(this.url, formData, {
|
||||
withCredentials: false
|
||||
@@ -403,10 +398,17 @@ export default {
|
||||
parent.$emit.apply(parent, [eventName].concat(params));
|
||||
}
|
||||
},
|
||||
checkUpload() {
|
||||
return this.limit > this.fileList.length
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.uploader-loading {
|
||||
z-index: 202408221749;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.uploader {
|
||||
line-height: 1;
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
export default {
|
||||
name: "AiEchart",
|
||||
props: {
|
||||
@@ -18,7 +16,7 @@ export default {
|
||||
series: Object,
|
||||
theme: {
|
||||
default: '0'
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
renderComponent: {
|
||||
@@ -34,7 +32,6 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
@@ -46,10 +43,8 @@ export default {
|
||||
if (this.theme === '0') {
|
||||
return ['#2896FF', '#09DBFE', '#61FDB9', '#FFBB69', '#8429FF', '#ea7ccc']
|
||||
}
|
||||
|
||||
return ['#D4380D', '#CF1322', '#D55800', '#FA8C16', '#FFC53D', '#FFA940', '#FFC53D', '#780000']
|
||||
return this.color || ['#D4380D', '#CF1322', '#D55800', '#FA8C16', '#FFC53D', '#FFA940', '#FFC53D', '#780000']
|
||||
},
|
||||
|
||||
chartOptions() {
|
||||
let {type, data, ops: options = {}} = this,
|
||||
style = this.series ? this.series : this.ops.daemon ? this.ops.daemon : {},
|
||||
@@ -83,8 +78,6 @@ export default {
|
||||
},
|
||||
axisLabel: {color: '#fff'}
|
||||
},
|
||||
legend, series, ...options,
|
||||
color: colors,
|
||||
grid: {
|
||||
left: '0%',
|
||||
right: '0%',
|
||||
@@ -92,6 +85,8 @@ export default {
|
||||
top: '40px',
|
||||
containLabel: true
|
||||
},
|
||||
legend, series, ...options,
|
||||
color: colors,
|
||||
}
|
||||
if (JSON.stringify(this.ops) != JSON.stringify(ops)) this.$emit("update:ops", ops)
|
||||
return ops
|
||||
@@ -112,7 +107,6 @@ export default {
|
||||
this.getChartData(oldDims != current)
|
||||
}
|
||||
},
|
||||
|
||||
theme() {
|
||||
this.refresh()
|
||||
}
|
||||
@@ -133,22 +127,21 @@ export default {
|
||||
}
|
||||
}, true)
|
||||
}
|
||||
this.resize()
|
||||
},
|
||||
initChart() {
|
||||
this.chart = echarts.init(this.$refs.AiEchart)
|
||||
this.chart.setOption(this.chartOptions || {})
|
||||
},
|
||||
resize() {
|
||||
const {clientHeight, clientWidth} = this.$refs.AiEchart || {},
|
||||
h = this.chart?.getHeight(), w = this.chart?.getWidth()
|
||||
if (h != clientHeight || w != clientWidth) {
|
||||
this.chart?.resize()
|
||||
}
|
||||
},
|
||||
watchResize() {
|
||||
this.timer && clearInterval(this.timer)
|
||||
this.timer = setInterval(() => {
|
||||
const {clientHeight, clientWidth} = this.$refs.AiEchart || {},
|
||||
h = this.chart?.getHeight(), w = this.chart?.getWidth()
|
||||
if (h != clientHeight || w != clientWidth) {
|
||||
this.chart?.resize()
|
||||
}
|
||||
}, 1000)
|
||||
//5分钟后停止监听
|
||||
setTimeout(() => this.timer && clearInterval(this.timer), 5 * 60 * 1000)
|
||||
window.onresize = this.resize
|
||||
},
|
||||
refresh() {
|
||||
this.chart.setOption(this.chartOptions || {})
|
||||
@@ -160,9 +153,6 @@ export default {
|
||||
this.initChart()
|
||||
this.getChartData()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.timer && clearInterval(this.timer)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
157
ui/packages/tools/AiEchartMap.vue
Normal file
157
ui/packages/tools/AiEchartMap.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div class="AiEchartMap" v-resize="onDomResize">
|
||||
<div class="chart-map" ref="dvMap"/>
|
||||
<slot v-if="$slots.default"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import http from "dui/lib/js/request";
|
||||
import {mapState} from "vuex";
|
||||
import "../../lib/cdn/turf.min.js";
|
||||
|
||||
const assign = (target, source = {}) => {
|
||||
Object.entries(source).forEach(([key, value]) => {
|
||||
if (typeof value === 'object') {
|
||||
if (target[key] && typeof target[key] === 'object') {
|
||||
assign(target[key], value)
|
||||
} else {
|
||||
target[key] = value
|
||||
}
|
||||
} else {
|
||||
target[key] = value
|
||||
}
|
||||
})
|
||||
}
|
||||
export default {
|
||||
name: 'AiEchartMap',
|
||||
props: {
|
||||
geoJson: {type: [String, Object]},
|
||||
data: Array,
|
||||
ops: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
geo: null,
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
directives: {
|
||||
resize: {
|
||||
bind(el, binding) {
|
||||
let width = ''
|
||||
let height = ''
|
||||
|
||||
function isResize() {
|
||||
const style = document.defaultView.getComputedStyle(el)
|
||||
if (width !== style.width || height !== style.height) {
|
||||
binding.value({
|
||||
width: style.width,
|
||||
height: style.height
|
||||
})
|
||||
}
|
||||
width = style.width
|
||||
height = style.height
|
||||
}
|
||||
|
||||
el.__vueSetInterval__ = setInterval(isResize, 300)
|
||||
},
|
||||
unbind(el) {
|
||||
clearInterval(el.__vueSetInterval__)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user'])
|
||||
},
|
||||
mounted() {
|
||||
this.$load(this.$refs.dvMap).then(() => this.initChart())
|
||||
},
|
||||
methods: {
|
||||
onDomResize() {
|
||||
this.$nextTick(() => {
|
||||
this.chart.resize()
|
||||
})
|
||||
},
|
||||
loadLibs() {
|
||||
let getGeo
|
||||
if (typeof this.geoJson === 'string') {
|
||||
getGeo = this.getData(this.geoJson).then(res => this.geo = res?.data)
|
||||
} else if (typeof this.geoJson === 'object') {
|
||||
getGeo = Promise.resolve(this.geo = this.$copy(this.geoJson))
|
||||
}
|
||||
return Promise.all([getGeo])
|
||||
},
|
||||
handleMapOps(geoJson, ops, stopInit = false) {
|
||||
if (stopInit) {
|
||||
clearTimeout(this.timer)
|
||||
this.chart?.hideLoading()
|
||||
}
|
||||
const {echarts, turf} = window
|
||||
const boundary = geoJson.features?.length > 1 ? turf.union(geoJson) : this.$copy(geoJson.features[0])
|
||||
boundary.properties = {...boundary.properties}
|
||||
boundary.properties.name = "boundary"
|
||||
geoJson.features.unshift(boundary)
|
||||
echarts.registerMap('customMap', geoJson)
|
||||
const option = {
|
||||
geo: {
|
||||
map: 'customMap', roam: true, emphasis: {disabled: true},
|
||||
itemStyle: {areaColor: "transparent", borderColor: '#97CAE6'},
|
||||
silent: true,
|
||||
label: {show: true, color: '#fff'},
|
||||
regions: [
|
||||
{
|
||||
name: "boundary", itemStyle: {
|
||||
areaColor: "rgba(2,188,255,0.16)", shadowColor: "#02F0FF21", shadowOffsetY: 20, shadowBlur: 20,
|
||||
borderColor: '#02F0FF', borderWidth: 3
|
||||
}, label: {show: false}
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
assign(option, ops)
|
||||
return option
|
||||
},
|
||||
initChart() {
|
||||
const {echarts} = window
|
||||
this.chart = echarts.init(this.$refs.dvMap)
|
||||
this.chart.on('finished', () => {
|
||||
console.log(this.chart.getOption())
|
||||
})
|
||||
this.chart.showLoading({
|
||||
text: "数据加载中...", textColor: "#fff",
|
||||
maskColor: 'rgba(0, 0, 0, 0.2)'
|
||||
})
|
||||
this.$emit('map', this.chart)
|
||||
this.timer = setTimeout(() => {
|
||||
this.loadLibs().then(() => {
|
||||
this.chart.setOption(this.handleMapOps(this.geo, this.ops))
|
||||
}).finally(() => {
|
||||
this.chart.hideLoading()
|
||||
})
|
||||
}, 1000)
|
||||
},
|
||||
getData(url = `https://geo.datav.aliyun.com/areas_v3/bound/geojson?code=${this.user.info.areaId?.substring(0, 6)}`) {
|
||||
return http.post(`/app/appdvcpconfig/apiForward?url=${encodeURIComponent(url)}`)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiEchartMap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
.chart-map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<section class="AiLocateDialog">
|
||||
<ai-dialog :visible.sync="dialog" title="标绘" @closed="$emit('visible',false),selected={}"
|
||||
<ai-dialog :visible.sync="dialog" :title="$attrs.dialogTitle||'标绘'" @closed="$emit('visible',false),selected={}"
|
||||
@opened="$nextTick(()=>initMap())"
|
||||
@onConfirm="handleConfirm" v-bind="$attrs">
|
||||
<div id="amap" v-if="dialog"/>
|
||||
<div id="amap" v-if="dialog" v-loading="searching" element-loading-text="搜索中.." element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)"/>
|
||||
<div class="poi">
|
||||
<el-input ref="poiInput" v-model="search" size="small" clearable @change="handleSearch" placeholder="请输入地点"/>
|
||||
<el-input ref="poiInput" v-model="search" size="small" clearable @change="handleSearch" placeholder="请输入地点">
|
||||
<span slot="append" @click="handleSearch" class="el-icon-search"/>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-form class="selected" v-if="!!selected.location" id="result" size="mini" label-suffix=":"
|
||||
label-position="left">
|
||||
@@ -39,7 +41,7 @@ export default {
|
||||
map: null,
|
||||
AMap: null,
|
||||
selected: {},
|
||||
geo: null
|
||||
searching: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -63,37 +65,25 @@ export default {
|
||||
this.selected = {location: res.lnglat}
|
||||
this.poi?.searchNearBy('', res.lnglat, 100)
|
||||
});
|
||||
if (this.latlng) {
|
||||
let marker = new AMap.Marker({
|
||||
position: [this.latlng.lng, this.latlng.lat]
|
||||
})
|
||||
this.map.add(marker)
|
||||
}
|
||||
this.poi = new AMap.PlaceSearch().on('complete', ({poiList}) => {
|
||||
this.map.clearMap()
|
||||
if (poiList?.length > 0) {
|
||||
poiList?.pois?.map(e => {
|
||||
let marker = new AMap.Marker({
|
||||
position: e.location,
|
||||
}).on('click', () => this.selected = e)
|
||||
this.map.add(marker)
|
||||
})
|
||||
} else {
|
||||
let marker = new AMap.Marker({
|
||||
position: this.selected.location,
|
||||
})
|
||||
this.map.add(marker)
|
||||
}
|
||||
this.poi = new AMap.PlaceSearch({map: this.map}).on('markerClick', ({data}) => {
|
||||
const {name, location} = data
|
||||
this.selected = {location, name}
|
||||
})
|
||||
this.geo = new AMap.Geolocation({
|
||||
if (this.latlng) {
|
||||
this.map.add(new AMap.Marker({
|
||||
position: [this.latlng.lng, this.latlng.lat]
|
||||
}))
|
||||
}
|
||||
this.map.addControl(new AMap.Geolocation({
|
||||
enableHighAccuracy: true,//是否使用高精度定位
|
||||
zoomToAccuracy: true//定位成功后是否自动调整地图视野到定位点
|
||||
})
|
||||
this.map.addControl(this.geo)
|
||||
}))
|
||||
})
|
||||
},
|
||||
handleSearch(v) {
|
||||
if (v) {
|
||||
this.searching = true
|
||||
this.poi.on('complete', () => this.searching = false)
|
||||
this.poi.searchNearBy(v, this.map.getCenter(), 50000)
|
||||
}
|
||||
},
|
||||
@@ -114,7 +104,7 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiLocateDialog {
|
||||
:deep( .el-dialog__body ){
|
||||
:deep( .el-dialog__body ) {
|
||||
padding: 0;
|
||||
height: 480px;
|
||||
position: relative;
|
||||
@@ -176,6 +166,13 @@ export default {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.el-input-group__append {
|
||||
background: $primaryColor;
|
||||
color: #fff;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -38,6 +38,14 @@ module.exports = {
|
||||
'^/lan': '/'
|
||||
}
|
||||
},
|
||||
'/sse': {
|
||||
target: "http://192.168.1.87:10409/v1",
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
//地址重写
|
||||
'^/sse': '/'
|
||||
}
|
||||
},
|
||||
},
|
||||
disableHostCheck: true,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user