feat: 完善15- 角色默认信用与店铺实际额度管理
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m8s

This commit is contained in:
luo
2026-07-25 11:55:44 +08:00
parent 9289a6e940
commit ee508c3e1e
16 changed files with 457 additions and 100 deletions

View File

@@ -0,0 +1,108 @@
<template>
<div class="shop-detail-page">
<ElCard shadow="never">
<div class="detail-header">
<ElButton @click="handleBack">
<template #icon>
<ElIcon><ArrowLeft /></ElIcon>
</template>
返回
</ElButton>
<h2 class="detail-title">店铺详情</h2>
</div>
<div v-loading="loading">
<ElDescriptions v-if="shop" :column="2" border>
<ElDescriptionsItem label="店铺名称">{{ shop.shop_name || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="店铺编号">{{ shop.shop_code || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="店铺层级">{{ shop.level }}</ElDescriptionsItem>
<ElDescriptionsItem label="上级店铺">{{ shop.parent_shop_name || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="联系人">{{ shop.contact_name || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="联系电话">{{ shop.contact_phone || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="平台业务员">
{{ formatBusinessOwner(shop) }}
</ElDescriptionsItem>
<ElDescriptionsItem label="业务员通知状态">
{{ shop.business_owner_account_id ? (shop.business_owner_available ? '可用' : '已停用') : '-' }}
</ElDescriptionsItem>
<ElDescriptionsItem label="省份">{{ shop.province || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="城市">{{ shop.city || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="区县">{{ shop.district || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="详细地址">{{ shop.address || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="状态">{{ shop.status_name || shop.status }}</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间">{{ shop.created_at || '-' }}</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间">{{ shop.updated_at || '-' }}</ElDescriptionsItem>
</ElDescriptions>
</div>
</ElCard>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ArrowLeft } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { ShopService } from '@/api/modules'
import type { ShopResponse } from '@/types/api'
defineOptions({ name: 'ShopDetail' })
const route = useRoute()
const router = useRouter()
const loading = ref(false)
const shop = ref<ShopResponse | null>(null)
const formatBusinessOwner = (value: ShopResponse) => {
if (!value.business_owner_username) return '-'
return value.business_owner_phone_summary
? `${value.business_owner_username} (${value.business_owner_phone_summary})`
: value.business_owner_username
}
const handleBack = () => router.back()
const fetchDetail = async () => {
const id = Number(route.params.id)
if (!id) {
ElMessage.error('缺少店铺 ID')
return
}
loading.value = true
try {
const response = await ShopService.getShopDetail(id)
if (response.code !== 0 || !response.data) {
ElMessage.error(response.msg || '获取店铺详情失败')
return
}
shop.value = response.data
} catch (error: any) {
ElMessage.error(error?.message || '获取店铺详情失败')
} finally {
loading.value = false
}
}
onMounted(fetchDetail)
</script>
<style scoped lang="scss">
.shop-detail-page {
padding: 20px;
}
.detail-header {
display: flex;
gap: 16px;
align-items: center;
padding-bottom: 16px;
}
.detail-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: var(--el-text-color-primary);
}
</style>

View File

@@ -7,6 +7,7 @@
:items="searchFormItems"
show-expand
@reset="handleReset"
label-width="100px"
@search="handleSearch"
></ArtSearchBar>
@@ -203,9 +204,9 @@
>
<ElOption
v-for="role in defaultRoleList"
:key="role.ID"
:key="role.id"
:label="role.role_name"
:value="role.ID"
:value="role.id"
>
<div
style="display: flex; align-items: center; justify-content: space-between"
@@ -238,16 +239,6 @@
</template>
</ElDialog>
<ElDialog v-model="detailDialogVisible" title="店铺详情" width="500px">
<ElDescriptions v-if="detailShop" :column="1" border>
<ElDescriptionsItem label="店铺名称">{{ detailShop.shop_name }}</ElDescriptionsItem>
<ElDescriptionsItem label="店铺编号">{{ detailShop.shop_code }}</ElDescriptionsItem>
<ElDescriptionsItem label="平台业务员">
{{ formatBusinessOwner(detailShop) }}
</ElDescriptionsItem>
</ElDescriptions>
</ElDialog>
<!-- 店铺默认角色管理对话框 -->
<ElDialog
v-model="defaultRolesDialogVisible"
@@ -337,8 +328,9 @@
} from '@/types/api'
import { RoleType, RoleStatus } from '@/types/api'
import { formatDateTime } from '@/utils/business/format'
import { CommonStatus, getStatusText, STATUS_SELECT_OPTIONS } from '@/config/constants'
import { regionData } from '@/utils/constants/regionData'
import { CommonStatus, getStatusText, STATUS_SELECT_OPTIONS } from '@/config/constants'
import { regionData } from '@/utils/constants/regionData'
import { RoutesAlias } from '@/router/routesAlias'
defineOptions({ name: 'Shop' })
@@ -354,8 +346,6 @@
const defaultRoleList = ref<any[]>([])
const salespersonLoading = ref(false)
const salespersonOptions = ref<ShopBusinessOwnerCandidate[]>([])
const detailDialogVisible = ref(false)
const detailShop = ref<ShopResponse | null>(null)
// 级联选择相关
const parentShopCascadeOptions = ref<any[]>([])
@@ -445,20 +435,13 @@
: shop.business_owner_username
}
const showDetail = async (row: ShopResponse) => {
try {
const response = await ShopService.getShopDetail(row.id)
if (response.code !== 0 || !response.data) {
ElMessage.error(response.msg || '获取店铺详情失败')
return
}
detailShop.value = response.data
} catch (error: any) {
ElMessage.error(error?.message || '获取店铺详情失败')
return
}
detailDialogVisible.value = true
}
const handleNameClick = (row: ShopResponse) => {
if (hasAuth('shop:detail')) {
void router.push(`${RoutesAlias.ShopDetail}/${row.id}`)
} else {
ElMessage.warning('您没有查看详情的权限')
}
}
// 定义表单搜索初始值
const initialSearchState = {
@@ -685,11 +668,29 @@
// 动态列配置
const { columnChecks, columns } = useCheckedColumns(() => [
{
prop: 'shop_name',
label: '店铺名称',
width: 160,
showOverflowTooltip: true
{
prop: 'shop_name',
label: '店铺名称',
width: 160,
showOverflowTooltip: true,
formatter: (row: ShopResponse) => {
const hasPermission = hasAuth('shop:detail')
return h(
'span',
{
style: hasPermission
? 'color: var(--el-color-primary); cursor: pointer; text-decoration: underline;'
: '',
onClick: hasPermission
? (event: MouseEvent) => {
event.stopPropagation()
handleNameClick(row)
}
: undefined
},
row.shop_name
)
}
},
{
prop: 'shop_code',
@@ -768,12 +769,6 @@
const getActions = (row: ShopResponse) => {
const actions: any[] = []
actions.push({
label: '详情',
handler: () => showDetail(row),
type: 'primary'
})
if (hasAuth('shop:look_customer')) {
actions.push({
label: '账号列表',
@@ -860,10 +855,10 @@
const searchDefaultRoles = async (query: string) => {
defaultRoleLoading.value = true
try {
const params: any = {
page: 1,
pageSize: 20,
role_type: RoleType.CUSTOMER, // 仅客户角色
const params: any = {
page: 1,
page_size: 20,
role_type: RoleType.CUSTOMER, // 仅客户角色
status: RoleStatus.ENABLED // 仅启用的角色
}
if (query) {
@@ -1129,7 +1124,7 @@
// 将 PlatformRole 转换为与 ShopRoleResponse 兼容的格式
availableRoles.value = (res.data.items || []).map((role) => ({
...role,
role_id: role.ID,
role_id: role.id ?? role.ID,
role_name: role.role_name,
role_desc: role.role_desc,
role_type: role.role_type,