Files
one-pipe-system/src/views/product/shop/index.vue
sexygoat d43de4cd06
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 8m39s
修改bug
2026-03-11 17:09:35 +08:00

1143 lines
35 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<ArtTableFullScreen>
<div class="shop-page" id="table-full-screen">
<!-- 搜索栏 -->
<ArtSearchBar
v-model:filter="searchForm"
:items="searchFormItems"
@reset="handleReset"
@search="handleSearch"
></ArtSearchBar>
<ElCard shadow="never" class="art-table-card">
<!-- 表格头部 -->
<ArtTableHeader
:columnList="columnOptions"
v-model:columns="columnChecks"
@refresh="handleRefresh"
>
<template #left>
<ElButton @click="showDialog('add')" v-permission="'shop:add'">新增店铺</ElButton>
</template>
</ArtTableHeader>
<!-- 表格 -->
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="tableData"
:currentPage="pagination.currentPage"
:pageSize="pagination.pageSize"
:total="pagination.total"
:marginTop="10"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:default-expand-all="false"
:pagination="false"
:row-class-name="getRowClassName"
@selection-change="handleSelectionChange"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
@row-contextmenu="handleRowContextMenu"
@cell-mouse-enter="handleCellMouseEnter"
@cell-mouse-leave="handleCellMouseLeave"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<!-- 鼠标悬浮提示 -->
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
<!-- 新增/编辑对话框 -->
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增店铺' : '编辑店铺'"
width="50%"
>
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="100px">
<ElRow :gutter="20">
<ElCol :span="12">
<ElFormItem label="店铺名称" prop="shop_name">
<ElInput v-model="formData.shop_name" placeholder="请输入店铺名称" />
</ElFormItem>
</ElCol>
<ElCol :span="12" v-if="dialogType === 'add'">
<ElFormItem label="店铺编号" prop="shop_code">
<ElInput v-model="formData.shop_code" placeholder="请输入店铺编号" />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="20" v-if="dialogType === 'add'">
<ElCol :span="12">
<ElFormItem label="上级店铺" prop="parent_id">
<ElSelect
v-model="formData.parent_id"
placeholder="一级店铺可不选"
filterable
remote
:remote-method="searchParentShops"
:loading="parentShopLoading"
clearable
style="width: 100%"
>
<ElOption
v-for="shop in parentShopList"
:key="shop.id"
:label="shop.shop_name"
:value="shop.id"
/>
</ElSelect>
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="20">
<ElCol :span="12">
<ElFormItem label="省份" prop="province">
<ElInput v-model="formData.province" placeholder="请输入省份" />
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="城市" prop="city">
<ElInput v-model="formData.city" placeholder="请输入城市" />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="20">
<ElCol :span="12">
<ElFormItem label="区县" prop="district">
<ElInput v-model="formData.district" placeholder="请输入区县" />
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="详细地址" prop="address">
<ElInput v-model="formData.address" placeholder="请输入详细地址" />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="20">
<ElCol :span="12">
<ElFormItem label="联系人" prop="contact_name">
<ElInput v-model="formData.contact_name" placeholder="请输入联系人姓名" />
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="联系电话" prop="contact_phone">
<ElInput
v-model="formData.contact_phone"
placeholder="请输入联系电话"
maxlength="11"
/>
</ElFormItem>
</ElCol>
</ElRow>
<!-- 新增店铺时的初始账号信息 -->
<template v-if="dialogType === 'add'">
<div class="form-section-title">
<span class="title-text">初始账号信息</span>
</div>
<ElRow :gutter="20">
<ElCol :span="12">
<ElFormItem label="用户名" prop="init_username">
<ElInput v-model="formData.init_username" placeholder="请输入初始账号用户名" />
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="密码" prop="init_password">
<ElInput
v-model="formData.init_password"
type="password"
placeholder="请输入初始账号密码"
show-password
/>
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="20">
<ElCol :span="12">
<ElFormItem label="手机号" prop="init_phone">
<ElInput
v-model="formData.init_phone"
placeholder="请输入初始账号手机号"
maxlength="11"
/>
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="默认角色" prop="default_role_id">
<ElSelect
v-model="formData.default_role_id"
placeholder="请选择默认角色"
filterable
remote
:remote-method="searchDefaultRoles"
:loading="defaultRoleLoading"
clearable
style="width: 100%"
>
<ElOption
v-for="role in defaultRoleList"
:key="role.ID"
:label="role.role_name"
:value="role.ID"
>
<div
style="display: flex; justify-content: space-between; align-items: center"
>
<span>{{ role.role_name }}</span>
<ElTag type="success" size="small">客户角色</ElTag>
</div>
</ElOption>
</ElSelect>
</ElFormItem>
</ElCol>
</ElRow>
</template>
<ElFormItem v-if="dialogType === 'edit'" label="状态">
<ElSwitch
v-model="formData.status"
:active-value="CommonStatus.ENABLED"
:inactive-value="CommonStatus.DISABLED"
/>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit" :loading="submitLoading"
>提交</ElButton
>
</div>
</template>
</ElDialog>
<!-- 店铺操作右键菜单 -->
<ArtMenuRight
ref="shopOperationMenuRef"
:menu-items="shopOperationMenuItems"
:menu-width="140"
@select="handleShopOperationMenuSelect"
/>
<!-- 店铺默认角色管理对话框 -->
<ElDialog
v-model="defaultRolesDialogVisible"
:title="`设置默认角色 - ${currentShop?.shop_name || ''}`"
width="50%"
>
<div v-loading="defaultRolesLoading || rolesLoading">
<!-- 当前默认角色显示 -->
<div v-if="selectedRoleId" class="current-role-display">
<div class="current-role-label">当前默认角色</div>
<div class="current-role-value">
{{ availableRoles.find((r) => r.role_id === selectedRoleId)?.role_name || '未知角色' }}
</div>
</div>
<div v-else class="current-role-display no-role">
<div class="current-role-label">当前默认角色</div>
<div class="current-role-value">
暂未设置
</div>
</div>
<!-- 默认角色选择 -->
<div class="default-roles-section">
<div class="section-header">
<span style="color: white">选择默认角色</span>
</div>
<ElSelect
v-model="selectedRoleId"
filterable
placeholder="请选择默认角色"
style="width: 100%; margin-top: 12px"
@change="handleRoleChange"
:loading="rolesLoading"
>
<ElOption
v-for="role in availableRoles"
:key="role.role_id"
:label="role.role_name"
:value="role.role_id"
>
<div style="display: flex; gap: 8px; align-items: center">
<span>{{ role.role_name }}</span>
<ElTag type="success" size="small">客户角色</ElTag>
</div>
</ElOption>
</ElSelect>
<div style="margin-top: 8px; color: #909399; font-size: 12px">
只能选择一个客户角色选择后立即生效
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton @click="defaultRolesDialogVisible = false">关闭</ElButton>
</div>
</template>
</ElDialog>
</ElCard>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
import {
FormInstance,
ElMessage,
ElMessageBox,
ElTag,
ElSwitch,
ElSelect,
ElOption
} from 'element-plus'
import type { FormRules } from 'element-plus'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useAuth } from '@/composables/useAuth'
import { useTableContextMenu } from '@/composables/useTableContextMenu'
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
import { ShopService, RoleService } from '@/api/modules'
import type { SearchFormItem } from '@/types'
import type { ShopResponse, ShopRoleResponse } from '@/types/api'
import { RoleType } from '@/types/api'
import { formatDateTime } from '@/utils/business/format'
import { CommonStatus, getStatusText, STATUS_SELECT_OPTIONS } from '@/config/constants'
import { RoutesAlias } from '@/router/routesAlias'
defineOptions({ name: 'Shop' })
const { hasAuth } = useAuth()
const router = useRouter()
// 使用表格右键菜单功能
const {
showContextMenuHint,
hintPosition,
getRowClassName,
handleCellMouseEnter,
handleCellMouseLeave
} = useTableContextMenu()
const dialogType = ref('add')
const dialogVisible = ref(false)
const loading = ref(false)
const submitLoading = ref(false)
const parentShopLoading = ref(false)
const parentShopList = ref<ShopResponse[]>([])
const searchParentShopList = ref<ShopResponse[]>([])
const defaultRoleLoading = ref(false)
const defaultRoleList = ref<any[]>([])
// 默认角色管理相关状态
const defaultRolesDialogVisible = ref(false)
const defaultRolesLoading = ref(false)
const rolesLoading = ref(false)
const currentShop = ref<ShopResponse | null>(null)
const availableRoles = ref<ShopRoleResponse[]>([])
const selectedRoleId = ref<number | undefined>(undefined)
// 右键菜单
const shopOperationMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
const currentOperatingShop = ref<ShopResponse | null>(null)
// 定义表单搜索初始值
const initialSearchState = {
shop_name: '',
shop_code: '',
parent_id: undefined as number | undefined,
level: undefined as number | undefined,
status: undefined as number | undefined
}
// 响应式表单数据
const searchForm = reactive({ ...initialSearchState })
const pagination = reactive({
currentPage: 1,
pageSize: 20,
total: 0
})
// 表格数据
const tableData = ref<ShopResponse[]>([])
// 表格实例引用
const tableRef = ref()
// 选中的行数据
const selectedRows = ref<any[]>([])
// 重置表单
const handleReset = () => {
Object.assign(searchForm, { ...initialSearchState })
pagination.currentPage = 1
getShopList()
}
// 搜索处理
const handleSearch = () => {
pagination.currentPage = 1
getShopList()
}
// 表单配置项
const searchFormItems = computed<SearchFormItem[]>(() => [
{
label: '店铺名称',
prop: 'shop_name',
type: 'input',
config: {
clearable: true,
placeholder: '请输入店铺名称'
}
},
{
label: '店铺编号',
prop: 'shop_code',
type: 'input',
config: {
clearable: true,
placeholder: '请输入店铺编号'
}
},
{
label: '上级店铺',
prop: 'parent_id',
type: 'select',
options: searchParentShopList.value.map((shop) => ({
label: shop.shop_name,
value: shop.id
})),
config: {
clearable: true,
filterable: true,
remote: true,
remoteMethod: handleSearchParentShop,
loading: parentShopLoading.value,
placeholder: '请选择或搜索上级店铺'
}
},
{
label: '店铺层级',
prop: 'level',
type: 'select',
options: [
{ label: '1级', value: 1 },
{ label: '2级', value: 2 },
{ label: '3级', value: 3 },
{ label: '4级', value: 4 },
{ label: '5级', value: 5 },
{ label: '6级', value: 6 },
{ label: '7级', value: 7 }
],
config: {
clearable: true,
placeholder: '请选择店铺层级'
}
},
{
label: '状态',
prop: 'status',
type: 'select',
options: STATUS_SELECT_OPTIONS,
config: {
clearable: true,
placeholder: '请选择状态'
}
}
])
// 列配置
const columnOptions = [
{ label: '店铺名称', prop: 'shop_name' },
{ label: '店铺编号', prop: 'shop_code' },
{ label: '层级', prop: 'level' },
{ label: '所在地区', prop: 'region' },
{ label: '联系人', prop: 'contact_name' },
{ label: '联系电话', prop: 'contact_phone' },
{ label: '状态', prop: 'status' },
{ label: '创建时间', prop: 'created_at' },
{ label: '操作', prop: 'operation' }
]
// 显示对话框
const showDialog = (type: string, row?: ShopResponse) => {
dialogType.value = type
// 先清除验证状态
formRef.value?.clearValidate()
if (type === 'edit' && row) {
formData.id = row.id
formData.shop_name = row.shop_name
formData.shop_code = ''
formData.parent_id = undefined
formData.province = row.province || ''
formData.city = row.city || ''
formData.district = row.district || ''
formData.address = row.address || ''
formData.contact_name = row.contact_name || ''
formData.contact_phone = row.contact_phone || ''
formData.status = row.status
formData.init_username = ''
formData.init_password = ''
formData.init_phone = ''
formData.default_role_id = undefined
} else {
formData.id = 0
formData.shop_name = ''
formData.shop_code = ''
formData.parent_id = undefined
formData.province = ''
formData.city = ''
formData.district = ''
formData.address = ''
formData.contact_name = ''
formData.contact_phone = ''
formData.status = CommonStatus.ENABLED
formData.init_username = ''
formData.init_password = ''
formData.init_phone = ''
formData.default_role_id = undefined
}
// 再次确保清除验证状态
nextTick(() => {
formRef.value?.clearValidate()
})
dialogVisible.value = true
}
// 删除店铺
const deleteShop = (row: ShopResponse) => {
ElMessageBox.confirm(`确定要删除店铺 ${row.shop_name} 吗?`, '删除店铺', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
})
.then(async () => {
try {
await ShopService.deleteShop(row.id)
ElMessage.success('删除成功')
getShopList()
} catch (error) {
console.error(error)
}
})
.catch(() => {
// 用户取消删除
})
}
// 动态列配置
const { columnChecks, columns } = useCheckedColumns(() => [
{
prop: 'shop_name',
label: '店铺名称',
minWidth: 160,
showOverflowTooltip: true
},
{
prop: 'shop_code',
label: '店铺编号',
width: 140,
showOverflowTooltip: true
},
{
prop: 'level',
label: '层级',
width: 80,
formatter: (row: ShopResponse) => {
return h(ElTag, { type: 'info', size: 'small' }, () => `${row.level}`)
}
},
{
prop: 'region',
label: '所在地区',
minWidth: 170,
showOverflowTooltip: true,
formatter: (row: ShopResponse) => {
const parts: string[] = []
if (row.province) parts.push(row.province)
if (row.city) parts.push(row.city)
if (row.district) parts.push(row.district)
if (row.address) parts.push(row.address)
return parts.length > 0 ? parts.join(' / ') : '-'
}
},
{
prop: 'contact_name',
label: '联系人',
width: 100
},
{
prop: 'contact_phone',
label: '联系电话',
width: 130
},
{
prop: 'status',
label: '状态',
width: 80,
formatter: (row: ShopResponse) => {
return h(ElSwitch, {
modelValue: row.status,
activeValue: CommonStatus.ENABLED,
inactiveValue: CommonStatus.DISABLED,
activeText: getStatusText(CommonStatus.ENABLED),
inactiveText: getStatusText(CommonStatus.DISABLED),
inlinePrompt: true,
disabled: !hasAuth('shop:modify_status'),
'onUpdate:modelValue': (val: string | number | boolean) =>
handleStatusChange(row, val as number)
})
}
},
{
prop: 'created_at',
label: '创建时间',
width: 180,
formatter: (row: ShopResponse) => formatDateTime(row.created_at)
},
{
prop: 'operation',
label: '操作',
width: 110,
fixed: 'right',
formatter: (row: ShopResponse) => {
const buttons = []
if (hasAuth('shop:look_customer')) {
buttons.push(
h(ArtButtonTable, {
text: '账号列表',
onClick: () => viewCustomerAccounts(row)
})
)
}
return h('div', { style: 'display: flex; gap: 8px;' }, buttons)
}
}
])
// 表单实例
const formRef = ref<FormInstance>()
// 表单数据
const formData = reactive({
id: 0,
shop_name: '',
shop_code: '',
parent_id: undefined as number | undefined,
province: '',
city: '',
district: '',
address: '',
contact_name: '',
contact_phone: '',
status: CommonStatus.ENABLED,
init_username: '',
init_password: '',
init_phone: '',
default_role_id: undefined as number | undefined
})
// 搜索默认角色(仅加载客户角色)
const searchDefaultRoles = async (query: string) => {
defaultRoleLoading.value = true
try {
const params: any = {
page: 1,
pageSize: 20,
role_type: 2, // 仅客户角色
status: 1 // 仅启用的角色
}
if (query) {
params.role_name = query
}
const res = await RoleService.getRoles(params)
if (res.code === 0) {
defaultRoleList.value = res.data.items || []
}
} catch (error) {
console.error('获取默认角色列表失败:', error)
} finally {
defaultRoleLoading.value = false
}
}
onMounted(() => {
getShopList()
loadParentShopList()
loadSearchParentShopList()
searchDefaultRoles('') // 加载初始默认角色列表
})
// 加载上级店铺列表(用于新增对话框,默认加载20条)
const loadParentShopList = async (shopName?: string) => {
parentShopLoading.value = true
try {
const params: any = {
page: 1,
pageSize: 20
}
if (shopName) {
params.shop_name = shopName
}
const res = await ShopService.getShops(params)
if (res.code === 0) {
parentShopList.value = res.data.items || []
}
} catch (error) {
console.error('获取上级店铺列表失败:', error)
} finally {
parentShopLoading.value = false
}
}
// 加载搜索栏上级店铺列表(默认加载20条)
const loadSearchParentShopList = async (shopName?: string) => {
try {
const params: any = {
page: 1,
pageSize: 20
}
if (shopName) {
params.shop_name = shopName
}
const res = await ShopService.getShops(params)
if (res.code === 0) {
searchParentShopList.value = res.data.items || []
}
} catch (error) {
console.error('获取上级店铺列表失败:', error)
}
}
// 搜索上级店铺(用于新增对话框)
const searchParentShops = (query: string) => {
if (query) {
loadParentShopList(query)
} else {
loadParentShopList()
}
}
// 搜索上级店铺(用于搜索栏)
const handleSearchParentShop = (query: string) => {
if (query) {
loadSearchParentShopList(query)
} else {
loadSearchParentShopList()
}
}
// 获取店铺列表
const getShopList = async () => {
loading.value = true
try {
const params = {
page: 1,
page_size: 9999, // 获取所有数据用于构建树形结构
shop_name: searchForm.shop_name || undefined,
shop_code: searchForm.shop_code || undefined,
parent_id: searchForm.parent_id,
level: searchForm.level,
status: searchForm.status
}
const res = await ShopService.getShops(params)
if (res.code === 0) {
const items = res.data.items || []
tableData.value = buildTreeData(items)
pagination.total = res.data.total || 0
}
} catch (error) {
console.error('获取店铺列表失败:', error)
} finally {
loading.value = false
}
}
// 构建树形数据
const buildTreeData = (items: ShopResponse[]) => {
const map = new Map<number, ShopResponse & { children?: ShopResponse[] }>()
const tree: ShopResponse[] = []
// 先将所有项放入 map
items.forEach((item) => {
map.set(item.id, { ...item, children: [] })
})
// 构建树形结构
items.forEach((item) => {
const node = map.get(item.id)!
if (item.parent_id && map.has(item.parent_id)) {
// 有父节点,添加到父节点的 children 中
const parent = map.get(item.parent_id)!
if (!parent.children) parent.children = []
parent.children.push(node)
} else {
// 没有父节点或父节点不存在,作为根节点
tree.push(node)
}
})
return tree
}
const handleRefresh = () => {
getShopList()
}
// 处理表格行选择变化
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
// 表单验证规则
const rules = reactive<FormRules>({
shop_name: [
{ required: true, message: '请输入店铺名称', trigger: 'blur' },
{ min: 1, max: 100, message: '长度在 1 到 100 个字符', trigger: 'blur' }
],
shop_code: [
{ required: true, message: '请输入店铺编号', trigger: 'blur' },
{ min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' }
],
address: [{ max: 255, message: '地址不能超过255个字符', trigger: 'blur' }],
contact_name: [{ max: 50, message: '联系人姓名不能超过50个字符', trigger: 'blur' }],
contact_phone: [
{ len: 11, message: '联系电话必须为 11 位', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
],
init_username: [
{ required: true, message: '请输入初始账号用户名', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
],
init_password: [
{ required: true, message: '请输入初始账号密码', trigger: 'blur' },
{ min: 8, max: 32, message: '密码长度为 8-32 个字符', trigger: 'blur' }
],
init_phone: [
{ required: true, message: '请输入初始账号手机号', trigger: 'blur' },
{ len: 11, message: '手机号必须为 11 位', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
],
default_role_id: [{ required: true, message: '请选择默认角色', trigger: 'blur' }]
})
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
submitLoading.value = true
try {
if (dialogType.value === 'add') {
const data: any = {
shop_name: formData.shop_name,
shop_code: formData.shop_code,
init_username: formData.init_username,
init_password: formData.init_password,
init_phone: formData.init_phone,
default_role_id: formData.default_role_id
}
// 可选字段
if (formData.parent_id) data.parent_id = formData.parent_id
if (formData.province) data.province = formData.province
if (formData.city) data.city = formData.city
if (formData.district) data.district = formData.district
if (formData.address) data.address = formData.address
if (formData.contact_name) data.contact_name = formData.contact_name
if (formData.contact_phone) data.contact_phone = formData.contact_phone
await ShopService.createShop(data)
ElMessage.success('新增成功')
} else {
const data: any = {
shop_name: formData.shop_name,
status: formData.status
}
// 可选字段
if (formData.province) data.province = formData.province
if (formData.city) data.city = formData.city
if (formData.district) data.district = formData.district
if (formData.address) data.address = formData.address
if (formData.contact_name) data.contact_name = formData.contact_name
if (formData.contact_phone) data.contact_phone = formData.contact_phone
await ShopService.updateShop(formData.id, data)
ElMessage.success('更新成功')
}
dialogVisible.value = false
getShopList()
} catch (error) {
console.error(error)
} finally {
submitLoading.value = false
}
}
})
}
// 处理表格分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getShopList()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.currentPage = newCurrentPage
getShopList()
}
// 查看客户账号
const viewCustomerAccounts = (row: ShopResponse) => {
// 跳转到账号列表页面,通过 query 参数区分店铺类型
router.push({
name: 'EnterpriseCustomerAccounts',
params: { id: row.id },
query: { type: 'shop' }
})
}
// 店铺操作菜单项配置
const shopOperationMenuItems = computed((): MenuItemType[] => {
const items: MenuItemType[] = []
// 默认角色
if (hasAuth('shop:default_role')) {
items.push({
key: 'defaultRoles',
label: '默认角色'
})
}
// 编辑
if (hasAuth('shop:edit')) {
items.push({
key: 'edit',
label: '编辑'
})
}
// 删除
if (hasAuth('shop:delete')) {
items.push({
key: 'delete',
label: '删除'
})
}
return items
})
// 显示店铺操作右键菜单
const showShopOperationMenu = (e: MouseEvent, row: ShopResponse) => {
e.preventDefault()
e.stopPropagation()
currentOperatingShop.value = row
shopOperationMenuRef.value?.show(e)
}
// 处理表格行右键菜单
const handleRowContextMenu = (row: ShopResponse, column: any, event: MouseEvent) => {
// 如果用户有编辑或删除权限,显示右键菜单
if (hasAuth('shop:edit') || hasAuth('shop:delete')) {
showShopOperationMenu(event, row)
}
}
// 处理店铺操作菜单选择
const handleShopOperationMenuSelect = (item: MenuItemType) => {
if (!currentOperatingShop.value) return
switch (item.key) {
case 'defaultRoles':
showDefaultRolesDialog(currentOperatingShop.value)
break
case 'edit':
showDialog('edit', currentOperatingShop.value)
break
case 'delete':
deleteShop(currentOperatingShop.value)
break
}
}
// 状态切换
const handleStatusChange = async (row: ShopResponse, newStatus: number) => {
const oldStatus = row.status
// 先更新UI
row.status = newStatus
try {
await ShopService.updateShop(row.id, {
shop_name: row.shop_name,
status: newStatus
})
ElMessage.success('状态切换成功')
} catch (error) {
// 切换失败,恢复原状态
row.status = oldStatus
console.error(error)
}
}
// ========== 默认角色管理功能 ==========
// 显示默认角色管理对话框
const showDefaultRolesDialog = async (row: ShopResponse) => {
currentShop.value = row
defaultRolesDialogVisible.value = true
await loadAvailableRoles()
await loadShopDefaultRole(row.id)
}
// 加载店铺当前默认角色
const loadShopDefaultRole = async (shopId: number) => {
defaultRolesLoading.value = true
try {
const res = await ShopService.getShopRoles(shopId)
if (res.code === 0) {
const roles = res.data.roles || []
// 如果已有默认角色,预选第一个
selectedRoleId.value = roles.length > 0 ? roles[0].role_id : undefined
}
} catch (error) {
console.error('获取店铺默认角色失败:', error)
ElMessage.error('获取店铺默认角色失败')
} finally {
defaultRolesLoading.value = false
}
}
// 加载可用角色列表(仅客户角色)
const loadAvailableRoles = async () => {
rolesLoading.value = true
try {
const res = await RoleService.getRoles({
page: 1,
page_size: 9999,
role_type: 2, // 仅客户角色
status: 1 // RoleStatus.ENABLED
})
if (res.code === 0) {
// 将 PlatformRole 转换为与 ShopRoleResponse 兼容的格式
availableRoles.value = (res.data.items || []).map((role) => ({
...role,
role_id: role.ID,
role_name: role.role_name,
role_desc: role.role_desc,
role_type: role.role_type,
shop_id: 0
}))
}
} catch (error) {
console.error('获取角色列表失败:', error)
ElMessage.error('获取角色列表失败')
} finally {
rolesLoading.value = false
}
}
// 处理角色变更 - 选择后立即生效
const handleRoleChange = async (roleId: number) => {
if (!roleId) {
ElMessage.warning('请选择默认角色')
return
}
if (!currentShop.value) {
ElMessage.error('店铺信息异常')
return
}
defaultRolesLoading.value = true
try {
// 传递数组但只包含一个角色ID
const res = await ShopService.assignShopRoles(currentShop.value.id, {
role_ids: [roleId]
})
if (res.code === 0) {
ElMessage.success('设置默认角色成功')
}
} catch (error) {
console.error('设置默认角色失败:', error)
// 失败时重新加载当前默认角色
await loadShopDefaultRole(currentShop.value.id)
} finally {
defaultRolesLoading.value = false
}
}
</script>
<style lang="scss" scoped>
.shop-page {
// 店铺管理页面样式
}
.current-role-display {
border-radius: 8px;
padding: 16px 20px;
margin-bottom: 24px;
border: 2px solid var(--el-color-primary);
&.no-role {
border: 2px dashed var(--el-border-color);
.current-role-value {
color: var(--el-text-color-secondary);
}
}
.current-role-label {
font-size: 14px;
color: var(--el-text-color-secondary);
margin-bottom: 12px;
font-weight: 500;
}
.current-role-value {
font-size: 20px;
font-weight: 600;
color: var(--el-color-primary);
display: flex;
align-items: center;
}
}
.default-roles-section {
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
font-weight: 500;
color: #303133;
margin-bottom: 4px;
}
}
:deep(.el-table__row.table-row-with-context-menu) {
cursor: pointer;
}
</style>