Commit 9498e00e by zhuzhequan

Merge branch 'dev' into 'master'

Dev

See merge request !105
parents ad9c9103 9deae37e
...@@ -85,6 +85,23 @@ export function getCardOrderList( ...@@ -85,6 +85,23 @@ export function getCardOrderList(
}, },
) )
} }
export function uploadPRNFile(
id: number,
data: FormData,
) {
return axios.post<never, BaseRespData<string>>(
`/factory/podBatchDownload/uploadPRNFile?id=${id}`,
data
)
}
export function updatePRNDownloadStatus(
id: number
) {
return axios.get<never, BaseRespData<string>>(
`/factory/podBatchDownload/updatePRNDownloadStatus?id=${id}`
)
}
export function confirmOrderApi( export function confirmOrderApi(
data: number[], data: number[],
productionClient: string, productionClient: string,
......
import { BasePaginationData, BaseRespData } from '@/types/api'
import axios from './../axios'
import {
IListPage,
IsupplierType,
} from '@/views/supply/supplierManagement/types/index'
//供应商分页查询
export function getSupplierListApi(params: IListPage) {
return axios.get<never, BasePaginationData<never>>(
'/factory/supplier/list_page',
{ params },
)
}
// 删除供应商
export function deleteSupplierApi(params: { ids: string }) {
return axios.get<never, BaseRespData<never>>('/factory/supplier/delete', {
params,
})
}
// 编辑回显接口
export function getSupplierDetailApi(id: string | number) {
return axios.get<never, BaseRespData<never>>('factory/supplier/get', {
params: { id },
})
}
// 根据分类id获取属性信息
export function getPropertyByCateIdApi(cateId: string | number) {
return axios.get<never, BaseRespData<never>>(
'/factory/supplier/getPropertyByCateId',
{
params: { cateId },
},
)
}
// 根据spu获取商品信息
export function getProductInfoBySpuApi(spu: string | number) {
return axios.get<never, BaseRespData<never>>(
'/factory/supplier/getProductInfoBySpu',
{
params: { spu },
},
)
}
// 获取币种接口
export function getBaseCurrencyInfoApi() {
return axios.get<never, BaseRespData<never>>(
'factory/supplier/getBaseCurrencyInfo',
)
}
//新增
export function addSupplierApi(params: IsupplierType) {
return axios.post<never, BaseRespData<never>>('/factory/supplier/add', params)
}
//修改
export function updateSupplierApi(params: IsupplierType) {
return axios.post<never, BaseRespData<never>>(
'/factory/supplier/update',
params,
)
}
...@@ -16,7 +16,7 @@ type SimpleFormData = Record<string, unknown> ...@@ -16,7 +16,7 @@ type SimpleFormData = Record<string, unknown>
// 定义表单项配置接口 // 定义表单项配置接口
export interface IFormConfig { export interface IFormConfig {
fixed?: string fixed?: string
title?: string title?: string | boolean
prop?: string prop?: string
label?: string label?: string
type?: string type?: string
...@@ -190,6 +190,7 @@ export default defineComponent({ ...@@ -190,6 +190,7 @@ export default defineComponent({
flexWrap: 'wrap', flexWrap: 'wrap',
}} }}
> >
{typeof item.title === 'string' && item.title !== '' && (
<div <div
style={{ style={{
width: '100%', width: '100%',
...@@ -201,6 +202,7 @@ export default defineComponent({ ...@@ -201,6 +202,7 @@ export default defineComponent({
> >
{item.title} {item.title}
</div> </div>
)}
{item.render && item.render(item, this.formData)} {item.render && item.render(item, this.formData)}
</div> </div>
) : ( ) : (
......
...@@ -110,6 +110,13 @@ export default defineComponent({ ...@@ -110,6 +110,13 @@ export default defineComponent({
emit('getCheckboxRecords', selectRecords) emit('getCheckboxRecords', selectRecords)
} }
} }
//获取设置多选框
const setCheckboxRow = (row: TableRowData, checked: boolean) => {
const $table = tableRef.value
if ($table) {
$table.setCheckboxRow(row, checked)
}
}
//设置高亮行 //设置高亮行
const selectRowEvent = (row: TableRowData) => { const selectRowEvent = (row: TableRowData) => {
const $table = tableRef.value const $table = tableRef.value
...@@ -129,6 +136,7 @@ export default defineComponent({ ...@@ -129,6 +136,7 @@ export default defineComponent({
editConfig, editConfig,
getSelectEvent, getSelectEvent,
selectRowEvent, selectRowEvent,
setCheckboxRow,
attrs, attrs,
} }
}, },
......
...@@ -131,7 +131,7 @@ const router = createRouter({ ...@@ -131,7 +131,7 @@ const router = createRouter({
meta: { meta: {
title: '下载生产客户端', title: '下载生产客户端',
}, },
component: () => { }, component: () => {},
beforeEnter() { beforeEnter() {
// 假设你的下载链接是这个 // 假设你的下载链接是这个
const downloadLink = '/exeFiles/JomallProductionAssistantSetup.exe' const downloadLink = '/exeFiles/JomallProductionAssistantSetup.exe'
...@@ -317,6 +317,15 @@ const router = createRouter({ ...@@ -317,6 +317,15 @@ const router = createRouter({
name: 'MyDownloads', name: 'MyDownloads',
component: () => import('@/views/MyDownloads.vue'), component: () => import('@/views/MyDownloads.vue'),
}, },
{
path: '/supply/supplierManagement',
meta: {
title: '供应商管理',
},
name: 'supplierManagement',
component: () =>
import('@/views//supply/supplierManagement/index.vue'),
},
], ],
}, },
// 登录 // 登录
......
...@@ -150,6 +150,18 @@ const menu: MenuItem[] = [ ...@@ -150,6 +150,18 @@ const menu: MenuItem[] = [
}, },
{ {
index: '12',
id: 4,
label: '供应',
children: [
{
index: '/supply/supplierManagement',
id: 1,
label: '供应商管理',
},
],
},
{
index: '11', index: '11',
id: 3, id: 3,
label: '对账单', label: '对账单',
......
...@@ -48,8 +48,11 @@ export interface SearchForm { ...@@ -48,8 +48,11 @@ export interface SearchForm {
export interface PodUsOrderListData { export interface PodUsOrderListData {
id: number id: number
thirdOrderNumber?: string thirdOrderNumber?: string
prnUrl?: string
factoryOrderNumber?: string factoryOrderNumber?: string
prnDownloadStatus?: boolean
shopNumber?: string shopNumber?: string
isUpload?: boolean
factoryOnlineId?: number | null factoryOnlineId?: number | null
factoryNo?: number | null factoryNo?: number | null
factoryCode?: string | null factoryCode?: string | null
......
...@@ -997,7 +997,8 @@ ...@@ -997,7 +997,8 @@
style="display: flex; flex-direction: column" style="display: flex; flex-direction: column"
> >
<div <div
v-for="img in item.productMark !== 'normal' v-for="img in item.productMark !== 'normal' &&
item.productMark !== 'custom_normal'
? item.previewImgs ? item.previewImgs
: [{ url: item.variantImage }]" : [{ url: item.variantImage }]"
:key="img" :key="img"
......
...@@ -399,33 +399,43 @@ ...@@ -399,33 +399,43 @@
> >
<ElDropdown> <ElDropdown>
<el-button type="primary"> <el-button type="primary">
DTF排版<el-icon class="el-icon--right"><ArrowDown /></el-icon> DTF排版
<el-icon class="el-icon--right">
<ArrowDown />
</el-icon>
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<ElDropdownMenu> <ElDropdownMenu>
<ElDropdownItem <ElDropdownItem
:loading="tifDownloadLoading" :loading="tifDownloadLoading"
@click="downloadTif('tiff', 42)" @click="downloadTif('tiff', 42)"
>TIF(40+2cm)</ElDropdownItem >TIF(40+2cm)
</ElDropdownItem
> >
<ElDropdownItem <ElDropdownItem
:loading="tifDownloadLoading" :loading="tifDownloadLoading"
@click="downloadTif('tiff', 60)" @click="downloadTif('tiff', 60)"
>TIF(60cm)</ElDropdownItem >TIF(60cm)
</ElDropdownItem
> >
<ElDropdownItem <ElDropdownItem
:loading="pngDownloadLoading" :loading="pngDownloadLoading"
@click="downloadTif('png', 42)" @click="downloadTif('png', 42)"
>PNG(40+2cm)</ElDropdownItem >PNG(40+2cm)
</ElDropdownItem
> >
<ElDropdownItem <ElDropdownItem
:loading="pngDownloadLoading" :loading="pngDownloadLoading"
@click="downloadTif('png', 60)" @click="downloadTif('png', 60)"
>PNG(60cm)</ElDropdownItem >PNG(60cm)
</ElDropdownItem
>
</ElDropdownMenu>
</template
>
</ElDropdown
> >
</ElDropdownMenu></template </ElFormItem
></ElDropdown
></ElFormItem
> >
<!-- <ElFormItem <!-- <ElFormItem
v-if=" v-if="
...@@ -1781,6 +1791,26 @@ ...@@ -1781,6 +1791,26 @@
{{ row.automaticComposing ? '是' : '否' }} {{ row.automaticComposing ? '是' : '否' }}
</div> </div>
</template> </template>
<template #prn="{ row }">
<div style="display: flex">
<span :title="fileName(row)" class="flex-1">{{ fileName(row) }}</span>
<el-link :disabled="row.isUpload" underline="never" type="success" @click="uploadFile(row)">上传</el-link>
<el-icon v-if="row.isUpload" style="right: 0;top:5px;" class="is-loading"
>
<Loading
/>
</el-icon>
<el-link
:disabled="!row.prnUrl" style="margin-left: 8px" underline="never" type="primary"
@click="downloadRowProFile(row)">下载
</el-link>
<el-icon v-if="row.prnDownloadStatus" style="right: -2px" class="check-icon"
>
<CircleCheckFilled
/>
</el-icon>
</div>
</template>
<template #composingParam="{ row }"> <template #composingParam="{ row }">
<div style="white-space: pre-line"> <div style="white-space: pre-line">
{{ row.composingParam?.split(';').join('\n') }} {{ row.composingParam?.split(';').join('\n') }}
...@@ -1825,10 +1855,11 @@ ...@@ -1825,10 +1855,11 @@
<span class="operate-item"> <span class="operate-item">
<ElButton <ElButton
link link
title="打印拣货单"
type="primary" type="primary"
@click="printPickingOrderItem(row, 1)" @click="printPickingOrderItem(row, 1)"
> >
打印拣货单 拣货单
</ElButton> </ElButton>
<el-icon v-if="row.printPickOrder" class="check-icon" <el-icon v-if="row.printPickOrder" class="check-icon"
><CircleCheckFilled ><CircleCheckFilled
...@@ -1837,10 +1868,11 @@ ...@@ -1837,10 +1868,11 @@
<span class="operate-item"> <span class="operate-item">
<ElButton <ElButton
link link
title="打印生产单"
type="primary" type="primary"
@click="printPickingOrderItem(row, 2)" @click="printPickingOrderItem(row, 2)"
> >
打印生产单 生产单
</ElButton> </ElButton>
<el-icon v-if="row.printProductOrder" class="check-icon" <el-icon v-if="row.printProductOrder" class="check-icon"
><CircleCheckFilled ><CircleCheckFilled
...@@ -1850,11 +1882,12 @@ ...@@ -1850,11 +1882,12 @@
<ElButton <ElButton
:disabled="row.productNum > 50" :disabled="row.productNum > 50"
link link
title="重新排版"
type="warning" type="warning"
:loading="reComposingLoadingMap[row.id]" :loading="reComposingLoadingMap[row.id]"
@click="showArrange(1, row)" @click="showArrange(1, row)"
> >
新排版
</ElButton> </ElButton>
</span> </span>
<span class="operate-item"> <span class="operate-item">
...@@ -1994,8 +2027,8 @@ ...@@ -1994,8 +2027,8 @@
<div <div
v-for="(cardItem) in tableData as ProductList[]" v-for="(cardItem) in tableData as ProductList[]"
:key="cardItem.id" :key="cardItem.id"
class="card-list-item"
ref="cardRefs" ref="cardRefs"
class="card-list-item"
@click="cardClick(cardItem)" @click="cardClick(cardItem)"
@mouseleave="handleChangeImages(null, cardItem)" @mouseleave="handleChangeImages(null, cardItem)"
> >
...@@ -2043,24 +2076,25 @@ ...@@ -2043,24 +2076,25 @@
type="primary" type="primary"
:title="item.name || ''" :title="item.name || ''"
style="margin-bottom: 2px" style="margin-bottom: 2px"
>{{ item.name || '' }}</el-tag >{{ item.name || '' }}
</el-tag
> >
</div> </div>
</template> </template>
<div <div
class="flex" v-if="cardItem.customTagList?.length"
ref="tagRefs" ref="tagRefs"
class="flex"
style="gap: 5px; overflow: hidden" style="gap: 5px; overflow: hidden"
v-if="cardItem.customTagList?.length"
> >
<el-tag <el-tag
size="small"
type="primary"
v-for="(item, index) in cardItem.customTagList.slice( v-for="(item, index) in cardItem.customTagList.slice(
0, 0,
3, 3,
)" )"
:key="index" :key="index"
size="small"
type="primary"
><span ><span
style=" style="
width: 50px; width: 50px;
...@@ -2075,10 +2109,11 @@ ...@@ -2075,10 +2109,11 @@
></el-tag ></el-tag
> >
<el-tag <el-tag
v-if="cardItem.customTagList?.slice(3)?.length"
size="small" size="small"
type="primary" type="primary"
v-if="cardItem.customTagList?.slice(3)?.length" >+{{ cardItem.customTagList.slice(3).length }}
>+{{ cardItem.customTagList.slice(3).length }}</el-tag </el-tag
> >
</div> </div>
</el-tooltip> </el-tooltip>
...@@ -2639,8 +2674,8 @@ ...@@ -2639,8 +2674,8 @@
v-model:form="currentRow" v-model:form="currentRow"
v-model:visible="updateAddVisible" v-model:visible="updateAddVisible"
:country-list="countryList" :country-list="countryList"
@success="search"
:type="updateAddressType" :type="updateAddressType"
@success="search"
></UpdateAddress> ></UpdateAddress>
<ElDialog <ElDialog
v-model="exceptionDialogVisible" v-model="exceptionDialogVisible"
...@@ -2842,7 +2877,8 @@ ...@@ -2842,7 +2877,8 @@
typesettingVisible = false typesettingVisible = false
} }
" "
>取消</el-button >取消
</el-button
> >
<el-button type="primary" @click="submitTypesetting">确认</el-button> <el-button type="primary" @click="submitTypesetting">确认</el-button>
</template> </template>
...@@ -2903,6 +2939,7 @@ import { ...@@ -2903,6 +2939,7 @@ import {
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import { import {
getCardOrderList, getCardOrderList,
uploadPRNFile,
getOrderList, getOrderList,
getOrderTabData, getOrderTabData,
confirmOrderApi, confirmOrderApi,
...@@ -2964,7 +3001,7 @@ import { ...@@ -2964,7 +3001,7 @@ import {
getCustomTagListApi, getCustomTagListApi,
getLogisticsWayApi, getLogisticsWayApi,
printNormalPickPdfApi, printNormalPickPdfApi,
reissueOrderApi reissueOrderApi, updatePRNDownloadStatus,
} from '@/api/podUsOrder' } from '@/api/podUsOrder'
import { BaseRespData } from '@/types/api' import { BaseRespData } from '@/types/api'
...@@ -2997,6 +3034,7 @@ import { showConfirm } from '@/utils/ui' ...@@ -2997,6 +3034,7 @@ import { showConfirm } from '@/utils/ui'
import { import {
DocumentCopy, DocumentCopy,
EditPen, EditPen,
Loading,
CircleCheckFilled, CircleCheckFilled,
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import { Column, ElFormItem } from 'element-plus' import { Column, ElFormItem } from 'element-plus'
...@@ -3025,7 +3063,7 @@ import { ...@@ -3025,7 +3063,7 @@ import {
declare global { declare global {
interface Window { interface Window {
ActiveXObject: { ActiveXObject: {
new (type: string): XMLHttpRequest new(type: string): XMLHttpRequest
} }
VBS_BinaryToArray: { VBS_BinaryToArray: {
(data: unknown): { toArray(): number[] } (data: unknown): { toArray(): number[] }
...@@ -3552,6 +3590,12 @@ const tableColumns = computed(() => { ...@@ -3552,6 +3590,12 @@ const tableColumns = computed(() => {
prop: 'automaticComposing', prop: 'automaticComposing',
slot: 'automaticComposing', slot: 'automaticComposing',
align: 'center', align: 'center',
}, {
label: 'PRN文件',
width: 300,
prop: 'automaticComposing',
slot: 'prn',
align: 'right',
}, },
{ {
label: '排版参数', label: '排版参数',
...@@ -3563,7 +3607,7 @@ const tableColumns = computed(() => { ...@@ -3563,7 +3607,7 @@ const tableColumns = computed(() => {
{ {
label: '操作', label: '操作',
slot: 'operate', slot: 'operate',
width: 350, width: 300,
align: 'center', align: 'center',
fixed: 'right', fixed: 'right',
prop: 'operate', prop: 'operate',
...@@ -3967,7 +4011,10 @@ watch( ...@@ -3967,7 +4011,10 @@ watch(
}, },
{ deep: true, immediate: true }, // 添加immediate确保初始化时执行 { deep: true, immediate: true }, // 添加immediate确保初始化时执行
) )
const fileName = (row: PodUsOrderListData) => {
if (!row.prnUrl) return ''
return row.prnUrl.split('/')[row.prnUrl.split('/').length - 1]
}
const search = () => { const search = () => {
selection.value = [] selection.value = []
cardSelection.value = [] cardSelection.value = []
...@@ -4145,6 +4192,37 @@ const productionClientVisible = ref(false) ...@@ -4145,6 +4192,37 @@ const productionClientVisible = ref(false)
// productionClientVisible.value = true // productionClientVisible.value = true
// } // }
const downloadRowProFile = async (row: PodUsOrderListData) => {
const url =
`https://factory.jomalls.com/upload/factory` + row.prnUrl
window.open(url, '_blank')
await updatePRNDownloadStatus(
row.id,
)
search()
}
const uploadFile = (row: PodUsOrderListData) => {
const input = document.createElement('input')
input.style.display = 'none'
input.type = 'file'
input.multiple = false
input?.click()
row.isUpload = true
input.onchange = async function() {
try {
if (input.files && input.files.length) {
const fm = new FormData()
fm.append('file', input.files?.[0])
const res = await uploadPRNFile(row.id, fm)
row.prnUrl = res.message
}
} finally {
row.isUpload = false
}
}
}
const downloadTif = async (type: string, templateWidth: number) => { const downloadTif = async (type: string, templateWidth: number) => {
if (!cardSelection.value.length) { if (!cardSelection.value.length) {
return ElMessage.warning('请选择数据') return ElMessage.warning('请选择数据')
...@@ -6562,6 +6640,7 @@ const printNormal = async () => { ...@@ -6562,6 +6640,7 @@ const printNormal = async () => {
background: rgb(255 243 205); background: rgb(255 243 205);
color: rgb(91, 99, 18); color: rgb(91, 99, 18);
} }
.empty { .empty {
height: 100%; height: 100%;
display: flex; display: flex;
...@@ -6765,13 +6844,16 @@ const printNormal = async () => { ...@@ -6765,13 +6844,16 @@ const printNormal = async () => {
color: white; color: white;
font-weight: bold; font-weight: bold;
} }
.triangle-container-wrap { .triangle-container-wrap {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
} }
.triangle-container { .triangle-container {
position: relative; position: relative;
.triangle-marker { .triangle-marker {
width: 0; width: 0;
height: 0; height: 0;
...@@ -6779,6 +6861,7 @@ const printNormal = async () => { ...@@ -6779,6 +6861,7 @@ const printNormal = async () => {
border-right: 18px solid #e74c3c; border-right: 18px solid #e74c3c;
border-top: 18px solid #e74c3c; border-top: 18px solid #e74c3c;
} }
.content { .content {
position: absolute; position: absolute;
top: 0; top: 0;
...@@ -6790,13 +6873,16 @@ const printNormal = async () => { ...@@ -6790,13 +6873,16 @@ const printNormal = async () => {
font-size: 12px; font-size: 12px;
} }
} }
.triangle-container-wrap { .triangle-container-wrap {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
} }
.triangle-container { .triangle-container {
position: relative; position: relative;
.triangle-marker { .triangle-marker {
width: 0; width: 0;
height: 0; height: 0;
...@@ -6804,6 +6890,7 @@ const printNormal = async () => { ...@@ -6804,6 +6890,7 @@ const printNormal = async () => {
border-right: 18px solid #e74c3c; border-right: 18px solid #e74c3c;
border-top: 18px solid #e74c3c; border-top: 18px solid #e74c3c;
} }
.content { .content {
position: absolute; position: absolute;
top: 0; top: 0;
...@@ -6839,8 +6926,8 @@ const printNormal = async () => { ...@@ -6839,8 +6926,8 @@ const printNormal = async () => {
} }
.el-timeline .el-timeline
> .el-timeline-item:first-child > .el-timeline-item:first-child
.el-timeline-item__timestamp.is-top { .el-timeline-item__timestamp.is-top {
color: #409eff; color: #409eff;
} }
...@@ -6849,6 +6936,7 @@ const printNormal = async () => { ...@@ -6849,6 +6936,7 @@ const printNormal = async () => {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.tabs-node-quantity { .tabs-node-quantity {
position: relative; position: relative;
color: red; color: red;
...@@ -6856,6 +6944,7 @@ const printNormal = async () => { ...@@ -6856,6 +6944,7 @@ const printNormal = async () => {
top: -10px; top: -10px;
right: 0px; right: 0px;
} }
.operate-item { .operate-item {
position: relative; position: relative;
} }
...@@ -6867,4 +6956,12 @@ const printNormal = async () => { ...@@ -6867,4 +6956,12 @@ const printNormal = async () => {
top: 30%; top: 30%;
transform: translateY(-50%); transform: translateY(-50%);
} }
.flex-1 {
flex: 1;
flex-shrink: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style> </style>
import { defineComponent, ref } from 'vue'
import { ElDialog } from 'element-plus'
export default defineComponent({
name: 'CustomizeForm',
props: {
modelValue: {
type: Boolean,
default: false,
},
title: {
type: String,
default: '',
},
dialogWidth: {
type: String,
default: '600px',
},
},
emits: ['update:modelValue', 'close'],
setup(props, { emit, attrs, slots }) {
const formRef = ref<InstanceType<typeof ElDialog> | null>(null)
const isShow = ref(false)
watch(
() => props.modelValue,
(val) => {
isShow.value = val
},
{ immediate: true },
)
return () => {
return (
<div>
<ElDialog
ref={formRef}
v-model={isShow.value}
title={props.title}
width={props.dialogWidth}
onClose={() => {
emit('close')
}}
destroy-on-close={true}
close-on-click-modal={false}
{...attrs}
>
<div class="dialog-form">
{slots.default?.()}
{slots.footer?.()}
</div>
</ElDialog>
</div>
)
}
},
})
<template>
<div class="user-page flex-column card h-100 overflow-hidden">
<div class="header-filter-form">
<ElButton type="success" @click="showDialog">新增供应商</ElButton>
<ElButton type="danger" @click="deleteFn">删除</ElButton>
</div>
<div class="user-content flex-1 flex-column overflow-hidden">
<div class="user-list flex-1 overflow-hidden" v-loading="loading">
<CustomizeTable
border
v-model="tableData"
:config="tableConfig"
align="center"
@getCheckboxRecords="handleCheckboxRecords"
></CustomizeTable>
</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: 10px auto 0; text-align: right"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></ElPagination>
</div>
</div>
<Dialog
:title="editForm['id'] ? '编辑供应商' : '新增供应商'"
v-model="dialogVisible"
@close="cancelFn"
width="75%"
>
<CustomizeForm
v-loading="editLoading"
ref="editFormRef"
v-model="editForm"
:config="formConfig"
formItemWidth="100%"
:labelWidth="100"
>
</CustomizeForm>
<template #footer>
<div style="text-align: center">
<ElButton @click="cancelFn">取消</ElButton>
<ElButton type="primary" @click="saveSupplierFn">保存</ElButton>
</div>
</template>
</Dialog>
<Dialog
title="管理供应价格"
v-model="priceDialogVisible"
width="65%"
v-loading="priceLoading"
@close="cancelPiceFn"
teleported
>
<div style="display: flex; margin-bottom: 10px">
<div style="display: flex; align-items: center; gap: 20px">
<div><span style="color: red">*</span> 结算币种</div>
<el-select
style="width: 300px; flex: 1"
v-model="currentGoods.currencyCode"
@change="
(v:string) => {
currentGoods.currencyName =
currencyOptions?.find((el) => v == el.currencyCode)
?.currencyName || ''
console.log(73,currentGoods.currencyName);
}
"
>
<el-option
v-for="(item, index) in currencyOptions"
:key="index"
:label="`${item.currencyName}(${item.currencyCode})`"
:value="item.currencyCode"
>
</el-option>
</el-select>
</div>
</div>
<div
style="border-top: 1px solid #eee; margin-bottom: 10px"
v-if="showColorList.length"
>
<div style="font-size: 20px; font-weight: bold; margin: 10px 0">
颜色(Color)
</div>
<div class="flex" style="flex-wrap: wrap; gap: 5px">
<el-tag
:title="`${item.cnname}(${item.enname})`"
:color="item.bgColor"
v-for="(item, index) in showColorList"
:key="index"
:style="{ color: item.fontColor }"
class="tabBox"
:class="{ active: colorIndex === item.code }"
@click="optionSelection(item, 'color')"
>{{ item.cnname }}({{ item.enname }})</el-tag
>
</div>
</div>
<div
style="border-top: 1px solid #eee; margin-bottom: 10px"
v-if="showSizeList.length"
>
<div style="font-size: 20px; font-weight: bold; margin: 10px 0">
尺码(Size)
</div>
<div class="flex" style="flex-wrap: wrap; gap: 5px">
<el-tag
class="tabBox"
color="#ffff87"
style="color: #333"
effect="dark"
type="info"
v-for="(item, index) in showSizeList"
:key="index"
:class="{ active: sizeIndex === item.code }"
@click="optionSelection(item, 'size')"
>
{{ item.cnname }}</el-tag
>
</div>
</div>
<div
style="
display: flex;
justify-content: end;
border-top: 1px solid #eee;
"
>
<div style="margin: 10px 0">
<el-input
placeholder="请输入供应价格"
style="width: 200px; margin-right: 10px"
size="small"
v-model="supplierPirce"
clearable
></el-input
><ElButton type="primary" @click="updatePrices" size="small"
>批量更新供应价格</ElButton
>
<span style="color: #f56c6c; vertical-align: middle"
>(请注意!该操作会覆盖已有供应价格)</span
>
</div>
</div>
<CustomizeTable
ref="tableRef"
border
style="margin-bottom: 20px"
v-model="pricetableData"
:config="priceTableConfig"
align="center"
height="500px"
@getCheckboxRecords="selectPirce"
></CustomizeTable>
<template #footer>
<div style="text-align: center">
<ElButton @click="cancelPiceFn">取消</ElButton>
<ElButton type="primary" @click="savePiceFn">保存</ElButton>
</div>
</template>
</Dialog>
</template>
<script setup lang="tsx">
defineOptions({
name: 'DeclarationRule',
})
import {
getSupplierListApi,
deleteSupplierApi,
getSupplierDetailApi,
getPropertyByCateIdApi,
getProductInfoBySpuApi,
getBaseCurrencyInfoApi,
addSupplierApi,
updateSupplierApi,
} from '@/api/supplier/supplierManagement.ts'
import Dialog from './components/dialog.tsx'
import CustomizeForm from '@/components/CustomizeForm.tsx'
import CustomizeTable from '@/components/VxeTable.tsx'
import { IFormConfig } from '@/components/CustomizeForm.tsx'
import usePageList from '@/utils/hooks/usePageList'
import { useValue } from '@/utils/hooks/useValue'
import { showConfirm } from '@/utils/ui'
import { debounce, cloneDeep } from 'lodash-es'
import { Edit, CirclePlus } from '@element-plus/icons-vue'
import { TableColumn } from '@/components/VxeTable'
import {
IgoodsType,
IsupplierType,
IcurrencyCode,
IcolorType,
IsizeType,
IPropertyResponseItem,
Iprice,
} from './types/index.ts'
const [editForm, resetEditForm] = useValue<IsupplierType>({})
const {
loading,
currentPage,
pageSize,
total,
data: tableData,
refresh: search,
onCurrentPageChange: handleCurrentChange,
onPageSizeChange: handleSizeChange,
} = usePageList({
query: (page, pageSize) =>
getSupplierListApi({
pageSize: pageSize,
currentPage: page,
}).then(({ data }) => {
console.log(130, data)
return data
}),
})
const dialogVisible = ref(false)
const editLoading = ref(false)
const goodsTableData = ref([])
const supplierTableData = ref<IgoodsType[]>([])
const priceDialogVisible = ref(false)
const pricetableData = ref<Iprice[]>([])
const editFormRef = ref<InstanceType<typeof CustomizeForm> | null>(null)
const selection = ref<Iprice[]>([])
interface IOption {
[key: string]: unknown
}
//表单
const formConfig = computed<IFormConfig[]>(() => [
{
prop: 'supplierName',
type: 'input',
label: '供应商名称',
attrs: {
placeholder: '请输入供应商名称',
width: '33.333%',
},
rules: [
{
required: true,
message: '请输入供应商名称',
},
],
},
{
prop: 'contacts',
type: 'input',
label: '联系人',
attrs: {
placeholder: '请输入联系人',
width: '33.333%',
},
rules: [
{
required: true,
message: '请输入联系人',
},
],
},
{
prop: 'contactsNumber',
type: 'input',
label: '联系电话',
attrs: {
placeholder: '请输入联系电话',
width: '33.333%',
},
rules: [
{
required: true,
message: '请输入联系电话',
},
],
},
{
prop: 'address',
type: 'input',
label: '地址',
attrs: {
placeholder: '请输入地址',
},
rules: [
{
required: true,
message: '请输入地址',
},
],
},
{
prop: 'remark',
type: 'input',
label: '备注',
attrs: {
placeholder: '请输入备注',
type: 'textarea',
rows: 4,
},
},
{
prop: 'table',
title: true,
type: '',
label: '',
render: () => {
return (
<div style={{ width: '100%' }}>
<div class="flex" style={{ justifyContent: 'space-between' }}>
<div
style={{
display: 'flex',
flexWrap: 'nowrap',
alignItems: 'center',
gap: '20px',
marginBottom: '20px',
}}
>
<div>管理供应商品</div>
<el-input
v-model={searchSupplierGoods.value}
clearable
placeholder="九猫商品SPU"
style={{ width: '200px' }}
></el-input>
<el-popover
placement="bottom"
trigger="click"
width="750"
disabled={isShowSearch.value}
v-slots={{
reference: () => (
<el-button
type="primary"
onclick={() => searchSupplyGoodsFn()}
>
查询
</el-button>
),
}}
>
<CustomizeTable
v-loading={goodsLoading.value}
isShowCheckBox={false}
modelValue={goodsTableData.value}
config={searchTableConfig.value}
{...{ height: '400px', align: 'center', border: true }}
style={{ marginBottom: '20px' }}
></CustomizeTable>
</el-popover>
</div>
<div>
<el-button type="danger" onclick={() => deleteSupplyGoodsFn()}>
删除
</el-button>
</div>
</div>
<CustomizeTable
modelValue={supplierTableData.value}
config={formTableConfig.value}
{...{ height: '400px', align: 'center', border: true }}
style={{ marginBottom: '20px' }}
onGetCheckboxRecords={(v) => getSupplierItem(v)}
></CustomizeTable>
</div>
)
},
},
])
//列表表格
const tableConfig = ref<TableColumn[]>([
{
prop: 'supplierName',
label: '供应商名称',
},
{
prop: 'contacts',
label: '联系人',
},
{
prop: 'contactsNumber',
label: '联系电话',
},
{
prop: 'address',
label: '地址',
},
{
prop: 'remark',
label: '备注',
},
{
prop: 'opeare',
label: '操作',
attrs: {
align: 'center',
},
render: {
default: ({ row }: { row: IsupplierType }) => (
<div>
<el-icon
size="24"
title="编辑"
color="#EF6C00"
style="cursor: pointer; vertical-align: middle"
onclick={() => editSupplier(row.id as string)}
>
<Edit />
</el-icon>
</div>
),
},
},
])
//表单表格
const formTableConfig = ref<TableColumn[]>([
{
prop: 'productName',
label: '商品名称',
},
{
prop: 'productNo',
label: '款号',
},
{
prop: 'productSpu',
label: '商品SPU',
},
{
prop: 'productImage',
label: '商品图片',
render: {
default: ({ row }: { row: IgoodsType }) => (
<div>
<el-image style="width: 50px; height: 50px" src={row.productImage} />
</div>
),
},
},
{
prop: 'currencyCode',
label: '币种',
},
{
prop: 'supplyPrice',
label: '供应价格',
render: {
default: ({ row }: { row: IgoodsType }) => (
<div>{getSupplyPrice(row)}</div>
),
},
},
{
prop: 'opeare',
label: '操作',
attrs: {
align: 'center',
},
render: {
default: ({ row }: { row: IgoodsType }) => (
<el-button type="primary" onclick={() => addPice(row)} size="small">
管理供应价格
</el-button>
),
},
},
])
//查询商品表格
const searchTableConfig = ref<TableColumn[]>([
{
prop: 'name',
label: '商品名称',
},
{
prop: 'productNo',
label: '款号',
},
{
prop: 'sku',
label: '商品SPU',
},
{
prop: 'imgUrl',
label: '商品图片',
render: {
default: ({ row }: { row: IgoodsType }) => (
<div>
<el-image style="width: 50px; height: 50px" src={row.imgUrl} />
</div>
),
},
},
{
prop: 'opeare',
label: '操作',
attrs: {
align: 'center',
width: '65px',
},
render: {
default: ({ row }: { row: IgoodsType }) => (
<div>
<el-icon
size="24"
title="新增"
color="#EF6C00"
style="cursor: pointer; vertical-align: middle"
onclick={() => addGoods(row)}
>
<CirclePlus />
</el-icon>
</div>
),
},
},
])
//管理供应价格表格
const priceTableConfig = ref<TableColumn[]>([
{
prop: 'productItemImage',
label: 'SKU图片',
render: {
default: ({ row }: { row: Iprice }) => (
<div>
<el-image
style="width: 50px; height: 50px"
src={row.productItemImage}
/>
</div>
),
},
},
{
prop: 'productItemSku',
label: 'SKU码',
},
{
prop: 'opeare',
label: '供应价格',
attrs: {
align: 'center',
width: '230px',
},
render: {
default: ({ row }: { row: Iprice }) => (
<div>
<el-input
modelValue={row.supplyPrice}
onInput={(v: string | number) => {
v = String(v).replace(/[^\d.]/g, '')
const parts = v.split('.')
if (parts.length > 2) v = parts[0] + '.' + parts.slice(1).join('')
row.supplyPrice = Number(v)
}}
clearable
placeholder="请输入更新价格"
style="width: 200px; margin-right: 20px"
size="small"
></el-input>
</div>
),
},
},
])
const searchSupplierGoods = ref('')
/**
* @description: 取消按钮
*/
function cancelFn() {
dialogVisible.value = false
searchSupplierGoods.value = ''
editFormRef.value?.resetFields()
resetEditForm()
}
/**
* @description: 取消供应价格按钮
*/
function cancelPiceFn() {
priceDialogVisible.value = false
}
//保存供应价格
function savePiceFn() {
if (!currentGoods.value.currencyCode) {
return ElMessage({
message: '请选择结算币种',
type: 'warning',
})
}
const hasEmptyPrice = pricetableData.value.some(
(item) =>
!item.supplyPrice ||
String(item.supplyPrice).trim() === '' ||
Number(item.supplyPrice) <= 0,
)
if (hasEmptyPrice) {
ElMessage.warning('供应价格为必填项,且必须大于0')
return
}
ElMessage({
message: '保存成功',
type: 'success',
})
supplierTableData.value.forEach((el) => {
if (el.id === currentGoods.value.id) {
el.currencyCode = currentGoods.value.currencyCode
el.currencyName = currentGoods.value.currencyName
el.supplierPriceItemList = [...pricetableData.value]
}
})
console.log('supplierTableData', supplierTableData.value)
cancelPiceFn()
}
/**
* @description: 检查数据
*/
async function checkData(): Promise<{
isValid: boolean
postData: IsupplierType
}> {
try {
// 1. 表单验证
await editFormRef.value?.validate()
// 2. 准备数据
const postData = { ...editForm.value }
const hasEmptyPriceItemList = supplierTableData.value?.some(
(product) => !product.supplierPriceItemList,
)
if (hasEmptyPriceItemList) {
ElMessage({
message: '请填写所有供应价格',
type: 'warning',
})
return { isValid: false, postData }
}
// 检查所有供应价格是否已填写
const hasEmptyPrice = supplierTableData.value?.some((product) =>
product.supplierPriceItemList?.some(
(item) => !item.supplyPrice && item.supplyPrice !== 0,
),
)
console.log(656, hasEmptyPrice)
// return
if (hasEmptyPrice) {
ElMessage({
message: '请填写所有供应价格',
type: 'warning',
})
return { isValid: false, postData }
}
if (supplierTableData.value?.length) {
postData.supplierProductInfoList = [...supplierTableData.value].map(
(el) => {
el.createTime && delete el.createTime
el.updateTime && delete el.updateTime
return {
...el,
}
},
)
}
// 所有验证通过
return { isValid: true, postData }
} catch (error) {
console.error('表单验证失败:', error)
// 表单验证失败时,仍然返回数据
const postData = { ...editForm.value }
return { isValid: false, postData }
}
}
/**
* @description: 保存按钮
*/
const saveSupplierFn = debounce(async () => {
const { isValid, postData } = await checkData()
if (isValid) {
try {
if (!postData.id) {
await addSupplierApi({
...postData,
})
} else {
await updateSupplierApi({
...postData,
})
}
ElMessage({
message: postData.id ? '更新成功' : '新增成功',
type: 'success',
})
cancelFn()
search()
} catch (e) {
return
}
}
}, 400)
/**
* @description: 获取选中数据
*/
function handleCheckboxRecords(value: never[]) {
selection.value = value
}
/**
* @description: 多选删除按钮
*/
async function deleteFn() {
if (!selection.value.length) {
return ElMessage({
message: '请选择供应商',
type: 'warning',
})
}
try {
await showConfirm('是否删除供应商', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const ids = {
ids: selection.value.map((item: IOption) => item.id).join(','),
}
await deleteSupplierApi(ids)
ElMessage({
message: '删除成功',
type: 'success',
})
search()
} catch (e) {
search()
// showError(e)
}
}
/**
* @description: 新增供应商
*/
async function showDialog() {
dialogVisible.value = true
supplierTableData.value = []
supplierSeletctions.value = []
}
/**
* @description: 编辑供应商
*/
async function editSupplier(id: string | number) {
dialogVisible.value = true
editLoading.value = true
supplierTableData.value = []
supplierSeletctions.value = []
try {
const { data } = await getSupplierDetailApi(id)
editForm.value = data
supplierTableData.value = cloneDeep(
editForm.value.supplierProductInfoList || [],
).map((product) => {
const customMap = new Map(
product.customProductInfo?.customProductItemList?.map((item) => [
item.id,
item,
]) || [],
)
product.supplierPriceItemList?.forEach((priceItem) => {
const customItem = customMap.get(priceItem.productItemId)
if (customItem) {
priceItem.propertyCode1 = customItem.propertyCode1
priceItem.propertyCode2 = customItem.propertyCode2
}
})
return product
})
console.log(728, editForm.value)
} catch (error) {
console.log(error)
} finally {
editLoading.value = false
}
}
//查询spu
const goodsLoading = ref(false)
const isShowSearch = ref(false)
const supplierSeletctions = ref<IgoodsType[]>([])
async function searchSupplyGoodsFn() {
if (!searchSupplierGoods.value) {
ElMessage.warning('请输入SPU')
isShowSearch.value = true
return
} else {
isShowSearch.value = false
goodsLoading.value = true
try {
const { data } = await getProductInfoBySpuApi(searchSupplierGoods.value)
goodsTableData.value = [data]
console.log(604, data)
} catch (error) {
console.log(error)
} finally {
goodsLoading.value = false
}
}
}
//删除商品
function deleteSupplyGoodsFn() {
if (!supplierSeletctions.value.length) {
return ElMessage.warning(`请勾选商品`)
}
const deleteIds = supplierSeletctions.value.map((el) => el.productSpu)
supplierTableData.value = supplierTableData.value.filter(
(el) => !deleteIds.includes(el.productSpu),
)
}
function getSupplierItem(v: IgoodsType[]) {
supplierSeletctions.value = v
}
const priceLoading = ref(false)
const currencyOptions = ref<IcurrencyCode[]>([])
const colorList = ref<IcolorType[]>([])
const showColorList = ref<IcolorType[]>([])
const sizeList = ref<IsizeType[]>([])
const showSizeList = ref<IsizeType[]>([])
const currentGoods = ref()
//管理供应价格按钮
async function addPice(product: IgoodsType) {
supplierPirce.value = ''
colorIndex.value = null
sizeIndex.value = null
priceDialogVisible.value = true
priceLoading.value = true
currentGoods.value = cloneDeep(product)
console.log('product', currentGoods.value)
const tempArr = product.supplierPriceItemList || product.customProductItemList
pricetableData.value = cloneDeep(
tempArr?.map((el) => {
return {
...el,
productItemImage: el.productItemImage || el.image,
productItemSku: el.productItemSku || el.sku,
productItemId: el.id || '',
}
}) || [],
)
try {
const [currencyResponse, propertyResponse] = await Promise.all([
getBaseCurrencyInfoApi(),
getPropertyByCateIdApi(
(product.categoryId || product.customProductInfo?.categoryId) as string,
),
])
currencyOptions.value = currencyResponse.data || []
const propertyData: IPropertyResponseItem[] = propertyResponse.data || []
colorList.value = propertyData.find((el) => el.id == 1)?.valueList || []
sizeList.value = propertyData.find((el) => el.id == 2)?.valueList || []
if (!currentGoods.value.propertyList?.length) {
currentGoods.value.propertyList = product.customProductInfo?.propertyList
}
// 创建属性映射
const propertyMap = createPropertyMap(currentGoods.value.propertyList || [])
console.log(758, propertyMap)
// 过滤显示的颜色和尺寸
showColorList.value = filterPropertyList(
colorList.value,
propertyMap.get(1) || [],
)
showSizeList.value = filterPropertyList(
sizeList.value,
propertyMap.get(2) || [],
)
} catch (error) {
console.log(error)
} finally {
priceLoading.value = false
}
}
// 辅助函数:创建属性映射
function createPropertyMap(propertyList: any[]): Map<number, number[]> {
const map = new Map<number, number[]>()
propertyList.forEach((item) => {
if (item.propertyId && item.valueId) {
if (!map.has(item.propertyId)) {
map.set(item.propertyId, [])
}
map.get(item.propertyId)!.push(item.valueId)
}
})
return map
}
// 辅助函数:过滤属性列表
function filterPropertyList<T extends { id?: number | string }>(
sourceList: T[],
targetIds: number[],
): T[] {
return sourceList.filter((item) => {
const itemId = typeof item.id === 'string' ? parseInt(item.id) : item.id
return itemId !== undefined && targetIds.includes(itemId as number)
})
}
//新增商品
function addGoods(data: IgoodsType) {
data.productImage = data.productImage || data.imgUrl || ''
data.productSpu = data.productSpu || data.sku || ''
data.productName = data.productName || data.name || ''
data.currencyCode = ''
data.currencyName = ''
data.productId = data.id || undefined
const idSet = new Set(supplierTableData.value.map((item) => item.productSpu))
if (idSet.has(data.productSpu)) {
ElMessage.warning(`商品已存在,请勿重复添加`)
return
}
supplierTableData.value.push(data)
ElMessage.success('商品添加成功')
}
const selectPirceList = ref<Iprice[]>([])
const supplierPirce = ref('')
function selectPirce(value: never[]) {
selectPirceList.value = value
}
//批量供应价格
function updatePrices() {
if (!supplierPirce.value) {
ElMessage.warning(`请输入供应价格`)
return
}
if (selectPirceList.value.length) {
selectPirceList.value.forEach((el) => {
el.supplyPrice = supplierPirce.value
})
} else {
ElMessage.warning(`请先勾选需要修改价格的商品`)
}
}
//获取颜色和尺寸
const colorIndex = ref()
const sizeIndex = ref()
const tableRef = ref()
const optionSelection = (row: IcolorType, type: 'color' | 'size') => {
updateSelectionIndex(row, type)
updateTableSelection()
}
// 更新选中索引的函数
const updateSelectionIndex = (row: IcolorType, type: 'color' | 'size') => {
if (type === 'color') {
colorIndex.value = colorIndex.value === row.code ? null : row.code
} else if (type === 'size') {
sizeIndex.value = sizeIndex.value === row.code ? null : row.code
}
}
// 更新表格选中状态的函数
const updateTableSelection = () => {
pricetableData.value.forEach((item) => {
tableRef.value.setCheckboxRow(item, false)
})
const rowsToSelect = getRowsToSelect()
rowsToSelect.forEach((item) => {
tableRef.value.setCheckboxRow(item, true)
})
console.log('rowsToSelect', rowsToSelect)
selection.value = [...(rowsToSelect || [])]
}
// 获取应该选中的行
const getRowsToSelect = () => {
return pricetableData.value.filter((item) => {
if (colorIndex.value && sizeIndex.value) {
return (
item.propertyCode1 === colorIndex.value &&
item.propertyCode2 === sizeIndex.value
)
}
if (colorIndex.value) {
return item.propertyCode1 === colorIndex.value
}
if (sizeIndex.value) {
return item.propertyCode2 === sizeIndex.value
}
return false
})
}
//获取供应价格区间
function getSupplyPrice(row: IgoodsType) {
let range = ''
const arr = row.supplierPriceItemList
if (arr?.length) {
const prices = arr
.map((item) => {
// 尝试转换为数字
const price = parseFloat(item.supplyPrice as string)
return isNaN(price) ? null : price
})
.filter((price) => price !== null) as number[]
if (prices.length === 0) {
range = '' // 或者根据需求返回默认值
}
// 计算最小值和最大值
const minPrice = Math.min(...prices)
const maxPrice = Math.max(...prices)
// 判断是否需要范围格式
if (minPrice === maxPrice) {
// 所有价格相同或只有一个价格
range = minPrice.toString()
} else {
// 价格有范围
range = `${minPrice}-${maxPrice}`
}
} else {
range = ''
}
row.supplyPriceRange = range as string
return range
}
</script>
<style lang="scss" scoped>
.header-filter-form {
margin-bottom: 20px;
:deep(.el-form-item) {
margin-right: 14px;
margin-bottom: 10px;
}
}
.user-operate-btn {
margin-bottom: 10px;
}
.dialog-footer {
text-align: center;
}
.tabBox {
cursor: pointer;
}
.active {
border: 2px solid rgb(0, 140, 255);
}
</style>
export interface IListPage {
pageSize: number | string
currentPage: number | string
}
export interface IgoodsType {
imgUrl?: string
productImage?: string
sku?: string
productSpu?: string
productNo?: string
id?: string
productId?: string
categoryId?: string
currencyCode?: string
currencyName?: string
name?: string
productName?: string
createTime?: string
updateTime?: string
customProductItemList?: Iprice[]
supplierPriceItemList?: Iprice[]
customProductInfo?: IgoodsType
propertyList?: []
supplyPriceRange?: string
}
export interface IsupplierType {
supplierName?: string
contacts?: string
contactsNumber?: string
address?: string
id?: string
supplierProductInfoList?: IgoodsType[]
remark?: string
}
export interface IcurrencyCode {
currencyName?: string
currencyCode?: string
id?: string
}
export interface IcolorType {
bgColor?: string
code?: string
fontColor?: string
cnname?: string
enname?: string
id?: string | number
}
export interface IsizeType {
code?: string
cnname?: string
id?: string | number
}
export interface IPropertyResponseItem {
id: string | number
valueList: IcolorType[] | IsizeType[]
}
export interface Iprice {
productItemSku?: string
productItemImage?: string
sku?: string
id?: string
image?: string
supplyPrice?: string | number
propertyCode1?: string
propertyCode2?: string
productItemId?: string
}
export interface IListPage {
pageSize: number | string
currentPage: number | string
}
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