style(admin): unify dialog layouts and dense readonly presentation
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
import { createRequire } from 'node:module'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { useAuthStore } from '@/core/auth/auth'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { useUnsavedWorkStore } from '@/core/unsaved-work/unsavedWork'
|
||||
import AppDrawer from '@/shared/components/AppDrawer.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import FeeItemsView from './FeeItemsView.vue'
|
||||
import * as api from '../api/platformFeeItems'
|
||||
|
||||
// Preserve the validator constructor interop through Element Plus's supported Node export.
|
||||
const { default: ElementPlus, ElCheckbox, ElDrawer, ElInputNumber, ElSelect } = createRequire(import.meta.url)('element-plus') as typeof import('element-plus')
|
||||
|
||||
vi.mock('../api/platformFeeItems', () => ({ listPlatformFeeItems: vi.fn(), getPlatformFeeItem: vi.fn(), createPlatformFeeItem: vi.fn(), updatePlatformFeeItem: vi.fn(), changePlatformFeeItemStatus: vi.fn(), deletePlatformFeeItem: vi.fn() }))
|
||||
let record: api.PlatformFeeItemResource
|
||||
const privateId = '9223372036854775807'
|
||||
const table = { props: ['data'], template: '<div><slot name="toolbar-actions"/><div class="real-row"><slot v-if="data?.length" name="actions" :row="data[0]"/></div></div>' }
|
||||
function button(label: string, selector = 'button') {
|
||||
const found = Array.from(document.querySelectorAll<HTMLButtonElement>(selector)).find(node => node.textContent?.trim() === label)
|
||||
expect(found, label).toBeDefined(); return found!
|
||||
}
|
||||
async function openView() {
|
||||
const wrapper = mount(FeeItemsView, { attachTo: document.body, global: { plugins: [ElementPlus], stubs: { DataTable: table, QueryToolbar: true } } }); await flushPromises(); return wrapper
|
||||
}
|
||||
async function openEditor(create = false) {
|
||||
const wrapper = await openView(); button(create ? '新增费用项目' : '编辑').click(); await flushPromises(); return wrapper
|
||||
}
|
||||
async function setText(placeholder: string, value: string) {
|
||||
const input = document.querySelector<HTMLInputElement>(`.el-drawer [placeholder="${placeholder}"]`)!
|
||||
input.value = value; input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); await flushPromises()
|
||||
}
|
||||
async function check(wrapper: Awaited<ReturnType<typeof openView>>, label: string, value: boolean) {
|
||||
const checkbox = wrapper.findAllComponents(ElCheckbox).find(node => node.props('ariaLabel') === label)!
|
||||
expect(checkbox, label).toBeDefined(); await checkbox.get('input').setValue(value); await flushPromises()
|
||||
}
|
||||
const currentDialog = (wrapper: Awaited<ReturnType<typeof openView>>) => wrapper.findAllComponents(AppDialog).find(dialog => dialog.props('modelValue'))!
|
||||
enableAutoUnmount(afterEach)
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks(); document.body.innerHTML = ''; setActivePinia(createPinia())
|
||||
const auth = useAuthStore(); auth.token = 'test-session'; auth.permissions = ['firstleg:fee-item:view','firstleg:fee-item:create','firstleg:fee-item:edit','firstleg:fee-item:delete']
|
||||
useAccessPreviewStore().initializeAfterLogin()
|
||||
record = { id: privateId, category: '操作费', tradeScope: 'DOMESTIC', feeCode: 'FEE-TEST', supplierCategory: null, feeTypeName: '测试操作费', defaultPrice: 0, pickupPayable: false, pickupReceivable: true, warehousePayable: false, warehouseReceivable: true, allocationFee: false, description: '适用说明', status: 'ACTIVE' }
|
||||
vi.mocked(api.listPlatformFeeItems).mockImplementation(async () => ({ records: [record], page: 1, size: 20, total: 1, pages: 1 }))
|
||||
vi.mocked(api.getPlatformFeeItem).mockImplementation(async () => record)
|
||||
vi.mocked(api.createPlatformFeeItem).mockImplementation(async () => record)
|
||||
vi.mocked(api.updatePlatformFeeItem).mockImplementation(async () => record)
|
||||
vi.mocked(api.changePlatformFeeItemStatus).mockResolvedValue(null)
|
||||
vi.mocked(api.deletePlatformFeeItem).mockResolvedValue(null)
|
||||
})
|
||||
|
||||
describe('fee item editing and confirmations', () => {
|
||||
it('creates from the original compact drawer with a zero default and all direction flags', async () => {
|
||||
const wrapper = await openEditor(true)
|
||||
expect(wrapper.findComponent(AppDrawer).props()).toMatchObject({ title: '新增费用项目', size: 'min(620px, 94vw)' })
|
||||
expect(document.querySelector('.el-drawer')?.classList.contains('admin-density-drawer--editor')).toBe(true)
|
||||
expect(document.querySelectorAll('.el-drawer .density-dialog-section')).toHaveLength(3)
|
||||
expect(document.querySelector('.el-dialog')).toBeNull()
|
||||
await setText('请输入费用名称', ' 新增操作费 '); await setText('例如 仓储成本', ' 操作费 ')
|
||||
wrapper.findComponent(ElInputNumber).vm.$emit('update:modelValue', 0)
|
||||
await check(wrapper, '提货环节应付', true); await check(wrapper, '提货环节应收', true)
|
||||
await check(wrapper, '仓库环节应付', true); await check(wrapper, '仓库环节应收', true)
|
||||
wrapper.findComponent(ElSelect).vm.$emit('update:modelValue', 'TRUCKING')
|
||||
await wrapper.findAllComponents(ElCheckbox).find(node => node.text() === '作为分摊费用')!.get('input').setValue(true)
|
||||
await setText('请输入适用场景或核算说明', ' 新说明 ')
|
||||
button('保存费用项目', '.el-drawer__footer button').click(); await flushPromises()
|
||||
expect(api.createPlatformFeeItem).toHaveBeenCalledWith({ category: '操作费', tradeScope: 'DOMESTIC', feeTypeName: '新增操作费', defaultPrice: 0, supplierCategory: 'TRUCKING', pickupPayable: true, pickupReceivable: true, warehousePayable: true, warehouseReceivable: true, allocationFee: true, description: '新说明' })
|
||||
expect(useUnsavedWorkStore().entries).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('validates required fields and payable supplier category without losing entered values', async () => {
|
||||
const wrapper = await openEditor(true)
|
||||
button('保存费用项目', '.el-drawer__footer button').click(); await flushPromises()
|
||||
expect(api.createPlatformFeeItem).not.toHaveBeenCalled()
|
||||
await vi.waitFor(() => expect(document.querySelector('.el-drawer')?.textContent).toContain('请输入费用名称'))
|
||||
await setText('请输入费用名称', '新费用'); await setText('例如 仓储成本', '操作费')
|
||||
await check(wrapper, '提货环节应付', true)
|
||||
button('保存费用项目', '.el-drawer__footer button').click(); await flushPromises()
|
||||
expect(api.createPlatformFeeItem).not.toHaveBeenCalled()
|
||||
expect(wrapper.findComponent(AppDrawer).props('modelValue')).toBe(true)
|
||||
expect(document.querySelector<HTMLInputElement>('.el-drawer [placeholder="请输入费用名称"]')!.value).toBe('新费用')
|
||||
})
|
||||
|
||||
it('edits the same long ID and clears the supplier payload when payable directions are removed', async () => {
|
||||
record = { ...record, pickupPayable: true, supplierCategory: 'TRUCKING', defaultPrice: 25 }
|
||||
const wrapper = await openEditor()
|
||||
expect(wrapper.findComponent(AppDrawer).props('title')).toBe('编辑费用项目 · 测试操作费')
|
||||
expect(document.querySelector('.el-drawer')?.textContent).not.toContain(privateId)
|
||||
wrapper.findComponent(ElInputNumber).vm.$emit('update:modelValue', null)
|
||||
await check(wrapper, '提货环节应付', false)
|
||||
expect(document.querySelector('.el-drawer [placeholder="请选择供应商类别"]')).toBeNull()
|
||||
button('保存费用项目', '.el-drawer__footer button').click(); await flushPromises()
|
||||
expect(api.updatePlatformFeeItem).toHaveBeenCalledWith(privateId, expect.objectContaining({ defaultPrice: null, supplierCategory: null, pickupPayable: false }))
|
||||
})
|
||||
|
||||
it('locks every close path during save and preserves the dirty draft after rejection', async () => {
|
||||
let rejectSave!: (error: Error) => void
|
||||
vi.mocked(api.updatePlatformFeeItem).mockImplementation(() => new Promise((_resolve,reject) => { rejectSave = reject }))
|
||||
const wrapper = await openEditor(); await setText('请输入费用名称', '修改后费用')
|
||||
expect(useUnsavedWorkStore().entries[0]).toMatchObject({ key: 'fee-item-editor', dirty: true, requiredPermission: 'firstleg:fee-item:edit' })
|
||||
button('保存费用项目', '.el-drawer__footer button').click(); await flushPromises()
|
||||
expect(wrapper.findComponent(ElDrawer).props()).toMatchObject({ showClose: false, closeOnPressEscape: false, closeOnClickModal: false })
|
||||
expect(button('取消', '.el-drawer__footer button').disabled).toBe(true)
|
||||
rejectSave(new Error('保存失败')); await flushPromises()
|
||||
expect(wrapper.findComponent(AppDrawer).props('modelValue')).toBe(true)
|
||||
expect(document.querySelector<HTMLInputElement>('.el-drawer [placeholder="请输入费用名称"]')!.value).toBe('修改后费用')
|
||||
expect(useUnsavedWorkStore().entries[0]?.dirty).toBe(true)
|
||||
vi.mocked(api.updatePlatformFeeItem).mockResolvedValue(record)
|
||||
button('保存费用项目', '.el-drawer__footer button').click(); await flushPromises()
|
||||
expect(api.updatePlatformFeeItem).toHaveBeenCalledTimes(2)
|
||||
expect(wrapper.findComponent(AppDrawer).props('modelValue')).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps unsaved registration and the existing permission-change closure', async () => {
|
||||
const wrapper = await openEditor()
|
||||
expect(useUnsavedWorkStore().entries[0]?.dirty).toBe(false)
|
||||
await setText('请输入费用名称', '权限变化前的修改')
|
||||
expect(useUnsavedWorkStore().entries[0]?.dirty).toBe(true)
|
||||
useAuthStore().permissions = ['firstleg:fee-item:view']; useAccessPreviewStore().initializeAfterLogin(); await flushPromises()
|
||||
expect(wrapper.findComponent(AppDrawer).props('modelValue')).toBe(false)
|
||||
expect(useUnsavedWorkStore().entries).toHaveLength(0)
|
||||
expect(api.updatePlatformFeeItem).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.each(['ACTIVE','DISABLED'] as const)('identifies the %s status confirmation and retains original status requests', async status => {
|
||||
record.status = status
|
||||
const verb = status === 'ACTIVE' ? '停用' : '启用'
|
||||
const wrapper = await openView(); button(verb, '.real-row button').click(); await flushPromises()
|
||||
expect(currentDialog(wrapper).props('title')).toBe(`${verb}费用项目 · 测试操作费`)
|
||||
expect(currentDialog(wrapper).props('confirmType')).toBe(status === 'ACTIVE' ? 'warning' : 'success')
|
||||
expect(api.changePlatformFeeItemStatus).not.toHaveBeenCalled()
|
||||
button(`确认${verb}`, '.el-dialog__footer button').click(); await flushPromises()
|
||||
expect(api.changePlatformFeeItemStatus).toHaveBeenCalledWith(privateId, status === 'ACTIVE' ? 'DISABLED' : 'ACTIVE')
|
||||
})
|
||||
|
||||
it('keeps a server-rejected delete confirmation open with its original target', async () => {
|
||||
vi.mocked(api.deletePlatformFeeItem).mockRejectedValueOnce(new Error('项目已使用')).mockResolvedValueOnce(null)
|
||||
const wrapper = await openView(); button('删除', '.real-row button').click(); await flushPromises()
|
||||
expect(currentDialog(wrapper).props()).toMatchObject({ title: '删除费用项目 · 测试操作费', confirmType: 'danger' })
|
||||
expect(document.querySelector('.el-dialog')?.textContent).toContain('已被业务单据使用')
|
||||
button('确认删除', '.el-dialog__footer button').click(); await flushPromises()
|
||||
expect(currentDialog(wrapper).props('modelValue')).toBe(true)
|
||||
button('确认删除', '.el-dialog__footer button').click(); await flushPromises()
|
||||
expect(api.deletePlatformFeeItem).toHaveBeenNthCalledWith(2, privateId)
|
||||
expect(wrapper.findAllComponents(AppDialog).every(dialog => !dialog.props('modelValue'))).toBe(true)
|
||||
})
|
||||
|
||||
it('cancels a confirmation without changing status or deleting data', async () => {
|
||||
const wrapper = await openView(); button('停用', '.real-row button').click(); await flushPromises()
|
||||
button('取消', '.el-dialog__footer button').click(); await flushPromises()
|
||||
expect(wrapper.findAllComponents(AppDialog).every(dialog => !dialog.props('modelValue'))).toBe(true)
|
||||
expect(api.changePlatformFeeItemStatus).not.toHaveBeenCalled(); expect(api.deletePlatformFeeItem).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -132,7 +132,8 @@ describe('FeeItemsView public master data and access', () => {
|
||||
expect(source).toContain('appearance="integrated" :stripe="false"')
|
||||
expect(source).toContain(':page-sizes="[20, 50, 100]"')
|
||||
expect(source).toContain('@media (max-width: 720px)')
|
||||
expect(source).toContain('.direction-grid, .form-grid.two-cols { grid-template-columns: 1fr; }')
|
||||
expect(source).toContain('.fee-item-editor-table tr { display: grid; }')
|
||||
expect(source).toContain('class="density-table density-table--editable fee-item-editor-table"')
|
||||
expect(source).not.toContain('pricingMode')
|
||||
expect(source).not.toContain('计价模式')
|
||||
expect(source).not.toContain('固定费用')
|
||||
|
||||
@@ -24,7 +24,7 @@ import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import AppDrawer from '@/shared/components/AppDrawer.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||||
import FormSection from '@/shared/components/FormSection.vue'
|
||||
import InlineHelp from '@/shared/components/InlineHelp.vue'
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
@@ -92,7 +92,7 @@ const rules: FormRules<FeeItemForm> = {
|
||||
const hasPayableDirection = computed(() => form.pickupPayable || form.warehousePayable)
|
||||
|
||||
const canView = computed(() => access.can('firstleg:fee-item:view'))
|
||||
const editorTitle = computed(() => form.id ? '编辑费用项目' : '新增费用项目')
|
||||
const editorTitle = computed(() => form.id && form.feeTypeName.trim() ? `编辑费用项目 · ${form.feeTypeName.trim()}` : form.id ? '编辑费用项目' : '新增费用项目')
|
||||
const editorPermission = computed(() => form.id ? 'firstleg:fee-item:edit' : 'firstleg:fee-item:create')
|
||||
|
||||
const { captureBaseline } = useUnsavedWorkRegistration({
|
||||
@@ -429,46 +429,38 @@ onMounted(() => {
|
||||
</DataTable></section>
|
||||
</ListPageLayout>
|
||||
|
||||
<AppDrawer class="admin-density admin-density-drawer"
|
||||
<AppDrawer class="admin-density admin-density-drawer admin-density-drawer--editor"
|
||||
v-model="editorOpen"
|
||||
:title="editorTitle"
|
||||
size="min(620px, 94vw)"
|
||||
:loading="editorLoading"
|
||||
:confirm-loading="saving"
|
||||
>
|
||||
<el-form class="density-form" ref="formRef" :model="form" :rules="rules" label-position="top" :disabled="saving || editorLoading">
|
||||
<FormSection class="density-form-section" title="基本信息" description="费用项目为平台公共主数据,不按客户单独维护。">
|
||||
<div class="form-grid two-cols">
|
||||
<el-form-item label="费用名称" prop="feeTypeName" required><el-input v-model="form.feeTypeName" maxlength="128" placeholder="请输入费用名称" /></el-form-item>
|
||||
<el-form-item label="费用分类" prop="category" required><el-input v-model="form.category" maxlength="64" placeholder="例如 仓储成本" /></el-form-item>
|
||||
</div>
|
||||
<el-form-item label="贸易范围" prop="tradeScope" required>
|
||||
<el-segmented v-model="form.tradeScope" :options="[{ label: '国内', value: 'DOMESTIC' }, { label: '国际', value: 'INTERNATIONAL' }]" />
|
||||
</el-form-item>
|
||||
<el-form-item label="默认价格(CNY)" prop="defaultPrice">
|
||||
<el-input-number v-model="form.defaultPrice" :min="0" :max="9999999999.99" :precision="2" controls-position="right" placeholder="不填则由使用时手工录入" />
|
||||
</el-form-item>
|
||||
<p class="muted-value">选择费用项目时自动带入,使用时仍可修改;清空表示不设置默认价格。</p>
|
||||
</FormSection>
|
||||
<FormSection class="density-form-section" title="费用方向" description="选择费用在提货和仓库环节的应收、应付属性。">
|
||||
<div class="direction-grid">
|
||||
<fieldset><legend>提货环节</legend><el-checkbox v-model="form.pickupPayable">应付</el-checkbox><el-checkbox v-model="form.pickupReceivable">应收</el-checkbox></fieldset>
|
||||
<fieldset><legend>仓库环节</legend><el-checkbox v-model="form.warehousePayable">应付</el-checkbox><el-checkbox v-model="form.warehouseReceivable">应收</el-checkbox></fieldset>
|
||||
</div>
|
||||
<el-checkbox v-model="form.allocationFee">作为分摊费用</el-checkbox>
|
||||
<el-form-item v-if="hasPayableDirection" label="供应商类别" required>
|
||||
<el-select v-model="form.supplierCategory" placeholder="请选择 Warehouse 供应商类别">
|
||||
<el-option v-for="(label, value) in categoryLabels" :key="value" :label="label" :value="value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</FormSection>
|
||||
<FormSection class="density-form-section" title="适用说明" :bordered="false">
|
||||
<el-form-item label="说明"><el-input v-model="form.description" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="请输入适用场景或核算说明" /></el-form-item>
|
||||
</FormSection>
|
||||
<el-form class="density-form density-dialog-sections" ref="formRef" :model="form" :rules="rules" label-position="left" label-width="100px" :disabled="saving || editorLoading">
|
||||
<section class="density-dialog-section" data-tone="work" aria-label="费用基本信息">
|
||||
<h3 class="density-dialog-section__title">基本信息 <InlineHelp title="默认价格说明">选择费用项目时自动带入,使用时仍可修改;清空表示不设置默认价格。</InlineHelp></h3>
|
||||
<table class="density-table density-table--editable fee-item-editor-table"><tbody>
|
||||
<tr><td><el-form-item label="费用名称" prop="feeTypeName" required><el-input v-model="form.feeTypeName" maxlength="128" placeholder="请输入费用名称" /></el-form-item></td><td><el-form-item label="费用分类" prop="category" required><el-input v-model="form.category" maxlength="64" placeholder="例如 仓储成本" /></el-form-item></td></tr>
|
||||
<tr><td><el-form-item label="贸易范围" prop="tradeScope" required><el-segmented v-model="form.tradeScope" :options="[{ label: '国内', value: 'DOMESTIC' }, { label: '国际', value: 'INTERNATIONAL' }]" /></el-form-item></td><td><el-form-item label="默认价格(CNY)" prop="defaultPrice"><el-input-number v-model="form.defaultPrice" :min="0" :max="9999999999.99" :precision="2" controls-position="right" placeholder="不填则由使用时手工录入" /></el-form-item></td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
<section class="density-dialog-section" data-tone="work" aria-label="费用方向与供应商">
|
||||
<h3 class="density-dialog-section__title">费用方向 <InlineHelp title="费用方向说明">选择费用在提货和仓库环节的应收、应付属性。</InlineHelp></h3>
|
||||
<table class="density-table density-table--editable fee-direction-table"><thead><tr><th scope="col">环节</th><th scope="col">应付</th><th scope="col">应收</th></tr></thead><tbody>
|
||||
<tr><th scope="row">提货环节</th><td><el-checkbox v-model="form.pickupPayable" aria-label="提货环节应付" /></td><td><el-checkbox v-model="form.pickupReceivable" aria-label="提货环节应收" /></td></tr>
|
||||
<tr><th scope="row">仓库环节</th><td><el-checkbox v-model="form.warehousePayable" aria-label="仓库环节应付" /></td><td><el-checkbox v-model="form.warehouseReceivable" aria-label="仓库环节应收" /></td></tr>
|
||||
<tr><td colspan="3"><el-checkbox v-model="form.allocationFee">作为分摊费用</el-checkbox></td></tr>
|
||||
<tr v-if="hasPayableDirection"><td colspan="3"><el-form-item label="供应商类别" required><el-select v-model="form.supplierCategory" placeholder="请选择供应商类别"><el-option v-for="(label, value) in categoryLabels" :key="value" :label="label" :value="value" /></el-select></el-form-item></td></tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
<section class="density-dialog-section" data-tone="work" aria-label="费用适用说明">
|
||||
<h3 class="density-dialog-section__title">适用说明</h3>
|
||||
<table class="density-table density-table--editable"><tbody><tr><td><el-form-item label="说明"><el-input v-model="form.description" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="请输入适用场景或核算说明" /></el-form-item></td></tr></tbody></table>
|
||||
</section>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="drawer-footer">
|
||||
<el-button :disabled="saving" @click="editorOpen = false">取消</el-button>
|
||||
<template #footer="{close}">
|
||||
<div class="density-actions density-actions--footer">
|
||||
<el-button :disabled="saving" @click="close">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" :disabled="editorLoading" @click="saveFeeItem"><Save :size="16" />保存费用项目</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -476,13 +468,13 @@ onMounted(() => {
|
||||
|
||||
<AppDialog class="admin-density-dialog"
|
||||
v-model="statusOpen"
|
||||
:title="statusTarget?.status === 'ACTIVE' ? '停用费用项目' : '启用费用项目'"
|
||||
:title="`${statusTarget?.status === 'ACTIVE' ? '停用' : '启用'}费用项目${statusTarget?.feeTypeName?.trim() ? ' · ' + statusTarget.feeTypeName.trim() : ''}`"
|
||||
:confirm-text="statusTarget?.status === 'ACTIVE' ? '确认停用' : '确认启用'"
|
||||
:confirm-type="statusTarget?.status === 'ACTIVE' ? 'warning' : 'success'"
|
||||
:confirm-loading="statusSaving"
|
||||
@confirm="confirmStatusChange"
|
||||
>
|
||||
<p class="delete-message">
|
||||
<p class="density-dialog-note">
|
||||
确定{{ statusTarget?.status === 'ACTIVE' ? '停用' : '启用' }}“<strong>{{ statusTarget?.feeTypeName }}</strong>”吗?
|
||||
<template v-if="statusTarget?.status === 'ACTIVE'">停用后该项目不能再用于新的费用记录。</template>
|
||||
</p>
|
||||
@@ -490,29 +482,28 @@ onMounted(() => {
|
||||
|
||||
<AppDialog class="admin-density-dialog"
|
||||
v-model="deleteOpen"
|
||||
title="删除费用项目"
|
||||
:title="deleteTarget?.feeTypeName?.trim() ? `删除费用项目 · ${deleteTarget.feeTypeName.trim()}` : '删除费用项目'"
|
||||
confirm-text="确认删除"
|
||||
confirm-type="danger"
|
||||
:confirm-loading="deleting"
|
||||
@confirm="confirmDelete"
|
||||
>
|
||||
<p class="delete-message">确定删除“<strong>{{ deleteTarget?.feeTypeName }}</strong>”吗?删除后无法恢复;已被业务单据使用的项目将由服务端拒绝删除。</p>
|
||||
<p class="density-dialog-note">确定删除“<strong>{{ deleteTarget?.feeTypeName }}</strong>”吗?删除后无法恢复;已被业务单据使用的项目将由服务端拒绝删除。</p>
|
||||
</AppDialog>
|
||||
</AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.fee-items-page { min-width: 0; }
|
||||
.fee-item-editor-table { table-layout: fixed; }
|
||||
.fee-item-editor-table td { width: 50%; }
|
||||
.fee-item-name { display: flex; min-width: 0; flex-direction: column; gap: var(--admin-space-1); }
|
||||
.fee-item-name strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.fee-item-name small { color: var(--cc-muted); font-size: var(--admin-font-caption); }
|
||||
.description-cell { display: -webkit-box; overflow: hidden; line-height: 1.5; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
|
||||
.direction-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--admin-space-5); margin-bottom: var(--admin-space-5); }
|
||||
.direction-grid fieldset { min-width: 0; margin: 0; padding: var(--admin-space-5); border: 1px solid var(--el-border-color); border-radius: var(--admin-radius); }
|
||||
.direction-grid legend { padding: 0 5px; color: var(--el-text-color-secondary); font-size: var(--admin-font-body); }
|
||||
.delete-message { margin: 0; color: var(--el-text-color-regular); line-height: 1.7; overflow-wrap: anywhere; }
|
||||
@media (max-width: 720px) {
|
||||
.direction-grid, .form-grid.two-cols { grid-template-columns: 1fr; }
|
||||
.fee-item-editor-table tr { display: grid; }
|
||||
.fee-item-editor-table td { width: auto; }
|
||||
.table-actions { flex-wrap: wrap; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user