add registration event sort
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
|
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
|
||||||
<title>Gao Customer H5</title>
|
<title>糕店管理工具</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ async function update(req: RegistrationEvent): Promise<void> {
|
|||||||
return await axios.post(`${BASE_URI}/update`, req);
|
return await axios.post(`${BASE_URI}/update`, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sort(idList: string[]): Promise<void> {
|
||||||
|
return await axios.post(`${BASE_URI}/sort`, idList);
|
||||||
|
}
|
||||||
|
|
||||||
async function remove(id: string): Promise<void> {
|
async function remove(id: string): Promise<void> {
|
||||||
return await axios.post(`${BASE_URI}/delete`, undefined, {
|
return await axios.post(`${BASE_URI}/delete`, undefined, {
|
||||||
params: { id }
|
params: { id }
|
||||||
@@ -37,5 +41,6 @@ export default {
|
|||||||
detail,
|
detail,
|
||||||
create,
|
create,
|
||||||
update,
|
update,
|
||||||
|
sort,
|
||||||
remove
|
remove
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-can="pageAccess" class="registration-event-edit">
|
<div v-can="pageAccess" class="registration-event-edit">
|
||||||
<t-form class="form" label-align="left">
|
<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="颜色">
|
<t-form-item label="颜色">
|
||||||
<t-button
|
<t-button
|
||||||
size="extra-small"
|
size="extra-small"
|
||||||
@@ -16,44 +10,59 @@
|
|||||||
>
|
>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<span class="swatch" :style="{ background: formModel.color }" />
|
<span class="swatch" :style="{ background: formModel.color }" />
|
||||||
<span v-text="formModel.color" />
|
<span v-text="formModel.color.toLowerCase()" />
|
||||||
</div>
|
</div>
|
||||||
</t-button>
|
</t-button>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="状态">
|
<t-form-item label="名称">
|
||||||
<t-radio-group v-model="formModel.status" class="inline-options" variant="default-filled">
|
<t-input
|
||||||
<t-radio :block="false" borderless value="DRAFT">草稿</t-radio>
|
class="name"
|
||||||
<t-radio :block="false" borderless value="ACTIVE">可登记</t-radio>
|
v-model.trim="formModel.name"
|
||||||
<t-radio :block="false" borderless value="INACTIVE">不可登记</t-radio>
|
placeholder="登记事件名称"
|
||||||
</t-radio-group>
|
borderless
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="说明">
|
||||||
|
<t-textarea
|
||||||
|
class="description"
|
||||||
|
v-model.trim="formModel.description"
|
||||||
|
placeholder="事件说明"
|
||||||
|
borderless
|
||||||
|
/>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="起始时间" arrow @click="openBeginAtPicker">
|
||||||
|
<div class="begin-at">
|
||||||
|
<div class="picker-text" v-text="formModel.beginAtText || '不限'" />
|
||||||
|
<t-button
|
||||||
|
v-if="formModel.beginAtText"
|
||||||
|
size="extra-small"
|
||||||
|
content="不限"
|
||||||
|
@click.stop="clearBeginAt"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="终止时间" arrow @click="openEndAtPicker">
|
||||||
|
<div class="end-at">
|
||||||
|
<div class="picker-text" v-text="formModel.endAtText || '不限'" />
|
||||||
|
<t-button
|
||||||
|
v-if="formModel.endAtText"
|
||||||
|
size="extra-small"
|
||||||
|
content="不限"
|
||||||
|
@click.stop="clearEndAt"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="约束" arrow @click="openLimitPicker">
|
||||||
|
<div class="picker-text" v-text="getLimitText(formModel.limitType)" />
|
||||||
|
</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-item>
|
||||||
<t-form-item label="需要拍照" content-align="right">
|
<t-form-item label="需要拍照" content-align="right">
|
||||||
<t-switch v-model="formModel.requirePhoto" />
|
<t-switch v-model="formModel.requirePhoto" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="排序">
|
<t-form-item label="状态" arrow @click="openStatusPicker">
|
||||||
<t-input v-model.trim="formModel.sortText" type="number" placeholder="排序值" borderless />
|
<div class="picker-text" v-text="getStatusText(formModel.status)" />
|
||||||
</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-item label="起始时间" arrow @click="openBeginAtPicker">
|
|
||||||
<div class="picker-text" v-text="formModel.beginAtText || '不限'" />
|
|
||||||
</t-form-item>
|
|
||||||
<t-form-item label="终止时间" arrow @click="openEndAtPicker">
|
|
||||||
<div class="picker-text" v-text="formModel.endAtText || '不限'" />
|
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
</t-form>
|
</t-form>
|
||||||
<section class="roles">
|
<section class="roles">
|
||||||
@@ -113,13 +122,26 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</t-popup>
|
</t-popup>
|
||||||
|
<t-popup v-model="limitPickerVisible" placement="bottom">
|
||||||
|
<t-picker
|
||||||
|
v-model="limitPickerDraft"
|
||||||
|
:columns="limitPickerColumns"
|
||||||
|
title="约束"
|
||||||
|
@cancel="limitPickerVisible = false"
|
||||||
|
@confirm="confirmLimitPicker"
|
||||||
|
/>
|
||||||
|
</t-popup>
|
||||||
|
<t-popup v-model="statusPickerVisible" placement="bottom">
|
||||||
|
<t-picker
|
||||||
|
v-model="statusPickerDraft"
|
||||||
|
:columns="statusPickerColumns"
|
||||||
|
title="状态"
|
||||||
|
@cancel="statusPickerVisible = false"
|
||||||
|
@confirm="confirmStatusPicker"
|
||||||
|
/>
|
||||||
|
</t-popup>
|
||||||
<t-popup v-model="beginAtPickerVisible" placement="bottom">
|
<t-popup v-model="beginAtPickerVisible" placement="bottom">
|
||||||
<div class="picker-popup">
|
<div class="picker-popup">
|
||||||
<div class="picker-header">
|
|
||||||
<t-button variant="text" size="small" @click="clearBeginAt">清空</t-button>
|
|
||||||
<span v-text="'起始时间'" />
|
|
||||||
<t-button variant="text" size="small" @click="beginAtPickerVisible = false">取消</t-button>
|
|
||||||
</div>
|
|
||||||
<t-date-time-picker
|
<t-date-time-picker
|
||||||
v-model="beginAtDraftText"
|
v-model="beginAtDraftText"
|
||||||
:default-value="beginAtDraftText || nowDateTimeText"
|
:default-value="beginAtDraftText || nowDateTimeText"
|
||||||
@@ -133,11 +155,6 @@
|
|||||||
</t-popup>
|
</t-popup>
|
||||||
<t-popup v-model="endAtPickerVisible" placement="bottom">
|
<t-popup v-model="endAtPickerVisible" placement="bottom">
|
||||||
<div class="picker-popup">
|
<div class="picker-popup">
|
||||||
<div class="picker-header">
|
|
||||||
<t-button variant="text" size="small" @click="clearEndAt">清空</t-button>
|
|
||||||
<span v-text="'终止时间'" />
|
|
||||||
<t-button variant="text" size="small" @click="endAtPickerVisible = false">取消</t-button>
|
|
||||||
</div>
|
|
||||||
<t-date-time-picker
|
<t-date-time-picker
|
||||||
v-model="endAtDraftText"
|
v-model="endAtDraftText"
|
||||||
:default-value="endAtDraftText || nowDateTimeText"
|
:default-value="endAtDraftText || nowDateTimeText"
|
||||||
@@ -176,9 +193,7 @@ const isCreateMode = computed(() => !routeId.value);
|
|||||||
const savePermissionCode = computed(() => isCreateMode.value ? gaoPermissionCodes.registrationEvent.create : gaoPermissionCodes.registrationEvent.update);
|
const savePermissionCode = computed(() => isCreateMode.value ? gaoPermissionCodes.registrationEvent.create : gaoPermissionCodes.registrationEvent.update);
|
||||||
const deletePermissionCode = gaoPermissionCodes.registrationEvent.delete;
|
const deletePermissionCode = gaoPermissionCodes.registrationEvent.delete;
|
||||||
const pageAccess = computed(() => ({
|
const pageAccess = computed(() => ({
|
||||||
value: isCreateMode.value
|
value: [gaoPermissionCodes.registrationEvent.read, gaoPermissionCodes.user.read, savePermissionCode.value],
|
||||||
? [gaoPermissionCodes.registrationEvent.read, gaoPermissionCodes.user.read, savePermissionCode.value]
|
|
||||||
: [gaoPermissionCodes.registrationEvent.read, gaoPermissionCodes.user.read, { value: [savePermissionCode.value, deletePermissionCode], mode: "or" as const }],
|
|
||||||
mode: "and" as const
|
mode: "and" as const
|
||||||
}));
|
}));
|
||||||
const roleOptions = ref<Role[]>([]);
|
const roleOptions = ref<Role[]>([]);
|
||||||
@@ -191,40 +206,50 @@ const endAtDraftText = ref("");
|
|||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
const removing = ref(false);
|
const removing = ref(false);
|
||||||
const colorSwatchList = [
|
const colorSwatchList = [
|
||||||
"#2f88ff",
|
"#2F88FF",
|
||||||
"#0ea5e9",
|
"#0EA5E9",
|
||||||
"#10b981",
|
"#10B981",
|
||||||
"#22c55e",
|
"#22C55E",
|
||||||
"#f59e0b",
|
"#F59E0B",
|
||||||
"#f97316",
|
"#F97316",
|
||||||
"#ef4444",
|
"#EF4444",
|
||||||
"#ec4899",
|
"#EC4899",
|
||||||
"#8b5cf6",
|
"#8B5CF6",
|
||||||
"#64748b"
|
"#64748B"
|
||||||
];
|
];
|
||||||
const limitOptions: LimitOption[] = [
|
const limitOptions: LimitOption[] = [
|
||||||
{ label: "不限", value: "NONE" },
|
{ label: "不限", value: "NONE" },
|
||||||
{ label: "上午/下午各一次", value: "DAILY_HALF_DAY" },
|
{ label: "上午和下午各一次", value: "DAILY_HALF_DAY" },
|
||||||
{ label: "每天一次", value: "NATURAL_DAY" },
|
{ label: "间隔小时内一次", value: "INTERVAL_HOUR" },
|
||||||
{ label: "间隔小时", value: "INTERVAL_HOUR" },
|
{ label: "每个自然日一次", value: "NATURAL_DAY" },
|
||||||
{ label: "每月一次", value: "NATURAL_MONTH" },
|
{ label: "每个自然月一次", value: "NATURAL_MONTH" },
|
||||||
{ label: "事件总次数", value: "TOTAL_LIMIT" },
|
{ label: "事件总次数", value: "TOTAL_LIMIT" },
|
||||||
{ label: "单客户总次数", value: "CUSTOMER_TOTAL_LIMIT" }
|
{ label: "单客户总次数", value: "CUSTOMER_TOTAL_LIMIT" }
|
||||||
];
|
];
|
||||||
|
const statusOptions: Array<{ label: string; value: RegistrationEventStatus }> = [
|
||||||
|
{ label: "草稿", value: "DRAFT" },
|
||||||
|
{ label: "可登记", value: "ACTIVE" },
|
||||||
|
{ label: "不可登记", value: "INACTIVE" }
|
||||||
|
];
|
||||||
|
const limitPickerColumns = [limitOptions];
|
||||||
|
const statusPickerColumns = [statusOptions];
|
||||||
const formModel = reactive({
|
const formModel = reactive({
|
||||||
id: "",
|
id: "",
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
color: "#2f88ff",
|
color: "#0f766e",
|
||||||
status: "ACTIVE" as RegistrationEventStatus,
|
status: "ACTIVE" as RegistrationEventStatus,
|
||||||
requirePhoto: false,
|
requirePhoto: false,
|
||||||
limitType: "NONE" as RegistrationLimitType,
|
limitType: "NONE" as RegistrationLimitType,
|
||||||
limitValueText: "",
|
limitValueText: "",
|
||||||
beginAtText: "",
|
beginAtText: "",
|
||||||
endAtText: "",
|
endAtText: "",
|
||||||
sortText: "0",
|
|
||||||
roleCodeList: [] as string[]
|
roleCodeList: [] as string[]
|
||||||
});
|
});
|
||||||
|
const limitPickerVisible = ref(false);
|
||||||
|
const limitPickerDraft = ref<string[]>([formModel.limitType]);
|
||||||
|
const statusPickerVisible = ref(false);
|
||||||
|
const statusPickerDraft = ref<RegistrationEventStatus[]>([formModel.status]);
|
||||||
const nowDateTimeText = computed(() => Time.formatDateTime(new Date()));
|
const nowDateTimeText = computed(() => Time.formatDateTime(new Date()));
|
||||||
const limitValuePlaceholder = computed(() => {
|
const limitValuePlaceholder = computed(() => {
|
||||||
if (formModel.limitType === "INTERVAL_HOUR") {
|
if (formModel.limitType === "INTERVAL_HOUR") {
|
||||||
@@ -251,6 +276,40 @@ function confirmColorPicker(): void {
|
|||||||
colorPickerVisible.value = false;
|
colorPickerVisible.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLimitText(value: RegistrationLimitType): string {
|
||||||
|
return limitOptions.find(option => option.value === value)?.label || "不限";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusText(value: RegistrationEventStatus): string {
|
||||||
|
return statusOptions.find(option => option.value === value)?.label || "可登记";
|
||||||
|
}
|
||||||
|
|
||||||
|
function openLimitPicker(): void {
|
||||||
|
limitPickerDraft.value = [formModel.limitType];
|
||||||
|
limitPickerVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmLimitPicker(value: Array<string | number>): void {
|
||||||
|
const limitType = value[0];
|
||||||
|
if (typeof limitType === "string") {
|
||||||
|
formModel.limitType = limitType as RegistrationLimitType;
|
||||||
|
}
|
||||||
|
limitPickerVisible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openStatusPicker(): void {
|
||||||
|
statusPickerDraft.value = [formModel.status];
|
||||||
|
statusPickerVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmStatusPicker(value: Array<string | number>): void {
|
||||||
|
const status = value[0];
|
||||||
|
if (status === "DRAFT" || status === "ACTIVE" || status === "INACTIVE") {
|
||||||
|
formModel.status = status;
|
||||||
|
}
|
||||||
|
statusPickerVisible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
function isValueLimitType(value: RegistrationLimitType): boolean {
|
function isValueLimitType(value: RegistrationLimitType): boolean {
|
||||||
return value === "INTERVAL_HOUR" || value === "TOTAL_LIMIT" || value === "CUSTOMER_TOTAL_LIMIT";
|
return value === "INTERVAL_HOUR" || value === "TOTAL_LIMIT" || value === "CUSTOMER_TOTAL_LIMIT";
|
||||||
}
|
}
|
||||||
@@ -308,7 +367,6 @@ function buildPayload(): RegistrationEvent {
|
|||||||
limitValue: resolveLimitValue(),
|
limitValue: resolveLimitValue(),
|
||||||
beginAt: resolveTimestamp(formModel.beginAtText),
|
beginAt: resolveTimestamp(formModel.beginAtText),
|
||||||
endAt: resolveTimestamp(formModel.endAtText),
|
endAt: resolveTimestamp(formModel.endAtText),
|
||||||
sort: Math.floor(Number(formModel.sortText) || 0),
|
|
||||||
roleCodeList: [...formModel.roleCodeList]
|
roleCodeList: [...formModel.roleCodeList]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -432,7 +490,6 @@ async function fillForm(): Promise<void> {
|
|||||||
limitValueText: current.limitValue ? String(current.limitValue) : "",
|
limitValueText: current.limitValue ? String(current.limitValue) : "",
|
||||||
beginAtText: current.beginAt ? Time.formatDateTime(new Date(current.beginAt)) : "",
|
beginAtText: current.beginAt ? Time.formatDateTime(new Date(current.beginAt)) : "",
|
||||||
endAtText: current.endAt ? Time.formatDateTime(new Date(current.endAt)) : "",
|
endAtText: current.endAt ? Time.formatDateTime(new Date(current.endAt)) : "",
|
||||||
sortText: String(current.sort ?? 0),
|
|
||||||
roleCodeList: [...(current.roleCodeList || [])]
|
roleCodeList: [...(current.roleCodeList || [])]
|
||||||
});
|
});
|
||||||
colorDraft.value = formModel.color;
|
colorDraft.value = formModel.color;
|
||||||
@@ -453,6 +510,22 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.form {
|
.form {
|
||||||
border-radius: .75rem;
|
border-radius: .75rem;
|
||||||
|
|
||||||
|
.description {
|
||||||
|
width: 100%;
|
||||||
|
height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.end-at,
|
||||||
|
.begin-at {
|
||||||
|
--td-button-extra-small-height: 24px;
|
||||||
|
|
||||||
|
gap: .5rem;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-text {
|
.picker-text {
|
||||||
@@ -461,10 +534,6 @@ onMounted(async () => {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textarea {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-trigger {
|
.color-trigger {
|
||||||
--td-button-extra-small-padding-horizontal: 2px;
|
--td-button-extra-small-padding-horizontal: 2px;
|
||||||
|
|
||||||
@@ -482,7 +551,7 @@ onMounted(async () => {
|
|||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: .25rem;
|
border-radius: .35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
@@ -492,13 +561,6 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline-options,
|
|
||||||
.limit-options {
|
|
||||||
gap: .5rem;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roles {
|
.roles {
|
||||||
gap: .75rem;
|
gap: .75rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -555,6 +617,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.picker-popup {
|
.picker-popup {
|
||||||
background: var(--td-bg-color-container);
|
background: var(--td-bg-color-container);
|
||||||
|
border-radius: 1.25rem 1.25rem 0 0;
|
||||||
|
|
||||||
.picker-header {
|
.picker-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import { Toast } from "tdesign-mobile-vue";
|
|||||||
import { Time, useUserStore, type Role } from "timi-web";
|
import { Time, useUserStore, type Role } from "timi-web";
|
||||||
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
|
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
|
||||||
import GaoUserAPI from "@/api/GaoUserAPI";
|
import GaoUserAPI from "@/api/GaoUserAPI";
|
||||||
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { RegistrationEvent, RegistrationLimitType } from "@/types/RegistrationEvent";
|
import type { RegistrationEvent, RegistrationLimitType } from "@/types/RegistrationEvent";
|
||||||
import { gaoPermissionCodes } from "@/utils/gaoPermission";
|
import { gaoPermissionCodes } from "@/utils/gaoPermission";
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ defineOptions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const navBarStore = useNavBarStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const defaultColor = "#2f88ff";
|
const defaultColor = "#2f88ff";
|
||||||
const registrationEventList = ref<RegistrationEvent[]>([]);
|
const registrationEventList = ref<RegistrationEvent[]>([]);
|
||||||
@@ -74,6 +76,10 @@ function createRegistrationEvent(): void {
|
|||||||
router.push("/management/registration-events/create");
|
router.push("/management/registration-events/create");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function goSort(): Promise<void> {
|
||||||
|
await router.push("/management/registration-events/sort");
|
||||||
|
}
|
||||||
|
|
||||||
function editRegistrationEvent(item: RegistrationEvent): void {
|
function editRegistrationEvent(item: RegistrationEvent): void {
|
||||||
if (!item.id) {
|
if (!item.id) {
|
||||||
return;
|
return;
|
||||||
@@ -111,9 +117,9 @@ function getLimitText(item: RegistrationEvent): string {
|
|||||||
const limitTextMap: Record<RegistrationLimitType, string> = {
|
const limitTextMap: Record<RegistrationLimitType, string> = {
|
||||||
NONE: "不限次数",
|
NONE: "不限次数",
|
||||||
DAILY_HALF_DAY: "上午/下午各一次",
|
DAILY_HALF_DAY: "上午/下午各一次",
|
||||||
NATURAL_DAY: "每天一次",
|
|
||||||
INTERVAL_HOUR: `每隔 ${item.limitValue || 0} 小时`,
|
INTERVAL_HOUR: `每隔 ${item.limitValue || 0} 小时`,
|
||||||
NATURAL_MONTH: "每月一次",
|
NATURAL_DAY: "每个自然日一次",
|
||||||
|
NATURAL_MONTH: "每个自然月一次",
|
||||||
TOTAL_LIMIT: `事件总上限 ${item.limitValue || 0} 次`,
|
TOTAL_LIMIT: `事件总上限 ${item.limitValue || 0} 次`,
|
||||||
CUSTOMER_TOTAL_LIMIT: `单客户总上限 ${item.limitValue || 0} 次`
|
CUSTOMER_TOTAL_LIMIT: `单客户总上限 ${item.limitValue || 0} 次`
|
||||||
};
|
};
|
||||||
@@ -156,6 +162,10 @@ async function loadData(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
if (canUpdateRegistrationEvent.value) {
|
||||||
|
navBarStore.setRightText("排序", "var(--td-brand-color)", goSort);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await loadData();
|
await loadData();
|
||||||
} catch {
|
} catch {
|
||||||
@@ -166,6 +176,10 @@ onMounted(async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|||||||
@@ -0,0 +1,353 @@
|
|||||||
|
<template>
|
||||||
|
<div v-can="gaoPermissionCodes.registrationEvent.update" class="registration-event-sort">
|
||||||
|
<div class="tip" v-text="'按住右侧拖动手柄调整事件顺序,保存后生效'" />
|
||||||
|
<div ref="sortListRef" class="items">
|
||||||
|
<t-cell-group v-if="registrationEventList.length" theme="card">
|
||||||
|
<t-cell
|
||||||
|
class="cell"
|
||||||
|
:title="item.name || '未命名事件'"
|
||||||
|
v-for="(item, index) in registrationEventList"
|
||||||
|
:key="item.id"
|
||||||
|
:class="{ dragging: draggingIndex === index }"
|
||||||
|
>
|
||||||
|
<template #leftIcon>
|
||||||
|
<div class="color" :style="{ background: item.color || defaultColor }" />
|
||||||
|
</template>
|
||||||
|
<template #note>
|
||||||
|
<t-tag
|
||||||
|
:theme="getStatusTheme(item.status)"
|
||||||
|
variant="outline"
|
||||||
|
:content="getStatusText(item.status)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #rightIcon>
|
||||||
|
<t-icon
|
||||||
|
class="handle"
|
||||||
|
role="button"
|
||||||
|
name="move"
|
||||||
|
aria-label="拖动排序"
|
||||||
|
@pointerdown.stop="startDrag($event, index)"
|
||||||
|
@pointermove.stop="moveDrag"
|
||||||
|
@pointerup.stop="finishDrag"
|
||||||
|
@pointercancel.stop="finishDrag"
|
||||||
|
@lostpointercapture.stop="finishDrag"
|
||||||
|
@touchstart.stop="startTouchDrag($event, index)"
|
||||||
|
@touchmove.stop="moveTouchDrag"
|
||||||
|
@touchend.stop="finishTouchDrag"
|
||||||
|
@touchcancel.stop="finishTouchDrag"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
|
</t-cell-group>
|
||||||
|
<div v-else class="empty-block" v-text="loading ? '正在加载登记事件' : '暂无登记事件'" />
|
||||||
|
</div>
|
||||||
|
<t-button
|
||||||
|
class="save"
|
||||||
|
theme="primary"
|
||||||
|
:loading="saving"
|
||||||
|
:disabled="!sortDirty || saving || loading"
|
||||||
|
@click="saveSort"
|
||||||
|
>
|
||||||
|
保存排序
|
||||||
|
</t-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Toast, type TagProps } from "tdesign-mobile-vue";
|
||||||
|
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
|
||||||
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
|
import type { RegistrationEvent, RegistrationEventStatus } from "@/types/RegistrationEvent";
|
||||||
|
import { gaoPermissionCodes } from "@/utils/gaoPermission";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "RegistrationEventSort"
|
||||||
|
});
|
||||||
|
|
||||||
|
const navBarStore = useNavBarStore();
|
||||||
|
const defaultColor = "#2f88ff";
|
||||||
|
const registrationEventList = ref<RegistrationEvent[]>([]);
|
||||||
|
const sortListRef = ref<HTMLElement>();
|
||||||
|
const loading = ref(false);
|
||||||
|
const saving = ref(false);
|
||||||
|
const sortDirty = ref(false);
|
||||||
|
const draggingIndex = ref(-1);
|
||||||
|
let pointerId: number | undefined;
|
||||||
|
let pointerStartY = 0;
|
||||||
|
let pointerMoved = false;
|
||||||
|
let pointerTarget: HTMLElement | undefined;
|
||||||
|
let dragInputType: "pointer" | "touch" | undefined;
|
||||||
|
|
||||||
|
function getStatusText(status?: RegistrationEventStatus): string {
|
||||||
|
if (status === "DRAFT") {
|
||||||
|
return "草稿";
|
||||||
|
}
|
||||||
|
if (status === "INACTIVE") {
|
||||||
|
return "不可登记";
|
||||||
|
}
|
||||||
|
return "可登记";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusTheme(status?: RegistrationEventStatus): TagProps["theme"] {
|
||||||
|
if (status === "DRAFT") {
|
||||||
|
return "warning";
|
||||||
|
}
|
||||||
|
if (status === "INACTIVE") {
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
return "primary";
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDropIndex(clientY: number): number {
|
||||||
|
const elements = sortListRef.value?.querySelectorAll<HTMLElement>(".item");
|
||||||
|
if (!elements?.length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < elements.length; index++) {
|
||||||
|
const rect = elements[index].getBoundingClientRect();
|
||||||
|
if (clientY < rect.top + rect.height / 2) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return elements.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function beginDrag(index: number, clientY: number, target: EventTarget | null): void {
|
||||||
|
pointerStartY = clientY;
|
||||||
|
pointerMoved = false;
|
||||||
|
draggingIndex.value = index;
|
||||||
|
pointerTarget = target instanceof HTMLElement ? target : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveDragAt(clientY: number): void {
|
||||||
|
if (0 > draggingIndex.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pointerMoved && 8 > Math.abs(clientY - pointerStartY)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointerMoved = true;
|
||||||
|
const targetIndex = resolveDropIndex(clientY);
|
||||||
|
if (0 > targetIndex || targetIndex === draggingIndex.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [item] = registrationEventList.value.splice(draggingIndex.value, 1);
|
||||||
|
registrationEventList.value.splice(targetIndex, 0, item);
|
||||||
|
draggingIndex.value = targetIndex;
|
||||||
|
sortDirty.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishDragState(): void {
|
||||||
|
if (pointerMoved) {
|
||||||
|
sortDirty.value = true;
|
||||||
|
}
|
||||||
|
pointerId = undefined;
|
||||||
|
pointerTarget = undefined;
|
||||||
|
dragInputType = undefined;
|
||||||
|
draggingIndex.value = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startDrag(event: PointerEvent, index: number): void {
|
||||||
|
if (saving.value || loading.value || dragInputType || !event.isPrimary || event.button !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
pointerId = event.pointerId;
|
||||||
|
dragInputType = "pointer";
|
||||||
|
beginDrag(index, event.clientY, event.currentTarget);
|
||||||
|
pointerTarget?.setPointerCapture(event.pointerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveDrag(event: PointerEvent): void {
|
||||||
|
if (dragInputType !== "pointer" || pointerId !== event.pointerId || 0 > draggingIndex.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
moveDragAt(event.clientY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishDrag(event: PointerEvent): void {
|
||||||
|
if (dragInputType !== "pointer" || pointerId !== event.pointerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pointerTarget?.hasPointerCapture(event.pointerId)) {
|
||||||
|
pointerTarget.releasePointerCapture(event.pointerId);
|
||||||
|
}
|
||||||
|
finishDragState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTouchDrag(event: TouchEvent, index: number): void {
|
||||||
|
if (saving.value || loading.value || dragInputType || event.touches.length !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
const touch = event.touches[0];
|
||||||
|
pointerId = touch.identifier;
|
||||||
|
dragInputType = "touch";
|
||||||
|
beginDrag(index, touch.clientY, event.currentTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveTouchDrag(event: TouchEvent): void {
|
||||||
|
if (dragInputType !== "touch" || pointerId === undefined || 0 > draggingIndex.value || event.touches.length !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
moveDragAt(event.touches[0].clientY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishTouchDrag(event: TouchEvent): void {
|
||||||
|
if (dragInputType !== "touch" || pointerId === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const touch = event.changedTouches[0];
|
||||||
|
if (touch && touch.identifier !== pointerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finishDragState();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSort(): Promise<void> {
|
||||||
|
if (!sortDirty.value || saving.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
saving.value = true;
|
||||||
|
try {
|
||||||
|
const idList = registrationEventList.value
|
||||||
|
.map(item => item.id)
|
||||||
|
.filter((id): id is string => Boolean(id));
|
||||||
|
await RegistrationEventAPI.sort(idList);
|
||||||
|
registrationEventList.value.forEach((item, index) => {
|
||||||
|
item.sort = index;
|
||||||
|
});
|
||||||
|
sortDirty.value = false;
|
||||||
|
showToast("登记事件排序已保存");
|
||||||
|
} catch {
|
||||||
|
showToast("登记事件排序保存失败", "error");
|
||||||
|
} finally {
|
||||||
|
saving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToast(message: string, theme: "success" | "error" = "success"): void {
|
||||||
|
Toast({
|
||||||
|
theme,
|
||||||
|
direction: "row",
|
||||||
|
message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadData(): Promise<void> {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const result = await RegistrationEventAPI.list({
|
||||||
|
index: 0,
|
||||||
|
size: 100
|
||||||
|
});
|
||||||
|
registrationEventList.value = result.list || [];
|
||||||
|
sortDirty.value = false;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
try {
|
||||||
|
await loadData();
|
||||||
|
} catch {
|
||||||
|
showToast("登记事件加载失败", "error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.registration-event-sort {
|
||||||
|
padding: calc(var(--app-nav-height) + 1rem) 0 6rem;
|
||||||
|
|
||||||
|
.tip {
|
||||||
|
color: #52607a;
|
||||||
|
padding: 0 1rem .75rem;
|
||||||
|
font-size: .8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items {
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.dragging {
|
||||||
|
z-index: 1;
|
||||||
|
box-shadow: 0 .5rem 1rem rgba(15, 23, 42, .16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
border-radius: .4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle {
|
||||||
|
color: #52607a;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell.dragging,
|
||||||
|
.cell.dragging * {
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-block {
|
||||||
|
color: #52607a;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save {
|
||||||
|
left: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.theme-dark) {
|
||||||
|
.registration-event-sort {
|
||||||
|
.tip,
|
||||||
|
.empty-block,
|
||||||
|
.handle {
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
background: rgba(15, 23, 42, .88);
|
||||||
|
box-shadow: 0 .5rem 1rem rgba(2, 6, 23, .28);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { RouteRecordRaw } from "vue-router";
|
import type { RouteRecordRaw } from "vue-router";
|
||||||
import RegistrationEventEdit from "@/pages/management/RegistrationEventEdit.vue";
|
import RegistrationEventEdit from "@/pages/management/RegistrationEventEdit.vue";
|
||||||
import RegistrationEventList from "@/pages/management/RegistrationEventList.vue";
|
import RegistrationEventList from "@/pages/management/RegistrationEventList.vue";
|
||||||
|
import RegistrationEventSort from "@/pages/management/RegistrationEventSort.vue";
|
||||||
import RegistrationEventRecordDetail from "@/pages/management/RegistrationEventRecordDetail.vue";
|
import RegistrationEventRecordDetail from "@/pages/management/RegistrationEventRecordDetail.vue";
|
||||||
import RegistrationEventRecordEdit from "@/pages/management/RegistrationEventRecordEdit.vue";
|
import RegistrationEventRecordEdit from "@/pages/management/RegistrationEventRecordEdit.vue";
|
||||||
import RegistrationEventRecordList from "@/pages/management/RegistrationEventRecordList.vue";
|
import RegistrationEventRecordList from "@/pages/management/RegistrationEventRecordList.vue";
|
||||||
@@ -20,6 +21,20 @@ const registrationRoutes: RouteRecordRaw[] = [
|
|||||||
darkBodyBackground: "#10161f"
|
darkBodyBackground: "#10161f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/management/registration-events/sort",
|
||||||
|
name: "RegistrationEventSort",
|
||||||
|
component: RegistrationEventSort,
|
||||||
|
meta: {
|
||||||
|
depth: 4,
|
||||||
|
navBarVisible: true,
|
||||||
|
navBarCanBack: true,
|
||||||
|
navBarTitle: "登记事件排序",
|
||||||
|
tabBarVisible: false,
|
||||||
|
bodyBackground: "#F5F5F8",
|
||||||
|
darkBodyBackground: "#10161f"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/management/registration-events/create",
|
path: "/management/registration-events/create",
|
||||||
name: "RegistrationEventCreate",
|
name: "RegistrationEventCreate",
|
||||||
|
|||||||
Reference in New Issue
Block a user