This commit is contained in:
qiuyuan 2025-11-05 09:37:23 +08:00
commit 120742819a
58 changed files with 4071 additions and 734 deletions

View File

@ -1,87 +1,89 @@
<!DOCTYPE html> <!doctype html>
<html lang="zh-cn"> <html lang="zh-cn">
<head>
<meta charset="UTF-8" />
<link
rel="icon"
href="/logo-hahayun.png" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0" />
<title></title>
</head>
<head> <body>
<meta charset="UTF-8" /> <div id="app">
<link rel="icon" <div class="app-loading">
href="/logo-hahayun.png" /> <div class="app-loading__loader"></div>
<meta name="viewport" <div class="app-loading__title">正在加载资源</div>
content="width=device-width, initial-scale=1.0" /> <div class="app-loading__desc">初次加载需要较长时间 请耐心等待</div>
<title></title> </div>
</head> <style>
.app-loading {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
background: #ffffff;
}
<body> .app-loading__loader {
<div id="app"> box-sizing: border-box;
<div class="app-loading"> width: 32px;
<div class="app-loading__loader"></div> height: 32px;
<div class="app-loading__title">正在加载资源</div> border: 3px solid transparent;
<div class="app-loading__desc">初次加载需要较长时间 请耐心等待</div> border-top-color: rgba(0, 0, 0, 0.65);
</div> border-radius: 50%;
<style> animation: 1.2s loader linear infinite;
.app-loading { position: relative;
position: fixed; }
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
background: #ffffff;
}
.app-loading__loader { .app-loading__loader:before {
box-sizing: border-box; box-sizing: border-box;
width: 32px; content: '';
height: 32px; display: block;
border: 3px solid transparent; width: inherit;
border-top-color: rgba(0, 0, 0, .65); height: inherit;
border-radius: 50%; position: absolute;
animation: 1.2s loader linear infinite; top: -3px;
position: relative; left: -3px;
} border: 3px solid rgba(0, 0, 0, 0.25);
border-radius: 50%;
opacity: 0.5;
}
.app-loading__loader:before { .app-loading__title {
box-sizing: border-box; font-size: 18px;
content: ''; color: rgba(0, 0, 0, 0.85);
display: block; margin-top: 30px;
width: inherit; }
height: inherit;
position: absolute;
top: -3px;
left: -3px;
border: 3px solid rgba(0, 0, 0, .25);
border-radius: 50%;
opacity: .5;
}
.app-loading__title { .app-loading__desc {
font-size: 18px; font-size: 14px;
color: rgba(0, 0, 0, .85); color: rgba(0, 0, 0, 0.65);
margin-top: 30px; margin-top: 8px;
} }
.app-loading__desc { @keyframes loader {
font-size: 14px; 0% {
color: rgba(0, 0, 0, .65); transform: rotate(0deg);
margin-top: 8px; }
}
@keyframes loader { 100% {
0% { transform: rotate(360deg);
transform: rotate(0deg); }
} }
</style>
100% { </div>
transform: rotate(360deg); <script src="/libs/tinymce/tinymce.min.js"></script>
} <script
} type="module"
</style> src="/src/main.js"></script>
</div> </body>
<script src="/libs/tinymce/tinymce.min.js"></script> </html>
<script type="module"
src="/src/main.js"></script>
</body>
</html>

107
package-lock.json generated
View File

@ -12,7 +12,6 @@
"@ant-design/colors": "^7.0.0", "@ant-design/colors": "^7.0.0",
"@ant-design/icons-vue": "^6.1.0", "@ant-design/icons-vue": "^6.1.0",
"@icon-park/vue-next": "^1.4.2", "@icon-park/vue-next": "^1.4.2",
"@jiaminghi/data-view": "^2.10.0",
"@tinymce/tinymce-vue": "^5.1.0", "@tinymce/tinymce-vue": "^5.1.0",
"ant-design-vue": "^4.0.1", "ant-design-vue": "^4.0.1",
"axios": "^1.4.0", "axios": "^1.4.0",
@ -905,62 +904,6 @@
"url": "https://github.com/sponsors/kazupon" "url": "https://github.com/sponsors/kazupon"
} }
}, },
"node_modules/@jiaminghi/bezier-curve": {
"version": "0.0.9",
"resolved": "https://registry.npmmirror.com/@jiaminghi/bezier-curve/-/bezier-curve-0.0.9.tgz",
"integrity": "sha512-u9xJPOEl6Dri2E9FfmJoGxYQY7vYJkURNX04Vj64tdi535tPrpkuf9Sm0lNr3QTKdHQh0DdNRsaa62FLQNQEEw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.5.5"
}
},
"node_modules/@jiaminghi/c-render": {
"version": "0.4.3",
"resolved": "https://registry.npmmirror.com/@jiaminghi/c-render/-/c-render-0.4.3.tgz",
"integrity": "sha512-FJfzj5hGj7MLqqqI2D7vEzHKbQ1Ynnn7PJKgzsjXaZpJzTqs2Yw5OSeZnm6l7Qj7jyPAP53lFvEQNH4o4j6s+Q==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.5.5",
"@jiaminghi/bezier-curve": "*",
"@jiaminghi/color": "*",
"@jiaminghi/transition": "*"
}
},
"node_modules/@jiaminghi/charts": {
"version": "0.2.18",
"resolved": "https://registry.npmmirror.com/@jiaminghi/charts/-/charts-0.2.18.tgz",
"integrity": "sha512-K+HXaOOeWG9OOY1VG6M4mBreeeIAPhb9X+khG651AbnwEwL6G2UtcAQ8GWCq6GzhczcLwwhIhuaHqRygwHC0sA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.5.5",
"@jiaminghi/c-render": "^0.4.3"
}
},
"node_modules/@jiaminghi/color": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/@jiaminghi/color/-/color-1.1.3.tgz",
"integrity": "sha512-ZY3hdorgODk4OSTbxyXBPxAxHPIVf9rPlKJyK1C1db46a50J0reFKpAvfZG8zMG3lvM60IR7Qawgcu4ZDO3+Hg==",
"license": "MIT"
},
"node_modules/@jiaminghi/data-view": {
"version": "2.10.0",
"resolved": "https://registry.npmmirror.com/@jiaminghi/data-view/-/data-view-2.10.0.tgz",
"integrity": "sha512-Cud2MTiMcqc5k2KWabR/svuVQmXHANqURo+yj40370/LdI/gyUJ6LG203hWXEnT1nMCeiv/SLVmxv3PXLScCeA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.5.5",
"@jiaminghi/charts": "*"
}
},
"node_modules/@jiaminghi/transition": {
"version": "1.1.11",
"resolved": "https://registry.npmmirror.com/@jiaminghi/transition/-/transition-1.1.11.tgz",
"integrity": "sha512-owBggipoHMikDHHDW5Gc7RZYlVuvxHADiU4bxfjBVkHDAmmck+fCkm46n2JzC3j33hWvP9nSCAeh37t6stgWeg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.5.5"
}
},
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15", "version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
@ -5614,56 +5557,6 @@
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.14.4.tgz", "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.14.4.tgz",
"integrity": "sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==" "integrity": "sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA=="
}, },
"@jiaminghi/bezier-curve": {
"version": "0.0.9",
"resolved": "https://registry.npmmirror.com/@jiaminghi/bezier-curve/-/bezier-curve-0.0.9.tgz",
"integrity": "sha512-u9xJPOEl6Dri2E9FfmJoGxYQY7vYJkURNX04Vj64tdi535tPrpkuf9Sm0lNr3QTKdHQh0DdNRsaa62FLQNQEEw==",
"requires": {
"@babel/runtime": "^7.5.5"
}
},
"@jiaminghi/c-render": {
"version": "0.4.3",
"resolved": "https://registry.npmmirror.com/@jiaminghi/c-render/-/c-render-0.4.3.tgz",
"integrity": "sha512-FJfzj5hGj7MLqqqI2D7vEzHKbQ1Ynnn7PJKgzsjXaZpJzTqs2Yw5OSeZnm6l7Qj7jyPAP53lFvEQNH4o4j6s+Q==",
"requires": {
"@babel/runtime": "^7.5.5",
"@jiaminghi/bezier-curve": "*",
"@jiaminghi/color": "*",
"@jiaminghi/transition": "*"
}
},
"@jiaminghi/charts": {
"version": "0.2.18",
"resolved": "https://registry.npmmirror.com/@jiaminghi/charts/-/charts-0.2.18.tgz",
"integrity": "sha512-K+HXaOOeWG9OOY1VG6M4mBreeeIAPhb9X+khG651AbnwEwL6G2UtcAQ8GWCq6GzhczcLwwhIhuaHqRygwHC0sA==",
"requires": {
"@babel/runtime": "^7.5.5",
"@jiaminghi/c-render": "^0.4.3"
}
},
"@jiaminghi/color": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/@jiaminghi/color/-/color-1.1.3.tgz",
"integrity": "sha512-ZY3hdorgODk4OSTbxyXBPxAxHPIVf9rPlKJyK1C1db46a50J0reFKpAvfZG8zMG3lvM60IR7Qawgcu4ZDO3+Hg=="
},
"@jiaminghi/data-view": {
"version": "2.10.0",
"resolved": "https://registry.npmmirror.com/@jiaminghi/data-view/-/data-view-2.10.0.tgz",
"integrity": "sha512-Cud2MTiMcqc5k2KWabR/svuVQmXHANqURo+yj40370/LdI/gyUJ6LG203hWXEnT1nMCeiv/SLVmxv3PXLScCeA==",
"requires": {
"@babel/runtime": "^7.5.5",
"@jiaminghi/charts": "*"
}
},
"@jiaminghi/transition": {
"version": "1.1.11",
"resolved": "https://registry.npmmirror.com/@jiaminghi/transition/-/transition-1.1.11.tgz",
"integrity": "sha512-owBggipoHMikDHHDW5Gc7RZYlVuvxHADiU4bxfjBVkHDAmmck+fCkm46n2JzC3j33hWvP9nSCAeh37t6stgWeg==",
"requires": {
"@babel/runtime": "^7.5.5"
}
},
"@jridgewell/sourcemap-codec": { "@jridgewell/sourcemap-codec": {
"version": "1.4.15", "version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",

View File

@ -21,7 +21,6 @@
"@ant-design/colors": "^7.0.0", "@ant-design/colors": "^7.0.0",
"@ant-design/icons-vue": "^6.1.0", "@ant-design/icons-vue": "^6.1.0",
"@icon-park/vue-next": "^1.4.2", "@icon-park/vue-next": "^1.4.2",
"@jiaminghi/data-view": "^2.10.0",
"@tinymce/tinymce-vue": "^5.1.0", "@tinymce/tinymce-vue": "^5.1.0",
"ant-design-vue": "^4.0.1", "ant-design-vue": "^4.0.1",
"axios": "^1.4.0", "axios": "^1.4.0",

View File

@ -30,4 +30,26 @@ const theme = ref({
}) })
</script> </script>
<style lang="less"></style> <style lang="less">
.amap-info-content{
min-height: 130px;
min-width: 200px;
/* height: 293px; */
background-image: url(/src/assets/marker.png);
background-size: 100% 100%;
/* background-position: center; */
background-repeat: no-repeat;
background-color: transparent;
padding: 20px;
}
.bottom-center .amap-info-sharp:after {
position: absolute;
content: "";
margin-top: -7px;
border-top: 8px solid rgba(0, 0, 0, .3);
filter: blur(2px);
z-index: -1;
}
</style>

View File

@ -17,4 +17,7 @@ export const delItem = (id) => request.basic.delete(`/api/v1/customers/${id}`)
export const getCount=(params)=>request.basic.get('/api/v1/customers/count',params) export const getCount=(params)=>request.basic.get('/api/v1/customers/count',params)
//创建工单 //创建工单
export const createOrderItem=(params)=> request.basic.post('/api/v1/orders', params) export const createOrderItem=(params)=> request.basic.post('/api/v1/orders', params)
//转入转出
export const inOutLogs=(params)=>request.basic.post('/api/v1/in-out-logs',params)

BIN
src/assets/marker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,147 @@
<template>
<div class="map-wrapper">
<!-- 地图容器 -->
<div ref="mapContainer" class="map-container"></div>
<!-- 遮罩层外部遮罩中间透明区域显示地图 -->
<div class="map-mask"></div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import AMapLoader from '@amap/amap-jsapi-loader'
//
const mapContainer = ref(null)
//
let map = null
// 🎯 线
const targetArea = {
center: [116.397428, 39.90923], //
bounds: [
[116.385, 39.895], // 西 [minLng, minLat]
[116.410, 39.920] // [maxLng, maxLat]
],
zoom: 14 //
}
// 🌆
const markers = [
{ position: [116.397428, 39.90923], title: '天安门', address: '北京市东城区长安街', tel: '010-12345678' },
{ position: [116.3961, 39.9087], title: '故宫博物院', address: '北京市东城区景山前街4号', tel: '010-87654321' },
{ position: [116.4076, 39.9037], title: '王府井', address: '北京市东城区王府井大街', tel: '010-11223344' },
{ position: [116.3780, 39.9042], title: '国家大剧院', address: '北京市西城区西长安街2号', tel: '010-55667788' },
{ position: [116.4255, 39.9075], title: '北京站', address: '北京市东城区毛家湾胡同甲13号', tel: '010-34534534' },
{ position: [116.4625, 39.9100], title: '三里屯', address: '北京市朝阳区三里屯路', tel: '010-67867867' }
]
//
const initMap = async () => {
try {
await AMapLoader.load({
key: '38b334d84b1f89aa39d4efae76f0b341', // Key
version: '2.0'
})
console.log('高德地图 SDK 加载成功')
//
map = new window.AMap.Map(mapContainer.value, {
center: targetArea.center,
zoom: targetArea.zoom,
viewMode: '3D',
pitch: 35,
rotation: 0,
showLabel: true
})
// 🔒
const amapBounds = new window.AMap.Bounds(
new window.AMap.LngLat(targetArea.bounds[0][0], targetArea.bounds[0][1]),
new window.AMap.LngLat(targetArea.bounds[1][0], targetArea.bounds[1][1])
)
map.setLimitBounds(amapBounds)
console.log('地图已限制在指定区域内')
// 使
markers.forEach(item => {
const marker = new window.AMap.Marker({
position: item.position,
title: item.title,
extData: item //
})
//
const infoWindow = new window.AMap.InfoWindow({
content: `
<div style="color: #333; font-family: Microsoft YaHei;">
<h4 style="margin: 0 0 8px;">${item.title}</h4>
<p><b>📍 地址</b>${item.address}</p>
<p><b>📞 电话</b>${item.tel}</p>
</div>
`,
offset: new window.AMap.Pixel(0, -10)
})
//
marker.on('click', () => {
infoWindow.open(map, marker.getPosition())
})
//
map.add(marker)
})
} catch (e) {
console.error('地图初始化失败:', e)
alert('地图加载失败,请检查高德 Key 或网络连接')
}
}
//
onMounted(() => {
console.log('组件已挂载,准备初始化地图')
initMap()
})
</script>
<style scoped>
.map-wrapper {
background: #0f0f0f;
color: #fff;
font-family: 'Microsoft YaHei', Arial, sans-serif;
position: relative;
width: 100%;
height: 600px;
border-radius: 8px;
overflow: hidden;
}
.map-container {
width: 100%;
height: 100%;
}
/* 遮罩层:全屏黑色半透明,中间圆形透明区域 */
.map-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(
circle at center,
transparent 0%,
transparent 200px,
rgba(255, 255, 255, 0.8) 300px,
rgba(255, 255, 255, 0.95) 100%
);
pointer-events: none; /* 允许点击穿透到地图 */
z-index: 10;
border-radius: 8px;
}
</style>

View File

