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 ArtSearchSelect from './widget/art-search-select/index.vue'
|
||||||
import ArtSearchRadio from './widget/art-search-radio/index.vue'
|
import ArtSearchRadio from './widget/art-search-radio/index.vue'
|
||||||
import ArtSearchDate from './widget/art-search-date/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'
|
import { SearchComponentType, SearchFormItem } from '@/types'
|
||||||
|
|
||||||
const { width } = useWindowSize()
|
const { width } = useWindowSize()
|
||||||
@@ -131,6 +132,7 @@
|
|||||||
const componentsMap: Record<string, any> = {
|
const componentsMap: Record<string, any> = {
|
||||||
input: ArtSearchInput,
|
input: ArtSearchInput,
|
||||||
select: ArtSearchSelect,
|
select: ArtSearchSelect,
|
||||||
|
'tree-select': ArtSearchTreeSelect,
|
||||||
radio: ArtSearchRadio,
|
radio: ArtSearchRadio,
|
||||||
datetime: ArtSearchDate,
|
datetime: ArtSearchDate,
|
||||||
date: 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'
|
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 {
|
export interface ColumnOption {
|
||||||
type?: 'selection' | 'expand' | 'index'
|
type?: 'selection' | 'expand' | 'index'
|
||||||
@@ -76,6 +93,27 @@ export function useCheckedColumns(columnsFactory: () => ColumnOption[]) {
|
|||||||
const columnMap = new Map<string, ColumnOption>()
|
const columnMap = new Map<string, ColumnOption>()
|
||||||
|
|
||||||
cols.forEach((column) => {
|
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') {
|
if (column.type === 'selection') {
|
||||||
columnMap.set(SELECTION_KEY, column)
|
columnMap.set(SELECTION_KEY, column)
|
||||||
} else if (column.type === 'expand') {
|
} else if (column.type === 'expand') {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Option } from '../common'
|
|||||||
export type SearchComponentType =
|
export type SearchComponentType =
|
||||||
| 'input'
|
| 'input'
|
||||||
| 'select'
|
| 'select'
|
||||||
|
| 'tree-select'
|
||||||
| 'radio'
|
| 'radio'
|
||||||
| 'checkbox'
|
| 'checkbox'
|
||||||
| 'date'
|
| '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']
|
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']
|
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']
|
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']
|
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']
|
ArtSidebarMenu: typeof import('./../components/core/layouts/art-menus/art-sidebar-menu/index.vue')['default']
|
||||||
ArtStatsCard: typeof import('./../components/core/cards/ArtStatsCard.vue')['default']
|
ArtStatsCard: typeof import('./../components/core/cards/ArtStatsCard.vue')['default']
|
||||||
ArtTable: typeof import('./../components/core/tables/ArtTable.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']
|
ArtTableFullScreen: typeof import('./../components/core/tables/ArtTableFullScreen.vue')['default']
|
||||||
ArtTableHeader: typeof import('./../components/core/tables/ArtTableHeader.vue')['default']
|
ArtTableHeader: typeof import('./../components/core/tables/ArtTableHeader.vue')['default']
|
||||||
ArtTextScroll: typeof import('./../components/core/text-effect/ArtTextScroll.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']
|
BoxStyleSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/BoxStyleSettings.vue')['default']
|
||||||
CardOperationDialog: typeof import('./../components/business/CardOperationDialog.vue')['default']
|
CardOperationDialog: typeof import('./../components/business/CardOperationDialog.vue')['default']
|
||||||
CardStatusTag: typeof import('./../components/business/CardStatusTag.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']
|
ColorSettings: typeof import('./../components/core/layouts/art-settings-panel/widget/ColorSettings.vue')['default']
|
||||||
CommentItem: typeof import('./../components/custom/comment-widget/widget/CommentItem.vue')['default']
|
CommentItem: typeof import('./../components/custom/comment-widget/widget/CommentItem.vue')['default']
|
||||||
CommentWidget: typeof import('./../components/custom/comment-widget/index.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']
|
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||||
ElCalendar: typeof import('element-plus/es')['ElCalendar']
|
ElCalendar: typeof import('element-plus/es')['ElCalendar']
|
||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
|
ElCascader: typeof import('element-plus/es')['ElCascader']
|
||||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
|
|||||||
@@ -45,3 +45,25 @@ export function generatePackageCode(): string {
|
|||||||
|
|
||||||
return `PKG${year}${month}${day}${hours}${minutes}${seconds}${randomChars}`
|
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="extra-traffic-info">
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="label">已使用流量(真)</span>
|
<span class="label">总流量(虚)</span>
|
||||||
<span class="value">{{ cardInfo.realUsedFlow || '0.00MB' }}</span>
|
<span class="value">{{ cardInfo.realUsedFlow || '0.00MB' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="label">实际流量</span>
|
<span class="label">已使用量(虚)</span>
|
||||||
<span class="value">{{ cardInfo.actualFlow || '0.00MB' }}</span>
|
<span class="value">{{ cardInfo.actualFlow || '0.00MB' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -280,9 +280,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<div v-else class="empty-state">
|
<ElCard v-else>
|
||||||
<ElEmpty description="请在上方输入ICCID进行查询" />
|
<ElEmpty description="请在上方输入ICCID进行查询" />
|
||||||
</div>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -75,9 +75,11 @@
|
|||||||
clearable
|
clearable
|
||||||
style="flex: 1"
|
style="flex: 1"
|
||||||
/>
|
/>
|
||||||
<ElButton v-if="dialogType === 'add'" @click="handleGenerateSeriesCode">
|
<CodeGeneratorButton
|
||||||
生成编码
|
v-if="dialogType === 'add'"
|
||||||
</ElButton>
|
code-type="series"
|
||||||
|
@generated="handleCodeGenerated"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="系列名称" prop="series_name">
|
<ElFormItem label="系列名称" prop="series_name">
|
||||||
@@ -407,6 +409,7 @@
|
|||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import CodeGeneratorButton from '@/components/business/CodeGeneratorButton.vue'
|
||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import {
|
import {
|
||||||
CommonStatus,
|
CommonStatus,
|
||||||
@@ -414,7 +417,6 @@
|
|||||||
frontendStatusToApi,
|
frontendStatusToApi,
|
||||||
apiStatusToFrontend
|
apiStatusToFrontend
|
||||||
} from '@/config/constants'
|
} from '@/config/constants'
|
||||||
import { generateSeriesCode } from '@/utils/codeGenerator'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
defineOptions({ name: 'PackageSeries' })
|
defineOptions({ name: 'PackageSeries' })
|
||||||
@@ -938,14 +940,13 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成系列编码
|
// 处理编码生成
|
||||||
const handleGenerateSeriesCode = () => {
|
const handleCodeGenerated = (code: string) => {
|
||||||
form.series_code = generateSeriesCode()
|
form.series_code = code
|
||||||
// 清除该字段的验证错误提示
|
// 清除该字段的验证错误提示
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
formRef.value?.clearValidate('series_code')
|
formRef.value?.clearValidate('series_code')
|
||||||
})
|
})
|
||||||
ElMessage.success('编码生成成功')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除套餐系列
|
// 删除套餐系列
|
||||||
|
|||||||
@@ -65,7 +65,18 @@
|
|||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="12" v-if="dialogType === 'add'">
|
<ElCol :span="12" v-if="dialogType === 'add'">
|
||||||
<ElFormItem label="店铺编号" prop="shop_code">
|
<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>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
@@ -73,44 +84,37 @@
|
|||||||
<ElRow :gutter="20" v-if="dialogType === 'add'">
|
<ElRow :gutter="20" v-if="dialogType === 'add'">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="上级店铺" prop="parent_id">
|
<ElFormItem label="上级店铺" prop="parent_id">
|
||||||
<ElSelect
|
<ElTreeSelect
|
||||||
v-model="formData.parent_id"
|
v-model="formData.parent_id"
|
||||||
|
:data="parentShopTreeData"
|
||||||
placeholder="一级店铺可不选"
|
placeholder="一级店铺可不选"
|
||||||
filterable
|
filterable
|
||||||
remote
|
|
||||||
:remote-method="searchParentShops"
|
|
||||||
:loading="parentShopLoading"
|
|
||||||
clearable
|
clearable
|
||||||
|
check-strictly
|
||||||
|
:render-after-expand="false"
|
||||||
|
:props="{
|
||||||
|
label: 'shop_name',
|
||||||
|
value: 'id',
|
||||||
|
children: 'children'
|
||||||
|
}"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
|
||||||
<ElOption
|
|
||||||
v-for="shop in parentShopList"
|
|
||||||
:key="shop.id"
|
|
||||||
:label="shop.shop_name"
|
|
||||||
:value="shop.id"
|
|
||||||
/>
|
/>
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
|
|
||||||
<ElRow :gutter="20">
|
<ElRow :gutter="20">
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="省份" prop="province">
|
<ElFormItem label="所在地区" prop="region">
|
||||||
<ElInput v-model="formData.province" placeholder="请输入省份" />
|
<ElCascader
|
||||||
</ElFormItem>
|
v-model="formData.region"
|
||||||
</ElCol>
|
:options="regionData"
|
||||||
<ElCol :span="12">
|
placeholder="请选择省/市/区"
|
||||||
<ElFormItem label="城市" prop="city">
|
clearable
|
||||||
<ElInput v-model="formData.city" placeholder="请输入城市" />
|
filterable
|
||||||
</ElFormItem>
|
style="width: 100%"
|
||||||
</ElCol>
|
@change="handleRegionChange"
|
||||||
</ElRow>
|
/>
|
||||||
|
|
||||||
<ElRow :gutter="20">
|
|
||||||
<ElCol :span="12">
|
|
||||||
<ElFormItem label="区县" prop="district">
|
|
||||||
<ElInput v-model="formData.district" placeholder="请输入区县" />
|
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
@@ -298,7 +302,9 @@
|
|||||||
ElTag,
|
ElTag,
|
||||||
ElSwitch,
|
ElSwitch,
|
||||||
ElSelect,
|
ElSelect,
|
||||||
ElOption
|
ElOption,
|
||||||
|
ElTreeSelect,
|
||||||
|
ElCascader
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
import type { FormRules } from 'element-plus'
|
import type { FormRules } from 'element-plus'
|
||||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||||
@@ -308,6 +314,7 @@
|
|||||||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||||||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||||||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||||||
|
import CodeGeneratorButton from '@/components/business/CodeGeneratorButton.vue'
|
||||||
import { ShopService, RoleService } from '@/api/modules'
|
import { ShopService, RoleService } from '@/api/modules'
|
||||||
import type { SearchFormItem } from '@/types'
|
import type { SearchFormItem } from '@/types'
|
||||||
import type { ShopResponse, ShopRoleResponse } from '@/types/api'
|
import type { ShopResponse, ShopRoleResponse } from '@/types/api'
|
||||||
@@ -315,6 +322,7 @@
|
|||||||
import { formatDateTime } from '@/utils/business/format'
|
import { formatDateTime } from '@/utils/business/format'
|
||||||
import { CommonStatus, getStatusText, STATUS_SELECT_OPTIONS } from '@/config/constants'
|
import { CommonStatus, getStatusText, STATUS_SELECT_OPTIONS } from '@/config/constants'
|
||||||
import { RoutesAlias } from '@/router/routesAlias'
|
import { RoutesAlias } from '@/router/routesAlias'
|
||||||
|
import { regionData } from '@/utils/constants/regionData'
|
||||||
|
|
||||||
defineOptions({ name: 'Shop' })
|
defineOptions({ name: 'Shop' })
|
||||||
|
|
||||||
@@ -336,7 +344,7 @@
|
|||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const parentShopLoading = ref(false)
|
const parentShopLoading = ref(false)
|
||||||
const parentShopList = ref<ShopResponse[]>([])
|
const parentShopList = ref<ShopResponse[]>([])
|
||||||
const searchParentShopList = ref<ShopResponse[]>([])
|
const parentShopTreeData = ref<ShopResponse[]>([])
|
||||||
const defaultRoleLoading = ref(false)
|
const defaultRoleLoading = ref(false)
|
||||||
const defaultRoleList = ref<any[]>([])
|
const defaultRoleList = ref<any[]>([])
|
||||||
|
|
||||||
@@ -415,36 +423,19 @@
|
|||||||
{
|
{
|
||||||
label: '上级店铺',
|
label: '上级店铺',
|
||||||
prop: 'parent_id',
|
prop: 'parent_id',
|
||||||
type: 'select',
|
type: 'tree-select',
|
||||||
options: searchParentShopList.value.map((shop) => ({
|
|
||||||
label: shop.shop_name,
|
|
||||||
value: shop.id
|
|
||||||
})),
|
|
||||||
config: {
|
config: {
|
||||||
|
data: parentShopTreeData.value,
|
||||||
clearable: true,
|
clearable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
remote: true,
|
checkStrictly: true,
|
||||||
remoteMethod: handleSearchParentShop,
|
renderAfterExpand: false,
|
||||||
loading: parentShopLoading.value,
|
props: {
|
||||||
placeholder: '请选择或搜索上级店铺'
|
label: 'shop_name',
|
||||||
}
|
value: 'id',
|
||||||
|
children: 'children'
|
||||||
},
|
},
|
||||||
{
|
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: '请选择店铺层级'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -463,7 +454,6 @@
|
|||||||
const columnOptions = [
|
const columnOptions = [
|
||||||
{ label: '店铺名称', prop: 'shop_name' },
|
{ label: '店铺名称', prop: 'shop_name' },
|
||||||
{ label: '店铺编号', prop: 'shop_code' },
|
{ label: '店铺编号', prop: 'shop_code' },
|
||||||
{ label: '层级', prop: 'level' },
|
|
||||||
{ label: '所在地区', prop: 'region' },
|
{ label: '所在地区', prop: 'region' },
|
||||||
{ label: '联系人', prop: 'contact_name' },
|
{ label: '联系人', prop: 'contact_name' },
|
||||||
{ label: '联系电话', prop: 'contact_phone' },
|
{ label: '联系电话', prop: 'contact_phone' },
|
||||||
@@ -487,6 +477,12 @@
|
|||||||
formData.province = row.province || ''
|
formData.province = row.province || ''
|
||||||
formData.city = row.city || ''
|
formData.city = row.city || ''
|
||||||
formData.district = row.district || ''
|
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.address = row.address || ''
|
||||||
formData.contact_name = row.contact_name || ''
|
formData.contact_name = row.contact_name || ''
|
||||||
formData.contact_phone = row.contact_phone || ''
|
formData.contact_phone = row.contact_phone || ''
|
||||||
@@ -500,6 +496,7 @@
|
|||||||
formData.shop_name = ''
|
formData.shop_name = ''
|
||||||
formData.shop_code = ''
|
formData.shop_code = ''
|
||||||
formData.parent_id = undefined
|
formData.parent_id = undefined
|
||||||
|
formData.region = []
|
||||||
formData.province = ''
|
formData.province = ''
|
||||||
formData.city = ''
|
formData.city = ''
|
||||||
formData.district = ''
|
formData.district = ''
|
||||||
@@ -556,14 +553,6 @@
|
|||||||
width: 140,
|
width: 140,
|
||||||
showOverflowTooltip: true
|
showOverflowTooltip: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
prop: 'level',
|
|
||||||
label: '层级',
|
|
||||||
width: 80,
|
|
||||||
formatter: (row: ShopResponse) => {
|
|
||||||
return h(ElTag, { type: 'info', size: 'small' }, () => `${row.level}级`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
prop: 'region',
|
prop: 'region',
|
||||||
label: '所在地区',
|
label: '所在地区',
|
||||||
@@ -643,6 +632,7 @@
|
|||||||
shop_name: '',
|
shop_name: '',
|
||||||
shop_code: '',
|
shop_code: '',
|
||||||
parent_id: undefined as number | undefined,
|
parent_id: undefined as number | undefined,
|
||||||
|
region: [] as string[], // 省市区级联数据
|
||||||
province: '',
|
province: '',
|
||||||
city: '',
|
city: '',
|
||||||
district: '',
|
district: '',
|
||||||
@@ -656,6 +646,28 @@
|
|||||||
default_role_id: undefined as number | undefined
|
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) => {
|
const searchDefaultRoles = async (query: string) => {
|
||||||
defaultRoleLoading.value = true
|
defaultRoleLoading.value = true
|
||||||
@@ -683,24 +695,26 @@
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getShopList()
|
getShopList()
|
||||||
loadParentShopList()
|
loadParentShopList()
|
||||||
loadSearchParentShopList()
|
|
||||||
searchDefaultRoles('') // 加载初始默认角色列表
|
searchDefaultRoles('') // 加载初始默认角色列表
|
||||||
})
|
})
|
||||||
|
|
||||||
// 加载上级店铺列表(用于新增对话框,默认加载20条)
|
// 加载上级店铺列表(用于新增对话框和搜索栏,获取所有店铺构建树形结构)
|
||||||
const loadParentShopList = async (shopName?: string) => {
|
const loadParentShopList = async (shopName?: string) => {
|
||||||
parentShopLoading.value = true
|
parentShopLoading.value = true
|
||||||
try {
|
try {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20
|
page_size: 9999 // 获取所有数据用于构建树形结构
|
||||||
}
|
}
|
||||||
if (shopName) {
|
if (shopName) {
|
||||||
params.shop_name = shopName
|
params.shop_name = shopName
|
||||||
}
|
}
|
||||||
const res = await ShopService.getShops(params)
|
const res = await ShopService.getShops(params)
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
parentShopList.value = res.data.items || []
|
const items = res.data.items || []
|
||||||
|
parentShopList.value = items
|
||||||
|
// 构建树形数据
|
||||||
|
parentShopTreeData.value = buildTreeData(items)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取上级店铺列表失败:', 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 () => {
|
const getShopList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|||||||
Reference in New Issue
Block a user