Commit 9aa459de by qinjianhui

Merge branch 'dev' into 'master'

Dev

See merge request !277
parents ca66d631 c93e5c54
...@@ -15,7 +15,9 @@ declare module 'vue' { ...@@ -15,7 +15,9 @@ declare module 'vue' {
ElCarousel: typeof import('element-plus/es')['ElCarousel'] ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem'] ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
...@@ -27,24 +29,20 @@ declare module 'vue' { ...@@ -27,24 +29,20 @@ declare module 'vue' {
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover'] ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable'] ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
Icon: typeof import('./src/components/Icon.vue')['default'] Icon: typeof import('./src/components/Icon.vue')['default']
ImageView: typeof import('./src/components/ImageView.vue')['default'] ImageView: typeof import('./src/components/ImageView.vue')['default']
LeftRightLayout: typeof import('./src/components/leftRightLayout.vue')['default'] LeftRightLayout: typeof import('./src/components/leftRightLayout.vue')['default']
......
import { BasePaginationData, BaseRespData } from '@/types/api'
import axios from '../axios'
import {
EditForm,
SearchForm,
WarehouseRuleSettingData,
WarehouseRuleSpuItem,
} from '@/types/api/warehouse/warehouseRuleSetting'
export function getWarehouseRuleSettingListApi(
data: SearchForm,
page: number,
pageSize: number,
) {
return axios.post<never, BasePaginationData<WarehouseRuleSettingData>>(
'/factory/sharedInventoryGroup/list-page',
{ ...data, page, pageSize },
)
}
export function getQuerySpuApi(warehouseSpu: string, warehouseId: number | undefined) {
return axios.post<never, BaseRespData<WarehouseRuleSpuItem>>(
'/factory/sharedInventorySku/query-spu',
{ warehouseSpu, warehouseId },
)
}
export function addSharedInventoryGroupApi(data: EditForm) {
return axios.post<never, BaseRespData<void>>(
'/factory/sharedInventoryGroup/add',
data,
)
}
export function updateSharedInventoryGroupApi(data: EditForm) {
return axios.post<never, BaseRespData<void>>(
'/factory/sharedInventoryGroup/update',
data,
)
}
export function getWarehouseRuleSettingDetailApi(id: number | undefined) {
return axios.get<never, BaseRespData<EditForm>>(
`/factory/sharedInventoryGroup/get`,
{
params: { id },
},
)
}
export function deleteSharedInventoryGroupApi(ids: string) {
return axios.get<never, BaseRespData<never>>(
'/factory/sharedInventoryGroup/delete-ids',
{
params: { ids },
},
)
}
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
align="center" align="center"
:class="isMore ? 'is-more-active' : ''" :class="isMore ? 'is-more-active' : ''"
></ElTableColumn> ></ElTableColumn>
<template v-for="column in columns" :key="column.key"> <template v-for="column in columns" :key="column.prop">
<ElTableColumn <ElTableColumn
v-if="column.type === 'expand'" v-if="column.type === 'expand'"
v-bind="column" v-bind="column"
......
...@@ -83,6 +83,8 @@ export const dynamicComponentMap: Record<string, LazyComponent> = { ...@@ -83,6 +83,8 @@ export const dynamicComponentMap: Record<string, LazyComponent> = {
'/MyDownloads': () => import('@/views/MyDownloads.vue'), '/MyDownloads': () => import('@/views/MyDownloads.vue'),
'/supply/supplierManagement': () => '/supply/supplierManagement': () =>
import('@/views/supply/supplierManagement/index.vue'), import('@/views/supply/supplierManagement/index.vue'),
'/warehouse/warehouse-rule-setting': () =>
import('@/views/warehouse/warehouseRuleSetting/index.vue'),
} }
/** dashboard 是登录后的默认页,保留为静态路由 */ /** dashboard 是登录后的默认页,保留为静态路由 */
......
...@@ -112,18 +112,18 @@ const menu: MenuItem[] = [ ...@@ -112,18 +112,18 @@ const menu: MenuItem[] = [
{ index: '/warehouse/position', id: 322, label: '库位管理' }, { index: '/warehouse/position', id: 322, label: '库位管理' },
], ],
}, },
// { {
// index: 'warehouse-rules', index: 'warehouse-rules',
// id: 33, id: 33,
// label: '仓库规则', label: '仓库规则',
// children: [ children: [
// { {
// index: '/warehouse/warehouse-rule-setting', index: '/warehouse/warehouse-rule-setting',
// id: 331, id: 331,
// label: '共享库存设置', label: '仓库规则设置',
// }, },
// ], ],
// }, },
], ],
}, },
{ {
......
...@@ -151,6 +151,7 @@ export interface operateOrderListData { ...@@ -151,6 +151,7 @@ export interface operateOrderListData {
variantImage?: string variantImage?: string
thirdSpuCode?: string thirdSpuCode?: string
thirdSkuCode?: string thirdSkuCode?: string
originThirdSkuCode?: string
supplierProductNo?: string supplierProductNo?: string
productName?: string productName?: string
productMark?: string productMark?: string
......
...@@ -162,6 +162,7 @@ export interface operateOrderListData { ...@@ -162,6 +162,7 @@ export interface operateOrderListData {
variantImage?: string variantImage?: string
thirdSpuCode?: string thirdSpuCode?: string
thirdSkuCode?: string thirdSkuCode?: string
originThirdSkuCode?: string
supplierProductNo?: string supplierProductNo?: string
productName?: string productName?: string
productMark?: string productMark?: string
......
export interface SearchForm {
createStartTime: string
createEndTime: string
warehouseSpu: string
groupName: string
status: number
}
export interface WarehouseRuleSettingData {
id?: number
createTime?: string
warehouseSpu?: string
groupName?: string
status?: number
skuList?: WarehouseRuleSpuItem[]
}
export interface WarehouseRuleSpuItem {
dataVersion?: number
id?: number
sortOrder?: number
warehouseSku?: string
warehouseSpu?: string
skuImage?: string
skuName?: string
warehouseId?: number
warehouseName?: string
}
export interface EditForm {
id?: number
groupName?: string
status?: boolean
warehouseId?: number
dataVersion?: number
spuList?: WarehouseRuleSpuItem[]
skuList?: WarehouseRuleSpuItem[]
}
import type { Column as ElColumn } from 'element-plus/lib/components/index.js' import type { Column as ElColumn } from 'element-plus/lib/components/index.js'
import type { VNode } from 'vue'
interface Column<D> extends Omit<ElColumn, 'width'> { interface Column<D> extends Omit<ElColumn, 'width'> {
/** 为 false 时隐藏该列;未设置时默认显示 */ /** 为 false 时隐藏该列;未设置时默认显示 */
showColumn?: boolean showColumn?: boolean
width?: number | string width?: number | string
minWidth?: number | string minWidth?: number | string
key?: string
slot?: string slot?: string
headerSlot?: string headerSlot?: string
headerRender?: (scope: { column: never; index: number }) => JSX.Element headerRender?: (scope: { column: never; index: number }) => JSX.Element
render?: (scope: { row: D; index: number }) => JSX.Element render?: (row: D, index: number) => VNode | JSX.Element
formatDate?: (row: D) => string formatDate?: (row: D) => string
children?: Column<D>[] children?: Column<D>[]
subs?: Column<D>[] subs?: Column<D>[]
......
...@@ -62,7 +62,10 @@ ...@@ -62,7 +62,10 @@
<template #images> <template #images>
<div class="flex-between"> <div class="flex-between">
<div class="images-position"> <div class="images-position">
<div v-if="parseImageAry(item.imageAry).length" class="images-container"> <div
v-if="parseImageAry(item.imageAry).length"
class="images-container"
>
<div <div
v-for="(img, index) in parseImageAry(item.imageAry)" v-for="(img, index) in parseImageAry(item.imageAry)"
:key="index" :key="index"
...@@ -72,7 +75,9 @@ ...@@ -72,7 +75,9 @@
<el-image <el-image
:src="img.url" :src="img.url"
height="50" height="50"
:preview-src-list="parseImageAry(item.imageAry).map((i) => i.url)" :preview-src-list="
parseImageAry(item.imageAry).map((i) => i.url)
"
:initial-index="index" :initial-index="index"
/> />
</div> </div>
...@@ -175,13 +180,33 @@ ...@@ -175,13 +180,33 @@
<span class="info-value">{{ item.craftName }}</span> <span class="info-value">{{ item.craftName }}</span>
</div> </div>
<div class="card-info-row"> <div class="card-info-row">
<span <div class="third-sku-cell">
class="info-value clickable ellipsis" <el-tooltip effect="dark" placement="top">
:title="`库存SKU:${(item.thirdSkuCode as string) || ''}`" <template #content>
@click.stop="copyText((item.thirdSkuCode as string) || '')" <div>
> 下单库存SKU:
{{ item.thirdSkuCode }} {{
</span> (item.originThirdSkuCode as string) ||
(item.thirdSkuCode as string) ||
''
}}
</div>
<div>(共享库存替代)</div>
</template>
<el-icon style="color: #409eff; cursor: pointer"
><WarningFilled
/></el-icon>
</el-tooltip>
<span
class="info-value clickable ellipsis"
:title="`库存SKU: ${item.thirdSkuCode}`"
@click.stop="
copyText((item.thirdSkuCode as string) || '')
"
>
{{ item.thirdSkuCode }}
</span>
</div>
</div> </div>
<div class="card-info-row"> <div class="card-info-row">
<span class="info-label">店铺单号:</span> <span class="info-label">店铺单号:</span>
...@@ -306,6 +331,7 @@ import type { LogListData } from '@/types/api/order' ...@@ -306,6 +331,7 @@ import type { LogListData } from '@/types/api/order'
import LogList from '@/components/LogList.vue' import LogList from '@/components/LogList.vue'
import RightClickMenu from '@/components/RightClickMenu.vue' import RightClickMenu from '@/components/RightClickMenu.vue'
import platformJson from '../../../../json/platform.json' import platformJson from '../../../../json/platform.json'
import { WarningFilled } from '@element-plus/icons-vue'
const props = defineProps<{ const props = defineProps<{
status?: string status?: string
...@@ -715,6 +741,13 @@ defineExpose({ clearSelection, getSelectedIds, refresh }) ...@@ -715,6 +741,13 @@ defineExpose({ clearSelection, getSelectedIds, refresh })
.flex-row-gap6 { .flex-row-gap6 {
gap: 6px; gap: 6px;
} }
.third-sku-cell {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
overflow: hidden;
}
@media (max-width: 1920px) { @media (max-width: 1920px) {
.card-grid { .card-grid {
grid-template-columns: repeat(5, minmax(0, 1fr)); grid-template-columns: repeat(5, minmax(0, 1fr));
......
...@@ -1270,6 +1270,7 @@ import { ...@@ -1270,6 +1270,7 @@ import {
CaretBottom, CaretBottom,
Edit, Edit,
DocumentCopy, DocumentCopy,
WarningFilled,
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import { computed, nextTick, onMounted, ref } from 'vue' import { computed, nextTick, onMounted, ref } from 'vue'
import splitDiv from '@/components/splitDiv/splitDiv.vue' import splitDiv from '@/components/splitDiv/splitDiv.vue'
...@@ -2210,9 +2211,40 @@ const operationOrderColumns = computed(() => [ ...@@ -2210,9 +2211,40 @@ const operationOrderColumns = computed(() => [
key: 'thirdSkuCode', key: 'thirdSkuCode',
prop: 'thirdSkuCode', prop: 'thirdSkuCode',
label: '库存SKU', label: '库存SKU',
minWidth: 140, minWidth: 160,
align: 'center', align: 'center',
showOverflowTooltip: true, showOverflowTooltip: false,
render: (row: operateOrderListData) => {
const displaySku = row.thirdSkuCode || '-'
const originSku = row.originThirdSkuCode as string | undefined
const tipSku = originSku || row.thirdSkuCode || ''
return (
<div class="third-sku-cell">
<el-tooltip
effect="dark"
placement="top-start"
teleported
v-slots={{
content: () => (
<div>
<div>{`库存SKU: ${row.thirdSkuCode}`}</div>
<div>{`下单库存SKU: ${tipSku}`}</div>
<div>(共享库存替代)</div>
</div>
),
}}
>
<el-icon style="color:#409EFF;cursor:pointer;">
<WarningFilled />
</el-icon>
</el-tooltip>
<span class="ellipsis">
{displaySku}
</span>
</div>
)
},
}, },
{ {
key: 'productMark', key: 'productMark',
...@@ -3795,6 +3827,18 @@ onMounted(() => { ...@@ -3795,6 +3827,18 @@ onMounted(() => {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
:deep(.third-sku-cell) {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
vertical-align: middle;
}
:deep(.ellipsis) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
.table-pagination-bar { .table-pagination-bar {
......
<template>
<ElDialog
v-model="dialogVisible"
:title="editForm.id ? '编辑共享库存组' : '新增共享库存组'"
width="800px"
:close-on-click-modal="false"
destroy-on-close
>
<ElForm ref="editFormRef" :model="editForm" label-width="100px">
<div class="section-title">分组基础信息</div>
<ElFormItem
label="库存组名称"
prop="groupName"
:rules="[
{ required: true, message: '请输入库存组名称', trigger: 'blur' },
]"
>
<ElInput
v-model="editForm.groupName"
clearable
placeholder="请输入库存组名称"
style="width: 100%"
/>
</ElFormItem>
<ElFormItem
label="状态"
prop="status"
:rules="[{ required: true, message: '请选择状态', trigger: 'blur' }]"
>
<ElSwitch
v-model="editForm.status"
:active-value="true"
:inactive-value="false"
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
/>
</ElFormItem>
<div class="section-title">组内SPU清单</div>
<div class="spu-search-bar">
<ElSelect
v-model="editForm.warehouseId"
placeholder="请选择仓库"
style="width: 160px"
clearable
>
<ElOption
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
<ElInput
v-model="spuSearchKeyword"
clearable
placeholder="请输入库存SPU"
style="width: 200px"
@keyup.enter="handleSearchSpu"
/>
<ElPopover
v-model:visible="popoverVisible"
placement="bottom-start"
:width="600"
trigger="manual"
popper-class="spu-search-popover"
>
<template #reference>
<ElButton type="primary" @click="handleSearchSpu"> 查询 </ElButton>
</template>
<div class="popover-table">
<TableView
:paginated-data="spuSearchResultList"
:columns="spuSearchTableColumns"
size="small"
>
<template #spuImage="{ row }">
<ImageView :src="row.skuImage" width="40px" height="40px" />
</template>
<template #operation="{ row }">
<ElButton
type="success"
circle
size="small"
@click="handleAddSpu(row)"
>
<ElIcon><Plus /></ElIcon>
</ElButton>
</template>
</TableView>
</div>
</ElPopover>
</div>
<div class="spu-drag-tip">
<el-icon style="color: #409eff; font-size: 16px"><Warning /></el-icon
><span>拖动SPU调整扣减顺序</span>
</div>
<div
class="spu-table-wrapper"
@dragstart="handleRowDragStart"
@dragover.prevent
@drop="handleRowDrop"
@dragend="handleRowDragEnd"
>
<TableView
ref="spuTableRef"
:paginated-data="editForm.spuList ?? []"
:columns="spuTableColumns"
row-key="warehouseSpu"
size="small"
>
<template #sortNo="{ row }">
{{ row.sortOrder }}
</template>
<template #spuImage="{ row }">
<ImageView :src="row.skuImage" width="40px" height="40px" />
</template>
<template #operation="{ index, row }">
<ElButton link type="danger" @click="handleRemoveSpu(index, row)">
删除
</ElButton>
</template>
</TableView>
</div>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="handleCancel">取消</ElButton>
<ElButton type="primary" @click="handleSave"> 保存 </ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import {
addSharedInventoryGroupApi,
getQuerySpuApi,
updateSharedInventoryGroupApi,
} from '@/api/warehouse/warehouseRuleSetting'
import ImageView from '@/components/ImageView.vue'
import TableView from '@/components/TableView.vue'
import {
EditForm,
WarehouseRuleSpuItem,
} from '@/types/api/warehouse/warehouseRuleSetting'
import { Plus, Warning } from '@element-plus/icons-vue'
import { useWarehouseRuleSettingTableColumns } from '../hooks/useWarehouseRuleSettingTableColumns'
import { WarehouseListData } from '@/types'
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'saveSuccess'): void
}>()
const props = withDefaults(
defineProps<{
visible: boolean
warehouseList: WarehouseListData[]
}>(),
{
warehouseList: () => [],
},
)
const dialogVisible = computed({
get() {
return props.visible
},
set(value: boolean) {
emit('update:visible', value)
},
})
const createDefaultForm = (): EditForm => ({
id: undefined,
groupName: '',
status: true,
spuList: [],
warehouseId: props.warehouseList[0]?.id,
})
const editForm = ref<EditForm>(createDefaultForm())
const editFormRef = ref()
const spuSearchKeyword = ref('')
const spuSearchResultList = ref<WarehouseRuleSpuItem[]>([])
const popoverVisible = ref(false)
const dragIndex = ref<number | null>(null)
const spuTableRef = ref()
watch(
() => props.visible,
(visible) => {
if (visible && props.warehouseList.length) {
editForm.value.warehouseId = props.warehouseList[0].id
}
if (visible) {
setupRowDrag()
}
},
)
const { spuTableColumns, spuSearchTableColumns } =
useWarehouseRuleSettingTableColumns()
const updateSortNo = (list: WarehouseRuleSpuItem[]) => {
return list.map((item, index) => ({
...item,
sortOrder: index + 1,
}))
}
const resetFields = (data?: EditForm) => {
if (data) {
const spuList = data.spuList ?? data.skuList ?? []
editForm.value = {
id: data.id,
groupName: data.groupName ?? '',
dataVersion: data.dataVersion ?? 0,
status:
typeof data.status === 'boolean' ? data.status : data.status === 1,
spuList: updateSortNo(
spuList.map((item) => ({
...item,
warehouseSpu: item.warehouseSpu ?? item.warehouseSku,
})),
),
}
} else {
editForm.value = createDefaultForm()
}
spuSearchKeyword.value = ''
spuSearchResultList.value = []
popoverVisible.value = false
dragIndex.value = null
nextTick(() => {
editFormRef.value?.clearValidate()
})
}
const getRowIndexFromEvent = (e: DragEvent) => {
const row = (e.target as HTMLElement).closest('tr.el-table__row')
if (!row?.parentElement) {
return null
}
return Array.from(row.parentElement.children).indexOf(row)
}
const setupRowDrag = () => {
nextTick(() => {
const tableEl = spuTableRef.value?.tableRef?.$el as HTMLElement | undefined
const rows = tableEl?.querySelectorAll(
'.el-table__body-wrapper tbody tr.el-table__row',
)
rows?.forEach((row) => {
row.setAttribute('draggable', 'true')
})
})
}
watch(() => editForm.value.spuList, setupRowDrag, { deep: true })
const handleRowDragStart = (e: DragEvent) => {
const target = e.target as HTMLElement
if (target.closest('button, a, .el-button')) {
e.preventDefault()
return
}
const index = getRowIndexFromEvent(e)
if (index === null || index < 0) {
return
}
dragIndex.value = index
e.dataTransfer!.effectAllowed = 'move'
}
const handleRowDrop = (e: DragEvent) => {
const index = getRowIndexFromEvent(e)
if (index === null || index < 0) {
return
}
handleDrop(index)
}
const handleRowDragEnd = () => {
dragIndex.value = null
}
const handleDrop = (index: number) => {
if (dragIndex.value === null || dragIndex.value === index) {
return
}
const list = [...(editForm.value.spuList ?? [])]
const [movedItem] = list.splice(dragIndex.value, 1)
list.splice(index, 0, movedItem)
editForm.value.spuList = updateSortNo(list)
dragIndex.value = null
}
const handleSearchSpu = async () => {
const keyword = spuSearchKeyword.value.trim()
if (!keyword) {
ElMessage.warning('请输入库存SPU')
popoverVisible.value = false
return
}
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const res = await getQuerySpuApi(keyword, editForm.value.warehouseId)
if (res.code !== 200) {
popoverVisible.value = false
spuSearchKeyword.value = ''
return
}
spuSearchResultList.value = [res.data]
if (spuSearchResultList.value.length === 0) {
ElMessageBox.alert('未查询到SPU数据', '提示', {
type: 'warning',
})
popoverVisible.value = false
} else {
popoverVisible.value = true
}
} catch (error) {
console.error(error)
spuSearchKeyword.value = ''
popoverVisible.value = false
} finally {
loading.close()
}
}
const handleAddSpu = (row: WarehouseRuleSpuItem) => {
if (
editForm.value.spuList?.some(
(item) => item.warehouseSpu === row.warehouseSpu,
)
) {
ElMessage.warning('该SPU已添加')
return
}
const existingList = editForm.value.spuList ?? []
if (
existingList.length > 0 &&
existingList[0].warehouseId !== row.warehouseId
) {
ElMessageBox.alert('请选择同一仓库内的SPU', '提示', {
type: 'warning',
})
return
}
editForm.value.spuList = updateSortNo([
...(editForm.value.spuList ?? []),
{
warehouseSpu: row.warehouseSpu,
skuName: row.skuName,
skuImage: row.skuImage,
warehouseId: row.warehouseId,
warehouseName: row.warehouseName,
},
])
spuSearchResultList.value = spuSearchResultList.value.filter(
(item) => item.warehouseSpu !== row.warehouseSpu,
)
if (spuSearchResultList.value.length === 0) {
popoverVisible.value = false
}
}
const handleRemoveSpu = async (index: number, row: WarehouseRuleSpuItem) => {
try {
await ElMessageBox.confirm(
`确定删除${row.warehouseSpu}与共享库存组${editForm.value.groupName}关系吗?`,
'提示',
{
type: 'warning',
},
)
} catch {
return
}
const list = [...(editForm.value.spuList ?? [])]
list.splice(index, 1)
editForm.value.spuList = updateSortNo(list)
}
const handleCancel = () => {
dialogVisible.value = false
}
const handleSave = async () => {
const valid = await editFormRef.value?.validate().catch(() => false)
if (!valid) {
return
}
if (!editForm.value.spuList?.length) {
ElMessageBox.alert('请添加组内SPU', '提示', {
type: 'warning',
})
return
}
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
const editId = editForm.value.id
const request = editId
? updateSharedInventoryGroupApi
: addSharedInventoryGroupApi
const res = await request({
groupName: editForm.value.groupName!,
status: !!editForm.value.status,
id: editId,
dataVersion: editForm.value.dataVersion!,
skuList: editForm.value.spuList.map((item) => ({
warehouseSpu: item.warehouseSpu!,
sortOrder: item.sortOrder!,
warehouseId: item.warehouseId!,
})),
})
if (res.code !== 200) {
return
}
ElMessage.success('保存成功')
emit('saveSuccess')
dialogVisible.value = false
} catch (error) {
console.error(error)
} finally {
loading.close()
}
}
defineExpose({
resetFields,
})
</script>
<style scoped lang="scss">
.section-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-left: 8px;
border-left: 3px solid #409eff;
& + .el-form-item {
margin-top: 0;
}
&:not(:first-child) {
margin-top: 8px;
}
}
.spu-search-bar {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.spu-drag-tip {
font-size: 12px;
color: #909399;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 4px;
}
.spu-table-wrapper {
height: 280px;
:deep(tbody tr.el-table__row) {
cursor: move;
user-select: none;
}
:deep(img) {
-webkit-user-drag: none;
}
}
.dialog-footer {
display: flex;
justify-content: center;
}
.popover-table {
height: 200px;
}
</style>
<style lang="scss">
.spu-search-popover {
.el-button.is-circle {
padding: 6px;
}
}
</style>
import { ElButton, ElTag } from 'element-plus'
import type { WarehouseRuleSettingData } from '@/types/api/warehouse/warehouseRuleSetting'
const statusMap: Record<'true' | 'false', { label: string; type: 'success' | 'danger' }> = {
true: { label: '启用', type: 'success' },
false: { label: '禁用', type: 'danger' },
}
export interface UseWarehouseRuleSettingTableColumnsOptions {
onEdit?: (row: WarehouseRuleSettingData) => void
}
export function useWarehouseRuleSettingTableColumns(
options: UseWarehouseRuleSettingTableColumnsOptions = {},
) {
const columns = computed(() => [
{
label: '库存组名称',
prop: 'groupName',
align: 'left',
},
{
label: '共享库存SPU',
prop: 'warehouseSpu',
align: 'left',
render: (row: WarehouseRuleSettingData) => {
const spuList = [
...new Set(
(row.skuList ?? [])
.map((item) => item.warehouseSpu)
.filter(Boolean),
),
]
return <span>{spuList.join(',')}</span>
},
},
{
label: '创建时间',
prop: 'createTime',
width: 180,
align: 'center',
},
{
label: '状态',
prop: 'status',
width: 100,
align: 'center',
render: (row: WarehouseRuleSettingData) => {
const status = statusMap[row.status ? 'true' : 'false']
return status ? (
<ElTag type={status.type}>{status.label}</ElTag>
) : (
<span>-</span>
)
},
},
{
label: '操作',
width: 100,
align: 'center',
render: (row: WarehouseRuleSettingData) => {
return (
<div>
<ElButton
link
type="primary"
onClick={() => options?.onEdit?.(row)}
>
编辑
</ElButton>
</div>
)
},
},
])
const spuTableColumns = computed(() => [
{
label: '扣减顺序',
width: 90,
align: 'center',
slot: 'sortNo',
},
{
label: '图片',
width: 80,
align: 'center',
slot: 'spuImage',
},
{
label: '库存SPU',
prop: 'warehouseSpu',
minWidth: 120,
align: 'left',
},
{
label: '商品名称',
prop: 'skuName',
minWidth: 160,
align: 'left',
},
{
label: '所属仓库',
prop: 'warehouseName',
width: 120,
align: 'center',
},
{
label: '操作',
width: 80,
align: 'center',
slot: 'operation',
},
])
const spuSearchTableColumns = computed(() => [
{
label: '图片',
width: 80,
align: 'center',
slot: 'spuImage',
},
{
label: '库存SPU',
prop: 'warehouseSpu',
minWidth: 120,
align: 'center',
},
{
label: '商品名称',
prop: 'skuName',
minWidth: 160,
align: 'left',
},
{
label: '所属仓库',
prop: 'warehouseName',
width: 120,
align: 'center',
},
{
label: '操作',
width: 80,
align: 'center',
slot: 'operation',
},
])
return { columns, spuTableColumns, spuSearchTableColumns }
}
<template>
<div class="page card h-100 flex-gap-10 overflow-hidden flex">
<div class="content-container flex-1 flex-column overflow-hidden">
<div class="search-form">
<ElForm ref="searchFormRef" :model="searchForm" inline>
<ElFormItem label="创建时间">
<ElDatePicker
v-model="dateRange"
:default-time="[
new Date(0, 0, 0, 0, 0, 0),
new Date(0, 0, 0, 23, 59, 59),
]"
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
clearable
style="width: 260px"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</ElFormItem>
<ElFormItem label="库存SPU">
<ElInput
v-model="searchForm.warehouseSpu"
clearable
placeholder="库存SPU"
style="width: 140px"
/>
</ElFormItem>
<ElFormItem label="库存组名称">
<ElInput
v-model="searchForm.groupName"
clearable
placeholder="库存组名称"
style="width: 140px"
/>
</ElFormItem>
<ElFormItem label="状态"
><ElSelect
v-model="searchForm.status"
placeholder="请选择"
style="width: 140px"
clearable
>
<ElOption label="启用" :value="1" />
<ElOption label="禁用" :value="0" />
</ElSelect>
</ElFormItem>
<ElFormItem>
<ElButton @click="handleReset">重置</ElButton>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" @click="search">查询</ElButton>
</ElFormItem>
<ElFormItem>
<ElButton type="success" @click="() => handleEdit()">新增</ElButton>
</ElFormItem>
<ElFormItem>
<ElButton type="danger" @click="handleDelete">删除</ElButton>
</ElFormItem>
</ElForm>
</div>
<div v-loading="loading" class="table-wrapper">
<TableView
serial-numberable
:selectionable="true"
:paginated-data="tableData"
:columns="tableColumns"
@selection-change="handleSelectionChange"
/>
</div>
<div class="table-pagination-bar">
<div class="selected-count-text">
已选择
<strong style="color: red">{{ selectedRows.length }}</strong>
条数据
</div>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[100, 200, 300, 400, 500]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin: 0 auto"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</div>
</div>
<EditFormDialog
ref="editFormRef"
v-model:visible="editDialogVisible"
:warehouse-list="warehouseList"
@save-success="search"
/>
</template>
<script lang="ts" setup>
import {
getWarehouseRuleSettingListApi,
getWarehouseRuleSettingDetailApi,
deleteSharedInventoryGroupApi,
} from '@/api/warehouse/warehouseRuleSetting'
import {
SearchForm,
WarehouseRuleSettingData,
} from '@/types/api/warehouse/warehouseRuleSetting'
import usePageList from '@/utils/hooks/usePageList'
import { useValue } from '@/utils/hooks/useValue'
import { useWarehouseRuleSettingTableColumns } from './hooks/useWarehouseRuleSettingTableColumns'
import EditFormDialog from './components/EditDialog.vue'
import { WarehouseListData } from '@/types'
import { loadWarehouseListApi } from '@/api/common.ts'
const [searchForm, resetSearchForm] = useValue<SearchForm>({
createStartTime: '',
createEndTime: '',
warehouseSpu: '',
groupName: '',
status: 1,
})
const dateRange = ref([])
const handleReset = () => {
resetSearchForm()
dateRange.value = []
search()
}
const editDialogVisible = ref(false)
const editFormRef = ref()
const warehouseList = ref<WarehouseListData[]>([])
const {
loading,
currentPage,
pageSize,
total,
data: tableData,
refresh: search,
onCurrentPageChange: handleCurrentChange,
onPageSizeChange: handleSizeChange,
} = usePageList<WarehouseRuleSettingData>({
query: (page, pageSize) =>
getWarehouseRuleSettingListApi(
{
...searchForm.value,
createEndTime: dateRange.value && dateRange.value[1],
createStartTime: dateRange.value && dateRange.value[0],
},
page,
pageSize,
).then((res) => res.data),
})
const selectedRows = ref<WarehouseRuleSettingData[]>([])
const handleSelectionChange = (selection: WarehouseRuleSettingData[]) => {
selectedRows.value = selection
}
const handleEdit = async (row?: WarehouseRuleSettingData) => {
if (row) {
// 调用接口,获取数据
const loading = ElLoading.service({
fullscreen: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await getWarehouseRuleSettingDetailApi(row.id)
if (res.code !== 200) return
editFormRef.value.resetFields(res.data)
editDialogVisible.value = true
} catch (error) {
console.error(error)
} finally {
loading.close()
}
} else {
await editFormRef.value.resetFields()
editDialogVisible.value = true
}
}
const getSharedSpuText = (rows: WarehouseRuleSettingData[]) =>
[
...new Set(
rows.flatMap((row) =>
(row.skuList ?? [])
.map((item) => item.warehouseSpu)
.filter(Boolean),
),
),
].join(',')
const handleDelete = async () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请选择要删除的数据')
return
}
const groupNames = selectedRows.value
.map((item) => item.groupName)
.join(',')
const spuText = getSharedSpuText(selectedRows.value)
try {
await ElMessageBox.confirm(
`<div>
<div style="font-size: 16px;line-height: 24px; margin-bottom: 8px;">
确定删除共享库存组<strong style="color: red;">${groupNames}</strong>吗?
</div>
<div style="font-size: 14px; color: #606266; line-height: 22px;">
共享库存组SPU:${spuText || '-'}
</div>
</div>`,
'',
{
type: 'warning',
dangerouslyUseHTMLString: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
customClass: 'delete-shared-inventory-group-box',
},
)
} catch {
return
}
const loading = ElLoading.service({
fullscreen: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await deleteSharedInventoryGroupApi(
selectedRows.value.map((item) => item.id).join(','),
)
if (res.code !== 200) return
ElMessage.success(res.message)
search()
} catch (error) {
console.error(error)
} finally {
loading.close()
}
}
const { columns: tableColumns } = useWarehouseRuleSettingTableColumns({
onEdit: handleEdit,
})
const loadWarehouseList = async () => {
const res = await loadWarehouseListApi()
if (res.code !== 200) return
warehouseList.value = res.data || []
}
onMounted(async () => {
loadWarehouseList()
})
</script>
<style scoped lang="scss">
.table-wrapper {
flex: 1;
overflow: hidden;
}
.table-pagination-bar {
margin-top: 10px;
display: flex;
align-items: center;
}
.selected-count-text {
font-size: 14px;
color: #606266;
white-space: nowrap;
}
</style>
<style>
.delete-shared-inventory-group-box .el-message-box__title {
display: none;
}
.delete-shared-inventory-group-box .el-message-box__header {
padding-bottom: 0;
}
.delete-shared-inventory-group-box .el-message-box__message {
word-break: break-all;
line-height: 1.6;
padding: 10px 0;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment