Commit 9aa459de by qinjianhui

Merge branch 'dev' into 'master'

Dev

See merge request !277
parents ca66d631 c93e5c54
......@@ -15,7 +15,9 @@ declare module 'vue' {
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
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']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
......@@ -27,24 +29,20 @@ declare module 'vue' {
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
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']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
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']
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']
ImageView: typeof import('./src/components/ImageView.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 @@
align="center"
:class="isMore ? 'is-more-active' : ''"
></ElTableColumn>
<template v-for="column in columns" :key="column.key">
<template v-for="column in columns" :key="column.prop">
<ElTableColumn
v-if="column.type === 'expand'"
v-bind="column"
......
......@@ -83,6 +83,8 @@ export const dynamicComponentMap: Record<string, LazyComponent> = {
'/MyDownloads': () => import('@/views/MyDownloads.vue'),
'/supply/supplierManagement': () =>
import('@/views/supply/supplierManagement/index.vue'),
'/warehouse/warehouse-rule-setting': () =>
import('@/views/warehouse/warehouseRuleSetting/index.vue'),
}
/** dashboard 是登录后的默认页,保留为静态路由 */
......
......@@ -112,18 +112,18 @@ const menu: MenuItem[] = [
{ index: '/warehouse/position', id: 322, label: '库位管理' },
],
},
// {
// index: 'warehouse-rules',
// id: 33,
// label: '仓库规则',
// children: [
// {
// index: '/warehouse/warehouse-rule-setting',
// id: 331,
// label: '共享库存设置',
// },
// ],
// },
{
index: 'warehouse-rules',
id: 33,
label: '仓库规则',
children: [
{
index: '/warehouse/warehouse-rule-setting',
id: 331,
label: '仓库规则设置',
},
],
},
],
},
{
......
......@@ -151,6 +151,7 @@ export interface operateOrderListData {
variantImage?: string
thirdSpuCode?: string
thirdSkuCode?: string
originThirdSkuCode?: string
supplierProductNo?: string
productName?: string
productMark?: string
......
......@@ -162,6 +162,7 @@ export interface operateOrderListData {
variantImage?: string
thirdSpuCode?: string
thirdSkuCode?: string
originThirdSkuCode?: string
supplierProductNo?: string
productName?: 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 { VNode } from 'vue'
interface Column<D> extends Omit<ElColumn, 'width'> {
/** 为 false 时隐藏该列;未设置时默认显示 */
showColumn?: boolean
width?: number | string
minWidth?: number | string
key?: string
slot?: string
headerSlot?: string
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
children?: Column<D>[]
subs?: Column<D>[]
......
......@@ -62,7 +62,10 @@
<template #images>
<div class="flex-between">
<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
v-for="(img, index) in parseImageAry(item.imageAry)"
:key="index"
......@@ -72,7 +75,9 @@
<el-image
:src="img.url"
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"
/>
</div>
......@@ -175,13 +180,33 @@
<span class="info-value">{{ item.craftName }}</span>
</div>
<div class="card-info-row">
<span
class="info-value clickable ellipsis"
:title="`库存SKU:${(item.thirdSkuCode as string) || ''}`"
@click.stop="copyText((item.thirdSkuCode as string) || '')"
>
{{ item.thirdSkuCode }}
</span>
<div class="third-sku-cell">
<el-tooltip effect="dark" placement="top">
<template #content>
<div>
下单库存SKU:
{{
(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 class="card-info-row">
<span class="info-label">店铺单号:</span>
......@@ -306,6 +331,7 @@ import type { LogListData } from '@/types/api/order'
import LogList from '@/components/LogList.vue'
import RightClickMenu from '@/components/RightClickMenu.vue'
import platformJson from '../../../../json/platform.json'
import { WarningFilled } from '@element-plus/icons-vue'
const props = defineProps<{
status?: string
......@@ -715,6 +741,13 @@ defineExpose({ clearSelection, getSelectedIds, refresh })
.flex-row-gap6 {
gap: 6px;
}
.third-sku-cell {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
overflow: hidden;
}
@media (max-width: 1920px) {
.card-grid {
grid-template-columns: repeat(5, minmax(0, 1fr));
......
......@@ -1270,6 +1270,7 @@ import {
CaretBottom,
Edit,
DocumentCopy,
WarningFilled,
} from '@element-plus/icons-vue'
import { computed, nextTick, onMounted, ref } from 'vue'
import splitDiv from '@/components/splitDiv/splitDiv.vue'
......@@ -2210,9 +2211,40 @@ const operationOrderColumns = computed(() => [
key: 'thirdSkuCode',
prop: 'thirdSkuCode',
label: '库存SKU',
minWidth: 140,
minWidth: 160,
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',
......@@ -3795,6 +3827,18 @@ onMounted(() => {
text-overflow: ellipsis;
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 {
......
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