霖雨寺

This commit is contained in:
wenfp
2026-07-27 09:57:35 +08:00
commit 6e531cf7af
57 changed files with 7529 additions and 0 deletions
@@ -0,0 +1,223 @@
<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>