| | |
| | | createTimeEnd?: string |
| | | } |
| | | |
| | | export interface EvaluationTagListItem { |
| | | createBy?: string |
| | | createTime?: string |
| | | deleted?: boolean |
| | | evaluateDimension?: number |
| | | evaluateDimensionName?: string |
| | | id: number | string |
| | | sortOrder?: number |
| | | status?: 0 | 1 |
| | | statusName?: string |
| | | tagContent?: string |
| | | updateBy?: string |
| | | updateTime?: string |
| | | } |
| | | |
| | | export interface SaveOrUpdateEvaluationTagData { |
| | | evaluateDimension: 1 | 2 | 3 |
| | | id?: number | string |
| | |
| | | }>>('/operation/api/v1/evaluateTag/pageList', data) |
| | | } |
| | | |
| | | export function fetchAllEvaluationTags() { |
| | | return request.Post<Service.ResponseResult<EvaluationTagListItem[]>>('/operation/api/v1/evaluateTag/getAllXpOrderEvaluateTag') |
| | | } |
| | | |
| | | export function saveOrUpdateEvaluationTag(data: SaveOrUpdateEvaluationTagData) { |
| | | return request.Post<Service.ResponseResult<any>>('/operation/api/v1/evaluateTag/saveOrUpdate', data) |
| | | } |
| | |
| | | watch, |
| | | } from 'vue' |
| | | import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router' |
| | | import { usePermission } from '@/hooks/usePermission' |
| | | import { |
| | | checkPointNameDuplicate, |
| | | createPoint, |
| | |
| | | const router = useRouter() |
| | | const route = useRoute() |
| | | const workbenchStore = useWorkbenchStore() |
| | | const { hasPermission } = usePermission() |
| | | const { VITE_MAP_KEY, VITE_MAP_SECURITY } = import.meta.env; |
| | | |
| | | (window as any)._AMapSecurityConfig = { |
| | |
| | | |
| | | // 正式工作台当前只保留场站新增入口,点位和围栏创建能力先关闭。 |
| | | const availableCreateToolOptions = computed(() => |
| | | createToolOptions.filter(item => item.value === 'station'), |
| | | createToolOptions.filter((item) => { |
| | | if (item.value !== 'station') |
| | | return false |
| | | return !item.permissionCode || hasPermission(item.permissionCode) |
| | | }), |
| | | ) |
| | | const shouldShowCreateTool = computed(() => availableCreateToolOptions.value.length > 0) |
| | | |
| | | const selectedCreateToolOption = computed(() => { |
| | | return ( |
| | |
| | | } |
| | | |
| | | async function toggleCreateMenu() { |
| | | if (!shouldShowCreateTool.value) { |
| | | showCreateMenu.value = false |
| | | return |
| | | } |
| | | if (createToolDisabledReason.value) { |
| | | if (isCreateWorkflowActive.value) { |
| | | await handleDisabledCreateToolTrigger() |
| | |
| | | } |
| | | |
| | | async function handleCreateByType(type: WorkbenchTab) { |
| | | if (!availableCreateToolOptions.value.some(item => item.value === type)) { |
| | | showCreateMenu.value = false |
| | | return |
| | | } |
| | | if (createToolDisabledReason.value) { |
| | | showCreateMenu.value = false |
| | | return |
| | |
| | | }}</span> |
| | | </div> |
| | | |
| | | <div class="tool-menu-wrapper coverage"> |
| | | <div v-if="shouldShowCreateTool" class="tool-menu-wrapper coverage"> |
| | | <div |
| | | class="operate-item" |
| | | :class="{ active: showLayerMenu }" |
| | |
| | | value: WorkbenchTab; |
| | | icon: string; |
| | | activeIcon: string; |
| | | permissionCode?: string | number; |
| | | }; |
| | | |
| | | // 左侧 tab 切换按钮配置。 |
| | |
| | | value: "station", |
| | | icon: createStationDefaultIcon, |
| | | activeIcon: createPointActiveIcon, |
| | | permissionCode: 202642929, |
| | | }, |
| | | ]; |
| | | |
| | |
| | | watch, |
| | | } from 'vue' |
| | | import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router' |
| | | import { usePermission } from '@/hooks/usePermission' |
| | | import { |
| | | addFence, |
| | | addStation, |
| | |
| | | const router = useRouter() |
| | | const route = useRoute() |
| | | const workbenchStore = useWorkbenchStore() |
| | | const { hasPermission } = usePermission() |
| | | const { VITE_MAP_KEY, VITE_MAP_SECURITY } = import.meta.env; |
| | | |
| | | (window as any)._AMapSecurityConfig = { |
| | |
| | | // 恢复左侧场站 tab 展示;新建场站入口仍通过 createToolOptions 过滤,暂不开放。 |
| | | const visibleTabOptions = computed(() => tabOptions) |
| | | const visibleCreateToolOptions = computed(() => |
| | | createToolOptions.filter(item => item.value !== 'station'), |
| | | createToolOptions.filter((item) => { |
| | | if (item.value === 'station') |
| | | return false |
| | | return !item.permissionCode || hasPermission(item.permissionCode) |
| | | }), |
| | | ) |
| | | const shouldShowCreateTool = computed(() => visibleCreateToolOptions.value.length > 0) |
| | | |
| | | const selectedCreateToolOption = computed(() => { |
| | | return ( |
| | |
| | | } |
| | | |
| | | async function toggleCreateMenu() { |
| | | if (!shouldShowCreateTool.value) { |
| | | showCreateMenu.value = false |
| | | return |
| | | } |
| | | if (createToolDisabledReason.value) { |
| | | if (pointCreateMode.value || fenceCreateMode.value || stationCreateMode.value) { |
| | | await handleDisabledCreateToolTrigger() |
| | |
| | | } |
| | | |
| | | async function handleCreateByType(type: WorkbenchTab) { |
| | | if (!visibleCreateToolOptions.value.some(item => item.value === type)) { |
| | | showCreateMenu.value = false |
| | | return |
| | | } |
| | | if (createToolDisabledReason.value) { |
| | | showCreateMenu.value = false |
| | | return |
| | |
| | | }}</span> |
| | | </div> |
| | | |
| | | <div class="tool-menu-wrapper coverage"> |
| | | <div v-if="shouldShowCreateTool" class="tool-menu-wrapper coverage"> |
| | | <div |
| | | class="operate-item" |
| | | :class="{ active: showLayerMenu }" |
| | |
| | | value: WorkbenchTab; |
| | | icon: string; |
| | | activeIcon: string; |
| | | permissionCode?: string | number; |
| | | }; |
| | | |
| | | // 左侧 tab 切换按钮配置。 |
| | |
| | | value: "point", |
| | | icon: createPointDefaultIcon, |
| | | activeIcon: createPointActiveIcon, |
| | | permissionCode: 2026061507, |
| | | }, |
| | | { |
| | | label: "新建围栏", |
| | | value: "fence", |
| | | icon: createFenceDefaultIcon, |
| | | activeIcon: createPointActiveIcon, |
| | | permissionCode: 2026061504, |
| | | }, |
| | | { |
| | | label: "新建场站", |
| | |
| | | import type { DataTableColumns } from 'naive-ui' |
| | | import { useBoolean } from '@/hooks' |
| | | import { getOrderEvaluateList } from '@/service/api/transaction' |
| | | import { fetchAllEvaluationTags } from '@/service/api/evaluationTag' |
| | | import { NButton, NImage, NImageGroup } from 'naive-ui' |
| | | import { onlyAllowNumber, onlyAllowNumberAndLetter } from '@/utils' |
| | | import SearchPanel from '@/components/custom/SearchPanel.vue' |
| | |
| | | |
| | | const initialModel = { |
| | | orderRating: null, |
| | | evaluateTagIds: [] as string[], |
| | | phoneNumber: '', |
| | | plateNumber: '', |
| | | time: null, |
| | |
| | | } |
| | | |
| | | const isElectronicAreaIdOpen = ref(false) |
| | | const isEvaluateTagOpen = ref(false) |
| | | const evaluateTagOptions = ref<Array<{ label: string, value: string }>>([]) |
| | | const evaluateTagOptionsLoading = ref(false) |
| | | |
| | | function normalizeEvaluationTagOptions(source: any[]) { |
| | | return source |
| | | .filter(item => item?.id !== undefined && item?.id !== null && (item?.tagContent || item?.labelContent)) |
| | | .sort((a, b) => Number(a?.sortOrder || 0) - Number(b?.sortOrder || 0)) |
| | | .map(item => ({ |
| | | label: item.tagContent || item.labelContent, |
| | | value: String(item.id), |
| | | })) |
| | | } |
| | | |
| | | async function loadEvaluationTagOptions() { |
| | | evaluateTagOptionsLoading.value = true |
| | | try { |
| | | const res = await fetchAllEvaluationTags() |
| | | const list = Array.isArray(res?.data) |
| | | ? res.data |
| | | : Array.isArray(res?.data?.records) |
| | | ? res.data.records |
| | | : [] |
| | | evaluateTagOptions.value = normalizeEvaluationTagOptions(list) |
| | | } catch (error) { |
| | | console.error('加载评价标签下拉选项失败:', error) |
| | | } finally { |
| | | evaluateTagOptionsLoading.value = false |
| | | } |
| | | } |
| | | |
| | | const columns = computed<DataTableColumns<any>>(() => [ |
| | | { |
| | |
| | | title: '评价标签', |
| | | key: 'feedbackType', |
| | | resizable: true, |
| | | width: 200, |
| | | width: 320, |
| | | render: (row: any) => { |
| | | // 兼容处理:支持数组或逗号分隔的字符串 |
| | | let tags: string[] = [] |
| | |
| | | ), |
| | | }, |
| | | { |
| | | label: '评价标签', |
| | | path: 'evaluateTagIds', |
| | | render: () => ( |
| | | <n-select |
| | | v-model:value={model.value.evaluateTagIds} |
| | | onUpdate:show={(val: boolean) => isEvaluateTagOpen.value = val} |
| | | options={evaluateTagOptions.value} |
| | | multiple |
| | | filterable |
| | | max-tag-count="responsive" |
| | | clearable |
| | | loading={evaluateTagOptionsLoading.value} |
| | | > |
| | | {{ |
| | | arrow: () => ( |
| | | <img |
| | | src="https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/e120f81c-9f15-4e1a-9938-22252898edf9.png" |
| | | class={`arrow-icon ${isEvaluateTagOpen.value ? 'arrow-icon--open' : ''}`} |
| | | style={{ width: '16px', height: '16px' }} |
| | | alt="arrow" |
| | | /> |
| | | ), |
| | | empty: () => ( |
| | | <div class="flex-center"> |
| | | <img |
| | | src={noDataImg} |
| | | style={{ width: '50%' }} |
| | | /> |
| | | </div> |
| | | ), |
| | | }} |
| | | </n-select> |
| | | ), |
| | | }, |
| | | { |
| | | label: '服务车辆', |
| | | path: 'plateNumber', |
| | | render: () => <n-input v-model:value={model.value.plateNumber} />, |
| | |
| | | const params = { |
| | | ...pageParams.value, |
| | | orderRating: model.value.orderRating, |
| | | evaluateTagIds: model.value.evaluateTagIds.length ? model.value.evaluateTagIds.join(',') : undefined, |
| | | phoneNumber: model.value.phoneNumber, |
| | | plateNumber: model.value.plateNumber, |
| | | nickName: model.value.nickName, |
| | |
| | | } |
| | | |
| | | onMounted(() => { |
| | | void loadEvaluationTagOptions() |
| | | getList() |
| | | }) |
| | | |
| | |
| | | <div class="flex-1 flex flex-col h-full"> |
| | | <n-data-table |
| | | class="flex-1" :columns="columns" :data="listData" :loading="loading" flex-height |
| | | :bordered="false" :header-height="18" :row-height="18" :scroll-x="1800" |
| | | :bordered="false" :header-height="18" :row-height="18" :scroll-x="1920" |
| | | > |
| | | <template #empty> |
| | | <div class="flex-center"> |