fix store diary style
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
/>
|
/>
|
||||||
</t-button>
|
</t-button>
|
||||||
<div
|
<div
|
||||||
|
v-if="showTypeLabel"
|
||||||
class="type-label white glass-black clip-text"
|
class="type-label white glass-black clip-text"
|
||||||
v-text="getAttachTypeText(item.attachType)"
|
v-text="getAttachTypeText(item.attachType)"
|
||||||
/>
|
/>
|
||||||
@@ -165,6 +166,7 @@ interface Props {
|
|||||||
showViewerActions?: boolean;
|
showViewerActions?: boolean;
|
||||||
showAdd?: boolean;
|
showAdd?: boolean;
|
||||||
showRemove?: boolean;
|
showRemove?: boolean;
|
||||||
|
showTypeLabel?: boolean;
|
||||||
submit?: (photoItems: PhotoItem[]) => Promise<void>;
|
submit?: (photoItems: PhotoItem[]) => Promise<void>;
|
||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
@@ -181,6 +183,7 @@ type ViewerPhotoItem = PhotoItem & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ATTACH_TYPE_TEXT_MAP: Record<string, string> = {
|
const ATTACH_TYPE_TEXT_MAP: Record<string, string> = {
|
||||||
|
ADDITIONAL: "附加照片",
|
||||||
PHOTO: "照片",
|
PHOTO: "照片",
|
||||||
POINT_CARD: "积分卡",
|
POINT_CARD: "积分卡",
|
||||||
OTHER: "其他文件"
|
OTHER: "其他文件"
|
||||||
@@ -196,6 +199,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
required: false,
|
required: false,
|
||||||
showAdd: true,
|
showAdd: true,
|
||||||
showRemove: true,
|
showRemove: true,
|
||||||
|
showTypeLabel: true,
|
||||||
showViewerActions: true
|
showViewerActions: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -423,7 +423,12 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.content) {
|
:deep(.content) {
|
||||||
|
padding: 0;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.toolbar) {
|
:deep(.toolbar) {
|
||||||
|
|||||||
@@ -11,6 +11,18 @@
|
|||||||
underline
|
underline
|
||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
|
<div v-if="additionalPhotoItemList.length" class="additional-photos-wrapper">
|
||||||
|
<photo-scroller
|
||||||
|
:model-value="additionalPhotoList"
|
||||||
|
:photo-items="additionalPhotoItemList"
|
||||||
|
:show-add="false"
|
||||||
|
:show-remove="false"
|
||||||
|
:show-type-label="false"
|
||||||
|
:show-viewer-actions="false"
|
||||||
|
title="附加照片"
|
||||||
|
v-entrance-transition="140"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<section class="content" v-entrance-transition="70">
|
<section class="content" v-entrance-transition="70">
|
||||||
<article-view
|
<article-view
|
||||||
:article="article"
|
:article="article"
|
||||||
@@ -57,11 +69,13 @@
|
|||||||
import { ArticleView } from "timi-tdesign-mobile";
|
import { ArticleView } from "timi-tdesign-mobile";
|
||||||
import { DialogPlugin } from "tdesign-mobile-vue";
|
import { DialogPlugin } from "tdesign-mobile-vue";
|
||||||
import { PassTimeLabel } from "timi-web";
|
import { PassTimeLabel } from "timi-web";
|
||||||
import { UserAPI, useUserStore } from "timi-web-api";
|
import { UserAPI, type Attachment, useUserStore } from "timi-web-api";
|
||||||
import StoreDiaryAPI from "@/api/StoreDiaryAPI";
|
import StoreDiaryAPI from "@/api/StoreDiaryAPI";
|
||||||
|
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { StoreDiary } from "@/types/StoreDiary";
|
import type { StoreDiary } from "@/types/StoreDiary";
|
||||||
|
import type { PhotoItem } from "@/types/Photo";
|
||||||
import { getAttachmentReadURL } from "@/utils/attachment";
|
import { getAttachmentReadURL } from "@/utils/attachment";
|
||||||
import { buildFallbackAvatar } from "@/utils/avatar";
|
import { buildFallbackAvatar } from "@/utils/avatar";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
@@ -87,6 +101,16 @@ const isLoading = ref(false);
|
|||||||
const isDeleting = ref(false);
|
const isDeleting = ref(false);
|
||||||
let loadToken = 0;
|
let loadToken = 0;
|
||||||
|
|
||||||
|
const additionalPhotoItemList = computed<PhotoItem[]>(() => (article.value?.attachmentList || [])
|
||||||
|
.filter(item => item.attachType === "ADDITIONAL" && !!item.id)
|
||||||
|
.map(item => ({
|
||||||
|
attachment: item as Partial<Attachment>,
|
||||||
|
attachType: "ADDITIONAL",
|
||||||
|
previewUrl: getAttachmentReadURL(item.id || ""),
|
||||||
|
url: getAttachmentReadURL(item.id || "", 90)
|
||||||
|
})));
|
||||||
|
const additionalPhotoList = computed(() => additionalPhotoItemList.value.map(item => item.url));
|
||||||
|
|
||||||
function resolveAttachmentUrl(attachmentId: string): string {
|
function resolveAttachmentUrl(attachmentId: string): string {
|
||||||
return getAttachmentReadURL(attachmentId);
|
return getAttachmentReadURL(attachmentId);
|
||||||
}
|
}
|
||||||
@@ -222,6 +246,12 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.additional-photos-wrapper {
|
||||||
|
margin: 1rem 1rem 0 1rem;
|
||||||
|
border-bottom: 1px solid rgba(148, 163, 184, .2);
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,26 @@
|
|||||||
placeholder="可填写本篇日记摘要"
|
placeholder="可填写本篇日记摘要"
|
||||||
/>
|
/>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
|
<section class="photo-wrapper" v-entrance-transition="140">
|
||||||
|
<photo-scroller
|
||||||
|
v-model="photoList"
|
||||||
|
v-model:photo-items="photoItemList"
|
||||||
|
title="附加照片"
|
||||||
|
:edit-mode="true"
|
||||||
|
:loading="isPhotoUploading"
|
||||||
|
:show-type-label="false"
|
||||||
|
:show-viewer-actions="false"
|
||||||
|
@add="openPhotoPicker"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
ref="photoInputRef"
|
||||||
|
class="file-input"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
multiple
|
||||||
|
@change="handlePhotoSelected"
|
||||||
|
>
|
||||||
|
</section>
|
||||||
<section class="editor-wrapper" v-entrance-transition="70">
|
<section class="editor-wrapper" v-entrance-transition="70">
|
||||||
<article-editor
|
<article-editor
|
||||||
ref="articleEditorRef"
|
ref="articleEditorRef"
|
||||||
@@ -44,13 +64,15 @@ import {
|
|||||||
type ArticleEditorExpose,
|
type ArticleEditorExpose,
|
||||||
type UploadResult
|
type UploadResult
|
||||||
} from "timi-tdesign-mobile";
|
} from "timi-tdesign-mobile";
|
||||||
import { AttachmentAPI, useUserStore } from "timi-web-api";
|
import { AttachmentAPI, type Attachment, useUserStore } from "timi-web-api";
|
||||||
import StoreDiaryAPI from "@/api/StoreDiaryAPI";
|
import StoreDiaryAPI from "@/api/StoreDiaryAPI";
|
||||||
|
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { StoreDiary } from "@/types/StoreDiary";
|
import type { StoreDiary } from "@/types/StoreDiary";
|
||||||
|
import type { PhotoItem } from "@/types/Photo";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import { compressUploadImage } from "@/utils/imageCompression";
|
import { compressUploadImage, compressUploadImageList } from "@/utils/imageCompression";
|
||||||
import { getAttachmentReadURL } from "@/utils/attachment";
|
import { getAttachmentReadURL } from "@/utils/attachment";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { permissionCodes } from "@/utils/permission";
|
import { permissionCodes } from "@/utils/permission";
|
||||||
@@ -105,6 +127,83 @@ function updateArticle(value: ArticleEditorArticle): void {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- 附加照片 ----------
|
||||||
|
|
||||||
|
const photoInputRef = ref<HTMLInputElement>();
|
||||||
|
const photoItemList = ref<PhotoItem[]>([]);
|
||||||
|
const isPhotoUploading = ref(false);
|
||||||
|
const photoList = computed<string[]>({
|
||||||
|
get: () => photoItemList.value.map(item => item.url),
|
||||||
|
set: value => {
|
||||||
|
const urlSet = new Set(value);
|
||||||
|
photoItemList.value = photoItemList.value.filter(item => urlSet.has(item.url));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function openPhotoPicker(): void {
|
||||||
|
photoInputRef.value?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePhotoSelected(event: Event): Promise<void> {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
const fileList = Array.from(input.files || []);
|
||||||
|
if (!fileList.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPhotoUploading.value = true;
|
||||||
|
try {
|
||||||
|
const tempFileList = await AttachmentAPI.upload(await compressUploadImageList(fileList));
|
||||||
|
for (const tempFile of tempFileList) {
|
||||||
|
if (!tempFile.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
photoItemList.value.push({
|
||||||
|
tempFileId: tempFile.id,
|
||||||
|
attachType: "ADDITIONAL",
|
||||||
|
url: AttachmentAPI.getTempReadURL(tempFile.id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
Message.error(getErrorMessage(error, "附加照片上传失败"));
|
||||||
|
} finally {
|
||||||
|
isPhotoUploading.value = false;
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPhotoItemList(attachmentList?: StoreDiary["attachmentList"]): PhotoItem[] {
|
||||||
|
return (attachmentList || []).flatMap(item => {
|
||||||
|
if (item.attachType !== "ADDITIONAL" || !item.id) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [{
|
||||||
|
attachment: item as Partial<Attachment>,
|
||||||
|
attachType: "ADDITIONAL",
|
||||||
|
previewUrl: getAttachmentReadURL(item.id),
|
||||||
|
url: getAttachmentReadURL(item.id, 90)
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAdditionalAttachmentList(): Attachment[] {
|
||||||
|
return photoItemList.value.flatMap(item => {
|
||||||
|
if (item.attachment) {
|
||||||
|
return [{
|
||||||
|
...item.attachment,
|
||||||
|
attachType: "ADDITIONAL"
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
if (item.tempFileId) {
|
||||||
|
return [{
|
||||||
|
attachType: "ADDITIONAL",
|
||||||
|
tempFileId: item.tempFileId
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const selectedStoreName = computed(() => {
|
const selectedStoreName = computed(() => {
|
||||||
const store = currentStoreStore.storeList.find(item => item.id === article.value.bizId)
|
const store = currentStoreStore.storeList.find(item => item.id === article.value.bizId)
|
||||||
|| (currentStoreStore.loginStore?.id === article.value.bizId ? currentStoreStore.loginStore : undefined);
|
|| (currentStoreStore.loginStore?.id === article.value.bizId ? currentStoreStore.loginStore : undefined);
|
||||||
@@ -187,7 +286,10 @@ async function save(): Promise<void> {
|
|||||||
description: article.value.description?.trim() || "",
|
description: article.value.description?.trim() || "",
|
||||||
contentType: normalizeContentType(editorValue?.contentType ?? article.value.contentType),
|
contentType: normalizeContentType(editorValue?.contentType ?? article.value.contentType),
|
||||||
content: editorValue?.content ?? article.value.content ?? "",
|
content: editorValue?.content ?? article.value.content ?? "",
|
||||||
attachmentList: editorValue?.attachmentList ?? article.value.attachmentList ?? [],
|
attachmentList: [
|
||||||
|
...(editorValue?.attachmentList ?? article.value.attachmentList ?? []).filter(item => item.attachType !== "ADDITIONAL"),
|
||||||
|
...buildAdditionalAttachmentList()
|
||||||
|
],
|
||||||
status: article.value.status || "NORMAL"
|
status: article.value.status || "NORMAL"
|
||||||
};
|
};
|
||||||
if (!validateArticle(value)) {
|
if (!validateArticle(value)) {
|
||||||
@@ -234,6 +336,7 @@ async function loadArticle(): Promise<void> {
|
|||||||
...createEmptyArticle(),
|
...createEmptyArticle(),
|
||||||
bizId: article.value.bizId || currentStoreStore.currentStoreId
|
bizId: article.value.bizId || currentStoreStore.currentStoreId
|
||||||
};
|
};
|
||||||
|
photoItemList.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,6 +349,7 @@ async function loadArticle(): Promise<void> {
|
|||||||
content: result.content || "",
|
content: result.content || "",
|
||||||
attachmentList: result.attachmentList || []
|
attachmentList: result.attachmentList || []
|
||||||
};
|
};
|
||||||
|
photoItemList.value = buildPhotoItemList(result.attachmentList);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -278,9 +382,10 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
|
|
||||||
.editor-wrapper {
|
.editor-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
overflow: visible;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow: visible;
|
border-top: 1px solid var(--td-component-border);
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -292,23 +397,28 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
|
|
||||||
:deep(.tp-editor) {
|
:deep(.tp-editor) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.content) {
|
:deep(.content) {
|
||||||
|
padding: 0;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
|
||||||
|
.value {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.toolbar) {
|
:deep(.toolbar) {
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
top: var(--app-nav-height);
|
top: var(--app-nav-height);
|
||||||
|
width: 100%;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.toolbar .scroll) {
|
:deep(.toolbar .scroll) {
|
||||||
@@ -317,6 +427,14 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.photo-wrapper {
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
.file-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
:key="calendarRenderKey"
|
:key="calendarRenderKey"
|
||||||
class="calendar-view"
|
class="calendar-view"
|
||||||
:value="selectedCalendarDate"
|
:value="selectedCalendarDate"
|
||||||
|
:min-date="calendarMinDate"
|
||||||
:format="calendarFormat"
|
:format="calendarFormat"
|
||||||
switch-mode="month"
|
switch-mode="month"
|
||||||
:use-popup="false"
|
:use-popup="false"
|
||||||
@@ -26,17 +27,7 @@
|
|||||||
class="item"
|
class="item"
|
||||||
v-entrance-transition="index * 30"
|
v-entrance-transition="index * 30"
|
||||||
>
|
>
|
||||||
<t-cell
|
<store-diary-list-item :diary="item" @select="toDetail" />
|
||||||
class="cell"
|
|
||||||
:title="getTitle(item)"
|
|
||||||
:description="getDescription(item)"
|
|
||||||
arrow
|
|
||||||
@click="toDetail(item)"
|
|
||||||
>
|
|
||||||
<template #note>
|
|
||||||
<pass-time-label :timestamp="item.updatedAt || item.createdAt" underline />
|
|
||||||
</template>
|
|
||||||
</t-cell>
|
|
||||||
<template v-if="canDelete" #right>
|
<template v-if="canDelete" #right>
|
||||||
<t-button
|
<t-button
|
||||||
class="swipe-cell-button"
|
class="swipe-cell-button"
|
||||||
@@ -62,21 +53,14 @@
|
|||||||
@left-button-click="closeDiaryPopup"
|
@left-button-click="closeDiaryPopup"
|
||||||
>
|
>
|
||||||
<div class="diary-popup">
|
<div class="diary-popup">
|
||||||
<t-cell-group v-if="selectedDateDiaries.length" theme="card">
|
<t-cell-group v-if="selectedDateDiaries.length">
|
||||||
<t-cell
|
<store-diary-list-item
|
||||||
v-for="(item, index) in selectedDateDiaries"
|
v-for="(item, index) in selectedDateDiaries"
|
||||||
:key="item.id || index"
|
:key="item.id || index"
|
||||||
class="cell"
|
:diary="item"
|
||||||
:title="getTitle(item)"
|
|
||||||
:description="getDescription(item)"
|
|
||||||
arrow
|
|
||||||
hover
|
hover
|
||||||
@click="toSelectedDiary(item)"
|
@select="toSelectedDiary"
|
||||||
>
|
/>
|
||||||
<template #note>
|
|
||||||
<pass-time-label :timestamp="item.updatedAt || item.createdAt" underline />
|
|
||||||
</template>
|
|
||||||
</t-cell>
|
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<div v-else class="popup-empty" v-text="'该日期暂无门店日记'" />
|
<div v-else class="popup-empty" v-text="'该日期暂无门店日记'" />
|
||||||
</div>
|
</div>
|
||||||
@@ -87,10 +71,11 @@
|
|||||||
import { BottomPopup } from "timi-tdesign-mobile";
|
import { BottomPopup } from "timi-tdesign-mobile";
|
||||||
import { type CalendarProps, DialogPlugin, type TDate } from "tdesign-mobile-vue";
|
import { type CalendarProps, DialogPlugin, type TDate } from "tdesign-mobile-vue";
|
||||||
import { type PageResult, useUserStore } from "timi-web-api";
|
import { type PageResult, useUserStore } from "timi-web-api";
|
||||||
import { PassTimeLabel, Time } from "timi-web";
|
import { Time } from "timi-web";
|
||||||
import StoreDiaryAPI, { type StoreDiaryListOptions } from "@/api/StoreDiaryAPI";
|
import StoreDiaryAPI, { type StoreDiaryListOptions } from "@/api/StoreDiaryAPI";
|
||||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
|
import StoreDiaryListItem from "@/pages/store/component/StoreDiaryListItem.vue";
|
||||||
import type { NavBarAction } from "@/types/NavBarAction";
|
import type { NavBarAction } from "@/types/NavBarAction";
|
||||||
import type { StoreDiary } from "@/types/StoreDiary";
|
import type { StoreDiary } from "@/types/StoreDiary";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
@@ -102,6 +87,7 @@ defineOptions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
const navBarStore = useNavBarStore();
|
const navBarStore = useNavBarStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const currentStoreStore = useCurrentStoreStore();
|
const currentStoreStore = useCurrentStoreStore();
|
||||||
@@ -112,10 +98,12 @@ const canShowActionMenu = computed(() => canCreate.value);
|
|||||||
// ---------- 日历 ----------
|
// ---------- 日历 ----------
|
||||||
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const selectedCalendarDate = ref<Date>(today);
|
const calendarMinDate = new Date(today.getFullYear() - 2, 0, 1);
|
||||||
|
const initialCalendarDate = parseCalendarDate(route.query.calendarDate);
|
||||||
|
const selectedCalendarDate = ref<Date>(initialCalendarDate);
|
||||||
const calendarMonth = reactive({
|
const calendarMonth = reactive({
|
||||||
year: today.getFullYear(),
|
year: initialCalendarDate.getFullYear(),
|
||||||
month: today.getMonth() + 1
|
month: initialCalendarDate.getMonth() + 1
|
||||||
});
|
});
|
||||||
const calendarRenderKey = ref(0);
|
const calendarRenderKey = ref(0);
|
||||||
const calendarDiaryList = ref<StoreDiary[]>([]);
|
const calendarDiaryList = ref<StoreDiary[]>([]);
|
||||||
@@ -290,12 +278,24 @@ function getDiaryDateText(item: StoreDiary): string {
|
|||||||
return date ? Time.formatDate(date) : "";
|
return date ? Time.formatDate(date) : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTitle(item: StoreDiary): string {
|
function parseCalendarDate(value: unknown): Date {
|
||||||
return item.title?.trim() || "未命名日记";
|
const text = Array.isArray(value) ? String(value[0] || "") : String(value || "");
|
||||||
|
const [year, month, day] = text.split("-").map(Number);
|
||||||
|
const date = new Date(year, month - 1, day);
|
||||||
|
if (
|
||||||
|
!Number.isInteger(year)
|
||||||
|
|| !Number.isInteger(month)
|
||||||
|
|| !Number.isInteger(day)
|
||||||
|
|| month < 1
|
||||||
|
|| 12 < month
|
||||||
|
|| day < 1
|
||||||
|
|| date.getFullYear() !== year
|
||||||
|
|| date.getMonth() + 1 !== month
|
||||||
|
|| date.getDate() !== day
|
||||||
|
) {
|
||||||
|
return today;
|
||||||
}
|
}
|
||||||
|
return date;
|
||||||
function getDescription(item: StoreDiary): string {
|
|
||||||
return item.description?.trim() || "暂无摘要";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 导航 ----------
|
// ---------- 导航 ----------
|
||||||
@@ -347,6 +347,13 @@ async function toDetail(item: StoreDiary): Promise<void> {
|
|||||||
if (!item.id) {
|
if (!item.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await router.replace({
|
||||||
|
name: "StoreDiaryList",
|
||||||
|
query: {
|
||||||
|
...route.query,
|
||||||
|
calendarDate: getDiaryDateText(item) || Time.formatDate(selectedCalendarDate.value)
|
||||||
|
}
|
||||||
|
});
|
||||||
await router.push({
|
await router.push({
|
||||||
name: "StoreDiaryDetail",
|
name: "StoreDiaryDetail",
|
||||||
params: { id: item.id }
|
params: { id: item.id }
|
||||||
@@ -363,7 +370,7 @@ async function confirmDelete(item: StoreDiary): Promise<void> {
|
|||||||
const diaryId = item.id;
|
const diaryId = item.id;
|
||||||
const dialog = DialogPlugin.confirm({
|
const dialog = DialogPlugin.confirm({
|
||||||
title: "删除门店日记",
|
title: "删除门店日记",
|
||||||
content: `确定删除“${getTitle(item)}”吗?删除后无法恢复。`,
|
content: `确定删除“${item.title?.trim() || "未命名日记"}”吗?删除后无法恢复。`,
|
||||||
cancelBtn: "取消",
|
cancelBtn: "取消",
|
||||||
confirmBtn: "删除",
|
confirmBtn: "删除",
|
||||||
onCancel() {
|
onCancel() {
|
||||||
@@ -463,28 +470,6 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.cell {
|
|
||||||
:deep(.t-cell__title) {
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.t-cell__title-text),
|
|
||||||
:deep(.t-cell__description) {
|
|
||||||
overflow: hidden;
|
|
||||||
min-width: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.t-cell__note) {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
min-width: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.diary-popup {
|
.diary-popup {
|
||||||
padding-bottom: .5rem;
|
padding-bottom: .5rem;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<t-cell
|
||||||
|
class="cell"
|
||||||
|
:title="getTitle(props.diary)"
|
||||||
|
:description="getDescription(props.diary)"
|
||||||
|
:arrow="true"
|
||||||
|
:hover="props.hover"
|
||||||
|
@click="handleSelect"
|
||||||
|
>
|
||||||
|
<template #leftIcon>
|
||||||
|
<t-image
|
||||||
|
v-if="getCoverUrl(props.diary)"
|
||||||
|
:src="getCoverUrl(props.diary)"
|
||||||
|
alt="文章首图"
|
||||||
|
fit="cover"
|
||||||
|
class="cover"
|
||||||
|
/>
|
||||||
|
<div v-else class="cover placeholder">
|
||||||
|
<t-icon name="article" size="2rem" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #note>
|
||||||
|
<pass-time-label :timestamp="props.diary.updatedAt || props.diary.createdAt" underline />
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { PassTimeLabel } from "timi-web";
|
||||||
|
import type { StoreDiary } from "@/types/StoreDiary";
|
||||||
|
import { getAttachmentReadURL } from "@/utils/attachment";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "StoreDiaryListItem"
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
diary: StoreDiary;
|
||||||
|
hover?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
hover: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
select: [diary: StoreDiary];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function getTitle(item: StoreDiary): string {
|
||||||
|
return item.title?.trim() || "未命名日记";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDescription(item: StoreDiary): string {
|
||||||
|
return item.description?.trim() || "暂无摘要";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCoverUrl(item: StoreDiary): string {
|
||||||
|
const attachment = item.attachmentList?.[0];
|
||||||
|
return attachment?.id ? getAttachmentReadURL(attachment.id, 128) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelect(): void {
|
||||||
|
emit("select", props.diary);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.cell {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
:deep(.t-cell__title) {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.t-cell__title-text),
|
||||||
|
:deep(.t-cell__description) {
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.t-cell__note) {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
flex: none;
|
||||||
|
width: 3.5rem;
|
||||||
|
height: 3.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
color: var(--td-text-color-placeholder);
|
||||||
|
display: flex;
|
||||||
|
background: var(--td-bg-color-secondarycontainer);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user