Commit 07c79315 by wuqian

商品注册主流程初版

parent 1f0e8ec6
...@@ -15,6 +15,7 @@ declare module 'vue' { ...@@ -15,6 +15,7 @@ declare module 'vue' {
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']
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']
ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
...@@ -32,6 +33,7 @@ declare module 'vue' { ...@@ -32,6 +33,7 @@ declare module 'vue' {
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'] ElImage: typeof import('element-plus/es')['ElImage']
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink'] ElLink: typeof import('element-plus/es')['ElLink']
...@@ -49,12 +51,12 @@ declare module 'vue' { ...@@ -49,12 +51,12 @@ declare module 'vue' {
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']
ElTableV2: typeof import('element-plus/es')['ElTableV2']
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline'] ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree'] ElTree: typeof import('element-plus/es')['ElTree']
ElUpload: typeof import('element-plus/es')['ElUpload'] ElUpload: typeof import('element-plus/es')['ElUpload']
...@@ -70,6 +72,7 @@ declare module 'vue' { ...@@ -70,6 +72,7 @@ declare module 'vue' {
Select: typeof import('./src/components/Form/Select.vue')['default'] Select: typeof import('./src/components/Form/Select.vue')['default']
ShipmentOrderDetail: typeof import('./src/components/ShipmentOrderDetail.vue')['default'] ShipmentOrderDetail: typeof import('./src/components/ShipmentOrderDetail.vue')['default']
SideBar: typeof import('./src/components/SideBar.vue')['default'] SideBar: typeof import('./src/components/SideBar.vue')['default']
SkuAttibute: typeof import('./src/components/skuAttibute.vue')['default']
SplitDiv: typeof import('./src/components/splitDiv/splitDiv.vue')['default'] SplitDiv: typeof import('./src/components/splitDiv/splitDiv.vue')['default']
'Switch ': typeof import('./src/components/Form/Switch .vue')['default'] 'Switch ': typeof import('./src/components/Form/Switch .vue')['default']
TableView: typeof import('./src/components/TableView.vue')['default'] TableView: typeof import('./src/components/TableView.vue')['default']
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
"vue-dompurify-html": "^5.1.0", "vue-dompurify-html": "^5.1.0",
"vue-router": "^4.3.0", "vue-router": "^4.3.0",
"vue-tsc": "^2.1.10", "vue-tsc": "^2.1.10",
"vuedraggable": "^4.1.0",
"vxe-table": "^4.13.31", "vxe-table": "^4.13.31",
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
...@@ -5840,6 +5841,12 @@ ...@@ -5840,6 +5841,12 @@
"node": ">=12.17.0" "node": ">=12.17.0"
} }
}, },
"node_modules/sortablejs": {
"version": "1.14.0",
"resolved": "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.14.0.tgz",
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==",
"license": "MIT"
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
...@@ -6637,6 +6644,18 @@ ...@@ -6637,6 +6644,18 @@
"typescript": ">=5.0.0" "typescript": ">=5.0.0"
} }
}, },
"node_modules/vuedraggable": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz",
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
"license": "MIT",
"dependencies": {
"sortablejs": "1.14.0"
},
"peerDependencies": {
"vue": "^3.0.1"
}
},
"node_modules/vxe-pc-ui": { "node_modules/vxe-pc-ui": {
"version": "4.6.12", "version": "4.6.12",
"resolved": "https://registry.npmmirror.com/vxe-pc-ui/-/vxe-pc-ui-4.6.12.tgz", "resolved": "https://registry.npmmirror.com/vxe-pc-ui/-/vxe-pc-ui-4.6.12.tgz",
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
"vue-dompurify-html": "^5.1.0", "vue-dompurify-html": "^5.1.0",
"vue-router": "^4.3.0", "vue-router": "^4.3.0",
"vue-tsc": "^2.1.10", "vue-tsc": "^2.1.10",
"vuedraggable": "^4.1.0",
"vxe-table": "^4.13.31", "vxe-table": "^4.13.31",
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
......
...@@ -7,12 +7,26 @@ ...@@ -7,12 +7,26 @@
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import zhCn from 'element-plus/es/locale/lang/zh-cn.mjs' import zhCn from 'element-plus/es/locale/lang/zh-cn.mjs'
import en from 'element-plus/es/locale/lang/en.mjs' import en from 'element-plus/es/locale/lang/en.mjs'
import { setKeyCode } from '@/store/product'
const language = ref('zh-cn') const language = ref('zh-cn')
const locale = computed(() => (language.value === 'zh-cn' ? zhCn : en)) const locale = computed(() => (language.value === 'zh-cn' ? zhCn : en))
// 在组件挂载时清空 localStorage // 在组件挂载时清空 localStorage
onMounted(() => { onMounted(() => {
localStorage.removeItem('socket_connect') localStorage.removeItem('socket_connect')
document.addEventListener('keydown', (e: any) => {
setKeyCode(e.key)
})
document.addEventListener('keyup', () => {
setKeyCode(null)
})
})
onUnmounted(() => {
document.removeEventListener('keydown', (e) => {
setKeyCode(e.key)
})
document.removeEventListener('keyup', () => {
setKeyCode(null)
})
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
......
...@@ -3,6 +3,7 @@ import axios from './axios' ...@@ -3,6 +3,7 @@ import axios from './axios'
import { LogisticsData } from '@/types/api/order' import { LogisticsData } from '@/types/api/order'
import { LogisticBill } from '@/types/api/podMakeOrder' import { LogisticBill } from '@/types/api/podMakeOrder'
import { UploadResponse } from '@/types/api/product'
import { userData } from '@/types/api/user' import { userData } from '@/types/api/user'
import { VersionImageList } from '@/types/api/typesetting' import { VersionImageList } from '@/types/api/typesetting'
import { SupplierItem, WarehouseListData } from '@/types' import { SupplierItem, WarehouseListData } from '@/types'
...@@ -26,7 +27,7 @@ export function getUserListApi() { ...@@ -26,7 +27,7 @@ export function getUserListApi() {
// 上传图片文件 // 上传图片文件
export function uploadImageApi(data: FormData) { export function uploadImageApi(data: FormData) {
return axios.post<never, BaseRespData<never> & VersionImageList>( return axios.post<never, BaseRespData<never> & UploadResponse>(
'upload/ossUpload', 'upload/ossUpload',
data, data,
) )
......
import axios from '@/api/axios.ts'
import { BasePaginationData, BaseRespData } from '@/types/api'
import {
IntercategoryTree,
InterSearchCriteriaWithStatus,
InterCardItem,
ProductDetail,
InterCraftItem,
} from '@/types/api/product'
// 状态统计列表
export function getStatusCountApi() {
return axios.get<never, BaseRespData<IntercategoryTree[]>>(
'custom/product/info/getStatusCountList',
)
}
export function getCardListPageApi(
data: InterSearchCriteriaWithStatus,
currentPage: number,
pageSize: number,
) {
return axios.post<never, BasePaginationData<InterCardItem>>(
'custom/product/info/page',
{
...data,
currentPage,
pageSize,
},
)
}
// 类目树
export function getcategoryTreeApi() {
return axios.get<never, BaseRespData<IntercategoryTree[]>>(
'custom/product/info/categoryTree',
)
}
// 工艺列表
export function getcraftListApi() {
return axios.get<never, BaseRespData<InterCraftItem[]>>(
'custom/product/info/craftList',
)
}
// 所有币种
export function getAllCurrencyApi() {
return axios.get<never, BaseRespData<IntercategoryTree[]>>(
'custom/product/info/getAllCurrency',
)
}
// 根据类别获取属性值
export function getByCateIdApi(id: number) {
return axios.get<never, BaseRespData<never>>(
'custom/product/info/getByCateId',
{ params: { id } },
)
}
// 状态转换
export function updateStatusApi(data?: {
ids: string
status?: string | number
remark?: string | null
}) {
return axios.post<never, BaseRespData<never>>(
'custom/product/info/updateStatus',
data,
)
}
// 新增商品
export function createProductApi(from: ProductDetail) {
return axios.post<never, BaseRespData<never>>('custom/product/info/create', {
...from,
})
}
// 编辑商品
export function updateProductApi(from: ProductDetail) {
return axios.post<never, BaseRespData<never>>('custom/product/info/update', {
...from,
})
}
// 操作日志
export function getLogListApi(id: number) {
return axios.get<never, BaseRespData<never>>(
'custom/product/info/getLogList',
{ params: { id } },
)
}
// 查看详情
export function getByIdApi(id: number) {
return axios.get<never, BaseRespData<never>>('custom/product/info/getById', {
params: { id },
})
}
...@@ -141,6 +141,12 @@ const mainImageSrc = computed<string>(() => { ...@@ -141,6 +141,12 @@ const mainImageSrc = computed<string>(() => {
) { ) {
return item[props.imageField] as string return item[props.imageField] as string
} }
if (
props.imageField === 'img_url' &&
typeof item[props.imageField] === 'string'
) {
return item[props.imageField] as string
}
// 默认返回空字符串 // 默认返回空字符串
return '' return ''
}) })
......
...@@ -124,6 +124,20 @@ export default defineComponent({ ...@@ -124,6 +124,20 @@ export default defineComponent({
$table.setCurrentRow(row) $table.setCurrentRow(row)
} }
} }
//滚动到指定行
const scrollToRowEvent = (row: TableRowData, top = true) => {
const $table = tableRef.value
if ($table) {
$table.scrollToRow(row, top ? 'top' : 'bottom')
}
}
//清除所有勾选
const clearCheckboxEvent = () => {
const $table = tableRef.value
if ($table) {
$table.clearCheckboxRow()
}
}
onMounted(() => { onMounted(() => {
getList() getList()
...@@ -137,6 +151,8 @@ export default defineComponent({ ...@@ -137,6 +151,8 @@ export default defineComponent({
getSelectEvent, getSelectEvent,
selectRowEvent, selectRowEvent,
setCheckboxRow, setCheckboxRow,
scrollToRow: scrollToRowEvent,
clearCheckbox: clearCheckboxEvent,
attrs, attrs,
} }
}, },
......
...@@ -5,13 +5,13 @@ ...@@ -5,13 +5,13 @@
style="border-bottom: 1px solid #ccc" style="border-bottom: 1px solid #ccc"
:editor="editorRef" :editor="editorRef"
:default-config="toolbarConfig" :default-config="toolbarConfig"
:mode="mode" :mode="modeType"
/> />
<Editor <Editor
v-model="html" v-model="html"
style="height: 300px; overflow-y: hidden" style="height: 300px; overflow-y: hidden"
:default-config="editorConfig" :default-config="editorConfig"
:mode="mode" :mode="modeType"
@on-created="onCreated" @on-created="onCreated"
/> />
</div> </div>
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
// @ts-ignore // @ts-ignore
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import '@wangeditor/editor/dist/css/style.css' import '@wangeditor/editor/dist/css/style.css'
import { computed, onBeforeUnmount, ref, shallowRef } from 'vue' import { computed, onBeforeUnmount, shallowRef } from 'vue'
import { uploadImg } from '@/utils/file' import { uploadImg } from '@/utils/file'
const props = defineProps({ const props = defineProps({
modelValue: String, modelValue: String,
...@@ -33,6 +33,10 @@ const props = defineProps({ ...@@ -33,6 +33,10 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
modeType: {
type: String,
default: 'default',
},
insertData: { insertData: {
type: Array, type: Array,
default: () => [ default: () => [
...@@ -67,6 +71,7 @@ const toolbarConfig = computed(() => { ...@@ -67,6 +71,7 @@ const toolbarConfig = computed(() => {
}) })
const editorConfig = computed(() => { const editorConfig = computed(() => {
return { return {
readOnly: props.modeType === 'simple',
placeholder: props.placeholder, placeholder: props.placeholder,
MENU_CONF: { MENU_CONF: {
uploadImage: { uploadImage: {
...@@ -89,7 +94,6 @@ const editorConfig = computed(() => { ...@@ -89,7 +94,6 @@ const editorConfig = computed(() => {
}, },
} }
}) })
const mode = ref('default')
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const onCreated = (editor: any) => { const onCreated = (editor: any) => {
console.log(editor) console.log(editor)
......
<template>
<ElDialog
v-model="visible"
title="属性选择"
width="65%"
:close-on-click-modal="false"
>
<div class="dialog-body" style="height: 60vh; overflow: auto">
<div class="header">
<slot name="header" />
</div>
<div v-if="skuOptions?.length > 0">
<div
v-for="(item, index) in skuOptions"
:key="index"
class="option_wrap"
>
<div class="title">
<span>{{ item.cnname }}</span>
<span>({{ item.enname }})</span>
</div>
<div v-if="checkSkuArr(index)">
<el-checkbox-group
v-model="localSkuArr[index].listCode"
class="attribute-checkbox"
>
<el-checkbox
v-for="(item2, index2) in item.valueList || []"
:key="index2"
:label="item2.code"
:disabled="isDisabled(item2.code)"
:style="{
background: item2.bgColor,
color: item2.fontColor,
padding: '0 10px',
}"
@change="(v:boolean) => optionChange(index, index2, v)"
>
{{ item2.isCome }}
{{ item2.cnname + '(' + item2.enname + ')' }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确认</el-button>
</span>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { defineModel, defineEmits, defineExpose, defineProps } from 'vue'
const visible = defineModel<boolean>('visible', { default: false })
import { SkuPropertyValue, SkuProperty, InterSkuArr } from '@/types/api/product'
import { storeToRefs } from 'pinia'
import { useOptionsStore } from '@/store/product'
const store = useOptionsStore()
const { keyCode } = storeToRefs(store)
const emits = defineEmits<{
(event: 'success'): void
(e: 'update:skuArr', payload: { skuArr: InterSkuArr[] }): void
(event: 'close'): void
}>()
const props = defineProps({
skuOptions: {
type: Array as () => SkuProperty[],
required: true,
},
skuArr: {
type: Array as () => InterSkuArr[],
required: true,
},
save: {
type: Function,
required: true,
},
})
const mode = ref('add')
const currentIndex = ref<number>(-1)
const childrenIndex = ref<number>(-1)
const localSkuArr = ref(JSON.parse(JSON.stringify(props.skuArr)))
watch(
() => props.skuArr,
(newVal: InterSkuArr[]) => {
localSkuArr.value = JSON.parse(JSON.stringify(newVal)) || [] //留下一开始回显的值
},
)
const handleSubmit = async () => {
emits('update:skuArr', {
skuArr: JSON.parse(JSON.stringify(localSkuArr.value)),
}) // 只提交本地深拷贝
props.save() //提交属性的同时,表格数据变化
ElMessage.success('提交成功')
visible.value = false
}
const open = async (key = 'add') => {
mode.value = key
}
const checkSkuArr = (index: number) => {
const skuArr = localSkuArr.value
return skuArr && skuArr.length > 0 && skuArr[index]?.listCode !== undefined
}
const optionChange = (i: number, j: number, value: boolean) => {
const list = props.skuOptions
const skuArr = localSkuArr.value
if (keyCode.value === 'Shift' && value) {
if (currentIndex.value === i) {
let min: number, max: number
if (j > childrenIndex.value) {
min = childrenIndex.value
max = j
} else {
min = j
max = childrenIndex.value
}
const codeList = skuArr[i].listCode
for (let index = min; index <= max; index++) {
list[i].valueList[index].selected = value ? 1 : 0
if (!codeList.includes(list[i].valueList[index].code)) {
codeList.push(list[i].valueList[index].code)
}
}
} else {
currentIndex.value = i
childrenIndex.value = j
list[i].valueList[j].selected = value ? 1 : 0
}
} else {
if (value) {
currentIndex.value = i
childrenIndex.value = j
} else {
currentIndex.value = -1
childrenIndex.value = -1
}
list[i].valueList[j].selected = value ? 1 : 0
}
// 保证valueList和listCode一致
skuArr[i].valueList = list[i].valueList.filter((v: SkuPropertyValue) =>
skuArr[i].listCode.includes(v.code),
)
}
// const handleClose = () => {
// emit('close')
// }
// 判断某属性是否在父组件skuArr中已被选中(不区分index)
const isDisabled = (code: string) => {
return (props.skuArr ?? []).some((item: InterSkuArr) =>
item.listCode.includes(code),
)
}
defineExpose({
open,
})
</script>
<style lang="scss" scoped>
:deep(.el-checkbox__label) {
font-size: 12px;
width: 85px;
overflow: hidden;
white-space: nowrap;
vertical-align: middle;
text-overflow: ellipsis;
}
:deep(.el-checkbox) {
padding-top: 3px;
padding-bottom: 3px;
margin-bottom: 10px !important;
}
.title {
padding-left: 5px;
font-size: 16px;
line-height: 36px;
font-weight: bold;
border-bottom: 1px solid #efefef;
margin-bottom: 10px;
}
.attribute-checkbox {
padding-left: 5px;
}
.option_wrap + .option_wrap {
border-top: 1px solid #bbb;
}
.dialog-footer {
margin-top: 10px;
width: 100%;
display: flex;
justify-content: center;
}
</style>
...@@ -33,6 +33,7 @@ import issueDoc from '@/views/warehouse/issueDoc.vue' ...@@ -33,6 +33,7 @@ import issueDoc from '@/views/warehouse/issueDoc.vue'
import ExternalAuthorisationPage from '@/views/system/externalAuthorisationPage.vue' import ExternalAuthorisationPage from '@/views/system/externalAuthorisationPage.vue'
import CustomersPage from '@/views/system/CustomersPage.vue' import CustomersPage from '@/views/system/CustomersPage.vue'
import stockingPlan from '@/views/warehouse/stockingPlan.vue' import stockingPlan from '@/views/warehouse/stockingPlan.vue'
import productManagement from '@/views/product/productManagement.vue'
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
routes: [ routes: [
...@@ -303,6 +304,13 @@ const router = createRouter({ ...@@ -303,6 +304,13 @@ const router = createRouter({
component: stockingPlan, component: stockingPlan,
}, },
{ {
path: '/product/product-management',
meta: {
title: '商品管理',
},
component: productManagement,
},
{
path: '/warehouse/stocking-application', path: '/warehouse/stocking-application',
meta: { meta: {
title: '入库申请单', title: '入库申请单',
......
...@@ -103,6 +103,11 @@ const menu: MenuItem[] = [ ...@@ -103,6 +103,11 @@ const menu: MenuItem[] = [
id: 124, id: 124,
label: '备货计划', label: '备货计划',
}, },
{
index: '/product/product-management',
id: 125,
label: '商品管理',
},
], ],
}, },
......
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import {
getcategoryTreeApi,
getcraftListApi,
getAllCurrencyApi,
} from '@/api/product'
import { warehouseInfoGetAll } from '@/api/warehouse'
interface ApiResponse {
data: unknown
code?: number
message?: string
}
type OptionItem = Record<string, unknown>
export const getOptionsApi = (
name: string,
params?: Record<string, unknown>,
): Promise<ApiResponse> => {
switch (name) {
case 'category':
return getcategoryTreeApi() as Promise<ApiResponse>
case 'craft':
return getcraftListApi() as Promise<ApiResponse>
case 'currency':
return getAllCurrencyApi() as Promise<ApiResponse>
case 'warehouse':
return warehouseInfoGetAll() as Promise<ApiResponse>
default:
throw new Error(`Unknown option name: ${name}:params${params}`)
}
}
// 模块级共享状态
let _keyCode: string | null = null
export const useOptionsStore = defineStore('options', () => {
// 使用 computed 与模块级 _keyCode 保持同步
const keyCode = computed({
get: () => _keyCode,
set: (v: string | null) => {
_keyCode = v
},
})
const dataMap = ref<Partial<Record<string, OptionItem[]>>>({})
const extractList = (response: ApiResponse): OptionItem[] => {
const { data } = response
if (Array.isArray(data)) {
return data as OptionItem[]
}
if (
data &&
typeof data === 'object' &&
'data' in data &&
Array.isArray((data as { data: unknown }).data)
) {
return (data as { data: OptionItem[] }).data
}
console.warn('Unexpected response structure:', response)
return []
}
const fetchOption = async (
name: string,
params?: Record<string, unknown>,
): Promise<OptionItem[]> => {
if (dataMap.value[name] && name !== 'canlogistics') {
return dataMap.value[name]!
}
try {
const response = await getOptionsApi(name, params)
const list = extractList(response)
dataMap.value[name] = list
return list
} catch (error) {
console.error(`Failed to fetch ${name}:`, error)
throw error
}
}
const getOptions = async (
names: string | string[],
params?: Record<string, unknown>,
): Promise<Record<string, OptionItem[]>> => {
const nameArray = Array.isArray(names) ? names : [names]
const results = await Promise.all(
nameArray.map(async (name) => {
try {
const list = await fetchOption(
name,
name === 'canlogistics' ? params : undefined,
)
return { name, list }
} catch {
return { name, list: [] }
}
}),
)
return Object.fromEntries(results.map(({ name, list }) => [name, list]))
}
const clearCache = (name?: string) => {
if (name) {
delete dataMap.value[name]
} else {
dataMap.value = {}
}
}
const setKeyCode = (code: string | null) => {
keyCode.value = code
}
return {
keyCode,
dataMap,
getOptions,
clearCache,
setKeyCode,
}
})
// 导出供直接导入使用(与 store 内部共享 _keyCode)
export const keyCode = {
get value() {
return _keyCode
},
set value(v: string | null) {
_keyCode = v
},
}
export const setKeyCode = (code: string | null) => {
_keyCode = code
}
export interface IntercategoryTree {
count: number
remark: string
status: number | string
children: IntercategoryTree[] | null
}
export interface InterSearchCriteria {
category_id: number | null
diyUserId: number | null
name: string
print_type: number | null
product_type: string
sku: string
}
// 需要 status 的场景
export interface InterSearchCriteriaWithStatus extends InterSearchCriteria {
status: string
}
export interface InterCardItem {
colorImageList: string[]
remark: string
cnRemark: string
id: number
sku: string
product_no: string
name: string
namespace: string | null
affiliated_factory: string | null
third_sku: string | null
title: string
color_images: string
img_url: string
category_id: number
weight: number
purchasing_min: number | null
factory_price: number
sales_price: number
sales_price_max: number
status: number | string | null
property1_cate_id: number
property2_cate_id: number
property3_cate_id?: number | null
property1_enname: string
property2_enname: string
property3_enname?: string | null
material: string
print_type: number
origin_code: string
origin_name_cn: string
origin_name_en: string
currency_code: string
currency_name: string
product_type: string
factory_id: number | null
factory_code: string | null
processing: boolean
create_time: string
update_time: string
sort: number | null
diy_id: number
diy_sku: string
}
/** 图片信息 */
export interface ImageInfo {
id: number | null
product_id?: number | null
image_url: string
sort: number
type?: number
create_time?: string
}
/** 产品备注信息 */
export interface ProductRemark {
id: number
product_id: number
remark: string // HTML格式
create_time?: string
}
/** SKU属性值 */
export interface SkuPropertyValue {
id: number
cateCode: string
cateName: string
code: string
cnname: string
enname: string
fontColor: string
bgColor: string
battery: boolean
liquid: boolean
knife: boolean
selected: number
sort: number
enable: boolean
publicData: boolean
isCome?: number | null
}
/** SKU属性定义 */
export interface SkuProperty {
id: number
cnname: string
enname: string
sort: number
skuProperty: boolean
multi: boolean
enable: boolean
categoryInfoId: number | null
propertyValueIds: number[] | null
valueList: SkuPropertyValue[]
}
// 选中的sku
export interface InterSkuArr {
id: number
name: string
enname: string
valueList: SkuPropertyValue[]
listCode: string[]
selected?: number
}
/** 产品SKU */
export interface ProductSku {
id: number
product_id: number
sku: string
sku_name: string
product_no: string
image: string
// image_ary: string // JSON字符串
property_cate_id1: number
property_cate_id2: number
property_cate_id3: number | null
property1_id: number
property2_id: number
property3_id: number | null
property_code1: string
property_code2: string
property_code3: string | null
option_enname1: string
option_enname2: string
option_enname3: string | null
custom_value1: string
custom_value2: string
custom_value3: string | null
factory_price: number
sales_price: number
sku_weight: number | null
reg_count: number | null
print_type?: number | null
size_type?: number
sort: number
create_time?: string
_X_ROW_KEY?: string | null
}
/** 属性关联 */
export interface PropertyRelation {
id: number
info_id: number
property_id: number
value_id: number
sku_property: {
type: 'Buffer'
data: number[]
}
}
// 编辑接口
export interface ProductDetail {
// 图片相关
colorImageList?: string[]
color_images: string
img_url: string
imageList: ImageInfo[]
sizeList: ImageInfo[]
// 描述备注
// remark: string // HTML格式
cnRemark: string // HTML格式
productRemark?: ProductRemark
productCnRemark?: ProductRemark
// 基础信息
id: number | null
sku?: string
product_no: string
name: string
title: string
userMark: string | null
// 分类属性
category_id: number | null
property1_cate_id: number | null
property2_cate_id: number | null
property3_cate_id?: number | null
property1_enname: string
property2_enname: string
property3_enname?: string | null
// 价格重量
weight: number | null
factory_price: number | null
sales_price?: number | null
// sales_price_max?: number
// purchasing_min?: number | null
// 状态类型
print_type: number | null
product_type: 'platform' | 'customer' | string
processing: boolean
// 材质产地
material: string
// origin_code: string
// origin_name_cn: string
// origin_name_en: string
// 货币
currency_code: string
currency_name: string
// 工厂
factory_id?: number | null
factory_code?: string | null
affiliated_factory?: string | null
// DIY相关
diy_id?: number
diy_sku?: string
diyUserIds?: number[]
// 其他
namespace: string | null
third_sku?: string | null
sort?: number | null
create_time?: string
update_time?: string
// 工艺
craftIds: string[]
// 关联数据
productList: ProductSku[]
properties?: PropertyRelation[]
skuProperties: SkuProperty[]
factoryIds?: number[] | null
// warehouseIds: number[]
}
export interface InterCategoryNode {
id: number
name: string
enname: string
pid: number
pids: string
deep: number
sort: number
enpath: string
cnpath: string
leaf: boolean
createTime: string
publicData?: boolean
children?: InterCategoryNode[]
}
export interface InterCountryType {
id: number
nameCn: string
nameEn: string
countryCode: string
}
export interface InterCustomerItem {
id: number
name: string
sku?: string
user_mark?: string
}
export interface InterCraftItem {
id: string
craft_name: string
craft_code: string
craft_type: string
}
export interface CurrencyType {
id: number
currencyName: string
currencyCode: string
}
export interface ProcessTypeData {
label: string
value: string
}
export interface InterProductType {
name: string
code: string
}
export interface UploadResponse {
code: number
requestId: string
filePath: string
message: string
}
export interface TableRowData {
[key: string]: unknown
}
export interface InterReceiptItem {
inNo: string
warehouseId: string
warehouseName: string
remark: string
factoryCode: string
factoryId: number | null
productList: unknown[]
}
.pop-p {
position: relative;
min-height: 24px;
margin-top: 3px;
margin-bottom: 10px;
font-size: 14px;
line-height: 28px;
color: #333;
font-weight: bold;
text-align: center;
span {
font-size: 12px;
color: #666;
}
.pop-p-but {
position: absolute;
display: inline-block;
right: 0;
top: 0;
height: 20px;
}
}
.variants {
padding: 5px 10px;
.title {
color: #303133;
font-size: 16px;
font-weight: 600;
line-height: 36px;
padding-left: 10px;
border-bottom: 1px solid #efefef;
margin-bottom: 10px;
}
li {
display: inline-block;
position: relative;
width: 100px;
padding: 0 6px;
margin-left: 6px;
box-sizing: border-box;
border: 1px solid #dcdfe6;
background: #fff;
border-radius: 4px;
overflow: hidden;
white-space: nowrap;
font-weight: 500;
font-size: 12px;
line-height: 24px;
color: #606266;
cursor: pointer;
text-overflow: ellipsis;
padding-right: 10px;
}
li.active {
border: 1px solid blue;
}
li i {
position: absolute;
top: 7px;
right: 5px;
}
}
.variants + .variants {
border-top: 1px solid #bbb;
}
.form-footer {
margin-top: 10px;
width: 100%;
display: flex;
justify-content: center;
}
\ No newline at end of file
<template>
<ElDialog
v-model="visible"
title="生成入库单"
width="70%"
:close-on-click-modal="false"
>
<div class="dialog-form">
<ElForm
ref="editFormRef"
:model="editForm"
:rules="rules"
inline
label-width="90px"
>
<ElFormItem label="入库单号" prop="account">
<ElInput v-model.trim="editForm.inNo" clearable disabled />
</ElFormItem>
<ElFormItem label="工厂:" prop="factoryCode">
<span>{{ editForm.factoryCode }}</span>
</ElFormItem>
<ElFormItem label="仓库" prop="warehouseId" required>
<ElSelect
v-model="editForm.warehouseId"
clearable
:disabled="formId"
placeholder="请选择仓库"
style="width: 160px"
@change="handleWarehouseChange(editForm.warehouseId)"
>
<ElOption
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
></ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem label="备注" prop="remark" style="width: 45%">
<ElInput
v-model.trim="editForm.remark"
placeholder="请输入备注"
clearable
/>
</ElFormItem>
</ElForm>
<div
style="
height: 250px;
width: 100%;
padding: 0;
box-sizing: border-box;
border: 1px solid #ddd;
overflow: hidden;
"
>
<CustomizeTable
ref="tableRef"
v-model="editForm.productList"
:config="tableConfig"
highlight-current-row
border="full"
@get-checkbox-records="productSelectionChange"
></CustomizeTable>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="submitProduct">确认</el-button>
</span>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { defineModel, defineEmits, defineProps, defineExpose } from 'vue'
const visible = defineModel<boolean>('visible', { default: false })
const emits = defineEmits(['success'])
import { InterReceiptItem, ProductSku } from '@/types/api/product'
import { warehouseInfo } from '@/api/warehouse'
const props = defineProps({
warehouseList: {
type: Array as () => warehouseInfo[],
required: true,
},
})
const createDefaultForm = (
overrides?: Partial<InterReceiptItem>,
): InterReceiptItem => ({
inNo: '',
warehouseId: '',
warehouseName: '',
remark: '',
factoryCode: '',
factoryId: 0,
productList: [],
...overrides, // 支持部分覆盖
})
const editForm = reactive<InterReceiptItem>(createDefaultForm())
const submitProduct = () => {
visible.value = false
emits('success')
}
const open = (data: ProductSku) => {
visible.value = true
editForm.productList = data
}
import CustomizeTable from '@/components/VxeTable.tsx'
import type { VxeTablePropTypes } from 'vxe-table'
const tableRef = ref<InstanceType<typeof CustomizeTable> | null>(null)
import { TableColumn } from '@/components/VxeTable'
import ImageView from '@/components/ImageView.vue'
const tableConfig = computed<TableColumn[]>(() => {
const baseColumns: TableColumn[] = [
{
prop: 'image',
label: 'SKU图片',
attrs: { align: 'center', width: 80 },
render: {
default: ({ row }: { row: ProductSku }) => {
return h(ImageView, {
src: row.image,
alt: 'SKU图片',
})
},
},
},
{
prop: 'sku_name',
label: '库存SKU',
attrs: { align: 'center' },
},
{
prop: 'sku',
label: '商品名称',
attrs: { align: 'center' },
},
{
prop: 'buyStored',
label: '入库数量',
attrs: { align: 'center', width: 120 },
// render: {
// default: ({ row }: { row: ProductSku }) =>
// h(ElInput, {
// modelValue: row.buyStored,
// 'onUpdate:modelValue': (val: number) => {
// row.buyStored = val
// // 触发 setCostPrice 逻辑
// setCostPrice(row)
// },
// placeholder: '入库数量',
// style: 'width: 120px',
// clearable: true,
// size: 'small',
// }),
// },
},
{
prop: 'currencyName',
label: '币种',
attrs: { align: 'center', width: 80 },
},
{
prop: 'costPrice',
label: '成本价',
attrs: { align: 'center', width: 100 },
},
{
prop: 'totalPrice',
label: '总成本',
attrs: { align: 'center', width: 100 },
},
{
prop: 'locationCode',
label: '库位',
attrs: { align: 'center', width: 120 },
// render: {
// default: ({ row }: { row: ProductSku }) =>
// h(
// ElSelect,
// {
// modelValue: row.locationId,
// 'onUpdate:modelValue': (val: number) => {
// row.locationId = val
// handleLocationChange(val, row)
// },
// clearable: true,
// placeholder: '请输入库位',
// style: 'width: 120px',
// filterable: true,
// },
// () =>
// locationList.value.map((item) =>
// h(ElOption, {
// key: item.locationId,
// label: item.locationCode,
// value: item.locationId,
// }),
// ),
// ),
// },
},
{
prop: 'userMark',
label: '所属客户',
attrs: { align: 'center', width: 100 },
},
// {
// prop: 'remark',
// label: '备注',
// attrs: { align: 'center', width: 140, showOverflowTooltip: true },
// render: {
// default: ({ row }: { row: ProductSku }) =>
// h(ElInput, {
// modelValue: row.remark,
// 'onUpdate:modelValue': (val: string) => (row.remark = val),
// clearable: true,
// size: 'small',
// }),
// },
// },
]
return baseColumns
})
const handleWarehouseChange = (val: number | string | undefined) => {
const found = props.warehouseList.find(
(item: warehouseInfo) => item.id === val,
)
editForm.warehouseName = found ? found.name : ''
}
const receiptTableSelection = ref([])
const productSelectionChange = (v) => {
receiptTableSelection.value = v
}
defineExpose({
open,
})
</script>
<style scoped lang="scss">
.dialog-footer {
margin-top: 10px;
width: 100%;
display: flex;
justify-content: center;
}
</style>
.left {
width: 160px;
:deep(.el-tree-node__content) {
height: 30px;
line-height: 30px;
}
:deep(.el-tree-node__label) {
font-size: 13px;
cursor: pointer;
display: inline-block;
width: 100%;
color: black !important;
padding: 3px 7px;
}
:deep(.el-tree-node__expand-icon) {
display: none;
}
:deep(.is-current) {
.tree-node-label,
.tree-node-count {
background-color: #ecf5ff;
color: #409eff !important;
}
.el-tree-node__children {
.tree-node-label,
.tree-node-count {
background-color: transparent !important;
color: black !important;
}
}
}
}
.tree-node {
display: flex;
color: #333;
font-weight: 500;
}
.right {
flex: 1;
flex-shrink: 0;
background: white;
overflow: hidden;
}
.card-list {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: max-content;
gap: 10px;
height: 100%;
overflow-y: auto;
.card-list-item {
cursor: pointer;
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
.images-position {
display: flex;
height: 30px;
gap: 10px;
.item-image {
width: 30px;
height: 30px;
border: 1px solid #909399;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
b {
margin-right: 5px;
font-size: 15px;
}
}
.grid-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
font-size: 12px;
margin-top: 10px;
.grid-item {
display: flex;
overflow: hidden;
}
.tag-position {
justify-content: flex-end;
}
}
}
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
margin: 10px 0;
:deep(.el-pagination) {
margin: 0 !important;
}
.total {
color: #606266;
font-size: 15px;
}
.pageSize {
line-height: 39px;
color: #606266;
font-size: 15px;
}
}
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