This commit is contained in:
59
src/components/business/CodeGeneratorButton.vue
Normal file
59
src/components/business/CodeGeneratorButton.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<ElButton @click="handleGenerate">
|
||||
{{ buttonText }}
|
||||
</ElButton>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
generateSeriesCode,
|
||||
generatePackageCode,
|
||||
generateShopCode
|
||||
} from '@/utils/codeGenerator'
|
||||
|
||||
interface Props {
|
||||
// 编码类型: series(套餐系列) | package(套餐) | shop(店铺)
|
||||
codeType: 'series' | 'package' | 'shop'
|
||||
// 按钮文字
|
||||
buttonText?: string
|
||||
// 表单字段名,用于清除验证
|
||||
fieldName?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
buttonText: '生成编码'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'generated', code: string): void
|
||||
}>()
|
||||
|
||||
// 生成编码
|
||||
const handleGenerate = () => {
|
||||
let code = ''
|
||||
|
||||
switch (props.codeType) {
|
||||
case 'series':
|
||||
code = generateSeriesCode()
|
||||
break
|
||||
case 'package':
|
||||
code = generatePackageCode()
|
||||
break
|
||||
case 'shop':
|
||||
code = generateShopCode()
|
||||
break
|
||||
default:
|
||||
ElMessage.error('未知的编码类型')
|
||||
return
|
||||
}
|
||||
|
||||
// 触发生成事件,将编码传递给父组件
|
||||
emit('generated', code)
|
||||
ElMessage.success('编码生成成功')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 可以添加特定样式
|
||||
</style>
|
||||
@@ -75,6 +75,7 @@
|
||||
import ArtSearchSelect from './widget/art-search-select/index.vue'
|
||||
import ArtSearchRadio from './widget/art-search-radio/index.vue'
|
||||
import ArtSearchDate from './widget/art-search-date/index.vue'
|
||||
import ArtSearchTreeSelect from './widget/art-search-tree-select/index.vue'
|
||||
import { SearchComponentType, SearchFormItem } from '@/types'
|
||||
|
||||
const { width } = useWindowSize()
|
||||
@@ -131,6 +132,7 @@
|
||||
const componentsMap: Record<string, any> = {
|
||||
input: ArtSearchInput,
|
||||
select: ArtSearchSelect,
|
||||
'tree-select': ArtSearchTreeSelect,
|
||||
radio: ArtSearchRadio,
|
||||
datetime: ArtSearchDate,
|
||||
date: ArtSearchDate,
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<el-tree-select v-model="value" v-bind="config" @change="(val) => changeValue(val)" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SearchFormItem } from '@/types'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// 定义组件值类型
|
||||
export type ValueVO = unknown
|
||||
|
||||
// 定义组件props
|
||||
const prop = defineProps<{
|
||||
value: ValueVO // 树形选择框的值
|
||||
item: SearchFormItem // 表单项配置
|
||||
}>()
|
||||
|
||||
// 定义emit事件
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: ValueVO): void // 更新树形选择框值的事件
|
||||
}>()
|
||||
|
||||
// 计算属性:处理v-model双向绑定
|
||||
const value = computed({
|
||||
get: () => prop.value,
|
||||
set: (value: ValueVO) => emit('update:value', value)
|
||||
})
|
||||
|
||||
// 合并默认配置和自定义配置
|
||||
const config = reactive({
|
||||
placeholder: `${t('table.searchBar.searchSelectPlaceholder')}${prop.item.label}`,
|
||||
...(prop.item.config || {})
|
||||
})
|
||||
|
||||
// 树形选择框值变化处理函数
|
||||
const changeValue = (val: unknown): void => {
|
||||
if (prop.item.onChange) {
|
||||
prop.item.onChange({
|
||||
prop: prop.item.prop,
|
||||
val
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -2,6 +2,23 @@
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
/**
|
||||
* 格式化空值为 "-"
|
||||
* @param value 要格式化的值
|
||||
* @returns 格式化后的值
|
||||
*/
|
||||
const formatEmptyValue = (value: any): any => {
|
||||
// 判断值是否为空
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
}
|
||||
// 如果是数字0,返回0
|
||||
if (value === 0) {
|
||||
return '0'
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// 定义列配置接口
|
||||
export interface ColumnOption {
|
||||
type?: 'selection' | 'expand' | 'index'
|
||||
@@ -76,6 +93,27 @@ export function useCheckedColumns(columnsFactory: () => ColumnOption[]) {
|
||||
const columnMap = new Map<string, ColumnOption>()
|
||||
|
||||
cols.forEach((column) => {
|
||||
// 为普通列添加空值处理
|
||||
if (!column.type || (column.type !== 'selection' && column.type !== 'expand' && column.type !== 'index')) {
|
||||
const originalFormatter = column.formatter
|
||||
|
||||
// 包装原有的 formatter,在其基础上添加空值处理
|
||||
column.formatter = (row: any, ...args: any[]) => {
|
||||
let value
|
||||
|
||||
// 如果有自定义 formatter,先执行
|
||||
if (originalFormatter) {
|
||||
value = originalFormatter(row, ...args)
|
||||
} else {
|
||||
// 没有自定义 formatter,直接取字段值
|
||||
value = row[column.prop as string]
|
||||
}
|
||||
|
||||
// 对结果进行空值处理
|
||||
return formatEmptyValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
if (column.type === 'selection') {
|
||||
columnMap.set(SELECTION_KEY, column)
|
||||
} else if (column.type === 'expand') {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Option } from '../common'
|
||||
export type SearchComponentType =
|
||||
| 'input'
|
||||
| 'select'
|
||||
| 'tree-select'
|
||||
| 'radio'
|
||||
| 'checkbox'
|
||||
| 'date'
|
||||
|
||||
4
src/types/components.d.ts
vendored
4
src/types/components.d.ts
vendored
@@ -56,10 +56,12 @@ declare module 'vue' {
|
||||
ArtSearchInput: typeof import('./../components/core/forms/art-search-bar/widget/art-search-input/index.vue')['default']
|
||||
ArtSearchRadio: typeof import('./../components/core/forms/art-search-bar/widget/art-search-radio/index.vue')['default']
|
||||
ArtSearchSelect: typeof import('./../components/core/forms/art-search-bar/widget/art-search-select/index.vue')['default']
|
||||
ArtSearchTreeSelect: typeof import('./../components/core/forms/art-search-bar/widget/art-search-tree-select/index.vue')['default']
|
||||
ArtSettingsPanel: typeof import('./../components/core/layouts/art-settings-panel/index.vue')['default']
|
||||
ArtSidebarMenu: typeof import('./../components/core/layouts/art-menus/art-sidebar-menu/index.vue')['default']
|
||||
ArtStatsCard: typeof import('./../components/core/cards/ArtStatsCard.vue')['default']
|
||||
ArtTable: typeof import('./../components/core/tables/ArtTable.vue')['default']
|
||||
ArtTableColumn: typeof import('./../components/core/tables/ArtTableColumn.vue')['default']
|
||||
ArtTableFullScreen: typeof import('./../components/core/tables/ArtTableFullScreen.vue')['default']
|
||||
ArtTableHeader: typeof import('./../components/core/tables/ArtTableHeader.vue')['default']
|
||||
ArtTextScroll: typeof import('./../components/core/text-effect/ArtTextScroll.vue')['default']
|
||||
@@ -73,6 +75,7 @@ declare module 'vue' {
|
||||
BoxStyleSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/BoxStyleSettings.vue')['default']
|
||||
CardOperationDialog: typeof import('./../components/business/CardOperationDialog.vue')['default']
|
||||
CardStatusTag: typeof import('./../components/business/CardStatusTag.vue')['default']
|
||||
CodeGeneratorButton: typeof import('./../components/business/CodeGeneratorButton.vue')['default']
|
||||
ColorSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/ColorSettings.vue')['default']
|
||||
CommentItem: typeof import('./../components/custom/comment-widget/widget/CommentItem.vue')['default']
|
||||
CommentWidget: typeof import('./../components/custom/comment-widget/index.vue')['default']
|
||||
@@ -86,6 +89,7 @@ declare module 'vue' {
|
||||
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||
ElCalendar: typeof import('element-plus/es')['ElCalendar']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCascader: typeof import('element-plus/es')['ElCascader']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
|
||||
@@ -45,3 +45,25 @@ export function generatePackageCode(): string {
|
||||
|
||||
return `PKG${year}${month}${day}${hours}${minutes}${seconds}${randomChars}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成店铺编码
|
||||
* 规则: SHOP + 年月日时分秒 + 4位随机数
|
||||
* 示例: SHOP20260314143025ABCD
|
||||
*/
|
||||
export function generateShopCode(): string {
|
||||
const now = new Date()
|
||||
const year = now.getFullYear()
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(now.getDate()).padStart(2, '0')
|
||||
const hours = String(now.getHours()).padStart(2, '0')
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0')
|
||||
|
||||
// 生成4位随机大写字母
|
||||
const randomChars = Array.from({ length: 4 }, () =>
|
||||
String.fromCharCode(65 + Math.floor(Math.random() * 26))
|
||||
).join('')
|
||||
|
||||
return `SHOP${year}${month}${day}${hours}${minutes}${seconds}${randomChars}`
|
||||
}
|
||||
|
||||
220
src/utils/constants/regionData.ts
Normal file
220
src/utils/constants/regionData.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* 省市区三级联动数据
|
||||
* 简化版数据,包含主要省市区
|
||||
*/
|
||||
|
||||
export interface RegionItem {
|
||||
value: string
|
||||
label: string
|
||||
children?: RegionItem[]
|
||||
}
|
||||
|
||||
export const regionData: RegionItem[] = [
|
||||
{
|
||||
value: '北京市',
|
||||
label: '北京市',
|
||||
children: [
|
||||
{
|
||||
value: '北京市',
|
||||
label: '北京市',
|
||||
children: [
|
||||
{ value: '东城区', label: '东城区' },
|
||||
{ value: '西城区', label: '西城区' },
|
||||
{ value: '朝阳区', label: '朝阳区' },
|
||||
{ value: '丰台区', label: '丰台区' },
|
||||
{ value: '石景山区', label: '石景山区' },
|
||||
{ value: '海淀区', label: '海淀区' },
|
||||
{ value: '门头沟区', label: '门头沟区' },
|
||||
{ value: '房山区', label: '房山区' },
|
||||
{ value: '通州区', label: '通州区' },
|
||||
{ value: '顺义区', label: '顺义区' },
|
||||
{ value: '昌平区', label: '昌平区' },
|
||||
{ value: '大兴区', label: '大兴区' },
|
||||
{ value: '怀柔区', label: '怀柔区' },
|
||||
{ value: '平谷区', label: '平谷区' },
|
||||
{ value: '密云区', label: '密云区' },
|
||||
{ value: '延庆区', label: '延庆区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '上海市',
|
||||
label: '上海市',
|
||||
children: [
|
||||
{
|
||||
value: '上海市',
|
||||
label: '上海市',
|
||||
children: [
|
||||
{ value: '黄浦区', label: '黄浦区' },
|
||||
{ value: '徐汇区', label: '徐汇区' },
|
||||
{ value: '长宁区', label: '长宁区' },
|
||||
{ value: '静安区', label: '静安区' },
|
||||
{ value: '普陀区', label: '普陀区' },
|
||||
{ value: '虹口区', label: '虹口区' },
|
||||
{ value: '杨浦区', label: '杨浦区' },
|
||||
{ value: '闵行区', label: '闵行区' },
|
||||
{ value: '宝山区', label: '宝山区' },
|
||||
{ value: '嘉定区', label: '嘉定区' },
|
||||
{ value: '浦东新区', label: '浦东新区' },
|
||||
{ value: '金山区', label: '金山区' },
|
||||
{ value: '松江区', label: '松江区' },
|
||||
{ value: '青浦区', label: '青浦区' },
|
||||
{ value: '奉贤区', label: '奉贤区' },
|
||||
{ value: '崇明区', label: '崇明区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '广东省',
|
||||
label: '广东省',
|
||||
children: [
|
||||
{
|
||||
value: '广州市',
|
||||
label: '广州市',
|
||||
children: [
|
||||
{ value: '荔湾区', label: '荔湾区' },
|
||||
{ value: '越秀区', label: '越秀区' },
|
||||
{ value: '海珠区', label: '海珠区' },
|
||||
{ value: '天河区', label: '天河区' },
|
||||
{ value: '白云区', label: '白云区' },
|
||||
{ value: '黄埔区', label: '黄埔区' },
|
||||
{ value: '番禺区', label: '番禺区' },
|
||||
{ value: '花都区', label: '花都区' },
|
||||
{ value: '南沙区', label: '南沙区' },
|
||||
{ value: '从化区', label: '从化区' },
|
||||
{ value: '增城区', label: '增城区' }
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '深圳市',
|
||||
label: '深圳市',
|
||||
children: [
|
||||
{ value: '罗湖区', label: '罗湖区' },
|
||||
{ value: '福田区', label: '福田区' },
|
||||
{ value: '南山区', label: '南山区' },
|
||||
{ value: '宝安区', label: '宝安区' },
|
||||
{ value: '龙岗区', label: '龙岗区' },
|
||||
{ value: '盐田区', label: '盐田区' },
|
||||
{ value: '龙华区', label: '龙华区' },
|
||||
{ value: '坪山区', label: '坪山区' },
|
||||
{ value: '光明区', label: '光明区' }
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '东莞市',
|
||||
label: '东莞市',
|
||||
children: [
|
||||
{ value: '莞城区', label: '莞城区' },
|
||||
{ value: '南城区', label: '南城区' },
|
||||
{ value: '东城区', label: '东城区' },
|
||||
{ value: '万江区', label: '万江区' }
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '佛山市',
|
||||
label: '佛山市',
|
||||
children: [
|
||||
{ value: '禅城区', label: '禅城区' },
|
||||
{ value: '南海区', label: '南海区' },
|
||||
{ value: '顺德区', label: '顺德区' },
|
||||
{ value: '三水区', label: '三水区' },
|
||||
{ value: '高明区', label: '高明区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '浙江省',
|
||||
label: '浙江省',
|
||||
children: [
|
||||
{
|
||||
value: '杭州市',
|
||||
label: '杭州市',
|
||||
children: [
|
||||
{ value: '上城区', label: '上城区' },
|
||||
{ value: '拱墅区', label: '拱墅区' },
|
||||
{ value: '西湖区', label: '西湖区' },
|
||||
{ value: '滨江区', label: '滨江区' },
|
||||
{ value: '萧山区', label: '萧山区' },
|
||||
{ value: '余杭区', label: '余杭区' },
|
||||
{ value: '临平区', label: '临平区' },
|
||||
{ value: '钱塘区', label: '钱塘区' },
|
||||
{ value: '富阳区', label: '富阳区' },
|
||||
{ value: '临安区', label: '临安区' }
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '宁波市',
|
||||
label: '宁波市',
|
||||
children: [
|
||||
{ value: '海曙区', label: '海曙区' },
|
||||
{ value: '江北区', label: '江北区' },
|
||||
{ value: '北仑区', label: '北仑区' },
|
||||
{ value: '镇海区', label: '镇海区' },
|
||||
{ value: '鄞州区', label: '鄞州区' },
|
||||
{ value: '奉化区', label: '奉化区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '江苏省',
|
||||
label: '江苏省',
|
||||
children: [
|
||||
{
|
||||
value: '南京市',
|
||||
label: '南京市',
|
||||
children: [
|
||||
{ value: '玄武区', label: '玄武区' },
|
||||
{ value: '秦淮区', label: '秦淮区' },
|
||||
{ value: '建邺区', label: '建邺区' },
|
||||
{ value: '鼓楼区', label: '鼓楼区' },
|
||||
{ value: '浦口区', label: '浦口区' },
|
||||
{ value: '栖霞区', label: '栖霞区' },
|
||||
{ value: '雨花台区', label: '雨花台区' },
|
||||
{ value: '江宁区', label: '江宁区' },
|
||||
{ value: '六合区', label: '六合区' },
|
||||
{ value: '溧水区', label: '溧水区' },
|
||||
{ value: '高淳区', label: '高淳区' }
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '苏州市',
|
||||
label: '苏州市',
|
||||
children: [
|
||||
{ value: '姑苏区', label: '姑苏区' },
|
||||
{ value: '虎丘区', label: '虎丘区' },
|
||||
{ value: '吴中区', label: '吴中区' },
|
||||
{ value: '相城区', label: '相城区' },
|
||||
{ value: '吴江区', label: '吴江区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '四川省',
|
||||
label: '四川省',
|
||||
children: [
|
||||
{
|
||||
value: '成都市',
|
||||
label: '成都市',
|
||||
children: [
|
||||
{ value: '锦江区', label: '锦江区' },
|
||||
{ value: '青羊区', label: '青羊区' },
|
||||
{ value: '金牛区', label: '金牛区' },
|
||||
{ value: '武侯区', label: '武侯区' },
|
||||
{ value: '成华区', label: '成华区' },
|
||||
{ value: '龙泉驿区', label: '龙泉驿区' },
|
||||
{ value: '青白江区', label: '青白江区' },
|
||||
{ value: '新都区', label: '新都区' },
|
||||
{ value: '温江区', label: '温江区' },
|
||||
{ value: '双流区', label: '双流区' },
|
||||
{ value: '郫都区', label: '郫都区' },
|
||||
{ value: '新津区', label: '新津区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -154,11 +154,11 @@
|
||||
<!-- 额外流量信息 -->
|
||||
<div class="extra-traffic-info">
|
||||
<div class="info-item">
|
||||
<span class="label">已使用流量(真)</span>
|
||||
<span class="label">总流量(虚)</span>
|
||||
<span class="value">{{ cardInfo.realUsedFlow || '0.00MB' }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">实际流量</span>
|
||||
<span class="label">已使用量(虚)</span>
|
||||
<span class="value">{{ cardInfo.actualFlow || '0.00MB' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -280,9 +280,9 @@
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="empty-state">
|
||||
<ElCard v-else>
|
||||
<ElEmpty description="请在上方输入ICCID进行查询" />
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -75,9 +75,11 @@
|
||||
clearable
|
||||
style="flex: 1"
|
||||
/>
|
||||
<ElButton v-if="dialogType === 'add'" @click="handleGenerateSeriesCode">
|
||||
生成编码
|
||||
</ElButton>
|
||||
<CodeGeneratorButton
|
||||
v-if="dialogType === 'add'"
|
||||
code-type="series"
|
||||
@generated="handleCodeGenerated"
|
||||
/>
|
||||
</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="系列名称" prop="series_name">
|
||||
@@ -407,6 +409,7 @@
|
||||
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 { formatDateTime } from '@/utils/business/format'
|
||||
import {
|
||||
CommonStatus,
|
||||
@@ -414,7 +417,6 @@
|
||||
frontendStatusToApi,
|
||||
apiStatusToFrontend
|
||||
} from '@/config/constants'
|
||||
import { generateSeriesCode } from '@/utils/codeGenerator'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
defineOptions({ name: 'PackageSeries' })
|
||||
@@ -938,14 +940,13 @@
|
||||
})
|
||||
}
|
||||
|
||||
// 生成系列编码
|
||||
const handleGenerateSeriesCode = () => {
|
||||
form.series_code = generateSeriesCode()
|
||||
// 处理编码生成
|
||||
const handleCodeGenerated = (code: string) => {
|
||||
form.series_code = code
|
||||
// 清除该字段的验证错误提示
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate('series_code')
|
||||
})
|
||||
ElMessage.success('编码生成成功')
|
||||
}
|
||||
|
||||
// 删除套餐系列
|
||||
|
||||
@@ -65,7 +65,18 @@
|
||||
</ElCol>
|
||||
<ElCol :span="12" v-if="dialogType === 'add'">
|
||||
<ElFormItem label="店铺编号" prop="shop_code">
|
||||
<ElInput v-model="formData.shop_code" placeholder="请输入店铺编号" />
|
||||
<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>
|
||||
@@ -73,44 +84,37 @@
|
||||
<ElRow :gutter="20" v-if="dialogType === 'add'">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="上级店铺" prop="parent_id">
|
||||
<ElSelect
|
||||
<ElTreeSelect
|
||||
v-model="formData.parent_id"
|
||||
:data="parentShopTreeData"
|
||||
placeholder="一级店铺可不选"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="searchParentShops"
|
||||
:loading="parentShopLoading"
|
||||
clearable
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
:props="{
|
||||
label: 'shop_name',
|
||||
value: 'id',
|
||||
children: 'children'
|
||||
}"
|
||||
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 label="所在地区" prop="region">
|
||||
<ElCascader
|
||||
v-model="formData.region"
|
||||
:options="regionData"
|
||||
placeholder="请选择省/市/区"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
@change="handleRegionChange"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
@@ -298,7 +302,9 @@
|
||||
ElTag,
|
||||
ElSwitch,
|
||||
ElSelect,
|
||||
ElOption
|
||||
ElOption,
|
||||
ElTreeSelect,
|
||||
ElCascader
|
||||
} from 'element-plus'
|
||||
import type { FormRules } from 'element-plus'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
@@ -308,6 +314,7 @@
|
||||
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'
|
||||
@@ -315,6 +322,7 @@
|
||||
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' })
|
||||
|
||||
@@ -336,7 +344,7 @@
|
||||
const submitLoading = ref(false)
|
||||
const parentShopLoading = ref(false)
|
||||
const parentShopList = ref<ShopResponse[]>([])
|
||||
const searchParentShopList = ref<ShopResponse[]>([])
|
||||
const parentShopTreeData = ref<ShopResponse[]>([])
|
||||
const defaultRoleLoading = ref(false)
|
||||
const defaultRoleList = ref<any[]>([])
|
||||
|
||||
@@ -415,36 +423,19 @@
|
||||
{
|
||||
label: '上级店铺',
|
||||
prop: 'parent_id',
|
||||
type: 'select',
|
||||
options: searchParentShopList.value.map((shop) => ({
|
||||
label: shop.shop_name,
|
||||
value: shop.id
|
||||
})),
|
||||
type: 'tree-select',
|
||||
config: {
|
||||
data: parentShopTreeData.value,
|
||||
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: '请选择店铺层级'
|
||||
checkStrictly: true,
|
||||
renderAfterExpand: false,
|
||||
props: {
|
||||
label: 'shop_name',
|
||||
value: 'id',
|
||||
children: 'children'
|
||||
},
|
||||
placeholder: '请选择上级店铺'
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -463,7 +454,6 @@
|
||||
const columnOptions = [
|
||||
{ label: '店铺名称', prop: 'shop_name' },
|
||||
{ label: '店铺编号', prop: 'shop_code' },
|
||||
{ label: '层级', prop: 'level' },
|
||||
{ label: '所在地区', prop: 'region' },
|
||||
{ label: '联系人', prop: 'contact_name' },
|
||||
{ label: '联系电话', prop: 'contact_phone' },
|
||||
@@ -487,6 +477,12 @@
|
||||
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 || ''
|
||||
@@ -500,6 +496,7 @@
|
||||
formData.shop_name = ''
|
||||
formData.shop_code = ''
|
||||
formData.parent_id = undefined
|
||||
formData.region = []
|
||||
formData.province = ''
|
||||
formData.city = ''
|
||||
formData.district = ''
|
||||
@@ -556,14 +553,6 @@
|
||||
width: 140,
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'level',
|
||||
label: '层级',
|
||||
width: 80,
|
||||
formatter: (row: ShopResponse) => {
|
||||
return h(ElTag, { type: 'info', size: 'small' }, () => `${row.level}级`)
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'region',
|
||||
label: '所在地区',
|
||||
@@ -643,6 +632,7 @@
|
||||
shop_name: '',
|
||||
shop_code: '',
|
||||
parent_id: undefined as number | undefined,
|
||||
region: [] as string[], // 省市区级联数据
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
@@ -656,6 +646,28 @@
|
||||
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
|
||||
@@ -683,24 +695,26 @@
|
||||
onMounted(() => {
|
||||
getShopList()
|
||||
loadParentShopList()
|
||||
loadSearchParentShopList()
|
||||
searchDefaultRoles('') // 加载初始默认角色列表
|
||||
})
|
||||
|
||||
// 加载上级店铺列表(用于新增对话框,默认加载20条)
|
||||
// 加载上级店铺列表(用于新增对话框和搜索栏,获取所有店铺构建树形结构)
|
||||
const loadParentShopList = async (shopName?: string) => {
|
||||
parentShopLoading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
page: 1,
|
||||
pageSize: 20
|
||||
page_size: 9999 // 获取所有数据用于构建树形结构
|
||||
}
|
||||
if (shopName) {
|
||||
params.shop_name = shopName
|
||||
}
|
||||
const res = await ShopService.getShops(params)
|
||||
if (res.code === 0) {
|
||||
parentShopList.value = res.data.items || []
|
||||
const items = res.data.items || []
|
||||
parentShopList.value = items
|
||||
// 构建树形数据
|
||||
parentShopTreeData.value = buildTreeData(items)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取上级店铺列表失败:', error)
|
||||
@@ -709,43 +723,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 加载搜索栏上级店铺列表(默认加载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
|
||||
|
||||
Reference in New Issue
Block a user