优化目录

This commit is contained in:
zwgczx committed 2026-03-17 12:05:22 +08:00
1 parent 4e343cccf1
commit 2b18fe9991
46 files changed
+1061 -1308

No files matched your search

+320
View File
@@ -0,0 +1,320 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { AgGridVue } from 'ag-grid-vue3'
import type {
CellValueChangedEvent,
ColDef,
GridApi,
GridReadyEvent,
ICellRendererParams,
ValueFormatterParams
} from 'ag-grid-community'
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
import { Button } from '@/components/ui/button'
import { myTheme } from '@/lib/diyAgGridOptions'
import { workList } from '@/sql'
import type { WorkType } from '@/sql'
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
interface WorkContentRow {
id: string
content: string
type: WorkType
remark: string
checked: boolean
custom: boolean
path: string[]
}
interface WorkContentState {
detailRows: WorkContentRow[]
}
// dictMode: 'service' 按serviceId筛选workList;'additional' 取serviceid=-1的数据;'none' 不加载词典
const props = withDefaults(defineProps<{
title?: string
storageKey: string
serviceId?: number | string
dictMode?: 'service' | 'additional' | 'none'
}>(), {
title: '工作内容',
dictMode: 'none'
})
const emit = defineEmits<{
checkedChange: [value: string[]]
}>()
const zxFwPricingStore = useZxFwPricingStore()
const gridApi = ref<GridApi<WorkContentRow> | null>(null)
const rowData = ref<WorkContentRow[]>([])
const TYPE_LABEL_MAP: Record<number, WorkType> = {
0: '基本工作',
1: '可选工作',
2: '日常顾问',
3: '专项顾问',
4: '附加工作'
}
const buildDefaultRowsFromDict = (): WorkContentRow[] => {
const rows: WorkContentRow[] = []
const entries = Object.values(workList) as Array<{ text: string; serviceid: number; order: number; type: number }>
let filtered: typeof entries
if (props.dictMode === 'service') {
const sid = Number(props.serviceId)
filtered = entries.filter(e => e.serviceid === sid)
} else if (props.dictMode === 'additional') {
filtered = entries.filter(e => e.serviceid === -1)
} else {
return []
}
filtered.sort((a, b) => a.order - b.order)
for (const entry of filtered) {
const content = String(entry.text || '').trim()
if (!content) continue
const typeLabel = TYPE_LABEL_MAP[entry.type] ?? '基本工作'
rows.push({
id: `dict-${entry.order}`,
content,
type: typeLabel,
remark: '',
checked: false,
custom: false,
path: [typeLabel, content]
})
}
return rows
}
const checkedIds = computed(() =>
rowData.value.filter(item => item.checked).map(item => item.id)
)
// 导出用:自定义内容全部包含,默认词典内容只含勾选的
const selectedTexts = computed(() =>
rowData.value
.filter(item => item.custom || item.checked)
.map(item => item.content)
.filter(Boolean)
)
defineExpose({ selectedTexts })
const emitCheckedChange = () => {
emit('checkedChange', [...checkedIds.value])
}
const saveToStore = () => {
const payload: WorkContentState = {
detailRows: rowData.value.map(item => ({ ...item }))
}
zxFwPricingStore.setKeyState(props.storageKey, payload)
emitCheckedChange()
}
const loadFromStore = async () => {
const state = await zxFwPricingStore.loadKeyState<WorkContentState>(props.storageKey)
if (Array.isArray(state?.detailRows) && state.detailRows.length > 0) {
rowData.value = state.detailRows.map(item => ({
...item,
type: item.custom ? '自定义' : (item.type || '基本工作'),
path: Array.isArray(item.path) && item.path.length ? item.path : ['自定义', item.content || '未命名']
})) as WorkContentRow[]
} else {
rowData.value = buildDefaultRowsFromDict()
saveToStore()
}
emitCheckedChange()
}
const handleCheckedToggle = (id: string, checked: boolean) => {
const target = rowData.value.find(item => item.id === id)
if (!target) return
target.checked = checked
gridApi.value?.refreshCells({ force: true })
saveToStore()
}
const contentCellRenderer = (params: ICellRendererParams<WorkContentRow>) => {
const data = params.data
if (!data) return ''
const wrapper = document.createElement('div')
wrapper.className = 'work-content-cell'
// 自定义行不显示 checkbox,直接显示文本;空时显示 placeholder
if (data.custom) {
const label = document.createElement('span')
if (!data.content) {
label.className = 'work-content-placeholder'
label.textContent = '点击输入工作内容'
} else {
label.className = 'work-content-text'
label.textContent = data.content
}
wrapper.appendChild(label)
return wrapper
}
const checkbox = document.createElement('input')
checkbox.type = 'checkbox'
checkbox.className = 'work-content-check'
checkbox.checked = Boolean(data.checked)
checkbox.addEventListener('change', () => {
handleCheckedToggle(data.id, checkbox.checked)
})
const label = document.createElement('span')
label.className = 'work-content-text'
label.textContent = String(data.content || '')
wrapper.appendChild(checkbox)
wrapper.appendChild(label)
return wrapper
}
const columnDefs: ColDef<WorkContentRow>[] = [
{
headerName: '序号',
minWidth: 60,
width: 70,
pinned: 'left',
suppressMovable: true,
editable: false,
valueGetter: params => (params.node?.rowIndex ?? 0) + 1
},
{
headerName: '工作内容',
field: 'content',
minWidth: 320,
flex: 2,
editable: params => Boolean(params.data?.custom),
cellClass: params => (params.data?.custom ? 'editable-cell-line' : ''),
valueParser: params => String(params.newValue || '').trim(),
cellRenderer: contentCellRenderer
},
{
headerName: '工作类型',
field: 'type',
minWidth: 100,
width: 120,
editable: false,
valueFormatter: (params: ValueFormatterParams<WorkContentRow>) => String(params.value || '')
},
{
headerName: '备注',
field: 'remark',
minWidth: 180,
flex: 1.2,
editable: true,
cellEditor: 'agLargeTextCellEditor',
wrapText: true,
autoHeight: true,
cellStyle: { whiteSpace: 'normal', lineHeight: '1.4' },
cellClass: 'editable-cell-line remark-wrap-cell',
cellClassRules: {
'editable-cell-empty': params => params.value == null || params.value === ''
},
valueFormatter: params => params.value || '点击输入'
}
]
const addCustomRow = () => {
const ts = Date.now()
rowData.value.push({
id: `custom-${ts}`,
content: '',
type: '自定义' as WorkType,
remark: '',
checked: false,
custom: true,
path: ['自定义', `自定义-${ts}`]
})
saveToStore()
}
const onGridReady = (event: GridReadyEvent<WorkContentRow>) => {
gridApi.value = event.api
}
const onCellValueChanged = (event: CellValueChangedEvent<WorkContentRow>) => {
const row = event.data
if (!row) return
if (event.colDef.field === 'content' && row.custom) {
row.path = ['自定义', row.content || `自定义-${row.id}`]
}
if (event.colDef.field === 'type' && row.custom) {
row.type = '自定义'
}
saveToStore()
}
onMounted(() => {
void loadFromStore()
})
onBeforeUnmount(() => {
saveToStore()
})
</script>
<template>
<div class="h-full min-h-0 xmMx">
<div class="h-full min-h-0 rounded-2xl border border-border/60 bg-card/90 shadow-sm backdrop-blur-sm">
<div class="flex items-center justify-between border-b border-border/60 px-4 py-3">
<h3 class="text-sm font-semibold text-foreground">{{ props.title }}</h3>
<Button type="button" size="sm" variant="outline" class="cursor-pointer" @click="addCustomRow">
添加自定义内容
</Button>
</div>
<div class="ag-theme-quartz h-[calc(100%-56px)] min-h-0 w-full">
<AgGridVue
:style="{ height: '100%' }"
:rowData="rowData"
:columnDefs="columnDefs"
:theme="myTheme"
:getRowId="(params: { data: WorkContentRow }) => params.data.id"
:animateRows="true"
:localeText="AG_GRID_LOCALE_CN"
:tooltipShowDelay="500"
:singleClickEdit="true"
:stopEditingWhenCellsLoseFocus="true"
:defaultColDef="{ resizable: true, sortable: false, filter: false }"
:suppressColumnVirtualisation="false"
:suppressRowVirtualisation="false"
@grid-ready="onGridReady"
@cell-value-changed="onCellValueChanged"
/>
</div>
</div>
</div>
</template>
<style scoped>
:deep(.work-content-placeholder) {
color: #94a3b8;
font-style: italic;
min-width: 0;
flex: 1;
}
:deep(.work-content-cell) {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
gap: 8px;
}
:deep(.work-content-text) {
min-width: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(.work-content-check) {
width: 14px;
height: 14px;
cursor: pointer;
}
</style>