support solid cover product
This commit is contained in:
@@ -52,7 +52,7 @@
|
||||
<photo-scroller
|
||||
v-model="attachmentUrlList"
|
||||
v-model:photo-items="attachmentItemList"
|
||||
title="附件列表"
|
||||
title="附件列表(取第一张图片作为封面)"
|
||||
:edit-mode="true"
|
||||
:loading="isAttachmentUploading"
|
||||
:show-viewer-actions="false"
|
||||
@@ -61,6 +61,24 @@
|
||||
@add-other="openAttachmentPicker('OTHER')"
|
||||
/>
|
||||
</t-form-item>
|
||||
<t-form-item label="使用纯色作为封面" label-width="8rem" content-align="right">
|
||||
<t-button
|
||||
size="extra-small"
|
||||
variant="outline"
|
||||
class="color-trigger"
|
||||
:disabled="isAttachmentUploading"
|
||||
@click="openCoverColorPicker"
|
||||
>
|
||||
<div class="content">
|
||||
<span
|
||||
class="swatch"
|
||||
:class="{ empty: !solidColorCoverColor }"
|
||||
:style="{ background: solidColorCoverColor || undefined }"
|
||||
/>
|
||||
<span v-text="solidColorCoverColor || '选择颜色'" />
|
||||
</div>
|
||||
</t-button>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<card-container
|
||||
title="产品单位"
|
||||
@@ -75,8 +93,8 @@
|
||||
>
|
||||
<t-form-item label="单位名称" required-mark>
|
||||
<t-input v-model.trim="unit.name" borderless maxlength="32" placeholder="如 瓶、次" />
|
||||
<!-- <t-button v-if="1 < form.unitList.length" theme="danger" variant="text" size="small" @click="removeUnit(index)">删除</t-button>-->
|
||||
<t-button
|
||||
v-if="1 < form.unitList.length"
|
||||
theme="danger"
|
||||
variant="text"
|
||||
size="extra-small"
|
||||
@@ -139,11 +157,29 @@
|
||||
<t-popup v-model="statusPickerVisible" placement="bottom">
|
||||
<t-picker v-model="statusDraft" :columns="statusOptions" title="产品状态" @cancel="statusPickerVisible = false" @confirm="confirmStatus" />
|
||||
</t-popup>
|
||||
<bottom-popup
|
||||
v-model="coverColorPickerVisible"
|
||||
title="颜色选择"
|
||||
:right-button-text="isAttachmentUploading ? '上传中' : '确定'"
|
||||
@left-button-click="cancelCoverColorPicker"
|
||||
@right-button-click="confirmCoverColorPicker"
|
||||
>
|
||||
<t-color-picker
|
||||
v-if="coverColorPickerVisible"
|
||||
v-model="coverColorDraft"
|
||||
format="HEX"
|
||||
type="multiple"
|
||||
:swatch-colors="colorSwatchList"
|
||||
@palette-bar-change="updateCoverColorDraftFromPalette"
|
||||
/>
|
||||
</bottom-popup>
|
||||
<input ref="attachmentInputRef" class="file-input" type="file" accept="image/*" @change="handleAttachmentSelected" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { BottomPopup } from "timi-tdesign-mobile";
|
||||
import type { ColorObject } from "tdesign-mobile-vue";
|
||||
import { type Attachment, AttachmentAPI, useUserStore } from "timi-web-api";
|
||||
import ProductAPI from "@/api/ProductAPI";
|
||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||
@@ -162,8 +198,13 @@ type ProductUnitForm = ProductUnit & { clientKey: string; salePriceText: string
|
||||
type ProductAttachmentItem = PhotoItem & {
|
||||
attachment?: Attachment;
|
||||
attachType: ProductAttachmentType;
|
||||
isSolidColorCover?: boolean;
|
||||
solidColor?: string;
|
||||
};
|
||||
|
||||
const SOLID_COLOR_COVER_PURPOSE = "PRODUCT_SOLID_COLOR_COVER";
|
||||
const DEFAULT_COVER_COLOR = "#2F88FF";
|
||||
|
||||
defineOptions({ name: "ProductEdit" });
|
||||
|
||||
const route = useRoute();
|
||||
@@ -192,6 +233,8 @@ const statusPickerVisible = ref(false);
|
||||
const attachmentInputRef = ref<HTMLInputElement>();
|
||||
const attachmentPickerTarget = ref<ProductAttachmentType>("PHOTO");
|
||||
const isAttachmentUploading = ref(false);
|
||||
const coverColorPickerVisible = ref(false);
|
||||
const coverColorDraft = ref(DEFAULT_COVER_COLOR);
|
||||
const typeDraft = ref<PickerValue[]>([]);
|
||||
const categoryDraft = ref<PickerValue[]>([]);
|
||||
const statusDraft = ref<PickerValue[]>([]);
|
||||
@@ -205,6 +248,18 @@ const statusOptions = [
|
||||
{ label: "启用", value: "ACTIVE" },
|
||||
{ label: "停用", value: "INACTIVE" }
|
||||
];
|
||||
const colorSwatchList = [
|
||||
"#2F88FF",
|
||||
"#0EA5E9",
|
||||
"#10B981",
|
||||
"#22C55E",
|
||||
"#F59E0B",
|
||||
"#F97316",
|
||||
"#EF4444",
|
||||
"#EC4899",
|
||||
"#8B5CF6",
|
||||
"#64748B"
|
||||
];
|
||||
const selectedCategoryName = computed(() => categoryList.value.find(item => item.id === form.categoryId)?.name || "不设置分类");
|
||||
const attachmentAddActions = [
|
||||
{
|
||||
@@ -230,6 +285,7 @@ const attachmentUrlList = computed<string[]>({
|
||||
attachmentItemList.value = attachmentItemList.value.filter(item => urlSet.has(item.url));
|
||||
}
|
||||
});
|
||||
const solidColorCoverColor = computed(() => attachmentItemList.value.find(item => item.isSolidColorCover)?.solidColor || "");
|
||||
|
||||
function createUnit(unit?: Partial<ProductUnit>): ProductUnitForm {
|
||||
return {
|
||||
@@ -326,6 +382,77 @@ function openAttachmentPicker(target: ProductAttachmentType): void {
|
||||
attachmentInputRef.value?.click();
|
||||
}
|
||||
|
||||
function openCoverColorPicker(): void {
|
||||
coverColorDraft.value = solidColorCoverColor.value || DEFAULT_COVER_COLOR;
|
||||
coverColorPickerVisible.value = true;
|
||||
}
|
||||
|
||||
function cancelCoverColorPicker(): void {
|
||||
coverColorPickerVisible.value = false;
|
||||
coverColorDraft.value = solidColorCoverColor.value || DEFAULT_COVER_COLOR;
|
||||
}
|
||||
|
||||
function updateCoverColorDraftFromPalette(context: { color: ColorObject }): void {
|
||||
coverColorDraft.value = context.color.hex;
|
||||
}
|
||||
|
||||
function createSolidColorPNG(color: string): Promise<File> {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = 1;
|
||||
canvas.height = 1;
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) {
|
||||
return Promise.reject(new Error("纯色封面生成失败"));
|
||||
}
|
||||
|
||||
context.fillStyle = color;
|
||||
context.fillRect(0, 0, 1, 1);
|
||||
return new Promise((resolve, reject) => {
|
||||
canvas.toBlob(blob => {
|
||||
if (!blob) {
|
||||
reject(new Error("纯色封面生成失败"));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(new File([blob], `product-cover-${Date.now()}.png`, { type: "image/png" }));
|
||||
}, "image/png");
|
||||
});
|
||||
}
|
||||
|
||||
async function confirmCoverColorPicker(): Promise<void> {
|
||||
if (isAttachmentUploading.value) {
|
||||
return;
|
||||
}
|
||||
const color = coverColorDraft.value;
|
||||
if (color === solidColorCoverColor.value) {
|
||||
coverColorPickerVisible.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
coverColorPickerVisible.value = false;
|
||||
isAttachmentUploading.value = true;
|
||||
try {
|
||||
const [tempFile] = await AttachmentAPI.upload(await createSolidColorPNG(color));
|
||||
if (!tempFile?.id) {
|
||||
throw new Error("纯色封面上传失败");
|
||||
}
|
||||
attachmentItemList.value = [
|
||||
{
|
||||
attachType: "PHOTO",
|
||||
isSolidColorCover: true,
|
||||
solidColor: color,
|
||||
tempFileId: tempFile.id,
|
||||
url: AttachmentAPI.getTempReadURL(tempFile.id)
|
||||
},
|
||||
...attachmentItemList.value.filter(item => !item.isSolidColorCover)
|
||||
];
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "纯色封面上传失败"));
|
||||
} finally {
|
||||
isAttachmentUploading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAttachmentSelected(event: Event): Promise<void> {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const file = input.files?.[0];
|
||||
@@ -362,7 +489,11 @@ function buildAttachmentList(): Attachment[] {
|
||||
if (item.tempFileId) {
|
||||
return [{
|
||||
attachType: item.attachType,
|
||||
tempFileId: item.tempFileId
|
||||
tempFileId: item.tempFileId,
|
||||
metadata: item.isSolidColorCover && item.solidColor ? {
|
||||
purpose: SOLID_COLOR_COVER_PURPOSE,
|
||||
color: item.solidColor
|
||||
} : undefined
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
@@ -428,7 +559,10 @@ function validate(): boolean {
|
||||
}
|
||||
|
||||
async function save(): Promise<void> {
|
||||
if (isSaving.value || !userStore.hasPermissions(savePermissionCode.value) || !validate()) {
|
||||
if (isSaving.value || isAttachmentUploading.value) {
|
||||
return;
|
||||
}
|
||||
if (!userStore.hasPermissions(savePermissionCode.value) || !validate()) {
|
||||
return;
|
||||
}
|
||||
isSaving.value = true;
|
||||
@@ -499,12 +633,18 @@ async function load(): Promise<void> {
|
||||
});
|
||||
attachmentItemList.value = (result.attachmentList || [])
|
||||
.filter(item => !!item.id)
|
||||
.map(item => ({
|
||||
attachment: item,
|
||||
attachType: item.attachType === "OTHER" ? "OTHER" : "PHOTO",
|
||||
previewUrl: getAttachmentReadURL(item.id || ""),
|
||||
url: getAttachmentReadURL(item.id || "", 90)
|
||||
}));
|
||||
.map(item => {
|
||||
const metadata = item.metadata;
|
||||
const solidColor = metadata?.purpose === SOLID_COLOR_COVER_PURPOSE && typeof metadata.color === "string" ? metadata.color : undefined;
|
||||
return {
|
||||
attachment: item,
|
||||
attachType: item.attachType === "OTHER" ? "OTHER" : "PHOTO",
|
||||
isSolidColorCover: !!solidColor,
|
||||
solidColor,
|
||||
previewUrl: getAttachmentReadURL(item.id || ""),
|
||||
url: getAttachmentReadURL(item.id || "", 90)
|
||||
};
|
||||
});
|
||||
minStockQuantityText.value = result.minStockQuantity === null || result.minStockQuantity === undefined ? "" : String(result.minStockQuantity);
|
||||
if (!form.unitList.length) {
|
||||
addUnit();
|
||||
@@ -541,6 +681,38 @@ onUnmounted(() => navBarStore.clearRight());
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.color-trigger {
|
||||
--td-button-extra-small-padding-horizontal: 2px;
|
||||
|
||||
gap: .75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: .875rem;
|
||||
|
||||
.content {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.swatch {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border: 1px solid var(--td-component-stroke);
|
||||
display: block;
|
||||
border-radius: .35rem;
|
||||
|
||||
&.empty {
|
||||
background: var(--td-bg-color-secondarycontainer);
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--td-text-color-secondary);
|
||||
font-size: .875rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.units {
|
||||
padding: 1rem 0;
|
||||
|
||||
@@ -587,4 +759,5 @@ onUnmounted(() => navBarStore.clearRight());
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user