fix(admin): standardize resource initialization and lists

This commit is contained in:
2026-08-05 12:19:13 +08:00
parent d88e18bd09
commit 2162fe5e11
36 changed files with 419 additions and 267 deletions

View File

@@ -17,7 +17,6 @@
>
<template v-if="item.money" #prefix>¥</template>
</a-statistic>
<div class="metric-hint">{{ item.hint }}</div>
</a-card>
</a-grid-item>
</a-grid>
@@ -69,15 +68,15 @@ const updatedAtLabel = computed(() => updatedAt.value || (errorMessage.value ? '
const overview = ref<DashboardOverview>(emptyOverview());
const router = useRouter();
const userStore = useUserStore();
const cards: { key: CountKey; label: string; hint: string; money?: boolean }[] = [
{ key: 'gas_basic_count', label: '气站总数', hint: '正常运营气站总数' },
{ key: 'delivery_basic_count', label: '配送点总数', hint: '正常运营配送点总数' },
{ key: 'user_count', label: '客户总数', hint: '有效客户账户总数' },
{ key: 'product_count', label: '智能气阀', hint: '平台智能气阀总数' },
{ key: 'today_order_count', label: '今日订单', hint: '自然日新增' },
{ key: 'today_order_amount', label: '今日应付金额', hint: '订单口径', money: true },
{ key: 'pending_ticket_count', label: '待受理工单', hint: '客服待办' },
{ key: 'paid_amount', label: '累计实收金额', hint: '支付成功口径', money: true },
const cards: { key: CountKey; label: string; money?: boolean }[] = [
{ key: 'gas_basic_count', label: '气站总数' },
{ key: 'delivery_basic_count', label: '配送点总数' },
{ key: 'user_count', label: '客户总数' },
{ key: 'product_count', label: '智能气阀' },
{ key: 'today_order_count', label: '今日订单' },
{ key: 'today_order_amount', label: '今日应付金额', money: true },
{ key: 'pending_ticket_count', label: '待受理工单' },
{ key: 'paid_amount', label: '累计实收金额', money: true },
];
const actions = [
{ label: '新建气站', route: 'organization-gas-basic', menu: 'gas_basic', icon: IconPlus },
@@ -148,8 +147,7 @@ onMounted(loadOverview);
.dashboard-spin { width: 100%; }
.dashboard-alert { margin-bottom: 16px; }
.dashboard-meta { margin-bottom: 12px; color: var(--color-text-3); font-size: 12px; text-align: right; }
.metric-card { min-height: 132px; }
.metric-hint { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--color-border-2); color: var(--color-text-2); font-size: 13px; line-height: 20px; }
.metric-card { min-height: 112px; }
.section-card { margin-top: 16px; }
.quick-action { height: 52px; justify-content: flex-start; padding: 0 18px; }
</style>

View File

@@ -6,7 +6,6 @@
<main class="login-card" aria-label="和气平台总后台登录">
<img class="login-card-logo" :src="logoUrl" alt="和气" />
<LoginForm />
<div class="login-footer">© 2026 和气 · 安全连接每一程</div>
</main>
</div>
</template>
@@ -61,16 +60,6 @@ import LoginForm from './components/login-form.vue';
margin: 0 auto 14px;
}
.login-footer {
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid rgba(29, 44, 75, 0.08);
color: #8a94a6;
font-size: 12px;
line-height: 20px;
text-align: center;
}
@media (max-width: @screen-sm) {
.login-page {
padding: 82px 16px 20px;

View File

@@ -31,8 +31,9 @@
</a-form>
<a-table :data="list" :loading="loading" :pagination="false" row-key="identity">
<template #columns>
<a-table-column title="ID" data-index="id" :width="100" />
<a-table-column v-for="field in displayFields" :key="field.key" :title="field.label" ellipsis tooltip>
<a-table-column title="ID" data-index="id" :width="80" />
<a-table-column title="唯一标识" data-index="identity" :width="280" ellipsis tooltip />
<a-table-column v-for="field in displayFields" :key="field.key" :title="field.label" :width="columnWidth(field)" ellipsis tooltip>
<template #cell="{ record }">
{{ displayFieldValue(field, record) }}
</template>
@@ -95,9 +96,11 @@
</div>
<a-table :data="accountList" :loading="accountLoading" :pagination="false" row-key="identity">
<template #columns>
<a-table-column title="用户名" data-index="username" />
<a-table-column title="显示名称" data-index="display_name" />
<a-table-column title="角色编码" data-index="role_code" />
<a-table-column title="ID" data-index="id" :width="80" />
<a-table-column title="唯一标识" data-index="identity" :width="280" ellipsis tooltip />
<a-table-column title="用户名" data-index="username" :width="160" />
<a-table-column title="显示名称" data-index="display_name" :width="180" />
<a-table-column title="角色编码" data-index="role_code" :width="140" />
<a-table-column title="状态" :width="90">
<template #cell="{ record }">
<a-tag :color="Number(record.status) === 1 ? 'green' : 'red'">
@@ -282,7 +285,7 @@ const loading = ref(false);
const saving = ref(false);
const actionSubmitting = ref(false);
const page = ref(Math.max(1, Number(route.query.page) || 1));
const pageSize = 20;
const pageSize = 50;
const total = ref(0);
const list = ref<Row[]>([]);
const accountCounts = ref<Record<string, number>>({});
@@ -292,7 +295,7 @@ const accountLoading = ref(false);
const accountList = ref<Row[]>([]);
const accountOwner = ref<Row>({});
const accountPage = ref(1);
const accountPageSize = 10;
const accountPageSize = 50;
const accountTotal = ref(0);
const accountFormVisible = ref(false);
const accountSaving = ref(false);
@@ -362,6 +365,7 @@ const formMode = computed<'create' | 'edit'>(() =>
const formFields = computed(() =>
props.definition.fields.filter(
(field) =>
field.key !== 'identity' &&
(formMode.value === 'create' || field.type !== 'password') &&
!(
formMode.value === 'edit' &&
@@ -375,6 +379,7 @@ const displayFields = computed(() =>
props.definition.fields
.filter(
(field) =>
field.key !== 'identity' &&
field.key !== 'password' &&
!(
props.definition.name === 'gas_basic' &&
@@ -1128,12 +1133,26 @@ watch(
function optionLabel(option: Row) {
return String(option.name ?? option.title ?? option.code ?? option.username ?? option.contract_no ?? option.order_no ?? option.identity);
}
function columnWidth(field: ResourceField) {
if (field.key === 'identity') return 280;
if (field.type === 'datetime' || field.type === 'date') return 180;
if (field.type === 'money' || field.type === 'number') return 140;
if (field.type === 'boolean') return 110;
if (field.type === 'select') return 140;
if (field.type === 'identity' || field.type === 'identity-list') return 240;
if (field.type === 'textarea') return 280;
if (field.key.includes('phone')) return 150;
if (/(name|title|address|content|remark|reason|terms)$/.test(field.key)) return 220;
return 180;
}
</script>
<style scoped lang="less">
.filters {
margin-bottom: 16px;
}
.workflow-alert,
.action-alert {
margin-bottom: 16px;

View File

@@ -7,8 +7,9 @@
</a-form>
<a-table :data="list" :loading="loading" :pagination="false" row-key="identity">
<template #columns>
<a-table-column title="业务标识" data-index="identity" :width="220" ellipsis tooltip />
<a-table-column v-for="field in displayFields" :key="field.key" :title="field.label" :data-index="field.key" ellipsis tooltip />
<a-table-column title="ID" data-index="id" :width="80" />
<a-table-column title="唯一标识" data-index="identity" :width="280" ellipsis tooltip />
<a-table-column v-for="field in displayFields" :key="field.key" :title="field.label" :data-index="field.key" :width="columnWidth(field)" ellipsis tooltip />
<a-table-column title="操作" :width="90"><template #cell="{ record }"><a-button size="mini" @click="openDetail(record)">详情</a-button></template></a-table-column>
</template>
</a-table>
@@ -35,13 +36,13 @@ import { Message } from '@arco-design/web-vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { resourceApi } from '@/api/resource';
import { buildResourcePayload, isMissingField } from '@/api/resource-form';
import type { DetailAction, ResourceUiDefinition } from '@/api/resources';
import type { DetailAction, ResourceField, ResourceUiDefinition } from '@/api/resources';
type Row = Record<string, unknown>;
const props = defineProps<{ definition: ResourceUiDefinition }>();
const loading = ref(false);
const page = ref(1);
const pageSize = 20;
const pageSize = 50;
const total = ref(0);
const list = ref<Row[]>([]);
const filters = reactive({ keyword: '' });
@@ -51,8 +52,20 @@ const actionVisible = ref(false);
const activeAction = ref<DetailAction>();
const actionForm = reactive<Record<string, any>>({});
const displayFields = computed(() =>
props.definition.fields.filter((field) => field.key !== 'status'),
props.definition.fields.filter((field) => field.key !== 'identity' && field.key !== 'status'),
);
function columnWidth(field: ResourceField) {
if (field.type === 'datetime' || field.type === 'date') return 180;
if (field.type === 'money' || field.type === 'number') return 140;
if (field.type === 'boolean') return 110;
if (field.type === 'select') return 140;
if (field.type === 'identity' || field.type === 'identity-list') return 240;
if (field.type === 'textarea') return 280;
if (field.key.includes('phone')) return 150;
if (/(name|title|address|content|remark|reason|terms)$/.test(field.key)) return 220;
return 180;
}
const detailEntries = computed(() =>
Object.entries(detail.value).filter(
([key]) => key !== 'id' && !key.endsWith('_id'),