refactor: 合并 biz_support_unit/biz_execution_unit/biz_service_org 为 biz_org, 删除 2 张孤儿表, 改 biz_person.work_unit → org_id FK
主要改动: - SQL: biz_support_unit → biz_org, 加 org_type, 加 business_nature; 删 biz_execution_unit, biz_service_org - SQL: biz_project.support_unit_id/name + service_org_id/name → org_id/name/type - SQL: biz_meeting.support_unit_name → org_name - SQL: biz_person.work_unit → org_id (FK) - 后端: 新 BizOrg entity/mapper/service/controller - 后端: BizProject/BizMeeting 字段重命名 - 后端: 删 12 个 BizSupportUnit/BizExecutionUnit/BizServiceOrg Java 文件 - 后端: BizPersonImportVO.workUnit → orgName (导入时查 biz_org 取 org_id) - 后端: BizAuthController.registerExecutor 完整实现 (原 registerSupplier stub) - 前端: 新 admin/Orgs.vue + manager/Orgs.vue (原 SupportUnits) - 前端: RegisterExecutor.vue (原 RegisterSupplier, 单页 2 步) - 前端: sponsor/executor/manager 多个文件 workUnit → orgId/orgName 重命名 - 前端: 统一 supplier → executor, 业务命名 sponsor(赞助方) / executor(执行方=供应商) - 前端: 全工程 execution → executor (company type / role / person unit_type)
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<el-cascader
|
||||
v-model="selected"
|
||||
:options="options"
|
||||
:props="cascaderProps"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:clearable="clearable"
|
||||
:filterable="filterable"
|
||||
:show-all-levels="showAllLevels"
|
||||
:collapse-tags="collapseTags"
|
||||
:separator="separator"
|
||||
class="area-cascader"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import areaData from '@/utils/areaData.json'
|
||||
|
||||
/**
|
||||
* 公共地区控件 (el-cascader 封装)
|
||||
* 3 级: 省 / 市 / 区 (基于 china-area-data 标准行政区划)
|
||||
*
|
||||
* 默认 v-model = 地区编码数组 ['110000', '110100', '110101']
|
||||
* 设置 format="string" 时 v-model = joinSep 拼接的字符串, 例 '北京市/北京市/东城区'
|
||||
* (用于直接存 varchar 列, 避免 JSON 序列化)
|
||||
*
|
||||
* 用法:
|
||||
* <area-cascader v-model="form.region" placeholder="请选择地区" />
|
||||
* <area-cascader v-model="form.region" format="string" join-sep="/" />
|
||||
*/
|
||||
const props = defineProps({
|
||||
modelValue: { type: [Array, String], default: () => [] },
|
||||
placeholder: { type: String, default: '请选择省/市/区' },
|
||||
disabled: { type: Boolean, default: false },
|
||||
clearable: { type: Boolean, default: true },
|
||||
filterable: { type: Boolean, default: true },
|
||||
showAllLevels: { type: Boolean, default: true },
|
||||
collapseTags: { type: Boolean, default: false },
|
||||
separator: { type: String, default: '/' },
|
||||
format: { type: String, default: 'array' }, // 'array' | 'string'
|
||||
joinSep: { type: String, default: '/' },
|
||||
props: { type: Object, default: () => ({}) }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
const areaMap = new Map() // value -> {label, parent}
|
||||
const labelToValue = new Map()
|
||||
;(function buildMap(list, parent) {
|
||||
for (const item of list) {
|
||||
areaMap.set(item.value, { label: item.label, parent })
|
||||
labelToValue.set(item.label, item.value)
|
||||
if (item.children) buildMap(item.children, item.value)
|
||||
}
|
||||
})(areaData, null)
|
||||
|
||||
// 把 value 数组翻译成 label 数组
|
||||
function toLabels(values) {
|
||||
if (!Array.isArray(values)) return []
|
||||
return values.map(v => areaMap.get(v)?.label || '')
|
||||
}
|
||||
|
||||
// 把 label 数组翻译成 value 数组 (用于字符串回填)
|
||||
function toValues(labels) {
|
||||
if (!Array.isArray(labels)) return []
|
||||
return labels.map(l => labelToValue.get(l) || '')
|
||||
}
|
||||
|
||||
// 字符串 "北京市/北京市/东城区" → value 数组
|
||||
function stringToValues(str) {
|
||||
if (typeof str !== 'string' || !str) return []
|
||||
const labels = str.split(props.joinSep)
|
||||
return toValues(labels)
|
||||
}
|
||||
|
||||
// 静态引用, 组件树只在加载时构建一次
|
||||
const options = areaData
|
||||
|
||||
const cascaderProps = computed(() => ({
|
||||
value: 'value',
|
||||
label: 'label',
|
||||
children: 'children',
|
||||
expandTrigger: 'hover',
|
||||
checkStrictly: false,
|
||||
multiple: false,
|
||||
...props.props
|
||||
}))
|
||||
|
||||
// 内部 selected 永远是数组 (el-cascader 要求)
|
||||
const selected = ref([])
|
||||
let inited = false
|
||||
|
||||
function syncFromModel() {
|
||||
if (!inited) {
|
||||
// 首次挂载: 从 modelValue 初始化 selected
|
||||
if (props.format === 'string') {
|
||||
if (Array.isArray(props.modelValue)) {
|
||||
selected.value = props.modelValue
|
||||
} else if (typeof props.modelValue === 'string' && props.modelValue) {
|
||||
selected.value = stringToValues(props.modelValue)
|
||||
} else {
|
||||
selected.value = []
|
||||
}
|
||||
} else {
|
||||
selected.value = Array.isArray(props.modelValue) ? props.modelValue : []
|
||||
}
|
||||
inited = true
|
||||
}
|
||||
// 首次挂载之后, 父传回的 modelValue 不再覆盖 selected (selected 由 el-cascader 内部维护)
|
||||
// 但父可能传新值 (loadExpertProfile 等),需要重新解析
|
||||
// 简化策略: 父传空字符串 → 清空; 父传非空且跟当前不同 → 重新解析
|
||||
if (props.format === 'string') {
|
||||
if (typeof props.modelValue === 'string' && !props.modelValue) {
|
||||
selected.value = []
|
||||
} else if (typeof props.modelValue === 'string' && props.modelValue) {
|
||||
const newVals = stringToValues(props.modelValue)
|
||||
// 只有当跟当前 selected 真的不一样时, 才更新
|
||||
if (JSON.stringify(newVals) !== JSON.stringify(selected.value)) {
|
||||
selected.value = newVals
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (Array.isArray(props.modelValue)) {
|
||||
selected.value = props.modelValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, () => syncFromModel(), { immediate: true })
|
||||
|
||||
function handleChange(val) {
|
||||
selected.value = val || []
|
||||
if (props.format === 'string') {
|
||||
const labels = toLabels(val || [])
|
||||
const str = labels.filter(Boolean).join(props.joinSep)
|
||||
emit('update:modelValue', str)
|
||||
emit('change', str)
|
||||
} else {
|
||||
emit('update:modelValue', val || [])
|
||||
emit('change', val || [])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.area-cascader {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user