This commit is contained in:
Leo_Ding 2025-10-15 13:55:54 +08:00
commit 65caeb0f1a
7 changed files with 1857 additions and 1360 deletions

View File

@ -1,5 +1,7 @@
// 服务设施模块 // 服务设施模块
import request from '@/utils/request' import request from '@/utils/request'
// 节点管理
// 获取节点 // 获取节点
export const getNodeList = (params) => request.basic.get('/api/v1/service-nodes', params) export const getNodeList = (params) => request.basic.get('/api/v1/service-nodes', params)
@ -9,3 +11,41 @@ export const createNode = (params) => request.basic.post('/api/v1/service-nodes'
// 删除节点 // 删除节点
export const delNode = (id) => request.basic.delete(`/api/v1/service-nodes/${id}`) export const delNode = (id) => request.basic.delete(`/api/v1/service-nodes/${id}`)
// 服务组织管理
// 新建服务组织
export const createServiceOrg = (params) => request.basic.post('/api/v1/organizations', params)
// 获取服务组织列表
export const getServiceOrgList = (params) => request.basic.get('/api/v1/organizations', params)
// 获取服务组织详情
export const getServiceOrgDetail = (id) => request.basic.get(`/api/v1/organizations/${id}`)
// 修改服务组织
export const updateServiceOrg = (id, params) => request.basic.put(`/api/v1/organizations/${id}`, params)
// 停用启用接口
export const enableOrDisable = (id, params) => request.basic.put(`/api/v1/organizations/status/${id}`, params)
// 服务站点
// 新增服务站点
export const createServiceSite = (params) => request.basic.post('/api/v1/stations', params)
// 获取所有机构下拉
export const getOrgSelect = () => request.basic.get('/api/v1/organizations/all')
// 获取服务站点列表
export const getServiceSiteList = (params) => request.basic.get('/api/v1/stations', params)
// 获取服务站点详情
export const getServiceSiteDetail = (id) => request.basic.get(`/api/v1/stations/${id}`)
// 修改服务站点
export const updateServiceSite = (id, params) => request.basic.put(`/api/v1/stations/${id}`, params)
// 删除服务站点
export const delServiceSite = (id) => request.basic.delete(`/api/v1/stations/${id}`)
//停用启用服务站点的参数是id和paramsparams里面有status1为启用0为停用
export const enableOrDisableServiceSite = (id, params) => request.basic.put(`/api/v1/stations/status/${id}`, params)

View File

@ -1,7 +1,7 @@
<template> <template>
<!-- 主弹框标题显示当前组织名称 --> <!-- 主弹框标题显示当前组织名称 -->
<a-modal <a-modal
:title="`${orgRecord.orgName || '组织'} - 设备管理`" :title="`${currentOrg.orgName || '组织'} - 设备管理`"
v-model:visible="visible" v-model:visible="visible"
:maskClosable="false" :maskClosable="false"
:width="800" :width="800"
@ -22,14 +22,12 @@
row-key="deviceId" row-key="deviceId"
:pagination="false" :pagination="false"
> >
<!-- 连接状态列用徽章显示 -->
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'online'"> <template v-if="column.key === 'online'">
<a-badge :status="record.online ? 'success' : 'error'"> <a-badge :status="record.online ? 'success' : 'error'">
{{ record.online ? '在线' : '离线' }} {{ record.online ? '在线' : '离线' }}
</a-badge> </a-badge>
</template> </template>
<!-- 操作列编辑/删除 -->
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<a-button size="small" type="link" @click="handleEdit(record)"> <a-button size="small" type="link" @click="handleEdit(record)">
编辑 编辑
@ -51,7 +49,6 @@
> >
<a-form <a-form
:model="formData" :model="formData"
:rules="formRules"
ref="formRef" ref="formRef"
layout="horizontal" layout="horizontal"
:label-col="{ span: 6 }" :label-col="{ span: 6 }"
@ -114,18 +111,12 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, defineProps, defineEmits, defineExpose } from 'vue'; import { ref, reactive, defineExpose } from 'vue';
import { PlusOutlined } from '@ant-design/icons-vue'; import { PlusOutlined } from '@ant-design/icons-vue';
import { message, Form } from 'ant-design-vue'; import { message } from 'ant-design-vue';
// 1. props // 1. props.orgRecord ref
const props = defineProps({ const currentOrg = ref({ orgName: '默认组织' }); //
orgRecord: {
type: Object,
default: () => ({ orgName: '默认组织' }), //
required: false
}
})
// //
const visible = ref(false); const visible = ref(false);
@ -133,7 +124,22 @@ const showAddModal = ref(false);
const formRef = ref(null); const formRef = ref(null);
const isEditing = ref(false); const isEditing = ref(false);
// 2. // API
const devices = ref([
{ deviceId: 'DEV001', deviceName: '温度传感器', deviceType: 'sensor', deviceManufacturer: 'vendor1', online: true },
{ deviceId: 'DEV002', deviceName: '智能控制器', deviceType: 'controller', deviceManufacturer: 'vendor2', online: false },
{ deviceId: 'DEV003', deviceName: '网络网关', deviceType: 'gateway', deviceManufacturer: 'vendor3', online: true },
]);
//
const formData = reactive({
deviceId: '',
deviceName: '',
deviceType: '',
deviceManufacturer: '',
});
//
const columns = ref([ const columns = ref([
{ title: '设备ID', dataIndex: 'deviceId', key: 'deviceId', align: 'center' }, { title: '设备ID', dataIndex: 'deviceId', key: 'deviceId', align: 'center' },
{ title: '设备名称', dataIndex: 'deviceName', key: 'deviceName', align: 'center' }, { title: '设备名称', dataIndex: 'deviceName', key: 'deviceName', align: 'center' },
@ -167,73 +173,49 @@ const columns = ref([
{ title: '操作', key: 'action', align: 'center', fixed: 'right', width: 120 } { title: '操作', key: 'action', align: 'center', fixed: 'right', width: 120 }
]); ]);
// orgRecord.id // 2. open orgRecord currentOrg
const devices = ref([
{ deviceId: 'DEV001', deviceName: '温度传感器', deviceType: 'sensor', deviceManufacturer: 'vendor1', online: true },
{ deviceId: 'DEV002', deviceName: '智能控制器', deviceType: 'controller', deviceManufacturer: 'vendor2', online: false },
{ deviceId: 'DEV003', deviceName: '网络网关', deviceType: 'gateway', deviceManufacturer: 'vendor3', online: true },
]);
//
const formData = reactive({
deviceId: '',
deviceName: '',
deviceType: '',
deviceManufacturer: '',
});
//
const formRules = reactive({
deviceId: [{ required: true, message: '请输入设备ID' }],
deviceName: [{ required: true, message: '请输入设备名称' }],
deviceType: [{ required: true, message: '请选择设备类型' }],
deviceManufacturer: [{ required: true, message: '请选择设备供应商' }],
});
// 3.
function open(options = {}) { function open(options = {}) {
const { orgRecord } = options; const { orgRecord } = options;
if (orgRecord) { if (orgRecord) {
// currentOrg.value = { ...orgRecord }; //
props.orgRecord = orgRecord; } else {
currentOrg.value = { orgName: '默认组织' };
} }
// API
// loadDevicesByOrgId(currentOrg.value.id);
visible.value = true; visible.value = true;
} }
// //
function handleCancel() { function handleCancel() {
visible.value = false; visible.value = false;
//
showAddModal.value = false; showAddModal.value = false;
resetForm(); resetForm();
} }
// / //
async function handleFormSubmit() { async function handleFormSubmit() {
try { try {
//
await formRef.value.validate(); await formRef.value.validate();
if (isEditing.value) { if (isEditing.value) {
//
const index = devices.value.findIndex(dev => dev.deviceId === formData.deviceId); const index = devices.value.findIndex(dev => dev.deviceId === formData.deviceId);
if (index !== -1) { if (index !== -1) {
devices.value[index] = { ...formData, online: devices.value[index].online }; devices.value[index] = { ...formData, online: devices.value[index].online };
message.success('设备编辑成功'); message.success('设备编辑成功');
} }
} else { } else {
// ID
const isExist = devices.value.some(dev => dev.deviceId === formData.deviceId); const isExist = devices.value.some(dev => dev.deviceId === formData.deviceId);
if (isExist) { if (isExist) {
message.error('设备ID已存在'); message.error('设备ID已存在');
return; return;
} }
// 线
devices.value.push({ ...formData, online: false }); devices.value.push({ ...formData, online: false });
message.success('设备添加成功'); message.success('设备添加成功');
} }
//
showAddModal.value = false; showAddModal.value = false;
resetForm(); resetForm();
} catch (error) { } catch (error) {
@ -253,7 +235,7 @@ function resetForm() {
formData.deviceManufacturer = ''; formData.deviceManufacturer = '';
} }
// //
function handleEdit(record) { function handleEdit(record) {
isEditing.value = true; isEditing.value = true;
formData.deviceId = record.deviceId; formData.deviceId = record.deviceId;
@ -269,14 +251,13 @@ function handleDelete(record) {
message.success('设备已删除'); message.success('设备已删除');
} }
// 4. // 3. open
defineExpose({ defineExpose({
open, open,
}); });
</script> </script>
<style scoped> <style scoped>
/* 可选:调整弹框内边距 */
.ant-modal-body { .ant-modal-body {
padding: 20px; padding: 20px;
} }

View File

@ -0,0 +1,449 @@
<template>
<a-modal
:width="800"
:open="modal.open"
:title="modal.title"
:confirm-loading="modal.confirmLoading"
:after-close="onAfterClose"
:cancel-text="isViewMode ? '关闭' : '取消'"
:ok-text="okText"
@ok="handleOk"
@cancel="handleCancel"
:ok-button-props="{ hidden: isViewMode }"
>
<a-form layout="vertical" ref="formRef" :model="formData" :rules="formRules">
<!-- 基本信息 -->
<a-card title="基本信息" class="mb-4">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="服务组织名称" name="orgName">
<a-input v-model:value="formData.orgName" :disabled="true" placeholder="系统生成或上级指定" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="组织编号" name="orgCode">
<a-input v-model:value="formData.orgCode" :disabled="true" placeholder="系统自动生成" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="组织机构代码" name="orgIdentityCode" :required="!isViewMode">
<a-input
v-model:value="formData.orgIdentityCode"
:disabled="isViewMode"
placeholder="请输入统一社会信用代码等"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="等级" name="level" :required="!isViewMode">
<a-select
v-model:value="formData.level"
:disabled="isViewMode"
placeholder="请选择等级"
>
<a-select-option value="1">一级</a-select-option>
<a-select-option value="2">二级</a-select-option>
<a-select-option value="3">三级</a-select-option>
<a-select-option value="4">四级</a-select-option>
<a-select-option value="5">五级</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="负责人姓名" name="manager" :required="!isViewMode">
<a-input
v-model:value="formData.manager"
:disabled="isViewMode"
placeholder="请输入负责人姓名"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="联系电话" name="phone" :required="!isViewMode">
<a-input
v-model:value="formData.phone"
:disabled="isViewMode"
placeholder="请输入联系电话"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="法人姓名">
<a-input
v-model:value="formData.legalPerson"
:disabled="isViewMode"
placeholder="请输入法人姓名"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="法人联系电话">
<a-input
v-model:value="formData.legalPhone"
:disabled="isViewMode"
placeholder="请输入法人联系电话"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="营业执照名称">
<a-input
v-model:value="formData.businessLicenseName"
:disabled="isViewMode"
placeholder="请输入营业执照上的全称"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="登记机关">
<a-input
v-model:value="formData.registrationAuthority"
:disabled="isViewMode"
placeholder="如:南通市通州区市场监督管理局"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="登记证号">
<a-input
v-model:value="formData.registrationNumber"
:disabled="isViewMode"
placeholder="社会组织登记证号"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="中标组织">
<a-select
v-model:value="formData.isWinningOrg"
:disabled="isViewMode"
placeholder="请选择是否为中标组织"
>
<a-select-option value="yes"></a-select-option>
<a-select-option value="no"></a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 钱包设置 -->
<a-card title="钱包设置" class="mb-4">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="使用钱包" name="useWallet">
<a-switch v-model:checked="formData.useWallet" :disabled="isViewMode" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="可用钱包" name="availableWallets">
<a-select
mode="multiple"
v-model:value="formData.availableWallets"
placeholder="选择可接入的钱包"
:disabled="isViewMode || !formData.useWallet"
>
<a-select-option value="wallet_a">通用助餐钱包</a-select-option>
<a-select-option value="wallet_b">居家服务钱包</a-select-option>
<a-select-option value="wallet_c">康复辅具钱包</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 地址信息 -->
<a-card title="地址信息" class="mb-4">
<a-row :gutter="16">
<a-col :span="24">
<a-form-item label="服务组织地址" name="address" :required="!isViewMode">
<a-cascader
v-model:value="formData.address"
:options="addressOptions"
:disabled="isViewMode"
placeholder="请选择省/市/区/街道"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="详细地址" name="detailAddress" :required="!isViewMode">
<a-input
v-model:value="formData.detailAddress"
:disabled="isViewMode"
placeholder="请输入街道门牌号等详细信息"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="地图定位地址">
<a-input
v-model:value="formData.location"
placeholder="点击右侧按钮选择地图位置"
readonly
addon-after-icon="environment"
:disabled="isViewMode"
>
<template #addonAfter>
<a-button
v-if="!isViewMode"
type="link"
@click="openMapSelector"
>
选择位置
</a-button>
<span v-else>已定位</span>
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 资质上传 -->
<a-card title="资质材料" class="mb-4">
<a-row :gutter="16">
<a-col :span="24">
<a-form-item label="上传资质附件">
<a-upload
v-model:file-list="formData.qualificationFiles"
:before-upload="beforeUploadFile"
accept=".pdf,.doc,.docx"
:max-count="3"
:disabled="isViewMode"
>
<a-button v-if="!isViewMode" type="primary" ghost>
<plus-outlined /> 上传文件
</a-button>
</a-upload>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="上传资质图片">
<a-upload
list-type="picture-card"
v-model:file-list="formData.qualificationImages"
:before-upload="beforeUploadImage"
:max-count="5"
:disabled="isViewMode"
>
<div v-if="!isViewMode && formData.qualificationImages.length < 5">
<plus-outlined />
<div class="ant-upload-text">上传图片</div>
</div>
</a-upload>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="上传营业执照">
<a-upload
list-type="picture-card"
v-model:file-list="formData.businessLicenseImage"
:before-upload="beforeUploadImage"
:max-count="1"
:disabled="isViewMode"
>
<div v-if="!isViewMode && !formData.businessLicenseImage.length">
<plus-outlined />
<div class="ant-upload-text">上传执照</div>
</div>
</a-upload>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 其他信息 -->
<a-card title="其他信息" class="mb-4">
<a-form-item label="备注">
<a-textarea
v-model:value="formData.remark"
rows="4"
:disabled="isViewMode"
placeholder="请输入备注信息"
/>
</a-form-item>
</a-card>
</a-form>
</a-modal>
</template>
<script setup>
import { ref, reactive, defineEmits, defineExpose, computed } from 'vue' // computed
import { PlusOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import { useModal, useForm } from '@/hooks'
const emit = defineEmits(['ok'])
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal()
const { formData, formRef, formRules, resetForm } = useForm()
//
formData.value = {
orgName: '',
orgCode: '',
orgIdentityCode: '',
level: '',
manager: '',
phone: '',
legalPerson: '',
legalPhone: '',
businessLicenseName: '',
registrationAuthority: '',
registrationNumber: '',
isWinningOrg: '',
useWallet: false,
availableWallets: [],
address: [],
detailAddress: '',
location: '',
qualificationFiles: [],
qualificationImages: [],
businessLicenseImage: [],
remark: '',
mode: 'edit' //
}
//
formRules.value = {
orgIdentityCode: [{ required: true, message: '请输入组织机构代码' }],
level: [{ required: true, message: '请选择等级' }],
manager: [{ required: true, message: '请输入负责人姓名' }],
phone: [
{ required: true, message: '请输入联系电话' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }
],
address: [{ required: true, message: '请选择服务组织地址' }],
detailAddress: [{ required: true, message: '请输入详细地址' }]
}
//
const addressOptions = ref([
{
value: 'jiangsu',
label: '江苏省',
children: [
{
value: 'nantong',
label: '南通市',
children: [
{
value: 'tongzhou',
label: '通州区',
children: [
{ value: 'liuqiao', label: '刘桥镇' },
{ value: 'pingchao', label: '平潮镇' }
]
}
]
}
]
}
])
//
const isViewMode = computed(() => formData.value.mode === 'view')
const okText = ref('确定')
/**
* 打开地图选择器
*/
function openMapSelector() {
formData.value.location = '江苏省南通市通州区刘桥镇人民政府附近 (经纬度: 32.1234, 120.5678)'
}
/**
* 文件上传前校验文件
*/
function beforeUploadFile(file) {
const isPdfOrDoc = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
].includes(file.type)
if (!isPdfOrDoc) {
message.error('仅支持上传 PDF 或 Word 文档!')
return Upload.LIST_IGNORE
}
const isLt5M = file.size / 1024 / 1024 < 5
if (!isLt5M) {
message.error('文件大小不能超过 5MB!')
return Upload.LIST_IGNORE
}
return true
}
/**
* 图片上传前校验
*/
function beforeUploadImage(file) {
const isImg = file.type.startsWith('image/')
if (!isImg) {
message.error('只能上传图片文件!')
return false
}
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
message.error('图片大小不能超过 2MB!')
return false
}
return true
}
//
defineExpose({
open({ record, mode = 'edit' }) {
showModal({
type: mode,
title: mode === 'edit' ? '编辑服务组织' : '服务组织详情'
})
Object.assign(formData.value, record || {})
formData.value.mode = mode
}
})
/**
* 提交处理仅编辑模式触发
*/
function handleOk() {
if (isViewMode.value) return //
formRef.value
.validateFields()
.then((values) => {
showLoading()
try {
console.log('提交数据:', values)
setTimeout(() => {
hideLoading()
hideModal()
emit('ok')
message.success('操作成功')
}, 800)
} catch (e) {
hideLoading()
}
})
.catch(() => hideLoading())
}
function handleCancel() {
hideModal()
}
function onAfterClose() {
resetForm()
}
</script>
<style lang="less" scoped>
.mb-4 {
margin-bottom: 16px;
}
.ant-upload-list-picture-card .ant-upload-list-item,
.ant-upload-list-picture .ant-upload-list-item {
width: 104px;
height: 104px;
}
</style>

View File

@ -1,449 +1,526 @@
<template> <template>
<a-modal <a-modal
:width="800" :width="800"
:open="modal.open" :open="visible"
:title="modal.title" :title="mode === 'create' ? '新增组织' : mode === 'edit' ? '编辑组织' : '组织详情'"
:confirm-loading="modal.confirmLoading" :confirm-loading="confirmLoading"
:after-close="onAfterClose" :after-close="onAfterClose"
:cancel-text="isViewMode ? '关闭' : '取消'" :cancel-text="t('button.cancel')"
:ok-text="okText" :ok-text="mode === 'view' ? t('button.close') : mode === 'edit' ? '保存' : '新增'"
@ok="handleOk" @ok="handleOk"
@cancel="handleCancel" @cancel="handleCancel"
:ok-button-props="{ hidden: isViewMode }" :maskClosable="false"
> >
<a-form layout="vertical" ref="formRef" :model="formData" :rules="formRules"> <a-form
<!-- 基本信息 --> ref="formRef"
<a-card title="基本信息" class="mb-4"> :model="formData"
:rules="isView ? {} : formRules"
layout="vertical"
autocomplete="off"
:disabled="isView"
>
<!-- 基础信息 -->
<a-card class="mb-4" title="基础信息">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :span="12"> <a-col :span="12">
<a-form-item label="服务组织名称" name="orgName"> <a-form-item :label="'组织名称'" name="name" :required="true">
<a-input v-model:value="formData.orgName" :disabled="true" placeholder="系统生成或上级指定" /> <a-input v-model:value="formData.name" placeholder="请输入组织全称" allow-clear />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="12"> <a-col :span="12">
<a-form-item label="组织编号" name="orgCode"> <a-form-item :label="'组织机构代码'" name="orgCode" :required="true">
<a-input v-model:value="formData.orgCode" :disabled="true" placeholder="系统自动生成" /> <a-input v-model:value="formData.orgCode" placeholder="统一社会信用代码或组织机构代码" allow-clear />
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12"> <a-col :span="12">
<a-form-item label="组织机构代码" name="orgIdentityCode" :required="!isViewMode"> <a-form-item :label="'组织等级'" name="orgLv" :required="true">
<a-input <a-select v-model:value="formData.orgLv" placeholder="请选择组织等级" allow-clear>
v-model:value="formData.orgIdentityCode" <a-select-option
:disabled="isViewMode" v-for="item in dicsStore.dictOptions.Level"
placeholder="请输入统一社会信用代码等" :key="item.dval"
/> :value="item.dval"
</a-form-item> >
</a-col> {{ item.introduction }}
<a-col :span="12"> </a-select-option>
<a-form-item label="等级" name="level" :required="!isViewMode">
<a-select
v-model:value="formData.level"
:disabled="isViewMode"
placeholder="请选择等级"
>
<a-select-option value="1">一级</a-select-option>
<a-select-option value="2">二级</a-select-option>
<a-select-option value="3">三级</a-select-option>
<a-select-option value="4">四级</a-select-option>
<a-select-option value="5">五级</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="负责人姓名" name="manager" :required="!isViewMode">
<a-input
v-model:value="formData.manager"
:disabled="isViewMode"
placeholder="请输入负责人姓名"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="联系电话" name="phone" :required="!isViewMode">
<a-input
v-model:value="formData.phone"
:disabled="isViewMode"
placeholder="请输入联系电话"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="法人姓名">
<a-input
v-model:value="formData.legalPerson"
:disabled="isViewMode"
placeholder="请输入法人姓名"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="法人联系电话">
<a-input
v-model:value="formData.legalPhone"
:disabled="isViewMode"
placeholder="请输入法人联系电话"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="营业执照名称">
<a-input
v-model:value="formData.businessLicenseName"
:disabled="isViewMode"
placeholder="请输入营业执照上的全称"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="登记机关">
<a-input
v-model:value="formData.registrationAuthority"
:disabled="isViewMode"
placeholder="如:南通市通州区市场监督管理局"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="登记证号">
<a-input
v-model:value="formData.registrationNumber"
:disabled="isViewMode"
placeholder="社会组织登记证号"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="中标组织">
<a-select
v-model:value="formData.isWinningOrg"
:disabled="isViewMode"
placeholder="请选择是否为中标组织"
>
<a-select-option value="yes"></a-select-option>
<a-select-option value="no"></a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
</a-card> </a-card>
<!-- 钱包设置 --> <!-- 联系人信息 -->
<a-card title="钱包设置" class="mb-4"> <a-card class="mb-4" title="联系人信息" style="margin-top: 20px">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :span="12"> <a-col :span="12">
<a-form-item label="使用钱包" name="useWallet"> <a-form-item :label="'负责人姓名'" name="concatName" :required="true">
<a-switch v-model:checked="formData.useWallet" :disabled="isViewMode" /> <a-input v-model:value="formData.concatName" placeholder="请输入负责人姓名" allow-clear />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :span="12"> <a-col :span="12">
<a-form-item label="可用钱包" name="availableWallets"> <a-form-item :label="'联系电话'" name="concatPhone" :required="true">
<a-select <a-input v-model:value="formData.concatPhone" placeholder="请输入手机号或固话" allow-clear />
mode="multiple"
v-model:value="formData.availableWallets"
placeholder="选择可接入的钱包"
:disabled="isViewMode || !formData.useWallet"
>
<a-select-option value="wallet_a">通用助餐钱包</a-select-option>
<a-select-option value="wallet_b">居家服务钱包</a-select-option>
<a-select-option value="wallet_c">康复辅具钱包</a-select-option>
</a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item :label="'法人姓名'" name="legalName">
<a-input v-model:value="formData.legalName" placeholder="非必填" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item :label="'法人联系电话'" name="legalPhone">
<a-input v-model:value="formData.legalPhone" placeholder="非必填" allow-clear />
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 资质信息 -->
<a-card class="mb-4" title="资质信息" style="margin-top: 20px">
<template #extra>
<a-tooltip title="营业执照、登记机关等用于资质审核">
<info-circle-outlined style="color: #8c8c8c" />
</a-tooltip>
</template>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item :label="'营业执照名称'" name="businessLicenseName">
<a-input v-model:value="formData.businessLicenseName" placeholder="非必填" allow-clear />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item :label="'登记机关'" name="registrationAuthorityName">
<a-input v-model:value="formData.registrationAuthorityName" placeholder="非必填" allow-clear />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="24">
<a-form-item :label="'登记证号'" name="registrationAuthorityNo">
<a-input v-model:value="formData.registrationAuthorityNo" placeholder="非必填" allow-clear />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="24">
<a-form-item :label="'中标组织'" name="winOrg">
<a-input v-model:value="formData.winOrg" placeholder="若为中标单位请填写,否则可留空" allow-clear />
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 资质附件 -->
<a-card class="mb-4" title="资质附件" style="margin-top: 20px">
<!-- 营业执照 -->
<a-form-item :label="'营业执照图片'" name="businessLicenseFiles">
<a-upload
v-model:file-list="formData.businessLicenseFiles"
list-type="picture-card"
:before-upload="beforeUploadImage"
:custom-request="dummyRequest"
accept="image/*"
multiple
:disabled="isView"
>
<div v-if="formData.businessLicenseFiles.length < 5">
<plus-outlined />
<div class="ant-upload-text">上传</div>
</div>
</a-upload>
<div class="upload-tip">仅支持 JPG/PNG最多5张每张 5MB</div>
</a-form-item>
<!-- 登记证 -->
<a-form-item :label="'登记证附件'" name="registrationCertificateFiles">
<a-upload
v-model:file-list="formData.registrationCertificateFiles"
list-type="picture-card"
:before-upload="beforeUploadImage"
:custom-request="dummyRequest"
accept="image/*"
multiple
:disabled="isView"
>
<div v-if="formData.registrationCertificateFiles.length < 5">
<plus-outlined />
<div class="ant-upload-text">上传</div>
</div>
</a-upload>
<div class="upload-tip">仅支持 JPG/PNG最多5张每张 5MB</div>
</a-form-item>
<!-- 其他资质 -->
<a-form-item :label="'其他资质附件'" name="otherQualificationFiles">
<a-upload
v-model:file-list="formData.otherQualificationFiles"
:before-upload="beforeUploadFile"
:custom-request="dummyRequest"
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png"
multiple
:disabled="isView"
>
<a-button>点击上传资质文件</a-button>
</a-upload>
<div class="upload-tip">支持 PDF/Word/Excel/图片单个 10MB</div>
</a-form-item>
</a-card> </a-card>
<!-- 地址信息 --> <!-- 地址信息 -->
<a-card title="地址信息" class="mb-4"> <a-card class="mb-4" title="地址信息" style="margin-top: 20px">
<a-row :gutter="16"> <a-form-item :label="'所在地区'" name="areaCodes" :required="true">
<a-col :span="24"> <AreaCascader
<a-form-item label="服务组织地址" name="address" :required="!isViewMode"> v-model:value="formData.areaValue"
<a-cascader @change="onAreaChange"
v-model:value="formData.address" :disabled="isView"
:options="addressOptions"
:disabled="isViewMode"
placeholder="请选择省/市/区/街道"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="详细地址" name="detailAddress" :required="!isViewMode">
<a-input
v-model:value="formData.detailAddress"
:disabled="isViewMode"
placeholder="请输入街道门牌号等详细信息"
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="地图定位地址">
<a-input
v-model:value="formData.location"
placeholder="点击右侧按钮选择地图位置"
readonly
addon-after-icon="environment"
:disabled="isViewMode"
>
<template #addonAfter>
<a-button
v-if="!isViewMode"
type="link"
@click="openMapSelector"
>
选择位置
</a-button>
<span v-else>已定位</span>
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 资质上传 -->
<a-card title="资质材料" class="mb-4">
<a-row :gutter="16">
<a-col :span="24">
<a-form-item label="上传资质附件">
<a-upload
v-model:file-list="formData.qualificationFiles"
:before-upload="beforeUploadFile"
accept=".pdf,.doc,.docx"
:max-count="3"
:disabled="isViewMode"
>
<a-button v-if="!isViewMode" type="primary" ghost>
<plus-outlined /> 上传文件
</a-button>
</a-upload>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="上传资质图片">
<a-upload
list-type="picture-card"
v-model:file-list="formData.qualificationImages"
:before-upload="beforeUploadImage"
:max-count="5"
:disabled="isViewMode"
>
<div v-if="!isViewMode && formData.qualificationImages.length < 5">
<plus-outlined />
<div class="ant-upload-text">上传图片</div>
</div>
</a-upload>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="上传营业执照">
<a-upload
list-type="picture-card"
v-model:file-list="formData.businessLicenseImage"
:before-upload="beforeUploadImage"
:max-count="1"
:disabled="isViewMode"
>
<div v-if="!isViewMode && !formData.businessLicenseImage.length">
<plus-outlined />
<div class="ant-upload-text">上传执照</div>
</div>
</a-upload>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 其他信息 -->
<a-card title="其他信息" class="mb-4">
<a-form-item label="备注">
<a-textarea
v-model:value="formData.remark"
rows="4"
:disabled="isViewMode"
placeholder="请输入备注信息"
/> />
</a-form-item> </a-form-item>
<a-form-item :label="'详细地址'" name="address" :required="true">
<a-textarea
v-model:value="formData.address"
placeholder="请输入街道、门牌号等详细地址"
:rows="2"
show-count
:maxlength="256"
/>
</a-form-item>
</a-card>
<!-- 其他 -->
<a-card class="mb-4" title="其他" style="margin-top: 20px">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item :label="'状态'" name="status" :required="true">
<a-select v-model:value="formData.status" style="width: 120px">
<a-select-option value="1">启用</a-select-option>
<a-select-option value="2">禁用</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="24">
<a-form-item :label="'备注'" name="remark">
<a-textarea
v-model:value="formData.remark"
placeholder="可填写特殊说明、内部备注等"
:rows="3"
show-count
:maxlength="500"
/>
</a-form-item>
</a-col>
</a-row>
</a-card> </a-card>
</a-form> </a-form>
</a-modal> </a-modal>
</template> </template>
<script setup> <script setup>
import { ref, reactive, defineEmits, defineExpose, computed } from 'vue' // computed import { ref, reactive, nextTick, computed } from 'vue';
import { PlusOutlined } from '@ant-design/icons-vue' import { message } from 'ant-design-vue';
import { message } from 'ant-design-vue' import { InfoCircleOutlined, PlusOutlined } from '@ant-design/icons-vue';
import { useModal, useForm } from '@/hooks' import AreaCascader from '@/components/AreaCascader/index.vue';
import { useDicsStore } from '@/store';
import apis from '@/apis';
import { useI18n } from 'vue-i18n';
const emit = defineEmits(['ok']) const { t } = useI18n();
const { modal, showModal, hideModal, showLoading, hideLoading } = useModal() const visible = ref(false);
const { formData, formRef, formRules, resetForm } = useForm() const formRef = ref();
const confirmLoading = ref(false);
const dicsStore = useDicsStore();
const mode = ref('create');
const isView = computed(() => mode.value === 'view');
const isEditing = computed(() => mode.value === 'edit');
const emit = defineEmits(['success']);
const editingId = ref(null);
// const formData = reactive({
formData.value = { name: '',
orgName: '',
orgCode: '', orgCode: '',
orgIdentityCode: '', orgLv: '',
level: '', concatName: '',
manager: '', concatPhone: '',
phone: '', legalName: '',
legalPerson: '',
legalPhone: '', legalPhone: '',
businessLicenseName: '', businessLicenseName: '',
registrationAuthority: '', registrationAuthorityName: '',
registrationNumber: '', registrationAuthorityNo: '',
isWinningOrg: '', winOrg: '',
useWallet: false, areaValue: [],
availableWallets: [], address: '',
address: [],
detailAddress: '',
location: '',
qualificationFiles: [],
qualificationImages: [],
businessLicenseImage: [],
remark: '', remark: '',
mode: 'edit' // status: '1',
} businessLicenseFiles: [],
registrationCertificateFiles: [],
otherQualificationFiles: [],
});
// const formRules = {
formRules.value = { name: [{ required: true, message: '请输入组织名称' }],
orgIdentityCode: [{ required: true, message: '请输入组织机构代码' }], orgCode: [{ required: true, message: '请输入组织机构代码' }],
level: [{ required: true, message: '请选择等级' }], orgLv: [{ required: true, message: '请选择组织等级' }],
manager: [{ required: true, message: '请输入负责人姓名' }], concatName: [{ required: true, message: '请输入负责人姓名' }],
phone: [ concatPhone: [{ required: true, message: '请输入联系电话' }],
{ required: true, message: '请输入联系电话' }, status: [{ required: true, message: '请选择状态' }],
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' } areaCodes: [
{
required: true,
validator: () => {
if (!Array.isArray(formData.areaValue) || formData.areaValue.length === 0) {
return Promise.reject(new Error('请选择服务组织地址'));
}
return Promise.resolve();
},
},
], ],
address: [{ required: true, message: '请选择服务组织地址' }], address: [{ required: true, message: '请输入详细地址' }],
detailAddress: [{ required: true, message: '请输入详细地址' }] };
}
// const open = async ({ record = null, mode: _mode = 'create' }) => {
const addressOptions = ref([ visible.value = true;
{ mode.value = _mode;
value: 'jiangsu', editingId.value = null;
label: '江苏省',
children: [ await nextTick();
{ if (record && _mode !== 'create') {
value: 'nantong', editingId.value = record.id;
label: '南通市', await getDetail(record.id);
children: [ } else {
{ resetForm();
value: 'tongzhou',
label: '通州区',
children: [
{ value: 'liuqiao', label: '刘桥镇' },
{ value: 'pingchao', label: '平潮镇' }
]
}
]
}
]
} }
])
// if (formRef.value) formRef.value.clearValidate();
const isViewMode = computed(() => formData.value.mode === 'view') };
const okText = ref('确定')
/** const getDetail = async (id) => {
* 打开地图选择器 const res = await apis.serviceMenu.getServiceOrgDetail(id);
*/ if (res?.success) {
function openMapSelector() { const data = res.data;
formData.value.location = '江苏省南通市通州区刘桥镇人民政府附近 (经纬度: 32.1234, 120.5678)'
}
/** const areaLabels = data.areaLabels
* 文件上传前校验文件 ? (Array.isArray(data.areaLabels) ? data.areaLabels : String(data.areaLabels).split('/').filter(Boolean))
*/ : [];
function beforeUploadFile(file) {
const isPdfOrDoc = [ const bizFiles = (data.businessLicenseUrls || []).map((url, i) => ({
uid: `biz-${i}`,
name: `营业执照-${i + 1}.jpg`,
status: 'done',
url,
}));
const regFiles = (data.registrationCertificateUrls || []).map((url, i) => ({
uid: `reg-${i}`,
name: `登记证-${i + 1}.jpg`,
status: 'done',
url,
}));
const qualFiles = (data.qualificationFileUrls || []).map((item, i) => ({
uid: `qual-${i}`,
name: item.name || `附件-${i + 1}`,
status: 'done',
url: item.url,
}));
Object.assign(formData, {
name: data.name || '',
orgCode: data.orgCode || '',
orgLv: data.orgLv || '',
concatName: data.concatName || '',
concatPhone: data.concatPhone || '',
legalName: data.legalName || '',
legalPhone: data.legalPhone || '',
businessLicenseName: data.businessLicenseName || '',
registrationAuthorityName: data.registrationAuthorityName || '',
registrationAuthorityNo: data.registrationAuthorityNo || '',
winOrg: data.winOrg || '',
areaValue: areaLabels,
address: data.address || '',
remark: data.remark || '',
status: String(data.status) || '1',
businessLicenseFiles: bizFiles,
registrationCertificateFiles: regFiles,
otherQualificationFiles: qualFiles,
});
} else {
message.error(res?.message || '获取详情失败');
}
};
const handleOk = () => {
if (mode.value === 'view') {
handleCancel();
return;
}
handleSubmit();
};
const handleSubmit = async () => {
try {
const values = await formRef.value.validateFields();
confirmLoading.value = true;
//
const bizUrls = [];
for (const f of formData.businessLicenseFiles) {
if (f.originFileObj) bizUrls.push(await uploadFile(f.originFileObj));
else if (f.url) bizUrls.push(f.url);
}
const regUrls = [];
for (const f of formData.registrationCertificateFiles) {
if (f.originFileObj) regUrls.push(await uploadFile(f.originFileObj));
else if (f.url) regUrls.push(f.url);
}
const qualFiles = [];
for (const f of formData.otherQualificationFiles) {
if (f.originFileObj) {
const url = await uploadFile(f.originFileObj);
qualFiles.push({ name: f.name, url });
} else if (f.url) {
qualFiles.push({ name: f.name, url: f.url });
}
}
const submitData = {
...values,
areaCodes: formData.areaValue,
businessLicenseUrls: bizUrls,
registrationCertificateUrls: regUrls,
qualificationFileUrls: qualFiles,
};
let res;
if (isEditing.value) {
res = await apis.serviceMenu.updateServiceOrg(editingId.value, submitData);
} else {
res = await apis.serviceMenu.createServiceOrg(submitData);
}
confirmLoading.value = false;
if (res?.success) {
message.success(isEditing.value ? '更新成功' : '创建成功');
handleCancel();
emit('success');
} else {
message.error(res?.message || '操作失败');
}
} catch (err) {
console.error(err);
confirmLoading.value = false;
message.error('提交失败,请重试');
}
};
const handleCancel = () => {
visible.value = false;
};
const onAfterClose = () => {
resetForm();
confirmLoading.value = false;
if (formRef.value) formRef.value.clearValidate();
};
// === ===
const dummyRequest = ({ onSuccess }) => {
setTimeout(() => onSuccess('ok'), 0);
};
const beforeUploadImage = (file) => {
const isImage = file.type.startsWith('image/');
if (!isImage) {
message.error('只能上传图片!');
return false;
}
const isLt5M = file.size / 1024 / 1024 < 5;
if (!isLt5M) {
message.error('图片不能超过5MB');
return false;
}
return true;
};
const beforeUploadFile = (file) => {
const allowedTypes = [
'application/pdf', 'application/pdf',
'application/msword', 'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
].includes(file.type) 'application/vnd.ms-excel',
if (!isPdfOrDoc) { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
message.error('仅支持上传 PDF 或 Word 文档!') 'image/jpeg',
return Upload.LIST_IGNORE 'image/png',
'image/jpg',
];
if (!allowedTypes.includes(file.type)) {
message.error('仅支持 PDF、Word、Excel、JPG、PNG 格式!');
return false;
} }
const isLt5M = file.size / 1024 / 1024 < 5 if (file.size / 1024 / 1024 >= 10) {
if (!isLt5M) { message.error('文件不能超过10MB');
message.error('文件大小不能超过 5MB!') return false;
return Upload.LIST_IGNORE
} }
return true return true;
} };
/** const uploadFile = async (file) => {
* 图片上传前校验 const formData = new FormData();
*/ formData.append('file', file);
function beforeUploadImage(file) { const res = await apis.file.upload(formData);
const isImg = file.type.startsWith('image/') if (res?.success && res.data?.url) {
if (!isImg) { return res.data.url;
message.error('只能上传图片文件!') } else {
return false throw new Error(res?.message || '上传失败');
} }
const isLt2M = file.size / 1024 / 1024 < 2 };
if (!isLt2M) {
message.error('图片大小不能超过 2MB!')
return false
}
return true
}
// const resetForm = () => {
defineExpose({ Object.assign(formData, {
open({ record, mode = 'edit' }) { name: '',
showModal({ orgCode: '',
type: mode, orgLv: '',
title: mode === 'edit' ? '编辑服务组织' : '服务组织详情' concatName: '',
}) concatPhone: '',
Object.assign(formData.value, record || {}) legalName: '',
formData.value.mode = mode legalPhone: '',
} businessLicenseName: '',
}) registrationAuthorityName: '',
registrationAuthorityNo: '',
winOrg: '',
areaValue: [],
address: '',
remark: '',
status: '1',
businessLicenseFiles: [],
registrationCertificateFiles: [],
otherQualificationFiles: [],
});
};
/** const onAreaChange = (selectedCodes, selectedLabels) => {
* 提交处理仅编辑模式触发 formData.areaValue = [...selectedCodes];
*/ formRef.value?.validateFields(['areaCodes']).catch(() => {});
function handleOk() { };
if (isViewMode.value) return //
formRef.value defineExpose({ open, close: handleCancel });
.validateFields()
.then((values) => {
showLoading()
try {
console.log('提交数据:', values)
setTimeout(() => {
hideLoading()
hideModal()
emit('ok')
message.success('操作成功')
}, 800)
} catch (e) {
hideLoading()
}
})
.catch(() => hideLoading())
}
function handleCancel() {
hideModal()
}
function onAfterClose() {
resetForm()
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.upload-tip {
color: #8c8c8c;
font-size: 12px;
margin-top: 4px;
}
.mb-4 { .mb-4 {
margin-bottom: 16px; margin-bottom: 16px;
} }
.ant-upload-list-picture-card .ant-upload-list-item, .ant-upload-text {
.ant-upload-list-picture .ant-upload-list-item { margin-top: 4px;
width: 104px;
height: 104px;
} }
</style> </style>

View File

@ -4,49 +4,40 @@
<a-form :label-col="{ style: { width: '100px' } }" :model="searchFormData" layout="inline"> <a-form :label-col="{ style: { width: '100px' } }" :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="'所在区域'" name="area"> <a-form-item label="所在区域" name="area">
<AreaCascader v-model:value="searchFormData.currentNode" @change="onAreaChange" /> <AreaCascader v-model:value="searchFormData.currentNode" @change="onAreaChange" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col v-bind="colSpan"> <a-col v-bind="colSpan">
<a-form-item :label="'组织名称'" name="name"> <a-form-item label="组织名称" name="name">
<a-input :placeholder="'请输组织名称'" v-model:value="searchFormData.name"></a-input> <a-input v-model:value="searchFormData.name" placeholder="请输入组织名称" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col v-bind="colSpan"> <a-col v-bind="colSpan">
<a-form-item :label="'负责人'" name="contactPerson"> <a-form-item label="负责人" name="contactPerson">
<a-input :placeholder="'请输负责人'" v-model:value="searchFormData.contactPerson"></a-input> <a-input v-model:value="searchFormData.contactPerson" placeholder="请输入负责人" />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col v-bind="colSpan"> <a-col v-bind="colSpan">
<a-form-item :label="'等级'" name="level"> <a-form-item label="等级" name="level">
<a-select v-model:value="searchFormData.level" @change="handleChange"> <a-select v-model:value="searchFormData.level">
<a-select-option v-for="item in dicsStore.dictOptions.level" :key="item.dval" <a-select-option v-for="item in dicsStore.dictOptions.level" :key="item.dval"
:value="item.dval"> :value="item.dval">
{{ item.introduction }} {{ item.introduction }}
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<!-- <a-col v-bind="colSpan">
<a-form-item :label="'可用钱包'" name="wallet">
<a-select v-model:value="searchFormData.wallet" @change="handleChange">
<a-select-option value="jack">已结单</a-select-option>
<a-select-option value="lucy">已作废</a-select-option>
</a-select>
</a-form-item>
</a-col> -->
<a-col v-bind="colSpan"> <a-col v-bind="colSpan">
<a-form-item :label="'是否中标'" name="bidStatus"> <a-form-item label="是否中标" name="bidStatus">
<a-select v-model:value="searchFormData.bidStatus" @change="handleChange"> <a-select v-model:value="searchFormData.bidStatus">
<a-select-option v-for="item in dicsStore.dictOptions.WinTheBidding" :key="item.dval" <a-select-option v-for="item in dicsStore.dictOptions.WinTheBidding" :key="item.dval"
:value="item.dval"> :value="item.dval">
{{ item.introduction }} {{ item.introduction }}
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -57,324 +48,289 @@
<a-col> <a-col>
<a-space> <a-space>
<a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button> <a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button ghost type="primary" @click="handleSearch"> <a-button ghost type="primary" @click="handleSearch">{{ $t('button.search') }}</a-button>
{{ $t('button.search') }}
</a-button>
</a-space> </a-space>
</a-col> </a-col>
</a-row> </a-row>
</a-form> </a-form>
</template> </template>
</x-search-bar> </x-search-bar>
<a-card> <a-card>
<!-- 添加标题和横线 -->
<div class="title-container"> <div class="title-container">
<h3>服务组织列表</h3> <h3>服务组织列表</h3>
<div class="title-line"></div> <div class="title-line"></div>
</div> </div>
<div style="margin: 20px 0;">
<a-button type="primary" @click="handleCreate">
<template #icon>
<PlusCircleOutlined />
</template>
新建
</a-button>
</div>
<a-table rowKey="id" :loading="loading" :columns="columns" :data-source="listData"
:pagination="paginationConfig" :scroll="{ x: 2200 }">
<template #bodyCell="{ column, record, index }">
<!-- 序号 -->
<template v-if="column.key === 'serialNumber'">
{{ index + 1 }}
</template>
<a-table rowKey="id" :loading="loading" :pagination="true" :columns="columns" :data-source="listData">
<template #bodyCell="{ column, record }"> <!-- 所在区域 -->
<template v-if="'menuType' === column.key"> <template v-else-if="column.key === 'areaLabels'">
<!--菜单--> {{ record.areaLabels?.join('/') || '-' }}
<a-tag v-if="menuTypeEnum.is('page', record.type)" color="processing"> </template>
{{ menuTypeEnum.getDesc(record.type) }}
</a-tag> <!-- 状态 -->
<!--按钮--> <template v-else-if="column.key === 'status'">
<a-tag v-if="menuTypeEnum.is('button', record.type)" color="success"> <a-tag :color="record.status === '1' ? 'processing' : 'default'">
{{ menuTypeEnum.getDesc(record.type) }} {{ record.status === '1' ? '启用' : '停用' }}
</a-tag> </a-tag>
</template> </template>
<template v-if="'createAt' === column.key"> <!-- 等级orgLv -->
<!-- 等级 -->
<template v-else-if="column.key === 'orgLv'">
{{ getLevelText(record.orgLv) }}
</template>
<!-- 是否中标winOrg -->
<template v-else-if="column.key === 'winOrg'">
{{
record.winOrg
? (dicsStore.dictOptions.WinTheBidding.find(item => item.dval === record.winOrg)?.introduction ||
record.winOrg)
: '-'
}}
</template>
<!-- 创建时间 -->
<template v-else-if="column.key === 'createdAt'">
{{ formatUtcDateTime(record.created_at) }} {{ formatUtcDateTime(record.created_at) }}
</template> </template>
<template v-if="'statusType' === column.key"> <!-- 操作 -->
<!--状态--> <template v-else-if="column.key === 'actions'">
<a-tag v-if="statusTypeEnum.is('enabled', record.status)" color="processing">
{{ statusTypeEnum.getDesc(record.status) }}
</a-tag>
<!--状态-->
<a-tag v-if="statusTypeEnum.is('disabled', record.status)" color="processing">
{{ statusTypeEnum.getDesc(record.status) }}
</a-tag>
</template>
<template v-if="'actions' === column.key">
<span class="action-buttons"> <span class="action-buttons">
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button> <a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-button type="link" size="small" @click="handleDetail(record)">详情</a-button> <a-button type="link" size="small" @click="handleDetail(record)">详情</a-button>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-button type="link" size="small" @click="handleToggleStatus(record)"> <a-button type="link" size="small" @click="handleToggleStatus(record)">
{{ record.status === 'active' ? '停用' : '启用' }} {{ record.status === 'enabled' ? '停用' : '启用' }}
</a-button> </a-button>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a-button type="link" size="small" @click="handleDeviceManagement(record)">设备管理</a-button> <!-- <a-button type="link" size="small" @click="handleDeviceManagement(record)">设备管理</a-button> -->
</span> </span>
</template> </template>
<!-- 其他字段默认显示 -->
<template v-else>
{{ record[column.dataIndex] || '-' }}
</template>
</template> </template>
</a-table> </a-table>
</a-card> </a-card>
<edit-dialog @ok="onOk" ref="editDialogRef" /> <EditDialog ref="editDialogRef" @success="handleRefresh" />
<!-- 设备管理弹框组件 --> <DeviceManagementModal ref="deviceManagementModalRef" />
<device-management-modal ref="deviceManagementModalRef" />
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue'
import { Modal, message, Divider } from 'ant-design-vue' import { Modal, message, Divider } from 'ant-design-vue'
import { ref } from 'vue' import { PlusCircleOutlined } from '@ant-design/icons-vue'
import { UnorderedListOutlined, EditOutlined, PlusCircleOutlined, DeleteOutlined } from '@ant-design/icons-vue'
import apis from '@/apis' import apis from '@/apis'
import { config } from '@/config' import { config } from '@/config'
import { menuTypeEnum, statusTypeEnum } from '@/enums/system' import { statusTypeEnum } from '@/enums/system'
import { usePagination, useForm } from '@/hooks' import { usePagination } from '@/hooks'
import { formatUtcDateTime } from '@/utils/util' import { formatUtcDateTime } from '@/utils/util'
import EditDialog from './components/EditDialog.vue' import EditDialog from './components/EditDialog.vue'
//
import DeviceManagementModal from './components/AddEquipments.vue' import DeviceManagementModal from './components/AddEquipments.vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import storage from '@/utils/storage'
import AreaCascader from '@/components/AreaCascader/index.vue' import AreaCascader from '@/components/AreaCascader/index.vue'
import { useDicsStore } from '@/store' import { useDicsStore } from '@/store'
import dayjs from 'dayjs'
defineOptions({ const { t } = useI18n()
name: 'menu',
})
const { t } = useI18n() // t
// /
const showImportModal = ref(false)
// ref -
const deviceManagementModalRef = ref(null)
// ref
const editDialogRef = ref(null)
const columns = ref([
{ title: '序号', dataIndex: 'serialNumber', key: 'serialNumber', fixed: true, width: 180 },
{ title: '机构编号', dataIndex: 'orgCode', key: 'orgCode', width: 240 },
{ title: '节点编号', dataIndex: 'nodeCode', key: 'nodeCode', width: 240 },
{ title: '机构名称', dataIndex: 'orgName', key: 'orgName', width: 240 },
{ title: '联系人', dataIndex: 'contactPerson', key: 'contactPerson', width: 240 },
{ title: '联系电话', dataIndex: 'contactPhone', key: 'contactPhone', width: 240 },
{ title: '机构类型', dataIndex: 'orgType', key: 'orgType', width: 240 },
{ title: '机构性质', dataIndex: 'orgNature', key: 'orgNature', width: 240 },
{ title: '状态', dataIndex: 'status', key: 'status', width: 240 },
{ title: '等级', dataIndex: 'level', key: 'level', width: 240 },
{ title: '营业执照注册号', dataIndex: 'businessLicenseNo', key: 'businessLicenseNo', width: 240 },
{ title: '所在区域', dataIndex: 'region', key: 'region', width: 240 },
{ title: '详细地址', dataIndex: 'addressDetail', key: 'addressDetail', width: 240 },
{ title: '操作', dataIndex: 'actions', key: 'actions', width: 320 },
])
const { listData, loading, showLoading, hideLoading, searchFormData, paginationState, resetPagination } =
usePagination()
const { resetForm } = useForm()
const dicsStore = useDicsStore() const dicsStore = useDicsStore()
getMenuList() //
const {
listData,
loading,
showLoading,
hideLoading,
searchFormData,
paginationState,
resetPagination
} = usePagination()
/** // refs
* 获取菜单列表 const editDialogRef = ref(null)
* @return {Promise<void>} const deviceManagementModalRef = ref(null)
*/
async function getMenuList() { //
const columns = ref([
{ title: '序号', key: 'serialNumber', width: 80 },
{ title: '机构编号', dataIndex: 'orgCode', key: 'orgCode', width: 150 },
{ title: '机构名称', dataIndex: 'name', key: 'name', width: 180 }, // name
{ title: '联系人', dataIndex: 'concatName', key: 'concatName', width: 120 }, // concatName
{ title: '联系电话', dataIndex: 'concatPhone', key: 'concatPhone', width: 150 }, // concatPhone
{ title: '状态', dataIndex: 'status', key: 'status', width: 100 },
{ title: '等级', dataIndex: 'orgLv', key: 'orgLv', width: 100 }, // orgLv
{ title: '所在区域', key: 'areaLabels', width: 200 },
{ title: '详细地址', dataIndex: 'address', key: 'address', width: 200 },
{ title: '是否中标', dataIndex: 'winOrg', key: 'winOrg', width: 100 },
{ title: '创建时间', dataIndex: 'created_at', key: 'createdAt', width: 180 }, // created_at
{ title: '操作', key: 'actions', width: 320, fixed: 'right' },
])
//
const paginationConfig = computed(() => ({
current: paginationState.current,
pageSize: paginationState.pageSize,
total: paginationState.total,
showSizeChanger: true,
showQuickJumper: true,
onChange(page, pageSize) {
paginationState.current = page
paginationState.pageSize = pageSize
getList()
},
onShowSizeChange(current, size) {
paginationState.current = 1
paginationState.pageSize = size
getList()
}
}))
//
async function getList() {
try { try {
showLoading() showLoading()
const platform = storage.local.getItem('platform') const { pageSize, current } = paginationState
const { data, success, total } = await apis.menu const params = {
.getMenuList({ ...searchFormData.value,
...searchFormData.value, pageSize,
platform current
}) }
.catch(() => { const { success, data, total } = await apis.serviceMenu.getServiceOrgList(params)
throw new Error()
})
hideLoading() hideLoading()
if (config('http.code.success') === success) { if (config('http.code.success') === success) {
data.forEach((item) => { listData.value = data || []
item.name = t(item.code) || item.name paginationState.total = total || 0
})
listData.value = data
paginationState.total = total
} }
} catch (error) { } catch (error) {
hideLoading() hideLoading()
listData.value = []
paginationState.total = 0
} }
} }
/** //
* 搜索
*/
function handleSearch() { function handleSearch() {
resetPagination() resetPagination()
getMenuList() getList()
} }
/** //
* 重置
*/
function handleResetSearch() { function handleResetSearch() {
searchFormData.value = {} searchFormData.value = {}
resetPagination() resetPagination()
getMenuList() getList()
} }
/** //
* 删除 function onAreaChange(codes, labels) {
* @param id // AreaCascader [codes, labels]
*/ searchFormData.value.areaCodes = codes
function handleDelete({ id }) { searchFormData.value.areaLabels = labels
Modal.confirm({
title: t('pages.system.menu.delTip'),
content: t('button.confirm'),
okText: t('button.confirm'),
onOk: () => {
return new Promise((resolve, reject) => {
; (async () => {
try {
const { success } = await apis.menu.delMenu(id).catch(() => {
throw new Error()
})
if (config('http.code.success') === success) {
resolve()
message.success(t('component.message.success.delete'))
await getMenuList()
}
} catch (error) {
reject()
}
})()
})
},
})
} }
/** //
* 编辑完成
*/
async function onOk() {
await getMenuList()
}
/**
* 处理下载模板
*/
function handleDownloadTemplate() {
// API
apis.downloadTemplate().then(() => {
message.success('模板下载成功')
}).catch(() => {
message.error('模板下载失败')
})
}
/**
* 处理文件上传
* @param file 选中的文件
*/
async function handleFileUpload(file) {
try {
showLoading()
// FormData
const formData = new FormData()
formData.append('file', file)
// API
const { success } = await apis.uploadServiceSite(formData)
hideLoading()
if (config('http.code.success') === success) {
message.success('文件上传成功')
showImportModal.value = false
//
await getMenuList()
} else {
message.error('文件上传失败')
}
} catch (error) {
hideLoading()
message.error('文件上传失败,请重试')
}
}
//
function handleChange() {
//
}
//
function handleEdit(record) { function handleEdit(record) {
console.log("==record===", record)
editDialogRef.value.open({ record, mode: 'edit' }) editDialogRef.value.open({ record, mode: 'edit' })
} }
//
function handleDetail(record) { function handleDetail(record) {
//
editDialogRef.value.open({ record, mode: 'view' }) editDialogRef.value.open({ record, mode: 'view' })
} }
// /
function handleToggleStatus(record) { function handleToggleStatus(record) {
// const isEnable = record.status !== '2'; //
const isActivate = record.status !== 'active'; const actionText = isEnable ? '启用' : '停用';
const actionText = isActivate ? '启用' : '停用'; const confirmText = isEnable ? '确定要启用该组织吗?' : '确定要停用该组织吗?';
const confirmText = isActivate ? '确定要启用该组织吗?' : '确定要停用该组织吗?';
Modal.confirm({ Modal.confirm({
title: `${actionText}组织`, title: `${actionText}组织`,
content: confirmText, content: confirmText,
okText: '确定', okText: '确定',
cancelText: '取消', cancelText: '取消',
okType: isActivate ? 'primary' : 'danger', okType: isEnable ? 'primary' : 'danger',
onOk: async () => { onOk: async () => {
try { try {
// API // id + { status: '1' '2' }
console.log(`${actionText}操作执行`, record); const { success } = await apis.serviceMenu.enableOrDisable(
record.id,
// API { status: isEnable ? '1' : '2' }
// const { success } = await apis.org.toggleStatus({ id: record.id, status: isActivate ? 'active' : 'inactive' }); );
//
const success = true;
if (success) { if (success) {
message.success(`${actionText}成功`); message.success(`${actionText}成功`);
// //
record.status = isActivate ? 'active' : 'inactive'; getList()
//
await getMenuList();
} else { } else {
message.error(`${actionText}失败`); message.error(`${actionText}失败`);
} }
} catch (error) { } catch (error) {
message.error(`${actionText}过程中发生错误: ${error.message}`); console.error('操作异常:', error);
message.error('操作失败');
} }
}, }
onCancel() {
console.log('取消操作');
},
}); });
} }
// //
function handleDeviceManagement(record) { function handleDeviceManagement(record) {
//
if (deviceManagementModalRef.value) { if (deviceManagementModalRef.value) {
// open deviceManagementModalRef.value.open({ orgRecord: record })
deviceManagementModalRef.value.open({ orgRecord: record });
} else { } else {
console.error('设备管理弹框组件未正确初始化'); message.error('设备管理组件未加载')
message.error('设备管理功能加载失败,请刷新页面重试');
} }
} }
//
function handleCreate() {
editDialogRef.value.open({ record: null, mode: 'create' })
}
// emit
function handleRefresh() {
getList()
}
//
const getLevelText = (level) => {
if (!level) return '-'
const num = Number(level)
if (num >= 1 && num <= 5) {
return `${num}`
}
return level //
}
//
getList()
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
// 线
.title-container { .title-container {
margin-bottom: 16px; margin-bottom: 16px;
} }
@ -382,11 +338,10 @@ function handleDeviceManagement(record) {
.title-line { .title-line {
height: 1px; height: 1px;
width: 100%; width: 100%;
background-color: #e8e8e8; // 线 background-color: #e8e8e8;
margin-top: 8px; margin-top: 8px;
} }
//
.action-buttons { .action-buttons {
.ant-btn-link { .ant-btn-link {
padding: 0 4px; padding: 0 4px;

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@
<a-select v-model:value="searchFormData.type" placeholder="请选择站点类型" allow-clear> <a-select v-model:value="searchFormData.type" placeholder="请选择站点类型" allow-clear>
<a-select-option v-for="item in dicsStore.dictOptions.Station_Type" :key="item.dval" <a-select-option v-for="item in dicsStore.dictOptions.Station_Type" :key="item.dval"
:value="item.dval"> :value="item.dval">
{{ item.introduction }} {{ item.introduction }}
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -35,7 +35,7 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col v-bind="colSpan" style="text-align: right;"> <a-col v-bind="colSpan" style="text-align: right;">
<a-space> <a-space>
<a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button> <a-button @click="handleResetSearch">{{ $t('button.reset') }}</a-button>
<a-button ghost type="primary" @click="handleSearch"> <a-button ghost type="primary" @click="handleSearch">
@ -88,8 +88,8 @@
</a-space> </a-space>
</x-action-bar> </x-action-bar>
<a-table rowKey="id" :loading="loading" :pagination="true" :columns="columns" :data-source="listData"> <a-table rowKey="id" :loading="loading" :pagination="true" :columns="columns" :data-source="listData" :scroll="{ x: 'max-content' }">
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }" >
<template v-if="'menuType' === column.key"> <template v-if="'menuType' === column.key">
<!--菜单--> <!--菜单-->
<a-tag v-if="menuTypeEnum.is('page', record.type)" color="processing"> <a-tag v-if="menuTypeEnum.is('page', record.type)" color="processing">
@ -105,71 +105,61 @@
{{ formatUtcDateTime(record.created_at) }} {{ formatUtcDateTime(record.created_at) }}
</template> </template>
<template v-if="'statusType' === column.key"> <template v-else-if="column.key === 'status'">
<!--状态--> <a-tag :color="record.status === '1' ? 'processing' : 'default'" v-if="record.status != '' ">
<a-tag v-if="statusTypeEnum.is('enabled', record.status)" color="processing"> {{ record.status === '1' ? '启用' : '停用' }}
{{ statusTypeEnum.getDesc(record.status) }}
</a-tag>
<!--状态-->
<a-tag v-if="statusTypeEnum.is('disabled', record.status)" color="processing">
{{ statusTypeEnum.getDesc(record.status) }}
</a-tag> </a-tag>
<span v-else>
--
</span>
</template> </template>
<template v-if="'action' === column.key"> <template v-if="'action' === column.key">
<x-action-button @click="$refs.editDialogRef.handleEdit(record)"> <x-action-button @click="$refs.editDialogRef.handleEdit(record)">
<a-tooltip> <a-tooltip>
<template #title>{{ $t('pages.system.menu.edit') }}</template> <template #title>编辑</template>
<edit-outlined /> 编辑
</a-tooltip> </a-tooltip>
</x-action-button> </x-action-button>
<x-action-button @click="$refs.editDialogRef.handleCreateChild(record)"> <x-action-button @click="$refs.editDialogRef.handleView(record)">
<a-tooltip> <a-tooltip>
<template #title>{{ $t('pages.system.menu.button.addChild') }}</template> <template #title>查看详情</template>
<plus-circle-outlined /> 详情
</a-tooltip> </a-tooltip>
</x-action-button> </x-action-button>
<x-action-button @click="handleDelete(record)"> <x-action-button @click="handleDelete(record)">
<a-tooltip> <a-tooltip>
<template #title>{{ $t('pages.system.delete') }}</template> <template #title>{{ $t('pages.system.delete') }}</template>
<delete-outlined style="color: #ff4d4f" /> 删除
</a-tooltip>
</x-action-button>
<x-action-button @click="handleStatusChange(record)">
<a-tooltip>
<template #title>{{ $t('pages.system.delete') }}</template>
{{ record.status === '1' ? '停用' : '启用' }}
</a-tooltip> </a-tooltip>
</x-action-button> </x-action-button>
</template> </template>
</template> </template>
</a-table> </a-table>
</a-card> </a-card>
<edit-dialog @ok="onOk" ref="editDialogRef" /> <edit-dialog @ok="onOk" ref="editDialogRef" />
<!-- 导入弹框组件 --> <!-- 导入弹框组件 -->
<import-modal <import-modal v-model:visible="showImportModal" @download="handleDownloadTemplate" @upload="handleFileUpload" />
v-model:visible="showImportModal"
@download="handleDownloadTemplate"
@upload="handleFileUpload"/>
<!-- 导入记录弹框组件 --> <!-- 导入记录弹框组件 -->
<ImportRecordsModal <ImportRecordsModal v-model:visible="showImportRecordModal" title="导入记录" :fetchData="fetchImportRecords"
v-model:visible="showImportRecordModal" @close="handleImportRecordClose" @view-details="handleViewImportDetails"
title="导入记录" @download-result="handleDownloadImportResult" @retry-import="handleRetryImport" />
:fetchData="fetchImportRecords"
@close="handleImportRecordClose"
@view-details="handleViewImportDetails"
@download-result="handleDownloadImportResult"
@retry-import="handleRetryImport"
/>
<!-- 导出记录弹框组件 --> <!-- 导出记录弹框组件 -->
<ExportRecordsModal <ExportRecordsModal v-model="showExportRecordModal" title="导出记录" :fetchData="fetchExportRecords"
v-model="showExportRecordModal" @close="handleExportRecordClose" @view-details="handleViewExportDetails"
title="导出记录" @download-result="handleDownloadExportResult" @retry-export="handleRetryExport" />
:fetchData="fetchExportRecords"
@close="handleExportRecordClose"
@view-details="handleViewExportDetails"
@download-result="handleDownloadExportResult"
@retry-export="handleRetryExport"
/>
</template> </template>
<script setup> <script setup>
@ -205,20 +195,41 @@ const showImportRecordModal = ref(false)
const showExportRecordModal = ref(false) const showExportRecordModal = ref(false)
const columns = ref([ const columns = ref([
{ title: '工单号', dataIndex: 'name', key: 'name', fixed: true, width: 280 }, {
{ title: '服务对象', dataIndex: 'code', key: 'code', width: 240 }, title: '站点名称',
{ title: '身份证号', dataIndex: 'type', key: 'menuType', width: 240 }, key: 'name',
{ title: '联系方式1', dataIndex: 'status', key: 'statusType', width: 240 }, width: 150,
{ title: '联系方式2', dataIndex: 'sequence', width: 240 }, ellipsis: true,
{ title: '计划服务时间', dataIndex: 'created_at', key: 'createAt', width: 240 }, customRender: ({ record }) => {
{ title: '计划服务时常(分钟)', dataIndex: 'created_at', key: 'createAt', width: 240 }, return record?.name || '--'
{ title: '服务项目', dataIndex: 'created_at', key: 'createAt', width: 240 }, }
{ title: '所属区域', dataIndex: 'created_at', key: 'createAt', width: 240 }, },
{ title: '服务地址', dataIndex: 'created_at', key: 'createAt', width: 240 }, {
{ title: '服务组织', dataIndex: 'created_at', key: 'createAt', width: 240 }, title: '所在机构',
{ title: '服务人员', dataIndex: 'created_at', key: 'createAt', width: 240 }, key: 'orgName',
{ title: '下单员', dataIndex: 'created_at', key: 'createAt', width: 240 }, width: 150,
{ title: '下单时间', dataIndex: 'created_at', key: 'createAt', width: 240 }, ellipsis: true,
customRender: ({ record }) => {
return record.organization?.name || '--'
}
},
{ title: '组织编码', dataIndex: 'orgCode', key: 'orgCode', width: 150 },
{ title: '站点类型', dataIndex: 'stationType', key: 'stationType', width: 120 },
{ title: '地址', dataIndex: 'address', key: 'address', width: 200, ellipsis: true },
{
title: '营业时间', key: 'businessHours', width: 150, customRender: ({ record }) => {
return `${record.openAt || '--'} - ${record.closeAt || '--'}`;
}
},
{ title: '面积(㎡)', dataIndex: 'area', key: 'area', width: 100 },
{ title: '人员数量', dataIndex: 'memberNum', key: 'memberNum', width: 100 },
{
title: '创建时间', key: 'created_at', width: 180, customRender: ({ record }) => {
return formatUtcDateTime(record.created_at);
}
},
{ title: '状态', dataIndex: 'status', key: 'status', width: 100 },
{ title: '操作', key: 'action', width: 260, fixed: 'right' }
]) ])
const { listData, loading, showLoading, hideLoading, searchFormData, paginationState, resetPagination } = const { listData, loading, showLoading, hideLoading, searchFormData, paginationState, resetPagination } =
usePagination() usePagination()
@ -227,29 +238,28 @@ const editDialogRef = ref()
import { useDicsStore } from '@/store' import { useDicsStore } from '@/store'
const dicsStore = useDicsStore() const dicsStore = useDicsStore()
getMenuList() getList()
/** /**
* 获取菜单列表 * 获取菜单列表
* @return {Promise<void>} * @return {Promise<void>}
*/ */
async function getMenuList() { async function getList() {
try { try {
showLoading() showLoading()
const platform = storage.local.getItem('platform') const { pageSize, current } = paginationState
const { data, success, total } = await apis.menu const { success, data, total } = await apis.serviceMenu
.getMenuList({ .getServiceSiteList({
pageSize,
current: current,
...searchFormData.value, ...searchFormData.value,
platform
}) })
.catch(() => { .catch(() => {
throw new Error() throw new Error()
}) })
hideLoading() hideLoading()
if (config('http.code.success') === success) { if (config('http.code.success') === success) {
data.forEach((item) => { //type80about
item.name = t(item.code) || item.name
})
listData.value = data listData.value = data
paginationState.total = total paginationState.total = total
} }
@ -262,7 +272,7 @@ async function getMenuList() {
*/ */
function handleSearch() { function handleSearch() {
resetPagination() resetPagination()
getMenuList() getList()
} }
/** /**
* 重置 * 重置
@ -270,7 +280,7 @@ function handleSearch() {
function handleResetSearch() { function handleResetSearch() {
searchFormData.value = {} searchFormData.value = {}
resetPagination() resetPagination()
getMenuList() getList()
} }
/** /**
* 删除 * 删除
@ -278,20 +288,20 @@ function handleResetSearch() {
*/ */
function handleDelete({ id }) { function handleDelete({ id }) {
Modal.confirm({ Modal.confirm({
title: t('pages.system.menu.delTip'), title: '确认删除',
content: t('button.confirm'), content: '确认删除该服务站点?',
okText: t('button.confirm'), okText: t('button.confirm'),
onOk: () => { onOk: () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
; (async () => { ; (async () => {
try { try {
const { success } = await apis.menu.delMenu(id).catch(() => { const { success } = await apis.serviceMenu.delServiceSite(id).catch(() => {
throw new Error() throw new Error()
}) })
if (config('http.code.success') === success) { if (config('http.code.success') === success) {
resolve() resolve()
message.success(t('component.message.success.delete')) message.success(t('component.message.success.delete'))
await getMenuList() await getList()
} }
} catch (error) { } catch (error) {
reject() reject()
@ -302,11 +312,46 @@ function handleDelete({ id }) {
}) })
} }
//
function handleStatusChange(record) {
console.log(record.status);
const isEnable = record.status == '1'; //
const actionText = isEnable ? '停用' : '启用';
const confirmText = isEnable ? '确定要停用该站点吗?' : '确定要启用该站点吗?';
Modal.confirm({
title: `${actionText}站点`,
content: confirmText,
okText: '确定',
cancelText: '取消',
okType: isEnable ? 'danger' : 'primary',
onOk: async () => {
try {
const { success } = await apis.serviceMenu.enableOrDisableServiceSite(
record.id,
{ status: isEnable ? '2' : '1' }
);
if (success) {
message.success(`${actionText}成功`);
//
getList()
} else {
message.error(`${actionText}失败`);
}
} catch (error) {
console.error('操作异常:', error);
message.error('操作失败');
}
}
});
}
/** /**
* 编辑完成 * 编辑完成
*/ */
async function onOk() { async function onOk() {
await getMenuList() await getList()
} }
/** /**