Files
admin/front/src/views/rituals/RitualManagement.vue
T
2026-07-27 09:57:35 +08:00

224 lines
6.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons-vue';
import { Modal, message } from 'ant-design-vue';
import type { Rule } from 'ant-design-vue/es/form';
import type { ColumnsType } from 'ant-design-vue/es/table';
import { computed, onMounted, reactive, ref } from 'vue';
import {
createRitualService,
deleteRitualService,
listRitualServices,
updateRitualService,
type RitualService,
} from '@/api/rituals';
type RitualFormState = {
id?: number;
name: string;
description: string;
price: number;
is_active: boolean;
};
const rituals = ref<RitualService[]>([]);
const loading = ref(false);
const saving = ref(false);
const modalOpen = ref(false);
const formRef = ref();
const formState = reactive<RitualFormState>({
name: '',
description: '',
price: 0,
is_active: true,
});
const isEditing = computed(() => formState.id !== undefined);
const modalTitle = computed(() => (isEditing.value ? '编辑法事' : '新建法事'));
const columns: ColumnsType<RitualService> = [
{ title: 'ID', dataIndex: 'id', width: 72 },
{ title: '法事名称', dataIndex: 'name', width: 180 },
{ title: '价格', dataIndex: 'price', width: 140 },
{ title: '状态', dataIndex: 'is_active', width: 100 },
{ title: '说明', dataIndex: 'description' },
{ title: '操作', key: 'actions', width: 140, fixed: 'right' },
];
const rules: Record<string, Rule[]> = {
name: [{ required: true, message: '请输入法事名称', trigger: 'blur' }],
price: [{ type: 'number', min: 0, message: '价格不能小于 0', trigger: 'change' }],
};
function formatMoney(value: number) {
return new Intl.NumberFormat('zh-CN', {
currency: 'CNY',
style: 'currency',
}).format(value);
}
function resetForm() {
formState.id = undefined;
formState.name = '';
formState.description = '';
formState.price = 0;
formState.is_active = true;
formRef.value?.clearValidate?.();
}
async function fetchRituals() {
loading.value = true;
try {
rituals.value = await listRitualServices();
} catch (error) {
message.error(error instanceof Error ? error.message : '加载法事失败,请先选择寺院');
} finally {
loading.value = false;
}
}
function openCreateModal() {
resetForm();
modalOpen.value = true;
}
function openEditModal(ritual: RitualService) {
formState.id = ritual.id;
formState.name = ritual.name;
formState.description = ritual.description || '';
formState.price = ritual.price;
formState.is_active = ritual.is_active;
modalOpen.value = true;
}
async function submitForm() {
await formRef.value?.validate?.();
saving.value = true;
try {
const payload = {
name: formState.name,
description: formState.description || null,
price: formState.price,
is_active: formState.is_active,
};
if (isEditing.value && formState.id !== undefined) {
await updateRitualService(formState.id, payload);
message.success('法事已更新');
} else {
await createRitualService(payload);
message.success('法事已创建');
}
modalOpen.value = false;
resetForm();
await fetchRituals();
} catch (error) {
message.error(error instanceof Error ? error.message : '保存法事失败');
} finally {
saving.value = false;
}
}
function confirmDelete(ritual: RitualService) {
Modal.confirm({
title: '删除法事',
content: `确认删除法事 ${ritual.name}`,
okText: '删除',
okType: 'danger',
cancelText: '取消',
async onOk() {
await deleteRitualService(ritual.id);
message.success('法事已删除');
await fetchRituals();
},
});
}
onMounted(fetchRituals);
</script>
<template>
<section class="workspace">
<div class="page-heading">
<h1>法事管理</h1>
<a-space>
<a-button :loading="loading" @click="fetchRituals">
<template #icon><ReloadOutlined /></template>
刷新
</a-button>
<a-button type="primary" @click="openCreateModal">
<template #icon><PlusOutlined /></template>
新建法事
</a-button>
</a-space>
</div>
<a-table
:columns="columns"
:data-source="rituals"
:loading="loading"
:pagination="{ pageSize: 8, showSizeChanger: false }"
bordered
row-key="id"
size="middle"
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'price'">
{{ formatMoney(record.price) }}
</template>
<template v-if="column.dataIndex === 'description'">
{{ record.description || '-' }}
</template>
<template v-if="column.dataIndex === 'is_active'">
<a-tag :color="record.is_active ? 'green' : 'default'">
{{ record.is_active ? '启用' : '停用' }}
</a-tag>
</template>
<template v-if="column.key === 'actions'">
<a-space>
<a-tooltip title="编辑">
<a-button type="text" @click="openEditModal(record)">
<template #icon><EditOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip title="删除">
<a-button type="text" danger @click="confirmDelete(record)">
<template #icon><DeleteOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
</a-table>
</section>
<a-modal
v-model:open="modalOpen"
:confirm-loading="saving"
:title="modalTitle"
ok-text="保存"
cancel-text="取消"
@ok="submitForm"
>
<a-form ref="formRef" :model="formState" :rules="rules" layout="vertical">
<a-form-item label="法事名称" name="name">
<a-input v-model:value="formState.name" placeholder="祈福法会" />
</a-form-item>
<a-form-item label="价格" name="price">
<a-input-number v-model:value="formState.price" :min="0" :precision="2" addon-before="¥" class="full-width-control" />
</a-form-item>
<a-form-item label="说明" name="description">
<a-textarea v-model:value="formState.description" :rows="3" />
</a-form-item>
<a-form-item label="状态" name="is_active">
<a-switch v-model:checked="formState.is_active" checked-children="启用" un-checked-children="停用" />
</a-form-item>
</a-form>
</a-modal>
</template>