add store auto closed day

This commit is contained in:
Timi
2026-08-12 12:03:47 +08:00
parent 171aee1602
commit 2a64f2ad72
6 changed files with 95 additions and 93 deletions
+14 -11
View File
@@ -15,6 +15,7 @@
</div> </div>
</header> </header>
<div class="calendar-card"> <div class="calendar-card">
<card-title description="设置门店休息日,该日无任何登记记录不视为中断连续登记事件" />
<t-calendar <t-calendar
:key="calendarRenderKey" :key="calendarRenderKey"
class="calendar" class="calendar"
@@ -39,19 +40,19 @@
</div> </div>
<t-form label-align="left"> <t-form label-align="left">
<t-form-item label="状态"> <t-form-item label="状态">
<t-radio-group v-model="formModel.openStatus" variant="default-filled"> <t-radio-group v-model="formModel.status" variant="default-filled">
<t-radio :block="false" borderless value="OPEN">营业</t-radio> <t-radio :block="false" borderless value="OPEN">营业</t-radio>
<t-radio :block="false" borderless value="CLOSED">休息</t-radio> <t-radio :block="false" borderless value="CLOSED">休息</t-radio>
</t-radio-group> </t-radio-group>
</t-form-item> </t-form-item>
<t-form-item <t-form-item
class="reason" class="reason"
:class="{ 'is-hidden': formModel.openStatus !== 'CLOSED' }" :class="{ 'is-hidden': formModel.status !== 'CLOSED' }"
label="原因" label="原因"
> >
<t-input <t-input
v-model.trim="formModel.reason" v-model.trim="formModel.reason"
:disabled="formModel.openStatus !== 'CLOSED'" :disabled="formModel.status !== 'CLOSED'"
placeholder="休息原因或备注" placeholder="休息原因或备注"
borderless borderless
/> />
@@ -84,7 +85,7 @@
import { DialogPlugin, Toast, type CalendarProps, type TDate } from "tdesign-mobile-vue"; import { DialogPlugin, Toast, type CalendarProps, type TDate } from "tdesign-mobile-vue";
import { useUserStore } from "timi-web"; import { useUserStore } from "timi-web";
import StoreAPI from "@/api/StoreAPI"; import StoreAPI from "@/api/StoreAPI";
import type { Store, StoreBusinessDay, StoreBusinessDayOpenStatus } from "@/types/Store"; import type { Store, StoreBusinessDay, StoreBusinessDayStatus } from "@/types/Store";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission"; import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
defineOptions({ defineOptions({
@@ -130,8 +131,8 @@ const calendarFormat = computed<NonNullable<CalendarProps["format"]>>(() => {
} }
return { return {
...dateItem, ...dateItem,
className: `business-day-${day.openStatus!.toLowerCase()}`, className: `business-day-${day.status!.toLowerCase()}`,
suffix: day.openStatus === "OPEN" ? "营" : "休" suffix: day.status === "OPEN" ? "营" : "休"
}; };
}; };
}); });
@@ -176,7 +177,7 @@ const isDeleting = ref(false);
const editingId = ref(""); const editingId = ref("");
const formModel = reactive({ const formModel = reactive({
dateText: "", dateText: "",
openStatus: "OPEN" as StoreBusinessDayOpenStatus, status: "OPEN" as StoreBusinessDayStatus,
reason: "正常休息" reason: "正常休息"
}); });
const savePermissionCode = computed(() => { const savePermissionCode = computed(() => {
@@ -192,7 +193,7 @@ function openDayEditor(day?: StoreBusinessDay, dateValue = toDateValue(new Date(
editingId.value = day?.id || ""; editingId.value = day?.id || "";
Object.assign(formModel, { Object.assign(formModel, {
dateText: formatDateValue(day?.dateValue || dateValue), dateText: formatDateValue(day?.dateValue || dateValue),
openStatus: day?.openStatus || "OPEN", status: day?.status || "OPEN",
reason: day ? day.reason : "正常休息" reason: day ? day.reason : "正常休息"
}); });
editorVisible.value = true; editorVisible.value = true;
@@ -215,8 +216,8 @@ async function saveDay(): Promise<void> {
id: editingId.value || undefined, id: editingId.value || undefined,
storeId: storeId.value, storeId: storeId.value,
dateValue: parseDateValue(formModel.dateText), dateValue: parseDateValue(formModel.dateText),
openStatus: formModel.openStatus, status: formModel.status,
reason: formModel.openStatus === "CLOSED" ? formModel.reason || undefined : undefined reason: formModel.status === "CLOSED" ? formModel.reason || undefined : undefined
}; };
if (editingId.value) { if (editingId.value) {
await StoreAPI.updateBusinessDay(day); await StoreAPI.updateBusinessDay(day);
@@ -317,10 +318,11 @@ onMounted(async () => {
.store-business-day-calendar { .store-business-day-calendar {
gap: .75rem; gap: .75rem;
display: grid; display: grid;
padding: calc(var(--app-nav-height) + 1rem) 1rem 6rem; padding: calc(var(--app-nav-height) + 1rem) 0 6rem;
.summary { .summary {
gap: .75rem; gap: .75rem;
margin: 0 1rem;
display: flex; display: flex;
padding: .75rem; padding: .75rem;
border-radius: .75rem; border-radius: .75rem;
@@ -350,6 +352,7 @@ onMounted(async () => {
} }
:deep(.t-calendar) { :deep(.t-calendar) {
margin: 0 1rem;
z-index: 1; z-index: 1;
overflow: hidden; overflow: hidden;
border-radius: .75rem; border-radius: .75rem;
+58 -75
View File
@@ -2,26 +2,39 @@
<div v-can="savePermissionCode" class="store-edit"> <div v-can="savePermissionCode" class="store-edit">
<t-form class="form" label-align="left"> <t-form class="form" label-align="left">
<t-form-item v-if="!isStoreManagerScope" label="编码"> <t-form-item v-if="!isStoreManagerScope" label="编码">
<t-input v-model.trim="formModel.code" placeholder="门店编码" borderless /> <t-input v-model.trim="formData.code" placeholder="门店编码" borderless />
</t-form-item> </t-form-item>
<t-form-item label="名称"> <t-form-item label="名称">
<t-input v-model.trim="formModel.name" placeholder="门店名称" borderless /> <t-input v-model.trim="formData.name" placeholder="门店名称" borderless />
</t-form-item> </t-form-item>
<t-form-item v-if="!isStoreManagerScope" label="启用" content-align="right"> <t-form-item v-if="!isStoreManagerScope" label="启用" content-align="right">
<t-switch v-model="formModel.status" :custom-value="['ACTIVE', 'INACTIVE']" /> <t-switch v-model="formData.status" :custom-value="['ACTIVE', 'INACTIVE']" />
</t-form-item> </t-form-item>
<t-form-item label="电话"> <t-form-item label="电话">
<t-input v-model.trim="formModel.telephone" type="tel" placeholder="联系电话" borderless /> <t-input v-model.trim="formData.telephone" type="tel" placeholder="联系电话" borderless />
</t-form-item> </t-form-item>
<t-form-item label="地址"> <t-form-item label="地址">
<t-input v-model.trim="formModel.address" placeholder="门店地址" borderless /> <t-input v-model.trim="formData.address" placeholder="门店地址" borderless />
</t-form-item> </t-form-item>
<t-form-item label="备注"> <t-form-item label="备注">
<t-textarea v-model.trim="formModel.remark" class="textarea" placeholder="备注" autosize borderless /> <t-textarea v-model.trim="formData.remark" class="textarea" placeholder="备注" autosize borderless />
</t-form-item> </t-form-item>
</t-form> </t-form>
<t-cell-group v-if="!isCreateMode" theme="card"> <t-cell-group v-if="!isCreateMode" theme="card">
<t-cell title="营业日管理" note="配置营业或休息日期" arrow hover @click="router.push(`/management/stores/${routeId}/business-days`)" /> <t-cell
title="营业日管理"
note="配置营业或休息日期"
arrow
@click="router.push(`/management/stores/${routeId}/business-days`)"
/>
<t-cell
title="自动休息日"
description="全天无任何登记记录时自动判定为休息日,休息日不视为中断连续登记事件"
>
<template #right-icon>
<t-switch v-model="formData.autoClosedDay" />
</template>
</t-cell>
</t-cell-group> </t-cell-group>
<div class="actions"> <div class="actions">
<t-button <t-button
@@ -36,7 +49,7 @@
class="action" class="action"
theme="primary" theme="primary"
:loading="isSaving" :loading="isSaving"
@click="saveStore" @click="doSave"
>保存</t-button> >保存</t-button>
</div> </div>
</div> </div>
@@ -46,7 +59,8 @@
import { DialogPlugin, Toast } from "tdesign-mobile-vue"; import { DialogPlugin, Toast } from "tdesign-mobile-vue";
import { useUserStore } from "timi-web"; import { useUserStore } from "timi-web";
import StoreAPI from "@/api/StoreAPI"; import StoreAPI from "@/api/StoreAPI";
import type { Store, StoreStatus } from "@/types/Store"; import { useCurrentStoreStore } from "@/store/currentStoreStore";
import type { StoreStatus } from "@/types/Store";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission"; import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
defineOptions({ defineOptions({
@@ -56,6 +70,7 @@ defineOptions({
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const currentStoreStore = useCurrentStoreStore();
const routeId = computed(() => String(route.params.id || "")); const routeId = computed(() => String(route.params.id || ""));
const isStoreManagerScope = computed(() => userStore.hasRoles(gaoRoleCodes.storeManager) && !userStore.hasRoles(gaoRoleCodes.globalManager)); const isStoreManagerScope = computed(() => userStore.hasRoles(gaoRoleCodes.storeManager) && !userStore.hasRoles(gaoRoleCodes.globalManager));
@@ -68,105 +83,81 @@ const savePermissionCode = computed(() => isCreateMode.value ? permissionCodes.s
const isSaving = ref(false); const isSaving = ref(false);
const isRemoving = ref(false); const isRemoving = ref(false);
const formModel = reactive({ const formData = reactive({
id: "", id: "",
code: "", code: "",
name: "", name: "",
status: "ACTIVE" as StoreStatus, status: "ACTIVE" as StoreStatus,
telephone: "", telephone: "",
address: "", address: "",
remark: "" remark: "",
autoClosedDay: true
}); });
const sortValue = ref(0);
// ---------- 页面数据 ---------- async function doSave(): Promise<void> {
async function fillForm(): Promise<void> {
if (!routeId.value) {
return;
}
const current = await StoreAPI.detail(routeId.value);
Object.assign(formModel, {
id: current.id || "",
code: current.code || "",
name: current.name || "",
status: current.status || "ACTIVE",
telephone: current.telephone || "",
address: current.address || "",
remark: current.remark || ""
});
sortValue.value = current.sort ?? 0;
}
// ---------- 保存 ----------
async function saveStore(): Promise<void> {
if (!validateForm()) { if (!validateForm()) {
return; return;
} }
isSaving.value = true; isSaving.value = true;
try { try {
const store = buildStorePayload();
if (isCreateMode.value) { if (isCreateMode.value) {
await StoreAPI.create(store); await StoreAPI.create(formData);
} else { } else {
await StoreAPI.update(store); await StoreAPI.update(formData);
await refreshCurrentStore();
} }
Toast.success("门店已保存"); Toast.success("门店已保存");
if (isStoreManagerScope.value) { if (userStore.hasPermissions(permissionCodes.store.create)) {
return;
}
await router.replace("/management/stores"); await router.replace("/management/stores");
} else {
await router.replace("/store");
}
} finally { } finally {
isSaving.value = false; isSaving.value = false;
} }
} }
function buildStorePayload(): Store { async function refreshCurrentStore(): Promise<void> {
if (isStoreManagerScope.value) { if (formData.id !== currentStoreStore.currentStoreId) {
return { return;
id: formModel.id || undefined,
name: formModel.name,
telephone: formModel.telephone || undefined,
address: formModel.address || undefined,
remark: formModel.remark || undefined
};
} }
return { await currentStoreStore.refreshCurrentStore(formData.id);
id: formModel.id || undefined,
code: formModel.code,
name: formModel.name,
status: formModel.status,
telephone: formModel.telephone || undefined,
address: formModel.address || undefined,
remark: formModel.remark || undefined,
sort: sortValue.value
};
} }
function validateForm(): boolean { function validateForm(): boolean {
if (!isStoreManagerScope.value && !formModel.code) { if (!isStoreManagerScope.value && !formData.code) {
Toast.warning("请填写门店编码"); Toast.warning("请填写门店编码");
return false; return false;
} }
if (!formModel.name) { if (!formData.name) {
Toast.warning("请填写门店名称"); Toast.warning("请填写门店名称");
return false; return false;
} }
return true; return true;
} }
async function confirmRemoveStore(): Promise<void> { onMounted(async () => {
if (!formModel.id) { try {
if (!routeId.value) {
return; return;
} }
const current = await StoreAPI.detail(routeId.value);
Object.assign(formData, current);
} catch {
Toast.error("门店数据加载失败");
}
});
// ---------- 删除 ----------
async function confirmRemoveStore(): Promise<void> {
if (!formData.id) {
return;
}
const dialog = DialogPlugin.confirm({ const dialog = DialogPlugin.confirm({
title: "删除门店", title: "删除门店",
content: `确定删除 ${formModel.name} 吗?删除后无法恢复。`, content: `确定删除 ${formData.name} 吗?删除后无法恢复。`,
cancelBtn: "取消", cancelBtn: "取消",
confirmBtn: "删除", confirmBtn: "删除",
onCancel() { onCancel() {
@@ -186,21 +177,13 @@ async function removeStore(): Promise<void> {
isRemoving.value = true; isRemoving.value = true;
try { try {
await StoreAPI.remove(formModel.id); await StoreAPI.remove(formData.id);
Toast.success("门店已删除"); Toast.success("门店已删除");
await router.replace("/management/stores"); await router.replace("/management/stores");
} finally { } finally {
isRemoving.value = false; isRemoving.value = false;
} }
} }
onMounted(async () => {
try {
await fillForm();
} catch {
Toast.error("门店数据加载失败");
}
});
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
-1
View File
@@ -43,7 +43,6 @@
</t-cell> </t-cell>
</t-cell-group> </t-cell-group>
<footer class="footer"> <footer class="footer">
<p>版本 1.0.0</p>
<p>技术支持:夜雨</p> <p>技术支持:夜雨</p>
<p>Copyright © 2026 imyeyu.com</p> <p>Copyright © 2026 imyeyu.com</p>
</footer> </footer>
+16
View File
@@ -101,6 +101,21 @@ export const useCurrentStoreStore = defineStore("currentStore", () => {
} }
} }
async function refreshCurrentStore(storeId = currentStoreId.value): Promise<void> {
if (!storeId || storeId !== currentStoreId.value) {
return;
}
const result = await StoreAPI.detail(storeId);
const index = storeList.value.findIndex(item => item.id === storeId);
if (-1 < index) {
storeList.value.splice(index, 1, result);
}
if (loginStore.value?.id === storeId) {
loginStore.value = result;
}
}
function selectStore(storeId: string): void { function selectStore(storeId: string): void {
if (!storeId) { if (!storeId) {
return; return;
@@ -133,6 +148,7 @@ export const useCurrentStoreStore = defineStore("currentStore", () => {
loadStoreList, loadStoreList,
loadLoginStore, loadLoginStore,
loadAfterLogin, loadAfterLogin,
refreshCurrentStore,
selectStore selectStore
}; };
}); });
+3 -2
View File
@@ -1,7 +1,7 @@
import type { BasePage, Model } from "timi-web"; import type { BasePage, Model } from "timi-web";
export type StoreStatus = "ACTIVE" | "INACTIVE" | "DELETED"; export type StoreStatus = "ACTIVE" | "INACTIVE" | "DELETED";
export type StoreBusinessDayOpenStatus = "OPEN" | "CLOSED"; export type StoreBusinessDayStatus = "OPEN" | "CLOSED";
export type Store = { export type Store = {
code?: string; code?: string;
@@ -10,13 +10,14 @@ export type Store = {
telephone?: string; telephone?: string;
address?: string; address?: string;
remark?: string; remark?: string;
autoClosedDay?: boolean;
sort?: number; sort?: number;
} & Model; } & Model;
export type StoreBusinessDay = { export type StoreBusinessDay = {
storeId?: string; storeId?: string;
dateValue?: number; dateValue?: number;
openStatus?: StoreBusinessDayOpenStatus; status?: StoreBusinessDayStatus;
businessDayNo?: number; businessDayNo?: number;
reason?: string; reason?: string;
} & Model; } & Model;
+4 -4
View File
@@ -1,4 +1,4 @@
import type { StoreBusinessDayOpenStatus, StoreStatus } from "@/types/Store"; import type { StoreBusinessDayStatus, StoreStatus } from "@/types/Store";
import { permissionCodes } from "@/utils/permission"; import { permissionCodes } from "@/utils/permission";
import { type Page, useUserStore } from "timi-web"; import { type Page, useUserStore } from "timi-web";
@@ -85,15 +85,15 @@ export function getStoreStatusTheme(status?: StoreStatus): "success" | "warning"
return "default"; return "default";
} }
export function getBusinessDayStatusText(status?: StoreBusinessDayOpenStatus): string { export function getBusinessDayStatusText(status?: StoreBusinessDayStatus): string {
const map: Record<StoreBusinessDayOpenStatus, string> = { const map: Record<StoreBusinessDayStatus, string> = {
OPEN: "营业", OPEN: "营业",
CLOSED: "休息" CLOSED: "休息"
}; };
return status ? map[status] : "未配置"; return status ? map[status] : "未配置";
} }
export function getBusinessDayStatusTheme(status?: StoreBusinessDayOpenStatus): "success" | "warning" | "default" { export function getBusinessDayStatusTheme(status?: StoreBusinessDayStatus): "success" | "warning" | "default" {
if (status === "OPEN") { if (status === "OPEN") {
return "success"; return "success";
} }