add registration event sort
This commit is contained in:
@@ -1,12 +1,6 @@
|
||||
<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="颜色">
|
||||
<t-button
|
||||
size="extra-small"
|
||||
@@ -16,44 +10,59 @@
|
||||
>
|
||||
<div class="content">
|
||||
<span class="swatch" :style="{ background: formModel.color }" />
|
||||
<span v-text="formModel.color" />
|
||||
<span v-text="formModel.color.toLowerCase()" />
|
||||
</div>
|
||||
</t-button>
|
||||
</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="DRAFT">草稿</t-radio>
|
||||
<t-radio :block="false" borderless value="ACTIVE">可登记</t-radio>
|
||||
<t-radio :block="false" borderless value="INACTIVE">不可登记</t-radio>
|
||||
</t-radio-group>
|
||||
<t-form-item label="名称">
|
||||
<t-input
|
||||
class="name"
|
||||
v-model.trim="formModel.name"
|
||||
placeholder="登记事件名称"
|
||||
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 label="需要拍照" content-align="right">
|
||||
<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-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 label="状态" arrow @click="openStatusPicker">
|
||||
<div class="picker-text" v-text="getStatusText(formModel.status)" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<section class="roles">
|
||||
@@ -113,13 +122,26 @@
|
||||
/>
|
||||
</div>
|
||||
</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">
|
||||
<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
|
||||
v-model="beginAtDraftText"
|
||||
:default-value="beginAtDraftText || nowDateTimeText"
|
||||
@@ -133,11 +155,6 @@
|
||||
</t-popup>
|
||||
<t-popup v-model="endAtPickerVisible" placement="bottom">
|
||||
<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
|
||||
v-model="endAtDraftText"
|
||||
: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 deletePermissionCode = gaoPermissionCodes.registrationEvent.delete;
|
||||
const pageAccess = computed(() => ({
|
||||
value: isCreateMode.value
|
||||
? [gaoPermissionCodes.registrationEvent.read, gaoPermissionCodes.user.read, savePermissionCode.value]
|
||||
: [gaoPermissionCodes.registrationEvent.read, gaoPermissionCodes.user.read, { value: [savePermissionCode.value, deletePermissionCode], mode: "or" as const }],
|
||||
value: [gaoPermissionCodes.registrationEvent.read, gaoPermissionCodes.user.read, savePermissionCode.value],
|
||||
mode: "and" as const
|
||||
}));
|
||||
const roleOptions = ref<Role[]>([]);
|
||||
@@ -191,40 +206,50 @@ const endAtDraftText = ref("");
|
||||
const saving = ref(false);
|
||||
const removing = ref(false);
|
||||
const colorSwatchList = [
|
||||
"#2f88ff",
|
||||
"#0ea5e9",
|
||||
"#10b981",
|
||||
"#22c55e",
|
||||
"#f59e0b",
|
||||
"#f97316",
|
||||
"#ef4444",
|
||||
"#ec4899",
|
||||
"#8b5cf6",
|
||||
"#64748b"
|
||||
"#2F88FF",
|
||||
"#0EA5E9",
|
||||
"#10B981",
|
||||
"#22C55E",
|
||||
"#F59E0B",
|
||||
"#F97316",
|
||||
"#EF4444",
|
||||
"#EC4899",
|
||||
"#8B5CF6",
|
||||
"#64748B"
|
||||
];
|
||||
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: "DAILY_HALF_DAY" },
|
||||
{ label: "间隔小时内一次", value: "INTERVAL_HOUR" },
|
||||
{ label: "每个自然日一次", value: "NATURAL_DAY" },
|
||||
{ label: "每个自然月一次", value: "NATURAL_MONTH" },
|
||||
{ label: "事件总次数", value: "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({
|
||||
id: "",
|
||||
name: "",
|
||||
description: "",
|
||||
color: "#2f88ff",
|
||||
color: "#0f766e",
|
||||
status: "ACTIVE" as RegistrationEventStatus,
|
||||
requirePhoto: false,
|
||||
limitType: "NONE" as RegistrationLimitType,
|
||||
limitValueText: "",
|
||||
beginAtText: "",
|
||||
endAtText: "",
|
||||
sortText: "0",
|
||||
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 limitValuePlaceholder = computed(() => {
|
||||
if (formModel.limitType === "INTERVAL_HOUR") {
|
||||
@@ -251,6 +276,40 @@ function confirmColorPicker(): void {
|
||||
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 {
|
||||
return value === "INTERVAL_HOUR" || value === "TOTAL_LIMIT" || value === "CUSTOMER_TOTAL_LIMIT";
|
||||
}
|
||||
@@ -308,7 +367,6 @@ function buildPayload(): RegistrationEvent {
|
||||
limitValue: resolveLimitValue(),
|
||||
beginAt: resolveTimestamp(formModel.beginAtText),
|
||||
endAt: resolveTimestamp(formModel.endAtText),
|
||||
sort: Math.floor(Number(formModel.sortText) || 0),
|
||||
roleCodeList: [...formModel.roleCodeList]
|
||||
};
|
||||
}
|
||||
@@ -432,7 +490,6 @@ async function fillForm(): Promise<void> {
|
||||
limitValueText: current.limitValue ? String(current.limitValue) : "",
|
||||
beginAtText: current.beginAt ? Time.formatDateTime(new Date(current.beginAt)) : "",
|
||||
endAtText: current.endAt ? Time.formatDateTime(new Date(current.endAt)) : "",
|
||||
sortText: String(current.sort ?? 0),
|
||||
roleCodeList: [...(current.roleCodeList || [])]
|
||||
});
|
||||
colorDraft.value = formModel.color;
|
||||
@@ -453,6 +510,22 @@ onMounted(async () => {
|
||||
|
||||
.form {
|
||||
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 {
|
||||
@@ -461,10 +534,6 @@ onMounted(async () => {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.color-trigger {
|
||||
--td-button-extra-small-padding-horizontal: 2px;
|
||||
|
||||
@@ -482,7 +551,7 @@ onMounted(async () => {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
display: block;
|
||||
border-radius: .25rem;
|
||||
border-radius: .35rem;
|
||||
}
|
||||
|
||||
span {
|
||||
@@ -492,13 +561,6 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
.inline-options,
|
||||
.limit-options {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.roles {
|
||||
gap: .75rem;
|
||||
display: flex;
|
||||
@@ -555,6 +617,7 @@ onMounted(async () => {
|
||||
|
||||
.picker-popup {
|
||||
background: var(--td-bg-color-container);
|
||||
border-radius: 1.25rem 1.25rem 0 0;
|
||||
|
||||
.picker-header {
|
||||
display: flex;
|
||||
|
||||
@@ -56,6 +56,7 @@ import { Toast } from "tdesign-mobile-vue";
|
||||
import { Time, useUserStore, type Role } from "timi-web";
|
||||
import RegistrationEventAPI from "@/api/RegistrationEventAPI";
|
||||
import GaoUserAPI from "@/api/GaoUserAPI";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { RegistrationEvent, RegistrationLimitType } from "@/types/RegistrationEvent";
|
||||
import { gaoPermissionCodes } from "@/utils/gaoPermission";
|
||||
|
||||
@@ -64,6 +65,7 @@ defineOptions({
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const navBarStore = useNavBarStore();
|
||||
const userStore = useUserStore();
|
||||
const defaultColor = "#2f88ff";
|
||||
const registrationEventList = ref<RegistrationEvent[]>([]);
|
||||
@@ -74,6 +76,10 @@ function createRegistrationEvent(): void {
|
||||
router.push("/management/registration-events/create");
|
||||
}
|
||||
|
||||
async function goSort(): Promise<void> {
|
||||
await router.push("/management/registration-events/sort");
|
||||
}
|
||||
|
||||
function editRegistrationEvent(item: RegistrationEvent): void {
|
||||
if (!item.id) {
|
||||
return;
|
||||
@@ -111,9 +117,9 @@ function getLimitText(item: RegistrationEvent): string {
|
||||
const limitTextMap: Record<RegistrationLimitType, string> = {
|
||||
NONE: "不限次数",
|
||||
DAILY_HALF_DAY: "上午/下午各一次",
|
||||
NATURAL_DAY: "每天一次",
|
||||
INTERVAL_HOUR: `每隔 ${item.limitValue || 0} 小时`,
|
||||
NATURAL_MONTH: "每月一次",
|
||||
NATURAL_DAY: "每个自然日一次",
|
||||
NATURAL_MONTH: "每个自然月一次",
|
||||
TOTAL_LIMIT: `事件总上限 ${item.limitValue || 0} 次`,
|
||||
CUSTOMER_TOTAL_LIMIT: `单客户总上限 ${item.limitValue || 0} 次`
|
||||
};
|
||||
@@ -156,6 +162,10 @@ async function loadData(): Promise<void> {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
navBarStore.clearRight();
|
||||
if (canUpdateRegistrationEvent.value) {
|
||||
navBarStore.setRightText("排序", "var(--td-brand-color)", goSort);
|
||||
}
|
||||
try {
|
||||
await loadData();
|
||||
} catch {
|
||||
@@ -166,6 +176,10 @@ onMounted(async () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
navBarStore.clearRight();
|
||||
});
|
||||
</script>
|
||||
|
||||
<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>
|
||||
Reference in New Issue
Block a user