fix: 修改bug,首页门店没过滤删除的

This commit is contained in:
2026-03-26 08:56:03 +08:00
parent 78410c1aae
commit 5e3ab5b901
14 changed files with 397 additions and 43 deletions

View File

@@ -4,14 +4,14 @@ NODE_ENV=production
VITE_DEV=true VITE_DEV=true
# 请求路径 # 请求路径
VITE_BASE_URL='http://localhost:48080' #VITE_BASE_URL='http://localhost:48080'
#VITE_BASE_URL='https://test.zmingzhikeji.cn' VITE_BASE_URL='https://api.tuanbanlv.com'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务 # 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=client VITE_UPLOAD_TYPE=client
# 上传路径 # 上传路径
VITE_UPLOAD_URL='https://www.zmingzhikeji.cn/admin-api/infra/file/upload' VITE_UPLOAD_URL='https://api.tuanbanlv.com/admin-api/infra/file/upload'
# 接口地址 # 接口地址
VITE_API_URL=/admin-api VITE_API_URL=/admin-api

Submodule ruoyi-vue-pro updated: 1be18e2ff5...39edcaadf4

View File

@@ -95,5 +95,26 @@ export const RenewalOrderApi = {
// 清空订单合同与客户签名(重新生成合同时先清空再扫码签名) // 清空订单合同与客户签名(重新生成合同时先清空再扫码签名)
clearContractAndSignature: async (id: number) => { clearContractAndSignature: async (id: number) => {
return await request.post({ url: `/car/renewal-order/clear-contract-sign`, params: { id } }) return await request.post({ url: `/car/renewal-order/clear-contract-sign`, params: { id } })
},
// 身份证识别(用于图片识别新增)
recognizeIdcard: async (file: File, idCardSide: 'front' | 'back' = 'front') => {
const formData = new FormData()
formData.append('file', file)
formData.append('idCardSide', idCardSide)
return await request.upload({
url: '/car/renewal-order/recognize-idcard',
data: formData
})
},
// 机动车销售发票识别(用于图片识别新增)
recognizeVehicleInvoice: async (file: File) => {
const formData = new FormData()
formData.append('file', file)
return await request.upload({
url: '/car/renewal-order/recognize-vehicle-invoice',
data: formData
})
} }
} }

View File

@@ -14,6 +14,8 @@ export interface PermissionAssignRoleDataScopeReqVO {
roleId: number roleId: number
dataScope: number dataScope: number
dataScopeDeptIds: number[] dataScopeDeptIds: number[]
storeDataScope?: number // 门店数据范围1全部门店 2本门店及以下 3仅本人 4指定门店
storeDataScopeStoreIds?: number[] // 指定门店ID列表storeDataScope=4 时使用)
} }
// 查询角色拥有的菜单权限 // 查询角色拥有的菜单权限

View File

@@ -9,6 +9,8 @@ export interface RoleVO {
type: number type: number
dataScope: number dataScope: number
dataScopeDeptIds: number[] dataScopeDeptIds: number[]
storeDataScope?: number // 门店数据范围1全部门店 2本门店及以下 3仅本人 4指定门店
storeDataScopeStoreIds?: number[] // 指定门店ID列表storeDataScope=4 时使用)
createTime: Date createTime: Date
} }

View File

@@ -178,7 +178,7 @@ service.interceptors.response.use(
} else { } else {
ElNotification.error({ title: msg }) ElNotification.error({ title: msg })
} }
return Promise.reject('error') return Promise.reject(new Error(msg))
} else { } else {
return data return data
} }

View File

@@ -46,6 +46,7 @@ export const SystemDataScopeEnum = {
DEPT_SELF: 5 // 仅本人数据权限 DEPT_SELF: 5 // 仅本人数据权限
} }
/** /**
* 用户的社交平台的类型枚举 * 用户的社交平台的类型枚举
*/ */

View File

@@ -252,5 +252,6 @@ export enum DICT_TYPE {
CAR_RENEWAL_PRODUCT_TYPE= 'car_renewal_product_type', CAR_RENEWAL_PRODUCT_TYPE= 'car_renewal_product_type',
CAR_RENEWAL_PAY_METHOD= 'car_renewal_pay_method', CAR_RENEWAL_PAY_METHOD= 'car_renewal_pay_method',
CAR_RENEWAL_YEAR= 'car_renewal_year', // 续保生效年限 CAR_RENEWAL_YEAR= 'car_renewal_year', // 续保生效年限
CAR_STORE_DATA_SCOPE = 'car_store_data_scope' // 门店数据权限1 全部门店 2 本门店及以下)
} }

