This commit is contained in:
liuye
2022-06-28 09:14:39 +08:00
parent fd755fa58a
commit 6896dfd197
36 changed files with 6404 additions and 179 deletions

View File

@@ -0,0 +1,210 @@
<template>
<ai-list class="List">
<template #title>
<ai-title title="群发通知" isShowBottomBorder></ai-title>
</template>
<template #content>
<ai-search-bar>
<template #left>
<el-button type="primary" icon="iconfont iconAdd" @click="toAdd('')">添加</el-button>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size"
@getList="getTableData" show-overflow-tooltip :col-configs="colConfigs" :dict="dict">
<!-- <el-table-column slot="type" width="240px" label="消息内容" align="center">
<template slot-scope="{ row }">
<el-popover
placement="bottom"
width="400"
:visible-arrow="false"
popper-class="wechat-message__container"
trigger="hover">
<div class="count row-content" slot="reference" v-if="row.content">{{ row.content }}</div>
<div class="message-info">
<h2 :style="{marginBottom: row.accessUrl ? '16px' : '0'}">{{ row.content }}</h2>
<div class="message-info__wrapper" v-if="row.accessUrl">
<img v-if="row.contentType == 'image'" :src="row.accessUrl">
<video style="width:40px; height: 40px;" v-if="row.contentType == 'video'" :src="row.accessUrl"></video>
<img src="../../../../examples/assets/file.png" v-if="row.contentType == 'file'" width="40" height="40"/>
<div class="message-info__wrapper--right">
<h3 v-if="row.contentType === 'image'">{{ row.media.file.name }}</h3>
<h3 v-if="row.contentType === 'video'">{{ row.media.file.name }}</h3>
<p v-if="row.contentType === 'image'">{{ row.media.file.fileSizeStr }}</p>
<p v-if="row.contentType === 'video'">{{ row.media.file.fileSizeStr }}</p>
</div>
</div>
</div>
</el-popover>
</template>
</el-table-column> -->
<el-table-column slot="options" label="操作" align="center">
<template slot-scope="{ row }">
<el-button type="text" @click="toAdd(row.id)">详情</el-button>
<!-- <el-button type="text" @click="handleDelete(row.id)">删除</el-button> -->
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: "List",
props: {
dict: Object,
instance: Function,
params: Object,
},
data() {
return {
tableData: [],
page: {current: 1, size: 10, total: 0, pages: 0},
id: '',
}
},
created() {
this.getTableData()
},
computed: {
colConfigs() {
let conType = {
text: "文本",
image: "图片",
video: "视频",
file: "附件"
}
return [
// { prop: "fileList", label: '消息类型', align: "center", width: "250px", formart: v => v?.map(e=> conType[e.contentType]).toString() },
{ prop: "fileList", label: '消息内容', align: "center", width: "250px", formart: v => v?.filter(e=> e.contentType == 'text')[0].content },
// { prop: "fileList", label: '消息内容', align: "center", width: "250px", formart: v => v?.filter(e => e.contentType == 'text')[0].content},
// { slot: 'type' },
{ prop: "messageSource", label: '消息类型', align: "center", formart: v => v==1? '居民': '居民群'},
{ prop: "createTime", label: '创建时间', align: "center", width: "250px"},
{ prop: "userName", label: '创建人', align: "center", width: "250px", },
{ slot: "options" ,},
]
},
...mapState(['user'])
},
methods: {
getTableData() {
this.instance.post(`/app/pushmessage/list?`, null, {
params: {
...this.page,
}
}).then(res => {
if (res?.data) {
this.tableData = res.data.records
this.page.total = res.data.total
}
})
},
mapType(type) {
return {
'image': '图片',
'video': '视频',
'file': '文件',
'text': '文本'
}[type]
},
toAdd(id) {
this.$emit('change', {
type: 'Add',
params: {
id: id || '',
},
})
}
},
}
</script>
<style lang="scss" scoped>
.List {
height: 100%;
background: #f3f4f5;
.count {
cursor: pointer;
color: #2266FF;
font-size: 14px;
&:hover {
opacity: 0.6;
}
}
}
img, video {
width: 40px;
height: 40px;
margin-right: 10px;
object-fit: cover;
}
.message-info {
padding: 8px;
min-height: 116px;
h2 {
color: #222222;
font-weight: 500;
font-size: 14px;
}
.message-info__wrapper {
display: flex;
align-items: center;
width: 368px;
height: 60px;
padding: 10px;
background: #FFFFFF;
border-radius: 2px;
border: 1px solid #D0D4DC;
.message-info__wrapper--right {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
h3 {
width: 100%;
color: #222222;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: normal;
}
img, video {
width: 40px;
height: 40px;
margin-right: 10px;
object-fit: cover;
}
p {
margin-top: 6px;
font-size: 14px;
color: #888888;
}
}
}
.row-content {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@@ -0,0 +1,195 @@
<template>
<section class="SelectDeptUser">
<el-input :value="selectText" disabled size="small" @click.native="dialog=true">
<el-button type="text" slot="append">开始选择</el-button>
</el-input>
<ai-dialog :visible.sync="dialog" title="选择部门/人员" width="700px" @onConfirm="handleSubmit" @close="selected=[],getDepts()">
<el-breadcrumb separator="/">
<el-breadcrumb-item v-for="(item, index) in selectDeptPath" :key="index">
<el-button type="text" @click="deptNameClick(item)">{{ item.name }}</el-button>
</el-breadcrumb-item>
</el-breadcrumb>
<div class="optionsItem" v-for="(row, index) in options" :key="index">
<el-row type="flex">
<el-checkbox class="fill" :label="row.uid" v-model="row.checked" @change="handleCheck(row,index)">{{ row.name }}</el-checkbox>
<el-button type="text" v-if="!row.parentid&&row.kind=='dept'" @click="openDialogTag(row)">添加标签</el-button>
<el-button type="text" v-if="row.kind=='dept'" @click="itemClick(row)">更多</el-button>
</el-row>
<el-tag effect="dark" size="small" v-for="tag in row.selectedTags" :key="tag.id" class="mar-4">{{ tag.name }}</el-tag>
</div>
<ai-empty v-if="!options.length"/>
<ai-dialog :visible.sync="dialogTag" title="选择标签" width="500px" @onConfirm="handleSelectTag" @close="selectedTags=[]" append-to-body>
<el-checkbox-group v-model="selectedTags">
<div class="optionsItem" v-for="(cls, index) in tagOps" :key="index">
<ai-title :title="cls.name"/>
<el-checkbox class="fill" v-for="(op, i) in cls.tagList" :key="i" :label="op.id">{{ op.name }}</el-checkbox>
</div>
</el-checkbox-group>
<ai-empty v-if="!tagOps.length"/>
</ai-dialog>
</ai-dialog>
</section>
</template>
<script>
export default {
name: "SelectDeptUser",
model: {
event: "change",
prop: "value"
},
props: {
value: {default: ""},
instance: Function,
dict: Object,
source: {default: 1}
},
computed: {
selectText: v => v.value?.length > 0 ? "已选择" : "请选择",
tagAction: v => v.source == 2 ? '/app/wxcp/wxgroupchattag/listAllByCorp' : '/app/wxcp/wxcorptag/listAllByCorp'
},
data() {
return {
dialog: false,
dialogTag: false,
currentCorp: {},
selected: [],
selectDeptPath: [],
selectedTags: [],
options: [],
tagOps: [],
allData: [],
tags: {}
}
},
methods: {
getDepts() {
this.instance.post('/app/wxcp/wxdepartment/listAllByCorp').then(res => {
if (res?.data) {
let parents = res.data.map(e => e.parentid)
this.allData = res.data.map(e => ({
...e,
hasChildren: parents.includes(e.id),
uid: [e.corpId, e.id].join("_"),
kind: 'dept',
checked: false
}))
this.deptInit()
}
})
},
getTagsByCorp(dvcpCorpId) {
return this.instance.post(this.tagAction, null, {
params: {
dvcpCorpId,
size: 9999
}
}).then(res => {
if (res?.data) {
return this.tagOps = res.data.records || []
}
})
},
deptInit() {
this.options = this.allData.filter(e => !e.parentid)
this.selectDeptPath = [{name: "可选范围", id: ''}]
},
itemClick(row) {
let index = this.selectDeptPath.findIndex(e => e.id == row.id)
if (index == -1) {
this.selectDeptPath.push(row)
this.getDeptsAndUsersByParent(row)
}
},
getDeptsAndUsersByParent(row) {
let {id: departmentId, corpId: cid} = row
this.options = this.allData.filter(e => e.parentid == departmentId && e.corpId == cid) || []
this.instance.post(`/app/wxcp/wxuser/listByDeptId`, null, {
params: {departmentId, status: 1, cid}
}).then(res => {
if (res?.data) {
res.data = res.data.map(e => ({
...e, kind: "user", checked: this.selected.some(s => {
if (e.id == s.id) return true
})
}))
this.options = [this.options, res.data].flat()
}
})
},
deptNameClick(row, index) {
if (!index) { //第一级别
this.deptInit()
} else {
let length = this.selectDeptPath.length - index
this.selectDeptPath.splice(index + 1, length)
this.getDeptsAndUsersByParent(row.id)
}
},
openDialogTag(row) {
this.getTagsByCorp(row.corpId).then(() => {
this.currentCorp = row
this.dialogTag = true
})
},
handleSelectTag() {
let {corpId} = this.currentCorp
this.currentCorp.selectedTags = this.$copy(this.tagOps.map(e => e.tagList).flat().filter(e => this.selectedTags.includes(e.id)))
this.tags[corpId] = this.$copy(this.currentCorp.selectedTags)
this.dialogTag = false
},
handleSubmit() {
let result = {}
this.selected?.map(e => {
let {kind, id} = e
result[e.corpId] = [result[e.corpId], {kind, id}].flat()
})
let selected = Object.keys(result).map(corpId => {
let res
if (result[corpId]) {
res = {
corpId,
objList: result[corpId].filter(e => !!e)
}
if (this.tags[corpId]?.length > 0) {
res.tagId = this.tags[corpId]?.map(e => e.id)
}
}
return res
}).filter(e => !!e)
this.$emit("change", selected)
this.dialog = false
},
isSelected(uid) {
return !!this.selected.find(e => e.uid == uid)
},
handleCheck(row, i) {
if (row.checked) {
this.selected.push(row)
} else {
this.selected.splice(i, 1)
}
this.$forceUpdate()
}
},
created() {
this.getDepts()
}
}
</script>
<style lang="scss" scoped>
.SelectDeptUser {
::v-deep.optionsItem {
border: 1px solid #eee;
margin-bottom: 8px;
padding: 8px;
border-radius: 4px;
.mar-4 {
margin-right: 4px;
margin-bottom: 4px;
}
}
}
</style>

View File

@@ -1,157 +0,0 @@
<template>
<section class="AppDVDemo">
<ai-list>
<ai-title slot="title" title="数据大屏草稿板" isShowBottomBorder/>
<template #content>
<div class="showPanel">
<ai-dv-wrapper :views="views" v-model="active">
<ai-echart :ops="ops" :data="views" theme="1"></ai-echart>
</ai-dv-wrapper>
</div>
</template>
</ai-list>
</section>
</template>
<script>
import AiDvWrapper from "./layout/AiDvWrapper/AiDvWrapper";
import ops from './components/AiEchart/template/pie/pieChart3'
import AiDvPanel from "./layout/AiDvPanel/AiDvPanel";
import AiDvDisplay from "./layout/AiDvDisplay/AiDvDisplay";
import AiDvSummary from "./layout/AiDvSummary/AiDvSummary";
import AiMonitor from "./components/AiMonitor/AiMonitor";
export default {
name: "AppDVDemo",
label: "数据大屏草稿板",
components: {AiMonitor, AiDvPanel, AiDvWrapper, AiDvDisplay, AiDvSummary},
data() {
return {
ops,
active: '',
views: [
{
"name": "阿斯达",
"v1": 23,
"v2": 33
},
{
"name": "水电费",
"v1": 12,
"v2": 34
},
{
"name": "凡哥",
"v1": 67,
"v2": 25
},
{
"name": "党费",
"v1": 98,
"v2": 85
}
],
list: [
{name: '阿斯达', v1: 23, v2: 33},
{name: '水电费', v1: 12, v2: 34},
{name: '凡哥', v1: 67, v2: 25},
{name: '党费', v1: 98, v2: 85},
{name: '阿萨德', v1: 98, v2: 85},
{name: '电饭锅', v1: 98, v2: 85},
{name: '户籍科', v1: 98, v2: 85},
],
data: [
{
"key": "阿斯达",
"value": '22',
"percentage": 33,
text: '同比上月'
},
{
"key": "阿斯达",
"value": '22',
"percentage": -33,
text: '同比上月'
}
],
value: [
{
"key": "宅基地 ",
"value": 1252292,
"value1": 12592,
"value2": 12592,
"value3": 12592,
"value4": 12592
},
{
"key": "人数",
"value": 12592,
"value1": 12592,
"value2": 12592,
"value3": 12592,
"value4": 12592
},
{
"key": "户数 ",
"value": 12592,
"value1": 12592,
"value2": 12592,
"value3": 12592,
"value4": 12592
},
{
"key": "村社区",
"value": 12592,
"value1": 12592,
"value2": 12592,
"value3": 12592,
"value4": 12592
}
],
markers: []
}
},
methods: {
getMarkers() {
this.$request.post('/app/appcommunitybuildinginfo/listByBuildingAndHomestead', null, {
params: {current: 1, size: 1000000},
withoutToken: true
}).then(res => {
if (res?.data) {
this.markers = res.data.map(e => ({
...e,
icon: "https://cdn.cunwuyun.cn/dvcp/dv/monitor.svg",
label: e.communityName
}))
}
})
}
},
created() {
// this.getMarkers()
setTimeout(() => {
this.list = this.list.map(e => ({name: e.name, '电费': e.v1, '水费': e.v2 * 2}))
}, 2000)
},
mounted() {
this.$initWxOpenData()
}
}
</script>
<style lang="scss" scoped>
@import "./lib/animation";
.AppDVDemo {
height: 100%;
.showPanel {
height: 600px;
}
.AiDvPanel {
width: 600px;
}
}
</style>

View File

@@ -0,0 +1,88 @@
<template>
<section class="AiDataPanel">
<b class="item-title" v-text="label"/>
<div class="num-bg">
<span ref="num" class="num" v-text="num"/>
</div>
</section>
</template>
<script>
import {gsap} from 'gsap'
export default {
name: "AiDataPanel",
props: {
label: {default: "标题"},
value: {default: 0}
},
data() {
return {
num: 0
}
},
methods: {
animation() {
let demo = {num: Math.max(this.value - 30, 0)}
gsap.to(demo, 1, {
num: this.value, onUpdate: () => {
this.num = demo.num?.toFixed(0)
}
})
}
},
mounted() {
this.animation()
}
}
</script>
<style lang="scss" scoped>
.AiDataPanel {
flex: 1;
width: 172px;
height: 160px;
display: flex;
flex-direction: column;
align-items: center;
background: #000;
& + .AiDataPanel {
margin-left: 16px;
}
.item-title {
font-size: 24px;
line-height: 32px;
background-image: -webkit-linear-gradient(bottom, #35BEFF, #EBF9FF, #FFFFFF);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-top: 30px;
}
.num-bg {
width: 100%;
height: 160px;
background-image: url(https://cdn.cunwuyun.cn/dvcp/dv/dianjiang/number-bg.png);
background-size: 100% 100%;
position: relative;
margin-top: -76px;
.num {
position: absolute;
left: 0;
bottom: 30px;
width: 100%;
text-align: center;
height: 50px;
font-size: 40px;
font-family: D-DIN-Bold, D-DIN;
font-weight: bold;
line-height: 54px;
background-image: -webkit-linear-gradient(bottom, #35BEFF, #EBF9FF, #FFFFFF);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
}
}
</style>

View File

@@ -0,0 +1,357 @@
<template>
<div class="AiDvRender" style="width: 100%; height: 100%;">
<ai-dv-display v-if="data.type === 'display'" :title="data.title" :list="values"></ai-dv-display>
<ai-dv-panel
style="height: 100%; width: 100%;"
v-if="data.type !== 'display'"
:title="data.title"
:border="data.border || ''">
<AiDvSummary v-if="data.type === 'summary'" :summaryTitle="data.summaryTitle" :key="`summary${index}`" :type="data.display" :data="values"/>
<AiSwiper v-else-if="data.type === 'swiper'" :heigth="'100%'" :data="values"/>
<dv-scroll-board
v-if="data.type === 'table'"
:class="'dvScrollBoard' + theme"
:config="formatTable(values, data.isShowIndex, data.rowNum)"
:key="data.height"
:theme="theme"
:style="{height: data.height + 'px', width: '100%'}"/>
<ai-echart
v-else-if="data.type === 'barChart1'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="data.config"/>
<ai-echart
v-else-if="data.type === 'barChart2'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:theme="theme"
:key="`chart${index}`"
:data="values"
:ops="data.config"/>
<ai-echart
v-else-if="data.type === 'barChart3'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="data.config"/>
<ai-echart
v-else-if="data.type === 'barChart5'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="data.config"/>
<ai-echart
v-else-if="data.type === 'barChart7'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="data.config"/>
<ai-echart
v-else-if="data.type === 'barChart8'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="data.config"/>
<ai-echart
v-else-if="data.type === 'barChart9'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="data.config"/>
<ai-echart
v-else-if="data.type === 'lineChart1'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="lineChart1"/>
<ai-echart
v-else-if="data.type === 'lineChart2'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="lineChart2"/>
<ai-echart
v-else-if="data.type === 'lineChart3'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="data.config"/>
<ai-echart
v-else-if="data.type === 'lineChart4'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="data.config"/>
<ai-echart
v-else-if="data.type === 'lineChart5'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="lineChart5"/>
<ai-echart
v-else-if="data.type === 'pieChart'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="pieChart"/>
<ai-echart
v-else-if="data.type === 'pieChart1'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="pieChart1"/>
<ai-echart
v-else-if="data.type === 'pieChart3'"
style="height: 100%; width: 100%;"
:ref="'chart' + index"
:key="`chart${index}`"
:theme="theme"
:data="values"
:ops="pieChart3"/>
<ai-map :markers="values" v-else-if="data.type=='map'" :mask="data.mask === '1'" :areaId="data.areaId || user.info.areaId"
:map-style="`amap://styles/${data.mapStyle}`" :pulseLines="data.pulseLines==1" :map.sync="map" :lib.sync="lib"/>
<ai-monitor :src="data.src" v-else-if="data.type === 'monitor'" :type="data.monitorType"/>
<video style="width: 100%; height: 100%; object-fit: fill;" loop :src="data.src" autoplay v-else-if="data.type === 'video'"/>
<AiDvPartyOrg style="width: 100%; height: 100%;" v-else-if="data.type === 'partyOrg'" :instance="instance"/>
</ai-dv-panel>
</div>
</template>
<script>
import {mapState} from 'vuex'
import AiSwiper from './AiSwiper.vue'
import pieChart from './AiEchart/template/pie/pieChart2'
import pieChart1 from './AiEchart/template/pie/pieChart1'
import pieChart3 from './AiEchart/template/pie/pieChart3'
import lineChart1 from './AiEchart/template/line/lineChart1'
import lineChart2 from './AiEchart/template/line/lineChart2'
import lineChart5 from './AiEchart/template/line/lineChart5'
import AiMonitor from "./AiMonitor/AiMonitor";
import AiDvPanel from "../layout/AiDvPanel/AiDvPanel";
import AiDvDisplay from "../layout/AiDvDisplay/AiDvDisplay";
import AiDvSummary from "../layout/AiDvSummary/AiDvSummary";
export default {
name: 'AiDvRender',
props: ['data', 'index', 'theme', 'instance'],
components: {
AiDvSummary,
AiDvDisplay,
AiDvPanel,
AiMonitor,
AiSwiper
},
data() {
return {
lineChart1,
lineChart2,
lineChart5,
pieChart,
pieChart1,
pieChart3,
map: null,
lib: null
}
},
computed: {
...mapState(['user']),
values() {
if (!this.data) {
return []
}
return this.data.type === 'map' ? this.data[this.data.dataType].map(e => {
return {...e, lng: e['经度'], lat: e['纬度'], label: e['地区名称']}
}) : this.data[this.data.dataType]
}
},
methods: {
formatTable(data, isShowIndex, rowNum) {
if (!data.length) {
return {
header: [],
data: []
}
}
let rows = []
const header = data.map(v => {
return v[Object.keys(v)[0]]
})
Object.keys(data[0]).forEach((item, index) => {
if (index !== 0) {
rows.push(item)
}
})
return {
header: header,
data: rows.map(item => {
return data.map(v => {
return v[item]
})
}),
headerBGC: 'transparent',
evenRowBGC: 'transparent',
oddRowBGC: 'rgba(0, 133, 255, 0.2)',
headerHeight: 42,
rowNum: rowNum || 7,
index: isShowIndex === '1',
waitTime: 8000,
carousel: 'page',
indexHeader: '排名',
align: ['center', 'center', 'center', 'center', 'center']
}
},
handleMapClick(count = 0) {
let {lib: AMap, map} = this
if (AMap) {
let infoWin = new AMap.InfoWindow({content: ""})
map.clearMap()
let markers = this.values.filter(e => e.lng).map(e => {
return new AMap.Marker({
map,
label: e.label,
icon: e.icon,
position: [e.lng, e.lat]
}).on('click', ({target}) => {
map.clearInfoWindow()
markers?.map(m => m.getIcon() == e.selectedIcon && m.setIcon(e.icon))
target.setIcon(e.selectedIcon)
infoWin.setContent([
`<div class="infoWin">`,
`<b>${e.label}</b>`,
`<div>累计成交金额:${e['累计成交金额(万)']}万元</div>`,
`<div>金融产品:${e['金融产品(万)']}万元</div>`,
`<div>融资需求:${e['融资需求(万)']}万元</div>`,
'</div>'
].join(''))
infoWin.open(map, [e.lng, e.lat])
})
})
map.setFitView(markers)
} else if (count < 10) {
console.log("正在加载...%s", count)
setTimeout(() => this.handleMapClick(++count), 1000)
}
}
},
mounted() {
if (this.data.type == 'map') {
this.handleMapClick()
}
}
}
</script>
<style lang="scss" scoped>
.AiDvRender {
::v-deep .dvScrollBoard1 {
.header {
background: rgba(0, 0, 0, 0.1) !important;
.header-item {
color: #FFBB73 !important;
font-size: 16px !important;
font-weight: 600;
}
}
.rows {
font-size: 16px;
font-weight: 600;
color: #FFFFFF;
line-height: 21px;
text-shadow: 0px 2px 4px rgba(117, 9, 9, 0.5);
background: linear-gradient(180deg, #FFF6C7 0%, #FF9A02 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
& > div:nth-of-type(2n - 1) {
background-color: transparent !important;
}
& > div:nth-of-type(2n) {
background-color: rgba(0, 0, 0, 0.1) !important;
}
.index {
color: #fff;
text-shadow: none;
background: #BD4921 !important;
-webkit-background-clip: inherit;
-webkit-text-fill-color: #fff;
}
}
}
::v-deep.amap-info-contentContainer {
.amap-info-content {
background: #0A3257;
border: 1px solid #7BE5FF;
padding: 16px;
font-family: PingFangSC-Semibold, PingFang SC;
.infoWin {
& > b {
display: block;
font-size: 18px;
font-weight: 600;
color: #FFFFFF;
margin-bottom: 13px;
}
& > div {
font-size: 16px;
font-weight: 400;
color: #7BE5FF;
& + div {
margin-top: 8px;
}
}
}
}
.amap-info-sharp {
border-top-color: #0A3257;
&:after {
border-top-color: #7BE5FF;
filter: none;
}
}
}
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<section class="AiStaData">
<ai-data-panel v-for="op in data" :key="op[key]"
:label="op[label]" :value="op[key]"/>
</section>
</template>
<script>
import AiDataPanel from "./AiDataPanel";
export default {
name: "AiStaData",
components: {AiDataPanel},
props: {
data: {default: () => []},
key: {default: "id"},
label: {default: "label"},
}
}
</script>
<style lang="scss" scoped>
.AiStaData {
width: 100%;
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: center;
}
</style>

View File

@@ -0,0 +1,92 @@
<template>
<div class="swiper">
<el-carousel height="100%" indicator-position="none">
<el-carousel-item v-for="(item, index) in data" :key="index">
<img :src="item.img">
<div class="swiper-content" v-if="item.title">
<h2>{{ item.title }}</h2>
<p>{{ item.content }}</p>
</div>
</el-carousel-item>
</el-carousel>
</div>
</template>
<script>
export default {
name: 'AiSwiper',
props: {
data: {
type: Array,
default: () => []
},
width: {
type: String,
default: '100%'
},
heigth: {
type: String,
default: '100%'
}
},
data () {
return {
}
},
mounted () {
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.swiper {
width: 100%;
height: 100%;
padding: 20px 0 0;
::v-deep .el-carousel {
height: 100%;
}
img {
width: 100%;
height: 100%;
}
.swiper-content {
position: absolute;
bottom: 0;
left: 0;
z-index: 1;
width: 100%;
padding: 10px;
text-align: center;
background: linear-gradient(180deg, rgba(0, 0, 0, 0.1) 0%, #000000 100%);
h2 {
margin-bottom: 4px;
color: #fff;
text-align: center;
font-size: 18px;
}
p {
line-height: 22px;
white-space: pre-line;
color: #B6DFFF;
font-size: 14px;
text-align: center;
}
}
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<section class="AppAiCode">
<component :is="currentPage" v-bind="$props"/>
</section>
</template>
<script>
import AcList from "./acList";
import AcAdd from "./acAdd";
export default {
name: "AppAiCode",
components: {AcAdd, AcList},
label: "低代码生成平台",
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
currentPage() {
let {hash} = this.$route
return hash == "#add" ? AcAdd : AcList
}
},
provide() {
return {
top: this
}
},
methods: {
handleGetCode(id) {
id && this.instance.post("/node/aicode/getCode", null, {
params: {id},
responseType: "blob"
}).then(res => {
if (res?.code == 1) {
this.$message.error(res.err)
} else {
const link = document.createElement('a')
let blob = new Blob([res], {type: 'application/zip'})
link.style.display = 'none'
link.href = URL.createObjectURL(blob)
link.setAttribute('download', 'aicode.zip')
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
})
}
},
created() {
this.dict.load("detailType")
}
}
</script>
<style lang="scss" scoped>
.AppAiCode {
height: 100%;
}
</style>

View File

@@ -0,0 +1,185 @@
<template>
<section class="acAdd">
<ai-detail list>
<ai-title slot="title" :title="pageTitle">
<template #rightBtn>
<el-button type="primary" v-if="isEdit" @click="top.handleGetCode($route.query.id)">生成代码</el-button>
</template>
</ai-title>
<template #content>
<el-form ref="AcForm" :model="form" size="small" label-width="120px" :rules="rules">
<el-tabs tab-position="left" @tab-click="handleSyncProps">
<el-tab-pane label="基本信息" lazy>
<ai-card title="基本信息">
<template #content>
<el-row type="flex">
<div class="fill">
<el-form-item label="应用名称" prop="name">
<el-input v-model="form.name" clearable placeholder="请输入"/>
</el-form-item>
<el-form-item label="应用模块" prop="appName">
<el-input v-model="form.appName" clearable placeholder="请输入"/>
</el-form-item>
</div>
<div class="fill">
<el-form-item label="核心码" prop="rightCode">
<el-input v-model="form.rightCode" clearable placeholder="请输入"/>
</el-form-item>
<el-form-item label="详情方式" prop="detailType">
<ai-select v-model="form.detailType" :selectList="dict.getDict('detailType')"/>
</el-form-item>
</div>
</el-row>
<el-form-item label="按钮配置" prop="btns">
<el-checkbox-group v-model="form.btns">
<el-checkbox label="insertEnable">添加</el-checkbox>
<el-checkbox label="importEnable">导入</el-checkbox>
<el-checkbox label="exportEnalbe">导出</el-checkbox>
<el-checkbox label="editEnable">编辑</el-checkbox>
<el-checkbox label="deleteEnable">删除</el-checkbox>
<el-checkbox label="batchDelEnable">批量删除</el-checkbox>
</el-checkbox-group>
</el-form-item>
</template>
</ai-card>
<ai-card title="字段设置">
<template #right>
<el-button type="text" @click="handleAddProps">批量添加</el-button>
<el-button type="text" @click="form.props.push({})">添加</el-button>
</template>
<template #content>
<ai-table :tableData="form.props" :isShowPagination="false" :colConfigs="colConfigs">
<el-table-column v-for="col in colConfigs" :key="col.slot" v-bind="col">
<template slot-scope="{row}">
<el-checkbox v-if="col.type=='checkBox'" v-model="row[col.slot]"/>
<span v-else-if="col.type=='chbShow'" v-text="row[col.slot]==true?'✔':''"/>
<el-input v-else size="small" v-model="row[col.slot]" placeholder="请输入" clearable/>
</template>
</el-table-column>
<el-table-column label="操作" slot="options" align="center">
<template slot-scope="{row,$index}">
<el-button type="text" @click="form.props.splice($index,1)">删除</el-button>
</template>
</el-table-column>
</ai-table>
</template>
</ai-card>
</el-tab-pane>
<el-tab-pane label="详情设计" lazy>
<detail-layout v-bind="$props" :form="form" v-model="form.detailConfig"/>
</el-tab-pane>
</el-tabs>
</el-form>
</template>
<template #footer>
<el-button @click="back">取消</el-button>
<el-button type="primary" @click="submit">提交</el-button>
</template>
</ai-detail>
</section>
</template>
<script>
import DetailLayout from "./detailLayout";
export default {
name: "acAdd",
components: {DetailLayout},
props: {
instance: Function,
dict: Object,
permissions: Function
},
inject: {
top: {}
},
computed: {
isEdit: v => !!v.$route.query.id,
pageTitle: v => v.isEdit ? "编辑应用" : "新增应用"
},
data() {
return {
form: {props: [], btns: []},
rules: {
name: {required: true, message: "请输入应用名称"},
appName: {required: true, message: "请输入应用模块"},
btns: {required: true, message: '请输入按钮配置', trigger: 'change'}
},
colConfigs: [
{slot: 'prop', label: "字段"},
{slot: 'label', label: "名称"},
{slot: 'dict', label: "字典"},
{slot: 'isSearch', label: "搜索字段", align: 'center', type: 'checkBox'},
{slot: 'isTable', label: "表格字段", align: 'center', type: 'checkBox'},
{slot: 'isDetail', label: "详情字段", align: 'center', type: 'chbShow'},
]
}
},
methods: {
getDetail() {
let {id} = this.$route.query
id && this.instance.post("/node/aicode/detail", null, {
params: {id}
}).then(res => {
if (res?.data) {
this.form = res.data
this.handleSyncProps()
}
})
},
back() {
this.$router.push({})
},
submit() {
this.$refs.AcForm.validate(v => {
if (v) {
this.instance.post("/node/aicode/addOrUpdate", this.form).then(res => {
if (res?.code == 0) {
this.$message.success("提交成功!")
this.back()
}
})
}
})
},
handleSyncProps() {
let detailPorps = this.form.detailConfig?.map(e => e.column)?.flat()?.map(e => e.prop)
this.form.props = this.form.props.map(e => ({...e, isDetail: !!detailPorps?.includes(e.prop)}))
},
handleAddProps() {
this.$prompt("请输入swagger示例JSON字符串", {
type: 'warning',
title: "批量添加字段",
dangerouslyUseHTMLString: true,
center: true,
}).then(({value}) => {
if (this.$checkJson(value)) {
Object.keys(JSON.parse(value)).map(prop => {
this.form.props.push({prop})
})
}
}).catch(() => 0)
},
$checkJson(str) {
if (typeof str == 'string') {
try {
let obj = JSON.parse(str);
return !!(typeof obj == 'object' && obj);
} catch (e) {
return false;
}
}
return false;
},
},
created() {
this.getDetail()
}
}
</script>
<style lang="scss" scoped>
.acAdd {
height: 100%;
}
</style>

View File

@@ -0,0 +1,90 @@
<template>
<section class="acList">
<ai-list>
<ai-title slot="title" title="低代码生成平台" isShowBottomBorder/>
<template #content>
<ai-search-bar>
<template #left>
<el-button type="primary" icon="iconfont iconAdd" @click="handleAdd()">添加</el-button>
</template>
<template #right>
<el-input size="small" placeholder="搜索应用" v-model="search.name" clearable
@change="page.current=1,getTableData()"/>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size"
@getList="getTableData" :col-configs="colConfigs" :dict="dict">
<el-table-column slot="options" label="操作" fixed="right" align="center" width="300">
<template slot-scope="{row}">
<el-button type="text" @click="handleAdd(row.id)">编辑</el-button>
<el-button type="text" @click="handleDelete(row.id)">删除</el-button>
<el-button type="text" @click="top.handleGetCode(row.id)">下载</el-button>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
</section>
</template>
<script>
export default {
name: "acList",
props: {
instance: Function,
dict: Object,
permissions: Function
},
inject: {
top: {}
},
data() {
return {
search: {name: ""},
page: {current: 1, size: 10, total: 0},
tableData: [],
colConfigs: [
{label: "应用名称", prop: "name", width: 200},
{label: "应用模块", prop: "appName"},
{label: "权限码", prop: "rightCode"},
{label: "详情展示方式", prop: "detailType", dict: "detailType"},
],
}
},
methods: {
getTableData() {
this.instance.post("/node/aicode/list", null, {
params: {...this.page, ...this.search}
}).then(res => {
if (res?.data) {
this.tableData = res.data.records
this.page.total = res.data.total
}
})
},
handleAdd(id) {
this.$router.push({hash: "#add", query: {id}})
},
handleDelete(ids) {
this.$confirm("是否要删除该应用?").then(() => {
this.instance.post("/node/aicode/delete", null, {
params: {ids}
}).then(res => {
if (res?.code == 0) {
this.$message.success("删除成功")
this.getTableData()
}
})
}).catch(() => 0)
}
},
created() {
this.getTableData()
}
}
</script>
<style lang="scss" scoped>
.acList {
}
</style>

View File

@@ -0,0 +1,274 @@
{
"config": [
{
"type": "info",
"tips": "(不能重复添加同一元素)",
"label": "信息",
"children": [
{
"type": "name",
"fieldName": "姓名",
"fieldTips": "请输入姓名",
"fixedLabel": "姓名",
"disable": "0",
"grid": 0.5,
"defaultValue": "",
"icon": "icontext_box",
"mustFill": "1",
"maxLength": 20
},
{
"type": "idNumber",
"fieldName": "身份证号",
"fixedLabel": "身份证号",
"fieldTips": "请输入身份证号",
"defaultValue": "",
"icon": "icontext_area",
"mustFill": "1",
"maxLength": 20,
"disable": "0",
"grid": 0.5
},
{
"type": "phone",
"fieldName": "联系方式",
"fixedLabel": "联系方式",
"fieldTips": "请输入联系方式",
"defaultValue": "",
"icon": "icontext_area",
"mustFill": "1",
"maxLength": 20,
"disable": "0",
"grid": 0.5
},
{
"type": "area",
"fieldName": "地区",
"fixedLabel": "地区",
"fieldTips": "请选择地区",
"defaultValue": "",
"icon": "icontext_area",
"mustFill": "1",
"areaPattern": "",
"disable": "0",
"grid": 0.5
}
]
},
{
"type": "options",
"tips": "(可重复添加)",
"label": "选项",
"children": [
{
"type": "radio",
"fieldName": "单选",
"fixedLabel": "单选",
"fieldTips": "请选择",
"grid": 0.5,
"icon": "iconradio",
"mustFill": "1",
"disable": "0",
"defaultValue": "",
"options": [
{
"label": "选项1",
"value": ""
},
{
"label": "选项2",
"value": ""
}
],
"title": ""
},
{
"type": "checkbox",
"fieldName": "多选",
"fixedLabel": "多选",
"fieldTips": "请选择",
"icon": "iconcheck_box",
"mustFill": "1",
"grid": 0.5,
"disable": "0",
"defaultValue": [],
"options": [
{
"label": "选项1",
"value": ""
},
{
"label": "选项2",
"value": ""
}
],
"title": ""
},
{
"type": "select",
"fieldName": "单下拉框",
"fixedLabel": "单下拉框",
"grid": 0.5,
"fieldTips": "请选择",
"icon": "iconSelect",
"mustFill": "1",
"defaultValue": "",
"disable": "0",
"options": [
{
"label": "选项1",
"value": ""
},
{
"label": "选项2",
"value": ""
}
],
"title": ""
},
{
"type": "onOff",
"fieldName": "开关",
"fixedLabel": "开关",
"grid": 0.5,
"fieldTips": "请选择开关",
"icon": "iconSelect",
"mustFill": "1",
"defaultValue": "0",
"disable": "0",
"title": ""
},
{
"type": "date",
"fieldName": "日期",
"fixedLabel": "日期",
"grid": 0.5,
"datetimePattern": "yyyy-MM-dd",
"fieldTips": "请选择日期",
"icon": "iconSelect",
"mustFill": "1",
"disable": "0",
"title": ""
},
{
"type": "time",
"fieldName": "时间",
"fixedLabel": "时间",
"grid": 0.5,
"datetimePattern": "HH:mm:ss",
"fieldTips": "请选择时间",
"icon": "iconSelect",
"mustFill": "1",
"disable": "0",
"title": ""
},
{
"type": "datetime",
"fieldName": "日期时间",
"fixedLabel": "日期时间",
"grid": 0.5,
"datetimePattern": "yyyy-MM-dd HH:mm:ss",
"fieldTips": "请选择日期时间",
"icon": "iconSelect",
"mustFill": "1",
"disable": "0",
"title": ""
}
]
},
{
"type": "input",
"tips": "(可重复添加)",
"label": "填空",
"children": [
{
"type": "input",
"fieldName": "单行填空",
"fieldTips": "请输入",
"fixedLabel": "单行填空",
"disable": "0",
"grid": 0.5,
"defaultValue": "",
"icon": "icontext_box",
"mustFill": "1",
"maxLength": 50
},
{
"type": "textarea",
"fieldName": "多行填空",
"fixedLabel": "多行填空",
"fieldTips": "请输入",
"lineNumber": 4,
"defaultValue": "",
"icon": "icontext_area",
"mustFill": "1",
"maxLength": 500,
"disable": "0",
"grid": 1
},
{
"type": "number",
"fieldName": "数字输入",
"fixedLabel": "数字输入",
"fieldTips": "请输入数字",
"defaultValue": "",
"icon": "icontext_area",
"mustFill": "1",
"maxValue": 10000,
"decimalPlaces": 0,
"minValue": 0,
"maxLength": 500,
"disable": "0",
"grid": 0.5
},
{
"type": "rtf",
"fieldName": "富文本",
"fixedLabel": "富文本",
"fieldTips": "请输入",
"defaultValue": "",
"icon": "icontext_area",
"mustFill": "1",
"maxLength": 5000,
"disable": "0",
"grid": 1
}
]
},
{
"type": "annex",
"tips": "(可重复添加)",
"label": "附件",
"children": [
{
"type": "upload",
"fieldTips": "请上传",
"fieldName": "上传附件",
"fixedLabel": "上传附件",
"disable": "0",
"fileChoseSize": 10,
"fileMaxCount": 9,
"defaultValue": "",
"icon": "iconpic",
"mustFill": "1",
"grid": 1
}
]
},
{
"type": "layout",
"tips": "(可重复添加)",
"label": "分组",
"children": [
{
"type": "group",
"fieldName": "卡片",
"fixedLabel": "卡片",
"icon": "iconpic",
"groupName": "分组标题",
"column": []
}
]
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,14 @@
</ai-search-bar> </ai-search-bar>
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size" <ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size"
@getList="getTableData" :col-configs="colConfigs" :dict="dict"> @getList="getTableData" :col-configs="colConfigs" :dict="dict">
<el-table-column type="expand" slot="expand">
<template slot-scope="{row}">
<ai-wrapper>
<ai-info-item labelWidth="200px" v-for="op in desConfigs" :key="op.prop" :value="row[op.prop]"
v-bind="op"/>
</ai-wrapper>
</template>
</el-table-column>
<el-table-column slot="process" label="打包进度"> <el-table-column slot="process" label="打包进度">
<template slot-scope="{row}"> <template slot-scope="{row}">
<span v-if="row.count==0" v-text="getProcessMsg(row)"/> <span v-if="row.count==0" v-text="getProcessMsg(row)"/>
@@ -24,27 +32,40 @@
<template slot-scope="{row}"> <template slot-scope="{row}">
<el-button type="text" @click="handleEdit(row)">编辑</el-button> <el-button type="text" @click="handleEdit(row)">编辑</el-button>
<el-button type="text" @click="handleDelete(row.id)">删除</el-button> <el-button type="text" @click="handleDelete(row.id)">删除</el-button>
<el-button type="text" @click="handleZip(row)">打包更新</el-button> <el-button type="text" @click="handleZip(row)" v-if="row.count==0">打包更新</el-button>
<el-button type="text" v-if="row.download&&row.target" @click="handleDownload(row)">下载</el-button> <el-button type="text" @click="handleCancelZip(row)" v-else>停止打包</el-button>
<el-button type="text" v-if="row.target" @click="handleDownload(row)">下载</el-button>
</template> </template>
</el-table-column> </el-table-column>
</ai-table> </ai-table>
</template> </template>
</ai-list> </ai-list>
<ai-dialog :visible.sync="dialog" title="部署任务设置" width="700px" @close="form={}" @onConfirm="submit"> <ai-dialog :visible.sync="dialog" title="部署任务设置" width="700px" @close="form={}" @onConfirm="submit">
<el-form ref="DialogForm" :model="form" size="small" label-width="120px" :rules="rules"> <el-form ref="DialogForm" :model="form" size="small" label-width="100px" :rules="rules">
<el-form-item label="项目/系统" prop="name"> <el-form-item label="项目/系统" prop="name">
<el-input v-model="form.name" clearable placeholder="请输入"/> <el-input v-model="form.name" clearable placeholder="请输入"/>
</el-form-item> </el-form-item>
<el-form-item label="系统类型" prop="type">
<ai-select v-model="form.type" :selectList="dict.getDict('systemType')"/>
</el-form-item>
<el-form-item label="打包脚本" prop="libShell"> <el-form-item label="打包脚本" prop="libShell">
<el-input v-model="form.libShell" clearable placeholder="请输入"/> <el-input v-model="form.libShell" clearable placeholder="请输入"/>
</el-form-item> </el-form-item>
<el-form-item label="更新脚本" prop="updateShell"> <el-form-item label="更新脚本" prop="updateShell">
<el-input v-model="form.updateShell" clearable placeholder="请输入"/> <el-input v-model="form.updateShell" clearable placeholder="请输入"/>
</el-form-item> </el-form-item>
<el-form-item label="开发环境nginx" prop="target"> <el-form-item label="项目URL" prop="webUrl">
<el-input v-model="form.webUrl" clearable placeholder="请输入"/>
</el-form-item>
<el-form-item label="打包地址" prop="webUrl">
<el-input v-model="form.zipPath" clearable placeholder="请输入"/>
</el-form-item>
<el-form-item label="nginx路径" prop="target">
<el-input v-model="form.target" clearable placeholder="请输入"/> <el-input v-model="form.target" clearable placeholder="请输入"/>
</el-form-item> </el-form-item>
<el-form-item label="node版本" prop="nodeVersion">
<el-input v-model="form.nodeVersion" clearable placeholder="请输入"/>
</el-form-item>
</el-form> </el-form>
</ai-dialog> </ai-dialog>
</section> </section>
@@ -62,7 +83,18 @@ export default {
permissions: Function permissions: Function
}, },
computed: { computed: {
...mapState(['user']) ...mapState(['user']),
desConfigs() {
let isLine = true
return [
{prop: "libShell", label: "打包脚本", width: 100},
{prop: "updateShell", label: "更新脚本", width: 100},
{prop: "zipPath", label: "打包地址", width: 100, isLine},
{prop: "target", label: "nginx路径", width: 100, isLine},
{prop: "webUrl", label: "项目URL", width: 100},
{prop: "nodeVersion", label: "node打包版本", width: 100},
]
},
}, },
data() { data() {
return { return {
@@ -70,10 +102,10 @@ export default {
page: {current: 1, size: 10, total: 0}, page: {current: 1, size: 10, total: 0},
tableData: [], tableData: [],
colConfigs: [ colConfigs: [
{slot: "expand"},
{label: "项目/系统名称", prop: "name", width: 200}, {label: "项目/系统名称", prop: "name", width: 200},
{label: "打包脚本", prop: "libShell"}, {label: "系统类型", prop: "type", dict: "systemType", width: 80},
{label: "更新脚本", prop: "updateShell"}, {label: "nginx路径", prop: "target"},
{label: "nginx地址", prop: "target"},
{slot: "process"}, {slot: "process"},
{slot: "options"} {slot: "options"}
], ],
@@ -81,9 +113,8 @@ export default {
form: {}, form: {},
rules: { rules: {
name: {required: true, message: "请输入项目/系统名称"}, name: {required: true, message: "请输入项目/系统名称"},
libShell: {required: true, message: "请输入 打包脚本"}, },
updateShell: {required: true, message: "请输入 更新脚本"}, timer: {}
}
} }
}, },
methods: { methods: {
@@ -118,29 +149,28 @@ export default {
}) })
}, },
handleZip(row) { handleZip(row) {
let {id} = row let {id} = row, {timer} = this
this.instance.post("/node/autodeploy/getZip", null, { this.instance.post("/node/autodeploy/getZip", null, {
params: {id} params: {id}
}).then(res => { }).then(res => {
if (res?.code == 0) { if (res?.code == 0) {
row.count = 1 row.count = 1
let timer = setInterval(() => { timer[id] = setInterval(() => {
if (row.count >= 100) { if (row.count >= 100) {
clearInterval(timer) clearInterval(timer[id])
row.count = 0
this.$message.error("打包失败!") this.$message.error("打包失败!")
} else if (row.count <= 20 && row.target) { } else if (row.count <= 10 && row.target) {
row.count++ row.count++
} else this.handleConfirmZip(row).then(v => { } else this.handleConfirmZip(row).then(v => {
if (v.error) { if (v.error) {
clearInterval(timer) clearInterval(timer[id])
this.$message.error("打包失败!") this.$message.error("打包失败!")
row.download = v.download this.refreshRow(row, v)
row.error = v.error
row.count = 0 row.count = 0
} else if (v.download) { } else if (v.download) {
clearInterval(timer) clearInterval(timer[id])
row.download = v.download this.refreshRow(row, v)
row.error = v.error
row.count = 0 row.count = 0
} else row.count++ } else row.count++
}) })
@@ -148,6 +178,23 @@ export default {
} }
}) })
}, },
refreshRow(row, v) {
row.error = v.error
row.download = v.download
row.zipTime = v.zipTime
},
handleCancelZip(row) {
let {id} = row
return this.instance.post("/node/autodeploy/cancelZip", null, {
params: {id}
}).then(res => {
if (res?.code == 0) {
clearInterval(this.timer[id])
row.count = 0
this.handleConfirmZip(row).then(v => this.refreshRow(row, v))
}
})
},
handleEdit(row) { handleEdit(row) {
this.form = JSON.parse(JSON.stringify(row)) this.form = JSON.parse(JSON.stringify(row))
this.dialog = true this.dialog = true
@@ -186,11 +233,17 @@ export default {
}) })
}, },
getProcessMsg(row) { getProcessMsg(row) {
return row.error || (row.download ? `最近打包时间:${row.download}` : `暂无打包`) let time = row.zipTime ? this.$moment(row.download).diff(row.zipTime, 's', true) : ""
return row.error || (row.download ? `最近打包时间:${row.download}(用时:${time}秒)` :
row.zipTime ? `正在打包,开始于:${row.zipTime}` : `暂无打包`)
} }
}, },
created() { created() {
this.dict.load("systemType")
this.getTableData() this.getTableData()
},
beforeDestroy() {
Object.values(this.timer).map(t => clearInterval(t))
} }
} }
</script> </script>

View File

@@ -228,5 +228,6 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.AppDeployWxmp { .AppDeployWxmp {
height: 100%;
} }
</style> </style>

View File

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

View File

@@ -0,0 +1,281 @@
<template>
<ai-detail class="form-add" :class="[currIndex === 1 ? 'form-add__active' : '']">
<ai-title title="表单配置" slot="title" isShowBottomBorder isShowBack @onBackClick="cancel(false)"></ai-title>
<template #content>
<div class="ai-step">
<div class="ai-step__item"
:class="[currIndex >= index ? 'ai-step__item--active' : '']"
v-for="(item, index) in statusList"
:key="index">
<div class="ai-step__item--icon" v-if="currIndex <= index">
<i v-if="currIndex === index"></i>
</div>
<div class="el-icon-success" v-if="currIndex > index">
</div>
<span>{{ item }}</span>
</div>
</div>
<basic-info ref="basicInfo" v-model="basicInfo" v-show="currIndex === 0" :dict="dict" :instance="instance"></basic-info>
<form-layout :appType="basicInfo.appType" :currIndex="currIndex" class="form-config__wrapper" v-model="tableInfos" ref="form" v-show="currIndex === 1" :dict="dict" :instance="instance"></form-layout>
<form-config
ref="config"
:showListFields="showListFields"
:btns="btns"
:fuzzyQueryFields="fuzzyQueryFields"
:tableInfos="tableInfos"
:orderFields="orderFields"
v-if="currIndex === 2">
</form-config>
</template>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button @click="back" v-if="currIndex > 0">上一步</el-button>
<el-button @click="next" type="primary">{{ currIndex === 2 ? '完成' : '下一步' }}</el-button>
</template>
</ai-detail>
</template>
<script>
import BasicInfo from './BasicInfo.vue'
import FormLayout from './FormLayout.vue'
import FormConfig from './FormConfig.vue'
export default {
name: 'add',
props: {
instance: Function,
dict: Object,
params: Object,
type: String
},
components: {
FormLayout,
BasicInfo,
FormConfig
},
data () {
return {
currIndex: 0,
basicInfo: {
saasPlatformId: '',
menuLeve1Style: '',
saasPlatformName: '',
menuLevel1Name: '',
menuLevel3Name: '',
menuLevel2Name: '',
appType: ''
},
orderFields: [],
showListFields: [],
fuzzyQueryFields: [],
btns: [],
btnKeys: ['insertEnable', 'importEnable', 'exportEnalbe', 'editEnable', 'deleteEnable', 'batchDelEnable'],
configInfo: {
btns: [],
orderType: '0',
fieldName: ''
},
info: {},
tableInfos: [],
statusList: ['基础设置', '表单设计', '列表设计']
}
},
mounted () {
if (this.params.id) {
this.getInfo()
}
},
methods: {
cancel (isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: isRefresh ? true : false,
isQuote: this.params.isQuote ? true : false
})
},
confirm () {
},
back () {
this.currIndex = this.currIndex - 1
},
confirmBasicInfo () {
return this.$refs.basicInfo.validate()
},
getInfo () {
this.instance.post(`/app/appapplicationinfo/queryDetailById?id=${this.params.id}`).then(res => {
if (res.code === 0) {
this.info = res.data
this.basicInfo = {
saasPlatformId: res.data.saasPlatformId,
menuLeve1Style: res.data.menuLeve1Style,
saasPlatformName: res.data.saasPlatformName,
menuLevel1Name: res.data.menuLevel1Name,
menuLevel3Name: res.data.menuLevel3Name,
menuLevel2Name: res.data.menuLevel2Name,
appType: res.data.appType,
corpId: res.data.corpId,
corpName: res.data.corpName
}
this.fuzzyQueryFields = res.data.fuzzyQueryFields
this.tableInfos = res.data.tableInfos
this.showListFields = res.data.showListFields
this.orderFields = res.data.orderFields
this.btns = Object.keys(res.data).filter(v => {
return this.btnKeys.indexOf(v) > -1 && res.data[v] === '1'
})
}
})
},
submit (info) {
this.instance.post(`/app/appapplicationinfo/addOrUpdate`, {
...this.info,
...this.basicInfo,
tableInfos: this.tableInfos,
...info.btns,
id: this.params.id,
applicationName: this.basicInfo.menuLevel3Name || this.basicInfo.menuLevel2Name,
fuzzyQueryFields: info.fuzzyQueryFields,
orderType: info.orderType,
orderFields: info.orderFields,
showListFields: info.showListFields
}).then(res => {
if (res.code === 0) {
this.$message.success(this.params.id ? '编辑成功' : '添加成功')
this.cancel(true)
}
})
},
next () {
if (this.currIndex === 0) {
if (!this.$refs.basicInfo.validate()) return
}
if (this.currIndex === 1) {
this.$refs.form.onConfirm()
}
if (this.currIndex === 2) {
const info = this.$refs.config.validate()
if (!info) return
this.submit(info)
return false
}
this.currIndex = this.currIndex + 1
}
}
}
</script>
<style lang="scss" scoped>
.form-add {
&.form-add__active {
::v-deep .ai-detail__content--wrapper {
max-width: 100%!important;
height: 100%!important;
background: #F5F6F9;
}
.form-config__wrapper {
height: calc(100% - 52px);
overflow-y: hidden;
}
::v-deep .ai-detail__content {
height: calc(100% - 114px)!important;
padding: 0!important;
overflow: hidden!important;
}
}
.ai-step {
display: flex;
align-items: center;
justify-content: center;
margin-top: 4px;
margin-bottom: 24px;
.ai-step__item {
display: flex;
position: relative;
align-items: center;
margin-right: 216px;
&.ai-step__item--active {
span {
color: #2266FF;
}
.ai-step__item--icon {
display: flex;
align-items: center;
justify-content: center;
border-color: #2266FF;
i {
width: 12px;
height: 12px;
border-radius: 50%;
background: #2266FF;
}
}
}
&:after {
position: absolute;
top: 50%;
right: -208px;
width: 200px;
height: 2px;
background: #D0D4DC;
content: ' ';
transform: translateY(-50%);
}
&:last-child {
margin-right: 0;
&::after {
display: none;
}
}
.ai-step__item--icon {
width: 24px;
height: 24px;
margin-right: 8px;
border-radius: 50%;
background: #FFFFFF;
border: 2px solid #D0D4DC;
}
.el-icon-success {
width: 24px;
height: 24px;
font-size: 24px;
margin-right: 8px;
color: #2266FF;
border-radius: 50%;
}
span {
color: #666666;
font-size: 14px;
}
}
}
}
</style>

View File

@@ -0,0 +1,151 @@
<template>
<div class="basicInfo">
<el-form ref="form" :model="form" label-width="110px" label-position="right">
<ai-card title="基本信息">
<template #content>
<div class="ai-form">
<el-form-item label="所属平台" prop="saasPlatformId" style="width: 100%;" :rules="[{ required: true, message: '请选择所属平台', trigger: 'change' }]">
<el-select
size="small"
style="width: 100%;"
placeholder="请选择所属平台"
clearable
@change="onChange"
v-model="form.saasPlatformId">
<el-option
v-for="(item, index) in sassList"
:key="index"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item v-if="form.saasPlatformId" style="width: 100%;" label="所属企业" prop="corpId" :rules="[{ required: true, message: '请选择所属企业', trigger: 'change' }]">
<ai-select
v-model="form.corpId"
placeholder="请选择所属企业"
clearable
@change="onCompanyChange"
:selectList="companyList">
</ai-select>
</el-form-item>
<el-form-item style="width: 100%;" label="一级菜单名称" prop="menuLevel1Name" :rules="[{ required: true, message: '请输入一级菜单名称', trigger: 'change' }]">
<el-input size="small" placeholder="请输入一级菜单名称" :maxlength="8" v-model="form.menuLevel1Name"></el-input>
</el-form-item>
<el-form-item style="width: 100%;" label="二级菜单名称" prop="menuLevel2Name" :rules="[{ required: true, message: '请输入二级菜单名称', trigger: 'change' }]">
<el-input size="small" placeholder="请输入二级菜单名称" :maxlength="8" v-model="form.menuLevel2Name"></el-input>
</el-form-item>
<el-form-item style="width: 100%;" label="三级菜单名称" prop="menuLevel3Name">
<el-input size="small" placeholder="请输入三级菜单名称" :maxlength="8" v-model="form.menuLevel3Name"></el-input>
</el-form-item>
<el-form-item style="width: 100%;" label="应用类型" prop="appType">
<ai-select
v-model="form.appType"
placeholder="请选择应用类型"
clearable
:selectList="dict.getDict('diyAppType')">
</ai-select>
</el-form-item>
</div>
</template>
</ai-card>
</el-form>
</div>
</template>
<script>
export default {
name: 'basicInfo',
model: {
prop: 'value',
event: 'change',
},
props: {
instance: Function,
dict: Object,
value: Object
},
data () {
return {
form: {
saasPlatformId: '',
menuLeve1Style: '',
saasPlatformName: '',
menuLevel1Name: '',
menuLevel2Name: '',
menuLevel3Name: '',
appType: '',
corpId: '',
corpName: ''
},
companyList: [],
sassList: []
}
},
watch: {
value (v) {
this.form = JSON.parse(JSON.stringify(v))
if (this.form.saasPlatformId) {
this.getCompanyList()
}
}
},
created () {
this.dict.load('diyAppType')
},
mounted () {
this.getSassList()
this.form = JSON.parse(JSON.stringify(this.value))
},
methods: {
onChange (e) {
this.form.saasPlatformName = this.sassList.filter(v => v.id === e)[0].name
this.form.saasPlatformId && this.getCompanyList()
},
validate () {
let result = false
this.$refs.form.validate(valid => {
result = valid
})
this.$emit('change', this.form)
return result
},
onCompanyChange (e) {
this.form.corpName = this.companyList.filter(v => v.dictValue === e)[0].dictName
},
getCompanyList () {
this.instance.post(`/app/appCorp/page?current=1&size=1000&saasId=${this.form.saasPlatformId}`).then(res => {
if (res.data.records.length) {
this.companyList = res.data.records.map(v => {
return {
dictValue: v.corpId,
dictName: v.name
}
})
}
})
},
getSassList () {
this.instance.post(`/app/appSaas/listAll`).then(res => {
if (res.data) {
this.sassList = res.data
}
})
}
}
}
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,244 @@
<template>
<div class="basicInfo">
<ai-card title="搜索字段" class="search-wrapper">
<template #content>
<el-checkbox-group v-model="queryFields">
<el-checkbox
:label="`${item.fieldDbName}~${item.fieldName}`"
v-for="(item, index) in tableInfos"
:key="index">
{{ item.fieldName }}
</el-checkbox>
</el-checkbox-group>
</template>
</ai-card>
<ai-card title="表格字段">
<template #content>
<div class="ai-table">
<div class="el-table el-table--border ai-header__border">
<el-scrollbar>
<table cellspacing="0" cellpadding="0" border="0" class="el-table__body">
<draggable element="thead" animation="500" class="el-table__header is-leaf ai-table__header" :sort="true" v-model="showFields">
<th v-for="(item, index) in showFields" style="background: #f3f4f5; text-align: center;" class="ai-table__header" :key="index">{{ item.fieldName }}</th>
</draggable>
<tbody element="tbody">
<tr>
<td @click="handleShow(index, item.isShow)" v-for="(item, index) in showFields" :key="index">{{ item.isShow ? '已显示' : '已隐藏' }}</td>
</tr>
</tbody>
</table>
</el-scrollbar>
</div>
</div>
</template>
</ai-card>
<ai-card title="排序和操作按钮">
<template #content>
<el-form ref="form" class="ai-form" :model="form" label-width="110px" label-position="right">
<el-form-item label="排序字段" prop="field">
<el-select
size="small"
placeholder="默认按创建时间排序"
clearable
v-model="form.field">
<el-option
v-for="(filed, index) in tableInfos"
:key="index"
:label="filed.fieldName"
:value="`${filed.fieldDbName}~${filed.fieldName}`">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="排序类型" prop="orderType" :rules="[{ required: true, message: '请输入排序类型', trigger: 'change' }]">
<el-radio-group v-model="form.orderType">
<el-radio label="asc">升序</el-radio>
<el-radio label="desc">降序</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="按钮配置" prop="btns" style="width: 100%;" :rules="[{ required: true, message: '请输入按钮配置', trigger: 'change' }]">
<el-checkbox-group v-model="form.btns">
<el-checkbox label="insertEnable">添加</el-checkbox>
<el-checkbox label="importEnable">导入</el-checkbox>
<el-checkbox label="exportEnalbe">导出</el-checkbox>
<el-checkbox label="editEnable">编辑</el-checkbox>
<el-checkbox label="deleteEnable">删除</el-checkbox>
<el-checkbox label="batchDelEnable">批量删除</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</template>
</ai-card>
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
name: 'configForm',
props: {
tableInfos: Array,
btns: Array,
showListFields: Array,
orderFields: Array,
fuzzyQueryFields: Array
},
components: {
draggable
},
data () {
return {
queryFields: [],
tableData: [{}],
btnKeys: ['insertEnable', 'importEnable', 'exportEnalbe', 'editEnable', 'deleteEnable', 'batchDelEnable'],
form: {
btns: [],
orderType: 'asc',
field: ''
},
showFields: []
}
},
mounted () {
const showIds = this.showListFields.map(v => v.fieldDbName)
this.showFields = JSON.parse(JSON.stringify(this.tableInfos)).map(item => {
item.isShow = showIds.indexOf(item.fieldDbName) > -1 ? true : false
return item
})
if (!this.showListFields.length) {
this.showFields.map(v => {
v.isShow = true
return v
})
}
this.tableInfos.map(item => {
this.tableData[0][item.fieldDbName] = '删除'
})
if (this.btns.length) {
this.form.btns = this.btns
}
const tableInfosIds = this.tableInfos.map(v => v.fieldDbName)
if (this.orderFields.length) {
let arr = this.orderFields.filter(v => {
return tableInfosIds.indexOf(v.fieldDbName) > -1
}).map(item => {
return `${item.fieldDbName}~${item.fieldName}`
})
if (arr.length) {
this.form.field = arr[0]
}
}
if (this.fuzzyQueryFields.length) {
this.queryFields = this.fuzzyQueryFields.filter(v => {
return tableInfosIds.indexOf(v.fieldDbName) > -1
}).map(item => {
return `${item.fieldDbName}~${item.fieldName}`
})
}
},
methods: {
validate () {
let result = false
this.$refs.form.validate(valid => {
result = valid
})
if (!result) {
return false
}
const btns = {}
this.btnKeys.forEach(item => {
btns[item] = this.form.btns.indexOf(item) > -1 ? 1 : 0
})
return {
btns,
orderFields: [{
fieldName: this.form.field.split('~')[1],
fieldDbName: this.form.field.split('~')[0],
orderType: this.form.orderType
}],
showListFields: this.showFields.filter(v => v.isShow).map((v, index) => {
return {
fieldName: v.fieldName,
fieldDbName: v.fieldDbName,
showListIndex: index
}
}),
fuzzyQueryFields: this.queryFields.map(v => {
return {
fieldName: v.split('~')[1],
fieldDbName: v.split('~')[0]
}
})
}
},
handleShow (index, isShow) {
const total = this.showFields.map(v => v.isShow).filter(v => !!v)
if (total.length <= 1 && isShow) {
return this.$message.error('表格列数不能小于1')
}
this.$set(this.showFields[index], 'isShow', !isShow)
}
}
}
</script>
<style scoped lang="scss">
.basicInfo {
.search-wrapper {
::v-deep .el-checkbox-group {
display: flex;
flex-wrap: wrap;
.el-checkbox {
width: 16.66%;
margin-bottom: 10px;
}
}
}
}
.ai-table {
.el-table--border {
border: 1px solid #d0d4dc;
border-bottom: none;
border-right: none;
}
table {
min-width: 100%;
}
th, tr {
min-width: 100px;
cursor: move;
}
tr {
td {
cursor: pointer;
color: #26f;
text-align: center;
user-select: none;
&:hover {
opacity: 0.7;
}
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,272 @@
export const components = [
{
type: 'info',
tips: '(不能重复添加同一元素)',
label: '信息',
children: [
{
type: 'name',
fieldName: '姓名',
fieldTips: '请输入姓名',
fixedLabel: '姓名',
disable: '0',
grid: 0.5,
defaultValue: '',
icon: 'icontext_box',
mustFill: '1',
maxLength: 20
},
{
type: 'idNumber',
fieldName: '身份证号',
fixedLabel: '身份证号',
fieldTips: '请输入身份证号',
defaultValue: '',
icon: 'icontext_area',
mustFill: '1',
maxLength: 20,
disable: '0',
grid: 0.5
},
{
type: 'phone',
fieldName: '联系方式',
fixedLabel: '联系方式',
fieldTips: '请输入联系方式',
defaultValue: '',
icon: 'icontext_area',
mustFill: '1',
maxLength: 20,
disable: '0',
grid: 0.5
},
{
type: 'area',
fieldName: '地区',
fixedLabel: '地区',
fieldTips: '请选择地区',
defaultValue: '',
icon: 'icontext_area',
mustFill: '1',
areaPattern: '',
disable: '0',
grid: 0.5
}
]
},
{
type: 'options',
tips: '(可重复添加)',
label: '选项',
children: [
{
type: 'radio',
fieldName: '单选',
fixedLabel: '单选',
fieldTips: '请选择',
grid: 0.5,
icon: 'iconradio',
mustFill: '1',
disable: '0',
defaultValue: '',
options: [
{
label: '选项1',
value: ''
},
{
label: '选项2',
value: ''
}
],
title: ''
},
{
type: 'checkbox',
fieldName: '多选',
fixedLabel: '多选',
fieldTips: '请选择',
icon: 'iconcheck_box',
mustFill: '1',
grid: 0.5,
disable: '0',
defaultValue: [],
options: [
{
label: '选项1',
value: ''
},
{
label: '选项2',
value: ''
}
],
title: ''
},
{
type: 'select',
fieldName: '单下拉框',
fixedLabel: '单下拉框',
grid: 0.5,
fieldTips: '请选择',
icon: 'iconSelect',
mustFill: '1',
defaultValue: '',
disable: '0',
options: [
{
label: '选项1',
value: ''
},
{
label: '选项2',
value: ''
}
],
title: ''
},
{
type: 'onOff',
fieldName: '开关',
fixedLabel: '开关',
grid: 0.5,
fieldTips: '请选择开关',
icon: 'iconSelect',
mustFill: '1',
defaultValue: '0',
disable: '0',
title: ''
},
{
type: 'date',
fieldName: '日期',
fixedLabel: '日期',
grid: 0.5,
datetimePattern: 'yyyy-MM-dd',
fieldTips: '请选择日期',
icon: 'iconSelect',
mustFill: '1',
disable: '0',
title: ''
},
{
type: 'time',
fieldName: '时间',
fixedLabel: '时间',
grid: 0.5,
datetimePattern: 'HH:mm:ss',
fieldTips: '请选择时间',
icon: 'iconSelect',
mustFill: '1',
disable: '0',
title: ''
},
{
type: 'datetime',
fieldName: '日期时间',
fixedLabel: '日期时间',
grid: 0.5,
datetimePattern: 'yyyy-MM-dd HH:mm:ss',
fieldTips: '请选择日期时间',
icon: 'iconSelect',
mustFill: '1',
disable: '0',
title: ''
}
]
},
{
type: 'input',
tips: '(可重复添加)',
label: '填空',
children: [
{
type: 'input',
fieldName: '单行填空',
fieldTips: '请输入',
fixedLabel: '单行填空',
disable: '0',
grid: 0.5,
defaultValue: '',
icon: 'icontext_box',
mustFill: '1',
maxLength: 50
},
{
type: 'textarea',
fieldName: '多行填空',
fixedLabel: '多行填空',
fieldTips: '请输入',
lineNumber: 4,
defaultValue: '',
icon: 'icontext_area',
mustFill: '1',
maxLength: 500,
disable: '0',
grid: 1
},
{
type: 'number',
fieldName: '数字输入',
fixedLabel: '数字输入',
fieldTips: '请输入数字',
defaultValue: '',
icon: 'icontext_area',
mustFill: '1',
maxValue: 10000,
decimalPlaces: 0,
minValue: 0,
maxLength: 500,
disable: '0',
grid: 0.5
},
{
type: 'rtf',
fieldName: '富文本',
fixedLabel: '富文本',
fieldTips: '请输入',
defaultValue: '',
icon: 'icontext_area',
mustFill: '1',
maxLength: 5000,
disable: '0',
grid: 1
}
]
},
{
type: 'annex',
tips: '(可重复添加)',
label: '附件',
children: [
{
type: 'upload',
fieldTips: '请上传',
fieldName: '上传附件',
fixedLabel: '上传附件',
disable: '0',
fileChoseSize: 10,
fileMaxCount: 9,
defaultValue: '',
icon: 'iconpic',
mustFill: '1',
grid: 1
}
]
},
{
type: 'layout',
tips: '(可重复添加)',
label: '分组',
children: [
{
type: 'group',
fieldName: '卡片',
fixedLabel: '卡片',
icon: 'iconpic',
groupName: '分组标题',
column: []
}
]
}
];

View File

@@ -0,0 +1,62 @@
<template>
<div class="AppHealthReport">
<keep-alive :include="['List']">
<component ref="component" :is="component" @change="onChange" :params="params" :instance="instance" :dict="dict"></component>
</keep-alive>
</div>
</template>
<script>
import List from './components/List.vue'
import Detail from './components/Detail.vue'
export default {
name: 'AppIntegralAudit',
label: '积分审核(三涧溪)',
components: {
List,
Detail
},
props: {
instance: Function,
dict: Object,
permissions: Function
},
data () {
return {
component: 'List',
params: {}
}
},
methods: {
onChange (data) {
if (data.type === 'Detail') {
this.component = 'Detail'
this.isShowDetail = true
this.params = data.params
}
if (data.type === 'list') {
this.component = 'List'
this.params = data.params
this.$nextTick(() => {
if (data.isRefresh) {
this.$refs.component.getList()
}
})
}
}
}
}
</script>
<style lang="scss" scoped>
.AppHealthReport {
height: 100%;
}
</style>

View File

@@ -0,0 +1,269 @@
<template>
<ai-detail class="audit">
<template slot="title">
<ai-title title="详情" isShowBack isShowBottomBorder @onBackClick="cancel(true)">
</ai-title>
</template>
<template slot="content">
<ai-card title="基本信息">
<template #content>
<ai-wrapper
label-width="120px">
<ai-info-item label="申请人" :value="info.residentName"></ai-info-item>
<ai-info-item label="申请时间" :value="info.createTime"></ai-info-item>
<ai-info-item label="积分类型" :value="dict.getLabel('atWillReportType', info.applyIntegralType)"></ai-info-item>
<ai-info-item label="规则类型" :value="info.residentName"></ai-info-item>
<ai-info-item label="申请描述" :value="info.description" isLine></ai-info-item>
<!-- <ai-info-item label="联系电话" isLine :value="info.residentPhone"></ai-info-item> -->
<ai-info-item label="图片资料" isLine v-show="info.applyFiles">
<ai-uploader v-model="info.applyFiles" disabled></ai-uploader>
</ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="审核详情" v-if="info.auditStatus !== '0'">
<div slot="content" style="margin-top: 16px;margin-bottom:24px">
<ai-wrapper
label-width="120px">
<ai-info-item label="审核结果" :value="info.auditStatus === '1' ? '通过' : '拒绝'"></ai-info-item>
<ai-info-item label="积分调整" isLine v-if="info.auditStatus === '1'"
:value="(info.auditIntegral >= 0 ? info.auditIntegral : info.auditIntegral) + '分'"></ai-info-item>
<ai-info-item label="审核人" :value="info.auditUserName"></ai-info-item>
<ai-info-item label="审核时间" :value="info.auditTime"></ai-info-item>
<ai-info-item label="审核意见" v-if="info.auditStatus === '2'" isLine :value="info.auditOpinion"></ai-info-item>
<!-- <ai-info-item label="积分规则类别" v-if="info.auditStatus === '1'" :value="dict.getLabel('atWillReportType', info.auditIntegralType)"></ai-info-item> -->
<!-- <ai-info-item label="积分规则事项" v-if="info.auditStatus === '1'" :value="info.auditRuleName"></ai-info-item> -->
</ai-wrapper>
</div>
</ai-card>
<ai-dialog
:visible.sync="isShow"
width="800px"
@close="onClose"
title="事件审核"
@onConfirm="onConfirm">
<el-form class="ai-form" label-width="120px" :model="form" ref="form">
<el-form-item label="是否通过审核" prop="pass" style="width: 100%;" :rules="[{ required: true, message: '请选择是否通过审核' }]">
<el-radio-group v-model="form.pass" @change="onStatusChange">
<el-radio label="0"></el-radio>
<el-radio label="1"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="积分分值" prop="pass" style="width: 100%;" :rules="[{ required: true, message: '请输入积分分值' }]">
<el-input v-model="num" placeholder="请输入1-10的整数"></el-input>
</el-form-item>
<el-form-item label="积分分值" prop="pass" style="width: 100%;" :rules="[{ required: true, message: '请输入积分分值' }]">
<el-input v-model="num" placeholder="请输入1-10的整数"></el-input>
</el-form-item>
<el-form-item label="加分项" prop="pass" style="width: 100%;" :rules="[{ required: true, message: '请选择加分项' }]">
<!-- <ai-select v-model="form.integralCalcType" :selectList="dict.getDict('integralCalcType')"/> -->
</el-form-item>
<el-form-item label="积分分值" prop="pass" style="width: 100%;" :rules="[{ required: true, message: '请输入积分分值' }]">
<el-input v-model="num" placeholder="请输入1-10的整数"></el-input>
</el-form-item>
<!-- <el-form-item v-if="form.pass === '1'" label="积分调整" prop="auditIntegral" style="width: 100%;" :rules="[{ required: true, message: '请输入积分调整' }]">
<el-input v-model.number="form.auditIntegral" clearable placeholder="请输入积分调整" @keyup.native="form.auditIntegral=Math.abs(form.auditIntegral)"/>
</el-form-item> -->
<!-- <el-form-item v-if="form.pass === '1'" label="积分规则类别" prop="auditRuleId" style="width: 100%;" :rules="[{ required: true, message: '' }]">
<div class="flex-warpper">
<el-form-item label-width="0" prop="auditIntegralType" :rules="[{ required: true, message: '请选择积分规则类别' }]">
<ai-select
v-model="form.auditIntegralType" clearable
style="width: 180px;"
placeholder="请选择积分规则类别"
:selectList="dict.getDict('atWillReportType')"
@change="onChange">
</ai-select>
</el-form-item>
<el-form-item style="margin: 0 10px;" prop="auditRuleId" :rules="[{ required: true, message: '请选择积分规则事项' }]">
<ai-select
v-model="form.auditRuleId"
clearable
style="width: 180px;"
placeholder="请选择积分规则事项"
:selectList="ruleList">
</ai-select>
</el-form-item>
<span>{{ integralText }}</span>
</div>
</el-form-item> -->
<el-form-item label="审核意见" v-if="form.pass === '0'" prop="opinion" style="width: 100%;" :rules="[{ required: true, message: '请输入审核意见' }]">
<el-input type="textarea" :rows="5" :maxlength="200" v-model="form.opinion" clearable placeholder="请输入审核意见" show-word-limit></el-input>
</el-form-item>
</el-form>
</ai-dialog>
</template>
<template #footer v-if="info.auditStatus === '0'">
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="isShow = true">审核</el-button>
</template>
</ai-detail>
</template>
<script>
export default {
name: 'Detail',
props: {
instance: Function,
dict: Object,
params: Object
},
data() {
const validatorRules = function (rule, value, callback) {
if (value === '') {
callback(new Error('请输入联系方式'))
} else if (!/^1\d{10}$/.test(value)) {
callback(new Error('手机号格式错误'))
} else {
callback()
}
}
return {
total: 0,
info: {
auditStatus: '0'
},
id: '',
isShow: true,
form: {
auditIntegralType: '',
auditRuleId: '',
opinion: '',
pass: ''
},
ruleList: [],
num: ''
}
},
computed: {
integralText() {
if (!this.form.auditRuleId) {
return ''
}
const integral = this.ruleList.filter(v => v.dictValue === this.form.auditRuleId)[0].integral
return integral >= 0 ? `+${integral}` : `${integral}`
}
},
created() {
let loading = this.$loading({
text: 'Loading',
});
if (this.params && this.params.id) {
this.id = this.params.id
this.dict.load(['atWillReportType', 'auditStatus']).then(() => {
this.getInfo(this.params.id)
this.$nextTick(() => {
loading.close()
})
})
}
},
methods: {
getInfo(id) {
this.instance.post(`/app/appvillagerintegraldeclare/queryDetailById?id=${id}`).then(res => {
if (res.code === 0) {
this.info = res.data
}
})
},
onStatusChange() {
this.$refs.form.clearValidate()
},
onClose() {
this.form.auditIntegralType = ''
this.form.auditRuleId = ''
this.form.pass = ''
this.form.opinion = ''
this.id = ''
},
onConfirm() {
this.$refs.form.validate(v => {
if (v) {
this.instance.post('/app/appvillagerintegraldeclare/examine', null, {
params: {
...this.form,
id: this.params.id,
}
}).then(res => {
if (res.code == 0) {
this.isShow = false
this.getInfo(this.params.id)
this.$message.success('审核成功!')
}
})
}
})
},
onChange(e) {
this.form.auditRuleId = ''
this.instance.post(`/app/appvillagerintegralrule/list?size=1000&classification=${e}`).then(res => {
if (res?.code == 0) {
this.ruleList = res.data.records.filter(v => v.ruleStatus === '1').map(v => {
return {
dictName: v.ruleName,
dictValue: v.id,
ruleName: v.ruleName,
integral: v.integral
}
})
}
})
},
cancel(isRefresh) {
this.$emit('change', {
type: 'list',
isRefresh: !!isRefresh
})
}
}
}
</script>
<style scoped lang="scss">
.audit {
.flex-warpper {
display: flex;
align-items: center;
::v-deep .el-form-item .el-form-item__content {
margin-left: 0 !important;
}
::v-deep .ai-select {
margin: 0 !important;
}
::v-deep .el-form-item {
width: auto;
margin-bottom: 0;
&:last-child {
margin-right: 10px;
}
}
}
}
</style>

View File

@@ -0,0 +1,236 @@
<template>
<ai-list class="list">
<ai-title
slot="title"
title="积分审核"
v-if="search.areaId"
isShowBottomBorder
:instance="instance"
:disabledLevel="disabledLevel"
isShowArea
v-model="search.areaId"
@change="changeArea">
</ai-title>
<template slot="content">
<div class="content">
<ai-search-bar>
<template #left>
<!-- <ai-select
v-model="search.applyIntegralType"
clearable
placeholder="请选择积分类型"
:selectList="dict.getDict('atWillReportType')"
@change="search.current = 1, getList()">
</ai-select> -->
<ai-select
v-model="search.auditStatus"
clearable
placeholder="请选择审核状态"
:selectList="dict.getDict('auditStatus')"
@change="search.current = 1, getList()">
</ai-select>
<el-date-picker
value-format="yyyy-MM-dd"
v-model="search.createTimeStart"
type="date"
size="small"
unlink-panels
placeholder="选择开始日期"
@change="search.current = 1, getList()" />
<el-date-picker
value-format="yyyy-MM-dd"
v-model="search.createTimeEnd"
type="date"
size="small"
unlink-panels
placeholder="选择结束日期"
@change="search.current = 1, getList()" />
</template>
<template #right>
<el-input
v-model="search.residentName"
size="small"
placeholder="请输入姓名/事件类型"
clearable
v-throttle="() => {search.current = 1, getList()}"
@clear="search.current = 1, search.residentName = '', 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: 8px;"
:current.sync="search.current"
:size.sync="search.size"
@getList="getList">
<el-table-column slot="options" width="120px" fixed="right" label="操作" align="center">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" @click="toDetail(row.id)">详情</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</div>
</template>
</ai-list>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'List',
props: {
instance: Function,
dict: Object
},
data () {
return {
search: {
current: 1,
size: 10,
residentName: '',
applyIntegralType: '',
areaId: '',
auditStatus: '',
createTimeStart: '',
createTimeEnd: ''
},
dictList: [{
dictName: '否',
dictValue: '0'
}, {
dictName: '是',
dictValue: '1'
}],
info: {},
colConfigs: [
{ prop: 'residentName', label: '申请人' },
{ prop: 'residentPhone', align: 'center', label: '联系电话' },
{ prop: 'createTime', align: 'center', label: '申请时间' },
{ prop: 'applyIntegralType', align: 'center', label: '积分类型', formart: v => this.dict.getLabel('atWillReportType', v) },
{ prop: 'auditStatus', align: 'center', label: '状态', formart: v => v ? this.dict.getLabel('auditStatus', v) : '-' },
{ prop: 'auditUserName', align: 'center', label: '审批人' },
{ prop: 'auditTime', align: 'center', label: '审批时间' }
],
tableData: [],
total: 0,
loading: false,
disabledLevel: 0
}
},
computed: {
...mapState(['user']),
param () {
return {
}
}
},
created () {
this.disabledLevel = this.user.info.areaList.length - 1
this.search.areaId = this.user.info.areaId
this.loading = true
this.dict.load(['atWillReportType', 'auditStatus']).then(() => {
this.getList()
})
},
methods: {
getList () {
this.instance.post(`/app/appvillagerintegraldeclare/list`, null, {
params: {
...this.search
}
}).then(res => {
if (res.code == 0) {
this.tableData = res.data.records
this.total = res.data.total
this.loading = false
} else {
this.loading = false
}
}).catch(() => {
this.loading = false
})
},
toDetail (id) {
this.$emit('change', {
type: 'Detail',
params: {
id: id || ''
}
})
},
changeArea () {
this.search.current = 1
this.$nextTick(() => {
this.getList()
})
}
}
}
</script>
<style scoped lang="scss">
.list {
::v-deep .ai-list__content {
padding: 0!important;
.ai-list__content--right-wrapper {
background: transparent!important;
box-shadow: none!important;
margin: 0!important;
padding: 12px 16px 12px!important;
}
}
.statistics-top {
display: flex;
align-items: center;
margin-bottom: 20px;
& > div {
flex: 1;
height: 96px;
line-height: 1;
margin-right: 20px;
padding: 16px 24px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
border-radius: 4px;
&:last-child {
margin-right: 0;
}
h3 {
font-size: 24px;
}
span {
display: block;
margin-bottom: 16px;
color: #888888;
font-size: 16px;
}
}
}
.content {
padding: 16px;
background: #FFFFFF;
box-shadow: 0px 4px 6px -2px rgba(15, 15, 21, 0.15);
}
}
</style>

View File

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

View File

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

View File

@@ -0,0 +1,175 @@
<template>
<section class="psList">
<ai-list>
<ai-title slot="title" title="党员积分" isShowBottomBorder/>
<template #left>
<ai-tree-menu title="组织目录" searchPlaceholder="请输入党组织名称" @search="onSearch">
<ai-party-tree
:filter-node-method="filterNode"
ref="tree"
:instance="instance"
:root="user.info.organizationId"
:current-node-key="user.info.id"
@select="onTreeChange"/>
</ai-tree-menu>
</template>
<template #content>
<ai-search-bar>
<template #left>
<div>统计周期</div>
<el-date-picker type="daterange" placeholder="日期" size="small" clearable v-model="createTime"
@change="handleSearchTime" start-placeholder="开始时间" end-placeholder="结束时间"
value-format="yyyy-MM-dd HH:mm:ss" :default-time="['00:00:00','23:59:59']"/>
</template>
<template #right>
<el-input size="small" placeholder="党员姓名" v-model="search.partyName" clearable
@change="page.current=1,getTableData()" suffix-icon="iconfont iconSearch"/>
<ai-download :instance="instance" url="/app/apppartyintegralinfo/downloadTemplate" :params="search" fileName="党员积分" :disabled="tableData.length == 0">
<el-button icon="iconfont iconExported" :disabled="tableData.length == 0">导出</el-button>
</ai-download>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size"
@getList="getTableData" :col-configs="colConfigs" :dict="dict">
<el-table-column slot="options" label="操作" fixed="right" align="center">
<template slot-scope="{row}">
<el-button type="text" @click="getFamilyByPartyId(row.idNumber)">家庭成员</el-button>
<el-button type="text" @click="showDetail(row.id)">详情</el-button>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<ai-dialog :visible.sync="dialog" title="家庭成员" :customFooter="true" width="780px" @close="familyList=[]">
<ai-table :tableData="familyList" :isShowPagination="false" :col-configs="familyCols" :dict="dict"/>
<div class="dialog-footer" slot="footer">
<el-button @click="dialog=false"> </el-button>
</div>
</ai-dialog>
</section>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "psList",
props: {
instance: Function,
dict: Object,
},
computed: {
...mapState(['user']),
colConfigs() {
return [
{label: "排行榜", prop: "name", align: "center"},
{label: "党员姓名", prop: "name", align: "center"},
{label: "党员类型", prop: "name", align: "center"},
{label: "获得积分", prop: "name", align: "center"},
// {label: "个人积分", prop: "integral", align: "center"},
// {label: "家庭积分", prop: "familySurplusIntegral", align: "center"},
// {label: "学习强国", prop: "learningIntegral", align: "center"},
{slot: "options"}
]
},
familyCols() {
return [
{
label: '与户主关系', prop: 'householdRelation', align: 'center', width: 165,
render: (h, {row}) => h('p', this.dict.getLabel('householdRelation', row.householdRelation || "户主"))
},
{label: '类型', prop: 'residentType', align: 'center', dict: "residentType"},
{label: '姓名', prop: 'name', align: 'center'},
{label: '身份证号', render: (h, {row}) => h('p', this.idCardNoUtil.hideId(row.idNumber)), width: 165},
{label: '联系电话', prop: 'phone', align: 'center', width: 120}
]
}
},
data() {
return {
search: {
createTime: '',
},
page: {current: 1, size: 10, total: 0},
tableData: [],
dialog: false,
familyList: [],
organizationName: '',
organizationTree: [],
defaultExpanded: [],
defaultChecked: [],
areaTree: [],
defaultProps: {
children: 'children',
label: 'name'
},
createTime: '',
}
},
methods: {
back() {
this.$router.push({})
},
getTableData() {
this.instance.post("/app/appparty/listByPartyIntegral", null, {
params: {...this.page, ...this.search}
}).then(res => {
if (res?.data) {
this.tableData = res.data?.records
this.page.total = res.data.total
}
})
},
onSearch(v) {
this.orgTree.filter(v)
},
onTreeChange(e) {
this.search.areaId = e.id
this.areaName = e.name
this.search.current = 1
this.$nextTick(() => {
this.getList()
})
},
filterNode(value, data) {
if (!value) return true
return data.name.indexOf(value) !== -1
},
handleSearchTime(v) {
// this.page.current = 1
// this.search.startTime = v?.[0]
// this.search.endTime = v?.[1]
// this.getTableData()
},
showDetail(id) {
this.$router.push({query: {id}})
},
getFamilyByPartyId(idNumber) {
this.instance.post("/app/appresident/queryHomeMember", null, {
params: {idNumber}
}).then(res => {
if (res?.data) {
this.familyList = res.data?.family || []
this.dialog = true
}
})
}
},
created() {
this.getTableData()
}
}
</script>
<style lang="scss" scoped>
.psList {
height: 100%;
}
</style>

View File

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

View File

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

View File

@@ -0,0 +1,355 @@
<template>
<section class="AppScoreRules">
<ai-list v-if="permissions('app_appvillagerintegralrule_detail')">
<template slot="title">
<ai-title title="积分规则" isShowBottomBorder></ai-title>
</template>
<template slot="content">
<ai-search-bar bottomBorder>
<template slot="left">
<!-- <el-cascader size="small" v-model="search.eventType" placeholder="请选择事件/类型" clearable
:props="{...etOps,checkStrictly:true}" @change="handleTypeSearch" ref="eventTypeSearch"/> -->
<ai-select
v-model="search.status"
@change="page.current = 1, getList()"
placeholder="请选择状态"
:selectList="dict.getDict('integralRuleStatus')">
</ai-select>
</template>
<template slot="right">
<el-input size="small" placeholder="事件名称" v-model="search.partyName" clearable
@change="page.current=1,getTableData()" suffix-icon="iconfont iconSearch"/>
</template>
</ai-search-bar>
<ai-search-bar style="margin-top: 16px;">
<template #left>
<el-button type="primary" icon="iconfont iconAdd" @click="dialog=true">添加</el-button>
</template>
</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="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 v-else>{{ row.integral > 0 ? '+' : '' }}{{ row.integral }}</span>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" align="center" fixed="right" width="200">
<template slot-scope="{ row }">
<div class="table-options">
<el-button type="text" :disabled="!permissions('app_appvillagerintegralrule_edit')" @click="changeStatus(row.id, 0)" v-if="row.status == 1">
停用
</el-button>
<el-button type="text" :disabled="!permissions('app_appvillagerintegralrule_edit')" @click="changeStatus(row.id, 1)" v-else>启用</el-button>
<el-button type="text" :disabled="!permissions('app_appvillagerintegralrule_edit')" @click="toEdit(row)">编辑</el-button>
<el-button type="text" :disabled="!permissions('app_appvillagerintegralrule_del')" @click="remove(row.id)">删除</el-button>
</div>
</template>
</el-table-column>
</ai-table>
</template>
</ai-list>
<ai-empty v-else>暂无应用权限</ai-empty>
<ai-dialog :title="dialogTitle" :visible.sync="dialog" @onConfirm="onConfirm" @closed="form={ladderRule: []}" width="800px">
<div class="form_div">
<el-form ref="DialogForm" :model="form" :rules="formRules" size="small" label-suffix="" label-width="100px">
<!-- <el-form-item label="事件/类型" prop="eventType">
<el-cascader v-model="form.eventType" :props="etOps" clearable placeholder="请选择" @change="handleTypeForm"
:options="cacheOps"/>
</el-form-item> -->
<el-form-item label="事件名称" prop="eventType">
<el-input v-model="form.name" clearable placeholder="请输入事件名称" type="text" maxlength="30" show-word-limit/>
</el-form-item>
<el-form-item label="简介说明" prop="eventType">
<el-input v-model="form.name" clearable placeholder="请输入简介说明" type="text" maxlength="100" show-word-limit/>
</el-form-item>
<el-form-item label="规则" prop="ruleType" required>
<el-radio-group v-model.trim="form.pass" @change="typeChange">
<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="周期范围" prop="scoringCycle">
<ai-select v-model="form.scoringCycle" :selectList="dict.getDict('integralRuleScoringCycle')"/>
</el-form-item>
<el-form-item label="奖励次数">
<el-input placeholder="请输入,周期范围内,不填写表示不限制" v-model.number="form.numberLimit" clearable/>
</el-form-item>
<el-form-item label="积分分值" prop="integral" v-if="form.pass == 0">
<el-input placeholder="请输入" v-model="form.integral" clearable/>
</el-form-item>
<el-form-item label="积分分值" prop="integral" v-if="form.pass == 1">
<ai-range v-model="form.num" ref="resetagefoo" />
</el-form-item>
<el-form-item label="积分分值" prop="integral" v-if="form.pass == 2">
<el-button type="text" @click="innerVisible = true" style="float: right;">添加</el-button>
<ai-table :tableData="integralData" :isShowPagination="false" :col-configs="integralConfigs" :dict="dict">
<el-table-column slot="options" align="center" label="操作" fixed="right" width="160px">
<el-row type="flex" justify="center" align="middle" slot-scope="{row}">
<el-button v-if="$permissions('admin_sysuser_distribute')&&!!row.sysUserId"
type="text" @click="appAllot(row)">删除
</el-button>
</el-row>
</el-table-column>
</ai-table>
</el-form-item>
<el-dialog width="30%" title="添加积分分值" :visible.sync="innerVisible" append-to-body>
<el-form ref="innerForm" :model="table" :rules="formRules" size="small" label-suffix="" label-width="100px">
<el-form-item label="加分项" prop="integral">
<el-input placeholder="请输入" v-model="form.integral" clearable/>
</el-form-item>
<el-form-item label="获得积分" prop="integral">
<el-input placeholder="请输入" v-model="form.integral" clearable/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="innerVisible = false"> </el-button>
<el-button type="primary" @click="save">保存</el-button>
</div>
</el-dialog>
</el-form>
</div>
</ai-dialog>
</section>
</template>
<script>
export default {
name: "AppScoreRules",
label: "积分规则(三涧溪)",
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
isEdit() {
return !!this.form.id
},
dialogTitle() {
return this.isEdit ? "编辑积分规则" : "添加积分规则"
},
etOps() {
return {
lazy: true,
value: "dictValue",
label: "dictName",
lazyLoad: (node, resolve) => {
if (node.level == 0) resolve(this.dict.getDict('integralRuleEvent'))
else if (node.level == 1) {
let dict = 'integralRuleEvent' + node.value
this.dict.load(dict).then(() => {
let nodes = this.dict.getDict(dict).map(e => ({...e, leaf: true}))
resolve(nodes)
})
}
}
}
},
},
data() {
return {
search: {status: "", eventType: null},
page: {current: 1, size: 10, total: 0},
colConfigs: [
{prop: "event", label: "事件", dict: "integralRuleEvent"},
{prop: "type", label: "类型", dict: "integralRuleEventType"},
{prop: "ruleType", label: "规则", dict: "integralRuleRuleType"},
{prop: "scoringCycle", label: "周期范围", dict: "integralRuleScoringCycle"},
{prop: "status", label: "状态", align: "center", width: 96, dict: "integralRuleStatus"},
{slot: "options", label: "操作", align: "center"},
],
integralConfigs: [
{prop: "event", label: "加分项", dict: "integralRuleEvent"},
{prop: "event", label: "获得积分", dict: "integralRuleEvent"},
{slot: "options", label: "操作", align: "center"},
],
tableData: [],
dialog: false,
form: {
ladderRule: [],
pass: '0',
},
formRules: {
eventType: [{required: true, message: "请选择事件/类型", trigger: "change"}],
scoringCycle: [{required: true, message: "请选择周期范围", trigger: "change"}],
integral: [{required: true, pattern: /^\d*[.\d]\d?$/, message: "请输入积分分值,最多保留一位小数"}],
numberLimit: [{pattern: /^\d*$/, message: "请输入正整数"}]
},
cacheOps: [],
integralFrom: '',
integralTo: '',
integralData: [],
innerVisible: false,
table: {},
};
},
created() {
this.dict.load("integralRuleStatus", "integralRuleRuleType", 'integralRuleScoringCycle', 'integralRuleEvent', 'integralRuleEventType').then(() => {
this.getList();
});
},
methods: {
getList() {
this.instance.post(`/app/appvillagerintegralrule/list`, null, {
params: {...this.search, ...this.page},
}).then(res => {
if (res?.data) {
this.tableData = res.data.records;
this.page.total = res.data.total;
}
});
},
toEdit(row) {
this.form = this.$copy(row)
let {ladderRule, event, type} = this.form,
dict = 'integralRuleEvent' + event
this.dict.load(dict).then(() => {
this.form.eventType = [event, type]
this.form.ladderRule = JSON.parse(ladderRule || "[]")
this.cacheOps = this.dict.getDict('integralRuleEvent').map(e => {
if (e.dictValue == event) {
e.children = this.dict.getDict(dict).map(d => ({...d, leaf: true}))
}
return e
})
this.$nextTick(() => {
this.dialog = true
})
})
},
// 保存
save() {
this.$refs.innerForm.validate((valid)=>{
if (valid) {
this.innerData.push(this.table)
}
this.innerVisible = false
})
},
typeChange(val) {
this.form.pass = val
},
handleInputFrom(event) {
this.$emit('focusfrom', event)
},
handleInputTo(event) {
this.$emit('blurto', event)
},
remove(id) {
this.$confirm("删除后不可恢复,是否要删除该事项?", {
type: 'error'
}).then(() => {
this.instance
.post(`/app/appvillagerintegralrule/delete?ids=${id}`)
.then((res) => {
if (res.code == 0) {
this.$message.success("删除成功!");
this.getList();
}
});
});
},
changeStatus(id, status) {
let text = status == 1 ? '启用' : '停用'
this.$confirm(`确定${text}该条规则?`).then(() => {
this.instance.post(`/app/appvillagerintegralrule/enableOrDisable?id=${id}`).then((res) => {
if (res.code == 0) {
this.$message.success(`${text}成功!`)
this.getList();
}
});
});
},
onReset() {
this.page.current = 1
this.search.classification = ""
this.search.integralType = ""
this.search.ruleStatus = ""
this.getList();
},
onConfirm() {
if(this.form.ruleType==1 && !this.form.ladderRule.length) {
return this.$message.error('请添加规则')
}
this.$refs.DialogForm.validate((valid) => {
if (valid) {
let formData = this.$copy(this.form)
formData.ladderRule = JSON.stringify(formData.ladderRule)
formData.integral = formData.integral || 0
this.instance.post(`/app/appvillagerintegralrule/addOrUpdate`, formData).then((res) => {
if (res.code == 0) {
this.$message.success(`${this.isEdit ? '编辑成功' : '添加成功'}`)
this.onReset()
this.dialog = false;
}
});
} else {
return false;
}
});
},
handleTypeSearch(v) {
this.search.event = v?.[0]
this.search.type = v?.[1]
this.page.current = 1
this.$refs.eventTypeSearch.dropDownVisible = false
this.getList()
},
handleTypeForm(v) {
if (this.dialog) {
this.form.event = v?.[0]
this.form.type = v?.[1]
this.form.ruleType = !this.form.event ? null : this.form.event == 3 ? 1 : 0
}
},
handleDelete(i) {
this.$confirm("是否要删除该规则?").then(() => {
this.form.ladderRule.splice(i, 1)
}).catch(() => 0)
},
checkIntegral(v) {
return /\.\d{2,}$/.test(v) ? Math.abs(v).toFixed(1) : Math.abs(v)
}
},
};
</script>
<style lang="scss" scoped>
.AppScoreRules {
height: 100%;
background: #f3f6f9;
}
</style>