@ -0,0 +1,119 @@
<template>
<div class="map-wrapper">
<div ref="mapContainer" style="width: 100%; height: 600px; border: 1px solid #333; border-radius: 8px;"></div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import AMapLoader from '@amap/amap-jsapi-loader'
import bgImageUrl from '@/assets/marker.png'
//
const mapContainer = ref(null)
//
let map = null
// 🌆
const markers = [
{ position: [116.397428, 39.90923], title: '天安门', address: '北京市东城区长安街', tel: '010-12345678' },
{ position: [116.3961, 39.9087], title: '故宫博物院', address: '北京市东城区景山前街4号', tel: '010-87654321' },
{ position: [116.4076, 39.9037], title: '王府井', address: '北京市东城区王府井大街', tel: '010-11223344' },
{ position: [116.3780, 39.9042], title: '国家大剧院', address: '北京市西城区西长安街2号', tel: '010-55667788' },
{ position: [116.3375, 39.9040], title: '中关村', address: '北京市海淀区中关村大街', tel: '010-99887766' },
{ position: [116.481485, 39.989692], title: '望京SOHO', address: '北京市朝阳区望京街', tel: '010-12312312' },
{ position: [116.4678, 39.9923], title: '798艺术区', address: '北京市朝阳区酒仙桥路', tel: '010-23423423' },
{ position: [116.4255, 39.9075], title: '北京站', address: '北京市东城区毛家湾胡同甲13号', tel: '010-34534534' },
{ position: [116.3260, 39.9870], title: '颐和园', address: '北京市海淀区新建宫门路19号', tel: '010-45645645' },
{ position: [116.4948, 39.8814], title: '北京南站', address: '北京市丰台区永外大街120号', tel: '010-56756756' },
{ position: [116.4625, 39.9100], title: '三里屯', address: '北京市朝阳区三里屯路', tel: '010-67867867' },
{ position: [116.3600, 39.9780], title: '奥林匹克公园', address: '北京市朝阳区科荟路15号', tel: '010-78978978' }
]
//
const initMap = async () => {
try {
// SDK
await AMapLoader.load({
key: '38b334d84b1f89aa39d4efae76f0b341', // 🔑 Key
version: '2.0' // 使
//
})
console.log('高德地图 SDK 加载成功')
//
map = new window.AMap.Map(mapContainer.value, {
zoom: 11, //
center: [116.397428, 39.90923], //
viewMode: '3D',
mapStyle: 'amap://styles/dark', // 🖤
pitch: 35, // 3D
rotation: 0 //
})
console.log('地图初始化完成,共添加 ${markers.length} 个标注')
//
markers.forEach(item => {
//
const marker = new window.AMap.Marker({
position: item.position,
icon: new AMap.Icon({
size:new AMap.Size(30,30),
imageSize: new AMap.Size(30, 30), //
image:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAAXNSR0IArs4c6QAADU9JREFUeF7tnHl4VdW1wH/73BtubkYSkqAhCbEkgAQemIIZRApaqZThq1ot4AS0FmsrarWVYkuR1ke/8njaSrGa9wpiS2UsBWpFLLwPzfQUSkBmmTIBmSAmIcm9uWeXfTI0xcAdcpOcAvuvA3ftfdb6ZY/r7LUE10unCIhO1b5eGb8A7Cfz4jTkJInMFIhkkDHmZivKJPKoQOToiC0lIr3YV307BbCfzM6woP1Ywt2A5qsSPVxPF/CuDguLRUa+t7r4BDBJ5oU1wqsgHwajFzuA/5ewW0OeAKq8VaSb5SN1xE0CUoE0IACQwEobYs6nIv0zT/XxGmCczEsWyHeAJKARWC9hvYao8fSlZpLTIUwg7wXuA2zAUYn8arHI/NQTPb0CGCdzkkBkC1Bz3DEQLwnwef7wRMHukpEQB/IFYABwViJHewLRY4BfkB+HO3F+BCQDuy8O3RcFor67DOyO90ik/eJcvhAYrnqiDTHS3XD2GGC8zF0BPAp8KpDPgKaG71VYdJtEvNwyRa0oEhkzr2SkRwDVaquhZQNNIGZfLcP2cmBahvMbgEVHv61E3JZ7OVmPACbI3L9I+CqwQSB+exV2u8+ZJOEJkF+TsKVYZEz2GWDLJvmU6n0C8SBQfS0ABMIlcpXqheCKLxKjSzuy220PjJe5jwOvgfxAoP3sGoFnmCmR84HRAvl4och83SeACTL3LQkPSfi1hthyLQEEOVnCkyBWFol0tYB+rnjSA9Xx5taWlXf/tQVQT2lZkfOLREa6rwDVjnyAgBkgOpwHugLqEIJDZnNjahKBSb2xqo27uIDrXBGOE5up3PsnKk53xXvbtymR/YDlautWJDLU/tenHqgWkAQ1jDVEWVcrfSfhUS+Q+GB/bGOtiODLva+Uxg9XUb76VUoOd5VOOjJGwO+BwiKR0d/0AF8j6Y6vEPmUBaFOBB6VAmp+P4UDKz0S9lLI1ACXcNNtoMkfcizXBfJtbr43gzC14ntdTtKw7UsULFYVn6XfkKGE9J/J4b963dAlFUwL8ANGzE3Adsdiir63lNIjSxiQ+XWiFnTG4AJq/zCF/W+OJzIqi+RVVTgLZnJ44R7qfPYSmQ7gAOz2jaQsCcOStJu6FffwyaqBBAVtZdg6DaydAajq/pyTj2dx9vgiEkdNp+9LDvTKuZx4dj0VPi1+pgO4m9RXe2MdpIMjk133lOFyvsOwxwZjv7+z8FT9Kpo+Gcnu76vnTxi5IggtthG9ahx7HjlDk3L6elVMBXA1g6elEmp4Ns7gyLmdggXhWKz53LLem0XDHYEfcGLGRipK15EyYzhB05V8MY07xrF3kbu6l/5uGoBq6G5m6EbR7P4nj5rfzuTQhueJHz6DG4zJ31/lI2reeIRD635K4qipRL/U2u4LnJi5gYoSb95jGoCvkDz+TsKfa1V+LWU/WUhh/nIG3j+SsMe8McqdbDGN2yawb/FkomL/k0TlwzTKQepWP8DB/3VXv/3vpgG4iZQfxGO7q1W5LE4/vYzTB95m8LcGEfSAN0a5k63Auesu9v0omaDgtxm8tnVxOo/zwDj2Pe2uvikBvsuwX0RiVV/AjLKCMwbAlQyeNRj7VG+McidbhfPju/lknjoKLmfgGtGyujegl46hYIa7+qYEuJmhv4zEOqJVuS1UzF9Ecd5Sku+7heDZ3hjlTraIxq1TObhkCpFxz5Pwu1b5BvQzd7L3EXf1TQlwDUPmxxAwulW5PdRmPc2xtbOJHTqN6P/2xih3sruoWfYsxzc+R1zaJPq0+S/r0I9NZN933NU3JcCXSZqWgr3t40wFzvzpHPqJBeVgHLbeAiHeGHYl2QUUPphDdfkykmclEdg2PZzGse1RDnu14ptmERlPZNxTxLYNJwlN3+bwvaU4G37FgBlJ2I39WmfLOZo+eohD6tsuGxjylg2tb2ub71A57zec/tibd5gGoFL6dwx6KQLrqFYDDlG3/Eec/GMUAbbXSV6nIdStgE6V31A8832qS54gdtRdRLTtARvRT0/lYIce5Su90FQARxES9Rzx6iONUXRo/C5H76uiyfFN+qaOJ/IXnaG3n7rXF1K4XrWxkkHLbWjKGWqUdZTPWUvFIW/bNxVApfwsYtLGEtE2sVfizH6G4y+q375P7IQRhD7jrZFKvhjH2nmcyFLPC0h85AvYHmptZx+1SxdTssmXdk0HUBkxnb6pYwiba0X0Vv8+RsObiyj8g3qeQUx6OuHPWBERnhgsQR6gbunLlGxW8t/ihjHphP1YPUtwFVD7ylJKt3rSVkcypgSoFE0kMHQ6MdP6EnCHFRFZjnNrFqWvl+Cs7UdAyKPcOC2GgHEBiKiODaO+HMeO9zm3NoeakkAs1jnEqs8AD0poLMPxt/eoWpNHrU9urH9OMyZ36QdjCZhERGoCgbfkUfPuB1SfbFVe/TaByGE3YE0KIeBG9f+NNFWW03Qqh5q9x6lv+8A/BHufiUTfW0nj8fepzi+kodbXXte+nml7oD+M6442TAdwNGFJRTSUn8LRLddD4ugVloAtOoeaY74ANw3AbxA9IQ7bxCqaPt7B+U0naTCuAIcRYLudsNRoAgaGoCVqiF4C7MLY5bgvEjQdGiSy8QJ6UTnOIzmc31WJy7i3GEev8DuImBRFQNoZHNtXU/Zn9QHLfcvNEj0OcAIRowcS9IQVEXWaxhWrKDf2gYkERo4h/IE+WMdpHq64XhhdXU3Tzmyq1xym4ayqN5Wo+/sR+JiO/Ow4Da/9mcq/edJejwJ8mJg5vbFOUooWUDt3J5+pW61MIWJCAvbHVU/zxAhfZdRxsZjGNzZSuVG1cSuhg9MI/bV6Pk/Ttrcoc3su7jGA04meH4LF8L4con7BTqpz1PP9RD8ZgeWyd+18hXWleudp2rqGiiVKJoOwEcMI+qV6rse1+y3K516pbo8AnELkdyIIuEcpdg7Xhk1UGBcyv0afOeEtPbIrQF2pzVpc29dTYRwVJxLxcBS9VHgGNejvbaD8vy5Xt9sBDiMkJQW7ul+Mjl61hkrDnTSG8Amx9PLpmOYv2BU4st6nem3zSIh604Iw9pZHaJi3m5oOvTTdDnAyEVlWNOMSzhkci3Op2dYHS9gYeq/zF4jOtPMRnz1cjPNsKsGZ/Qk0bkK4kGWbqGo7O7dvv1sBDsI+JAn7K0oBCbXvUKWCVxhL+FPBWCZ2xnB/1a1H/2A75w1nxgQiVmktR8WT1L+wn3oVwvEvpVsBZhD6ZHDLAlGL6y951PwqFIs9jVC1Crq9yOkvSO7a2UXdN87hPJdO6GMhWIwbEfXI7dlUf86d1q0AMwlbZkWo8C/KcLx4iPrsmwm6M5qA590Z1Z2/n8O1dB+1m5IITI3FZkBzQXE21bN6tAfeSugG0fJt4zj1M8ppKv0Pgp+2o6nwCNOURvQde6hbFIK1dwr21a3BkvnUGHvWHpsDRxC8WYBNwoUCar8uEU1DCVoUgPiiaegZkULywD4uGB/YhxP8tgaR6vnv1I3vUYApBCmnpk2Hswe5YOyzbsa+VEMMNBNAHVl0kPpvtuiXpSGMXcN+LvQswIHY32sFdYR6Q5kkApdpLfOiWSBKOHuUeuMPnIy9bdpp1bnHhnAfrKNtaP11ZM0ZnMY3iP70WiZMB1CcOUWDcUMhBuvdFohyIYvLcP1fVw1hn8McYun1RwF9zNL7WvRwlOD43ILRkY7+CnPwOdAmEusTFsRYEDpIj/1wXQNcCJCajsyvpMlwLrgvfgi0uR7q1clQrwSZM1saIa7Xgw076rFuj1jXbrir3lsi1PfqzoW7KurtAq7/JBCvuZ87/v0l/BZwrVC0C/l3XQw4/PbVH/Kvx4NQ8cH+CflXEK8nneh4VLmdA1urtU97IhB7JPKnV3nakyM2xCi/pT1RIFXiHYH48GIKAHVx8XriHV8cnS0QVeonFYCscseohUXdy+uW2wZdsDyp5BIq7ZP6ENa1qZ9alW9OPibVN1Z1plTTgPOilzxfwN8lnBDIyi4w1G9NSkQfATfJ5uRjt7ZLPvamDfGUu2HbXhGP58COtI+TuWkazP93T38nkX8F8bNuS393Kcx4+WGsQJss0TIFJEsjAaP06I8jERYB8Z3IP6ju0RSDbPKsiwopUAkYOSogW9K05XI5YTxpzyMjPWmoMzLxMmcWCK/i2Frfd6WcLp3RydO6pgCIlCKePOWQ/bKnijfLiZ1FpI1FiB7z9JgDoLFRzx4A2l4gyEOIDTra8BKRdsRD+S4RMw3A5tNO3rMgL3tX5V9XPzmvUGR6HUTtb4qmAohcY4knLu9iBNjIKxmqTkKF1I9CjPNw4fA3tn+2Zy6AhuMif7iGrq5ZqMSwHRWXQKQVivRdXYfF85ZNB7B5KOf+/OLm3Ih5u7QIxOJCkf5Dz03sWklTAkyUOwJ17MphMegS849J5LBikWma3K2mBKigJci80RK5s915XWpoXz4l0rZ3bZ/yrnXTAmwZyssupiQ2gqQF/E+hyPBrggrvUHUsbWqALU6L/SAtAldKobj9nD+M9mcbpgbY3AtzviIQvQpFhhFQaLbyD04785z4uYAcAAAAAElFTkSuQmCC', // 💧
}),
offset: new window.AMap.Pixel(-13, -30), //
title: item.title, //
extData: item // 便使
})
//
const infoWindow = new window.AMap.InfoWindow({
content: `
<div>
<h4 >${item.title}</h4>
<p><b>📍 地址</b>${item.address}</p>
<p><b>📞 电话</b>${item.tel}</p>
</div>
`,
offset: new window.AMap.Pixel(0, -30)
})
//
marker.on('click', () => {
infoWindow.open(map, marker.getPosition())
})
//
map.add(marker)
})
} catch (e) {
console.error('地图初始化失败:', e)
alert('地图加载失败,请检查高德 Key 或网络连接')
}
}
//
onMounted(() => {
console.log('组件已挂载,准备初始化地图')
initMap()
})
</script>
<style scoped>
.map-wrapper {
/* padding: 20px; */
background: #0f0f0f;
/* min-height: 100vh; */
color: #fff;
font-family: 'Microsoft YaHei', Arial, sans-serif;
}
h2 {
text-align: center;
color: #4facfe;
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,50 @@
<template>
<a-select ref="select" v-model:value="model" style="width: 100%" @change="handleChange" :defaultOpen="defaultOpen">
<a-select-option v-for="value in stationList" :key="value.id" :value="value.id">
{{ value.name }}
</a-select-option>
</a-select>
</template>
<script setup>
import { ref,defineModel } from 'vue'
import apis from '@/apis'
// 使 defineModel
// { currentOrg: String, onUpdateCurrentOrg: Function }
// ref
const model = defineModel('currentOrg', { type: String, default: '' })
// prop
const props = defineProps({
defaultOpen: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['change']) // change
//
const stationList = ref([])
getData()
async function getData() {
try {
const { data, success } = await apis.serviceMenu.getServiceSiteList({
pageSize: 100,
current: 1
})
if (success) {
stationList.value = data.map(item => ({ id: item.id, name: item.name }))
}
} catch (error) {
console.error('获取服务站点列表失败:', error)
}
}
// change
function handleChange(e) {
// model.value
emit('change', e)
}
</script>

View File

@ -47,9 +47,9 @@
<edit-outlined /> <edit-outlined />
{{ $t('component.RightContent.profile') }} {{ $t('component.RightContent.profile') }}
</a-menu-item> --> </a-menu-item> -->
<a-menu-item key="edit" @click="handleOpen"> <a-menu-item key="edit" @click="handleOpen">
<LeftOutlined /> <LeftOutlined />
返回平台 返回平台
</a-menu-item> </a-menu-item>
<a-menu-item key="logout" @click="handleLogout"> <a-menu-item key="logout" @click="handleLogout">
<login-outlined></login-outlined> <login-outlined></login-outlined>
@ -68,7 +68,7 @@ import { Modal } from 'ant-design-vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { computed, useSlots, ref } from 'vue' import { computed, useSlots, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { LoginOutlined, SettingOutlined, EditOutlined, TranslationOutlined, BarChartOutlined,LeftOutlined } from '@ant-design/icons-vue' import { LoginOutlined, SettingOutlined, EditOutlined, TranslationOutlined, BarChartOutlined, LeftOutlined } from '@ant-design/icons-vue'
import { useAppStore, useUserStore } from '@/store' import { useAppStore, useUserStore } from '@/store'
import ActionButton from './ActionButton.vue' import ActionButton from './ActionButton.vue'
import { theme as antTheme } from 'ant-design-vue' import { theme as antTheme } from 'ant-design-vue'
@ -157,9 +157,11 @@ function handleLogout() {
*/ */
function handleOpen() { function handleOpen() {
const multiTab = useMultiTab() storage.local.removeItem('platform')
multiTab.$reset() storage.local.removeItem('stationId')
router.replace({path:'/platForm'}) const multiTab = useMultiTab()
multiTab.$reset()
router.replace({ path: '/platForm' })
} }
/** /**

View File

@ -1,3 +1,5 @@
import callBaseSet from "../../../router/routes/callBaseSet";
export default { export default {
welcome: '欢迎', welcome: '欢迎',
home: '首页', home: '首页',
@ -63,5 +65,16 @@ export default {
serverProjectManage:'服务项目管理', serverProjectManage:'服务项目管理',
serviceStaffyuying:'服务人员', serviceStaffyuying:'服务人员',
workorderYunying:'工单管理', workorderYunying:'工单管理',
waitWorkOrder:'待派单' waitWorkOrder:'待派单',
callServerObj:'服务对象管理',
callBaseSet:'基础配置',
callType:'呼叫类型',
trafficMgt:'话务管理',
callLog:'通话记录',
myCallLog:'我的通话记录',
missedCalls:'未接来电',
quality:'质检管理',
qualityLog:'质检记录',
operatorMgt:'话务员管理',
operator:'话务员列表',
} }

View File

@ -1,12 +1,12 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import {spliceUrl} from '@/utils/util' import { spliceUrl } from '@/utils/util'
import App from '@/App.vue' import App from '@/App.vue'
import { useCore } from '@/core' import { useCore } from '@/core'
import './assets/iconfont/iconfont.css'; import './assets/iconfont/iconfont.css'
// import dataV from '@jiaminghi/data-view' // import dataV from '@jiaminghi/data-view'
const app = createApp(App) const app = createApp(App)
// app.use(dataV)
app.config.globalProperties.$spliceUrl=spliceUrl app.config.globalProperties.$spliceUrl = spliceUrl
useCore(app) useCore(app)
app.mount('#app') app.mount('#app')

View File

@ -0,0 +1,30 @@
import { DollarOutlined } from '@ant-design/icons-vue'
export default [
{
path: 'callBaseSet',
name: 'callBaseSet',
component: 'RouteViewLayout',
meta: {
icon: DollarOutlined,
title: '基础配置',
isMenu: true,
keepAlive: true,
permission: '*',
},
children: [
{
path: 'callType',
name: 'callType',
component: 'callBaseSet/callType/index.vue',
meta: {
title: '通话类型',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
],
},
]

View File

@ -0,0 +1,30 @@
import { DollarOutlined } from '@ant-design/icons-vue'
export default [
{
path: 'callServerObj',
name: 'callServerObj',
component: 'RouteViewLayout',
meta: {
icon: DollarOutlined,
title: '服务对象管理',
isMenu: true,
keepAlive: true,
permission: '*',
},
children: [
{
path: 'callServerList',
name: 'callServerList',
component: 'serverObj/serverList/index.vue',
meta: {
title: '服务对象列表',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
],
},
]

View File

@ -19,6 +19,10 @@ import serverSet from './serverSet'
import yunYingServerObj from './yunyingServerObj' import yunYingServerObj from './yunyingServerObj'
import serviceStaffYunYing from './serviceStaffYunYing' import serviceStaffYunYing from './serviceStaffYunYing'
import workorderYunying from './workorderYunying' import workorderYunying from './workorderYunying'
import callServerObj from './callServerObj'
import callBaseSet from './callBaseSet'
import trafficMgt from './trafficMgt'
import operator from './operator'
export default [ export default [
...home, ...home,
// ...form, // ...form,
@ -40,5 +44,9 @@ export default [
...serverSet, ...serverSet,
...yunYingServerObj, ...yunYingServerObj,
...serviceStaffYunYing, ...serviceStaffYunYing,
...workorderYunying ...workorderYunying,
...callServerObj,
...callBaseSet,
...trafficMgt,
...operator,
] ]

View File

@ -0,0 +1,30 @@
import { DollarOutlined } from '@ant-design/icons-vue'
export default [
{
path: 'operatorMgt',
name: 'operatorMgt',
component: 'RouteViewLayout',
meta: {
icon: DollarOutlined,
title: '话务员管理',
isMenu: true,
keepAlive: true,
permission: '*',
},
children: [
{
path: 'operator',
name: 'operator',
component: 'operatorMgt/operator/index.vue',
meta: {
title: '话务员列表',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
],
},
]

View File

@ -6,5 +6,5 @@ export default [
meta: { meta: {
title: '平台选择', title: '平台选择',
}, },
}, }
] ]

View File

@ -24,6 +24,17 @@ export default [
permission: '*', permission: '*',
}, },
}, },
{
path: 'callBaseSet',
name: 'callBaseSet',
component: 'baseSet/callBaseSet/index.vue',
meta: {
title: '基础配置',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
], ],
}, },
] ]

View File

@ -0,0 +1,29 @@
import { DollarOutlined } from '@ant-design/icons-vue'
export default [
{
path: 'serviceStaffCall',
name: 'serviceStaffCall',
component: 'RouteViewLayout',
meta: {
icon: DollarOutlined,
title: '服务人员',
isMenu: true,
keepAlive: true,
permission: '*',
},
children: [
{
path: 'serviceStaffList',
name: 'serviceStaffList',
component: 'serviceStaff/serviceStaffList/index.vue',
meta: {
title: '服务人员',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
],
},
]

View File

@ -0,0 +1,73 @@
import { DollarOutlined } from '@ant-design/icons-vue'
export default [
{
path: 'trafficMgt',
name: 'trafficMgt',
component: 'RouteViewLayout',
meta: {
icon: DollarOutlined,
title: '话务管理',
isMenu: true,
keepAlive: true,
permission: '*',
},
children: [
{
path: 'myCallLog',
name: 'myCallLog',
component: 'trafficMgt/myCallLog/index.vue',
meta: {
title: '我的通话记录',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'callLog',
name: 'callLog',
component: 'trafficMgt/callLog/index.vue',
meta: {
title: '通话记录',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'missedCalls',
name: 'missedCalls',
component: 'trafficMgt/missedCalls/index.vue',
meta: {
title: '未接来电',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'quality',
name: 'quality',
component: 'trafficMgt/quality/index.vue',
meta: {
title: '质检管理',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
{
path: 'qualityLog',
name: 'qualityLog',
component: 'trafficMgt/qualityLog/index.vue',
meta: {
title: '质检记录',
isMenu: true,
keepAlive: true,
permission: '*',
},
},
],
},
]

View File

@ -51,8 +51,9 @@ const useUserStore = defineStore('user', {
const appStore = useAppStore() const appStore = useAppStore()
const multiTab = useMultiTab() const multiTab = useMultiTab()
const router = useRouter() const router = useRouter()
storage.local.removeItem(config('storage.token')) // storage.local.removeItem(config('storage.token'))
storage.local.removeItem(config('storage.userInfo')) // storage.local.removeItem(config('storage.userInfo'))
storage.local.clear()
this.$reset() this.$reset()
appStore.$reset() appStore.$reset()
multiTab.$reset() multiTab.$reset()

View File

@ -0,0 +1,144 @@
<template>
<a-modal :open="modal.open" :title="modal.title" :width="600" :confirm-loading="modal.confirmLoading"
:after-close="onAfterClose" :cancel-text="cancelText" @ok="handleOk" @cancel="handleCancel">
<a-card>
<a-form ref="formRef" :model="formData" :rules="formRules">
<a-row :gutter="24">
<!-- 姓名 -->
<a-col :span="24">
<a-form-item label="类型名称" name="name">
<a-input v-model="formData.name"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="上级通话类型" name="callType" >
<a-select v-model:value="formData.callType" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_TYPE" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="电话方向" name="callDirection" >
<a-select v-model:value="formData.callDirection" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_DIRECTION" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="排序号" name="sortNo">
<a-input-number v-model="formData.sortNo" style="width: 100%;"></a-input-number>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="备注" name="remark">
<a-input v-model="formData.remark"></a-input>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
</a-modal>
</template>
<script setup>
import { cloneDeep } from 'lodash-es'
import { ref, defineProps } from 'vue'
import { config } from '@/config'
import apis from '@/apis'
import { useForm, useModal } from '@/hooks'
import { useDicsStore } from '@/store'
import AreaCascader from '@/components/AreaCascader/index.vue'
const dicsStore = useDicsStore()
const emit = defineEmits(['ok'])
const activeKey = ref('1')
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { formRecord, formData, formRef, formRules, resetForm } = useForm()
const cancelText = ref('取消')
formRules.value = {
name: [{ required: true, message: '请输入类型名称', trigger: 'blur' }],
}
/**
* 新建
*/
function handleCreate() {
showModal({
type: 'create',
title: '新建',
})
}
function handleEdit(record) {
formRecord.value = cloneDeep(record)
Object.assign(formData.value, formRecord.value)
showModal({
type: 'edit',
title: '编辑',
})
}
/**
* 确定
*/
function handleOk() {
formRef.value
.validateFields()
.then(async (values) => {
try {
showLoading()
const params = {
...formData.value,
}
let result = null
switch (modal.value.type) {
case 'create':
result = await apis.serverObj.createItem(params).catch(() => {
throw new Error()
})
break
case 'edit':
result = await apis.serverObj.updateItem(params).catch(() => {
throw new Error()
})
break
}
hideLoading()
if (config('http.code.success') === result?.code) {
hideModal()
emit('ok')
}
} catch (error) {
hideLoading()
}
})
.catch(() => {
hideLoading()
})
}
/**
* 取消
*/
function handleCancel() {
hideModal()
}
/**
* 关闭后
*/
function onAfterClose() {
resetForm()
hideLoading()
}
defineExpose({
handleCreate, handleEdit
})
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,211 @@
<template>
<a-row :gutter="16">
<a-col :span="24">
<x-search-bar class="mb-8-2">
<template #default="{ gutter, colSpan }">
<a-form :model="searchFormData" layout="inline">
<a-row :gutter="gutter">
<a-col v-bind="colSpan">
<a-form-item label="类型名称" name="name">
<a-input :placeholder="'请输入类型名称'"
v-model:value="searchFormData.name"></a-input>
</a-form-item>
</a-col>
<a-col class="align-right" v-bind="colSpan">
<a-space>
<a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button ghost type="primary" @click="handleSearch">
{{ $t('button.search') }}
</a-button>
</a-space>
</a-col>
</a-row>
</a-form>
</template>
</x-search-bar>
<a-card>
<template #extra>
<a-space>
<a-button type="primary" @click="$refs.editDialogRef.handleCreate(record)">新建</a-button>
</a-space>
</template>
<a-table :columns="columns" :data-source="listData" bordered="true" :loading="loading"
:pagination="paginationState" :scroll="{ x: 'max-content' }" @change="onTableChange">
<template #bodyCell="{ index, column, record }">
<template v-if="column.key === 'serialNumber'">
<span>{{ index + 1 }}</span>
</template>
<template v-if="column.key === 'categoryType'">
<span>{{ dicsStore.getDictLabel('PROJECT_TYPE', record.categoryType) }}</span>
</template>
<!-- <template v-if="column.key === 'status'">
<a-tag v-if="record.status === 'enabled'" color="green">启用</a-tag>
<a-tag v-else>停用</a-tag>
</template> -->
<template v-if="'action' === column.key">
<x-action-button @click="$refs.editDialogRef.handleEdit(record)">
<span>编辑</span>
</x-action-button>
<x-action-button @click="handleDelete(record)">
<span style="color: #ff4d4f;">删除</span>
</x-action-button>
</template>
</template>
</a-table>
</a-card>
</a-col>
</a-row>
<edit-dialog ref="editDialogRef" @ok="onOk"></edit-dialog>
</template>
<script setup>
import { message, Modal } from 'ant-design-vue'
import { ref } from 'vue'
import apis from '@/apis'
import { config } from '@/config'
import { usePagination } from '@/hooks'
import { useI18n } from 'vue-i18n'
import EditDialog from './components/EditDialog.vue'
import { useDicsStore } from '@/store'
defineOptions({
name: 'serverProjectManage',
})
const dicsStore = useDicsStore()
const activeKey = ref('1')
const columns = [
{
title: '类型名称',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 100,
},
{
title: '上级通话类型名称',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 180,
},
{
title: '电话方向',
dataIndex: 'remark',
key: 'remark',
align: 'center',
},
{
title: '排序号',
dataIndex: 'status',
key: 'status',
align: 'center',
width: 80,
},
{
title: '备注',
dataIndex: 'status',
key: 'status',
align: 'center',
width: 80,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
align: 'center',
width: 120,
fixed: 'right',
}
];
const { t } = useI18n() // t
const { listData, loading, showLoading, hideLoading, paginationState, resetPagination, searchFormData } = usePagination()
const editDialogRef = ref()
getPageList()
async function getPageList() {
try {
const { pageSize, current } = paginationState
const { success, data, total } = await apis.projectType
.getProjectList({
pageSize,
current: current,
...searchFormData.value,
})
.catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
listData.value = data
paginationState.total = total
}
} catch (error) {
}
}
/**
* 删除
*/
function handleDelete({ id }) {
Modal.confirm({
title: t('pages.system.user.delTip'),
content: t('button.confirm'),
okText: t('button.confirm'),
onOk: () => {
return new Promise((resolve, reject) => {
; (async () => {
try {
const { success } = await apis.projectType.delItem(id).catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
resolve()
message.success(t('component.message.success.delete'))
await getPageList()
}
} catch (error) {
reject()
}
})()
})
},
})
}
/**
* 分页
*/
function onTableChange({ current, pageSize }) {
paginationState.current = current
paginationState.pageSize = pageSize
getPageList()
}
/**
* 搜索
*/
function handleSearch() {
resetPagination()
getPageList()
}
/**
* 重置
*/
function handleResetSearch() {
searchFormData.value = {}
resetPagination()
getPageList()
}
/**
* 编辑完成
*/
async function onOk() {
await getPageList()
}
</script>
<style lang="less" scoped></style>

View File

@ -1,5 +1,6 @@
<template> <template>
<a-row :gutter="16"> <JsonMap />
<!-- <a-row :gutter="16">
<a-col :lg="24"> <a-col :lg="24">
<a-card> <a-card>
<a-row <a-row
@ -88,13 +89,14 @@
</p> </p>
</a-card> </a-card>
</a-col> </a-col>
</a-row> </a-row> -->
</template> </template>
<script setup> <script setup>
import ClipboardJS from 'clipboard' import ClipboardJS from 'clipboard'
import { CheckCircleFilled, CopyOutlined } from '@ant-design/icons-vue' import { CheckCircleFilled, CopyOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import JsonMap from '@/components/JsonMap/index.vue'
defineOptions({ defineOptions({
name: 'home', name: 'home',
}) })

View File

@ -6,61 +6,84 @@
</div> </div>
<div class="screen-content"> <div class="screen-content">
<div> <div>
<div>
<div class="card-title">服务对象人口统计</div>
<div class="card-content"></div>
</div>
<div>
<div class="card-title">服务对象人口统计</div>
<div class="card-content"></div>
</div>
<div>
<div class="card-title">服务对象人口统计</div>
<div class="card-content"></div>
</div>
</div>
<div>
<div>
<MapWidthMarkers />
</div>
<div> <div>
<div class="card-title">服务对象人口统计</div> <div class="card-title">服务对象人口统计</div>
<div class="card-content"> <div class="card-content">
<!-- <dv-active-ring-chart :config="config1" style="width:300px;height:300px" /> --> <borderBox1 />
</div> </div>
</div> </div>
<div></div>
<div></div>
</div> </div>
<div> <div>
<div></div> <div>
<div></div> <div class="card-title">服务对象人口统计</div>
</div> <div class="card-content"></div>
<div> </div>
<div></div> <div>
<div></div> <div class="card-title">服务对象人口统计</div>
<div></div> <div class="card-content"></div>
</div>
<div>
<div class="card-title">服务对象人口统计</div>
<div class="card-content"></div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import {ref} from 'vue' import { ref } from 'vue'
const config1=ref( import MapWidthMarkers from '@/components/MapWithMarkers/index.vue'
import { borderBox1 } from '@jiaminghi/data-view'
Vue.use(borderBox1)
const config1 = ref(
{ {
radius: '40%', radius: '40%',
activeRadius: '45%', activeRadius: '45%',
data: [ data: [
{ {
name: '周口', name: '周口',
value: 55 value: 55
}, },
{ {
name: '南阳', name: '南阳',
value: 120 value: 120
}, },
{ {
name: '西峡', name: '西峡',
value: 78 value: 78
}, },
{ {
name: '驻马店', name: '驻马店',
value: 66 value: 66
}, },
{ {
name: '新乡', name: '新乡',
value: 80 value: 80
} }
], ],
digitalFlopStyle: { digitalFlopStyle: {
fontSize: 20 fontSize: 20
}, },
showOriginValue: true showOriginValue: true
} }
) )
</script> </script>

10
src/views/login/map.vue Normal file
View File

@ -0,0 +1,10 @@
<template>
<div style="width: 100%;height: 100%;">
<JsonMap />
</div>
</template>
<script setup>
import {ref} from 'vue'
import JsonMap from '@/components/JsonMap/index.vue'
</script>

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="layout-container"> <div class="layout-container">
<StarBackground /> <StarBackground />
<div class="top"> <div class="top" @click="handleLogout">
<div> <div >
<span class="corner corner-top"></span> <span class="corner corner-top"></span>
<span class="corner corner-left"></span> <span class="corner corner-left"></span>
<span class="corner corner-bottom"></span> <span class="corner corner-bottom"></span>
@ -58,12 +58,7 @@
<span @click="currentPlatForm = 'jianguan'">{{ '< 返回' }}</span> <span @click="currentPlatForm = 'jianguan'">{{ '< 返回' }}</span>
</div> </div>
<h3>请选择您的管理组织</h3> <h3>请选择您的管理组织</h3>
<a-select ref="select" v-model:value="currentOrg" style="width: 100%" <ServiceStation @change="handleChange" :defaultOpen="true"/>
@change="handleChange" :defaultOpen="true">
<a-select-option v-for="value in stationList" :value="value.id">{{
value.name
}}</a-select-option>
</a-select>
</a-card> </a-card>
</div> </div>
</div> </div>
@ -83,13 +78,16 @@ import yunying from '@/assets/imgs/yunying.png'
import { useAppStore, useRouterStore, useUserStore } from '@/store' import { useAppStore, useRouterStore, useUserStore } from '@/store'
import { template } from 'lodash-es' import { template } from 'lodash-es'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import apis from '@/apis' import ServiceStation from '@/components/ServiceStation/index.vue'
import storage from '@/utils/storage' import storage from '@/utils/storage'
import { useI18n } from 'vue-i18n'
const { locale, t } = useI18n()
defineOptions({ defineOptions({
name: 'PlatForm', name: 'PlatForm',
}) })
const appStore = useAppStore() const appStore = useAppStore()
const routerStore = useRouterStore() const routerStore = useRouterStore()
const userStore = useUserStore()
const router = useRouter() const router = useRouter()
const stationList = ref([]) const stationList = ref([])
const currentPlatForm = ref('jianguan') const currentPlatForm = ref('jianguan')
@ -101,14 +99,7 @@ onBeforeMount(() => {
async function handleSelect(type) { async function handleSelect(type) {
if (type === 'yunying') { if (type === 'yunying') {
currentPlatForm.value = 'yunying' currentPlatForm.value = 'yunying'
try {
const { data, success } = await apis.serviceMenu.getServiceSiteList({ pageSize: 100, current: 1 })
if (success) {
stationList.value = data.map(item => ({ id: item.id, name: item.name }))
}
} catch (error) {
}
} else { } else {
storage.local.setItem('platform', type) storage.local.setItem('platform', type)
await appStore.init() await appStore.init()
@ -148,6 +139,23 @@ async function handleChange(e) {
goIndex() goIndex()
} }
/**
* 退出登录
*/
function handleLogout() {
Modal.confirm({
title: t('component.RightContent.logout'),
okText: t('button.confirm'),
cancelText: t('button.cancel'),
onOk: () => {
userStore.logout().then(() => {
router.push({
name: 'login',
})
})
},
})
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.body-bg { .body-bg {

View File

@ -0,0 +1,144 @@
<template>
<a-modal :open="modal.open" :title="modal.title" :width="600" :confirm-loading="modal.confirmLoading"
:after-close="onAfterClose" :cancel-text="cancelText" @ok="handleOk" @cancel="handleCancel">
<a-card>
<a-form ref="formRef" :model="formData" :rules="formRules">
<a-row :gutter="24">
<!-- 姓名 -->
<a-col :span="24">
<a-form-item label="类型名称" name="name">
<a-input v-model="formData.name"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="上级通话类型" name="callType" >
<a-select v-model:value="formData.callType" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_TYPE" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="电话方向" name="callDirection" >
<a-select v-model:value="formData.callDirection" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_DIRECTION" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="排序号" name="sortNo">
<a-input-number v-model="formData.sortNo" style="width: 100%;"></a-input-number>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="备注" name="remark">
<a-input v-model="formData.remark"></a-input>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
</a-modal>
</template>
<script setup>
import { cloneDeep } from 'lodash-es'
import { ref, defineProps } from 'vue'
import { config } from '@/config'
import apis from '@/apis'
import { useForm, useModal } from '@/hooks'
import { useDicsStore } from '@/store'
import AreaCascader from '@/components/AreaCascader/index.vue'
const dicsStore = useDicsStore()
const emit = defineEmits(['ok'])
const activeKey = ref('1')
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { formRecord, formData, formRef, formRules, resetForm } = useForm()
const cancelText = ref('取消')
formRules.value = {
name: [{ required: true, message: '请输入类型名称', trigger: 'blur' }],
}
/**
* 新建
*/
function handleCreate() {
showModal({
type: 'create',
title: '新建',
})
}
function handleEdit(record) {
formRecord.value = cloneDeep(record)
Object.assign(formData.value, formRecord.value)
showModal({
type: 'edit',
title: '编辑',
})
}
/**
* 确定
*/
function handleOk() {
formRef.value
.validateFields()
.then(async (values) => {
try {
showLoading()
const params = {
...formData.value,
}
let result = null
switch (modal.value.type) {
case 'create':
result = await apis.serverObj.createItem(params).catch(() => {
throw new Error()
})
break
case 'edit':
result = await apis.serverObj.updateItem(params).catch(() => {
throw new Error()
})
break
}
hideLoading()
if (config('http.code.success') === result?.code) {
hideModal()
emit('ok')
}
} catch (error) {
hideLoading()
}
})
.catch(() => {
hideLoading()
})
}
/**
* 取消
*/
function handleCancel() {
hideModal()
}
/**
* 关闭后
*/
function onAfterClose() {
resetForm()
hideLoading()
}
defineExpose({
handleCreate, handleEdit
})
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,247 @@
<template>
<a-row :gutter="16">
<a-col :span="24">
<x-search-bar class="mb-8-2">
<template #default="{ gutter, colSpan }">
<a-form :model="searchFormData" layout="inline">
<a-row :gutter="gutter">
<a-col v-bind="colSpan">
<a-form-item label="用户姓名" name="name">
<a-input :placeholder="'请输入用户姓名'" v-model:value="searchFormData.name"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="身份证号" name="idCard">
<a-input :placeholder="'请输入身份证号'" v-model:value="searchFormData.idCard"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="主叫号码" name="callerNumber">
<a-input :placeholder="'请输入主叫号码'"
v-model:value="searchFormData.callerNumber"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="被叫号码" name="calleeNumber">
<a-input :placeholder="'请输入被叫号码'"
v-model:value="searchFormData.calleeNumber"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="电话方向" name="callDirection">
<a-select v-model:value="searchFormData.callDirection" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_DIRECTION"
:key="item.dval" :value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col class="align-left" v-bind="colSpan">
<a-space>
<a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button ghost type="primary" @click="handleSearch">
{{ $t('button.search') }}
</a-button>
</a-space>
</a-col>
</a-row>
</a-form>
</template>
</x-search-bar>
<a-card>
<template #extra>
<a-space>
<a-button type="primary" @click="$refs.editDialogRef.handleCreate(record)">新建</a-button>
</a-space>
</template>
<a-table :columns="columns" :data-source="listData" bordered="true" :loading="loading"
:pagination="paginationState" :scroll="{ x: 'max-content' }" @change="onTableChange">
<template #bodyCell="{ index, column, record }">
<template v-if="column.key === 'serialNumber'">
<span>{{ index + 1 }}</span>
</template>
<template v-if="column.key === 'categoryType'">
<span>{{ dicsStore.getDictLabel('PROJECT_TYPE', record.categoryType) }}</span>
</template>
<!-- <template v-if="column.key === 'status'">
<a-tag v-if="record.status === 'enabled'" color="green">启用</a-tag>
<a-tag v-else>停用</a-tag>
</template> -->
<template v-if="'action' === column.key">
<x-action-button @click="$refs.editDialogRef.handleEdit(record)">
<span>编辑</span>
</x-action-button>
<x-action-button @click="handleDelete(record)">
<span style="color: #ff4d4f;">删除</span>
</x-action-button>
</template>
</template>
</a-table>
</a-card>
</a-col>
</a-row>
<edit-dialog ref="editDialogRef" @ok="onOk"></edit-dialog>
</template>
<script setup>
import { message, Modal } from 'ant-design-vue'
import { ref } from 'vue'
import apis from '@/apis'
import { config } from '@/config'
import { usePagination } from '@/hooks'
import { useI18n } from 'vue-i18n'
import EditDialog from './compontent/EditDialog.vue'
import { useDicsStore } from '@/store'
defineOptions({
name: 'serverProjectManage',
})
const dicsStore = useDicsStore()
const activeKey = ref('1')
const columns = [
{
title: '话务员ID',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 100,
},
{
title: '坐席姓名',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 180,
},
{
title: '性别',
dataIndex: 'callerNumber',
key: 'callerNumber',
align: 'center',
},
{
title: '身份证号',
dataIndex: 'calledNumber',
key: 'calledNumber',
align: 'center',
},
{
title: '联系电话',
dataIndex: 'idCardNumber',
key: 'idCardNumber',
align: 'center',
},
{
title: '话务工号',
dataIndex: 'region',
key: 'region',
align: 'center',
},
{
title: '常用分机',
dataIndex: 'callSource',
key: 'callSource',
align: 'center',
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
align: 'center',
width: 120,
fixed: 'right',
}
];
const { t } = useI18n() // t
const { listData, loading, showLoading, hideLoading, paginationState, resetPagination, searchFormData } = usePagination()
const editDialogRef = ref()
getPageList()
async function getPageList() {
try {
const { pageSize, current } = paginationState
const { success, data, total } = await apis.projectType
.getProjectList({
pageSize,
current: current,
...searchFormData.value,
})
.catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
listData.value = data
paginationState.total = total
}
} catch (error) {
}
}
/**
* 删除
*/
function handleDelete({ id }) {
Modal.confirm({
title: t('pages.system.user.delTip'),
content: t('button.confirm'),
okText: t('button.confirm'),
onOk: () => {
return new Promise((resolve, reject) => {
; (async () => {
try {
const { success } = await apis.projectType.delItem(id).catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
resolve()
message.success(t('component.message.success.delete'))
await getPageList()
}
} catch (error) {
reject()
}
})()
})
},
})
}
/**
* 分页
*/
function onTableChange({ current, pageSize }) {
paginationState.current = current
paginationState.pageSize = pageSize
getPageList()
}
/**
* 搜索
*/
function handleSearch() {
resetPagination()
getPageList()
}
/**
* 重置
*/
function handleResetSearch() {
searchFormData.value = {}
resetPagination()
getPageList()
}
/**
* 编辑完成
*/
async function onOk() {
await getPageList()
}
</script>
<style lang="less" scoped></style>

View File

@ -183,14 +183,6 @@ const columns = [
align: 'center', align: 'center',
width: 200, width: 200,
}, },
{
title: '所在节点',
dataIndex: 'currentNode',
key: 'currentNode',
align: 'center',
width: 120,
},
{ {
title: '档案号', title: '档案号',
dataIndex: 'fileNumber', dataIndex: 'fileNumber',

View File

@ -3,19 +3,6 @@
<template #default="{ gutter, colSpan }"> <template #default="{ gutter, colSpan }">
<a-form :model="searchFormData" layout="inline" labelAlign="left"> <a-form :model="searchFormData" layout="inline" labelAlign="left">
<a-row :gutter="[24, 24]"> <a-row :gutter="[24, 24]">
<!-- 所在区域 -->
<a-col :span="12">
<a-form-item label="所在节点" name="currentNode">
<a-tree-select v-model:value="value" show-search style="width: 100%"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="Please select"
allow-clear tree-default-expand-all :tree-data="treeData" tree-node-filter-prop="label">
<template #title="{ value: val, label }">
<b v-if="val === 'parent 1-1'" style="color: #08c">sss</b>
<template v-else>{{ label }}</template>
</template>
</a-tree-select>
</a-form-item>
</a-col>
<!-- 姓名 --> <!-- 姓名 -->
<a-col :span="8"> <a-col :span="8">
<a-form-item label="姓名" name="name"> <a-form-item label="姓名" name="name">
@ -175,14 +162,6 @@ const columns = [
align: 'center', align: 'center',
width: 200, width: 200,
}, },
{
title: '所在节点',
dataIndex: 'currentNode',
key: 'currentNode',
align: 'center',
width: 120,
},
{ {
title: '档案号', title: '档案号',
dataIndex: 'fileNumber', dataIndex: 'fileNumber',

View File

@ -1,18 +1,11 @@
<template> <template>
<x-search-bar class="mb-8-2"> <x-search-bar class="mb-8-2">
<template #default="{ gutter, colSpan }"> <template #default="{ gutter, colSpan }">
<a-form :model="searchFormData" labelAlign="left"> <a-form :model="searchFormData" labelAlign="left">
<a-row :gutter="12"> <a-row :gutter="12">
<a-col :span="8"> <a-col :span="8">
<a-form-item label="所在节点" name="currentNode"> <a-form-item label="所在站点" name="stationId">
<a-tree-select v-model:value="value" show-search style="width: 100%" <ServiceStation v-model:value="searchFormData.stationId" />
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="Please select"
allow-clear tree-default-expand-all :tree-data="treeData" tree-node-filter-prop="label">
<template #title="{ value: val, label }">
<b v-if="val === 'parent 1-1'" style="color: #08c">sss</b>
<template v-else>{{ label }}</template>
</template>
</a-tree-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<!-- 所在区域 --> <!-- 所在区域 -->
@ -37,7 +30,7 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<!-- 姓名 --> <!-- 姓名 -->
<a-col :span="8"> <a-col :span="8">
<a-form-item label="姓名" name="name"> <a-form-item label="姓名" name="name">
<a-input v-model:value="searchFormData.name" placeholder="请输入姓名" /> <a-input v-model:value="searchFormData.name" placeholder="请输入姓名" />
@ -49,13 +42,13 @@
<a-input v-model:value="searchFormData.idNumber" placeholder="请输入身份证号" /> <a-input v-model:value="searchFormData.idNumber" placeholder="请输入身份证号" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<!-- 身份证号 --> <!-- 身份证号 -->
<a-col :span="8"> <a-col :span="8">
<a-form-item label="身份证号" name="idNumber"> <a-form-item label="身份证号" name="idNumber">
<a-input v-model:value="searchFormData.idNumber" placeholder="请输入身份证号" /> <a-input v-model:value="searchFormData.idNumber" placeholder="请输入身份证号" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<!-- 身份证号 --> <!-- 身份证号 -->
<a-col :span="8"> <a-col :span="8">
<a-form-item label="档案号" name="fileNumber"> <a-form-item label="档案号" name="fileNumber">
<a-input v-model:value="searchFormData.fileNumber" placeholder="请输入档案号" /> <a-input v-model:value="searchFormData.fileNumber" placeholder="请输入档案号" />
@ -110,11 +103,11 @@
<x-action-button @click="$refs.detailRef.handleCreate(record)"> <x-action-button @click="$refs.detailRef.handleCreate(record)">
<span>详情</span> <span>详情</span>
</x-action-button> </x-action-button>
<x-action-button @click="$refs.detailRef.handleCreate(record)"> <x-action-button @click="$refs.detailRef.handleCreate(record)">
<span>编辑</span> <span>编辑</span>
</x-action-button> </x-action-button>
</template> </template>
</template> </template>
</a-table> </a-table>
@ -138,12 +131,13 @@ import detail from '../serverList/components/detail.vue'
import { useDicsStore } from '@/store' import { useDicsStore } from '@/store'
import AreaCascader from '@/components/AreaCascader/index.vue' import AreaCascader from '@/components/AreaCascader/index.vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import ServiceStation from '@/components/ServiceStation/index.vue'
defineOptions({ defineOptions({
name: 'serverList', name: 'serverList',
}) })
const totalCount = ref(0) // const totalCount = ref(0) //
const dicsStore = useDicsStore() const dicsStore = useDicsStore()
const labelOptions= ref([]) const labelOptions = ref([])
const columns = [ const columns = [
{ {
title: '序号', title: '序号',
@ -360,9 +354,9 @@ const columns = [
width: 160, width: 160,
}, },
{ {
title: '所在点', title: '所在点',
dataIndex: 'currentNode', dataIndex: 'stationName',
key: 'currentNode', key: 'stationName',
align: 'center', align: 'center',
width: 120, width: 120,
}, },

View File

@ -5,15 +5,8 @@
<a-row :gutter="[24, 24]"> <a-row :gutter="[24, 24]">
<!-- 所在区域 --> <!-- 所在区域 -->
<a-col :span="8"> <a-col :span="8">
<a-form-item label="所在节点" name="currentNode"> <a-form-item label="所在站点" name="stationId">
<a-tree-select v-model:value="value" show-search style="width: 100%" <ServiceStation v-model:value="searchFormData.stationId" />
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="Please select"
allow-clear tree-default-expand-all :tree-data="treeData" tree-node-filter-prop="label">
<template #title="{ value: val, label }">
<b v-if="val === 'parent 1-1'" style="color: #08c">sss</b>
<template v-else>{{ label }}</template>
</template>
</a-tree-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<!-- 所在区域 --> <!-- 所在区域 -->
@ -93,6 +86,7 @@ import EditDialog from './components/EditDialog.vue'
import { useDicsStore } from '@/store' import { useDicsStore } from '@/store'
import AreaCascader from '@/components/AreaCascader/index.vue' import AreaCascader from '@/components/AreaCascader/index.vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import ServiceStation from '@/components/ServiceStation/index.vue'
defineOptions({ defineOptions({
name: 'allocation', name: 'allocation',
}) })
@ -188,9 +182,9 @@ const columns = [
}, },
{ {
title: '所在点', title: '所在点',
dataIndex: 'currentNode', dataIndex: 'stationName',
key: 'currentNode', key: 'stationName',
align: 'center', align: 'center',
width: 120, width: 120,
}, },

View File

@ -46,15 +46,8 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="8"> <a-col :span="8">
<a-form-item label="所在节点" name="currentNode"> <a-form-item label="所在站点" name="stationId">
<a-tree-select v-model:value="value" show-search style="width: 100%" <ServiceStation v-model:value="searchFormData.stationId" />
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="Please select"
allow-clear tree-default-expand-all :tree-data="treeData" tree-node-filter-prop="label">
<template #title="{ value: val, label }">
<b v-if="val === 'parent 1-1'" style="color: #08c">sss</b>
<template v-else>{{ label }}</template>
</template>
</a-tree-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
@ -134,6 +127,7 @@ import EditDialog from './components/EditDialog.vue'
import { useDicsStore } from '@/store' import { useDicsStore } from '@/store'
import AreaCascader from '@/components/AreaCascader/index.vue' import AreaCascader from '@/components/AreaCascader/index.vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import ServiceStation from '@/components/ServiceStation/index.vue'
defineOptions({ defineOptions({
name: 'allocation', name: 'allocation',
}) })
@ -213,9 +207,9 @@ const columns = [
width: 200, width: 200,
}, },
{ {
title: '所在点', title: '所在点',
dataIndex: 'currentNode', dataIndex: 'stationName',
key: 'currentNode', key: 'stationName',
align: 'center', align: 'center',
width: 120, width: 120,
}, },

View File

@ -2,179 +2,175 @@
<div> <div>
<a-row :gutter="20"> <a-row :gutter="20">
<!-- 基本信息 --> <!-- 基本信息 -->
<a-col :span="8"> <a-col :span="6">
<div ><span class="label">服务对象姓名姓名:</span> {{ formData.name || '-' }}</div> <div><span class="label">出生日期:</span> {{ dayjs(formData.birthDate).format('YYYY-MM-DD') || '-' }}</div>
</a-col> </a-col>
<a-col :span="8"> <a-col :span="6">
<div ><span class="label">性别:</span> {{ formData.gender || '-' }}</div> <div><span class="label">关爱巡访电话:</span> {{ formData.careVisitPhone || '-' }}</div>
</a-col> </a-col>
<a-col :span="8"> <a-col :span="6">
<div ><span class="label">出生日期:</span> {{ formData.birthDate || '-' }}</div> <div><span class="label">联系方式:</span> {{ formData.contact1 || '-' }}</div>
</a-col> </a-col>
<a-col :span="8"> <a-col :span="6">
<div ><span class="label">关爱巡访电话:</span> {{ formData.careVisitPhone || '-' }}</div> <div><span class="label">其他电话1:</span> {{ formData.otherPhone1 || '-' }}</div>
</a-col> </a-col>
<a-col :span="8"> <a-col :span="6">
<div ><span class="label">联系方式:</span> {{ formData.contact1 || '-' }}</div> <div><span class="label">其他电话2:</span> {{ formData.otherPhone2 || '-' }}</div>
</a-col> </a-col>
<a-col :span="8"> <a-col :span="6">
<div ><span class="label">其他电话1:</span> {{ formData.otherPhone1 || '-' }}</div> <div><span class="label">社保卡号:</span> {{ formData.socialSecurityCardNumber || '-' }}</div>
</a-col> </a-col>
<a-col :span="8"> <a-col :span="6">
<div ><span class="label">其他电话2:</span> {{ formData.otherPhone2 || '-' }}</div> <div><span class="label">证件类型:</span> {{ dicsStore.getDictLabel('CARD_TYPE', formData.identityType) || '-' }}
</div>
</a-col> </a-col>
<a-col :span="8"> <a-col :span="6">
<div ><span class="label">联系状态:</span> {{ formData.contactStatus || '-' }}</div> <div><span class="label">证件号码:</span> {{ formData.identityNo || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">社保卡号:</span> {{ formData.socialSecurityCardNumber || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">证件类型:</span> {{ formData.identityType || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">证件号码:</span> {{ formData.idNumber || '-' }}</div>
</a-col> </a-col>
<a-col :span="6">
<div><span class="label">护理等级:</span> {{ dicsStore.getDictLabel('Care_Level', formData.archive.nursingLevel) ||
'-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">健康状况:</span> {{ dicsStore.getDictLabel('Health_Condition',
formData.archive.healthStatus) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">生存状态:</span> {{ dicsStore.getDictLabel('LIVING_STATUS',
formData.archive.survivalStatus) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">服务状态:</span> {{ dicsStore.getDictLabel('SERVICE_STATUS',
formData.archive.serviceStatus) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">服务形式:</span> {{ dicsStore.getDictLabel('Service_Format', formData.archive.serviceForm)
|| '-' }}</div>
</a-col>
<!-- 其他字段 -->
<a-col :span="6">
<div><span class="label">居住情况:</span> {{ dicsStore.getDictLabel('Living_Situation',
formData.archive.livingSituation) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">子女情况:</span> {{ dicsStore.getDictLabel('CHILDREN_STATE',
formData.archive.childrenSituation) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">统计分类:</span> {{ dicsStore.getDictLabel('Statistical_Classification',
formData.archive.statisticsCategory) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">智力情况:</span> {{ dicsStore.getDictLabel('Intellectual_Condition',
formData.archive.intellectualSituation) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">长期照料失能子女:</span> {{ dicsStore.getDictLabel('Disabled_Child',
formData.archive.longTermCareForDisabledChildren) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">子女探望情况:</span> {{ dicsStore.getDictLabel('Frequency_Visits',
formData.archive.childrenVisitStatus) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">人户分离:</span> {{ dicsStore.getDictLabel('Separation',
formData.archive.householdResidenceSeparation) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">民族:</span> {{ dicsStore.getDictLabel('Ethnicity', formData.archive.ethnicity) || '-' }}
</div>
</a-col>
<a-col :span="6">
<div><span class="label">完成能力评估:</span> {{ dicsStore.getDictLabel('Capability_Assessment',
formData.archive.completedCapacityAssessment) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">住出租屋/地下室:</span> {{ dicsStore.getDictLabel('Property_Basement',
formData.archive.livesInRentedRoomOrBasement) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">经济来源:</span> {{ dicsStore.getDictLabel('Source_Income',
formData.archive.economicSource) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">文化程度:</span> {{ dicsStore.getDictLabel('Level_Education',
formData.archive.educationLevel) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">宗教信仰:</span> {{ dicsStore.getDictLabel('Religious_belief', formData.archive.religion)
|| '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">职业情况:</span> {{ dicsStore.getDictLabel('Employment_Status',
formData.archive.occupation) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">政治面貌:</span> {{ dicsStore.getDictLabel('Political_affiliation',
formData.archive.politicalAffiliation) || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">婚姻情况:</span> {{ dicsStore.getDictLabel('Marital_Status',
formData.archive.maritalStatus) || '-' }}</div>
</a-col>
<!-- 政府购买服务时间 -->
<a-col :span="12">
<div>
<span class="label">政府购买服务时间:</span>
{{ formatDate(formData.archive.starGovernmentService) }}- {{ formatDate(formData.archive.endGovernmentService)
}}
</div>
</a-col>
<a-col :span="24"> <a-col :span="24">
<div > <div>
<span class="label">家庭地址:</span> <span class="label">家庭地址:</span>
{{ formatArea(formData.homeAreaLabels) }} {{ formData.homeDetailAddress || '' }} {{ formatArea(formData.archive.homeAreaLabels) }} {{ formData.archive.homeDetailAddress || '' }}
<span v-if="formData.lag && formData.lat" style="color: #999;"> <span v-if="formData.lag && formData.lat" style="color: #999;">
(经度: {{ formData.lag }}, 纬度: {{ formData.lat }}) (经度: {{ formData.lag }}, 纬度: {{ formData.lat }})
</span> </span>
</div> </div>
</a-col> </a-col>
<a-col :span="8">
<div ><span class="label">护理等级:</span> {{ formData.nursingLevel || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">健康状况:</span> {{ formData.healthStatus || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">生存状态:</span> {{ formData.survivalStatus || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">服务状态:</span> {{ formData.serviceStatus || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">服务形式:</span> {{ formData.serviceForm || '-' }}</div>
</a-col>
<a-col :span="24"> <a-col :span="24">
<div > <div>
<span class="label">户籍地址:</span> <span class="label">户籍地址:</span>
{{ formatArea(formData.houseAreaLabels) }} {{ formData.householdDetailAddress || '' }} {{ formatArea(formData.archive.houseAreaLabels) }} {{ formData.archive.householdDetailAddress || '' }}
</div> </div>
</a-col> </a-col>
<!-- 政府购买服务时间 -->
<a-col :span="8">
<div >
<span class="label">政府购买服务开始时间():</span>
{{ formatDate(formData.governmentPurchasedServiceStartDateStart) }}
</div>
</a-col>
<a-col :span="8">
<div >
<span class="label">政府购买服务开始时间():</span>
{{ formatDate(formData.governmentPurchasedServiceStartDateEnd) }}
</div>
</a-col>
<!-- 其他字段 -->
<a-col :span="8">
<div ><span class="label">居住情况:</span> {{ formData.livingSituation || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">子女情况:</span> {{ formData.childrenSituation || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">统计分类:</span> {{ formData.statisticsCategory || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">智力情况:</span> {{ formData.intellectualSituation || '-' }}</div>
</a-col>
<a-col :span="8">
<div >
<span class="label">长期照料失能子女:</span>
{{ formData.longTermCareForDisabledChildren === 'true' ? '是' : formData.longTermCareForDisabledChildren === 'false' ? '否' : '-' }}
</div>
</a-col>
<a-col :span="8">
<div ><span class="label">子女探望情况:</span> {{ formData.childrenVisitStatus || '-' }}</div>
</a-col>
<a-col :span="8">
<div >
<span class="label">人户分离:</span>
{{ formData.householdResidenceSeparation === 'true' ? '是' : formData.householdResidenceSeparation === 'false' ? '否' : '-' }}
</div>
</a-col>
<a-col :span="8">
<div ><span class="label">民族:</span> {{ formData.ethnicity || '-' }}</div>
</a-col>
<a-col :span="8">
<div >
<span class="label">完成能力评估:</span>
{{ formData.completedCapacityAssessment === 'true' ? '是' : formData.completedCapacityAssessment === 'false' ? '否' : '-' }}
</div>
</a-col>
<a-col :span="8">
<div >
<span class="label">住出租屋/地下室:</span>
{{ formData.livesInRentedRoomOrBasement === 'true' ? '是' : formData.livesInRentedRoomOrBasement === 'false' ? '否' : '-' }}
</div>
</a-col>
<a-col :span="8">
<div ><span class="label">经济来源:</span> {{ formData.economicSource || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">文化程度:</span> {{ formData.educationLevel || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">宗教信仰:</span> {{ formData.religion || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">职业情况:</span> {{ formData.occupation || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">政治面貌:</span> {{ formData.politicalAffiliation || '-' }}</div>
</a-col>
<a-col :span="8">
<div ><span class="label">婚姻情况:</span> {{ formData.maritalStatus || '-' }}</div>
</a-col>
<!-- 数组类字段 --> <!-- 数组类字段 -->
<a-col :span="24" v-if="formData.idCardPhotos && formData.idCardPhotos.length > 0"> <a-col :span="24" v-if="formData.archive.idCardPhotos && formData.archive.idCardPhotos.length > 0">
<div > <div>
<span class="label">身份证照片:</span> <span class="label">身份证照片:</span>
<div style="margin-top: 8px;"> <div style="margin-top: 8px;">
<a-image <a-image v-for="(url, index) in formData.archive.idCardPhotos" :key="index" :src="url" fit="cover"
v-for="(url, index) in formData.idCardPhotos" style="width: 100px; height: 60px; margin-right: 8px;" :preview-src-list="formData.archive.idCardPhotos" />
:key="index"
:src="url"
fit="cover"
style="width: 100px; height: 60px; margin-right: 8px;"
:preview-src-list="formData.idCardPhotos"
/>
</div> </div>
</div> </div>
</a-col> </a-col>
</a-row> </a-row>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, defineProps } from 'vue'; import { ref, defineProps, defineExpose } from 'vue';
import dayjs from 'dayjs'
import { useDicsStore } from '@/store'
const dicsStore = useDicsStore()
const props = defineProps({
formData: {
type: Object,
default: () => ({})
}
});
const formData=ref({});
// //
const formatArea = (areaArray) => { const formatArea = (areaArray) => {

View File

@ -1,6 +1,48 @@
<template> <template>
<div>新建文件</div> <div>
<a-row :gutter="20">
<!-- 基本信息 -->
<a-col :span="6">
<div><span class="label">残疾证号:</span> {{ formData.disabledPerson.disabledNo || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">残疾类型:</span> {{ formData.disabledPerson.disabledType || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">残疾等级:</span> {{ formData.disabledPerson.disabledLv || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">是否已签订托养协议:</span> {{ formData.disabledPerson.agreementSigned || '-' }}</div>
</a-col>
<a-col :span="6">
<div><span class="label">托养起止时间:</span> {{ formData.disabledPerson.starCareService || '-' }} - {{ formData.disabledPerson.endCareService || '-' }}</div>
</a-col>
<a-col :span="24" v-if="formData.disabledPerson.careServiceImgs && formData.disabledPerson.careServiceImgs.length > 0">
<div>
<span class="label">托养协议照片:</span>
<div style="margin-top: 8px;">
<a-image v-for="(url, index) in formData.disabledPerson.careServiceImgs" :key="index" :src="url" fit="cover"
style="width: 100px; height: 60px; margin-right: 8px;" :preview-src-list="formData.disabledPerson.careServiceImgs" />
</div>
</div>
</a-col>
</a-row>
</div>
</template> </template>
<script setup> <script setup>
import { ref, defineProps } from 'vue';
const props = defineProps({
formData: {
type: Object,
default: () => ({})
}
});
</script> </script>
<style scoped>
.label {
display: inline-block;
line-height: 45px;
}
</style>

View File

@ -624,6 +624,7 @@ function handleOk() {
params.archive.starGovernmentService = formData.value.governmentPurchasedServiceStartDate[0] params.archive.starGovernmentService = formData.value.governmentPurchasedServiceStartDate[0]
params.archive.endGovernmentService = formData.value.governmentPurchasedServiceStartDate[1] params.archive.endGovernmentService = formData.value.governmentPurchasedServiceStartDate[1]
} }
// params.stationId=storage.local.getItem('stationId')
// //
if (params.identityType === '1' && !isValidIdCard(params.identityNo)) { if (params.identityType === '1' && !isValidIdCard(params.identityNo)) {
return message.error('请输入正确的身份证号码') return message.error('请输入正确的身份证号码')

View File

@ -0,0 +1,156 @@
<template>
<a-modal :open="modal.open" :title="modal.title" :width="600" :confirm-loading="modal.confirmLoading"
:after-close="onAfterClose" :cancel-text="cancelText" @ok="handleOk" @cancel="handleCancel">
<a-spin tip="Loading..." :spinning="spining">
<a-card>
<a-form ref="formRef" :model="formData" :rules="formRules">
<a-row :gutter="24">
<a-col :span="24">
<a-form-item label="转出类型" name="directionType">
<a-radio-group v-model:value="formData.directionType">
<a-radio :value="'Transfer'">转出</a-radio>
<a-radio :value="'Death'">去世</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="转出原因" name="reason">
<a-select v-model:value="formData.reason" placeholder="请选择转出原因" allow-clear >
<a-select-option v-for="item in dicsStore.dictOptions.OUT_REASON"
:key="item.dval" :value="item.dval">
{{ item.introduction }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="转入节点" name="nStationId">
<ServiceStation @change="handleChange" v-model:value="formData.nStationId"/>
</a-form-item>
</a-col>
<a-col :span="24" v-if="formData.directionType==='Death'">
<a-form-item label="去世时间">
<a-date-picker v-model:value="formData.passWayAt" style="width: 100%;"/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="备注" name="remark">
<a-textarea v-model:value="formData.remark" placeholder="请输入备注"
:rows="1" :auto-size="{ minRows: 1, maxRows: 2 }" />
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
</a-spin>
</a-modal>
</template>
<script setup>
import { ref, defineProps, nextTick, watch } from 'vue'
import { config } from '@/config'
import apis from '@/apis'
import { useForm, useModal, useSpining } from '@/hooks'
import { useDicsStore } from '@/store'
import dayjs from 'dayjs'
import storage from '@/utils/storage'
import ServiceStation from '@/components/ServiceStation/index.vue'
const emit = defineEmits(['ok'])
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { formRecord, formData, formRef, formRules, resetForm } = useForm()
const cancelText = ref('取消')
const spining = ref(false)
formRules.value = {
reason: [{ required: true, message: '请选择原因', trigger: 'change' }],
directionType: [{ required: true, message: '请选择类型', trigger: 'change' }],
nStationId:[{ required: true, message: '请选择节点', trigger: 'change' }],
passWayAt:[{ required: true, message: '请选择去世时间', trigger: 'change' }]
}
const dicsStore = useDicsStore()
formData.value = {}
/**
* 新建
*/
function handleCreate(id) {
formData.value.directionType = 'Transfer'
formData.value.direction='Out'
formData.value.customerId=storage.local.getItem('stationId'),
formData.value.stationId=id
showModal({
type: 'create',
title: '转出',
})
}
function handleChange(e){
console.log(e)
}
/**
* 确定
*/
function handleOk() {
formRef.value
.validateFields()
.then(async (values) => {
try {
showLoading()
let params = {
...formData.value,
}
if(params.directionType==='Death'){
params.passWayAt=dayjs(formData.value.passWayAt)
}
let result = null
switch (modal.value.type) {
case 'create':
result = await apis.serverObj.inOutLogs(params).catch(() => {
throw new Error()
})
console.log('result', result.code)
break
case 'edit':
console.log(params)
result = await apis.serverObj.updateItem(params.id, params).catch(() => {
throw new Error()
})
break
}
hideLoading()
if (config('http.code.success') === true) {
hideModal()
emit('ok')
}
} catch (error) {
console.log(error.message)
hideLoading()
// message.error(error.message)
}
})
.catch(() => {
hideLoading()
})
}
/**
* 取消
*/
function handleCancel() {
hideModal()
}
/**
* 关闭后
*/
function onAfterClose() {
resetForm()
hideLoading()
}
defineExpose({
handleCreate
})
</script>
<style lang="less" scoped></style>

View File

@ -1,31 +1,40 @@
<template> <template>
<a-modal :open="modal.open" :title="modal.title" :width="1000" :confirm-loading="modal.confirmLoading" <a-modal :open="modal.open" :title="modal.title" :width="1200" :confirm-loading="modal.confirmLoading"
:after-close="onAfterClose" :cancel-text="cancelText" @ok="handleOk" @cancel="handleCancel"> :after-close="onAfterClose" :cancel-text="cancelText" @ok="handleOk" @cancel="handleCancel">
<a-card> <a-card>
<div style="display: flex;justify-content: space-around;"> <div style="display: flex; justify-content: space-around;flex-direction: column;">
<div <!-- 左侧信息栏 -->
style="width:280px;margin-top: 20px;border-right: 1px solid #f0f0f0;display: flex;flex-direction: column;align-items: center;"> <div style="margin-top: 20px;display: flex;align-items: center;margin-left: 20px;">
<gx-upload v-model="formData.imgList" accept-types=".jpg,.png,.webp" :fileNumber="1" /> <gx-upload v-model="formData.imgList" accept-types=".jpg,.png,.webp" :fileNumber="1" />
<div> <div style="margin-left: 20px;">
<p>{{ formData.name }}{{ formData.gender }}{{ formData.age }}</p> <p style="font-weight: bold;">
<p>身份证号{{ formData.idNumber }}</p> <span>{{ formData.name }}</span>
<p>手机号{{ formData.contact1 }}</p> <span style="margin: 0 50px;">{{ formData.gender === '1' ? '男' : '女' }}</span>
<!-- <p>联系人{{ formData.contactman }}</p> <span>{{ formData.age }}</span>
<p>联系方式{{ formData.contact1 }}</p> --> </p>
<p><a-tag color="#2db7f5" v-for="value in formData.serviceRecipientCategory">{{ value }}</a-tag></p> <p>
<span>身份证号{{ formData.idNumber }}</span>
<span style="margin: 0 50px;">手机号{{ formData.contact1 }}</span>
</p>
<p>
<a-tag color="#2db7f5" v-for="value in formData.serviceRecipientCategory" :key="value">
{{ value }}
</a-tag>
</p>
</div> </div>
</div> </div>
<div style="width: calc(100% - 280px);padding: 20px;">
<!-- Tab 页签 --> <!-- 右侧 Tab 区域 -->
<div style="padding: 20px;">
<a-tabs v-model:activeKey="activeKey" @change="handleTabChange"> <a-tabs v-model:activeKey="activeKey" @change="handleTabChange">
<a-tab-pane v-for="(tab, index) in tabsList" :key="index+1" :tab="tab" /> <a-tab-pane v-for="(tab, index) in tabsList" :key="index + 1" :tab="tab" />
</a-tabs> </a-tabs>
<!-- 动态组件区域 -->
<!-- 动态组件 + keep-alive -->
<div style="flex: 1; padding: 16px; overflow-y: auto;"> <div style="flex: 1; padding: 16px; overflow-y: auto;">
<keep-alive> <keep-alive>
<component v-if="currentComponent" :is="currentComponent" :ref="activeKey" <component v-if="currentComponent" :is="currentComponent" ref="dynamicComponentRef"
:key="activeKey" /> :key="tabsList[activeKey - 1]" :formData="formData" />
</keep-alive> </keep-alive>
</div> </div>
</div> </div>
@ -37,21 +46,43 @@
<script setup> <script setup>
import GxUpload from '@/components/GxUpload/index.vue' import GxUpload from '@/components/GxUpload/index.vue'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { ref, computed, defineAsyncComponent, defineExpose, getCurrentInstance, nextTick } from 'vue' import {
import { config } from '@/config' ref,
import apis from '@/apis' computed,
defineAsyncComponent,
defineExpose,
nextTick
} from 'vue'
import { useForm, useModal } from '@/hooks' import { useForm, useModal } from '@/hooks'
import { useDicsStore } from '@/store' import apis from '@/apis'
const childData = ref({})
const emit = defineEmits(['ok']) const emit = defineEmits(['ok'])
const activeKey = ref(0) // tab key
const activeKey = ref(1)
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal() const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { formRecord, formData, formRef, formRules, resetForm } = useForm() const { formData, resetForm } = useForm()
const cancelText = ref('取消') const cancelText = ref('取消')
const imgList = ref([]) const recordId = ref('');
const tabsList = ['基本信息', '残疾人信息', '联系人信息', '附件', '病史信息', '服务记录', '工单图片和视频', '联络历史', '操作记录', '转入转出记录'] // Tab
const tabsList = [
'基本信息',
'残疾人信息',
'联系人信息',
'附件',
'病史信息',
'服务记录',
'工单图片和视频',
'联络历史',
'操作记录',
'转入转出记录'
]
// ref
const dynamicComponentRef = ref(null) //
// tab -> //
const componentMap = { const componentMap = {
'基本信息': defineAsyncComponent(() => import('./BasicInfo.vue')), '基本信息': defineAsyncComponent(() => import('./BasicInfo.vue')),
'残疾人信息': defineAsyncComponent(() => import('./DisabledPersonInfo.vue')), '残疾人信息': defineAsyncComponent(() => import('./DisabledPersonInfo.vue')),
@ -63,37 +94,48 @@ const componentMap = {
'联络历史': defineAsyncComponent(() => import('./ContactHistory.vue')), '联络历史': defineAsyncComponent(() => import('./ContactHistory.vue')),
'操作记录': defineAsyncComponent(() => import('./OperationLog.vue')), '操作记录': defineAsyncComponent(() => import('./OperationLog.vue')),
'转入转出记录': defineAsyncComponent(() => import('./TransferRecords.vue')), '转入转出记录': defineAsyncComponent(() => import('./TransferRecords.vue')),
}; }
// //
const currentComponent = computed(() => { const currentComponent = computed(() => {
const tabName = tabsList[activeKey.value]; const tabName = tabsList[activeKey.value - 1]
return componentMap[tabName]; return componentMap[tabName] || null
}); })
const getBasicInfo = async () => {
const { data, success } = await apis.serverObj.getItem(recordId.value).catch()
if (!success) {
return
}
return data;
}
const getDisabledPersonInfo = async () => {
// tab return { ceshi: '残疾人信息' };
const handleTabChange = (key) => { }
// // Tab
nextTick(() => { const handleTabChange = async (key) => {
const tabName = tabsList[key]; switch (tabsList[key - 1]) {
const componentName = componentMap[tabName]; case '基本信息':
if (componentName) { formData.value = await getBasicInfo();
// break;
const instance = proxy.$refs[key]; case '残疾人信息':
if (instance && typeof instance.onTabClick === 'function') { formData.value = await getBasicInfo();
instance.onTabClick(); // onTabClick break;
} else if (instance && typeof instance.loadData === 'function') { case '联系人信息':
instance.loadData(); // loadData formData.value = await getBasicInfo();
} break;
} case '附件':
}); case '病史信息':
}; case '服务记录':
case '工单图片和视频':
case '联络历史':
case '操作记录':
case '转入转出记录':
// setup 使 $refs break
const { proxy } = getCurrentInstance(); }
/** }
* 新建 // ...
*/
function handleCreate(record) { function handleCreate(record) {
showModal({ showModal({
type: 'create', type: 'create',
@ -102,72 +144,27 @@ function handleCreate(record) {
formData.value = record formData.value = record
} }
/**
* 编辑
*/
function handleEdit(record = {}) { function handleEdit(record = {}) {
activeKey.value=1 activeKey.value = 1
showModal({ showModal({
type: 'edit', type: 'edit',
title: '编辑项', title: '详情',
}) })
recordId.value = record.id
formData.value = cloneDeep(record) formData.value = cloneDeep(record)
} }
const callback = (val) => {
console.log(val);
};
/**
* 确定
*/
function handleOk() { function handleOk() {
formRef.value // ...
.validateFields()
.then(async (values) => {
try {
showLoading()
const params = {
...formData.value,
}
let result = null
switch (modal.value.type) {
case 'create':
result = await apis.serverObj.createItem(params).catch(() => {
throw new Error()
})
break
case 'edit':
result = await apis.serverObj.updateItem(params).catch(() => {
throw new Error()
})
break
}
hideLoading()
if (config('http.code.success') === result?.code) {
hideModal()
emit('ok')
}
} catch (error) {
hideLoading()
}
})
.catch(() => {
hideLoading()
})
} }
/**
* 取消
*/
function handleCancel() { function handleCancel() {
hideModal() hideModal()
} }
/**
* 关闭后
*/
function onAfterClose() { function onAfterClose() {
activeKey.value=0 activeKey.value = 1
resetForm() formData.value = { archive: { idCardPhotos: [] } }
hideLoading() hideLoading()
} }
@ -175,6 +172,4 @@ defineExpose({
handleCreate, handleCreate,
handleEdit, handleEdit,
}) })
</script> </script>
<style lang="less" scoped></style>

View File

@ -3,13 +3,13 @@
<template #default="{ gutter, colSpan }"> <template #default="{ gutter, colSpan }">
<a-form :model="searchFormData" labelAlign="left"> <a-form :model="searchFormData" labelAlign="left">
<a-row :gutter="24"> <a-row :gutter="24">
<a-col :span="8" v-if="platForm === 'jianguan'"> <a-col :span="8" v-if="platForm !== 'yunying'">
<a-form-item label="所在节点" name="serviceNodeIds"> <a-form-item label="所在站点" name="stationId">
<node-tree v-model:value="searchFormData.serviceNodeIds" /> <ServiceStation v-model:value="searchFormData.stationId" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<!-- 所在区域 --> <!-- 所在区域 -->
<a-col :span="8" v-if="platForm === 'jianguan'"> <a-col :span="8" v-if="platForm !== 'yunying'">
<a-form-item label="所在区域" name="areaCodes"> <a-form-item label="所在区域" name="areaCodes">
<AreaCascader v-model:value="searchFormData.areaCodes" @change="onAreaChange" /> <AreaCascader v-model:value="searchFormData.areaCodes" @change="onAreaChange" />
</a-form-item> </a-form-item>
@ -244,8 +244,8 @@
</template> </template>
<template #extra> <template #extra>
<a-space> <a-space>
<a-button type="primary" @click="$refs.editDialogRef.handleCreate()">新建</a-button> <a-button type="primary" v-if="platForm!=='hujiao'" @click="$refs.editDialogRef.handleCreate()">新建</a-button>
<a-dropdown> <a-dropdown v-if="platForm!=='hujiao'">
<template #overlay> <template #overlay>
<a-menu @click="handleMenuClick"> <a-menu @click="handleMenuClick">
<a-menu-item key="1"> <a-menu-item key="1">
@ -340,10 +340,10 @@
<span>线下工单</span> <span>线下工单</span>
</x-action-button> </x-action-button>
<x-action-button @click="$refs.lineOrderRef.handleEdit(record, '1')" <x-action-button @click="$refs.lineOrderRef.handleEdit(record, '1')"
v-if="platForm === 'jianguan'"> v-if="platForm !== 'yunying'">
<span>线上工单</span> <span>线上工单</span>
</x-action-button> </x-action-button>
<x-action-button @click="checkHandler(record)"> <x-action-button @click="$refs.transferRef.handleCreate(record.id)">
<span>转出</span> <span>转出</span>
</x-action-button> </x-action-button>
</template> </template>
@ -353,9 +353,9 @@
</a-col> </a-col>
</a-row> </a-row>
<edit-dialog ref="editDialogRef" @ok="onOk"></edit-dialog> <edit-dialog ref="editDialogRef" @ok="onOk"></edit-dialog>
<detail ref="detailRef"></detail> <detail ref="detailRef" ></detail>
<LineOrder2 ref="lineOrderRef" /> <LineOrder2 ref="lineOrderRef" />
<TransferOut ref="transferRef" />
<!-- <a-drawer v-model:open="lineOpen" class="custom-class" width="600" root-class-name="root-class-name" :root-style="{ color: 'blue' }" :title="lineTitle" placement="right"> <!-- <a-drawer v-model:open="lineOpen" class="custom-class" width="600" root-class-name="root-class-name" :root-style="{ color: 'blue' }" :title="lineTitle" placement="right">
<LineOrder ref="lineOrderRef" /> <LineOrder ref="lineOrderRef" />
</a-drawer> --> </a-drawer> -->
@ -379,6 +379,8 @@ import NodeTree from '@/components/NodeTree/index.vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import {DownOutlined} from '@ant-design/icons-vue' import {DownOutlined} from '@ant-design/icons-vue'
import storage from '@/utils/storage' import storage from '@/utils/storage'
import TransferOut from './components/TransferOut.vue'
import ServiceStation from '@/components/ServiceStation/index.vue'
defineOptions({ defineOptions({
name: 'serverList', name: 'serverList',
}) })
@ -387,7 +389,7 @@ const totalCount = ref(0) // 总人数
const dicsStore = useDicsStore() const dicsStore = useDicsStore()
const lineOpen = ref(false) const lineOpen = ref(false)
const lineTitle = ref('线下工单') const lineTitle = ref('线下工单')
const transferRef=ref()
const serviceName = ref('') const serviceName = ref('')
const columns = [ const columns = [
{ {
@ -573,9 +575,9 @@ const columns = [
width: 160, width: 160,
}, },
{ {
title: '所在点', title: '所在点',
dataIndex: 'currentNode', dataIndex: 'stationName',
key: 'currentNode', key: 'stationName',
align: 'center', align: 'center',
width: 120, width: 120,
}, },
@ -638,7 +640,7 @@ async function getPageList() {
const { pageSize, current } = paginationState const { pageSize, current } = paginationState
const { success, data, total } = await apis.serverObj const { success, data, total } = await apis.serverObj
.getProjectList({ .getProjectList({
stationId:storage.local.getItem('stationId'), stationId:storage.local.getItem('stationId')||'',
companyId:storage.local.getItem('companyId'), companyId:storage.local.getItem('companyId'),
pageSize, pageSize,
current: current, current: current,
@ -678,6 +680,7 @@ const checkHandler = (record) => {
}, },
}) })
} }
/** /**
* 删除 * 删除
*/ */

View File

@ -190,9 +190,9 @@ const columns = [
}, },
{ {
title: '所在点', title: '所在点',
dataIndex: 'currentNode', dataIndex: 'stationName',
key: 'currentNode', key: 'stationName',
align: 'center', align: 'center',
width: 120, width: 120,
}, },

View File

@ -1,75 +1,68 @@
<template> <template>
<x-search-bar class="mb-8-2"> <x-search-bar class="mb-8-2">
<template #default="{ gutter, colSpan }"> <template #default="{ gutter, colSpan }">
<a-form :model="searchFormData" layout="inline" labelAlign="left"> <a-form :model="searchFormData" layout="inline" labelAlign="left">
<a-row :gutter="[24, 24]"> <a-row :gutter="[24, 24]">
<!-- 所在区域 --> <!-- 所在区域 -->
<a-col :span="12"> <a-col :span="12">
<a-form-item label="所在节点" name="currentNode"> <a-form-item label="所在站点" name="stationId">
<a-tree-select v-model:value="value" show-search style="width: 100%" <ServiceStation v-model:value="searchFormData.stationId" />
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" placeholder="Please select"
allow-clear tree-default-expand-all :tree-data="treeData" tree-node-filter-prop="label">
<template #title="{ value: val, label }">
<b v-if="val === 'parent 1-1'" style="color: #08c">sss</b>
<template v-else>{{ label }}</template>
</template>
</a-tree-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<!-- 姓名 --> <!-- 姓名 -->
<a-col :span="8"> <a-col :span="8">
<a-form-item label="姓名" name="name"> <a-form-item label="姓名" name="name">
<a-input v-model:value="searchFormData.name" placeholder="请输入姓名" /> <a-input v-model:value="searchFormData.name" placeholder="请输入姓名" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<!-- 身份证号 --> <!-- 身份证号 -->
<a-col :span="8"> <a-col :span="8">
<a-form-item label="身份证号" name="idNumber"> <a-form-item label="身份证号" name="idNumber">
<a-input v-model:value="searchFormData.idNumber" placeholder="请输入身份证号" /> <a-input v-model:value="searchFormData.idNumber" placeholder="请输入身份证号" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<!-- 操作按钮 --> <!-- 操作按钮 -->
<a-col class="align-left" :span="8"> <a-col class="align-left" :span="8">
<a-space>
<a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button ghost type="primary" @click="handleSearch">
{{ $t('button.search') }}
</a-button>
</a-space>
</a-col>
</a-row>
</a-form>
</template>
</x-search-bar>
<a-row :gutter="8" :wrap="false">
<a-col flex="auto">
<a-card title="电话关爱对象列表">
<template #extra>
<a-space> <a-space>
<a-button type="primary">导入</a-button> <a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button type="dashed">导入记录</a-button> <a-button ghost type="primary" @click="handleSearch">
<a-button type="primary">导出</a-button> {{ $t('button.search') }}
<a-button type="dashed">导出记录</a-button> </a-button>
</a-space> </a-space>
</template> </a-col>
<a-table :columns="columns" :data-source="listData" bordered="true" :loading="loading" </a-row>
:pagination="paginationState" :scroll="{ x: 'max-content' }" @change="onTableChange"> </a-form>
<template #bodyCell="{ index, column, record }"> </template>
<template v-if="column.key === 'serialNumber'"> </x-search-bar>
<span>{{ index + 1 }}</span> <a-row :gutter="8" :wrap="false">
</template> <a-col flex="auto">
<template v-if="'action' === column.key"> <a-card title="电话关爱对象列表">
<x-action-button @click="$refs.editDialogRef.handleCreate(record)"> <template #extra>
<span>编辑</span> <a-space>
</x-action-button> <a-button type="primary">导入</a-button>
</template> <a-button type="dashed">导入记录</a-button>
<a-button type="primary">导出</a-button>
<a-button type="dashed">导出记录</a-button>
</a-space>
</template>
<a-table :columns="columns" :data-source="listData" bordered="true" :loading="loading"
:pagination="paginationState" :scroll="{ x: 'max-content' }" @change="onTableChange">
<template #bodyCell="{ index, column, record }">
<template v-if="column.key === 'serialNumber'">
<span>{{ index + 1 }}</span>
</template> </template>
</a-table> <template v-if="'action' === column.key">
</a-card> <x-action-button @click="$refs.editDialogRef.handleCreate(record)">
</a-col> <span>编辑</span>
</a-row> </x-action-button>
<edit-dialog ref="editDialogRef" @ok="onOk"></edit-dialog> </template>
</template>
</a-table>
</a-card>
</a-col>
</a-row>
<edit-dialog ref="editDialogRef" @ok="onOk"></edit-dialog>
</template> </template>
<script setup> <script setup>
@ -138,7 +131,7 @@ const columns = [
align: 'center', align: 'center',
width: 120, width: 120,
}, },
// --- --- // --- ---
{ {
title: '联系方式1', title: '联系方式1',
@ -177,9 +170,9 @@ const columns = [
}, },
{ {
title: '所在点', title: '所在点',
dataIndex: 'currentNode', dataIndex: 'stationName',
key: 'currentNode', key: 'stationName',
align: 'center', align: 'center',
width: 120, width: 120,
}, },
@ -381,4 +374,4 @@ async function onOk() {
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped></style>

View File

@ -45,6 +45,7 @@ import apis from '@/apis'
import { useForm, useModal, useSpining } from '@/hooks' import { useForm, useModal, useSpining } from '@/hooks'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import storage from '@/utils/storage'
const emit = defineEmits(['ok']) const emit = defineEmits(['ok'])
const { t } = useI18n() // t const { t } = useI18n() // t
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal() const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()

View File

@ -1,7 +1,7 @@
<template> <template>
<x-search-bar class="mb-8-2"> <x-search-bar class="mb-8-2">
<template #default="{ gutter, colSpan }"> <template #default="{ gutter, colSpan }">
<a-form :label-col="{ style: { width: '100px' } }" :model="searchFormData" layout="inline"> <a-form :model="searchFormData" layout="inline">
<a-row :gutter="gutter"> <a-row :gutter="gutter">
<a-col v-bind="colSpan"> <a-col v-bind="colSpan">
<a-form-item :label="$t('pages.system.menu.form.name')" name="name"> <a-form-item :label="$t('pages.system.menu.form.name')" name="name">

View File

@ -88,7 +88,7 @@ import { ref, watch } from 'vue'
import { config } from '@/config' import { config } from '@/config'
import apis from '@/apis' import apis from '@/apis'
import { useForm, useModal } from '@/hooks' import { useForm, useModal } from '@/hooks'
import storage from '@/utils/storage'
const emit = defineEmits(['ok']) const emit = defineEmits(['ok'])
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal() const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
@ -108,7 +108,8 @@ formRules.value = {
const treeData = ref([]) const treeData = ref([])
const checkedKeys = ref([]) const checkedKeys = ref([])
async function getMenus() { async function getMenus() {
const { data } = await apis.menu.getMenuList().catch(() => { const platForm = storage.local.getItem('platform')
const { data } = await apis.menu.getMenuList({platform:platForm}).catch(() => {
throw new Error() throw new Error()
}) })

View File

@ -0,0 +1,144 @@
<template>
<a-modal :open="modal.open" :title="modal.title" :width="600" :confirm-loading="modal.confirmLoading"
:after-close="onAfterClose" :cancel-text="cancelText" @ok="handleOk" @cancel="handleCancel">
<a-card>
<a-form ref="formRef" :model="formData" :rules="formRules">
<a-row :gutter="24">
<!-- 姓名 -->
<a-col :span="24">
<a-form-item label="类型名称" name="name">
<a-input v-model="formData.name"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="上级通话类型" name="callType" >
<a-select v-model:value="formData.callType" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_TYPE" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="电话方向" name="callDirection" >
<a-select v-model:value="formData.callDirection" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_DIRECTION" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="排序号" name="sortNo">
<a-input-number v-model="formData.sortNo" style="width: 100%;"></a-input-number>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="备注" name="remark">
<a-input v-model="formData.remark"></a-input>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
</a-modal>
</template>
<script setup>
import { cloneDeep } from 'lodash-es'
import { ref, defineProps } from 'vue'
import { config } from '@/config'
import apis from '@/apis'
import { useForm, useModal } from '@/hooks'
import { useDicsStore } from '@/store'
import AreaCascader from '@/components/AreaCascader/index.vue'
const dicsStore = useDicsStore()
const emit = defineEmits(['ok'])
const activeKey = ref('1')
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { formRecord, formData, formRef, formRules, resetForm } = useForm()
const cancelText = ref('取消')
formRules.value = {
name: [{ required: true, message: '请输入类型名称', trigger: 'blur' }],
}
/**
* 新建
*/
function handleCreate() {
showModal({
type: 'create',
title: '新建',
})
}
function handleEdit(record) {
formRecord.value = cloneDeep(record)
Object.assign(formData.value, formRecord.value)
showModal({
type: 'edit',
title: '编辑',
})
}
/**
* 确定
*/
function handleOk() {
formRef.value
.validateFields()
.then(async (values) => {
try {
showLoading()
const params = {
...formData.value,
}
let result = null
switch (modal.value.type) {
case 'create':
result = await apis.serverObj.createItem(params).catch(() => {
throw new Error()
})
break
case 'edit':
result = await apis.serverObj.updateItem(params).catch(() => {
throw new Error()
})
break
}
hideLoading()
if (config('http.code.success') === result?.code) {
hideModal()
emit('ok')
}
} catch (error) {
hideLoading()
}
})
.catch(() => {
hideLoading()
})
}
/**
* 取消
*/
function handleCancel() {
hideModal()
}
/**
* 关闭后
*/
function onAfterClose() {
resetForm()
hideLoading()
}
defineExpose({
handleCreate, handleEdit
})
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,320 @@
<template>
<a-row :gutter="16">
<a-col :span="24">
<x-search-bar class="mb-8-2">
<template #default="{ gutter, colSpan }">
<a-form :model="searchFormData" layout="inline">
<a-row :gutter="gutter">
<a-col v-bind="colSpan">
<a-form-item label="用户姓名" name="name">
<a-input :placeholder="'请输入用户姓名'" v-model:value="searchFormData.name"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="身份证号" name="idCard">
<a-input :placeholder="'请输入身份证号'" v-model:value="searchFormData.idCard"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="主叫号码" name="callerNumber">
<a-input :placeholder="'请输入主叫号码'"
v-model:value="searchFormData.callerNumber"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="被叫号码" name="calleeNumber">
<a-input :placeholder="'请输入被叫号码'"
v-model:value="searchFormData.calleeNumber"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="电话方向" name="callDirection">
<a-select v-model:value="searchFormData.callDirection" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_DIRECTION"
:key="item.dval" :value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col class="align-left" v-bind="colSpan">
<a-space>
<a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button ghost type="primary" @click="handleSearch">
{{ $t('button.search') }}
</a-button>
</a-space>
</a-col>
</a-row>
</a-form>
</template>
</x-search-bar>
<a-card>
<template #extra>
<a-space>
<a-button type="primary" @click="$refs.editDialogRef.handleCreate(record)">新建</a-button>
</a-space>
</template>
<a-table :columns="columns" :data-source="listData" bordered="true" :loading="loading"
:pagination="paginationState" :scroll="{ x: 'max-content' }" @change="onTableChange">
<template #bodyCell="{ index, column, record }">
<template v-if="column.key === 'serialNumber'">
<span>{{ index + 1 }}</span>
</template>
<template v-if="column.key === 'categoryType'">
<span>{{ dicsStore.getDictLabel('PROJECT_TYPE', record.categoryType) }}</span>
</template>
<!-- <template v-if="column.key === 'status'">
<a-tag v-if="record.status === 'enabled'" color="green">启用</a-tag>
<a-tag v-else>停用</a-tag>
</template> -->
<template v-if="'action' === column.key">
<x-action-button @click="$refs.editDialogRef.handleEdit(record)">
<span>编辑</span>
</x-action-button>
<x-action-button @click="handleDelete(record)">
<span style="color: #ff4d4f;">删除</span>
</x-action-button>
</template>
</template>
</a-table>
</a-card>
</a-col>
</a-row>
<edit-dialog ref="editDialogRef" @ok="onOk"></edit-dialog>
</template>
<script setup>
import { message, Modal } from 'ant-design-vue'
import { ref } from 'vue'
import apis from '@/apis'
import { config } from '@/config'
import { usePagination } from '@/hooks'
import { useI18n } from 'vue-i18n'
import EditDialog from './components/EditDialog.vue'
import { useDicsStore } from '@/store'
defineOptions({
name: 'serverProjectManage',
})
const dicsStore = useDicsStore()
const activeKey = ref('1')
const columns = [
{
title: '用户姓名',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 100,
},
{
title: '服务对象分类',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 180,
},
{
title: '主叫号码',
dataIndex: 'callerNumber',
key: 'callerNumber',
align: 'center',
},
{
title: '被叫号码',
dataIndex: 'calledNumber',
key: 'calledNumber',
align: 'center',
width: 80,
},
{
title: '身份证号',
dataIndex: 'idCardNumber',
key: 'idCardNumber',
align: 'center',
width: 80,
},
{
title: '所在区域',
dataIndex: 'region',
key: 'region',
align: 'center',
width: 80,
},
{
title: '电话来源',
dataIndex: 'callSource',
key: 'callSource',
align: 'center',
width: 80,
},
{
title: '通话类型',
dataIndex: 'callType',
key: 'callType',
align: 'center',
width: 80,
},
{
title: '通话小结',
dataIndex: 'callSummary',
key: 'callSummary',
align: 'center',
width: 80,
},
{
title: '坐席姓名',
dataIndex: 'agentName',
key: 'agentName',
align: 'center',
width: 80,
},
{
title: '坐席工号',
dataIndex: 'callType',
key: 'callType',
align: 'center',
width: 80,
},
{
title: '分机号码',
dataIndex: 'extensionNumber',
key: 'extensionNumber',
align: 'center',
width: 80,
},
{
title: '电话方向',
dataIndex: 'callDirection',
key: 'callDirection',
align: 'center',
width: 80,
},
{
title: '是否接通',
dataIndex: 'isConnected',
key: 'isConnected',
align: 'center',
width: 80,
},
{
title: '结束时间',
dataIndex: 'endTime',
key: 'endTime',
align: 'center',
width: 80,
},
{
title: '开始时间',
dataIndex: 'startTime',
key: 'startTime',
align: 'center',
width: 80,
},
{
title: '通话时长',
dataIndex: 'isConnected',
key: 'isConnected',
align: 'center',
width: 80,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
align: 'center',
width: 120,
fixed: 'right',
}
];
const { t } = useI18n() // t
const { listData, loading, showLoading, hideLoading, paginationState, resetPagination, searchFormData } = usePagination()
const editDialogRef = ref()
getPageList()
async function getPageList() {
try {
const { pageSize, current } = paginationState
const { success, data, total } = await apis.projectType
.getProjectList({
pageSize,
current: current,
...searchFormData.value,
})
.catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
listData.value = data
paginationState.total = total
}
} catch (error) {
}
}
/**
* 删除
*/
function handleDelete({ id }) {
Modal.confirm({
title: t('pages.system.user.delTip'),
content: t('button.confirm'),
okText: t('button.confirm'),
onOk: () => {
return new Promise((resolve, reject) => {
; (async () => {
try {
const { success } = await apis.projectType.delItem(id).catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
resolve()
message.success(t('component.message.success.delete'))
await getPageList()
}
} catch (error) {
reject()
}
})()
})
},
})
}
/**
* 分页
*/
function onTableChange({ current, pageSize }) {
paginationState.current = current
paginationState.pageSize = pageSize
getPageList()
}
/**
* 搜索
*/
function handleSearch() {
resetPagination()
getPageList()
}
/**
* 重置
*/
function handleResetSearch() {
searchFormData.value = {}
resetPagination()
getPageList()
}
/**
* 编辑完成
*/
async function onOk() {
await getPageList()
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,144 @@
<template>
<a-modal :open="modal.open" :title="modal.title" :width="600" :confirm-loading="modal.confirmLoading"
:after-close="onAfterClose" :cancel-text="cancelText" @ok="handleOk" @cancel="handleCancel">
<a-card>
<a-form ref="formRef" :model="formData" :rules="formRules">
<a-row :gutter="24">
<!-- 姓名 -->
<a-col :span="24">
<a-form-item label="类型名称" name="name">
<a-input v-model="formData.name"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="上级通话类型" name="callType" >
<a-select v-model:value="formData.callType" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_TYPE" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="电话方向" name="callDirection" >
<a-select v-model:value="formData.callDirection" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_DIRECTION" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="排序号" name="sortNo">
<a-input-number v-model="formData.sortNo" style="width: 100%;"></a-input-number>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="备注" name="remark">
<a-input v-model="formData.remark"></a-input>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
</a-modal>
</template>
<script setup>
import { cloneDeep } from 'lodash-es'
import { ref, defineProps } from 'vue'
import { config } from '@/config'
import apis from '@/apis'
import { useForm, useModal } from '@/hooks'
import { useDicsStore } from '@/store'
import AreaCascader from '@/components/AreaCascader/index.vue'
const dicsStore = useDicsStore()
const emit = defineEmits(['ok'])
const activeKey = ref('1')
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { formRecord, formData, formRef, formRules, resetForm } = useForm()
const cancelText = ref('取消')
formRules.value = {
name: [{ required: true, message: '请输入类型名称', trigger: 'blur' }],
}
/**
* 新建
*/
function handleCreate() {
showModal({
type: 'create',
title: '新建',
})
}
function handleEdit(record) {
formRecord.value = cloneDeep(record)
Object.assign(formData.value, formRecord.value)
showModal({
type: 'edit',
title: '编辑',
})
}
/**
* 确定
*/
function handleOk() {
formRef.value
.validateFields()
.then(async (values) => {
try {
showLoading()
const params = {
...formData.value,
}
let result = null
switch (modal.value.type) {
case 'create':
result = await apis.serverObj.createItem(params).catch(() => {
throw new Error()
})
break
case 'edit':
result = await apis.serverObj.updateItem(params).catch(() => {
throw new Error()
})
break
}
hideLoading()
if (config('http.code.success') === result?.code) {
hideModal()
emit('ok')
}
} catch (error) {
hideLoading()
}
})
.catch(() => {
hideLoading()
})
}
/**
* 取消
*/
function handleCancel() {
hideModal()
}
/**
* 关闭后
*/
function onAfterClose() {
resetForm()
hideLoading()
}
defineExpose({
handleCreate, handleEdit
})
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,211 @@
<template>
<a-row :gutter="16">
<a-col :span="24">
<x-search-bar class="mb-8-2">
<template #default="{ gutter, colSpan }">
<a-form :model="searchFormData" layout="inline">
<a-row :gutter="gutter">
<a-col v-bind="colSpan">
<a-form-item label="类型名称" name="name">
<a-input :placeholder="'请输入类型名称'"
v-model:value="searchFormData.name"></a-input>
</a-form-item>
</a-col>
<a-col class="align-right" v-bind="colSpan">
<a-space>
<a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button ghost type="primary" @click="handleSearch">
{{ $t('button.search') }}
</a-button>
</a-space>
</a-col>
</a-row>
</a-form>
</template>
</x-search-bar>
<a-card>
<template #extra>
<a-space>
<a-button type="primary" @click="$refs.editDialogRef.handleCreate(record)">新建</a-button>
</a-space>
</template>
<a-table :columns="columns" :data-source="listData" bordered="true" :loading="loading"
:pagination="paginationState" :scroll="{ x: 'max-content' }" @change="onTableChange">
<template #bodyCell="{ index, column, record }">
<template v-if="column.key === 'serialNumber'">
<span>{{ index + 1 }}</span>
</template>
<template v-if="column.key === 'categoryType'">
<span>{{ dicsStore.getDictLabel('PROJECT_TYPE', record.categoryType) }}</span>
</template>
<!-- <template v-if="column.key === 'status'">
<a-tag v-if="record.status === 'enabled'" color="green">启用</a-tag>
<a-tag v-else>停用</a-tag>
</template> -->
<template v-if="'action' === column.key">
<x-action-button @click="$refs.editDialogRef.handleEdit(record)">
<span>编辑</span>
</x-action-button>
<x-action-button @click="handleDelete(record)">
<span style="color: #ff4d4f;">删除</span>
</x-action-button>
</template>
</template>
</a-table>
</a-card>
</a-col>
</a-row>
<edit-dialog ref="editDialogRef" @ok="onOk"></edit-dialog>
</template>
<script setup>
import { message, Modal } from 'ant-design-vue'
import { ref } from 'vue'
import apis from '@/apis'
import { config } from '@/config'
import { usePagination } from '@/hooks'
import { useI18n } from 'vue-i18n'
import EditDialog from './components/EditDialog.vue'
import { useDicsStore } from '@/store'
defineOptions({
name: 'serverProjectManage',
})
const dicsStore = useDicsStore()
const activeKey = ref('1')
const columns = [
{
title: '类型名称',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 100,
},
{
title: '上级通话类型名称',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 180,
},
{
title: '电话方向',
dataIndex: 'remark',
key: 'remark',
align: 'center',
},
{
title: '排序号',
dataIndex: 'status',
key: 'status',
align: 'center',
width: 80,
},
{
title: '备注',
dataIndex: 'status',
key: 'status',
align: 'center',
width: 80,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
align: 'center',
width: 120,
fixed: 'right',
}
];
const { t } = useI18n() // t
const { listData, loading, showLoading, hideLoading, paginationState, resetPagination, searchFormData } = usePagination()
const editDialogRef = ref()
getPageList()
async function getPageList() {
try {
const { pageSize, current } = paginationState
const { success, data, total } = await apis.projectType
.getProjectList({
pageSize,
current: current,
...searchFormData.value,
})
.catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
listData.value = data
paginationState.total = total
}
} catch (error) {
}
}
/**
* 删除
*/
function handleDelete({ id }) {
Modal.confirm({
title: t('pages.system.user.delTip'),
content: t('button.confirm'),
okText: t('button.confirm'),
onOk: () => {
return new Promise((resolve, reject) => {
; (async () => {
try {
const { success } = await apis.projectType.delItem(id).catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
resolve()
message.success(t('component.message.success.delete'))
await getPageList()
}
} catch (error) {
reject()
}
})()
})
},
})
}
/**
* 分页
*/
function onTableChange({ current, pageSize }) {
paginationState.current = current
paginationState.pageSize = pageSize
getPageList()
}
/**
* 搜索
*/
function handleSearch() {
resetPagination()
getPageList()
}
/**
* 重置
*/
function handleResetSearch() {
searchFormData.value = {}
resetPagination()
getPageList()
}
/**
* 编辑完成
*/
async function onOk() {
await getPageList()
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,144 @@
<template>
<a-modal :open="modal.open" :title="modal.title" :width="600" :confirm-loading="modal.confirmLoading"
:after-close="onAfterClose" :cancel-text="cancelText" @ok="handleOk" @cancel="handleCancel">
<a-card>
<a-form ref="formRef" :model="formData" :rules="formRules">
<a-row :gutter="24">
<!-- 姓名 -->
<a-col :span="24">
<a-form-item label="类型名称" name="name">
<a-input v-model="formData.name"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="上级通话类型" name="callType" >
<a-select v-model:value="formData.callType" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_TYPE" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="电话方向" name="callDirection" >
<a-select v-model:value="formData.callDirection" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_DIRECTION" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="排序号" name="sortNo">
<a-input-number v-model="formData.sortNo" style="width: 100%;"></a-input-number>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="备注" name="remark">
<a-input v-model="formData.remark"></a-input>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
</a-modal>
</template>
<script setup>
import { cloneDeep } from 'lodash-es'
import { ref, defineProps } from 'vue'
import { config } from '@/config'
import apis from '@/apis'
import { useForm, useModal } from '@/hooks'
import { useDicsStore } from '@/store'
import AreaCascader from '@/components/AreaCascader/index.vue'
const dicsStore = useDicsStore()
const emit = defineEmits(['ok'])
const activeKey = ref('1')
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { formRecord, formData, formRef, formRules, resetForm } = useForm()
const cancelText = ref('取消')
formRules.value = {
name: [{ required: true, message: '请输入类型名称', trigger: 'blur' }],
}
/**
* 新建
*/
function handleCreate() {
showModal({
type: 'create',
title: '新建',
})
}
function handleEdit(record) {
formRecord.value = cloneDeep(record)
Object.assign(formData.value, formRecord.value)
showModal({
type: 'edit',
title: '编辑',
})
}
/**
* 确定
*/
function handleOk() {
formRef.value
.validateFields()
.then(async (values) => {
try {
showLoading()
const params = {
...formData.value,
}
let result = null
switch (modal.value.type) {
case 'create':
result = await apis.serverObj.createItem(params).catch(() => {
throw new Error()
})
break
case 'edit':
result = await apis.serverObj.updateItem(params).catch(() => {
throw new Error()
})
break
}
hideLoading()
if (config('http.code.success') === result?.code) {
hideModal()
emit('ok')
}
} catch (error) {
hideLoading()
}
})
.catch(() => {
hideLoading()
})
}
/**
* 取消
*/
function handleCancel() {
hideModal()
}
/**
* 关闭后
*/
function onAfterClose() {
resetForm()
hideLoading()
}
defineExpose({
handleCreate, handleEdit
})
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,320 @@
<template>
<a-row :gutter="16">
<a-col :span="24">
<x-search-bar class="mb-8-2">
<template #default="{ gutter, colSpan }">
<a-form :model="searchFormData" layout="inline">
<a-row :gutter="gutter">
<a-col v-bind="colSpan">
<a-form-item label="用户姓名" name="name">
<a-input :placeholder="'请输入用户姓名'" v-model:value="searchFormData.name"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="身份证号" name="idCard">
<a-input :placeholder="'请输入身份证号'" v-model:value="searchFormData.idCard"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="主叫号码" name="callerNumber">
<a-input :placeholder="'请输入主叫号码'"
v-model:value="searchFormData.callerNumber"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="被叫号码" name="calleeNumber">
<a-input :placeholder="'请输入被叫号码'"
v-model:value="searchFormData.calleeNumber"></a-input>
</a-form-item>
</a-col>
<a-col v-bind="colSpan">
<a-form-item label="电话方向" name="callDirection">
<a-select v-model:value="searchFormData.callDirection" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_DIRECTION"
:key="item.dval" :value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col class="align-left" v-bind="colSpan">
<a-space>
<a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button ghost type="primary" @click="handleSearch">
{{ $t('button.search') }}
</a-button>
</a-space>
</a-col>
</a-row>
</a-form>
</template>
</x-search-bar>
<a-card>
<template #extra>
<a-space>
<a-button type="primary" @click="$refs.editDialogRef.handleCreate(record)">新建</a-button>
</a-space>
</template>
<a-table :columns="columns" :data-source="listData" bordered="true" :loading="loading"
:pagination="paginationState" :scroll="{ x: 'max-content' }" @change="onTableChange">
<template #bodyCell="{ index, column, record }">
<template v-if="column.key === 'serialNumber'">
<span>{{ index + 1 }}</span>
</template>
<template v-if="column.key === 'categoryType'">
<span>{{ dicsStore.getDictLabel('PROJECT_TYPE', record.categoryType) }}</span>
</template>
<!-- <template v-if="column.key === 'status'">
<a-tag v-if="record.status === 'enabled'" color="green">启用</a-tag>
<a-tag v-else>停用</a-tag>
</template> -->
<template v-if="'action' === column.key">
<x-action-button @click="$refs.editDialogRef.handleEdit(record)">
<span>编辑</span>
</x-action-button>
<x-action-button @click="handleDelete(record)">
<span style="color: #ff4d4f;">删除</span>
</x-action-button>
</template>
</template>
</a-table>
</a-card>
</a-col>
</a-row>
<edit-dialog ref="editDialogRef" @ok="onOk"></edit-dialog>
</template>
<script setup>
import { message, Modal } from 'ant-design-vue'
import { ref } from 'vue'
import apis from '@/apis'
import { config } from '@/config'
import { usePagination } from '@/hooks'
import { useI18n } from 'vue-i18n'
import EditDialog from './components/EditDialog.vue'
import { useDicsStore } from '@/store'
defineOptions({
name: 'serverProjectManage',
})
const dicsStore = useDicsStore()
const activeKey = ref('1')
const columns = [
{
title: '用户姓名',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 100,
},
{
title: '服务对象分类',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 180,
},
{
title: '主叫号码',
dataIndex: 'callerNumber',
key: 'callerNumber',
align: 'center',
},
{
title: '被叫号码',
dataIndex: 'calledNumber',
key: 'calledNumber',
align: 'center',
width: 80,
},
{
title: '身份证号',
dataIndex: 'idCardNumber',
key: 'idCardNumber',
align: 'center',
width: 80,
},
{
title: '所在区域',
dataIndex: 'region',
key: 'region',
align: 'center',
width: 80,
},
{
title: '电话来源',
dataIndex: 'callSource',
key: 'callSource',
align: 'center',
width: 80,
},
{
title: '通话类型',
dataIndex: 'callType',
key: 'callType',
align: 'center',
width: 80,
},
{
title: '通话小结',
dataIndex: 'callSummary',
key: 'callSummary',
align: 'center',
width: 80,
},
{
title: '坐席姓名',
dataIndex: 'agentName',
key: 'agentName',
align: 'center',
width: 80,
},
{
title: '坐席工号',
dataIndex: 'callType',
key: 'callType',
align: 'center',
width: 80,
},
{
title: '分机号码',
dataIndex: 'extensionNumber',
key: 'extensionNumber',
align: 'center',
width: 80,
},
{
title: '电话方向',
dataIndex: 'callDirection',
key: 'callDirection',
align: 'center',
width: 80,
},
{
title: '是否接通',
dataIndex: 'isConnected',
key: 'isConnected',
align: 'center',
width: 80,
},
{
title: '结束时间',
dataIndex: 'endTime',
key: 'endTime',
align: 'center',
width: 80,
},
{
title: '开始时间',
dataIndex: 'startTime',
key: 'startTime',
align: 'center',
width: 80,
},
{
title: '通话时长',
dataIndex: 'isConnected',
key: 'isConnected',
align: 'center',
width: 80,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
align: 'center',
width: 120,
fixed: 'right',
}
];
const { t } = useI18n() // t
const { listData, loading, showLoading, hideLoading, paginationState, resetPagination, searchFormData } = usePagination()
const editDialogRef = ref()
getPageList()
async function getPageList() {
try {
const { pageSize, current } = paginationState
const { success, data, total } = await apis.projectType
.getProjectList({
pageSize,
current: current,
...searchFormData.value,
})
.catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
listData.value = data
paginationState.total = total
}
} catch (error) {
}
}
/**
* 删除
*/
function handleDelete({ id }) {
Modal.confirm({
title: t('pages.system.user.delTip'),
content: t('button.confirm'),
okText: t('button.confirm'),
onOk: () => {
return new Promise((resolve, reject) => {
; (async () => {
try {
const { success } = await apis.projectType.delItem(id).catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
resolve()
message.success(t('component.message.success.delete'))
await getPageList()
}
} catch (error) {
reject()
}
})()
})
},
})
}
/**
* 分页
*/
function onTableChange({ current, pageSize }) {
paginationState.current = current
paginationState.pageSize = pageSize
getPageList()
}
/**
* 搜索
*/
function handleSearch() {
resetPagination()
getPageList()
}
/**
* 重置
*/
function handleResetSearch() {
searchFormData.value = {}
resetPagination()
getPageList()
}
/**
* 编辑完成
*/
async function onOk() {
await getPageList()
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,144 @@
<template>
<a-modal :open="modal.open" :title="modal.title" :width="600" :confirm-loading="modal.confirmLoading"
:after-close="onAfterClose" :cancel-text="cancelText" @ok="handleOk" @cancel="handleCancel">
<a-card>
<a-form ref="formRef" :model="formData" :rules="formRules">
<a-row :gutter="24">
<!-- 姓名 -->
<a-col :span="24">
<a-form-item label="类型名称" name="name">
<a-input v-model="formData.name"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="上级通话类型" name="callType" >
<a-select v-model:value="formData.callType" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_TYPE" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="电话方向" name="callDirection" >
<a-select v-model:value="formData.callDirection" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_DIRECTION" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="排序号" name="sortNo">
<a-input-number v-model="formData.sortNo" style="width: 100%;"></a-input-number>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="备注" name="remark">
<a-input v-model="formData.remark"></a-input>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
</a-modal>
</template>
<script setup>
import { cloneDeep } from 'lodash-es'
import { ref, defineProps } from 'vue'
import { config } from '@/config'
import apis from '@/apis'
import { useForm, useModal } from '@/hooks'
import { useDicsStore } from '@/store'
import AreaCascader from '@/components/AreaCascader/index.vue'
const dicsStore = useDicsStore()
const emit = defineEmits(['ok'])
const activeKey = ref('1')
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { formRecord, formData, formRef, formRules, resetForm } = useForm()
const cancelText = ref('取消')
formRules.value = {
name: [{ required: true, message: '请输入类型名称', trigger: 'blur' }],
}
/**
* 新建
*/
function handleCreate() {
showModal({
type: 'create',
title: '新建',
})
}
function handleEdit(record) {
formRecord.value = cloneDeep(record)
Object.assign(formData.value, formRecord.value)
showModal({
type: 'edit',
title: '编辑',
})
}
/**
* 确定
*/
function handleOk() {
formRef.value
.validateFields()
.then(async (values) => {
try {
showLoading()
const params = {
...formData.value,
}
let result = null
switch (modal.value.type) {
case 'create':
result = await apis.serverObj.createItem(params).catch(() => {
throw new Error()
})
break
case 'edit':
result = await apis.serverObj.updateItem(params).catch(() => {
throw new Error()
})
break
}
hideLoading()
if (config('http.code.success') === result?.code) {
hideModal()
emit('ok')
}
} catch (error) {
hideLoading()
}
})
.catch(() => {
hideLoading()
})
}
/**
* 取消
*/
function handleCancel() {
hideModal()
}
/**
* 关闭后
*/
function onAfterClose() {
resetForm()
hideLoading()
}
defineExpose({
handleCreate, handleEdit
})
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,211 @@
<template>
<a-row :gutter="16">
<a-col :span="24">
<x-search-bar class="mb-8-2">
<template #default="{ gutter, colSpan }">
<a-form :model="searchFormData" layout="inline">
<a-row :gutter="gutter">
<a-col v-bind="colSpan">
<a-form-item label="类型名称" name="name">
<a-input :placeholder="'请输入类型名称'"
v-model:value="searchFormData.name"></a-input>
</a-form-item>
</a-col>
<a-col class="align-right" v-bind="colSpan">
<a-space>
<a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button ghost type="primary" @click="handleSearch">
{{ $t('button.search') }}
</a-button>
</a-space>
</a-col>
</a-row>
</a-form>
</template>
</x-search-bar>
<a-card>
<template #extra>
<a-space>
<a-button type="primary" @click="$refs.editDialogRef.handleCreate(record)">新建</a-button>
</a-space>
</template>
<a-table :columns="columns" :data-source="listData" bordered="true" :loading="loading"
:pagination="paginationState" :scroll="{ x: 'max-content' }" @change="onTableChange">
<template #bodyCell="{ index, column, record }">
<template v-if="column.key === 'serialNumber'">
<span>{{ index + 1 }}</span>
</template>
<template v-if="column.key === 'categoryType'">
<span>{{ dicsStore.getDictLabel('PROJECT_TYPE', record.categoryType) }}</span>
</template>
<!-- <template v-if="column.key === 'status'">
<a-tag v-if="record.status === 'enabled'" color="green">启用</a-tag>
<a-tag v-else>停用</a-tag>
</template> -->
<template v-if="'action' === column.key">
<x-action-button @click="$refs.editDialogRef.handleEdit(record)">
<span>编辑</span>
</x-action-button>
<x-action-button @click="handleDelete(record)">
<span style="color: #ff4d4f;">删除</span>
</x-action-button>
</template>
</template>
</a-table>
</a-card>
</a-col>
</a-row>
<edit-dialog ref="editDialogRef" @ok="onOk"></edit-dialog>
</template>
<script setup>
import { message, Modal } from 'ant-design-vue'
import { ref } from 'vue'
import apis from '@/apis'
import { config } from '@/config'
import { usePagination } from '@/hooks'
import { useI18n } from 'vue-i18n'
import EditDialog from './components/EditDialog.vue'
import { useDicsStore } from '@/store'
defineOptions({
name: 'serverProjectManage',
})
const dicsStore = useDicsStore()
const activeKey = ref('1')
const columns = [
{
title: '类型名称',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 100,
},
{
title: '上级通话类型名称',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 180,
},
{
title: '电话方向',
dataIndex: 'remark',
key: 'remark',
align: 'center',
},
{
title: '排序号',
dataIndex: 'status',
key: 'status',
align: 'center',
width: 80,
},
{
title: '备注',
dataIndex: 'status',
key: 'status',
align: 'center',
width: 80,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
align: 'center',
width: 120,
fixed: 'right',
}
];
const { t } = useI18n() // t
const { listData, loading, showLoading, hideLoading, paginationState, resetPagination, searchFormData } = usePagination()
const editDialogRef = ref()
getPageList()
async function getPageList() {
try {
const { pageSize, current } = paginationState
const { success, data, total } = await apis.projectType
.getProjectList({
pageSize,
current: current,
...searchFormData.value,
})
.catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
listData.value = data
paginationState.total = total
}
} catch (error) {
}
}
/**
* 删除
*/
function handleDelete({ id }) {
Modal.confirm({
title: t('pages.system.user.delTip'),
content: t('button.confirm'),
okText: t('button.confirm'),
onOk: () => {
return new Promise((resolve, reject) => {
; (async () => {
try {
const { success } = await apis.projectType.delItem(id).catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
resolve()
message.success(t('component.message.success.delete'))
await getPageList()
}
} catch (error) {
reject()
}
})()
})
},
})
}
/**
* 分页
*/
function onTableChange({ current, pageSize }) {
paginationState.current = current
paginationState.pageSize = pageSize
getPageList()
}
/**
* 搜索
*/
function handleSearch() {
resetPagination()
getPageList()
}
/**
* 重置
*/
function handleResetSearch() {
searchFormData.value = {}
resetPagination()
getPageList()
}
/**
* 编辑完成
*/
async function onOk() {
await getPageList()
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,144 @@
<template>
<a-modal :open="modal.open" :title="modal.title" :width="600" :confirm-loading="modal.confirmLoading"
:after-close="onAfterClose" :cancel-text="cancelText" @ok="handleOk" @cancel="handleCancel">
<a-card>
<a-form ref="formRef" :model="formData" :rules="formRules">
<a-row :gutter="24">
<!-- 姓名 -->
<a-col :span="24">
<a-form-item label="类型名称" name="name">
<a-input v-model="formData.name"></a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="上级通话类型" name="callType" >
<a-select v-model:value="formData.callType" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_TYPE" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="电话方向" name="callDirection" >
<a-select v-model:value="formData.callDirection" allowClear>
<a-select-option v-for="item in dicsStore.dictOptions.CALL_DIRECTION" :key="item.dval"
:value="item.dval">{{
item.introduction }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="排序号" name="sortNo">
<a-input-number v-model="formData.sortNo" style="width: 100%;"></a-input-number>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="备注" name="remark">
<a-input v-model="formData.remark"></a-input>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
</a-modal>
</template>
<script setup>
import { cloneDeep } from 'lodash-es'
import { ref, defineProps } from 'vue'
import { config } from '@/config'
import apis from '@/apis'
import { useForm, useModal } from '@/hooks'
import { useDicsStore } from '@/store'
import AreaCascader from '@/components/AreaCascader/index.vue'
const dicsStore = useDicsStore()
const emit = defineEmits(['ok'])
const activeKey = ref('1')
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { formRecord, formData, formRef, formRules, resetForm } = useForm()
const cancelText = ref('取消')
formRules.value = {
name: [{ required: true, message: '请输入类型名称', trigger: 'blur' }],
}
/**
* 新建
*/
function handleCreate() {
showModal({
type: 'create',
title: '新建',
})
}
function handleEdit(record) {
formRecord.value = cloneDeep(record)
Object.assign(formData.value, formRecord.value)
showModal({
type: 'edit',
title: '编辑',
})
}
/**
* 确定
*/
function handleOk() {
formRef.value
.validateFields()
.then(async (values) => {
try {
showLoading()
const params = {
...formData.value,
}
let result = null
switch (modal.value.type) {
case 'create':
result = await apis.serverObj.createItem(params).catch(() => {
throw new Error()
})
break
case 'edit':
result = await apis.serverObj.updateItem(params).catch(() => {
throw new Error()
})
break
}
hideLoading()
if (config('http.code.success') === result?.code) {
hideModal()
emit('ok')
}
} catch (error) {
hideLoading()
}
})
.catch(() => {
hideLoading()
})
}
/**
* 取消
*/
function handleCancel() {
hideModal()
}
/**
* 关闭后
*/
function onAfterClose() {
resetForm()
hideLoading()
}
defineExpose({
handleCreate, handleEdit
})
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,211 @@
<template>
<a-row :gutter="16">
<a-col :span="24">
<x-search-bar class="mb-8-2">
<template #default="{ gutter, colSpan }">
<a-form :model="searchFormData" layout="inline">
<a-row :gutter="gutter">
<a-col v-bind="colSpan">
<a-form-item label="类型名称" name="name">
<a-input :placeholder="'请输入类型名称'"
v-model:value="searchFormData.name"></a-input>
</a-form-item>
</a-col>
<a-col class="align-right" v-bind="colSpan">
<a-space>
<a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button ghost type="primary" @click="handleSearch">
{{ $t('button.search') }}
</a-button>
</a-space>
</a-col>
</a-row>
</a-form>
</template>
</x-search-bar>
<a-card>
<template #extra>
<a-space>
<a-button type="primary" @click="$refs.editDialogRef.handleCreate(record)">新建</a-button>
</a-space>
</template>
<a-table :columns="columns" :data-source="listData" bordered="true" :loading="loading"
:pagination="paginationState" :scroll="{ x: 'max-content' }" @change="onTableChange">
<template #bodyCell="{ index, column, record }">
<template v-if="column.key === 'serialNumber'">
<span>{{ index + 1 }}</span>
</template>
<template v-if="column.key === 'categoryType'">
<span>{{ dicsStore.getDictLabel('PROJECT_TYPE', record.categoryType) }}</span>
</template>
<!-- <template v-if="column.key === 'status'">
<a-tag v-if="record.status === 'enabled'" color="green">启用</a-tag>
<a-tag v-else>停用</a-tag>
</template> -->
<template v-if="'action' === column.key">
<x-action-button @click="$refs.editDialogRef.handleEdit(record)">
<span>编辑</span>
</x-action-button>
<x-action-button @click="handleDelete(record)">
<span style="color: #ff4d4f;">删除</span>
</x-action-button>
</template>
</template>
</a-table>
</a-card>
</a-col>
</a-row>
<edit-dialog ref="editDialogRef" @ok="onOk"></edit-dialog>
</template>
<script setup>
import { message, Modal } from 'ant-design-vue'
import { ref } from 'vue'
import apis from '@/apis'
import { config } from '@/config'
import { usePagination } from '@/hooks'
import { useI18n } from 'vue-i18n'
import EditDialog from './components/EditDialog.vue'
import { useDicsStore } from '@/store'
defineOptions({
name: 'serverProjectManage',
})
const dicsStore = useDicsStore()
const activeKey = ref('1')
const columns = [
{
title: '类型名称',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 100,
},
{
title: '上级通话类型名称',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 180,
},
{
title: '电话方向',
dataIndex: 'remark',
key: 'remark',
align: 'center',
},
{
title: '排序号',
dataIndex: 'status',
key: 'status',
align: 'center',
width: 80,
},
{
title: '备注',
dataIndex: 'status',
key: 'status',
align: 'center',
width: 80,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
align: 'center',
width: 120,
fixed: 'right',
}
];
const { t } = useI18n() // t
const { listData, loading, showLoading, hideLoading, paginationState, resetPagination, searchFormData } = usePagination()
const editDialogRef = ref()
getPageList()
async function getPageList() {
try {
const { pageSize, current } = paginationState
const { success, data, total } = await apis.projectType
.getProjectList({
pageSize,
current: current,
...searchFormData.value,
})
.catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
listData.value = data
paginationState.total = total
}
} catch (error) {
}
}
/**
* 删除
*/
function handleDelete({ id }) {
Modal.confirm({
title: t('pages.system.user.delTip'),
content: t('button.confirm'),
okText: t('button.confirm'),
onOk: () => {
return new Promise((resolve, reject) => {
; (async () => {
try {
const { success } = await apis.projectType.delItem(id).catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
resolve()
message.success(t('component.message.success.delete'))
await getPageList()
}
} catch (error) {
reject()
}
})()
})
},
})
}
/**
* 分页
*/
function onTableChange({ current, pageSize }) {
paginationState.current = current
paginationState.pageSize = pageSize
getPageList()
}
/**
* 搜索
*/
function handleSearch() {
resetPagination()
getPageList()
}
/**
* 重置
*/
function handleResetSearch() {
searchFormData.value = {}
resetPagination()
getPageList()
}
/**
* 编辑完成
*/
async function onOk() {
await getPageList()
}
</script>
<style lang="less" scoped></style>

View File

@ -60,11 +60,8 @@
</a-col> </a-col>
<a-col v-bind="colSpan"> <a-col v-bind="colSpan">
<a-form-item label="所在节点" name="node"> <a-form-item label="所在站点" name="stationId">
<a-select v-model:value="searchFormData.node" placeholder="请选择所在节点"> <ServiceStation v-model:value="searchFormData.stationId" />
<a-select-option value="node1">节点1</a-select-option>
<a-select-option value="node2">节点2</a-select-option>
</a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
@ -249,7 +246,7 @@ import { useI18n } from 'vue-i18n'
import storage from '@/utils/storage' import storage from '@/utils/storage'
import AreaCascader from '@/components/AreaCascader/index.vue' import AreaCascader from '@/components/AreaCascader/index.vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import ServiceStation from '@/components/ServiceStation/index.vue'
defineOptions({ defineOptions({
name: 'menu', name: 'menu',
}) })

View File

@ -547,9 +547,9 @@ const columns = [
width: 160, width: 160,
}, },
{ {
title: '所在点', title: '所在点',
dataIndex: 'currentNode', dataIndex: 'stationName',
key: 'currentNode', key: 'stationName',
align: 'center', align: 'center',
width: 120, width: 120,
}, },

View File

@ -175,7 +175,7 @@
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz"
integrity sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q== integrity sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==
"@babel/runtime@^7.10.5", "@babel/runtime@^7.5.5": "@babel/runtime@^7.10.5":
version "7.18.6" version "7.18.6"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.6.tgz"
integrity sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ== integrity sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ==
@ -302,51 +302,6 @@
resolved "https://registry.npmmirror.com/@intlify/shared/-/shared-9.14.4.tgz" resolved "https://registry.npmmirror.com/@intlify/shared/-/shared-9.14.4.tgz"
integrity sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA== integrity sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==
"@jiaminghi/bezier-curve@*":
version "0.0.9"
resolved "https://registry.npmmirror.com/@jiaminghi/bezier-curve/-/bezier-curve-0.0.9.tgz"
integrity sha512-u9xJPOEl6Dri2E9FfmJoGxYQY7vYJkURNX04Vj64tdi535tPrpkuf9Sm0lNr3QTKdHQh0DdNRsaa62FLQNQEEw==
dependencies:
"@babel/runtime" "^7.5.5"
"@jiaminghi/c-render@^0.4.3":
version "0.4.3"
resolved "https://registry.npmmirror.com/@jiaminghi/c-render/-/c-render-0.4.3.tgz"
integrity sha512-FJfzj5hGj7MLqqqI2D7vEzHKbQ1Ynnn7PJKgzsjXaZpJzTqs2Yw5OSeZnm6l7Qj7jyPAP53lFvEQNH4o4j6s+Q==
dependencies:
"@babel/runtime" "^7.5.5"
"@jiaminghi/bezier-curve" "*"
"@jiaminghi/color" "*"
"@jiaminghi/transition" "*"
"@jiaminghi/charts@*":
version "0.2.18"
resolved "https://registry.npmmirror.com/@jiaminghi/charts/-/charts-0.2.18.tgz"
integrity sha512-K+HXaOOeWG9OOY1VG6M4mBreeeIAPhb9X+khG651AbnwEwL6G2UtcAQ8GWCq6GzhczcLwwhIhuaHqRygwHC0sA==
dependencies:
"@babel/runtime" "^7.5.5"
"@jiaminghi/c-render" "^0.4.3"
"@jiaminghi/color@*":
version "1.1.3"
resolved "https://registry.npmmirror.com/@jiaminghi/color/-/color-1.1.3.tgz"
integrity sha512-ZY3hdorgODk4OSTbxyXBPxAxHPIVf9rPlKJyK1C1db46a50J0reFKpAvfZG8zMG3lvM60IR7Qawgcu4ZDO3+Hg==
"@jiaminghi/data-view@^2.10.0":
version "2.10.0"
resolved "https://registry.npmmirror.com/@jiaminghi/data-view/-/data-view-2.10.0.tgz"
integrity sha512-Cud2MTiMcqc5k2KWabR/svuVQmXHANqURo+yj40370/LdI/gyUJ6LG203hWXEnT1nMCeiv/SLVmxv3PXLScCeA==
dependencies:
"@babel/runtime" "^7.5.5"
"@jiaminghi/charts" "*"
"@jiaminghi/transition@*":
version "1.1.11"
resolved "https://registry.npmmirror.com/@jiaminghi/transition/-/transition-1.1.11.tgz"
integrity sha512-owBggipoHMikDHHDW5Gc7RZYlVuvxHADiU4bxfjBVkHDAmmck+fCkm46n2JzC3j33hWvP9nSCAeh37t6stgWeg==
dependencies:
"@babel/runtime" "^7.5.5"
"@jridgewell/sourcemap-codec@^1.4.13": "@jridgewell/sourcemap-codec@^1.4.13":
version "1.4.15" version "1.4.15"
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz"