优化
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import ElementPlus from 'element-plus'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
||||
@@ -7,6 +9,8 @@ import { useAuthStore } from '@/core/auth/auth'
|
||||
import { listPlatformFeeItems } from '@/modules/fee-items/api/platformFeeItems'
|
||||
import FeeItemsView from './FeeItemsView.vue'
|
||||
|
||||
const source = readFileSync(join(process.cwd(), 'src/modules/fee-items/pages/FeeItemsView.vue'), 'utf8')
|
||||
|
||||
vi.mock('@/modules/fee-items/api/platformFeeItems', () => ({
|
||||
listPlatformFeeItems: vi.fn(),
|
||||
getPlatformFeeItem: vi.fn(),
|
||||
@@ -63,6 +67,11 @@ describe('FeeItemsView public master data and access', () => {
|
||||
|
||||
expect(wrapper.find('.query-toolbar').exists()).toBe(true)
|
||||
expect(wrapper.find('.data-table').exists()).toBe(true)
|
||||
expect(wrapper.find('.query-toolbar__actions').text()).not.toContain('新增费用项目')
|
||||
expect(wrapper.find('.data-table__business-actions').text()).toContain('新增费用项目')
|
||||
expect(wrapper.find('.data-table__toolbar-start').exists()).toBe(false)
|
||||
expect(wrapper.find('.data-table__pagination-left').text()).toContain('平台公共主数据 · 共 0 项')
|
||||
expect(wrapper.find('.list-page-layout__state').exists()).toBe(false)
|
||||
expect(wrapper.text()).toContain('平台公共主数据')
|
||||
expect(listPlatformFeeItems).toHaveBeenCalledWith(expect.objectContaining({ page: 1, size: 20 }))
|
||||
|
||||
@@ -73,10 +82,27 @@ describe('FeeItemsView public master data and access', () => {
|
||||
expect(listPlatformFeeItems).toHaveBeenCalledWith(expect.objectContaining({ page: 1, size: 20 }))
|
||||
})
|
||||
|
||||
it('renders the load error instead of an empty table panel', async () => {
|
||||
vi.mocked(listPlatformFeeItems).mockRejectedValueOnce(new Error('Gateway unavailable'))
|
||||
const wrapper = await mountView(['firstleg:fee-item:view'])
|
||||
|
||||
expect(wrapper.find('.list-page-layout__state').exists()).toBe(true)
|
||||
expect(wrapper.find('.table-panel').exists()).toBe(false)
|
||||
expect(wrapper.text()).toContain('费用项目加载失败')
|
||||
expect(wrapper.text()).toContain('Gateway unavailable')
|
||||
})
|
||||
|
||||
it('renders an explicit no-access state without loading customers or fee items', async () => {
|
||||
const wrapper = await mountView([])
|
||||
|
||||
expect(wrapper.text()).toContain('无权查看费用项目')
|
||||
expect(listPlatformFeeItems).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps all three row actions visible without a premature more menu', () => {
|
||||
expect(source).toContain('<Pencil :size="15" />编辑')
|
||||
expect(source).toContain('<Power :size="15" />')
|
||||
expect(source).toContain('<Trash2 :size="15" />删除')
|
||||
expect(source).not.toContain('MoreHorizontal')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { Pencil, Plus, Power, Trash2 } from '@lucide/vue'
|
||||
import { Pencil, Plus, Power, Save, Trash2 } from '@lucide/vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
|
||||
import {
|
||||
@@ -18,9 +18,12 @@ import {
|
||||
} from '@/modules/fee-items/api/platformFeeItems'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
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 ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
|
||||
@@ -319,30 +322,33 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="business-page fee-items-page">
|
||||
<AdminPage as="div" class="fee-items-page" spacing="normal">
|
||||
<el-result v-if="!canView" icon="warning" title="无权查看费用项目" sub-title="请联系管理员开通费用项目查看权限" />
|
||||
|
||||
<template v-else>
|
||||
<QueryToolbar
|
||||
v-model="filters"
|
||||
class="surface-panel"
|
||||
ariaLabel="费用项目筛选"
|
||||
:fields="queryFields"
|
||||
preference-key="fee-items-query"
|
||||
@search="applyFilters"
|
||||
/>
|
||||
<ListPageLayout v-else aria-label="费用项目管理">
|
||||
<template #toolbar>
|
||||
<QueryToolbar
|
||||
v-model="filters"
|
||||
class="surface-panel"
|
||||
ariaLabel="费用项目筛选"
|
||||
:fields="queryFields"
|
||||
preference-key="fee-items-query"
|
||||
@search="applyFilters"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<LoadErrorState
|
||||
v-if="loadError"
|
||||
title="费用项目加载失败"
|
||||
:message="loadError.message"
|
||||
:loading="loading"
|
||||
@retry="loadFeeItems"
|
||||
>
|
||||
<template #details><ApiErrorMeta :details="loadError" /></template>
|
||||
</LoadErrorState>
|
||||
<template v-if="loadError" #state>
|
||||
<LoadErrorState
|
||||
title="费用项目加载失败"
|
||||
:message="loadError.message"
|
||||
:loading="loading"
|
||||
@retry="loadFeeItems"
|
||||
>
|
||||
<template #details><ApiErrorMeta :details="loadError" /></template>
|
||||
</LoadErrorState>
|
||||
</template>
|
||||
|
||||
<section class="surface-panel table-panel">
|
||||
<section v-if="!loadError" class="surface-panel table-panel">
|
||||
<DataTable
|
||||
v-model:page="page"
|
||||
v-model:page-size="pageSize"
|
||||
@@ -356,9 +362,6 @@ onMounted(() => {
|
||||
row-key="id"
|
||||
empty-text="暂无符合条件的费用项目"
|
||||
>
|
||||
<template #toolbar-start>
|
||||
<div><h2>费用项目列表</h2><p>平台公共主数据 · 共 {{ total }} 项</p></div>
|
||||
</template>
|
||||
<template #toolbar-actions>
|
||||
<el-button
|
||||
v-if="access.can('firstleg:fee-item:create')"
|
||||
@@ -395,9 +398,10 @@ onMounted(() => {
|
||||
<el-button v-if="access.can('firstleg:fee-item:create')" type="primary" @click="openCreate"><Plus :size="16" />新增第一项费用</el-button>
|
||||
</el-empty>
|
||||
</template>
|
||||
<template #pagination-left>平台公共主数据 · 共 {{ total }} 项</template>
|
||||
</DataTable>
|
||||
</section>
|
||||
</template>
|
||||
</ListPageLayout>
|
||||
|
||||
<AppDrawer
|
||||
v-model="editorOpen"
|
||||
@@ -405,27 +409,34 @@ onMounted(() => {
|
||||
size="min(620px, 94vw)"
|
||||
:loading="editorLoading"
|
||||
:confirm-loading="saving"
|
||||
confirm-text="保存费用项目"
|
||||
@confirm="saveFeeItem"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" :disabled="saving || editorLoading">
|
||||
<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>
|
||||
<div class="form-section">
|
||||
<h3>费用方向</h3>
|
||||
<FormSection 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>
|
||||
</FormSection>
|
||||
<FormSection 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>
|
||||
</div>
|
||||
<el-form-item label="说明"><el-input v-model="form.description" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="请输入适用场景或核算说明" /></el-form-item>
|
||||
</FormSection>
|
||||
<FormSection 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>
|
||||
<template #footer>
|
||||
<div class="drawer-footer">
|
||||
<el-button :disabled="saving" @click="editorOpen = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" :disabled="editorLoading" @click="saveFeeItem"><Save :size="16" />保存费用项目</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</AppDrawer>
|
||||
|
||||
<AppDialog
|
||||
@@ -452,11 +463,11 @@ onMounted(() => {
|
||||
>
|
||||
<p class="delete-message">确定删除“<strong>{{ deleteTarget?.feeTypeName }}</strong>”吗?删除后无法恢复;已被业务单据使用的项目将由服务端拒绝删除。</p>
|
||||
</AppDialog>
|
||||
</div>
|
||||
</AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.fee-items-page { display: grid; gap: 16px; }
|
||||
.fee-items-page { min-width: 0; }
|
||||
.fee-item-name { display: flex; min-width: 0; flex-direction: column; gap: 4px; }
|
||||
.fee-item-name strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.fee-item-name small { color: var(--cc-muted); font-size: 11px; }
|
||||
|
||||
Reference in New Issue
Block a user