| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- <template>
- <AidopDemoShell :title="pageTitle" :subtitle="config.subtitle">
- <el-form :inline="true" :model="query" class="mb12" @submit.prevent>
- <el-form-item label="关键字">
- <el-input v-model="query.keyword" :placeholder="config.queryPlaceholder" clearable style="width: 260px" />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="loadList">查询</el-button>
- <el-button @click="resetQuery">重置</el-button>
- <el-button type="success" @click="openCreate">新增{{ config.entityLabel }}</el-button>
- </el-form-item>
- </el-form>
- <el-table :data="rows" v-loading="loading" border stripe>
- <el-table-column v-for="column in config.listColumns" :key="column.prop" :prop="column.prop" :label="column.label" :width="column.width" :min-width="column.minWidth" show-overflow-tooltip />
- <el-table-column label="操作" width="180" fixed="right" align="center">
- <template #default="{ row }">
- <el-button link type="primary" @click="openEdit(row)">编辑</el-button>
- <el-button link type="danger" @click="onDelete(row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- <div class="pager">
- <el-pagination
- v-model:current-page="query.page"
- v-model:page-size="query.pageSize"
- :total="total"
- :page-sizes="[20, 50, 100]"
- layout="total, sizes, prev, pager, next"
- @current-change="loadList"
- @size-change="loadList"
- />
- </div>
- <el-dialog v-model="dialogVisible" :title="dialogTitle" width="1200px" destroy-on-close @closed="resetForm">
- <el-form ref="formRef" :model="master" :rules="rules" label-width="120px" class="head-grid">
- <el-form-item v-for="field in config.headFields" :key="field.prop" :label="field.label" :prop="field.prop">
- <el-input-number
- v-if="field.type === 'number'"
- v-model="master[field.prop]"
- controls-position="right"
- style="width: 100%"
- />
- <el-input
- v-else
- v-model="master[field.prop]"
- :type="field.type === 'textarea' ? 'textarea' : 'text'"
- :rows="field.type === 'textarea' ? 3 : undefined"
- />
- </el-form-item>
- </el-form>
- <div class="detail-toolbar">
- <div class="detail-title">明细</div>
- <el-button type="primary" plain @click="addItem">新增行</el-button>
- </div>
- <el-table :data="items" border stripe class="detail-table">
- <el-table-column type="index" label="#" width="50" />
- <el-table-column v-for="column in config.detailColumns" :key="column.prop" :label="column.label" :width="column.type === 'number' ? 140 : undefined" :min-width="column.type === 'number' ? undefined : 150">
- <template #default="{ row }">
- <el-input-number
- v-if="column.type === 'number'"
- v-model="row[column.prop]"
- controls-position="right"
- style="width: 100%"
- />
- <el-input v-else v-model="row[column.prop]" />
- </template>
- </el-table-column>
- <el-table-column label="操作" width="80" fixed="right" align="center">
- <template #default="{ $index }">
- <el-button link type="danger" @click="removeItem($index)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- <template #footer>
- <el-button @click="dialogVisible = false">取消</el-button>
- <el-button type="primary" :loading="saving" @click="submitForm">保存</el-button>
- </template>
- </el-dialog>
- </AidopDemoShell>
- </template>
- <script setup lang="ts">
- import { computed, onMounted, reactive, ref } from 'vue';
- import { useRoute } from 'vue-router';
- import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
- import AidopDemoShell from '../../../components/AidopDemoShell.vue';
- import type { QualityAggregatePageConfig } from '../qualityConfigs';
- const props = defineProps<{ config: QualityAggregatePageConfig }>();
- const route = useRoute();
- const pageTitle = computed(() => (route.meta?.title as string) || props.config.entityLabel);
- const query = reactive({ keyword: '', page: 1, pageSize: 20 });
- const rows = ref<Record<string, any>[]>([]);
- const total = ref(0);
- const loading = ref(false);
- const dialogVisible = ref(false);
- const dialogTitle = ref('');
- const editingId = ref<number | null>(null);
- const saving = ref(false);
- const formRef = ref<FormInstance>();
- const master = reactive<Record<string, any>>({});
- const items = ref<Record<string, any>[]>([]);
- const rules = computed<FormRules>(() =>
- props.config.headFields.reduce<FormRules>((acc, field) => {
- if (field.required) acc[field.prop] = [{ required: true, message: `请填写${field.label}`, trigger: 'blur' }];
- return acc;
- }, {})
- );
- function resetForm() {
- editingId.value = null;
- Object.keys(master).forEach((key) => delete master[key]);
- Object.assign(master, structuredClone(props.config.initialMaster));
- items.value = [];
- formRef.value?.clearValidate();
- }
- async function loadList() {
- loading.value = true;
- try {
- const data = await props.config.api.list({
- keyword: query.keyword || undefined,
- page: query.page,
- pageSize: query.pageSize,
- });
- rows.value = data.list ?? [];
- total.value = data.total ?? 0;
- } catch {
- rows.value = [];
- total.value = 0;
- } finally {
- loading.value = false;
- }
- }
- function resetQuery() {
- query.keyword = '';
- query.page = 1;
- void loadList();
- }
- function addItem() {
- items.value.push(props.config.createEmptyItem());
- }
- function removeItem(index: number) {
- items.value.splice(index, 1);
- }
- function openCreate() {
- resetForm();
- dialogTitle.value = `新增${props.config.entityLabel}`;
- addItem();
- dialogVisible.value = true;
- }
- async function openEdit(row: Record<string, any>) {
- resetForm();
- editingId.value = Number(row.id);
- dialogTitle.value = `编辑${props.config.entityLabel}`;
- const detail = await props.config.api.get(editingId.value);
- Object.assign(master, structuredClone(detail.master ?? {}));
- items.value = structuredClone(detail.items ?? []);
- dialogVisible.value = true;
- }
- async function submitForm() {
- await formRef.value?.validate();
- saving.value = true;
- try {
- const payload = { ...master, items: items.value.map((item) => ({ ...item })) };
- if (editingId.value) {
- await props.config.api.update(editingId.value, payload);
- ElMessage.success('已保存');
- } else {
- await props.config.api.create(payload);
- ElMessage.success('已创建');
- }
- dialogVisible.value = false;
- await loadList();
- } finally {
- saving.value = false;
- }
- }
- function onDelete(row: Record<string, any>) {
- ElMessageBox.confirm(`确定删除${props.config.entityLabel}「${row[props.config.listColumns[0]?.prop] ?? row.id}」?`, '确认', { type: 'warning' })
- .then(async () => {
- await props.config.api.delete(Number(row.id));
- ElMessage.success('已删除');
- await loadList();
- })
- .catch(() => {});
- }
- onMounted(() => {
- resetForm();
- void loadList();
- });
- </script>
- <style scoped lang="scss">
- @import '/@/views/aidop/styles/aidop-demo.scss';
- .mb12 {
- margin-bottom: 12px;
- }
- .pager {
- margin-top: 12px;
- display: flex;
- justify-content: flex-end;
- }
- .head-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- column-gap: 12px;
- }
- .detail-toolbar {
- margin: 8px 0 12px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- .detail-title {
- font-size: 14px;
- font-weight: 600;
- }
- .detail-table {
- margin-bottom: 8px;
- }
- </style>
|