呼叫中心

This commit is contained in:
Leo_Ding 2025-11-03 10:20:30 +08:00
parent 9f0299b372
commit eec6e9b89b
27 changed files with 3074 additions and 84 deletions

View File

@ -1,6 +1,10 @@
<template>
<div class="map-wrapper">
<!-- 地图容器 -->
<div ref="mapContainer" class="map-container"></div>
<!-- 遮罩层外部遮罩中间透明区域显示地图 -->
<div class="map-mask"></div>
</div>
</template>
@ -10,96 +14,95 @@ import AMapLoader from '@amap/amap-jsapi-loader'
//
const mapContainer = ref(null)
//
let map = null
// - API
const districtName = '观南社区'
const city = '南通市'
// 🎯 线
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',
plugins: ['AMap.DistrictSearch'] //
version: '2.0'
})
console.log('高德地图 SDK 及插件加载成功')
console.log('高德地图 SDK 加载成功')
//
//
map = new window.AMap.Map(mapContainer.value, {
center: targetArea.center,
zoom: targetArea.zoom,
viewMode: '3D',
pitch: 20,
pitch: 35,
rotation: 0,
showLabel: true
})
// 使 DistrictSearch
const districtSearch = new window.AMap.DistrictSearch({
level: 'community', //
subdistrict: 0 //
})
// 🔒
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)
const city = '南通市'
const districtName = '观南社区'
console.log('地图已限制在指定区域内')
districtSearch.search(`${city}${districtName}`, (status, result) => {
if (status === 'complete' && result.districtList.length > 0) {
const data = result.districtList[0]
const bounds = data.boundaries
if (!bounds || bounds.length === 0) {
alert('❌ 未获取到观南社区的边界数据,请确认该社区存在')
return
}
//
const path = bounds[0].map(coord => new window.AMap.LngLat(coord[0], coord[1]))
//
const polygon = new window.AMap.Polygon({
path: path,
strokeColor: '#4facfe',
strokeWeight: 4,
strokeOpacity: 0.9,
fillColor: 'transparent'
})
map.add(polygon)
//
map.setFitView(polygon)
const currentZoom = map.getZoom()
map.setZoom(currentZoom - 0.5) //
//
// 使
markers.forEach(item => {
const marker = new window.AMap.Marker({
position: data.center,
label: { content: '观南社区', direction: 'top' }
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)
// 🛑
map.on('dragend', () => {
const center = map.getCenter()
const isInside = window.AMap.GeometryUtil.isPointInRing(center, path)
if (!isInside) {
console.log('已超出观南社区范围,自动回弹')
map.panTo(data.center)
}
})
console.log('✅ 成功加载并显示观南社区')
} else {
alert('🔍 未找到“观南社区”,请检查名称是否正确(建议尝试:观南村、或去掉“市”)')
}
})
} catch (e) {
console.error('地图初始化失败:', e)
alert('❌ 地图加载失败,请检查网络或高德 Key 权限')
alert('地图加载失败,请检查高德 Key 或网络连接')
}
}
//
onMounted(() => {
console.log('组件已挂载,准备初始化地图')
initMap()
@ -108,11 +111,13 @@ onMounted(() => {
<style scoped>
.map-wrapper {
background: #0f0f0f;
color: #fff;
font-family: 'Microsoft YaHei', Arial, sans-serif;
position: relative;
width: 100%;
height: 600px;
border: 1px solid #333;
border-radius: 8px;
background: #000; /* 外层黑色背景,无缝衔接 */
overflow: hidden;
}
@ -120,4 +125,23 @@ onMounted(() => {
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

@ -68,7 +68,7 @@ import { Modal } from 'ant-design-vue'
import { storeToRefs } from 'pinia'
import { computed, useSlots, ref } from 'vue'
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 ActionButton from './ActionButton.vue'
import { theme as antTheme } from 'ant-design-vue'
@ -157,9 +157,11 @@ function handleLogout() {
*/
function handleOpen() {
storage.local.removeItem('platform')
storage.local.removeItem('stationId')
const multiTab = useMultiTab()
multiTab.$reset()
router.replace({path:'/platForm'})
router.replace({ path: '/platForm' })
}
/**

View File

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

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 serviceStaffYunYing from './serviceStaffYunYing'
import workorderYunying from './workorderYunying'
import callServerObj from './callServerObj'
import callBaseSet from './callBaseSet'
import trafficMgt from './trafficMgt'
import operator from './operator'
export default [
...home,
// ...form,
@ -40,5 +44,9 @@ export default [
...serverSet,
...yunYingServerObj,
...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

@ -24,6 +24,17 @@ export default [
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 multiTab = useMultiTab()
const router = useRouter()
storage.local.removeItem(config('storage.token'))
storage.local.removeItem(config('storage.userInfo'))
// storage.local.removeItem(config('storage.token'))
// storage.local.removeItem(config('storage.userInfo'))
storage.local.clear()
this.$reset()
appStore.$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,6 +1,6 @@
<template>
<!-- <JsonMap /> -->
<a-row :gutter="16">
<JsonMap />
<!-- <a-row :gutter="16">
<a-col :lg="24">
<a-card>
<a-row
@ -89,7 +89,7 @@
</p>
</a-card>
</a-col>
</a-row>
</a-row> -->
</template>
<script setup>

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

@ -3,13 +3,13 @@
<template #default="{ gutter, colSpan }">
<a-form :model="searchFormData" labelAlign="left">
<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">
<node-tree v-model:value="searchFormData.serviceNodeIds" />
</a-form-item>
</a-col>
<!-- 所在区域 -->
<a-col :span="8" v-if="platForm === 'jianguan'">
<a-col :span="8" v-if="platForm !== 'yunying'">
<a-form-item label="所在区域" name="areaCodes">
<AreaCascader v-model:value="searchFormData.areaCodes" @change="onAreaChange" />
</a-form-item>
@ -244,8 +244,8 @@
</template>
<template #extra>
<a-space>
<a-button type="primary" @click="$refs.editDialogRef.handleCreate()">新建</a-button>
<a-dropdown>
<a-button type="primary" v-if="platForm!=='hujiao'" @click="$refs.editDialogRef.handleCreate()">新建</a-button>
<a-dropdown v-if="platForm!=='hujiao'">
<template #overlay>
<a-menu @click="handleMenuClick">
<a-menu-item key="1">
@ -340,7 +340,7 @@
<span>线下工单</span>
</x-action-button>
<x-action-button @click="$refs.lineOrderRef.handleEdit(record, '1')"
v-if="platForm === 'jianguan'">
v-if="platForm !== 'yunying'">
<span>线上工单</span>
</x-action-button>
<x-action-button @click="$refs.transferRef.handleCreate(record.id)">
@ -639,7 +639,7 @@ async function getPageList() {
const { pageSize, current } = paginationState
const { success, data, total } = await apis.serverObj
.getProjectList({
stationId:storage.local.getItem('stationId'),
stationId:storage.local.getItem('stationId')||'',
companyId:storage.local.getItem('companyId'),
pageSize,
current: current,

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>