init project

This commit is contained in:
Timi
2026-08-01 18:01:23 +08:00
parent 286fa1ba56
commit 11c48bf00c
107 changed files with 19350 additions and 1 deletions
@@ -0,0 +1,314 @@
<template>
<div v-can="pageAccess" class="registration-event-edit">
<t-form class="form" label-align="left">
<t-form-item label="名称">
<t-input v-model.trim="formModel.name" placeholder="登记事件名称" borderless />
</t-form-item>
<t-form-item label="说明">
<t-textarea v-model.trim="formModel.description" class="textarea" placeholder="事件说明" autosize borderless />
</t-form-item>
<t-form-item label="颜色">
<label class="color-picker">
<input v-model="formModel.color" type="color">
<span v-text="formModel.color" />
</label>
</t-form-item>
<t-form-item label="状态">
<t-radio-group v-model="formModel.status" class="inline-options" variant="default-filled">
<t-radio :block="false" borderless value="ACTIVE">有效</t-radio>
<t-radio :block="false" borderless value="INACTIVE">无效</t-radio>
</t-radio-group>
</t-form-item>
<t-form-item label="启用">
<t-switch v-model="formModel.enabled" />
</t-form-item>
<t-form-item label="拍照">
<t-switch v-model="formModel.requirePhoto" />
</t-form-item>
<t-form-item label="排序">
<t-input v-model.trim="formModel.sortText" type="number" placeholder="排序值" borderless />
</t-form-item>
<t-form-item label="约束">
<t-radio-group v-model="formModel.limitType" class="limit-options" variant="default-filled">
<t-radio
v-for="option in limitOptions"
:key="option.value"
:block="false"
borderless
:value="option.value"
>
<span v-text="option.label" />
</t-radio>
</t-radio-group>
</t-form-item>
<t-form-item v-if="isValueLimitType(formModel.limitType)" label="数值">
<t-input v-model.trim="formModel.limitValueText" type="number" :placeholder="limitValuePlaceholder" borderless />
</t-form-item>
</t-form>
<section class="roles">
<div class="title">
<span>可登记角色</span>
<span class="hint">未选择角色时不限制角色</span>
</div>
<t-checkbox-group v-model="formModel.roleCodeList" class="group">
<t-checkbox
v-for="item in roleOptions"
:key="item.code"
:value="item.code"
class="item"
borderless
>
<span v-text="item.name || item.code" />
</t-checkbox>
</t-checkbox-group>
</section>
<t-button v-can="savePermissionCode" class="save" theme="primary" @click="saveRegistrationEvent">保存</t-button>
</div>
</template>
<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import type { Role } from "timi-web";
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
import GaoUserAPI from "@/api/GaoUserAPI";
import type { RegistrationEvent, RegistrationEventStatus, RegistrationLimitType } from "@/types/RegistrationEvent";
import { gaoPermissionCodes } from "@/utils/gaoPermission";
type LimitOption = {
label: string;
value: RegistrationLimitType;
};
defineOptions({
name: "RegistrationEventEdit"
});
const route = useRoute();
const router = useRouter();
const routeId = computed(() => String(route.params.id || ""));
const isCreateMode = computed(() => !routeId.value);
const savePermissionCode = computed(() => isCreateMode.value ? gaoPermissionCodes.registrationEvent.create : gaoPermissionCodes.registrationEvent.update);
const pageAccess = computed(() => ({
value: [gaoPermissionCodes.registrationEvent.read, gaoPermissionCodes.user.read, savePermissionCode.value],
mode: "and" as const
}));
const roleOptions = ref<Role[]>([]);
const limitOptions: LimitOption[] = [
{ label: "不限", value: "NONE" },
{ label: "上午/下午各一次", value: "DAILY_HALF_DAY" },
{ label: "每天一次", value: "NATURAL_DAY" },
{ label: "间隔小时", value: "INTERVAL_HOUR" },
{ label: "每月一次", value: "NATURAL_MONTH" },
{ label: "累计次数", value: "TOTAL_LIMIT" }
];
const formModel = reactive({
id: "",
name: "",
description: "",
color: "#2f88ff",
status: "ACTIVE" as RegistrationEventStatus,
enabled: true,
requirePhoto: false,
limitType: "NONE" as RegistrationLimitType,
limitValueText: "",
sortText: "0",
roleCodeList: [] as string[]
});
const limitValuePlaceholder = computed(() => formModel.limitType === "INTERVAL_HOUR" ? "请输入间隔小时数" : "请输入累计上限次数");
function isValueLimitType(value: RegistrationLimitType): boolean {
return value === "INTERVAL_HOUR" || value === "TOTAL_LIMIT";
}
function resolveLimitValue(): number | undefined {
if (!isValueLimitType(formModel.limitType)) {
return undefined;
}
const value = Number(formModel.limitValueText);
return 0 < value ? Math.floor(value) : undefined;
}
function validateForm(): boolean {
if (!formModel.name.trim()) {
showToast("请输入登记事件名称", "warning");
return false;
}
if (isValueLimitType(formModel.limitType) && !resolveLimitValue()) {
showToast("请输入有效的约束数值", "warning");
return false;
}
return true;
}
function buildPayload(): RegistrationEvent {
return {
id: formModel.id || undefined,
name: formModel.name.trim(),
description: formModel.description,
color: formModel.color,
status: formModel.status,
enabled: formModel.enabled,
requirePhoto: formModel.requirePhoto,
limitType: formModel.limitType,
limitValue: resolveLimitValue(),
sort: Math.floor(Number(formModel.sortText) || 0),
roleCodeList: [...formModel.roleCodeList]
};
}
async function saveRegistrationEvent(): Promise<void> {
if (!validateForm()) {
return;
}
try {
const payload = buildPayload();
if (isCreateMode.value) {
await RegistrationEventAPI.create(payload);
} else {
await RegistrationEventAPI.update(payload);
}
showToast("登记事件已保存");
await router.replace("/management/registration-events");
} catch {
showToast("登记事件保存失败", "error");
}
}
function showToast(message: string, theme: "success" | "error" | "warning" = "success"): void {
Toast({
theme,
direction: "row",
message
});
}
async function fillForm(): Promise<void> {
roleOptions.value = await GaoUserAPI.roleList();
if (isCreateMode.value) {
return;
}
const current = await RegistrationEventAPI.detail(routeId.value);
Object.assign(formModel, {
id: current.id || "",
name: current.name || "",
description: current.description || "",
color: current.color || "#2f88ff",
status: current.status === "INACTIVE" ? "INACTIVE" : "ACTIVE",
enabled: current.enabled ?? true,
requirePhoto: current.requirePhoto ?? false,
limitType: current.limitType || "NONE",
limitValueText: current.limitValue ? String(current.limitValue) : "",
sortText: String(current.sort ?? 0),
roleCodeList: [...(current.roleCodeList || [])]
});
}
onMounted(async () => {
try {
await fillForm();
} catch {
showToast("登记事件加载失败", "error");
}
});
</script>
<style scoped lang="less">
.registration-event-edit {
padding: calc(var(--app-nav-height) + 1rem) 0 5rem 0;
.form {
border-radius: .75rem;
}
.textarea {
width: 100%;
}
.color-picker {
gap: .75rem;
display: flex;
align-items: center;
input {
padding: 0;
width: 2rem;
height: 2rem;
border: 0;
background: transparent;
}
span {
color: #52607a;
font-size: .875rem;
}
}
.inline-options,
.limit-options {
gap: .5rem;
display: flex;
flex-wrap: wrap;
}
.roles {
gap: .75rem;
display: flex;
padding: 1rem;
flex-direction: column;
.title {
gap: .25rem;
display: flex;
flex-direction: column;
span:first-child {
font-weight: 700;
}
.hint {
color: #52607a;
font-size: .8125rem;
}
}
.group {
gap: .5rem;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
.item {
margin: 0;
padding: .875rem;
background: #fff;
border: 1px solid rgba(148, 163, 184, .22);
border-radius: .75rem;
}
}
}
.save {
left: 1rem;
right: 1rem;
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
position: fixed;
}
}
:global(html.theme-dark) {
.registration-event-edit {
.color-picker span,
.roles .title .hint {
color: #94a3b8;
}
.roles .group .item {
color: #e2e8f0;
background: rgba(15, 23, 42, .72);
border-color: rgba(148, 163, 184, .16);
}
}
}
</style>