1124 lines
34 KiB
Vue
1124 lines
34 KiB
Vue
<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">
|
||
<div style="display: flex; gap: 8px">
|
||
<ElInput
|
||
v-model="formData.shop_code"
|
||
placeholder="请输入店铺编号或点击生成"
|
||
clearable
|
||
style="flex: 1"
|
||
/>
|
||
<CodeGeneratorButton code-type="shop" @generated="handleCodeGenerated" />
|
||
</div>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
|
||
<ElRow :gutter="20" v-if="dialogType === 'add'">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="上级店铺" prop="parent_id">
|
||
<ElTreeSelect
|
||
v-model="formData.parent_id"
|
||
:data="parentShopTreeData"
|
||
placeholder="一级店铺可不选"
|
||
filterable
|
||
clearable
|
||
check-strictly
|
||
:render-after-expand="false"
|
||
:props="{
|
||
label: 'shop_name',
|
||
value: 'id',
|
||
children: 'children'
|
||
}"
|
||
style="width: 100%"
|
||
/>
|
||
</ElFormItem>
|
||
</ElCol>
|
||
</ElRow>
|
||
|
||
<ElRow :gutter="20">
|
||
<ElCol :span="12">
|
||
<ElFormItem label="所在地区" prop="region">
|
||
<ElCascader
|
||
v-model="formData.region"
|
||
:options="regionData"
|
||
placeholder="请选择省/市/区"
|
||
clearable
|
||
filterable
|
||
style="width: 100%"
|
||
@change="handleRegionChange"
|
||
/>
|
||
</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,
|
||
ElTreeSelect,
|
||
ElCascader
|
||
} 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 CodeGeneratorButton from '@/components/business/CodeGeneratorButton.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'
|
||
import { regionData } from '@/utils/constants/regionData'
|
||
|
||
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 parentShopTreeData = 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: 'tree-select',
|
||
config: {
|
||
data: parentShopTreeData.value,
|
||
clearable: true,
|
||
filterable: true,
|
||
checkStrictly: true,
|
||
renderAfterExpand: false,
|
||
props: {
|
||
label: 'shop_name',
|
||
value: 'id',
|
||
children: 'children'
|
||
},
|
||
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: 'region' },
|
||
{ label: '联系人', prop: 'contact_name' },
|
||
{ label: '联系电话', prop: 'contact_phone' },
|
||
{ label: '状态', prop: 'status' },
|
||
{ label: '创建时间', prop: 'created_at' },
|
||
{ label: '操作', prop: 'operation' }
|
||
]
|
||
|
||
// 显示对话框
|
||
const showDialog = async (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 || ''
|
||
// 编辑时回显地区
|
||
if (row.province && row.city && row.district) {
|
||
formData.region = [row.province, row.city, row.district]
|
||
} else {
|
||
formData.region = []
|
||
}
|
||
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 {
|
||
// 新增模式下重新获取上级店铺列表
|
||
await loadParentShopList()
|
||
|
||
formData.id = 0
|
||
formData.shop_name = ''
|
||
formData.shop_code = ''
|
||
formData.parent_id = undefined
|
||
formData.region = []
|
||
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('删除成功')
|
||
await getShopList()
|
||
// 删除成功后也更新上级店铺列表
|
||
await loadParentShopList()
|
||
} catch (error) {
|
||
console.error(error)
|
||
}
|
||
})
|
||
.catch(() => {
|
||
// 用户取消删除
|
||
})
|
||
}
|
||
|
||
// 动态列配置
|
||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||
{
|
||
prop: 'shop_name',
|
||
label: '店铺名称',
|
||
width: 160,
|
||
showOverflowTooltip: true
|
||
},
|
||
{
|
||
prop: 'shop_code',
|
||
label: '店铺编号',
|
||
minWidth: 160,
|
||
showOverflowTooltip: true
|
||
},
|
||
{
|
||
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,
|
||
region: [] as string[], // 省市区级联数据
|
||
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 handleCodeGenerated = (code: string) => {
|
||
formData.shop_code = code
|
||
// 清除该字段的验证错误提示
|
||
nextTick(() => {
|
||
formRef.value?.clearValidate('shop_code')
|
||
})
|
||
}
|
||
|
||
// 处理地区选择变化
|
||
const handleRegionChange = (value: string[]) => {
|
||
if (value && value.length === 3) {
|
||
formData.province = value[0]
|
||
formData.city = value[1]
|
||
formData.district = value[2]
|
||
} else {
|
||
formData.province = ''
|
||
formData.city = ''
|
||
formData.district = ''
|
||
}
|
||
}
|
||
|
||
// 搜索默认角色(仅加载客户角色)
|
||
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()
|
||
searchDefaultRoles('') // 加载初始默认角色列表
|
||
})
|
||
|
||
// 加载上级店铺列表(用于新增对话框和搜索栏,获取所有店铺构建树形结构)
|
||
const loadParentShopList = async (shopName?: string) => {
|
||
parentShopLoading.value = true
|
||
try {
|
||
const params: any = {
|
||
page: 1,
|
||
page_size: 9999 // 获取所有数据用于构建树形结构
|
||
}
|
||
if (shopName) {
|
||
params.shop_name = shopName
|
||
}
|
||
const res = await ShopService.getShops(params)
|
||
if (res.code === 0) {
|
||
const items = res.data.items || []
|
||
parentShopList.value = items
|
||
// 构建树形数据
|
||
parentShopTreeData.value = buildTreeData(items)
|
||
}
|
||
} catch (error) {
|
||
console.error('获取上级店铺列表失败:', error)
|
||
} finally {
|
||
parentShopLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 获取店铺列表
|
||
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
|
||
await getShopList()
|
||
// 提交成功后也更新上级店铺列表
|
||
await loadParentShopList()
|
||
} 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>
|