fix: 修复商品模块typecheck与lint
All checks were successful
Build and Deploy TenantUI / build-and-deploy (push) Successful in 51s
All checks were successful
Build and Deploy TenantUI / build-and-deploy (push) Successful in 51s
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { UploadProps } from 'ant-design-vue';
|
||||
|
||||
import { Button, Select, Space, Upload } from 'ant-design-vue';
|
||||
|
||||
interface Props {
|
||||
@@ -69,10 +70,7 @@ function setExportCategoryIds(value: unknown) {
|
||||
已选择:{{ props.selectedFileName }}
|
||||
</div>
|
||||
<Space class="pbt-upload-actions">
|
||||
<Button
|
||||
:loading="props.submitting"
|
||||
@click="emit('downloadTemplate')"
|
||||
>
|
||||
<Button :loading="props.submitting" @click="emit('downloadTemplate')">
|
||||
下载导入模板
|
||||
</Button>
|
||||
<Button
|
||||
@@ -133,4 +131,3 @@ function setExportCategoryIds(value: unknown) {
|
||||
</footer>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -84,4 +84,3 @@ function setTargetCategory(value: unknown) {
|
||||
</footer>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { BatchPricePreviewRow, BatchScopeType } from '../types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Button, InputNumber, Select, Table } from 'ant-design-vue';
|
||||
|
||||
interface Props {
|
||||
@@ -223,4 +224,3 @@ function setAmount(value: null | number | string) {
|
||||
</footer>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { BatchScopeType } from '../types';
|
||||
|
||||
import { Button, Select } from 'ant-design-vue';
|
||||
|
||||
interface Props {
|
||||
@@ -135,4 +136,3 @@ function setScopeProductIds(value: unknown) {
|
||||
</footer>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -129,4 +129,3 @@ function setSyncStatus(value: boolean | string | undefined) {
|
||||
</footer>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { BatchToolCard, BatchToolKey } from '../types';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
interface Props {
|
||||
@@ -41,4 +42,3 @@ const emit = defineEmits<Emits>();
|
||||
</article>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -40,7 +40,10 @@ export const BATCH_TOOL_CARDS: BatchToolCard[] = [
|
||||
];
|
||||
|
||||
/** 范围选项。 */
|
||||
export const BATCH_SCOPE_OPTIONS: Array<{ label: string; value: BatchScopeType }> = [
|
||||
export const BATCH_SCOPE_OPTIONS: Array<{
|
||||
label: string;
|
||||
value: BatchScopeType;
|
||||
}> = [
|
||||
{ label: '全部商品', value: 'all' },
|
||||
{ label: '按分类', value: 'category' },
|
||||
{ label: '手动选择', value: 'manual' },
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { BatchCategoryOption, BatchProductOption } from '../../types';
|
||||
|
||||
import type { ProductPickerItemDto } from '#/api/product';
|
||||
import type { StoreListItemDto } from '#/api/store';
|
||||
|
||||
@@ -11,12 +13,7 @@ import {
|
||||
} from '#/api/product';
|
||||
import { getStoreListApi } from '#/api/store';
|
||||
|
||||
import {
|
||||
toBatchCategoryOptions,
|
||||
toBatchProductOptions,
|
||||
type BatchCategoryOption,
|
||||
type BatchProductOption,
|
||||
} from '../../types';
|
||||
import { toBatchCategoryOptions, toBatchProductOptions } from '../../types';
|
||||
|
||||
interface CreateBatchDataActionsOptions {
|
||||
categoryOptions: Ref<BatchCategoryOption[]>;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { BatchScopeType } from '../../types';
|
||||
|
||||
import type {
|
||||
ProductBatchPricePreviewDto,
|
||||
ProductBatchScopeDto,
|
||||
@@ -18,8 +20,6 @@ import {
|
||||
batchSyncProductStoreApi,
|
||||
} from '#/api/product';
|
||||
|
||||
import type { BatchScopeType } from '../../types';
|
||||
|
||||
interface CreateBatchToolActionsOptions {
|
||||
exportCategoryIds: Ref<string[]>;
|
||||
exportScopeType: Ref<'all' | 'category'>;
|
||||
@@ -91,7 +91,7 @@ function decodeBase64ToBlob(base64: string) {
|
||||
const binary = window.atob(base64);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let index = 0; index < binary.length; index++) {
|
||||
bytes[index] = binary.charCodeAt(index);
|
||||
bytes[index] = binary.codePointAt(index) ?? 0;
|
||||
}
|
||||
|
||||
return new Blob([bytes], {
|
||||
@@ -241,7 +241,9 @@ export function createBatchToolActions(options: CreateBatchToolActionsOptions) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.moveSourceCategoryId.value === options.moveTargetCategoryId.value) {
|
||||
if (
|
||||
options.moveSourceCategoryId.value === options.moveTargetCategoryId.value
|
||||
) {
|
||||
message.warning('源分类和目标分类不能相同');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { computed, onActivated, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import type { StoreListItemDto } from '#/api/store';
|
||||
|
||||
import { BATCH_TOOL_CARDS } from './batch-page/constants';
|
||||
import { createBatchDataActions } from './batch-page/data-actions';
|
||||
import { createBatchToolActions } from './batch-page/tool-actions';
|
||||
import type {
|
||||
BatchCategoryOption,
|
||||
BatchScopeType,
|
||||
@@ -12,6 +5,14 @@ import type {
|
||||
BatchToolKey,
|
||||
} from '../types';
|
||||
|
||||
import type { StoreListItemDto } from '#/api/store';
|
||||
|
||||
import { computed, onActivated, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { BATCH_TOOL_CARDS } from './batch-page/constants';
|
||||
import { createBatchDataActions } from './batch-page/data-actions';
|
||||
import { createBatchToolActions } from './batch-page/tool-actions';
|
||||
|
||||
/**
|
||||
* 文件职责:批量工具页面状态编排。
|
||||
* 1. 管理门店、范围、卡片展开状态。
|
||||
@@ -23,7 +24,9 @@ export function useProductBatchPage() {
|
||||
const isStoreLoading = ref(false);
|
||||
|
||||
const categoryOptions = ref<BatchCategoryOption[]>([]);
|
||||
const productOptions = ref<Array<{ label: string; price: number; spuCode: string; value: string }>>([]);
|
||||
const productOptions = ref<
|
||||
Array<{ label: string; price: number; spuCode: string; value: string }>
|
||||
>([]);
|
||||
const isCategoryLoading = ref(false);
|
||||
const isProductLoading = ref(false);
|
||||
|
||||
@@ -122,7 +125,9 @@ export function useProductBatchPage() {
|
||||
);
|
||||
|
||||
const selectedStoreLabel = computed(() => {
|
||||
return stores.value.find((item) => item.id === selectedStoreId.value)?.name || '';
|
||||
return (
|
||||
stores.value.find((item) => item.id === selectedStoreId.value)?.name || ''
|
||||
);
|
||||
});
|
||||
|
||||
const scopeEstimatedCount = computed(() => {
|
||||
@@ -143,8 +148,9 @@ export function useProductBatchPage() {
|
||||
const moveEstimatedCount = computed(() => {
|
||||
if (moveSourceCategoryId.value) {
|
||||
return (
|
||||
categoryOptions.value.find((item) => item.value === moveSourceCategoryId.value)
|
||||
?.productCount ?? 0
|
||||
categoryOptions.value.find(
|
||||
(item) => item.value === moveSourceCategoryId.value,
|
||||
)?.productCount ?? 0
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { Page } from '@vben/common-ui';
|
||||
import type { BatchToolKey } from './types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { Alert, Drawer, Empty, Spin } from 'ant-design-vue';
|
||||
|
||||
import StoreScopeToolbar from '../../shared/components/StoreScopeToolbar.vue';
|
||||
import BatchImportExportPanel from './components/BatchImportExportPanel.vue';
|
||||
import BatchMoveCategoryPanel from './components/BatchMoveCategoryPanel.vue';
|
||||
import BatchPriceAdjustPanel from './components/BatchPriceAdjustPanel.vue';
|
||||
import BatchSaleSwitchPanel from './components/BatchSaleSwitchPanel.vue';
|
||||
import BatchStoreSyncPanel from './components/BatchStoreSyncPanel.vue';
|
||||
import StoreScopeToolbar from '../../shared/components/StoreScopeToolbar.vue';
|
||||
import BatchToolCards from './components/BatchToolCards.vue';
|
||||
import { useProductBatchPage } from './composables/useProductBatchPage';
|
||||
import type { BatchToolKey } from './types';
|
||||
|
||||
const {
|
||||
activeTool,
|
||||
@@ -150,7 +152,9 @@ const activeToolTitle = computed(() => {
|
||||
@update:selected-store-id="setSelectedStoreId"
|
||||
>
|
||||
<template #actions>
|
||||
<div class="pbt-toolbar-tip">选择门店后,工具仅作用于当前门店商品</div>
|
||||
<div class="pbt-toolbar-tip">
|
||||
选择门店后,工具仅作用于当前门店商品
|
||||
</div>
|
||||
</template>
|
||||
</StoreScopeToolbar>
|
||||
|
||||
|
||||
@@ -43,8 +43,8 @@
|
||||
}
|
||||
|
||||
.pbt-card-desc {
|
||||
margin: 0;
|
||||
min-height: 40px;
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #9ca3af;
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
|
||||
.pbt-toolbar-tip {
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
line-height: 1.4;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.pbt-empty-wrap {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
.page-product-batch,
|
||||
.pbt-drawer {
|
||||
|
||||
.pbt-panel {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
|
||||
@@ -32,6 +32,9 @@ export function buildSkuCombinations(
|
||||
return;
|
||||
}
|
||||
const current = templates[depth];
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
for (const option of current.options) {
|
||||
walk(depth + 1, [
|
||||
...chain,
|
||||
|
||||
@@ -16,6 +16,10 @@ interface CreateProductDetailSkuActionsOptions {
|
||||
specTemplateOptions: Ref<ProductSpecDto[]>;
|
||||
}
|
||||
|
||||
function isDefined<T>(value: null | T | undefined): value is T {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
export function createProductDetailSkuActions(
|
||||
options: CreateProductDetailSkuActionsOptions,
|
||||
) {
|
||||
@@ -87,7 +91,7 @@ export function createProductDetailSkuActions(
|
||||
function buildSkuRows() {
|
||||
const selectedTemplates = form.specTemplateIds
|
||||
.map((id) => specTemplateOptions.value.find((item) => item.id === id))
|
||||
.filter(Boolean)
|
||||
.filter((item): item is ProductSpecDto => isDefined(item))
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
@@ -169,19 +173,21 @@ export function createProductDetailSkuActions(
|
||||
}
|
||||
|
||||
function setSkuPrice(index: number, value: number) {
|
||||
if (index < 0 || index >= form.skus.length) return;
|
||||
const current = form.skus[index];
|
||||
if (!current) return;
|
||||
|
||||
form.skus[index] = {
|
||||
...form.skus[index],
|
||||
price: Number.isFinite(value)
|
||||
? Math.max(0, value)
|
||||
: form.skus[index].price,
|
||||
...current,
|
||||
price: Number.isFinite(value) ? Math.max(0, value) : current.price,
|
||||
};
|
||||
}
|
||||
|
||||
function setSkuOriginalPrice(index: number, value: null | number) {
|
||||
if (index < 0 || index >= form.skus.length) return;
|
||||
const current = form.skus[index];
|
||||
if (!current) return;
|
||||
|
||||
form.skus[index] = {
|
||||
...form.skus[index],
|
||||
...current,
|
||||
originalPrice:
|
||||
value !== null && value !== undefined && Number(value) > 0
|
||||
? Number(value)
|
||||
@@ -190,17 +196,21 @@ export function createProductDetailSkuActions(
|
||||
}
|
||||
|
||||
function setSkuStock(index: number, value: number) {
|
||||
if (index < 0 || index >= form.skus.length) return;
|
||||
const current = form.skus[index];
|
||||
if (!current) return;
|
||||
|
||||
form.skus[index] = {
|
||||
...form.skus[index],
|
||||
...current,
|
||||
stock: Math.max(0, Math.floor(Number(value || 0))),
|
||||
};
|
||||
}
|
||||
|
||||
function setSkuEnabled(index: number, checked: boolean) {
|
||||
if (index < 0 || index >= form.skus.length) return;
|
||||
const current = form.skus[index];
|
||||
if (!current) return;
|
||||
|
||||
form.skus[index] = {
|
||||
...form.skus[index],
|
||||
...current,
|
||||
isEnabled: checked,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ import { DEFAULT_PRODUCT_DETAIL_FORM } from './product-detail-page/constants';
|
||||
import { createProductDetailDataActions } from './product-detail-page/data-actions';
|
||||
import { createProductDetailSkuActions } from './product-detail-page/sku-actions';
|
||||
|
||||
function isDefined<T>(value: null | T | undefined): value is T {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
export function useProductDetailPage() {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -71,10 +75,10 @@ export function useProductDetailPage() {
|
||||
return '#9ca3af';
|
||||
});
|
||||
|
||||
const skuTemplateColumns = computed(() =>
|
||||
const skuTemplateColumns = computed<ProductSpecDto[]>(() =>
|
||||
form.specTemplateIds
|
||||
.map((id) => specTemplateOptions.value.find((item) => item.id === id))
|
||||
.filter(Boolean),
|
||||
.filter((item): item is ProductSpecDto => isDefined(item)),
|
||||
);
|
||||
|
||||
const skuActions = createProductDetailSkuActions({
|
||||
|
||||
@@ -107,6 +107,36 @@ const sectionList = computed<ProductDetailSectionItem[]>(() => {
|
||||
const isOnSale = computed(() => form.status === 'on_sale');
|
||||
const maxUploadReached = computed(() => form.imageUrls.length >= 5);
|
||||
const skuCountText = computed(() => `共 ${form.skus.length} 个 SKU`);
|
||||
const originalPriceValue = computed<number | undefined>({
|
||||
get: () => form.originalPrice ?? undefined,
|
||||
set: (value) => {
|
||||
form.originalPrice = value ?? null;
|
||||
},
|
||||
});
|
||||
const warningStockValue = computed<number | undefined>({
|
||||
get: () => form.warningStock ?? undefined,
|
||||
set: (value) => {
|
||||
form.warningStock = value ?? null;
|
||||
},
|
||||
});
|
||||
const packingFeeValue = computed<number | undefined>({
|
||||
get: () => form.packingFee ?? undefined,
|
||||
set: (value) => {
|
||||
form.packingFee = value ?? null;
|
||||
},
|
||||
});
|
||||
const skuBatchPriceValue = computed<number | undefined>({
|
||||
get: () => skuBatch.price ?? undefined,
|
||||
set: (value) => {
|
||||
skuBatch.price = value ?? null;
|
||||
},
|
||||
});
|
||||
const skuBatchStockValue = computed<number | undefined>({
|
||||
get: () => skuBatch.stock ?? undefined,
|
||||
set: (value) => {
|
||||
skuBatch.stock = value ?? null;
|
||||
},
|
||||
});
|
||||
|
||||
const timedOnShelfValue = computed<Dayjs | undefined>({
|
||||
get: () =>
|
||||
@@ -386,7 +416,7 @@ watch(
|
||||
<div class="pd-pricing-inline">
|
||||
<span class="pd-pricing-unit">¥</span>
|
||||
<InputNumber
|
||||
v-model:value="form.originalPrice"
|
||||
v-model:value="originalPriceValue"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
@@ -418,7 +448,7 @@ watch(
|
||||
<div class="pd-pricing-inline">
|
||||
<span class="pd-pricing-unit is-empty"> ¥ </span>
|
||||
<InputNumber
|
||||
v-model:value="form.warningStock"
|
||||
v-model:value="warningStockValue"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
@@ -434,7 +464,7 @@ watch(
|
||||
<div class="pd-pricing-inline">
|
||||
<span class="pd-pricing-unit">¥</span>
|
||||
<InputNumber
|
||||
v-model:value="form.packingFee"
|
||||
v-model:value="packingFeeValue"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
@@ -478,7 +508,7 @@ watch(
|
||||
<label>批量设价</label>
|
||||
<span class="pd-unit">¥</span>
|
||||
<InputNumber
|
||||
v-model:value="skuBatch.price"
|
||||
v-model:value="skuBatchPriceValue"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
@@ -489,7 +519,7 @@ watch(
|
||||
<span class="pd-sku-batch-gap"></span>
|
||||
<label>批量设库存</label>
|
||||
<InputNumber
|
||||
v-model:value="skuBatch.stock"
|
||||
v-model:value="skuBatchStockValue"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
|
||||
@@ -38,6 +38,8 @@ export const SCHEDULE_COLOR_PALETTE = [
|
||||
'#2f54eb',
|
||||
] as const;
|
||||
|
||||
const DEFAULT_SCHEDULE_COLOR = '#1890ff';
|
||||
|
||||
/** 创建默认编辑表单。 */
|
||||
export function createDefaultScheduleForm(): ScheduleEditorForm {
|
||||
return {
|
||||
@@ -63,7 +65,7 @@ export function normalizeWeekDays(weekDays: number[]) {
|
||||
/** 按规则 ID 生成稳定颜色。 */
|
||||
export function resolveScheduleColor(scheduleId: string) {
|
||||
if (!scheduleId) {
|
||||
return SCHEDULE_COLOR_PALETTE[0];
|
||||
return DEFAULT_SCHEDULE_COLOR;
|
||||
}
|
||||
|
||||
let hash = 0;
|
||||
@@ -71,5 +73,8 @@ export function resolveScheduleColor(scheduleId: string) {
|
||||
hash = (hash * 31 + (char.codePointAt(0) ?? 0)) >>> 0;
|
||||
}
|
||||
|
||||
return SCHEDULE_COLOR_PALETTE[hash % SCHEDULE_COLOR_PALETTE.length];
|
||||
return (
|
||||
SCHEDULE_COLOR_PALETTE[hash % SCHEDULE_COLOR_PALETTE.length] ??
|
||||
DEFAULT_SCHEDULE_COLOR
|
||||
);
|
||||
}
|
||||
|
||||
@@ -99,8 +99,8 @@ export function useProductSchedulePage() {
|
||||
.map((item) => buildTimelineRow(item, getRuleColor(item.id)));
|
||||
});
|
||||
|
||||
function getRuleColor(scheduleId: string) {
|
||||
return resolveScheduleColor(scheduleId);
|
||||
function getRuleColor(scheduleId: string | undefined) {
|
||||
return resolveScheduleColor(scheduleId || '');
|
||||
}
|
||||
|
||||
function resolveProductName(productId: string) {
|
||||
|
||||
Reference in New Issue
Block a user