View File

@@ -109,7 +109,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="购置税凭证" prop="purchaseTaxInvoiceUrls" required> <el-form-item label="购置税凭证" prop="purchaseTaxInvoiceUrls">
<UploadImgs v-model="formData.purchaseTaxInvoiceUrls" :limit="10" :file-size="10" :file-type="['image/jpeg', 'image/png', 'image/jpg', 'image/gif']" /> <UploadImgs v-model="formData.purchaseTaxInvoiceUrls" :limit="10" :file-size="10" :file-type="['image/jpeg', 'image/png', 'image/jpg', 'image/gif']" />
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -182,6 +182,7 @@
placeholder="请选择门店" placeholder="请选择门店"
filterable filterable
style="width: 100%" style="width: 100%"
@change="handleStoreChange"
> >
<el-option <el-option
v-for="store in storeList" v-for="store in storeList"
@@ -538,14 +539,6 @@ const formRules = reactive({
}, },
trigger: 'change' trigger: 'change'
}], }],
purchaseTaxInvoiceUrls: [{
required: true,
validator: (_: any, val: string[] | undefined, cb: (e?: Error) => void) => {
if (!val || !Array.isArray(val) || val.length === 0) cb(new Error('请上传购置税凭证'))
else cb()
},
trigger: 'change'
}],
idCardFrontUrl: [{ required: true, message: '请上传身份证正面', trigger: 'change' }], idCardFrontUrl: [{ required: true, message: '请上传身份证正面', trigger: 'change' }],
idCardBackUrl: [{ required: true, message: '请上传身份证反面', trigger: 'change' }] idCardBackUrl: [{ required: true, message: '请上传身份证反面', trigger: 'change' }]
}) })
@@ -562,6 +555,14 @@ const getStoreList = async () => {
} }
} }
/** 选择门店时,服务购买方自动填充为门店名称 */
const handleStoreChange = (storeId: number) => {
if (storeId) {
const store = storeList.value.find(s => s.id === storeId)
if (store?.storeName) formData.value.serviceBuyer = store.storeName
}
}
/** 获取续保产品列表 */ /** 获取续保产品列表 */
const getProductList = async () => { const getProductList = async () => {
try { try {
@@ -605,7 +606,7 @@ const open = async (type: string, id?: number) => {
resetForm() resetForm()
// 加载门店列表和产品列表 // 加载门店列表和产品列表
await Promise.all([getStoreList(), getProductList()]) await Promise.all([getStoreList(), getProductList()])
// 新增:默认选择门店第一项,车辆购买方默认为门店名称(编辑不覆盖,均可修改) // 新增:默认选择门店第一项,服务购买方默认为门店名称(编辑不覆盖,均可修改)
if (!id && storeList.value.length > 0) { if (!id && storeList.value.length > 0) {
if (!formData.value.storeId) { if (!formData.value.storeId) {
formData.value.storeId = storeList.value[0].id formData.value.storeId = storeList.value[0].id
@@ -748,7 +749,31 @@ const openCopy = async (id: number) => {
formLoading.value = false formLoading.value = false
} }
} }
defineExpose({ open, openCopy }) // 提供 open / openCopy 方法,用于打开弹窗 /** 打开弹窗并预填识别结果(身份证 / 车辆购置发票) */
const openWithRecognizedData = async (recognizedData: Record<string, any>) => {
await open('create')
if (!recognizedData) return
// 服务购买方 = 门店名字,不由识别结果填充;车辆购买方 = 身份证上的人名
const idCardName = recognizedData.carBuyer || recognizedData.serviceBuyer || recognizedData.name
if (idCardName) formData.value.carBuyer = String(idCardName)
if (recognizedData.certNo) formData.value.certNo = String(recognizedData.certNo)
if (recognizedData.contactAddress) formData.value.contactAddress = String(recognizedData.contactAddress)
// 车辆购置发票识别字段
if (recognizedData.vin) formData.value.vin = String(recognizedData.vin)
if (recognizedData.engineNo) formData.value.engineNo = String(recognizedData.engineNo)
if (recognizedData.factoryModel) formData.value.factoryModel = String(recognizedData.factoryModel)
if (recognizedData.invoiceDate) formData.value.invoiceDate = String(recognizedData.invoiceDate)
if (recognizedData.invoiceAmount != null) formData.value.invoiceAmount = Number(recognizedData.invoiceAmount)
if (recognizedData.carModel) formData.value.carModel = String(recognizedData.carModel)
// 图片 URL身份证正反面、购车发票
if (recognizedData.idCardFrontUrl) formData.value.idCardFrontUrl = String(recognizedData.idCardFrontUrl)
if (recognizedData.idCardBackUrl) formData.value.idCardBackUrl = String(recognizedData.idCardBackUrl)
if (recognizedData.carInvoiceUrls && Array.isArray(recognizedData.carInvoiceUrls) && recognizedData.carInvoiceUrls.length > 0) {
formData.value.carInvoiceUrls = recognizedData.carInvoiceUrls.map((u: any) => String(u))
}
}
defineExpose({ open, openCopy, openWithRecognizedData }) // 提供 open / openCopy / openWithRecognizedData 方法
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调

View File

@@ -83,6 +83,14 @@
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
<el-button
type="primary"
plain
@click="openRecognizeDialog"
v-hasPermi="['car:renewal-order:create']"
>
<Icon icon="ep:picture" class="mr-5px" /> 图片识别新增
</el-button>
<el-button <el-button
type="success" type="success"
plain plain
@@ -231,6 +239,96 @@
<!-- 表单弹窗添加/修改 --> <!-- 表单弹窗添加/修改 -->
<RenewalOrderForm ref="formRef" @success="getList" /> <RenewalOrderForm ref="formRef" @success="getList" />
<!-- 图片识别新增身份证 + 车辆购置发票 -->
<el-dialog
v-model="recognizeDialogVisible"
title="图片识别新增"
width="480px"
:close-on-click-modal="false"
>
<el-tabs v-model="recognizeActiveTab">
<el-tab-pane label="身份证识别" name="idcard">
<div class="flex flex-col gap-16px">
<div>
<div class="text-sm text-gray-600 mb-8px">身份证正面人像面*</div>
<el-upload
ref="recognizeUploadRef"
:auto-upload="false"
:limit="1"
:on-change="handleRecognizeFileChange"
:on-exceed="() => message.warning('仅支持上传一张图片')"
accept="image/jpeg,image/png,image/jpg,image/gif"
drag
>
<el-icon class="el-icon--upload"><Icon icon="ep:upload-filled" /></el-icon>
<div class="el-upload__text">将身份证正面拖到此处,或<em>点击选择</em></div>
</el-upload>
<div v-if="recognizeSelectedFile" class="text-sm text-gray-500 mt-4px">已选择:{{ recognizeSelectedFile.name }}</div>
</div>
<div>
<div class="text-sm text-gray-600 mb-8px">身份证反面(国徽面,可选)</div>
<el-upload
ref="recognizeBackUploadRef"
:auto-upload="false"
:limit="1"
:on-change="handleRecognizeBackFileChange"
:on-exceed="() => message.warning('仅支持上传一张图片')"
accept="image/jpeg,image/png,image/jpg,image/gif"
drag
>
<el-icon class="el-icon--upload"><Icon icon="ep:upload-filled" /></el-icon>
<div class="el-upload__text">将身份证反面拖到此处,或<em>点击选择</em></div>
</el-upload>
<div v-if="recognizeBackSelectedFile" class="text-sm text-gray-500 mt-4px">已选择:{{ recognizeBackSelectedFile.name }}</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="车辆购置发票识别" name="invoice">
<div class="flex flex-col items-center gap-16px">
<el-upload
ref="invoiceUploadRef"
:auto-upload="false"
:limit="1"
:on-change="handleInvoiceFileChange"
:on-exceed="() => message.warning('仅支持上传一张图片')"
accept="image/jpeg,image/png,image/jpg,image/gif"
drag
>
<el-icon class="el-icon--upload"><Icon icon="ep:upload-filled" /></el-icon>
<div class="el-upload__text">将机动车销售发票照片拖到此处,或<em>点击选择</em></div>
<template #tip>
<div class="el-upload__tip">请上传机动车销售发票照片,支持 jpg/png/gif</div>
</template>
</el-upload>
<div v-if="invoiceSelectedFile" class="text-sm text-gray-500">
已选择:{{ invoiceSelectedFile.name }}
</div>
</div>
</el-tab-pane>
</el-tabs>
<template #footer>
<el-button @click="recognizeDialogVisible = false">取消</el-button>
<el-button
v-if="recognizeActiveTab === 'idcard'"
type="primary"
:loading="recognizeLoading"
:disabled="!recognizeSelectedFile"
@click="handleRecognizeSubmit"
>
识别并新增
</el-button>
<el-button
v-else
type="primary"
:loading="invoiceRecognizeLoading"
:disabled="!invoiceSelectedFile"
@click="handleInvoiceRecognizeSubmit"
>
识别并确定
</el-button>
</template>
</el-dialog>
<!-- 线上签名弹窗:二维码 + 复制链接 --> <!-- 线上签名弹窗:二维码 + 复制链接 -->
<el-dialog <el-dialog
v-model="onlineSignDialogVisible" v-model="onlineSignDialogVisible"
@@ -257,6 +355,7 @@ import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
import { erpPriceTableColumnFormatter } from '@/utils' import { erpPriceTableColumnFormatter } from '@/utils'
import download from '@/utils/download' import download from '@/utils/download'
import { RenewalOrderApi, RenewalOrderVO } from '@/api/car/renewalorder' import { RenewalOrderApi, RenewalOrderVO } from '@/api/car/renewalorder'
import * as FileApi from '@/api/infra/file'
import RenewalOrderForm from './RenewalOrderForm.vue' import RenewalOrderForm from './RenewalOrderForm.vue'
import TireStoreTree from '@/views/tire/tireuser/TireStoreTree.vue' import TireStoreTree from '@/views/tire/tireuser/TireStoreTree.vue'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
@@ -326,6 +425,113 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id) formRef.value.open(type, id)
} }
/** 图片识别新增 */
const recognizeDialogVisible = ref(false)
const recognizeActiveTab = ref<'idcard' | 'invoice'>('idcard')
const recognizeUploadRef = ref()
const recognizeSelectedFile = ref<File | null>(null)
const recognizeBackUploadRef = ref()
const recognizeBackSelectedFile = ref<File | null>(null)
const recognizeLoading = ref(false)
const invoiceUploadRef = ref()
const invoiceSelectedFile = ref<File | null>(null)
const invoiceRecognizeLoading = ref(false)
/** 提取识别/上传失败原因,用于提示用户 */
const getRecognizeErrorMessage = (e: any, fallback: string): string => {
return e?.response?.data?.msg ?? e?.message ?? e?.msg ?? (typeof e === 'string' ? e : fallback)
}
/** 上传文件获取 URL */
const uploadFileGetUrl = async (file: File): Promise<string> => {
const res = await FileApi.updateFile({ file }) as any
return res?.data ?? res?.url ?? ''
}
const openRecognizeDialog = () => {
recognizeActiveTab.value = 'idcard'
recognizeSelectedFile.value = null
recognizeBackSelectedFile.value = null
invoiceSelectedFile.value = null
recognizeUploadRef.value?.clearFiles()
recognizeBackUploadRef.value?.clearFiles()
invoiceUploadRef.value?.clearFiles()
recognizeDialogVisible.value = true
}
const handleRecognizeFileChange = (uploadFile: { raw?: File }) => {
recognizeSelectedFile.value = uploadFile?.raw || null
}
const handleRecognizeBackFileChange = (uploadFile: { raw?: File }) => {
recognizeBackSelectedFile.value = uploadFile?.raw || null
}
const handleInvoiceFileChange = (uploadFile: { raw?: File }) => {
invoiceSelectedFile.value = uploadFile?.raw || null
}
const handleRecognizeSubmit = async () => {
if (!recognizeSelectedFile.value) return
recognizeLoading.value = true
try {
const res = await RenewalOrderApi.recognizeIdcard(recognizeSelectedFile.value, 'front') as any
const data = res?.data ?? res
if (!data || typeof data !== 'object') {
const errMsg = res?.msg || '识别失败,未获取到有效数据,请重新上传'
message.error(errMsg)
return
}
// 上传身份证正反面获取 URL
try {
const idCardFrontUrl = await uploadFileGetUrl(recognizeSelectedFile.value)
if (idCardFrontUrl) data.idCardFrontUrl = idCardFrontUrl
if (recognizeBackSelectedFile.value) {
const idCardBackUrl = await uploadFileGetUrl(recognizeBackSelectedFile.value)
if (idCardBackUrl) data.idCardBackUrl = idCardBackUrl
}
} catch (uploadErr: any) {
const errMsg = getRecognizeErrorMessage(uploadErr, '图片上传失败,请重试')
message.error(`图片上传失败:${errMsg}`)
return
}
recognizeDialogVisible.value = false
formRef.value.openWithRecognizedData(data)
message.success('识别成功,请补充其他信息后保存')
} catch (e: any) {
const errMsg = getRecognizeErrorMessage(e, '识别失败,请重新上传正确的身份证照片')
message.error(errMsg.includes('请重新上传') ? errMsg : `${errMsg},请重新上传`)
} finally {
recognizeLoading.value = false
}
}
const handleInvoiceRecognizeSubmit = async () => {
if (!invoiceSelectedFile.value) return
invoiceRecognizeLoading.value = true
try {
const res = await RenewalOrderApi.recognizeVehicleInvoice(invoiceSelectedFile.value) as any
const data = res?.data ?? res
if (!data || typeof data !== 'object') {
const errMsg = res?.msg || '识别失败,未获取到有效数据,请重新上传'
message.error(errMsg)
return
}
// 上传发票图片获取 URL初始化到购车发票
try {
const invoiceUrl = await uploadFileGetUrl(invoiceSelectedFile.value)
if (invoiceUrl) data.carInvoiceUrls = [invoiceUrl]
} catch (uploadErr: any) {
const errMsg = getRecognizeErrorMessage(uploadErr, '图片上传失败,请重试')
message.error(`图片上传失败:${errMsg}`)
return
}
recognizeDialogVisible.value = false
formRef.value.openWithRecognizedData(data)
message.success('识别成功,请补充其他信息后保存')
} catch (e: any) {
const errMsg = getRecognizeErrorMessage(e, '识别失败,请重新上传正确的机动车销售发票照片')
message.error(errMsg.includes('请重新上传') ? errMsg : `${errMsg},请重新上传`)
} finally {
invoiceRecognizeLoading.value = false
}
}
/** 复制创建订单:基于原订单预填表单,进入新增模式 */ /** 复制创建订单:基于原订单预填表单,进入新增模式 */
const handleCopy = (id: number) => { const handleCopy = (id: number) => {
formRef.value.openCopy(id) formRef.value.openCopy(id)

View File

@@ -14,7 +14,13 @@
<el-input v-model="formData.code" placeholder="请输入岗位编码" /> <el-input v-model="formData.code" placeholder="请输入岗位编码" />
</el-form-item> </el-form-item>
<el-form-item label="岗位顺序" prop="sort"> <el-form-item label="岗位顺序" prop="sort">
<el-input v-model="formData.sort" placeholder="请输入岗位顺序" /> <el-input-number
v-model="formData.sort"
:min="0"
class="!w-full"
controls-position="right"
placeholder="请输入岗位顺序"
/>
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-select v-model="formData.status" clearable placeholder="请选择状态"> <el-select v-model="formData.status" clearable placeholder="请选择状态">
@@ -61,6 +67,7 @@ const formData = ref({
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: '岗位标题不能为空', trigger: 'blur' }], name: [{ required: true, message: '岗位标题不能为空', trigger: 'blur' }],
code: [{ required: true, message: '岗位编码不能为空', trigger: 'change' }], code: [{ required: true, message: '岗位编码不能为空', trigger: 'change' }],
sort: [{ required: true, message: '岗位顺序不能为空', trigger: 'change' }],
status: [{ required: true, message: '岗位状态不能为空', trigger: 'change' }], status: [{ required: true, message: '岗位状态不能为空', trigger: 'change' }],
remark: [{ required: false, message: '岗位内容不能为空', trigger: 'blur' }] remark: [{ required: false, message: '岗位内容不能为空', trigger: 'blur' }]
}) })
@@ -116,10 +123,10 @@ const resetForm = () => {
id: undefined, id: undefined,
name: '', name: '',
code: '', code: '',
sort: undefined, sort: 0,
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
remark: '' remark: ''
} as any }
formRef.value?.resetFields() formRef.value?.resetFields()
} }
</script> </script>

