Commit 137e3bf4 by zhuzhequan

Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	src/views/order/podUs/index.vue
parents 3cd17cdc 24140a09
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
"rules": { "rules": {
"vue/require-default-prop": "off", "vue/require-default-prop": "off",
"vue/multi-word-component-names": "off", "vue/multi-word-component-names": "off",
"no-console": "warn" "no-console": "off","no-unused-vars": "off",
}, },
"overrides": [{ "files": "*.vue", "rules": { "no-undef": "off" } }] "overrides": [{ "files": "*.vue", "rules": { "no-undef": "off" } }]
} }
...@@ -22,4 +22,5 @@ dist-ssr ...@@ -22,4 +22,5 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
.eslintrc.*
!/src/views/warehouse/manage.vue !/src/views/warehouse/manage.vue
...@@ -5,7 +5,88 @@ ...@@ -5,7 +5,88 @@
// Generated by unplugin-auto-import // Generated by unplugin-auto-import
export {} export {}
declare global { declare global {
const EffectScope: typeof import('vue')['EffectScope']
const ElLoading: typeof import('element-plus/es')['ElLoading'] const ElLoading: typeof import('element-plus/es')['ElLoading']
const ElMessage: typeof import('element-plus/es')['ElMessage'] const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const ElOption: typeof import('element-plus/es')['ElOption']
const ElSelect: typeof import('element-plus/es')['ElSelect']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const createPinia: typeof import('pinia')['createPinia']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const defineStore: typeof import('pinia')['defineStore']
const effectScope: typeof import('vue')['effectScope']
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const storeToRefs: typeof import('pinia')['storeToRefs']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useId: typeof import('vue')['useId']
const useLink: typeof import('vue-router')['useLink']
const useModel: typeof import('vue')['useModel']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
} }
...@@ -7,7 +7,10 @@ export {} ...@@ -7,7 +7,10 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
AmountInput: typeof import('./src/components/Form.vue/AmountInput.vue')['default']
CommonCard: typeof import('./src/components/CommonCard.vue')['default'] CommonCard: typeof import('./src/components/CommonCard.vue')['default']
DatePicker: typeof import('./src/components/Form.vue/DatePicker.vue')['default']
DateRangePicker: typeof import('./src/components/Form.vue/DateRangePicker.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
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']
...@@ -28,6 +31,7 @@ declare module 'vue' { ...@@ -28,6 +31,7 @@ declare module 'vue' {
ElImage: typeof import('element-plus/es')['ElImage'] ElImage: typeof import('element-plus/es')['ElImage']
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']
ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
...@@ -46,6 +50,7 @@ declare module 'vue' { ...@@ -46,6 +50,7 @@ declare module 'vue' {
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree'] ElTree: typeof import('element-plus/es')['ElTree']
ElUpload: typeof import('element-plus/es')['ElUpload']
Icon: typeof import('./src/components/Icon.vue')['default'] Icon: typeof import('./src/components/Icon.vue')['default']
ImageView: typeof import('./src/components/ImageView.vue')['default'] ImageView: typeof import('./src/components/ImageView.vue')['default']
LogList: typeof import('./src/components/LogList.vue')['default'] LogList: typeof import('./src/components/LogList.vue')['default']
...@@ -53,12 +58,15 @@ declare module 'vue' { ...@@ -53,12 +58,15 @@ declare module 'vue' {
RenderColumn: typeof import('./src/components/RenderColumn.vue')['default'] RenderColumn: typeof import('./src/components/RenderColumn.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
Select: typeof import('./src/components/Form.vue/Select.vue')['default']
ShipmentOrderDetail: typeof import('./src/components/ShipmentOrderDetail.vue')['default'] ShipmentOrderDetail: typeof import('./src/components/ShipmentOrderDetail.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.vue/Switch .vue')['default']
TableRightMenu: typeof import('./src/components/TableRightMenu.vue')['default'] TableRightMenu: typeof import('./src/components/TableRightMenu.vue')['default']
TableView: typeof import('./src/components/TableView.vue')['default'] TableView: typeof import('./src/components/TableView.vue')['default']
UploadExcel: typeof import('./src/components/UploadExcel.vue')['default'] UploadExcel: typeof import('./src/components/UploadExcel.vue')['default']
UploadImage: typeof import('./src/components/UploadImage.vue')['default'] UploadImage: typeof import('./src/components/UploadImage.vue')['default']
VxeTable: typeof import('./src/components/VxeTable.vue')['default']
WangEditor: typeof import('./src/components/WangEditor.vue')['default'] WangEditor: typeof import('./src/components/WangEditor.vue')['default']
} }
export interface ComponentCustomProperties { export interface ComponentCustomProperties {
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,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",
"vxe-table": "^4.13.31",
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
...@@ -31,7 +32,7 @@ ...@@ -31,7 +32,7 @@
"@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1", "@typescript-eslint/parser": "^7.1.1",
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^4.1.0", "@vitejs/plugin-vue-jsx": "^4.2.0",
"@vue/eslint-config-typescript": "^12.0.0", "@vue/eslint-config-typescript": "^12.0.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
......
import { BaseRespData } from '@/types/api'
import axios from './axios'
import {
LogisticsMethod,
LogisticsMethodList,
UpdateLogisticsMethodStatus,
} from '@/types/api/logistics'
interface Ikey {
[key: string]: unknown
}
interface ILogisticsParams {
logType: string
relaId: number | string
}
//获取日志
export function getLogisticsLog(params: ILogisticsParams) {
return axios.get<never, BaseRespData<never>>('logisticsLog/getList', {
params,
})
}
/**
* @description 物流方式
*/
//列表
export function getLogisticsWayList(data: LogisticsMethodList) {
return axios.post<never, BaseRespData<never>>('/logisticsWay/list_page', data)
}
//新增
export function addLogisticsWay(data: LogisticsMethod) {
return axios.post<Ikey, BaseRespData<never>>('/logisticsWay/add', data)
}
//修改
export function updateLogisticsWay(data: LogisticsMethod) {
return axios.post<Ikey, BaseRespData<never>>('/logisticsWay/update', data)
}
//更新状态
export function updateStatusLogisticsWay(params: UpdateLogisticsMethodStatus) {
return axios.get<Ikey, BaseRespData<never>>(
'/logisticsWay/updateStatusById',
{
params,
},
)
}
// 删除
export function deleteLogisticsWay(ids: string) {
return axios.get<Ikey, BaseRespData<never>>('/logisticsWay/delete', {
params: { ids: ids },
})
}
//获取申报规则列表
export function getRuleList() {
return axios.get<never, BaseRespData<never>>(
'/logisticsCustomsRule/getRuleList',
)
}
//获取仓库列表
export function getWarehouseList() {
return axios.get<never, BaseRespData<never>>('/factoryWarehouseInfo/getAll')
}
//获取平台列表
export function getPlatformList() {
return axios.get<never, BaseRespData<never>>('/logisticsWay/platform')
}
/**
* @description 发货地址
*/
//列表
export function getAddressByIdList(params: any) {
return axios.get<never, BaseRespData<never>>('/logisticsAddress/list_page', {
params,
})
}
//根据id查询地址
export function getAddressById(params: any) {
return axios.get<never, BaseRespData<never>>(
'/logisticsAddress/getAddressById',
{
params,
},
)
}
//新增
export function addAddress(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logisticsAddress/addAddress',
params,
)
}
//修改
export function updateAddress(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logisticsAddress/updateAddress',
params,
)
}
//删除
export function deleteAddressByIds(params: any) {
return axios.get<never, BaseRespData<never>>(
'/logisticsAddress/deleteAddressByIds',
{
params,
},
)
}
/**
* @description 物流报价
*/
//物流方式列表
export function getlogisticsWayAllList() {
return axios.get<never, BaseRespData<never>>('/logisticsWay/all_list', {})
}
//物流报价列表
export function getlogisticsQuotationList(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/list_page',
params,
)
}
//新增
export function addLogisticsQuotation(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/add',
params,
)
}
//修改
export function updateLogisticsQuotation(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/update',
params,
)
}
//删除
export function deleteLogisticsQuotation(params: any) {
return axios.get<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/delete',
{
params,
},
)
}
//导入
export function importLogisticsQuotation(formData: any) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/import',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
},
)
}
//下载模版
export function downloadLogisticsQuotationTemplate() {
return axios.get<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/exportExcel',
{
responseType: 'blob',
},
)
}
//根据ID查对象
export function getLogisticsQuotationByID(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/get',
params,
)
}
/**
* @description 申报规则
*/
//列表
export function getLogisticsCustomsRuleList(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logisticsCustomsRule/list_page',
params,
)
}
//新增
export function addLogisticsCustomsRule(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logisticsCustomsRule/add',
params,
)
}
//修改
export function updateLogisticsCustomsRule(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logisticsCustomsRule/update',
params,
)
}
//删除
export function deleteLogisticsCustomsRule(params: any) {
return axios.get<never, BaseRespData<never>>('/logisticsCustomsRule/delete', {
params,
})
}
/**
* @description 物流分区
*/
//列表
export function getLogisticsZoneList(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsZone/list',
params,
)
}
//新增
export function addLogisticsZone(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsZone/add',
params,
)
}
//修改
export function updateLogisticsZone(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsZone/update',
params,
)
}
//删除
export function deleteLogisticsZone(params: any) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsZone/delete',
params,
)
}
//物流分区导入
export function importLogisticsZone(formData: any) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsZone/import',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
},
)
}
//物流分区导入模板
export function exportExcelLogisticsZone() {
return axios.get<never, BaseRespData<never>>(
'/logistics/logisticsZone/exportExcel',
{
responseType: 'blob',
},
)
}
/**
* @description 运费试算
*/
export function getLogisticsTrialCalculation(params: any) {
return axios.get<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/logisticsTrialCalculation',
{
params,
},
)
}
...@@ -246,3 +246,12 @@ export function loadWarehouseListApi() { ...@@ -246,3 +246,12 @@ export function loadWarehouseListApi() {
'factoryWarehouseInfo/getAll', 'factoryWarehouseInfo/getAll',
) )
} }
export function refreshMaterialApi(data: {
orderIds: string | undefined
productIds: string | undefined
}) {
return axios.post<never, BaseRespData<never>>(
'factory/podJomallOrderProductUs/refreshDesignImages ',
data,
)
}
import { defineComponent, PropType, ref, computed, h } from 'vue'
import type { Component } from 'vue'
import { ElInput, ElForm, ElFormItem } from 'element-plus'
import type { FormInstance } from 'element-plus'
import AmountInput from './Form.vue/AmountInput.vue' // 金额输入框
import DateRangePicker from './Form.vue/DateRangePicker.vue' // 时间范围选择器
import DatePicker from './Form.vue/DatePicker.vue' // 单个日期选择器
import Select from './Form.vue/Select.vue' // 普通下拉选择框
import Switch from './Form.vue/Switch .vue'
import './Form.vue/form.scss'
import type { FormItemRule } from 'element-plus'
// 定义表单项配置接口
export interface IFormConfig {
fixed?: string
title?: string
prop?: string
label?: string
type?: string
btn?: JSX.Element | (() => JSX.Element)
attrs?: Record<string, unknown>
isIncludeProp?: boolean
rules?: FormItemRule | FormItemRule[]
render?: (
item?: IFormConfig,
formData?: Record<string, any>,
index?: number,
) => JSX.Element
}
// 定义组件类型
type ComponentType = Component
// 支持的组件映射
const componentConfig: Record<string, ComponentType> = {
input: ElInput,
amountInput: AmountInput,
dateRangePicker: DateRangePicker,
datePicker: DatePicker,
select: Select,
switch: Switch,
// 这里可以根据需要添加更多组件类型
}
export default defineComponent({
name: 'CustomizeForm',
props: {
config: {
type: Array as PropType<IFormConfig[]>,
default: () => [],
},
labelWidth: {
type: [String, Number],
default: 100,
},
formItemWidth: {
type: [String, Number],
default: '50%',
},
modelValue: {
type: Object as PropType<Record<string, unknown>>,
default: () => ({}),
},
size: {
type: String as PropType<'large' | 'default' | 'small'>,
default: 'default',
},
inline: {
type: Boolean,
default: false,
},
labelPosition: {
type: String as PropType<'left' | 'right' | 'top'>,
default: 'right',
},
},
emits: ['update:modelValue', 'validate'],
setup(props, { emit, attrs }) {
const formRef = ref<FormInstance>()
const formData = ref<Record<string, unknown>>(props.modelValue)
const tableConfig = ref<any[]>([])
watch(
() => props.config,
(val) => {
console.log('tableConfig', val)
tableConfig.value = val
},
{ immediate: true, deep: true },
)
// 监听表单数据变化
watch(
() => formData.value,
(val) => {
emit('update:modelValue', val)
},
{ deep: true },
)
// 监听外部数据变化
watch(
() => props.modelValue,
(val) => {
console.log(84, val)
formData.value = val
},
{ immediate: true, deep: true },
)
// 获取form表单的属性
const getFormAttrs = computed(() => ({
...attrs,
style: { width: '100%' },
}))
// 获取组件项的属性
const getComponentAttrs = (item: IFormConfig) => {
return item.attrs ? { ...item.attrs } : {}
}
// 表单验证方法
const validate = async () => {
if (!formRef.value) return false
return await formRef.value.validate()
}
// 表单验证方法
const clearValidate = async () => {
if (!formRef.value) return false
return await formRef.value.clearValidate()
}
// 重置表单
const resetFields = async () => {
await formRef.value?.resetFields()
}
function refashConfig(showFields?: string[]) {
if (showFields?.length) {
const filterArr = props.config.filter((item) =>
showFields.includes(item.prop as string),
)
tableConfig.value = [
...props.config.filter((item) => !item.isIncludeProp),
...filterArr,
]
.filter((item) => !item.fixed)
.concat(tableConfig.value.filter((item) => item.fixed))
} else {
tableConfig.value = [...props.config]
console.log(tableConfig.value)
}
}
return {
formRef,
formData,
validate,
resetFields,
getFormAttrs,
getComponentAttrs,
refashConfig,
tableConfig,
clearValidate,
}
},
render() {
return (
<ElForm
ref="formRef"
model={this.formData}
labelWidth={this.labelWidth}
size={this.size}
inline={this.inline}
labelPosition={this.labelPosition}
{...this.getFormAttrs}
style={{ display: 'flex', flexWrap: 'wrap' }}
class="customForm"
>
{this.tableConfig.map((item, index) =>
item.title ? (
<div
style={{
display: 'flex',
width: '100%',
flexWrap: 'wrap',
}}
>
<div
style={{
width: '100%',
textAlign: 'center',
fontWeight: 700,
marginBottom: '20px',
fontSize: '16px',
}}
>
{item.title}
</div>
{item.render && item.render(item, this.formData, this)}
</div>
) : (
<ElFormItem
key={index}
prop={item.prop}
label={item.label}
style={{
width: item.attrs?.width || this.formItemWidth || '50%',
}}
rules={item.rules}
>
{item.render
? item.render(item)
: h(componentConfig[item.type] || ElInput, {
modelValue: this.formData[item.prop],
'onUpdate:modelValue': (value: unknown) => {
this.formData[item.prop] = value
},
disabled:
typeof item.attrs?.disabled === 'function'
? item.attrs.disabled()
: item.attrs?.disabled,
...this.getComponentAttrs(item),
})}
{item.btn && item.btn()}
</ElFormItem>
),
)}
</ElForm>
)
},
})
<template>
<el-input
v-model.trim="val"
type="text"
:placeholder="placeholder"
class="amountInput"
:size="size"
:disabled="props.isDisabled"
@input="iptFn"
>
<template v-if="hasSuffix" #suffix>{{ suffix }}</template>
<template v-if="hasAppend" #append>
<component :is="appendRender" />
</template>
</el-input>
</template>
<script lang="tsx" setup>
import { isNumFloat } from '@/utils/validate'
const props = withDefaults(
defineProps<{
modelValue?: string | number
suffix?: string
placeholder?: string
size?: '' | 'default' | 'small' | 'large'
hasSuffix?: boolean
hasAppend?: boolean
appendRender?: () => JSX.Element
isDisabled?: boolean
}>(),
{
modelValue: '',
suffix: '元',
placeholder: '请输入费用',
size: 'default',
hasSuffix: true,
isDisabled: false,
hasAppend: false,
appendRender: null,
},
)
const emits = defineEmits<{
(e: 'update:modelValue', data: string | number | null): void
(e: 'input', data: string | number): void
}>()
const val = ref<string | number | null>(props.modelValue)
const iptFn = (e) => {
const bool = isNumFloat(e) // 只允许输入数字和最多一个小数点
if (e === '') {
val.value = null
emits('input', '')
} else {
if (bool) {
const parts = e.split('.') // 将输入的数字按小数点分割成整数部分和小数部分
if (parts[1] && parts[1].length > 2) {
// 如果小数部分超过两位,则只保留两位小数
val.value = `${parts[0]}.${parts[1].slice(0, 2)}`
} else {
val.value = e
}
} else {
const decimalCount = (e.match(/\./g) || []).length // 计算小数点的个数
if (decimalCount > 1) {
// 如果小数点个数大于1,则将多余的小数点替换为空字符串
val.value = e.replace(/\./g, (match, offset) => {
return offset === e.lastIndexOf('.') ? '.' : ''
})
} else {
val.value = e.replace(/[^\d\.]/g, '')
}
}
}
emits('input', val.value || '')
}
watch(
() => val.value,
(newVal) => {
if (newVal === '') {
emits('update:modelValue', null)
} else {
emits('update:modelValue', newVal)
}
},
)
watch(
() => props.modelValue,
(newVal: string | number) => {
if (newVal !== undefined) {
val.value = newVal
}
},
{
immediate: true,
},
)
</script>
<style lang="scss" scoped>
:deep() {
.el-form-item__content {
line-height: 0 !important;
}
}
</style>
<template>
<el-date-picker
ref="dateRef"
:type="props.type"
:placeholder="props.placeholder"
value-format="YYYY-MM-DD"
class="datePicker"
:popper-class="!props.isNeedTime ? '' : 'popperClass'"
:teleported="true"
:disabled-date="disabledDate"
/>
</template>
<script lang="ts" setup>
const dateRef = ref(null)
const props = withDefaults(
defineProps<{
placeholder?: string
type?: string
isNeedTime?: boolean
rowData?: any
isrowDate?: boolean
newDisableDate?: (...arg) => boolean
}>(),
{
placeholder: '请选择日期',
type: 'date',
isNeedTime: false,
isrowDate: false,
rowData: null,
newDisableDate: undefined,
},
)
// 用于存储动态的禁用日期逻辑
function disabledDate(time) {
if (props.isrowDate && props.newDisableDate) {
return props.newDisableDate(time, props.rowData)
}
}
</script>
<style lang="scss" scoped>
.popperClass .el-time-spinner__wrapper {
width: 100% !important;
}
.popperClass .el-scrollbar:nth-of-type(2) {
display: none !important;
}
</style>
<template>
<el-date-picker
ref="dateRangePickerRef"
class="dateRangePicker"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 220px"
:popper-class="!props.isNeedTime ? '' : 'popperClass'"
:teleported="true"
v-bind="attrs"
/>
</template>
<script lang="ts" setup>
const props = defineProps({
isNeedTime: {
type: Boolean,
default: false,
},
})
const attrs = useAttrs()
function disabledDate(time) {
// 获取当前日期
const today = new Date()
// 将时间戳转换为年月日格式的字符串
const year = today.getFullYear()
const month = today.getMonth()
const day = today.getDate()
const currentDate = new Date(year, month, day)
// 禁止选择同一天
return time.getTime() === currentDate.getTime()
}
const dateRangePickerRef = ref(null)
defineExpose({
dateRangePickerRef,
})
</script>
<style lang="scss">
.popperClass .el-time-spinner__wrapper {
width: 100% !important;
}
.popperClass .el-scrollbar:nth-of-type(2) {
display: none !important;
}
</style>
<template>
<!-- <el-row style="flex-wrap: nowrap"> -->
<el-select
v-model="model"
class="select"
placeholder="请选择"
:empty-values="[null, undefined, '']"
loading-text="加载中..."
v-bind="{
...getEvent,
}"
:loading="loading"
@change="changeFn"
>
<el-option
v-for="item in getOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<!-- </el-row> -->
</template>
<script lang="ts" setup>
import { cloneDeep, debounce } from 'lodash-es'
interface IOption {
[key: string]: any
}
const props = withDefaults(
defineProps<{
options: Array<IOption>
value?: string
label?: string
hasAllOptions?: boolean
labelIsValue?: boolean // value 使用label字段
initChange?: boolean // 首次是否触发change事件
modelValue?: string | number | string[] | number[]
isRefresh?: boolean
isValueKey?: boolean
load?: (arg: (val: boolean) => void) => void // focus后再加载数据的
}>(),
{
options: () => [],
value: 'value', // 值字段名 默认字段为value
label: 'label', // 描述字段名 默认字段为label
hasAllOptions: false, // 使用全部下拉选项
labelIsValue: false, // value 是 label
initChange: false,
modelValue: '',
isRefresh: false,
load: null,
isValueKey: false,
},
)
const emits = defineEmits<{
(e: 'change', option: IOption, value: string | number)
(e: 'update:modelValue', data: string | number | undefined)
(e: 'updatedOption')
}>()
const loading = ref<boolean>(false)
const model = ref(props.modelValue)
const allOption = {
value: '',
label: '全部',
}
const fields = reactive({
value: props.value,
label: props.label,
})
// 选项处理
const getOptions = computed(() => {
const value = props.labelIsValue ? fields.label : fields.value
const arr = cloneDeep(props.options)
const data = arr.map((item) => {
!item.value && (item.value = eval(`item.${value}`))
!item.label && (item.label = eval(`item.${fields.label}`))
return item
})
if (props.hasAllOptions) {
data.unshift(allOption)
return data
}
return data
})
const changeFn = (id) => {
const value = props.labelIsValue ? fields.label : fields.value
const findItem = props.options.find((item) => item[value] === id)
if (findItem) {
emits('change', findItem, id)
}
}
watch(model, (val) => {
emits('update:modelValue', val)
})
if (props.initChange) {
watch([() => props.modelValue, getOptions], ([val, getOptionsVal]) => {
if (val !== 0) {
model.value = val
getOptionsVal.length && changeFn(val)
}
})
} else {
watch(
() => props.modelValue,
(val) => {
// if (val !== 0) {
model.value = val
// }
},
)
}
const handleClickFn = debounce(() => {
model.value = null
emits('updatedOption')
}, 500)
const FocusFn = () => {
try {
props.load &&
props.load((val: boolean) => {
loading.value = val
})
} catch (err) {
console.log(err)
}
}
const getEvent = computed(() => {
let result = {}
if (props.load) {
result = {
onFocus: FocusFn,
}
}
return result
})
</script>
<style lang="scss" scoped>
.select {
width: 100%;
height: 100%;
}
.iConFont {
margin-left: 8px;
cursor: pointer;
&:hover {
color: #2f9bff;
}
}
</style>
<template>
<el-switch
v-model="model"
class="ml-2"
:style="{
'--el-switch-on-color': props.activeColor,
'--el-switch-off-color': props.inactiveColor,
}"
v-bind="$attrs"
/>
</template>
<script lang="ts" setup>
const model = ref()
const props = withDefaults(
defineProps<{
activeColor?: string
inactiveColor?: string
modelValue?: any
}>(),
{
activeColor: '#13ce66',
inactiveColor: ' #ff4949',
modelValue: null,
},
)
watch(
() => props.modelValue,
(val) => {
console.log(32, val)
model.value = val
},
{ immediate: true },
)
watch(model, (val) => {
emits('update:modelValue', val)
})
const emits = defineEmits<{
(e: 'update:modelValue', data: string | number | undefined)
}>()
</script>
<style lang="scss" scoped></style>
.customForm {
.el-form-item {
.el-form-item__content {
flex-wrap: nowrap;
}
}
}
\ No newline at end of file
import {
defineComponent,
PropType,
ref,
computed,
reactive,
watch,
h,
} from 'vue'
import type { Component } from 'vue'
import AmountInput from './Form.vue/AmountInput.vue' // 金额输入框
import DateRangePicker from './Form.vue/DateRangePicker.vue' // 时间范围选择器
import DatePicker from './Form.vue/DatePicker.vue' // 单个日期选择器
import Select from './Form.vue/Select.vue' // 普通下拉选择框
import { ElInput, ElForm, ElFormItem, ElButton } from 'element-plus'
import {
ISearchForm,
EComponenetType,
ISeachFormConfig,
EDataType,
} from '@/types/searchType'
// 组件 - 数据类型 映射
const componentDataTypeMap = new Map([
[EComponenetType.amountInput, EDataType.string],
[EComponenetType.input, EDataType.string],
[EComponenetType.dateRangePicker, EDataType.array],
[EComponenetType.datePicker, EDataType.string],
[EComponenetType.select, EDataType.undefined],
])
type ComponentType = Component
const componentConfig: Record<EComponenetType, ComponentType> = {
[EComponenetType.amountInput]: AmountInput,
[EComponenetType.input]: ElInput,
[EComponenetType.dateRangePicker]: DateRangePicker,
[EComponenetType.datePicker]: DatePicker,
[EComponenetType.select]: Select,
}
const styles = {
searchForm: {
position: 'relative',
},
searchFormHeader: {
display: 'flex',
marginBottom: '10px',
},
wrapper: {
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-start',
justifyContent: 'space-between',
},
} as const
export default defineComponent({
name: 'SearchForm',
props: {
config: {
type: Array as PropType<ISeachFormConfig[]>,
default: () => [],
},
labelWidth: {
type: [String, Number],
default: 58,
},
modelValue: {
type: Object as PropType<ISearchForm>,
default: () => ({}),
},
getTablelist: {
type: Function as PropType<(...args: unknown[]) => Promise<unknown>>,
default: null,
},
size: {
type: String as PropType<'large' | 'default' | 'small'>,
default: 'default',
},
addPermissionName: {
type: [String, Array] as PropType<string | string[]>,
default: '',
},
isSearchBtn: {
type: Boolean,
default: true,
},
isResetBtn: {
type: Boolean,
default: true,
},
isAddBtn: {
type: Boolean,
default: true,
},
isDeleteBtn: {
type: Boolean,
default: true,
},
},
emits: ['search', 'reset', 'fold', 'add', 'delete', 'update:modelValue'],
setup(props, { emit, slots, attrs }) {
const formRef = ref()
const formWrapperRef = ref()
const searchForm = ref<ISearchForm>({})
const selectResult = ref<ISeachFormConfig[]>([])
const componentWidth = computed(() =>
props.size === 'default' ? 200 : 220,
)
watch(
() => props.modelValue,
(val) => {
searchForm.value = val
},
{
immediate: true,
deep: true,
},
)
function initSeachForm() {
props.config.forEach((item, index) => {
if (componentDataTypeMap.has(item.type as EComponenetType)) {
const prop = item.prop || `value${index}`
if (!item.prop) {
item.prop = prop
}
}
})
}
watch(
() => props.config,
(val) => {
if (val.length) {
initSeachForm()
selectResult.value = val
}
},
{
immediate: true,
},
)
// 获取form表单的属性
const getFormAttrs = computed(() => ({
...attrs,
}))
// 获取组件项传进来的attrs
const getComponentAttrs = (item: ISeachFormConfig) => {
return item.attrs ? { ...item.attrs } : {}
}
const handleSubmit = (e: Event) => {
e.preventDefault()
}
return () => (
<div style={styles.searchForm}>
<div style={styles.searchFormHeader} class="pb-15">
{slots['header-content'] ? (
slots['header-content']()
) : (
<div style={styles.wrapper} class="mt-15">
<ElForm
ref={(el) => (formRef.value = el)}
class="w-full"
inline
model={searchForm.value}
labelPosition="left"
size={props.size}
{...{
onSubmit: handleSubmit,
}}
{...getFormAttrs.value}
>
<div ref={formWrapperRef} class="formItemWrapper w-full">
<div class="mt--18 flex align-center flex-wrap">
{selectResult.value.map((item, index) => (
<ElFormItem
key={index}
prop={item.prop}
label={item.label}
style={{ marginBottom: '0px' }}
>
{item.render
? item.render()
: h(componentConfig[item.type as EComponenetType], {
modelValue: searchForm.value[item.prop],
'onUpdate:modelValue': (value: unknown) => {
searchForm.value[item.prop] = value
},
...getComponentAttrs(item),
style: { width: `${componentWidth.value}px` },
})}
</ElFormItem>
))}
</div>
</div>
</ElForm>
</div>
)}
<div style={{ display: 'flex', alignItems: 'center' }}>
{props.isSearchBtn && (
<ElButton
class="btn"
type="primary"
onClick={() => emit('search', searchForm.value)}
>
查询
</ElButton>
)}
{props.isResetBtn && (
<ElButton
class="btn"
onClick={() => {
formRef.value?.resetFields()
// emit('update:modelValue', {})
emit('reset')
}}
>
重置
</ElButton>
)}
{props.isAddBtn && (
<ElButton class="btn" type="success" onClick={() => emit('add')}>
新增
</ElButton>
)}
{props.isDeleteBtn && (
<ElButton
class="btn"
type="danger"
onClick={() => emit('delete')}
>
删除
</ElButton>
)}
</div>
{slots.ontherBtn?.()}
</div>
</div>
)
},
})
import { VNode } from 'vue'
interface ColumnAttrs {
field?: string
title?: string
width?: string | number
[key: string]: unknown
}
interface TableColumn {
prop: string
label: string
attrs?: ColumnAttrs
render?: {
edit?: (params: { row: TableRowData }) => VNode | JSX.Element
default?: (params: { row: TableRowData }) => VNode | JSX.Element
[key: string]:
| ((params: { row: TableRowData }) => VNode | JSX.Element)
| undefined
}
}
interface TableRowData {
[key: string]: unknown
}
type SlotFunction = (scope: { row: TableRowData }) => VNode | JSX.Element
export default defineComponent({
name: 'CustomizeTable',
props: {
config: {
type: Array as PropType<TableColumn[]>,
default: () => [],
},
tableEditConfig: {
type: Object,
default: () => ({}),
},
modelValue: {
type: Object,
default: () => ({}),
},
getTablelist: {
type: Function as PropType<(...args: unknown[]) => Promise<unknown>>,
default: null,
},
isShowCheckBox: {
type: Boolean,
default: true,
},
},
emits: ['update:modelValue', 'checkbox-change', 'getCheckboxRecords'],
setup(props, { emit, attrs }) {
const tableRef = ref(null)
const tableData = ref<Record<string, unknown>[]>([])
const tableColumns = ref<TableColumn[]>([])
const editConfig = computed(() => {
return {
trigger: 'dblclick',
mode: 'cell',
enabled: false,
...props.tableEditConfig,
}
})
watch(
() => props.config,
(val) => {
if (val?.length) {
tableColumns.value = val
}
},
{
immediate: true,
},
)
watch(
() => props.modelValue,
(val) => {
tableData.value = Array.isArray(val) ? val : []
},
{
immediate: true,
},
)
async function getList() {
if (props.getTablelist) {
try {
const data = await props.getTablelist()
console.log(78, data)
} catch (error) {
console.log(error)
}
}
}
//获取选中数据
const getSelectEvent = () => {
const $table = tableRef.value
if ($table) {
const selectRecords = $table.getCheckboxRecords()
emit('getCheckboxRecords', selectRecords)
}
}
//设置高亮行
const selectRowEvent = (row: TableRowData) => {
const $table = tableRef.value
if ($table) {
console.log(110, row)
$table.setCurrentRow(row)
}
}
onMounted(() => {
getList()
})
return {
tableRef,
tableData,
tableColumns,
editConfig,
getSelectEvent,
selectRowEvent,
attrs,
}
},
render() {
return (
<vxe-table
ref={(el) => (this.tableRef = el)}
data={this.tableData}
height="100%"
edit-config={this.editConfig}
onCheckboxChange={this.getSelectEvent}
onCheckboxAll={this.getSelectEvent}
{...this.attrs}
>
{this.isShowCheckBox && (
<vxe-column type="checkbox" width="50" align="center"></vxe-column>
)}
<vxe-column align="center" type="seq" width="50" title="序号" />
{this.tableColumns.map((item: TableColumn, index: number) => (
<vxe-column
key={index}
field={item.prop}
title={item.label}
{...item.attrs}
edit-render={{}}
>
{{
...(() => {
// 创建基础插槽配置
const slots: Record<string, SlotFunction> = {
// 默认的 default 插槽实现
default: ({ row }) => <span>{row[item.prop]}</span>,
}
if (item.render) {
// 添加所有自定义插槽
Object.entries(item.render).forEach(
([slotName, renderFn]) => {
if (renderFn) {
slots[slotName] = (scope: { row: TableRowData }) =>
renderFn(scope)
}
},
)
}
return slots
})(),
}}
</vxe-column>
))}
</vxe-table>
)
},
})
...@@ -4,7 +4,16 @@ import App from './App.vue' ...@@ -4,7 +4,16 @@ import App from './App.vue'
import router from './router' import router from './router'
import store from './store' import store from './store'
import './styles/index.scss' import './styles/index.scss'
import VxeUITable from 'vxe-table'
import 'vxe-table/lib/style.css'
// 确保在渲染用户提供的HTML内容时,不会执行任何潜在的恶意脚本,从而提高应用的安全性 // 确保在渲染用户提供的HTML内容时,不会执行任何潜在的恶意脚本,从而提高应用的安全性
import vueDomPurifyHTMLPlugin from 'vue-dompurify-html' import vueDomPurifyHTMLPlugin from 'vue-dompurify-html'
createApp(App).use(vueDomPurifyHTMLPlugin).use(router).use(store).mount('#app') createApp(App)
.use(vueDomPurifyHTMLPlugin)
.use(router)
.use(store)
.use(VxeUITable)
.mount('#app')
...@@ -121,6 +121,126 @@ const router = createRouter({ ...@@ -121,6 +121,126 @@ const router = createRouter({
component: TypeseetingManagement, component: TypeseetingManagement,
}, },
{ {
path: '/logistics/logisticsMethod',
meta: {
title: '物流方式',
},
component: () => import('@/views/logistics/logisticsMethod.vue'),
},
{
path: '/logistics/shippingAddress',
meta: {
title: '发货地址',
},
component: () => import('@/views/logistics/shippingAddress.vue'),
},
{
path: '/logistics/logisticsQuotation',
meta: {
title: '物流报价',
},
component: () => import('@/views/logistics/logisticsQuotation.vue'),
},
{
path: '/logistics/declarationRule',
meta: {
title: '申报规则',
},
component: () => import('@/views/logistics/declarationRule.vue'),
},
{
path: '/logistics/logisticsPartition',
meta: {
title: '物流分区',
},
component: () => import('@/views/logistics/logisticsPartition.vue'),
},
{
path: '/logistics/logisticsCalculate',
meta: {
title: '运费试算',
},
component: () => import('@/views/logistics/logisticsCalculate.vue'),
},
{
path: '/warehouse/manage',
meta: {
title: '仓库管理',
},
component: WarehouseManage,
},
{
path: '/warehouse/receipt-doc',
meta: {
title: '入库单',
},
component: receiptDoc,
},
// {
// path: '/warehouse/issue-doc',
// meta: {
// title: '出库单',
// },
// component: issueDoc,
// },
{
path: '/warehouse/warning',
meta: {
title: '仓库预警',
},
component: WarehouseWarning,
},
{
path: '/warehouse/position',
meta: {
title: '库位管理',
},
component: WarehousePosition,
},
{
path: '/logistics/logisticsMethod',
meta: {
title: '物流方式',
},
component: () => import('@/views/logistics/logisticsMethod.vue'),
},
{
path: '/logistics/shippingAddress',
meta: {
title: '发货地址',
},
component: () => import('@/views/logistics/shippingAddress.vue'),
},
{
path: '/logistics/logisticsQuotation',
meta: {
title: '物流报价',
},
component: () => import('@/views/logistics/logisticsQuotation.vue'),
},
{
path: '/logistics/declarationRule',
meta: {
title: '申报规则',
},
component: () => import('@/views/logistics/declarationRule.vue'),
},
{
path: '/logistics/logisticsPartition',
meta: {
title: '物流分区',
},
component: () => import('@/views/logistics/logisticsPartition.vue'),
},
{
path: '/logistics/logisticsCalculate',
meta: {
title: '运费试算',
},
component: () => import('@/views/logistics/logisticsCalculate.vue'),
},
{
path: '/warehouse/manage', path: '/warehouse/manage',
meta: { meta: {
title: '仓库管理', title: '仓库管理',
...@@ -147,7 +267,8 @@ const router = createRouter({ ...@@ -147,7 +267,8 @@ const router = createRouter({
title: '仓库预警', title: '仓库预警',
}, },
component: WarehouseWarning, component: WarehouseWarning,
},{ },
{
path: '/warehouse/position', path: '/warehouse/position',
meta: { meta: {
title: '库位管理', title: '库位管理',
......
...@@ -124,6 +124,43 @@ const menu: MenuItem[] = [ ...@@ -124,6 +124,43 @@ const menu: MenuItem[] = [
}, },
], ],
}, },
{
index: '4',
id: 7,
label: '物流',
children: [
{
index: '/logistics/logisticsMethod',
id: 1,
label: '物流方式',
},
{
index: '/logistics/shippingAddress',
id: 2,
label: '发货地址',
},
{
index: '/logistics/logisticsQuotation',
id: 3,
label: '物流报价',
},
{
index: '/logistics/declarationRule',
id: 4,
label: '申报规则',
},
{
index: '/logistics/logisticsPartition',
id: 5,
label: '物流分区',
},
{
index: '/logistics/logisticsCalculate',
id: 6,
label: '运费试算',
},
],
},
// { // {
// index: '', // index: '',
// id: 3, // id: 3,
......
...@@ -12,6 +12,7 @@ export interface PaginationData<D> { ...@@ -12,6 +12,7 @@ export interface PaginationData<D> {
size: number size: number
current: number current: number
records: D[] records: D[]
data?: D[]
} }
export interface Statistics<D> { export interface Statistics<D> {
sumNotPassNum: number sumNotPassNum: number
......
export interface LogisticsMethod {
id?: number | string
name?: string
warehouseId?: number
warehouseName?: string
companyId?: number
company?: string
serviceCode?: string
siteUrl?: string
status: number | string
platformList: platformObj[]
ruleRef: ruleRefObj
}
export interface LogisticsMethodList {
name?: string
status?: number | string
serviceCode?: number | string
pageSize: number | string
currentPage: number | string
}
export interface UpdateLogisticsMethodStatus {
id?: number | string
status?: number | string
}
export interface LogisticsResponse {
code: number
data: {
total: number
size: number
current: number
records: LogisticsMethod[]
}
message: string
}
interface platformObj {
platform: string
logisticsName: string | number
showPlatform: (string | number)[]
}
interface ruleRefObj {
ruleId: string | number
ruleName: string | number
}
/*
* @Author: Try
* @Date: 2023-10-17 18:25:26
* @LastEditTime: 2024-06-04 14:02:19
* @LastEditors: Seven
* @FilePath: \shulong_tenant_pc\src\components\SearchForm\type\index.ts
* @Description: 头部注释配置模板
*/
export interface ISearchForm {
[key: string]: any
}
export interface IOptions {
[key: string]: any
}
export enum EComponenetType {
amountInput = 'amountInput', // 金额输入框
input = 'input', // 普通输入框
dateRangePicker = 'dateRangePicker', // 时间范围选择器
datePicker = 'datePicker', // 单个日期选择器
select = 'select', // 普通下拉选择框
}
// EComponenetType 枚举转联合
export type TComponenetType = `${EComponenetType}`
export interface IAttrs {
options?: IOptions
hasAllOptions?: boolean // 使用全部选项
value?: string
label?: string
placeholder?: string
[key: string]: any
}
export interface ISlots{
formItemSlot?: boolean
}
export interface ISeachFormConfig {
prop: string
type?: EComponenetType | TComponenetType
label?: string
defaultValue?: string
attrs?: IAttrs
labelWidth?: number | string
slot?: ISlots
[key: string]: any
}
export interface IKeyBoolean {
[key: string]: boolean
}
export enum EDataType {
array = 'array',
string = 'string',
number = 'number',
boolean = 'boolean',
object = 'object',
undefined = 'undefined',
}
// 下载导入模板的配置
export interface IDownloadXlsConfig {
filename: string
url: string
}
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(null)
const isShow = ref(false)
watch(
() => props.modelValue,
(val) => {
isShow.value = val
},
{ immediate: true },
)
return () => {
return (
<ElDialog
ref={(el) => (formRef.value = el)}
v-model={isShow.value}
title={props.title}
width={props.dialogWidth}
onClose={() => {
emit('close')
}}
close-on-click-modal={false}
{...attrs}
>
<div class="dialog-form">
{slots.default?.()}
{slots.footer?.()}
</div>
</ElDialog>
)
}
},
})
import { Ref, ref } from 'vue'
import { cloneDeep } from 'lodash-es'
export function useValue<T extends object>(
initialValue: T,
): [Ref<T>, () => void] {
const value = ref<T>(cloneDeep(initialValue)) as Ref<T>
const resetToDefault = () => {
value.value = cloneDeep(initialValue)
}
return [value, resetToDefault]
}
<template>
<div class="user-page flex-column card h-100 overflow-hidden">
<div class="header-filter-form">
<SearchForm
:config="searchConfig"
:isSearchBtn="false"
:isAddBtn="false"
:isDeleteBtn="false"
v-model="searchForm"
>
<template #ontherBtn>
<div style="margin-left: 10px; display: flex; gap: 10px">
<ElButton @click="search" type="primary">计算</ElButton>
</div>
</template>
</SearchForm>
</div>
<div
v-loading="loading"
class="user-content flex-1 flex-column overflow-hidden"
>
<div class="user-list flex-1 overflow-hidden">
<CustomizeTable
ref="tableRef"
v-model="tableData"
:config="tableConfig"
:merge-cells="mergeCells"
highlight-current-row
:isShowCheckBox="false"
border="full"
@getCheckboxRecords="handleCheckboxRecords"
></CustomizeTable>
</div>
</div>
</div>
</template>
<script setup lang="tsx">
defineOptions({
name: 'LogisticsCalculate',
})
import { getLogisticsTrialCalculation } from '@/api/logistics'
import SearchForm from '@/components/SearchForm.tsx'
import CustomizeTable from '@/components/VxeTable.tsx'
import type { VxeTablePropTypes } from 'vxe-table'
import { useValue } from './hooks/useValue'
const [searchForm] = useValue({ code: '', weight: '' })
const tableRef = ref(null)
const selection = ref([])
const mergeCells = ref<VxeTablePropTypes.MergeCells>([])
const tableData = ref([])
const searchConfig = ref([
{
prop: 'code',
type: 'input',
label: '邮编',
attrs: {
placeholder: '请输入邮编',
},
},
{
prop: 'weight',
type: 'amountInput',
label: '重量',
attrs: {
placeholder: '请输入重量',
hasUnit: true,
suffix: 'g',
},
},
])
const tableConfig = ref([
{
prop: 'logisticsWayName',
label: '物流名称',
attrs: {
align: 'center',
width: 100,
},
},
{
prop: 'warehouseName',
label: '发货仓库',
},
{
prop: 'logisticsWayCode',
label: '物流编码',
},
{
prop: 'partition',
label: '所在分区',
},
{
prop: 'status',
label: '状态',
render: {
default: ({ row }) => {
return (
<div style={{ color: row.status ? '#67c23a' : '#f56c6c' }}>
{row.status ? '成功' : '失败'}
</div>
)
},
},
},
{
prop: 'payFreight',
label: '预计运费($)',
},
])
async function search() {
if (!searchForm.value.code) {
ElMessage.warning('请输入邮编')
return
}
if (!searchForm.value.weight) {
ElMessage.warning('请输入重量')
return
}
await getList(searchForm.value)
const itemIndex = tableData.value.findIndex((item) => item.status)
console.log(123, itemIndex)
if (itemIndex !== -1) {
nextTick(() => {
tableRef.value?.selectRowEvent(tableData.value[itemIndex])
})
}
}
const loading = ref(false)
async function getList(data?) {
loading.value = true
try {
const res = await getLogisticsTrialCalculation({
...data,
})
tableData.value = [...res.data]
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
function handleCheckboxRecords(value: never[]) {
console.log(351, value)
selection.value = value
}
</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;
}
</style>
<template>
<div class="user-page flex-column card h-100 overflow-hidden">
<div class="header-filter-form">
<SearchForm
:config="searchConfig"
@search="search"
:isAddBtn="false"
@delete="deleteFn"
v-model="searchForm"
>
<template #ontherBtn>
<div style="margin-left: 10px; display: flex; gap: 10px">
<ElButton @click="downloadExcel" type="info" plain
>下载模版</ElButton
>
<el-upload
class="upload-demo"
action
:show-file-list="false"
:before-upload="onBeforeUploadImage"
:http-request="exportExcel"
>
<ElButton type="primary" plain> 导入</ElButton>
</el-upload>
</div>
</template></SearchForm
>
</div>
<div
v-loading="loading"
class="user-content flex-1 flex-column overflow-hidden"
>
<div class="user-list flex-1 overflow-hidden">
<CustomizeTable
v-model="tableData"
:config="tableConfig"
:merge-cells="mergeCells"
@edit-closed="editClosed"
:tableEditConfig="{
enabled: true,
}"
border="full"
@getCheckboxRecords="handleCheckboxRecords"
></CustomizeTable>
</div>
</div>
</div>
</template>
<script setup lang="tsx">
defineOptions({
name: 'LogisticsPartition',
})
import {
getLogisticsZoneList,
addLogisticsZone,
updateLogisticsZone,
deleteLogisticsZone,
importLogisticsZone,
exportExcelLogisticsZone,
getlogisticsWayAllList,
} from '@/api/logistics'
import SearchForm from '@/components/SearchForm.tsx'
import CustomizeTable from '@/components/VxeTable.tsx'
import type { VxeTablePropTypes } from 'vxe-table'
import { useValue } from './hooks/useValue'
import { showConfirm } from '@/utils/ui'
import { ElInput } from 'element-plus'
import { debounce } from 'lodash-es'
const [searchForm] = useValue<{
logisticsIdList?: string[] | string
codePrefix?: string
}>({ logisticsIdList: [] })
const selection = ref([])
const mergeCells = ref<VxeTablePropTypes.MergeCells>([])
const tableData = ref([])
const searchConfig = ref([
{
prop: 'logisticsIdList',
type: 'select',
label: '物流方式',
attrs: {
placeholder: '请选择物流方式',
multiple: true,
value: 'id',
label: 'name',
collapseTags: true,
collapseTagsTooltip: true,
options: [],
},
},
{
prop: 'codePrefix',
type: 'input',
label: '邮编编码',
attrs: {
placeholder: '请输入邮编编码',
},
},
])
const tableConfig = ref([])
onMounted(() => {
getAllList()
})
/**
* @description: 搜索
*/
async function search() {
await getList(searchForm.value)
setCellStyle()
}
/**
* @description: 转换表格数据
*/
function getTableData(arr) {
if (!Array.isArray(arr) || arr.length === 0) return []
return arr.map((parentItem) => {
const quotationList = parentItem.logisticsZoneList ?? []
quotationList?.forEach((el) => {
parentItem[el.logistics] = {
codePrefix: el.codePrefix || '',
highlight: el.highlight,
logisticsId: el.logisticsId,
}
})
return {
...parentItem,
}
})
}
/**
* @description: 删除按钮
*/
async function deleteFn() {
if (!selection.value.length) {
return ElMessage({
message: '请选择分区',
type: 'warning',
})
}
try {
await showConfirm('是否删除改分区下物流方式的邮编编码', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const whiteList = ['logisticsZoneList', 'zoneName', '_X_ROW_KEY']
const zomList = selection.value.map((item) => {
const arr = []
for (const key in item) {
if (!whiteList.includes(key)) {
arr.push(key)
}
}
return [...arr]
})
const params = {
logisticsList: zomList[0],
zoneNameList: selection.value.map((item) => item.zoneName),
}
await deleteLogisticsZone(params)
ElMessage({
message: '删除成功',
type: 'success',
})
getList(searchForm.value)
} catch (e) {
getList(searchForm.value)
// showError(e)
}
}
const loading = ref(false)
const editParams = ref({})
/**
* @description: 获取列表
*/
async function getList(data?) {
loading.value = true
try {
const res = await getLogisticsZoneList({
...data,
})
tableData.value = [...res.data]
const arr = await getTableData(tableData.value)
const whiteList = ['logisticsZoneList', 'zoneName']
const oldConfig = [
{
prop: 'zoneName',
label: '分区',
},
]
const newConfig = []
if (arr.length) {
for (const key in arr[0]) {
if (!whiteList.includes(key)) {
newConfig.push({
prop: key,
label: key,
render: {
edit: ({ row }) => {
return (
<div>
<ElInput
style={{ height: '24px' }}
modelValue={row[key]?.codePrefix}
onInput={(e) => {
console.log(233, row)
row[key].codePrefix = e
editParams.value = {
zoneName: row.zoneName,
logistics: key,
codePrefix: e || '',
logisticsId: row[key].logisticsId,
}
}}
/>
</div>
)
},
default: ({ row }) => {
return (
<span
class={row[key]?.highlight ? 'tableCell' : 'primaryCell'}
>
{row[key]?.codePrefix}
</span>
)
},
},
})
}
}
}
tableConfig.value = [...oldConfig, ...newConfig]
console.log(545, tableConfig.value)
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
/**
* @description: 处理复选框
*/
function handleCheckboxRecords(value: never[]) {
console.log(351, value)
selection.value = value
}
/**
* @description: 设置单元格样式
*/
function setCellStyle() {
const [cell, primaryCell] = ['.tableCell', '.primaryCell'].map((selector) =>
document.querySelectorAll(selector),
)
const getAncestor = (element, level = 2) => {
let current = element
while (level-- > 0 && current) {
current = current.parentElement
}
return current || null
}
const safeSetBackground = (element, color) => {
if (element?.style && typeof color === 'string') {
element.style.backgroundColor = color
}
}
if (cell.length) {
cell.forEach((item) => {
const grandParent = getAncestor(item)
safeSetBackground(grandParent, '#e6f7ff')
})
}
if (primaryCell.length) {
primaryCell.forEach((item) => {
const grandParent = getAncestor(item)
safeSetBackground(grandParent, 'transparent')
})
}
}
/**
* @description: 获取物流方式列表
*/
async function getAllList() {
try {
const res = await Promise.all([getlogisticsWayAllList(), getList()])
searchConfig.value[0].attrs!.options = [...(res[0]?.data || [])]
} catch (error) {
console.log(error)
}
}
/**
* @description: 上传文件
*/
function onBeforeUploadImage(file) {
const isIMAGE =
file.type ==
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
if (!isIMAGE) {
ElMessage.error('上传文件只能是excel格式!')
}
return isIMAGE
}
/**
* @description: 下载模版
*/
async function downloadExcel() {
try {
const res = await exportExcelLogisticsZone()
const blob = new Blob([res])
const filename = '物流分区模版.xlsx'
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = filename
link.click()
} catch (error) {
console.log(error)
}
}
/**
* @description: 导入
*/
async function exportExcel(file) {
try {
const formData = new FormData()
formData.append('file', file.file)
const res = await importLogisticsZone(formData)
if (res.code === 200) {
ElMessage.success('导入成功!')
getList(searchForm.value)
}
} catch (error) {
ElMessage.error('导入失败!')
}
}
/**
* @description: table编辑关闭事件
*/
async function editClosed() {
console.log(1111111, editParams.value)
try {
if (Object.keys(editParams.value).length) {
await updateLogisticsZone(editParams.value)
ElMessage.success('修改成功!')
getList(searchForm.value)
}
} catch {
getList(searchForm.value)
} finally {
editParams.value = {}
}
}
</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;
}
</style>
export interface DeclarationRuleList {
countries: string
createTime: string
currency: string
defaulted: string
fixedValue: string
fixedWeight: string
id: number
name: string
orderPercent: number
remark: string
shops: string
type: number
valueUp: number
wayIds: string
wayNames: string
weightPercent: number
weightUp: number
}
export interface AddDeclarationRuleObj {
currency?: string
fixedValue?: string
fixedWeight?: string
id?: number
limitAmountType?: string
limitWeightType?: string
name?: string
orderPercent?: number | string | null
remark?: string
shops?: string
type?: number
valueUp?: number | string | null
weightPercent?: number | string | null
weightUp?: number | string | null
}
export interface LogisticsQuotation {
addressLine1?: string
addressLine2?: string
addressLine3?: string
city?: string
cityCode?: string
countryCode?: string
countryName?: string
createTime?: string
cspAccount?: string
district?: string
districtCode?: string
factoryId?: number
id?: number
phoneNumber?: string
postalCode?: string
rfcTaxId?: string
shipperName?: string
stateProvince?: string
stateProvinceAbbr?: string
swDefault?: boolean
updateTime?: string
}
...@@ -3,22 +3,26 @@ import AutoImport from 'unplugin-auto-import/vite' ...@@ -3,22 +3,26 @@ import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite' import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
base: '/', base: '/',
server: { server: {
port: 9803, port: 9527,
host: true, host: true,
proxy: { proxy: {
"/api": { '/api': {
target: "http://10.168.31.230:8060" target: 'http://10.168.1.132:8060',
}
}
}, },
},
},
plugins: [ plugins: [
vue(), vue(),
vueJsx(),
AutoImport({ AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
resolvers: [ElementPlusResolver()], resolvers: [ElementPlusResolver()],
}), }),
Components({ Components({
......
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