View File

@@ -7,7 +7,7 @@
<el-form-item label="角色标识"> <el-form-item label="角色标识">
<el-tag>{{ formData.code }}</el-tag> <el-tag>{{ formData.code }}</el-tag>
</el-form-item> </el-form-item>
<el-form-item label="权限范围"> <el-form-item label="部门权限范围">
<el-select v-model="formData.dataScope"> <el-select v-model="formData.dataScope">
<el-option <el-option
v-for="item in getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE)" v-for="item in getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE)"
@@ -17,6 +17,36 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="门店权限范围">
<el-select v-model="formData.storeDataScope" placeholder="请选择门店数据权限" clearable>
<el-option
v-for="item in getIntDictOptions(DICT_TYPE.CAR_STORE_DATA_SCOPE)"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<div class="text-gray-400 text-xs mt-1">全部门店/指定门店查看全部或所选门店本门店及以下仅关联门店仅本人仅本人创建的数据</div>
</el-form-item>
<el-form-item
v-if="formData.storeDataScope === 4"
label="权限范围"
style="display: flex"
>
<el-card class="card" shadow="never">
<template #header>
指定门店可多选
</template>
<el-tree
ref="storeTreeRef"
:data="storeOptions"
:props="{ label: 'storeName', value: 'id' }"
show-checkbox
node-key="id"
empty-text="加载中..."
/>
</el-card>
</el-form-item>
</el-form> </el-form>
<el-form-item <el-form-item
v-if="formData.dataScope === SystemDataScopeEnum.DEPT_CUSTOM" v-if="formData.dataScope === SystemDataScopeEnum.DEPT_CUSTOM"
@@ -69,6 +99,7 @@ import { SystemDataScopeEnum } from '@/utils/constants'
import * as RoleApi from '@/api/system/role' import * as RoleApi from '@/api/system/role'
import * as DeptApi from '@/api/system/dept' import * as DeptApi from '@/api/system/dept'
import * as PermissionApi from '@/api/system/permission' import * as PermissionApi from '@/api/system/permission'
import { StoreApi } from '@/api/tire/store'
defineOptions({ name: 'SystemRoleDataPermissionForm' }) defineOptions({ name: 'SystemRoleDataPermissionForm' })
@@ -82,9 +113,13 @@ const formData = reactive({
name: '', name: '',
code: '', code: '',
dataScope: undefined, dataScope: undefined,
dataScopeDeptIds: [] dataScopeDeptIds: [],
storeDataScope: undefined as number | undefined,
storeDataScopeStoreIds: [] as number[]
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
const storeTreeRef = ref() // 门店树 Ref
const storeOptions = ref<any[]>([]) // 门店列表(用于指定门店)
const deptOptions = ref<any[]>([]) // 部门树形结构 const deptOptions = ref<any[]>([]) // 部门树形结构
const deptExpand = ref(true) // 展开/折叠 const deptExpand = ref(true) // 展开/折叠
const treeRef = ref() // 菜单树组件 Ref const treeRef = ref() // 菜单树组件 Ref
@@ -95,18 +130,36 @@ const checkStrictly = ref(true) // 是否严格模式,即父子不关联
const open = async (row: RoleApi.RoleVO) => { const open = async (row: RoleApi.RoleVO) => {
dialogVisible.value = true dialogVisible.value = true
resetForm() resetForm()
formLoading.value = true
try {
// 加载 Dept 列表。注意,必须放在前面,不然下面 setChecked 没数据节点 // 加载 Dept 列表。注意,必须放在前面,不然下面 setChecked 没数据节点
deptOptions.value = handleTree(await DeptApi.getSimpleDeptList()) deptOptions.value = handleTree(await DeptApi.getSimpleDeptList())
// 设置数据 // 加载门店列表(指定门店时使用)
formData.id = row.id const storeList = await StoreApi.findByProductType()
formData.name = row.name storeOptions.value = Array.isArray(storeList) ? storeList : (storeList?.data ?? storeList?.list ?? [])
formData.code = row.code // 从接口拉取最新角色数据,确保 storeDataScope 等字段正确回显
formData.dataScope = row.dataScope const role = await RoleApi.getRole(row.id)
formData.id = role.id
formData.name = role.name
formData.code = role.code
formData.dataScope = role.dataScope
const ADMIN_ROLES = ['super_admin', 'tenant_admin', 'crm_admin']
formData.storeDataScope = role.storeDataScope ?? (ADMIN_ROLES.includes(role.code ?? '') ? 1 : undefined)
formData.storeDataScopeStoreIds = role.storeDataScopeStoreIds ? Array.from(role.storeDataScopeStoreIds) : []
await nextTick() await nextTick()
// 需要在 DOM 渲染完成后,再设置选中状态 const deptIds = role.dataScopeDeptIds ? Array.from(role.dataScopeDeptIds) : []
row.dataScopeDeptIds?.forEach((deptId: number): void => { deptIds.forEach((deptId: number): void => {
treeRef.value.setChecked(deptId, true, false) treeRef.value?.setChecked(deptId, true, false)
}) })
// 设置指定门店勾选
if (formData.storeDataScope === 4 && formData.storeDataScopeStoreIds?.length) {
storeTreeRef.value?.setCheckedKeys(formData.storeDataScopeStoreIds)
} else {
storeTreeRef.value?.setCheckedKeys([])
}
} finally {
formLoading.value = false
}
} }
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
@@ -121,7 +174,12 @@ const submitForm = async () => {
dataScopeDeptIds: dataScopeDeptIds:
formData.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM formData.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM
? [] ? []
: treeRef.value.getCheckedKeys(false) : treeRef.value.getCheckedKeys(false),
storeDataScope: formData.storeDataScope,
storeDataScopeStoreIds:
formData.storeDataScope === 4
? (storeTreeRef.value?.getCheckedKeys(false) ?? formData.storeDataScopeStoreIds ?? [])
: []
} }
await PermissionApi.assignRoleDataScope(data) await PermissionApi.assignRoleDataScope(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
@@ -140,14 +198,17 @@ const resetForm = () => {
deptExpand.value = true deptExpand.value = true
checkStrictly.value = true checkStrictly.value = true
// 重置表单 // 重置表单
formData.value = { Object.assign(formData, {
id: undefined, id: undefined,
name: '', name: '',
code: '', code: '',
dataScope: undefined, dataScope: undefined,
dataScopeDeptIds: [] dataScopeDeptIds: [],
} storeDataScope: undefined,
storeDataScopeStoreIds: []
})
treeRef.value?.setCheckedNodes([]) treeRef.value?.setCheckedNodes([])
storeTreeRef.value?.setCheckedKeys([])
formRef.value?.resetFields() formRef.value?.resetFields()
} }

View File

@@ -14,7 +14,13 @@
<el-input v-model="formData.code" placeholder="请输入角色标识" /> <el-input v-model="formData.code" placeholder="请输入角色标识" />
</el-form-item> </el-form-item>
<el-form-item label="显示顺序" prop="sort"> <el-form-item label="显示顺序" prop="sort">
<el-input v-model="formData.sort" placeholder="请输入显示顺序" /> <el-input-number
v-model="formData.sort"
:min="0"
class="!w-full"
controls-position="right"
placeholder="请输入显示顺序"
/>
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-select v-model="formData.status" clearable placeholder="请选择状态"> <el-select v-model="formData.status" clearable placeholder="请选择状态">
@@ -54,7 +60,7 @@ const formData = ref({
id: undefined, id: undefined,
name: '', name: '',
code: '', code: '',
sort: undefined, sort: 0,
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
remark: '' remark: ''
}) })
@@ -90,7 +96,7 @@ const resetForm = () => {
id: undefined, id: undefined,
name: '', name: '',
code: '', code: '',
sort: undefined, sort: 0,
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
remark: '' remark: ''
} }

View File

@@ -109,6 +109,26 @@
:formatter="dateFormatter" :formatter="dateFormatter"
width="180px" width="180px"
/> />
<el-table-column label="操作" align="center" width="150" fixed="right">
<template #default="scope">
<el-button
v-hasPermi="['tire:store:update']"
link
type="primary"
@click="openForm('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPermi="['tire:store:delete']"
link
type="danger"
@click="handleDelete(scope.row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
<Pagination <Pagination
@@ -193,12 +213,14 @@ const openForm = (type: string, id?: number) => {
} }
/** 删除按钮操作 */ /** 删除按钮操作 */
const handleDelete = async (id: number) => { const handleDelete = async (row: StoreVO) => {
try { try {
// 删除的二次确认 // 删除的二次确认
await message.delConfirm() await message.delConfirm(
row?.storeName ? `是否确认删除门店「${row.storeName}」?` : '是否确认删除该门店?'
)
// 发起删除 // 发起删除
await StoreApi.deleteStore(id) await StoreApi.deleteStore(row.id)
message.success(t('common.delSuccess')) message.success(t('common.delSuccess'))
// 刷新列表 // 刷新列表
await getList() await getList()