Commit 7af87fdd by wuqian
parents 97ef1db0 3fa77032
......@@ -12,7 +12,7 @@
"rules": {
"vue/require-default-prop": "off",
"vue/multi-word-component-names": "off",
"no-console": "warn"
"no-console": "off","no-unused-vars": "off",
},
"overrides": [{ "files": "*.vue", "rules": { "no-undef": "off" } }]
}
......@@ -22,4 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.eslintrc.*
!/src/views/warehouse/manage.vue
......@@ -5,7 +5,88 @@
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const ElLoading: typeof import('element-plus/es')['ElLoading']
const ElMessage: typeof import('element-plus/es')['ElMessage']
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 {}
declare module 'vue' {
export interface GlobalComponents {
AmountInput: typeof import('./src/components/Form.vue/AmountInput.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']
ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
......@@ -28,6 +31,7 @@ declare module 'vue' {
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
......@@ -46,6 +50,7 @@ declare module 'vue' {
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElUpload: typeof import('element-plus/es')['ElUpload']
Icon: typeof import('./src/components/Icon.vue')['default']
ImageView: typeof import('./src/components/ImageView.vue')['default']
LogList: typeof import('./src/components/LogList.vue')['default']
......@@ -53,8 +58,10 @@ declare module 'vue' {
RenderColumn: typeof import('./src/components/RenderColumn.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Select: typeof import('./src/components/Form.vue/Select.vue')['default']
ShipmentOrderDetail: typeof import('./src/components/ShipmentOrderDetail.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']
TableView: typeof import('./src/components/TableView.vue')['default']
UploadExcel: typeof import('./src/components/UploadExcel.vue')['default']
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -24,6 +24,7 @@
"vue-dompurify-html": "^5.1.0",
"vue-router": "^4.3.0",
"vue-tsc": "^2.1.10",
"vxe-table": "^4.13.31",
"xlsx": "^0.18.5"
},
"devDependencies": {
......@@ -31,7 +32,7 @@
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"@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",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
......
import { BasePaginationData, BaseRespData } from '@/types/api'
import axios from './axios'
import {
LogisticsMethod,
LogisticsMethodList,
UpdateLogisticsMethodStatus,
} from '@/views/logistics/types/logistics'
import { AddDeclarationRuleObj } from '@/views/logistics/types/declarationRule'
import { LogisticsQuotation } from '@/views/logistics/types/logisticsQuotation'
import { LogisticsPartitionObj } from '@/views/logistics/types/logisticsPartition'
import { ShippingAddressObj } from '@/views/logistics/types/shippingAddress'
export interface ILogisticsList {
code: string
basicsName: string
apiData: unknown
}
export interface IForm {
name: string
code: string
id?: number
contact: string
phone: string
siteUrl: string
address: string
apiData: {
[key: string]: unknown
}
}
interface Ikey {
[key: string]: unknown
}
export interface ILogisticsCompanyData {
pageSize: number
currentPage: number
total?: number
name?: string
code?: string
}
export interface ILogisticsCompany {
id: number // bigint(20)
name: string | null // varchar(255)
code: string // varchar(30)
contact: string | null // varchar(255)
phone: string | null // varchar(100)
apiJson: string | null // varchar(600)
mobile: string | null // varchar(60)
carrier: string | null // varchar(6)
address: string | null // varchar(255)
siteUrl: string | null // varchar(255)
apiKey: string | null // varchar(256)
apiToken: string | null // varchar(256)
clientId: string | null // varchar(256)
accessToken: string | null // varchar(256)
accessTokenExpiresIn: number | null // int(11)
refreshToken: string | null // varchar(256)
refreshDate: string | null // datetime
refreshTokenExpiresIn: number | null // int(11)
authCode: string | null // varchar(500)
redirectUri: string | null // varchar(256)
createTime: string | null // timestamp
vat: string | null // varchar(60)
ioss: string | null // varchar(60)
basicType: number // int(11)
}
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')
}
//获取物流公司列表
export function getLogisticsCompanyList() {
return axios.get<never, BaseRespData<never>>('/logisticsCompany/all_options')
}
//获取物流公司列表
export function getUniuniList() {
return axios.get<never, BaseRespData<never>>(
'/logistics/logisticsUinuinWarehouse/listByCountryCode',
{
params: {
countryCode: 'US',
},
},
)
}
/**
* @description 发货地址
*/
//列表
export function getAddressByIdList(params: {
pageSize?: number
currentPage?: number
}) {
return axios.get<never, BaseRespData<never>>('/logisticsAddress/list_page', {
params,
})
}
//根据id查询地址
export function getAddressById(params: { id: number | string }) {
return axios.get<never, BaseRespData<never>>(
'/logisticsAddress/getAddressById',
{
params,
},
)
}
//新增
export function addAddress(params: ShippingAddressObj) {
return axios.post<never, BaseRespData<never>>(
'/logisticsAddress/addAddress',
params,
)
}
//修改
export function updateAddress(params: ShippingAddressObj) {
return axios.post<never, BaseRespData<never>>(
'/logisticsAddress/updateAddress',
params,
)
}
//删除
export function deleteAddressByIds(params: { ids: string }) {
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: {
logisticsIdList?: number[]
pageSize: number
currentPage: number
}) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/list_page',
params,
)
}
export function getlogisticsQuotationPage(params: {
logisticsIdList?: number[]
pageSize: number
currentPage: number
}) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/page',
params,
)
}
//新增
export function addLogisticsQuotation(params: LogisticsQuotation) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/add',
params,
)
}
//修改
export function updateLogisticsQuotation(params: LogisticsQuotation) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/update',
params,
)
}
//删除
export function deleteLogisticsQuotation(params: { ids: string }) {
return axios.get<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/delete',
{
params,
},
)
}
//导入
export function importLogisticsQuotation(formData: FormData) {
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: { id: number | string }) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/get',
params,
)
}
/**
* @description 申报规则
*/
//列表
export function getLogisticsCustomsRuleList(params: {
name?: string
pageSize: number
currentPage: number
}) {
return axios.post<never, BaseRespData<never>>(
'/logisticsCustomsRule/list_page',
params,
)
}
//新增
export function addLogisticsCustomsRule(params: AddDeclarationRuleObj) {
return axios.post<never, BaseRespData<never>>(
'/logisticsCustomsRule/add',
params,
)
}
//修改
export function updateLogisticsCustomsRule(params: AddDeclarationRuleObj) {
return axios.post<never, BaseRespData<never>>(
'/logisticsCustomsRule/update',
params,
)
}
//删除
export function deleteLogisticsCustomsRule(params: { ids: string }) {
return axios.get<never, BaseRespData<never>>('/logisticsCustomsRule/delete', {
params,
})
}
/**
* @description 物流分区
*/
//列表
export function getLogisticsZoneList(params: {
logisticsIdList?: string[] | string | number[] | number
codePrefix?: string
}) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsZone/list',
params,
)
}
//新增
export function addLogisticsZone(params: LogisticsPartitionObj) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsZone/add',
params,
)
}
//修改
export function updateLogisticsZone(params: LogisticsPartitionObj) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsZone/update',
params,
)
}
//删除
export function deleteLogisticsZone(params: {
logisticsList: string[]
zoneNameList: string[]
}) {
return axios.post<never, BaseRespData<never>>(
'/logistics/logisticsZone/delete',
params,
)
}
//物流分区导入
export function importLogisticsZone(formData: FormData) {
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: {
code?: string
weight?: string
}) {
return axios.get<never, BaseRespData<never>>(
'/logistics/logisticsQuotation/logisticsTrialCalculation',
{
params,
},
)
}
export function logisticsCompany(data: ILogisticsCompanyData) {
return axios.get<never, BasePaginationData<ILogisticsCompany>>(
'logisticsCompany/list_page',
{ params: data },
)
}
export function logisticsCompanyAdd(data: IForm) {
return axios.post<never, BaseRespData<never>>('/logisticsCompany/add', data)
}
export function logisticsCompanyUpdate(data: IForm) {
return axios.post<never, BaseRespData<never>>(
'/logisticsCompany/update',
data,
)
}
export function logisticsCompanyDelete(ids: string | number[]) {
return axios.get<never, BaseRespData<never>>('/logisticsCompany/delete', {
params: { ids },
})
}
export function logisticsCompanyAllCodelist() {
return axios.get<never, BaseRespData<ILogisticsList[]>>(
'/logisticsCompany/allCodelist',
)
}
......@@ -11,11 +11,11 @@ import {
import axios from './axios'
import { PodMakeOrderData } from '@/types/api/podMakeOrder'
export interface LogisticsData {
logisticsWayName: string; // 物流名称
warehouseName: string; // 发货仓库
status: boolean;
logisticsWayCode: string; // 物流编码
partition: string; // 所在分区
logisticsWayName: string // 物流名称
warehouseName: string // 发货仓库
status: boolean
logisticsWayCode: string // 物流编码
partition: string // 所在分区
}
export function getOrderTabData() {
return axios.get<never, BaseRespData<Tab[]>>(
......@@ -50,7 +50,12 @@ export function getCardOrderList(
},
)
}
export function confirmOrderApi(data: number[], productionClient: string,type:string,logisticsTrialCalculation?:LogisticsData) {
export function confirmOrderApi(
data: number[],
productionClient: string,
type: string,
logisticsTrialCalculation?: LogisticsData | null,
) {
return axios.post<never, BaseRespData<never>>(
'factory/podJomallOrderUs/confirmOrders',
{
......@@ -128,27 +133,19 @@ export function printProductionOrderApi(orderIds: number[]) {
orderIds,
)
}
export function printPrintOrderApi(
orderIds: number[],
productionClient: string,
) {
export function printPrintOrderApi(orderIds: number[]) {
return axios.post<never, BaseRespData<string>>(
'factory/podJomallOrderUs/printPickPdf',
{
ids: orderIds.join(','),
productionClient,
ids: orderIds.join(',')
},
)
}
export function printPickingOrderApi(
orderIds: number[],
productionClient: string,
) {
export function printPickingOrderApi(orderIds: number[]) {
return axios.post<never, BaseRespData<string>>(
'factory/podJomallOrderUs/pickingComplete',
{
ids: orderIds.join(','),
productionClient,
},
)
}
......@@ -238,7 +235,7 @@ export function updateRemarkApi(id: number, content: string) {
export function getLogisticsCalculation(id: number) {
return axios.get<never, BaseRespData<never>>(
'factory/podJomallOrderUs/getLogisticsCalculation',
{ params:{id} },
{ params: { id } },
)
}
export function loadWarehouseListApi() {
......@@ -246,3 +243,9 @@ export function loadWarehouseListApi() {
'factoryWarehouseInfo/getAll',
)
}
export function refreshMaterialApi(orderIds: string) {
return axios.post<never, BaseRespData<never>>(
'factory/podJomallOrderProductUs/refreshDesignImages ',
{ orderIds },
)
}
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'
type SimpleFormData = Record<string, unknown>
// 定义表单项配置接口
export interface IFormConfig {
fixed?: string
title?: string
prop?: string
label?: string
type?: string
btn?: JSX.Element | (() => JSX.Element)
attrs?: SimpleFormData
isIncludeProp?: boolean
rules?: FormItemRule | FormItemRule[]
render?: (
item?: IFormConfig,
formData?: SimpleFormData,
index?: number,
) => VNode | VNode[] | 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,
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 = shallowRef<IFormConfig[]>([])
watch(
() => props.config,
(val) => {
tableConfig.value = val
},
{ immediate: true, deep: true },
)
// 监听表单数据变化
watch(
() => formData.value,
(val) => {
emit('update:modelValue', val)
},
{ deep: true },
)
// 监听外部数据变化
watch(
() => props.modelValue,
(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 (clearFields?: string[]) => {
if (!formRef.value) return false
return await formRef.value.clearValidate(clearFields)
}
// 重置表单
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.filter((item) => !item.isIncludeProp),
]
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)}
</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 as keyof typeof componentConfig
] || ElInput,
{
modelValue: this.formData[item.prop as string],
'onUpdate:modelValue': (value: unknown) => {
this.formData[item.prop as string] = value
},
disabled:
typeof item.attrs?.disabled === 'function'
? item.attrs.disabled()
: item.attrs?.disabled,
...this.getComponentAttrs(item),
},
)}
{item.btn &&
(typeof item.btn === 'function' ? 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: undefined,
},
)
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: string) => {
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: string, offset: number) => {
console.log(67, match)
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?: unknown
isrowDate?: boolean
newDisableDate?: (...arg: unknown[]) => boolean
}>(),
{
placeholder: '请选择日期',
type: 'date',
isNeedTime: false,
isrowDate: false,
rowData: undefined,
newDisableDate: undefined,
},
)
// 用于存储动态的禁用日期逻辑
function disabledDate(time: Date) {
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()
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 } from 'lodash-es'
interface IOption {
[key: string]: unknown
}
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,
isValueKey: false,
},
)
const emits = defineEmits<{
(e: 'change', option: IOption, value: string | number): void
(
e: 'update:modelValue',
data: string | number | string[] | number[] | undefined,
): void
(e: 'updatedOption'): void
}>()
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: string | number) => {
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 as string | number)
}
})
} else {
watch(
() => props.modelValue,
(val) => {
// if (val !== 0) {
model.value = val
// }
},
)
}
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?: unknown
}>(),
{
activeColor: '#13ce66',
inactiveColor: ' #ff4949',
modelValue: undefined,
},
)
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): void
}>()
</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, 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'
import { VxeTableInstance } from 'vxe-table'
interface ColumnAttrs {
field?: string
title?: string
width?: string | number
[key: string]: unknown
}
export interface TableColumn {
prop: string
label: string
attrs?: ColumnAttrs
render?: {
edit?: (params: { row: TableRowData }) => VNode | VNode[] | JSX.Element
default?: (params: { row: TableRowData }) => VNode | VNode[] | JSX.Element
[key: string]:
| ((params: { row: TableRowData }) => VNode | VNode[] | JSX.Element)
| undefined
}
}
interface TableRowData {
[key: string]: unknown
}
type SlotFunction = (scope: {
row: TableRowData
}) => VNode | 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<VxeTableInstance | null>(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) {
$table.setCurrentRow(row)
}
}
onMounted(() => {
getList()
})
return {
tableRef,
tableData,
tableColumns,
editConfig,
getSelectEvent,
selectRowEvent,
attrs,
}
},
render() {
return (
<vxe-table
ref={(el: VxeTableInstance) => (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}
>
{{
...(() => {
// 创建基础插槽配置
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>
)
},
})
# CustomizeForm(待优化)
## CustomizeForm 属性 (Props)
| 参数 | 说明 | 类型 | 默认值 | 必填 |
| :-----------: | :------------------------------------------: | :---------------------------: | :-------: | :--: |
| config | 表单配置项 | IFormConfig[] | [] | ✓ |
| modelValue | 表单数据对象 | Record<string, unknown> | {} | ✕ |
| labelWidth | 表单标签宽度 | string \|number | 100 | ✕ |
| formItemWidth | 表单项宽度 | string \|number | 50% | ✕ |
| size | 组件尺寸 | 'large'\| 'default'\| 'small' | 'default' | ✕ |
| inline | 是否行内布局 | boolean | false | ✕ |
| labelPosition | 标签位置 | 'left'\| 'right'\| 'top' | 'right' | ✕ |
| ... | 其余属性与 element-plus 文档中 Form API 相同 | ... | ... | ... |
- **CustomizeForm 说明**:
- CustomizeForm 属性 (Props) 可自行添加,添加属性与 element-plus 文档中 form Props 相同
- 可参考文件 src\views\logistics\logisticsMethod.vue
---
### IFormConfig 配置项
| 属性 | 说明 | 类型 | 默认值 |
| :-----------: | :------------------------------------------------------: | :-----------------------------------: | :----: |
| prop | 表单字段名 | string | - |
| label | 表单标签 | string | - |
| title | 分组标题 | string | - |
| type | 控件类型 | string | - |
| attrs | 控件属性配置 | SimpleFormData | - |
| rules | 验证规则 | FormItemRule\| FormItemRule[] | - |
| render | 自定义渲染函数 | () => VNode \| VNode[] \| JSX.Element | - |
| btn | 操作按钮 | JSX.Element \|(() => JSX.Element) | - |
| isIncludeProp | 是否包含在 prop 属性中, 需搭配 refashConfig()方法使用 | boolean | - |
| fixed | 固定位置 | string | - |
- attrs 中 element-plus 文档中 FormItem API 相同
- 可参考文件 src\views\logistics\declarationRule.vue
---
### 控件类型 (SimpleFormData)
| 类型值 | 对应组件 | 说明 |
| :-------------: | :-------------: | :------------: |
| input | ElInput | 普通输入框 |
| amountInput | AmountInput | 金额输入框 |
| dateRangePicker | DateRangePicker | 日期范围选择器 |
| datePicker | DatePicker | 日期选择器 |
| select | Select | 下拉选择框 |
| switch | Switch | 开关组件 |
- form 表格如需动态渲染则需要传入计算属性 config
```tsx **config 示例**
const editForm = ref({})
const config = [
//含有title属性的对象独占一行,目前最好只做标题使用,也可以用render自定义内容但是可能有bug待优化
{
title: '个人信息',
render: () => VNode,
},
{
prop: 'name',
label: '姓名',
type: 'input',
attrs: {
placeholder: '请输入姓名',
clearable: true,
},
rules: { required: true, message: '姓名不能为空' },
},
{
prop: 'birthday',
label: '出生日期',
type: 'datePicker',
attrs: {
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
placeholder: '选择出生日期',
},
},
{
prop: 'gender',
label: '性别',
type: 'select',
attrs: {
options: [
{ label: '男', value: 1 },
{ label: '女', value: 2 },
{ label: '其他', value: 3 },
],
},
},
{
title: '联系信息',
},
{
prop: 'salary',
label: '期望薪资',
type: 'amountInput',
attrs: {
min: 0,
max: 100000,
precision: 0,
step: 1000,
controlsPosition: 'right',
//appendRender: amountInput组件插槽用法,需注意不可以直接使用v-model绑定
appendRender: () => {
return h(
ElSelect,
{
modelValue: editForm.value['unit'],
'onUpdate:modelValue': (val) => (editForm.value['unit'] = val),
disabled: editForm.value['id'] ? true : false,
placeholder: 'Select',
style: { width: '115px' },
},
() => [
h(ElOption, { label: 'oz', value: 'oz' }),
h(ElOption, { label: 'LB', value: 'LB' }),
],
)
},
},
},
{
prop: 'agree',
label: '用户协议',
type: 'switch',
},
]
```
---
## 事件 (Events)
| 事件名 | 说明 | 回调参数 |
| :---------------: | :----------: | :------------------: |
| update:modelValue | 表单数据更新 | 更新后的表单数据对象 |
---
## 可用方法
| 方法名 | 说明 | 参数 |
| :-----------: | :--------------------------------------------: | :-------------------: |
| validate | 表单验证 | 无 |
| clearValidate | 清除验证信息 | 无 |
| resetFields | 重置表单字段 | 无 |
| refashConfig | 刷新表单配置,需要搭配 isIncludeProp 字段 使用 | showFields?: string[] |
- **说明**: 可新增方法自行添加
- showFields 字段是一个由 config 中 设置为 isIncludeProp:true 的对象值组成的数组。例如['prop1', 'prop2']
```vue
<template>
<CustomizeForm ref="formRef" ... />
</template>
<script setup>
import { ref } from 'vue'
const formRef = ref()
// 验证表单
const validateForm = async () => {
try {
await formRef.value.validate()
// 验证通过逻辑
} catch (error) {
// 验证失败处理
}
}
// 重置表单
const resetForm = () => {
formRef.value.resetFields()
}
// 刷新配置
const refreshFormConfig = () => {
formRef.value.refashConfig(['prop1', 'prop2'])
}
</script>
```
# SearchForm(待优化)
## SearchForm 属性 (Props)
| 参数 | 说明 | 类型 | 默认值 | 必填 |
| :---------------: | :--------------------------------------------: | :---------------------------: | :-------: | :--: |
| config | 表单配置项 | ISeachFormConfig[] | [] | ✓ |
| modelValue | 表单数据对象 | ISearchForm | {} | ✕ |
| labelWidth | 表单标签宽度 | string | number | ✕ |
| size | 组件尺寸 | 'large'\| 'default' \|'small' | 'default' | ✕ |
| addPermissionName | 新增按钮权限名(目前没有作用待优化) | string \|string[] | '' | ✕ |
| isSearchBtn | 是否显示查询按钮 | boolean | true | ✕ |
| isResetBtn | 是否显示重置按钮 | boolean | true | ✕ |
| isAddBtn | 是否显示新增按钮 | boolean | true | ✕ |
| isDeleteBtn | 是否显示删除按钮 | boolean | true | ✕ |
| ... | 其余属性与 element-plus 文档中 form Props 相同 | ... | ... | ... |
- **Form 表格说明**:
- form 属性 (Props) 可自行添加
- 可参考文件 src\views\logistics\logisticsMethod.vue
---
### ISeachFormConfig 配置项
| 属性 | 说明 | 类型 | 默认值 |
| :----: | :------------: | :---------------------: | :----: |
| type | 控件类型 | EComponenetType | - |
| prop | 表单字段名 | string | - |
| label | 表单标签 | string | - |
| attrs | 控件属性配置 | Record<string, unknown> | - |
| render | 自定义渲染函数 | () => VNode | - |
- attrs 中 element-plus 文档中 FormItem API 相同
---
### 控件类型 (EComponenetType)
| 类型值 | 对应组件 | 说明 | 数据类型 |
| :-------------: | :-------------: | :------------: | :-------: |
| amountInput | AmountInput | 金额输入框 | string |
| input | ElInput | 普通输入框 | string |
| dateRangePicker | DateRangePicker | 日期范围选择器 | array |
| datePicker | DatePicker | 日期选择器 | string |
| select | Select | 下拉选择框 | undefined |
- form 表格如需动态渲染则需要传入计算属性 config
```tsx **config 示例**
const config = [
// 普通输入框
{
type: 'input',
prop: 'orderId',
label: '订单号',
attrs: {
placeholder: '请输入订单号',
clearable: true
}
},
// 下拉选择框
{
type: 'select',
prop: 'payStatus',
label: '支付状态',
attrs: {
options: [
{ label: '全部', value: '' },
{ label: '已支付', value: 1 },
{ label: '未支付', value: 0 }
],
style: 'width: 180px'
}
},
// 金额输入框
{
type: 'amountInput',
prop: 'amountRange',
label: '金额区间',
attrs: {
min: 0,
max: 10000,
precision: 2,
placeholder: '请输入金额'
}
},
// 日期范围选择
{
type: 'dateRangePicker',
prop: 'createTime',
label: '创建时间',
attrs: {
startPlaceholder: '开始日期',
endPlaceholder: '结束日期',
style: 'width: 320px'
}
},
// 自定义渲染
{
prop: 'customRender',
label: '自定义组件',
render: () => (
<div class="custom-render">
<el-switch active-text="开" inactive-text="关" />
<el-input-number :min="1" :max="10" />
</div>
)
}
]
```
---
## 事件 (Events)
| 事件名 | 说明 | 回调参数 |
| :---------------: | :----------: | :------------------: |
| update:modelValue | 表单数据更新 | 更新后的表单数据对象 |
| search | 点击查询按钮 | 无 |
| reset | 点击重置按钮 | 无 |
| add | 点击新增按钮 | 无 |
| delete | 点击删除按钮 | 无 |
---
## 插槽 (Slots)
| 插槽名 | 说明 | 作用域参数 |
| :------------: | :--------------------------: | :--------: |
| header-content | 替换整个表单头部内容 | - |
| ontherBtn | 在操作按钮区域添加额外的按钮 | - |
```vue **插槽示例**
<SearchForm :config="formConfig">
<template #header-content>
<div class="custom-header">
<h3>高级搜索面板</h3>
<p>使用以下条件进行精确搜索</p>
</div>
</template>
<template #ontherBtn>
<el-button type="warning" @click="exportData">导出数据</el-button>
</template>
</SearchForm>
```
---
## 可用方法
| 方法名 | 说明 | 参数 |
| :------------: | :------------: | :------: |
| getSelectEvent | 获取选中行数据 | 无 |
| selectRowEvent | 设置高亮行 | row: any |
- **可用方法说明**: 可自行添加
# vxeTable(待优化)
## Table 属性 (Props)
| 参数 | 说明 | 类型 | 默认值 |
| :-------------: | :---------------------------------------: | :-----------: | :----: |
| config | 表格列配置 | TableColumn[] | [] |
| tableEditConfig | 表格编辑配置 | Object | {} |
| modelValue | 表格数据 | Object[] | [] |
| getTablelist | 数据加载方法 | Function | null |
| isShowCheckBox | 是否显示复选框 | Boolean | true |
| ... | 其余属性与 vxetable 文档中 api Props 相同 | ... | ... |
- **Table 表格说明**:
- table 表格如需动态渲染则需要传入计算属性 config
- Table 属性 (Props) 可自行添加,添加属性与 vxetable 文档中 api Props 相同
- 可参考文件 src\views\logistics\logisticsMethod.vue
```tsx **config 示例**
const config = [
{
prop: 'name',
label: '姓名',
attrs: {
width: 120,
align: 'center',
fixed: 'left',
resizable: false,
className: 'status-column',
},
},
{
prop: 'status',
label: '状态',
render: {
default: ({ row }) => (
<Tag color={row.status === 1 ? 'success' : 'error'}>
{row.status === 1 ? '正常' : '禁用'}
</Tag>
),
edit: ({ row }) => (
<Select v-model={row.status}>
<Option value={1}>正常</Option>
<Option value={0}>禁用</Option>
</Select>
),
},
},
]
```
---
## TableColumn 结构
| 参数 | 说明 | 类型 |
| :----: | :------------: | :-------------: |
| prop | 列字段名 | string |
| label | 列标题 | string |
| attrs | 额外属性 | ColumnAttrs() |
| render | 自定义渲染函数 | RenderFunctions |
- **TableColumn 表格说明**:
- attrs 中属性与 vxetable 文档中 Column Props 相同
- RenderFunctions 默认使用 default,如要开启单元格编辑 edit 需在 Table 中传入 tableEditConfig.enabled=true
```ts
interface ColumnAttrs {
width?: string | number
[key: string]: unknown
}
interface RenderFunctions {
// 默认单元格渲染
default?: (params: { row: TableRowData }) => VNode | VNode[] | JSX.Element
// 编辑状态渲染
edit?: (params: { row: TableRowData }) => VNode | VNode[] | JSX.Element
// 其他自定义渲染
[key: string]:
| ((params: { row: TableRowData }) => VNode | VNode[] | JSX.Element)
| undefined
}
```
---
## 事件 (Events)
| 事件名 | 说明 | 回调参数 |
| :----------------: | :------------: | :--------------------------: |
| update:modelValue | 表格数据变化 | 表格数据数组 |
| checkbox-change | 复选框选择变化 | 当前选中行数据 |
| getCheckboxRecords | 获取选中行数据 | 当前选中行数据(含全选状态) |
---
## 可用方法
| 方法名 | 说明 | 参数 |
| :------------: | :------------: | :------: |
| getSelectEvent | 获取选中行数据 | 无 |
| selectRowEvent | 设置高亮行 | row: any |
- **说明**: 可新增方法自行添加
......@@ -4,7 +4,16 @@ import App from './App.vue'
import router from './router'
import store from './store'
import './styles/index.scss'
import VxeUITable from 'vxe-table'
import 'vxe-table/lib/style.css'
// 确保在渲染用户提供的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,54 @@ const router = createRouter({
component: TypeseetingManagement,
},
{
path: '/logistics/logisticsMethod',
meta: {
title: '物流方式',
},
component: () => import('@/views/logistics/logisticsMethod.vue'),
},{
path: '/logistics/logisticsCompany',
meta: {
title: '物流公司',
},
component: () => import('@/views/logistics/logisticsCompany.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: '仓库管理',
......@@ -147,7 +195,86 @@ const router = createRouter({
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',
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: '库位管理',
......
......@@ -17,6 +17,48 @@ const menu: MenuItem[] = [
// label: '商品',
// },
{
index: '4',
id: 7,
label: '物流',
children: [
{
index: '/logistics/logisticsCompany',
id: 11,
label: '物流公司',
},
{
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: '13',
id: 13,
label: '库存',
......@@ -49,6 +91,7 @@ const menu: MenuItem[] = [
},
],
},
{
index: '1',
id: 2,
......@@ -124,6 +167,7 @@ const menu: MenuItem[] = [
},
],
},
// {
// index: '',
// id: 3,
......
......@@ -2,6 +2,7 @@ export interface BaseRespData<D> {
code: number
message?: string
data: D
total?: number
}
export interface PaginationData<D> {
......@@ -12,6 +13,7 @@ export interface PaginationData<D> {
size: number
current: number
records: D[]
data?: D[]
}
export interface Statistics<D> {
sumNotPassNum: number
......
......@@ -79,6 +79,7 @@ export interface ProductList {
passNum?: number
notPassNum?: number
payAmount?: number
status?: number
weight?: number | null
diyId?: string
endProductId?: string
......
/*
* @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
}
/**
* @description 判读是否为外链
* @param path
* @returns {boolean}
*/
export function isExternal(path: string) {
return /^(https?:|mailto:|tel:|\/\/)/.test(path)
}
/**
* @description 判断是否为数字
* @param value
* @returns {boolean}
*/
export function isNumber(value: string) {
const reg = /^[0-9]+(\.[0-9]+)?$/
return reg.test(value)
}
/**
* @description 判断是否是名称
* @param value
* @returns {boolean}
*/
export function isName(value: string) {
const reg = /^[\u4e00-\u9fa5a-zA-Z0-9]+$/
return reg.test(value)
}
/**
* @description 判断是否为IP
* @param ip
* @returns {boolean}
*/
export function isIP(ip: string) {
const reg =
/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
return reg.test(ip)
}
/**
* @description 判断是否是传统网站
* @param url
* @returns {boolean}
*/
export function isUrl(url: string) {
const reg =
/^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return reg.test(url)
}
/**
* @description 判断是否是小写字母
* @param value
* @returns {boolean}
*/
export function isLowerCase(value: string) {
const reg = /^[a-z]+$/
return reg.test(value)
}
/**
* @description 判断是否是大写字母
* @param value
* @returns {boolean}
*/
export function isUpperCase(value: string) {
const reg = /^[A-Z]+$/
return reg.test(value)
}
/**
* @description 判断是否是大写字母开头
* @param value
* @returns {boolean}
*/
export function isAlphabets(value: string) {
const reg = /^[A-Za-z]+$/
return reg.test(value)
}
/**
* @description 判断是否是字符串
* @param value
* @returns {boolean}
*/
export function isString(value: unknown) {
return typeof value === 'string' || value instanceof String
}
/**
* @description 判断是否是数组
* @param arg
*/
export function isArray(arg: string | (string | number)[]) {
if (typeof Array.isArray === 'undefined') {
return Object.prototype.toString.call(arg) === '[object Array]'
}
return Array.isArray(arg)
}
/**
* @description 判断是否是对象
* @param arg
*/
export function isObject(arg: unknown) {
return Object.prototype.toString.call(arg) === '[object Object]'
}
/**
* @description 判断是否是端口号
* @param value
* @returns {boolean}
*/
export function isPort(value: string) {
const reg =
/^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/
return reg.test(value)
}
/**
* @description 判断是否是手机号
* @param value
* @returns {boolean}
*/
export function isPhone(value: string) {
const reg = /^1[3-9]\d{9}$/
return reg.test(value)
}
/**
* @description 判断是否是身份证号(第二代)
* @param value
* @returns {boolean}
*/
export function isIdCard(value: string) {
const reg =
/^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
return reg.test(value)
}
/**
* @description 判断是否是邮箱
* @param value
* @returns {boolean}
*/
export function isEmail(value: string) {
const reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/
return reg.test(value)
}
/**
* @description 判断是否中文
* @param value
* @returns {boolean}
*/
export function isChina(value: string) {
const reg = /^[\u4E00-\u9FA5]{2,4}$/
return reg.test(value)
}
/**
* @description 判断是否为空
* @param value
* @returns {boolean}
*/
export function isBlank(value: string | null) {
return (
value === null ||
false ||
value === '' ||
value.trim() === '' ||
value.toLocaleLowerCase().trim() === 'null'
)
}
/**
* @description 判断是否为固话
* @param value
* @returns {boolean}
*/
export function isTel(value: string) {
const reg =
/^(400|800)([0-9\\-]{7,10})|(([0-9]{4}|[0-9]{3})([- ])?)?([0-9]{7,8})(([- 转])*([0-9]{1,4}))?$/
return reg.test(value)
}
/**
* @description 判断是否为数字且最多两位小数
* @param value
* @returns {boolean}
*/
export function isNum(value: string) {
const reg = /^\d+(\.\d{1,2})?$/
return reg.test(value)
}
/**
* @description 判断是否为数字且最多两位小数
* @param value
* @returns {boolean}
*/
export function isNumFloat(value: string) {
const reg = /^(0|([1-9][0-9]*))(\.[\d]+)?$/
return reg.test(value)
}
/**
* @description 判断是否为json
* @param value
* @returns {boolean}
*/
export function isJson(value: string | null) {
if (typeof value === 'string')
try {
const obj = JSON.parse(value)
return !!(typeof obj === 'object' && obj)
} catch (e) {
return false
}
return false
}
const decimal4Regex = /^\d+(\.\d{1,4})?$/ // 4位小数
const decimal2Regex = /^\d+(\.\d{1,2})?$/ // 2位小数
/**
* @description 判断是否超过小数点4位
* @param value
* @returns {boolean}
*/
export function isDecimal4(value: string) {
return decimal4Regex.test(value)
}
/**
* @description 判断是否超过小数点2位
* @param value
* @returns {boolean}
*/
export function isDecimal2(value: string) {
return decimal2Regex.test(value)
}
import { defineComponent, ref } from 'vue'
import { ElDialog } from 'element-plus'
export default defineComponent({
name: 'CustomizeForm',
props: {
modelValue: {
type: Boolean,
default: false,
},
title: {
type: String,
default: '',
},
dialogWidth: {
type: String,
default: '600px',
},
},
emits: ['update:modelValue', 'close'],
setup(props, { emit, attrs, slots }) {
const formRef = ref<InstanceType<typeof ElDialog> | null>(null)
const isShow = ref(false)
watch(
() => props.modelValue,
(val) => {
isShow.value = val
},
{ immediate: true },
)
return () => {
return (
<ElDialog
ref={formRef}
v-model={isShow.value}
title={props.title}
width={props.dialogWidth}
onClose={() => {
emit('close')
}}
destroy-on-close={true}
close-on-click-modal={false}
{...attrs}
>
<div class="dialog-form">
{slots.default?.()}
{slots.footer?.()}
</div>
</ElDialog>
)
}
},
})
<template>
<div class="user-page flex-column card h-100 overflow-hidden">
<div class="header-filter-form">
<SearchForm
:config="searchConfig"
@search="search"
@add="addDialog"
@delete="deleteFn"
v-model="searchForm"
></SearchForm>
</div>
<div class="user-content flex-1 flex-column overflow-hidden">
<div class="user-list flex-1 overflow-hidden" v-loading="loading">
<CustomizeTable
v-model="tableData"
:config="tableConfig"
@getCheckboxRecords="handleCheckboxRecords"
></CustomizeTable>
</div>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[100, 200, 300, 400, 500]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin: 10px auto 0; text-align: right"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></ElPagination>
</div>
</div>
<LogDialog
:title="editForm['id'] ? '编辑申报规则' : '新增申报规则'"
dialogWidth="1000px"
v-model="dialogVisible"
@close="cancelFn"
>
<CustomizeForm
ref="editFormRef"
v-model="editForm"
:config="formConfig"
formItemWidth="100%"
:labelWidth="120"
>
</CustomizeForm>
<template #footer>
<div style="text-align: center">
<ElButton @click="cancelFn">取消</ElButton>
<ElButton type="primary" @click="save">保存</ElButton>
</div>
</template>
</LogDialog>
<LogDialog
title="操作日志"
v-model="logDialogVisible"
@close="logDialogVisible = false"
>
<div v-for="item in logList" :key="item.id" style="margin-bottom: 8px">
<span style="margin-right: 10px">{{ item.createTime }}</span>
<span>{{ item.description }}</span>
</div>
</LogDialog>
</template>
<script setup lang="tsx">
defineOptions({
name: 'DeclarationRule',
})
import {
getLogisticsCustomsRuleList,
addLogisticsCustomsRule,
updateLogisticsCustomsRule,
deleteLogisticsCustomsRule,
getLogisticsLog,
} from '@/api/logistics'
import SearchForm from '@/components/SearchForm.tsx'
import LogDialog from './components/LogDialog.tsx'
import CustomizeForm from '@/components/CustomizeForm.tsx'
import CustomizeTable from '@/components/VxeTable.tsx'
import { IFormConfig } from '@/components/CustomizeForm.tsx'
import usePageList from '@/utils/hooks/usePageList'
import { useValue } from './hooks/useValue'
import { showConfirm } from '@/utils/ui'
import { debounce } from 'lodash-es'
import { AddDeclarationRuleObj } from './types/declarationRule'
import { Edit, Delete, List } from '@element-plus/icons-vue'
import { ISeachFormConfig } from '@/types/searchType'
const [searchForm] = useValue({})
const [editForm, resetEditForm] = useValue<AddDeclarationRuleObj>({
type: 1,
currency: 'USD',
})
const {
currentPage,
pageSize,
total,
data: tableData,
refresh: search,
onCurrentPageChange: handleCurrentChange,
onPageSizeChange: handleSizeChange,
} = usePageList({
query: (page, pageSize) =>
getLogisticsCustomsRuleList({
...searchForm.value,
pageSize: pageSize,
currentPage: page,
}).then(({ data }) => {
console.log(130, data)
return data
}),
})
const dialogVisible = ref(false)
const logDialogVisible = ref(false)
const editFormRef = ref<InstanceType<typeof CustomizeForm> | null>(null)
const selection = ref([])
const searchConfig = ref<ISeachFormConfig[]>([
{
prop: 'name',
type: 'input',
label: '规则名称',
attrs: {
clearable: true,
placeholder: '请输入规则名称',
},
},
])
interface IOption {
[key: string]: unknown
}
const mapData = ref(
new Map<number, string[]>([
[1, ['fixedValue', 'fixedWeight']],
[2, ['orderPercent', 'valueUp', 'weightPercent', 'weightUp']],
]),
)
const formConfig = ref<IFormConfig[]>([
{
prop: 'name',
type: 'input',
label: '规则名称',
attrs: {
placeholder: '请输入规则名称',
},
rules: [
{
required: true,
message: '请输入规则名称',
},
],
},
{
prop: 'currency',
type: 'select',
label: '申报币种',
attrs: {
placeholder: '请选择申报币种',
options: [{ label: 'USD', value: 'USD' }],
},
rules: [
{
required: true,
message: '请选择申报币种',
},
],
},
{
prop: 'type',
type: 'select',
label: '申报类型',
attrs: {
placeholder: '请选择申报类型',
options: [
{ label: '固定', value: 1 },
{ label: '比例', value: 2 },
],
onChange: (item: IOption, value: number) => {
console.log(185, item)
if (value === 2) {
editForm.value.fixedValue = ''
editForm.value.fixedWeight = ''
editFormRef.value?.clearValidate()
} else {
editForm.value.orderPercent = ''
editForm.value.valueUp = ''
editForm.value.weightPercent = ''
editForm.value.weightUp = ''
editFormRef.value?.clearValidate()
}
editFormRef.value?.refashConfig(mapData.value.get(value) as string[])
},
},
rules: [
{
required: true,
message: '请选择申报类型',
},
],
},
{
prop: 'fixedValue',
type: 'amountInput',
label: '固定金额',
isIncludeProp: true,
attrs: {
placeholder: '请输入固定金额',
hasSuffix: true,
suffix: 'USD',
},
rules: [
{
required: true,
message: '请输入固定金额',
},
],
},
{
prop: 'fixedWeight',
type: 'amountInput',
label: '固定重量',
isIncludeProp: true,
attrs: {
placeholder: '请输入固定重量',
hasSuffix: true,
suffix: 'g',
},
rules: [
{
required: true,
message: '请输入固定重量',
},
],
},
{
prop: 'orderPercent',
type: 'amountInput',
label: '金额百分比',
isIncludeProp: true,
attrs: {
placeholder: '请输入金额百分比',
hasSuffix: true,
suffix: '%',
},
rules: [
{
required: true,
message: '请输入金额百分比',
},
],
},
{
prop: 'valueUp',
type: 'amountInput',
label: '申报价值上限',
isIncludeProp: true,
attrs: {
placeholder: '请输入申报价值上限',
hasSuffix: true,
suffix: 'USD',
},
rules: [
{
required: true,
message: '请输入申报价值上限',
},
],
},
{
prop: 'weightPercent',
type: 'amountInput',
label: '重量百分比',
isIncludeProp: true,
attrs: {
placeholder: '请输入重量百分比',
hasSuffix: true,
suffix: '%',
},
rules: [
{
required: true,
message: '请输入重量百分比',
},
],
},
{
prop: 'weightUp',
type: 'amountInput',
label: '申报重量上限',
isIncludeProp: true,
attrs: {
placeholder: '请输入申报重量上限',
hasSuffix: true,
suffix: 'g',
},
rules: [
{
required: true,
message: '请输入申报重量上限',
},
],
},
{
prop: 'remark',
type: 'input',
label: '备注',
fixed: 'last',
attrs: {
placeholder: '请输入备注',
type: 'textarea',
rows: 4,
},
},
])
const tableConfig = ref([
{
prop: 'name',
label: '规则名称',
},
{
prop: 'currency',
label: '申报币种',
},
{
prop: 'type',
label: '申报类型',
},
{
prop: 'fixedValue',
label: '固定金额',
},
{
prop: 'fixedWeight',
label: '固定重量',
},
{
prop: 'orderPercent',
label: '订单总金额百分比',
},
{
prop: 'valueUp',
label: '申报价值上限',
},
{
prop: 'weightPercent',
label: '按原订单报关重量',
},
{
prop: 'weightUp',
label: '申报重量上限',
},
{
prop: 'remark',
label: '备注',
},
{
prop: 'opeare',
label: '操作',
attrs: {
align: 'center',
},
render: {
default: ({ row }: { row: AddDeclarationRuleObj }) => (
<div>
<el-icon
size="24"
title="编辑"
color="#EF6C00"
style="cursor: pointer; vertical-align: middle"
onclick={() => editRule(row)}
>
<Edit />
</el-icon>
<el-icon
size="24"
title="删除"
color="#f56c6c"
style="cursor: pointer; vertical-align: middle"
onclick={() => deleteRule(row)}
>
<Delete />
</el-icon>
<el-icon
size="24"
title="日志"
color="#008aff"
style="cursor: pointer; vertical-align: middle"
onclick={() => showLog(row)}
>
<List />
</el-icon>
</div>
),
},
},
])
const loading = ref(false)
/**
* @description: 取消按钮
*/
function cancelFn() {
dialogVisible.value = false
editFormRef.value?.resetFields()
resetEditForm()
}
/**
* @description: 编辑按钮
*/
async function editRule(item: AddDeclarationRuleObj) {
try {
editForm.value = { ...item }
dialogVisible.value = true
nextTick(() => {
editFormRef.value?.refashConfig(
mapData.value.get(item.type as number) as string[],
)
})
console.log(493, editForm.value)
} catch (e) {
console.log(e)
}
}
/**
* @description: 检查数据
*/
async function checkData() {
const [isValid, postData] = await Promise.all([
new Promise<boolean>((resolve) => {
editFormRef.value
?.validate()
.then(() => resolve(true))
.catch((err) => {
resolve(false)
console.log(err)
})
}),
new Promise<AddDeclarationRuleObj>((resolve) => {
const params = { ...editForm.value }
resolve(params)
}),
])
return { isValid, postData }
}
/**
* @description: 保存按钮
*/
const save = debounce(async () => {
const { isValid, postData } = await checkData()
if (isValid) {
try {
if (!postData.id) {
await addLogisticsCustomsRule({
...postData,
})
} else {
await updateLogisticsCustomsRule({
...postData,
})
}
ElMessage({
message: postData.id ? '更新成功' : '新增成功',
type: 'success',
})
cancelFn()
search()
} catch (e) {
return
}
}
}, 400)
/**
* @description: 新增按钮打开弹窗
*/
function addDialog() {
dialogVisible.value = true
nextTick(() => {
console.log(999)
editFormRef.value?.refashConfig(mapData.value.get(1))
})
}
/**
* @description: 获取选中数据
*/
function handleCheckboxRecords(value: never[]) {
console.log(351, value)
selection.value = value
}
/**
* @description: 多选删除按钮
*/
async function deleteFn() {
if (!selection.value.length) {
return ElMessage({
message: '请选择申报规则',
type: 'warning',
})
}
try {
await showConfirm('是否删除申报规则', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const ids = {
ids: selection.value.map((item: IOption) => item.id).join(','),
}
await deleteLogisticsCustomsRule(ids)
ElMessage({
message: '删除成功',
type: 'success',
})
search()
} catch (e) {
search()
// showError(e)
}
}
/**
* @description: 单个删除按钮
*/
async function deleteRule(item: AddDeclarationRuleObj) {
try {
await showConfirm('是否删除申报规则', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const ids = { ids: [item.id].join(',') }
await deleteLogisticsCustomsRule(ids)
ElMessage({
message: '删除成功',
type: 'success',
})
search()
} catch (e) {
search()
// showError(e)
}
}
/**
* @description: 日志弹窗
*/
interface LogList {
id?: number
createTime?: string
description?: string
}
const logList = ref<LogList[]>([])
async function showLog(row: AddDeclarationRuleObj) {
logDialogVisible.value = true
try {
const { data } = await getLogisticsLog({
logType: 'logistics_customs_rule',
relaId: row.id as number,
})
logList.value = data
} catch (error) {
console.log(error)
}
}
</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>
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"
></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'
import { ISeachFormConfig } from '@/types/searchType'
import { TableColumn } from '@/components/VxeTable'
const [searchForm] = useValue({ code: '', weight: '' })
const tableRef = ref<InstanceType<typeof CustomizeTable> | null>(null)
const mergeCells = ref<VxeTablePropTypes.MergeCells>([])
const tableData = ref([])
const searchConfig = ref<ISeachFormConfig[]>([
{
prop: 'code',
type: 'input',
label: '邮编',
attrs: {
clearable: true,
placeholder: '请输入邮编',
},
},
{
prop: 'weight',
type: 'amountInput',
label: '重量',
attrs: {
clearable: true,
placeholder: '请输入重量',
hasUnit: true,
suffix: 'g',
},
},
])
const tableConfig = ref<TableColumn[]>([
{
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: '预计运费($)',
},
])
/**
* @description: 计算按钮
*/
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: { status: boolean }) => item.status,
)
console.log(123, itemIndex)
if (itemIndex !== -1) {
nextTick(() => {
tableRef.value?.selectRowEvent(tableData.value[itemIndex])
})
}
}
/**
* @description: 获取列表数据
*/
const loading = ref(false)
async function getList(data?: { code: string; weight: string }) {
loading.value = true
try {
const res = await getLogisticsTrialCalculation({
...data,
})
tableData.value = [...res.data]
} catch (error) {
console.log(error)
} finally {
loading.value = false
}
}
</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>
<script setup lang="ts">
import { nextTick, ref } from 'vue'
import SplitDiv from '@/components/splitDiv/splitDiv.vue'
import {
getLogisticsLog,
IForm,
ILogisticsCompany, ILogisticsCompanyData, ILogisticsList,
logisticsCompany,
logisticsCompanyAdd, logisticsCompanyAllCodelist, logisticsCompanyDelete,
logisticsCompanyUpdate,
} from '@/api/logistics.ts'
import LogDialog from '@/views/logistics/components/LogDialog.tsx'
interface LogList {
id?: number
createTime?: string
description?: string
}
const otherFields = ref<string[]>([])
const logDialogVisible = ref<boolean>(false)
const searchForm = ref({
name: '',
code: '',
})
const selections = ref<ILogisticsCompany[]>([])
const formRef = ref()
const logList = ref<LogList[]>([])
const logisticsList = ref<ILogisticsList[]>([])
const form = ref<IForm>({
name: '',
code: '',
contact: '',
phone: '',
siteUrl: '',
address: '',
apiData: {},
})
const createData = ref({
title: '',
show: false,
isEdit: false,
form: {
locationName: '',
locationCode: '',
pickingOrder: '',
warehouseName: '',
warehouseId: '',
remark: '',
},
})
const rules = {
code: [
{ required: true, message: '请选择物流公司编码', trigger: 'change' },
],
name: [
{ required: true, message: '请输入名称', trigger: 'blur' },
],
contact: [
{ required: true, message: '请输入联系人', trigger: 'blur' },
],
phone: [
{ required: true, message: '请输入电话/手机', trigger: 'blur' },
],
siteUrl: [
{ required: true, message: '请输入网址', trigger: 'blur' },
],
address: [
{ required: true, message: '请输入地址', trigger: 'blur' },
],
}
const leftData = ref<ILogisticsCompany[]>([])
const pagination = ref<ILogisticsCompanyData>({
pageSize: 50,
currentPage: 1,
total: 0,
})
async function getData() {
const res = await logisticsCompany({
...pagination.value,
...searchForm.value,
})
console.log(res)
leftData.value = res.data.records
pagination.value.total = res.data.total
}
const handleSelectionChange = (data: ILogisticsCompany[]) => {
selections.value = data
}
const handleSizeChange = (pageSize: number) => {
pagination.value.pageSize = pageSize
getData()
}
const handleCurrentChange = (currentPage: number) => {
pagination.value.currentPage = currentPage
getData()
}
const handleConfirm = async () => {
await formRef.value?.validate()
if (!createData.value.isEdit) {
await logisticsCompanyAdd(form.value)
} else {
await logisticsCompanyUpdate(form.value)
}
createData.value.show = false
ElMessage.success('操作成功')
await getData()
}
const handleShowLog = async (row: ILogisticsCompany) => {
const { data } = await getLogisticsLog({
logType: 'logistics_company',
relaId: row.id as number,
})
logDialogVisible.value = true
logList.value = data
}
const handleBatchDelete = async (row: ILogisticsCompany | null) => {
if (!row && !selections.value.length) {
return ElMessage.warning('请选择要删除的数据')
}
await ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
const str = row && row.id ? row.id?.toString() : selections.value.map((el) => el.id).join(',')
await logisticsCompanyDelete(str)
ElMessage.success('删除成功')
await getData()
}
const createWarehouse = () => {
createData.value.show = true
createData.value.isEdit = false
createData.value.title = '新增物流公司'
form.value = {
name: '',
code: '',
contact: '',
phone: '',
siteUrl: '',
address: '',
apiData: {},
}
otherFields.value = []
nextTick(() => {
formRef.value?.clearValidate()
})
}
const updateWarehouse = (item: IForm) => {
createData.value.show = true
createData.value.isEdit = true
otherFields.value = []
createData.value.title = '修改物流公司'
form.value = {
id: item.id,
name: item.name,
code: item.code,
contact: item.contact,
phone: item.phone,
siteUrl: item.siteUrl,
address: item.address,
apiData: item.apiData,
}
codeChange(item.code)
nextTick(() => {
formRef.value?.clearValidate()
})
}
const codeChange = (v: string) => {
const item = logisticsList.value.find(d => d.code === v)
if (item) {
otherFields.value = Object.keys(item.apiData || {})
}
}
const getLogisticsList = async () => {
const { data } = await logisticsCompanyAllCodelist()
logisticsList.value = data
}
getData()
getLogisticsList()
</script>
<template>
<split-div>
<template #top>
<el-card>
<el-form inline :model="searchForm">
<el-form-item label="名称">
<el-input
v-model="searchForm.name" style="width: 140px;" placeholder="请输入名称"
clearable></el-input>
</el-form-item>
<el-form-item label="编码">
<el-input
v-model="searchForm.code" style="width: 140px;" placeholder="请输入编码"
clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getData">查询</el-button>
<el-button type="success" @click="createWarehouse">新增</el-button>
<el-button type="danger" @click="handleBatchDelete(null)">删除</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<template #bottom>
<el-card style="height: 100%">
<div class="manage">
<div class="table-flex">
<div class="left-table">
<div class="table-container">
<el-table height="100%" :data="leftData" border @selection-change="handleSelectionChange">
<el-table-column type="selection" />
<el-table-column type="index" label="序号" width="60" />
<el-table-column
label="名称"
prop="name"
align="center"
show-overflow-tooltip>
</el-table-column>
<el-table-column
label="编码"
prop="code"
align="center"
show-overflow-tooltip>
</el-table-column>
<el-table-column
label="联系人"
prop="contact"
align="center"
show-overflow-tooltip>
</el-table-column>
<el-table-column
label="电话"
prop="phone"
align="center"
show-overflow-tooltip>
</el-table-column>
<el-table-column
label="网址"
prop="siteUrl"
align="center"
show-overflow-tooltip>
</el-table-column>
<el-table-column
label="地址"
prop="address"
align="center"
show-overflow-tooltip>
</el-table-column>
<el-table-column width="260" label="操作" align="center">
<template #default="{row}">
<el-button text type="primary" @click="updateWarehouse(row)">编辑</el-button>
<el-button text type="danger" @click="handleBatchDelete(row)">删除</el-button>
<el-button text type="info" @click="handleShowLog(row)">操作日志</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination">
<el-pagination
v-model:current-page="pagination.currentPage"
v-model:page-size="pagination.pageSize"
:page-sizes="[50, 100, 150, 200]"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
<LogDialog
v-model="logDialogVisible"
title="操作日志"
@close="logDialogVisible = false"
>
<div v-for="item in logList" :key="item.id" style="margin-bottom: 8px">
<span style="margin-right: 10px">{{ item.createTime }}</span>
<span>{{ item.description }}</span>
</div>
</LogDialog>
<el-dialog v-model="createData.show" :close-on-click-modal="false" width="700px" :title="createData.title">
<el-form v-if="createData.show" ref="formRef" label-width="110px" :rules="rules" :model="form">
<el-form-item label="物流公司编码" prop="code">
<el-select v-model="form.code" clearable filterable @change="codeChange">
<el-option
v-for="it in logisticsList" :key="it.code" :label="it.basicsName"
:value="it.code">
{{ it.code }}
<span style="font-size: 14px; color: #999; line-height: 24px">
({{ it.basicsName }})
</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" clearable placeholder="请输入名称"></el-input>
</el-form-item>
<el-form-item label="联系人" prop="contact">
<el-input v-model="form.contact" clearable placeholder="请输入联系人"></el-input>
</el-form-item>
<el-form-item label="电话/手机" prop="phone">
<el-input v-model="form.phone" clearable placeholder="请输入电话/手机"></el-input>
</el-form-item>
<el-form-item label="网址" prop="siteUrl">
<el-input v-model="form.siteUrl" clearable placeholder="请输入网址"></el-input>
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="form.address" clearable placeholder="请输入地址"></el-input>
</el-form-item>
<el-form-item v-for="o in otherFields" :key="o" required :label="o" :prop="`apiData.${o}`">
<el-input v-model="form.apiData[o]" clearable :placeholder="`请输入${o}`"></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="createData.show=false">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</template>
</el-dialog>
</div>
</el-card>
</template>
</split-div>
</template>
<style scoped lang="scss">
.el-card {
::v-deep(.el-card__body) {
height: 100%;
}
}
.manage {
height: 100%;
display: flex;
flex-direction: column;
.header {
margin-bottom: 10px;
}
.table-flex {
flex: 1;
flex-shrink: 0;
overflow: hidden;
display: flex;
}
.right-table {
flex: 1;
margin-left: 10px;
flex-shrink: 0;
}
.left-table {
height: 100%;
display: flex;
width: 100%;
flex-direction: column;
.pagination {
display: flex;
margin-top: 10px;
justify-content: center;
}
.table-container {
flex: 1;
flex-shrink: 0;
overflow: hidden;
}
}
}
</style>
<template>
<div class="user-page flex-column card h-100 overflow-hidden">
<div class="header-filter-form">
<SearchForm
v-model="searchForm"
:config="searchConfig"
@search="search"
@add="addDialog"
@delete="deleteFn"
></SearchForm>
</div>
<div class="user-content flex-1 flex-column overflow-hidden">
<div v-loading="loading" class="user-list flex-1 overflow-hidden">
<CustomizeTable
v-model="tableData"
:config="tableConfig"
@get-checkbox-records="handleCheckboxRecords"
></CustomizeTable>
</div>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[100, 200, 300, 400, 500]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin: 10px auto 0; text-align: right"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></ElPagination>
</div>
</div>
<LogDialog
v-model="dialogVisible"
:title="editForm.id ? '编辑物流方式' : '新增物流方式'"
dialog-width="1000px"
@close="cancelFn"
>
<CustomizeForm
ref="editFormRef"
v-model="editForm"
:config="formConfig"
form-item-width="100%"
>
</CustomizeForm>
<template #footer>
<div style="text-align: center">
<ElButton @click="cancelFn">取消</ElButton>
<ElButton type="primary" @click="save">保存</ElButton>
</div>
</template>
</LogDialog>
<LogDialog
v-model="logDialogVisible"
title="操作日志"
@close="logDialogVisible = false"
>
<div v-for="item in logList" :key="item.id" style="margin-bottom: 8px">
<span style="margin-right: 10px">{{ item.createTime }}</span>
<span>{{ item.description }}</span>
</div>
</LogDialog>
</template>
<script setup lang="tsx">
defineOptions({
name: 'LogisticsMethod',
})
import {
getLogisticsWayList,
addLogisticsWay,
updateLogisticsWay,
updateStatusLogisticsWay,
deleteLogisticsWay,
getWarehouseList,
getRuleList,
getPlatformList,
getLogisticsLog,
getLogisticsCompanyList,
getUniuniList,
} from '@/api/logistics'
import { ISeachFormConfig } from '@/types/searchType'
import { TableColumn } from '@/components/VxeTable'
// import type { PromiseSettledResult } from 'types'
import type {
LogisticsMethod,
platformObj,
} from '@/views/logistics/types/logistics.ts'
import SearchForm from '@/components/SearchForm.tsx'
import LogDialog from './components/LogDialog.tsx'
import CustomizeForm from '@/components/CustomizeForm.tsx'
import CustomizeTable from '@/components/VxeTable.tsx'
import { IFormConfig } from '@/components/CustomizeForm.tsx'
import usePageList from '@/utils/hooks/usePageList'
import { useValue } from './hooks/useValue'
import { showConfirm } from '@/utils/ui'
import { Edit, Delete, List } from '@element-plus/icons-vue'
import { debounce } from 'lodash-es'
const [searchForm] = useValue({})
const [editForm, resetEditForm] = useValue<LogisticsMethod>({
platformList: [{ platform: '', logisticsName: '', showPlatform: [] }],
ruleRef: { ruleId: '', ruleName: '' },
status: 1,
})
const {
loading,
currentPage,
pageSize,
total,
data: tableData,
refresh: search,
onCurrentPageChange: handleCurrentChange,
onPageSizeChange: handleSizeChange,
} = usePageList({
query: (page, pageSize) =>
getLogisticsWayList({
...searchForm.value,
pageSize: pageSize,
currentPage: page,
}).then(({ data }) => {
return data
}),
})
const dialogVisible = ref(false)
const logDialogVisible = ref(false)
const editFormRef = ref<InstanceType<typeof CustomizeForm> | null>(null)
const selection = ref([])
const searchConfig = ref<ISeachFormConfig[]>([
{
prop: 'name',
type: 'input',
label: '物流方式',
attrs: {
clearable: true,
placeholder: '请输入物流方式',
},
},
{
prop: 'status',
type: 'select',
label: '启用状态',
attrs: {
clearable: true,
placeholder: '请选择启用状态',
options: [
{ label: '启用', value: 1 },
{ label: '禁用', value: 0 },
],
},
},
{
prop: 'serviceCode',
type: 'input',
label: '物流编码',
attrs: {
clearable: true,
placeholder: '请输入物流编码',
},
},
])
const platformList = ref([])
const warehouseList = ref([])
const ruleNameList = ref([])
const uniuniList = ref([])
const logisticsCompanyList = ref([])
const formConfig = computed<IFormConfig[]>(() => [
{ title: '物流基础信息' },
{
prop: 'name',
type: 'input',
label: '物流名称',
attrs: {
placeholder: '请输入物流名称',
},
rules: [
{
required: true,
message: '请输入物流名称',
},
],
},
{
prop: 'warehouseId',
type: 'select',
label: '仓库名称',
attrs: {
placeholder: '请选择仓库名称',
label: 'name',
value: 'id',
options: [...(warehouseList.value || [])],
onChange: (value: { name: string; id: string | number }) => {
editForm.value.warehouseName = value.name
},
},
rules: [
{
required: true,
message: '请选择仓库名称',
},
],
},
{
prop: 'companyId',
type: 'select',
label: '物流公司',
attrs: {
placeholder: '请选择物流公司',
label: 'name',
value: 'id',
options: [...(logisticsCompanyList.value || [])],
onChange: (value: {
code: string
name: string
id: string | number
}) => {
console.log(222, value)
editForm.value.company = value.name
if (value.code === 'UINUIN') {
editFormRef.value?.refashConfig(['uinuinWarehouseId'])
} else {
editFormRef.value?.refashConfig([])
editFormRef.value?.clearValidate(['uinuinWarehouseId'])
editForm.value.uinuinWarehouseId = undefined
}
},
},
rules: [
{
required: true,
message: '请选择仓库名称',
},
],
},
{
prop: 'uinuinWarehouseId',
type: 'select',
label: 'uniuni仓库',
isIncludeProp: true,
attrs: {
placeholder: '请选择uniuni仓库',
label: 'warehouseName',
value: 'warehouseId',
options: [...(uniuniList.value || [])],
},
rules: [
{
required: true,
message: '请选择uniuni仓库',
},
],
},
{
prop: 'ruleId',
type: 'select',
label: '申报规则',
fixed: 'last',
attrs: {
placeholder: '请选择申报规则',
label: 'name',
value: 'id',
options: [...(ruleNameList.value || [])],
onChange: (value: { name: string; id: string | number }) => {
editForm.value.ruleRef.ruleId = value.id
editForm.value.ruleRef.ruleName = value.name
},
},
rules: [
{
required: true,
message: '请选择申报规则',
},
],
},
{
prop: 'serviceCode',
type: 'input',
label: '物流编码',
fixed: 'last',
attrs: {
placeholder: '请输入物流编码',
},
rules: [
{
required: true,
message: '请输入物流编码',
},
],
},
{
prop: 'siteUrl',
type: 'input',
label: '查询网址',
fixed: 'last',
attrs: {
placeholder: '请输入查询网址',
},
rules: [
{
required: true,
message: '请输入物流编码',
},
],
},
{
prop: 'status',
type: 'switch',
label: '启用状态',
fixed: 'last',
attrs: {
activeValue: 1,
inactiveValue: 0,
},
},
{
title: '平台物流名称',
fixed: 'last',
render: (item, formData) => {
console.log(283, item, formData)
return (formData?.platformList as platformObj[])?.map(
(item: platformObj, index: number) => (
<div style="display: flex; width:100%">
<el-form-item
key={index}
class="renderItem"
label="平台名称"
style="flex:50%"
prop={`platformList.${index}.showPlatform`}
rules={[
{
required: true,
message: '请选择平台名称',
trigger: 'blur',
},
]}
>
<el-select
multiple
collapse-tags
collapse-tags-tooltip
v-model={item['showPlatform']}
>
{platformList.value?.map((el, idx) => (
<el-option label={el} value={el} key={idx}></el-option>
))}
</el-select>
</el-form-item>
<el-form-item
key={index}
class="renderItem"
label="物流名称"
style="display: flex;flex:50%"
prop={`platformList.${index}.logisticsName`}
rules={[
{
required: true,
message: '请输入物流名称',
trigger: 'blur',
},
]}
>
<el-input
v-model={item.logisticsName}
placeholder="请输入物流名称"
/>
</el-form-item>
<div style="display: flex;flex:20%">
{(formData?.platformList as platformObj[])?.length - 1 ===
index && (
<el-button
style="margin-left: 10px"
type="primary"
onClick={() => addCol()}
>
新增
</el-button>
)}
{index >= 1 && (
<el-button
style="margin-left: 10px"
type="primary"
onClick={() => deleteCol(index)}
>
删除
</el-button>
)}
</div>
</div>
),
)
},
},
])
const tableConfig = ref<TableColumn[]>([
{
prop: 'name',
label: '物流名称',
},
{
prop: 'warehouseName',
label: '发货仓库',
},
{
prop: 'serviceCode',
label: '物流编码',
},
{
prop: 'company',
label: '物流公司',
},
{
prop: 'status',
label: '状态',
render: {
default: ({ row }) => (
<div>
<el-switch
v-model={row.status}
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
inline-prompt
active-text="启用"
inactive-text="禁用"
active-value={1}
inactive-value={0}
onClick={() => changeStatus(row as unknown as LogisticsMethod)}
/>
</div>
),
},
},
{
prop: 'status1',
label: '平台物流名称',
attrs: { width: '400px', align: 'center' },
render: {
default: ({ row }) =>
(row.platformList as platformObj[])?.map((el: platformObj) => (
<div>
<span style="margin-right:18px;">
<span>{'平台:'}</span>
<span class="logistics-name">{el.platform}</span>
</span>
<span>
<span>{'物流名称:'}</span>
<span class="logistics-name">{el.logisticsName}</span>
</span>
</div>
)),
},
},
{
prop: 'status2',
label: '申报规则',
render: {
default: ({ row }) => (
<div>{(row.ruleList as { ruleName: string }[])?.[0]?.ruleName}</div>
),
},
},
{
prop: 'siteUrl',
label: '查询网址',
attrs: { width: '200px', align: 'center' },
render: {
default: ({ row }) => (
<div>
<el-link href={row.siteUrl} target="_blank">
{row.siteUrl}
</el-link>
</div>
),
},
},
{
prop: 'opeare',
label: '操作',
attrs: {
align: 'center',
},
render: {
default: ({ row }) => (
<div>
<el-icon
size="24"
title="编辑"
color="#EF6C00"
style="cursor: pointer; vertical-align: middle"
onclick={() => editWay(row as unknown as LogisticsMethod)}
>
<Edit />
</el-icon>
<el-icon
size="24"
title="删除"
color="#f56c6c"
style="cursor: pointer; vertical-align: middle"
onclick={() => deleteWay(row as unknown as LogisticsMethod)}
>
<Delete />
</el-icon>
<el-icon
size="24"
title="日志"
color="#008aff"
style="cursor: pointer; vertical-align: middle"
onclick={() => showLog(row as unknown as LogisticsMethod)}
>
<List />
</el-icon>
</div>
),
},
},
])
onMounted(() => {
getAllList()
})
/**
* @description: 更改状态
*/
async function changeStatus(data: LogisticsMethod) {
loading.value = true
try {
const params = {
id: data.id,
status: data.status,
}
await updateStatusLogisticsWay(params)
ElMessage({
message: '更改状态成功',
type: 'success',
})
search()
} catch (error) {
console.log(error)
}
}
/**
* @description: 取消按钮
*/
function cancelFn() {
dialogVisible.value = false
editFormRef.value?.resetFields()
resetEditForm()
}
/**
* @description: 删除按钮
*/
async function deleteWay(item: LogisticsMethod) {
try {
await showConfirm('是否删除物流方式', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const ids = [item.id].join(',')
await deleteLogisticsWay(ids)
ElMessage({
message: '删除成功',
type: 'success',
})
search()
} catch (e) {
search()
// showError(e)
}
}
/**
* @description: 编辑按钮
*/
async function editWay(item: LogisticsMethod) {
try {
editForm.value = { ...item }
editForm.value.ruleId = item.ruleList?.[0]?.ruleId || ''
editForm.value.platformList =
item.platformList?.map((el) => {
el.showPlatform = el.platform.split(',')
return el
}) || []
console.log(493, editForm.value)
dialogVisible.value = true
} catch (e) {
console.log(e)
}
}
/**
* @description: 检查数据
*/
async function checkData() {
const [isValid, postData] = await Promise.all([
new Promise<boolean>((resolve) => {
editFormRef.value
?.validate()
.then(() => resolve(true))
.catch((err) => {
resolve(false)
console.log(err)
})
}),
new Promise<LogisticsMethod>((resolve) => {
const params = { ...editForm.value }
if (params.platformList?.length) {
params.platformList.forEach((el) => {
el.platform = el.showPlatform.join()
})
}
resolve(params)
}),
])
console.log(isValid, postData)
return { isValid, postData }
}
/**
* @description: 保存按钮
*/
const save = debounce(async () => {
const { isValid, postData } = await checkData()
if (isValid) {
try {
if (!postData.id) {
await addLogisticsWay({
...postData,
})
} else {
await updateLogisticsWay({
...postData,
})
}
ElMessage({
message: '保存成功',
type: 'success',
offset: window.innerHeight / 2,
})
cancelFn()
search()
} catch (e) {
return
}
}
}, 400)
/**
* @description: 新增按钮打开弹窗
*/
function addDialog() {
dialogVisible.value = true
nextTick(() => {
editFormRef.value?.refashConfig([])
})
}
/**
* @description: 多选删除按钮
*/
async function deleteFn() {
if (!selection.value.length) {
return ElMessage({
message: '请选择物流方式',
type: 'warning',
})
}
try {
await showConfirm('是否删除物流方式', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const ids = selection.value.map((item: { id: number }) => item.id).join(',')
await deleteLogisticsWay(ids)
ElMessage({
message: '删除成功',
type: 'success',
})
search()
} catch (e) {
search()
// showError(e)
}
}
/**
* @description: 获取选中数据
*/
function handleCheckboxRecords(value: never[]) {
console.log(351, value)
selection.value = value
}
/**
* @description: 新增平台物流行
*/
function addCol() {
editForm.value.platformList?.push({
platform: '',
logisticsName: '',
showPlatform: [],
})
}
/**
* @description: 删除平台物流行
*/
function deleteCol(index: number) {
editForm.value.platformList?.splice(index, 1)
}
/**
* @description: 获取列表数据
*/
async function getAllList() {
try {
const res = await Promise.allSettled([
getWarehouseList(),
getRuleList(),
getPlatformList(),
getLogisticsCompanyList(),
getUniuniList(),
])
res.forEach(
(item: PromiseSettledResult<{ code: number; data: never[] }>, index) => {
if (item.status === 'fulfilled') {
if (item.value.code === 200) {
if (index == 0) {
warehouseList.value = item.value.data || []
} else if (index == 1) {
ruleNameList.value = item.value.data || []
} else if (index == 2) {
platformList.value = item.value.data || []
} else if (index == 3) {
logisticsCompanyList.value = item.value.data || []
} else if (index == 4) {
uniuniList.value = item.value.data || []
}
}
}
},
)
console.log(545, res)
} catch (error) {
console.log(error)
}
}
/**
* @description: 日志弹窗
*/
interface LogList {
id?: number
createTime?: string
description?: string
}
const logList = ref<LogList[]>([])
async function showLog(row: LogisticsMethod) {
logDialogVisible.value = true
try {
const { data } = await getLogisticsLog({
logType: 'logistics_way',
relaId: row.id as number,
})
logList.value = data
} catch (error) {
console.log(error)
}
}
</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,
}"
align="center"
border="full"
@getCheckboxRecords="handleCheckboxRecords"
></CustomizeTable>
</div>
</div>
</div>
</template>
<script setup lang="tsx">
defineOptions({
name: 'LogisticsPartition',
})
import {
getLogisticsZoneList,
updateLogisticsZone,
deleteLogisticsZone,
importLogisticsZone,
exportExcelLogisticsZone,
getlogisticsWayAllList,
} from '@/api/logistics'
import SearchForm from '@/components/SearchForm.tsx'
import { ISeachFormConfig } from '@/types/searchType'
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 { TableColumn } from '@/components/VxeTable'
const [searchForm] = useValue<{
logisticsIdList?: string[] | string
codePrefix?: string
}>({ logisticsIdList: [] })
const selection = ref([])
const mergeCells = ref<VxeTablePropTypes.MergeCells>([])
const tableData = ref([])
const searchConfig = ref<ISeachFormConfig[]>([
{
prop: 'logisticsIdList',
type: 'select',
label: '物流方式',
attrs: {
placeholder: '请选择物流方式',
multiple: true,
clearable: true,
value: 'id',
label: 'name',
collapseTags: true,
collapseTagsTooltip: true,
options: [],
},
},
{
prop: 'codePrefix',
type: 'input',
label: '邮编编码',
attrs: {
clearable: true,
placeholder: '请输入邮编编码',
},
},
])
const tableConfig = ref<TableColumn[]>([])
onMounted(() => {
getAllList()
})
/**
* @description: 搜索
*/
async function search() {
await getList(searchForm.value)
setCellStyle()
}
/**
* @description: 转换表格数据
*/
// 定义物流区域项的类型
interface LogisticsZoneItem {
logistics: string
codePrefix: string
highlight: boolean
logisticsId: string
}
// 定义父项的类型,允许动态添加属性
interface ParentItem {
logisticsZoneList: LogisticsZoneItem[]
[key: string]: unknown // 添加索引签名,允许任意字符串键
}
function getTableData(arr: ParentItem[]) {
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: { zoneName: string }) => 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?: {
logisticsIdList?: string[] | string
codePrefix?: string
}) {
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,
attrs: {
'edit-render': {},
},
render: {
edit: ({
row,
}: {
row: {
[key: string]: { codePrefix: string; logisticsId: string }
}
}) => {
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,
}: {
row: {
[key: string]: { codePrefix: string; highlight: string }
}
}) => {
return (
<span
class={row[key]?.highlight ? 'tableCell' : 'primaryCell'}
>
{row[key]?.codePrefix}
</span>
)
},
},
})
}
}
}
tableConfig.value = [...oldConfig, ...newConfig] as TableColumn[]
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: Element, level = 2) => {
let current = element
while (level-- > 0 && current) {
current = current.parentElement as Element
}
return current || null
}
const safeSetBackground = (element: HTMLElement, color: string) => {
if (element?.style && typeof color === 'string') {
element.style.backgroundColor = color
}
}
if (cell.length) {
cell.forEach((item) => {
const grandParent = getAncestor(item)
safeSetBackground(grandParent as HTMLElement, '#e6f7ff')
})
}
if (primaryCell.length) {
primaryCell.forEach((item) => {
const grandParent = getAncestor(item)
safeSetBackground(grandParent as HTMLElement, 'transparent')
})
}
}
/**
* @description: 获取物流方式列表
*/
async function getAllList() {
try {
const res = await Promise.allSettled([getlogisticsWayAllList()])
res.forEach(
(item: PromiseSettledResult<{ code: number; data: never[] }>, index) => {
if (item?.status === 'fulfilled') {
if (item.value.code === 200) {
if (index == 0) {
searchConfig.value[0].attrs!.options = [
...(item.value.data || []),
]
}
}
}
},
)
getList()
} catch (error) {
console.log(error)
}
}
/**
* @description: 上传文件
*/
function onBeforeUploadImage(file: 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 as unknown as BlobPart])
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: { file: 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>
<template>
<div class="user-page flex-column card h-100 overflow-hidden">
<div class="header-filter-form">
<SearchForm
:config="searchConfig"
@search="search"
@add="addDialog"
@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>
<el-upload
class="upload-demo"
action
:show-file-list="false"
:before-upload="onBeforeUploadImage"
:http-request="updateExcel"
>
<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"
:seq-config="{
seqMethod: (row:any) => {
if (mergeCells?.length) {
return row.row.seq
}
return row.seq
},
}"
border="full"
@getCheckboxRecords="handleCheckboxRecords"
></CustomizeTable>
</div>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[100, 200, 300, 400, 500]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="allTotal"
style="margin: 10px auto 0; text-align: right"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></ElPagination>
</div>
</div>
<LogDialog
:title="editForm.id ? '编辑物流报价' : '新增物流报价'"
dialogWidth="1000px"
v-model="dialogVisible"
@close="cancelFn"
>
<CustomizeForm
ref="editFormRef"
v-model="editForm"
:config="formConfig"
formItemWidth="100%"
>
</CustomizeForm>
<template #footer>
<div style="text-align: center">
<ElButton @click="cancelFn">取消</ElButton>
<ElButton type="primary" @click="save">保存</ElButton>
</div>
</template>
</LogDialog>
<LogDialog
title="操作日志"
v-model="logDialogVisible"
@close="logDialogVisible = false"
>
<div v-for="item in logList" :key="item.id" style="margin-bottom: 8px">
<span style="margin-right: 10px">{{ item.createTime }}</span>
<span>{{ item.description }}</span>
</div>
</LogDialog>
</template>
<script setup lang="tsx">
defineOptions({
name: 'LogisticsQuotation',
})
import {
getlogisticsWayAllList,
getlogisticsQuotationList,
addLogisticsQuotation,
updateLogisticsQuotation,
deleteLogisticsQuotation,
importLogisticsQuotation,
downloadLogisticsQuotationTemplate,
getLogisticsLog,
getlogisticsQuotationPage,
} from '@/api/logistics'
import { ISeachFormConfig } from '@/types/searchType'
import { TableColumn } from '@/components/VxeTable'
import SearchForm from '@/components/SearchForm.tsx'
import LogDialog from './components/LogDialog.tsx'
import CustomizeForm from '@/components/CustomizeForm.tsx'
import CustomizeTable from '@/components/VxeTable.tsx'
import type { VxeTablePropTypes } from 'vxe-table'
// import usePageList from '@/utils/hooks/usePageList'
import { useValue } from './hooks/useValue'
import { showConfirm } from '@/utils/ui'
import { Edit, List } from '@element-plus/icons-vue'
import { debounce } from 'lodash-es'
import { LogisticsQuotation } from './types/logisticsQuotation'
const [searchForm] = useValue({ logisticsIdList: [] })
const [editForm, resetEditForm] = useValue<LogisticsQuotation>({
unit: 'oz',
})
// const {
// loading,
// currentPage,
// pageSize,
// data,
// total,
// refresh: search,
// onCurrentPageChange: handleCurrentChange,
// onPageSizeChange: handleSizeChange,
// } = usePageList({
// query: (page, pageSize) =>
// getlogisticsQuotationPage({
// ...searchForm.value,
// pageSize: pageSize,
// currentPage: page,
// }).then(({ data }) => {
// return data
// }),
// })
const allTotal = ref(0)
const dialogVisible = ref(false)
const formVisible = ref(false)
const editFormRef = ref<InstanceType<typeof CustomizeForm> | null>(null)
const selection = ref([])
const mergeCells = ref<VxeTablePropTypes.MergeCells>([])
const tableData = ref<LogisticsQuotation[]>([])
const currentPage = ref<number>(1)
const pageSize = ref<number>(100)
const loading = ref<boolean>(false)
const searchConfig = computed<ISeachFormConfig[]>(() => [
{
prop: 'logisticsIdList',
type: 'select',
label: '物流方式',
attrs: {
placeholder: '请选择物流方式',
multiple: true,
clearable: true,
value: 'id',
label: 'name',
collapseTags: true,
collapseTagsTooltip: true,
options: [...(logisticsWayList.value || [])],
},
},
])
const formConfig = computed(() => [
{
prop: 'logistics',
type: 'select',
label: '物流方式',
attrs: {
placeholder: '请选择物流方式',
value: 'name',
label: 'name',
options: [...(logisticsWayList.value || [])],
disabled: editForm.value?.['id'] ? true : false,
onChange: (val: { name: string; id: number }) => {
editForm.value['logisticsId'] = val.id
},
},
rules: [{ required: true, message: '请选择物流方式' }],
},
{
prop: 'rate',
type: 'amountInput',
label: 'Rate(oz/LB)',
attrs: {
placeholder: '请输入Rate(oz/LB)',
disabled: editForm.value?.['id'] ? true : false,
hasSuffix: false,
hasAppend: true,
appendRender: () => {
return h(
ElSelect,
{
modelValue: editForm.value['unit'],
'onUpdate:modelValue': (val) => (editForm.value['unit'] = val),
disabled: editForm.value['id'] ? true : false,
placeholder: 'Select',
style: { width: '115px' },
},
() => [
h(ElOption, { label: 'oz', value: 'oz' }),
h(ElOption, { label: 'LB', value: 'LB' }),
],
)
},
},
rules: [{ required: true, message: '请输入Rate' }],
},
{
prop: 'zone1',
type: 'amountInput',
label: 'ZONE 1($)',
attrs: {
hasSuffix: false,
placeholder: '请输入ZONE 1',
},
},
{
prop: 'zone2',
type: 'amountInput',
label: 'ZONE 2($)',
attrs: {
hasSuffix: false,
placeholder: '请输入ZONE 2',
},
},
{
prop: 'zone3',
type: 'amountInput',
label: 'ZONE 3($)',
attrs: {
hasSuffix: false,
placeholder: '请输入ZONE 3',
},
},
{
prop: 'zone4',
type: 'amountInput',
label: 'ZONE 4($)',
attrs: {
hasSuffix: false,
placeholder: '请输入ZONE 4',
},
},
{
prop: 'zone5',
type: 'amountInput',
label: 'ZONE 5($)',
attrs: {
hasSuffix: false,
placeholder: '请输入ZONE 5',
},
},
{
prop: 'zone6',
type: 'amountInput',
label: 'ZONE 6($)',
attrs: {
hasSuffix: false,
placeholder: '请输入ZONE 6',
},
},
{
prop: 'zone7',
type: 'amountInput',
label: 'ZONE 7($)',
attrs: {
hasSuffix: false,
placeholder: '请输入ZONE 7',
},
},
{
prop: 'zone8',
type: 'amountInput',
label: 'ZONE 8($)',
attrs: {
hasSuffix: false,
placeholder: '请输入ZONE 8',
},
},
{
prop: 'zone9',
type: 'amountInput',
label: 'ZONE 9($)',
attrs: {
hasSuffix: false,
placeholder: '请输入ZONE 9',
},
},
])
const tableConfig = ref<TableColumn[]>([
{
prop: 'rateType',
label: 'Rate(oz/LB)',
attrs: {
align: 'center',
width: 100,
},
},
{
prop: 'rateG',
label: 'Rate(g)',
},
{
prop: 'rateKg',
label: 'Rate(kg)',
},
{
prop: 'logistics',
label: 'Logistics',
},
{
prop: 'zone1',
label: 'ZONE 1($)',
},
{
prop: 'zone2',
label: 'ZONE 2($)',
},
{
prop: 'zone3',
label: 'ZONE 3($)',
},
{
prop: 'zone4',
label: 'ZONE 4($)',
},
{
prop: 'zone5',
label: 'ZONE 5($)',
},
{
prop: 'zone6',
label: 'ZONE 6($)',
},
{
prop: 'zone7',
label: 'ZONE 7($)',
},
{
prop: 'zone8',
label: 'ZONE 8($)',
},
{
prop: 'zone9',
label: 'ZONE 9($)',
},
{
prop: 'opeare',
label: '操作',
attrs: {
align: 'center',
},
render: {
default: ({ row }) => (
<div>
<el-icon
size="24"
title="编辑"
color="#EF6C00"
style="cursor: pointer; vertical-align: middle"
onclick={() => editWay(row)}
>
<Edit />
</el-icon>
<el-icon
size="24"
title="日志"
color="#008aff"
style="cursor: pointer; vertical-align: middle"
onclick={() => showLog(row as unknown as LogisticsQuotation)}
>
<List />
</el-icon>
</div>
),
},
},
])
/**
* @description: 监听表格合并单元格数据
*/
// watch(
// () => data.value as LogisticsQuotation[],
// (val) => {
// console.log(1111111111111)
// const isEqual =
// JSON.stringify(searchForm.value) ===
// JSON.stringify({ logisticsIdList: [] })
// if (isEqual) {
// tableData.value = [...val]
// allTotal.value = total.value as number
// mergeCells.value = []
// }
// },
// { deep: true },
// )
onMounted(() => {
getAllList()
})
async function search() {
loading.value = true
const isEqual =
JSON.stringify(searchForm.value) === JSON.stringify({ logisticsIdList: [] })
try {
if (isEqual) {
await getList()
} else {
await getSearchList()
}
} catch (e) {
console.log(e)
} finally {
loading.value = false
}
}
/**
* @description: 转换表格数据
*/
function getTableData(arr: LogisticsQuotation[]) {
if (!arr.length) return []
return arr.flatMap((parentItem, index) => {
const quotations = parentItem.logisticsQuotationList
if (!quotations?.length) return []
return quotations.map((quotation) => ({
...quotation,
seq: index + 1,
}))
})
}
/**
* @description: 取消按钮
*/
function cancelFn() {
dialogVisible.value = false
// editFormRef.value?.resetFields()
formVisible.value = false
resetEditForm()
}
/**
* @description: 编辑按钮
*/
async function editWay(item: LogisticsQuotation) {
try {
editForm.value = { ...item }
console.log(432, editForm.value)
editFormRef.value?.refashConfig()
dialogVisible.value = true
} catch (e) {
console.log(e)
}
}
/**
* @description: 检查数据
*/
async function checkData() {
const [isValid, postData] = await Promise.all([
new Promise<boolean>((resolve) => {
editFormRef.value
?.validate()
.then(() => resolve(true))
.catch((err) => {
resolve(false)
console.log(err)
})
}),
new Promise<LogisticsQuotation>((resolve) => {
const params = { ...editForm.value }
resolve(params)
}),
])
console.log(isValid, postData)
return { isValid, postData }
}
/**
* @description: 保存提交表单数据
*/
const save = debounce(async () => {
const { isValid, postData } = await checkData()
if (isValid) {
try {
if (!postData.id) {
await addLogisticsQuotation({
...postData,
})
} else {
await updateLogisticsQuotation({
...postData,
})
}
ElMessage({
message: postData.id ? '更新成功' : '新增成功',
type: 'success',
})
cancelFn()
search()
} catch (e) {
return
}
}
}, 400)
/**
* @description: 新增按钮打开弹窗
*/
async function addDialog() {
// await getAllList()
formVisible.value = true
dialogVisible.value = true
console.log(502, editForm.value)
}
/**
* @description: 删除按钮
*/
async function deleteFn() {
try {
if (!selection.value.length) {
return ElMessage({
message: '请选择物流报价',
type: 'warning',
})
}
await showConfirm('是否删除物流报价', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
const isEqual =
JSON.stringify(searchForm.value) ===
JSON.stringify({ logisticsIdList: [] })
let ids
if (isEqual) {
ids = {
ids: selection.value.map((item: { id: number }) => item.id).join(','),
}
} else {
console.log(589, selection.value)
const rateTypes = selection.value.map(
(item: { rateType: string }) => item.rateType,
)
const data = templeData.value.filter((item) =>
rateTypes.includes(item.rateType as string),
)
ids = {
ids: getTableData(data)
.map((item) => item.id)
.join(','),
}
}
await deleteLogisticsQuotation(ids)
ElMessage({
message: '删除成功',
type: 'success',
})
search()
} catch (e) {
search()
ElMessage({
message: '删除失败',
type: 'error',
})
// showError(e)
}
}
/**
* @description: 获取选中数据
*/
function handleCheckboxRecords(value: never[]) {
console.log(351, value)
selection.value = value
}
/**
* @description: 获取物流方式列表
*/
const logisticsWayList = ref([])
async function getAllList() {
try {
const res = await Promise.allSettled([getlogisticsWayAllList()])
res.forEach(
(item: PromiseSettledResult<{ code: number; data: never[] }>, index) => {
if (item.status === 'fulfilled') {
if (item.value.code === 200) {
if (index == 0) {
logisticsWayList.value = [...(item.value.data || [])]
}
}
}
},
)
getList()
} catch (error) {
console.log(error)
}
}
/**
* @description: 上传文件
*/
function onBeforeUploadImage(file: 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 downloadLogisticsQuotationTemplate()
const blob = new Blob([res as unknown as BlobPart])
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: { file: File }) {
try {
const formData = new FormData()
formData.append('file', file.file)
formData.append('type', 'add')
const res = await importLogisticsQuotation(formData)
if (res.code === 200) {
ElMessage.success('导入成功!')
search()
}
} catch (error) {
ElMessage.error('导入失败!')
}
}
/**
* @description: 更新文件
*/
async function updateExcel(file: { file: File }) {
try {
const formData = new FormData()
formData.append('file', file.file)
formData.append('type', 'update')
const res = await importLogisticsQuotation(formData)
if (res.code === 200) {
ElMessage.success('导入成功!')
search()
}
} catch (error) {
ElMessage.error('导入失败!')
}
}
function handleCurrentChange(val: number) {
currentPage.value = val
search()
}
function handleSizeChange(val: number) {
pageSize.value = val
search()
}
async function getList() {
try {
const { data } = await getlogisticsQuotationPage({
...searchForm.value,
pageSize: pageSize.value,
currentPage: currentPage.value,
})
tableData.value = [...(data as { records: LogisticsQuotation[] }).records]
allTotal.value = (data as { total: number }).total
mergeCells.value = []
} catch (error) {
console.log(error)
}
}
const templeData = ref<LogisticsQuotation[]>([])
async function getSearchList() {
try {
const { data } = await getlogisticsQuotationList({
...searchForm.value,
pageSize: pageSize.value,
currentPage: currentPage.value,
})
templeData.value = [...(data as { records: LogisticsQuotation[] }).records]
tableData.value = getTableData([
...(data as { records: LogisticsQuotation[] }).records,
])
allTotal.value = (data as { total: number }).total
let startRow = 0
const newMergeCells = []
console.log(477, tableData.value)
for (
let i = 0;
i < (data as { records: LogisticsQuotation[] }).records.length;
i++
) {
const item = (data as { records: LogisticsQuotation[] }).records[i]
const rowspan = item.logisticsQuotationList?.length || 0
if (rowspan > 0) {
for (let col = 0; col < 5; col++) {
newMergeCells.push({
row: startRow,
col: col,
rowspan: rowspan,
colspan: 1,
})
}
startRow += rowspan
}
}
mergeCells.value = newMergeCells
} catch (error) {
console.log(error)
}
}
/**
* @description: 日志弹窗
*/
interface LogList {
id?: number
createTime?: string
description?: string
}
const logList = ref<LogList[]>([])
const logDialogVisible = ref(false)
async function showLog(row: LogisticsQuotation) {
logDialogVisible.value = true
try {
const { data } = await getLogisticsLog({
logType: 'logistics_quotation',
relaId: row.id as number,
})
logList.value = data
} catch (error) {
console.log(error)
}
}
</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">
<el-button type="success" @click="addDialog">
{{ '新增' }}
</el-button>
<el-button
:disabled="(tableData as ShippingAddressObj[]).filter((el) => el.checked).length === 0"
type="danger"
@click="
deleteSection(
(tableData as ShippingAddressObj[])
.filter((el) => el.checked)
.map((el) => el.id as number)
)
"
>
{{ '删除' }}
</el-button>
</div>
<div class="user-content flex-1 flex-column overflow-hidden">
<div class="user-list flex-1 overflow-hidden" v-loading="loading">
<el-row :gutter="20">
<el-col
v-for="(item, index) in tableData as ShippingAddressObj[]"
:key="index"
:span="12"
>
<div class="address-item">
<div class="check">
<el-checkbox v-model="item.checked"></el-checkbox>
</div>
<div class="left">
<div class="name">
<b>{{ item.shipperName }}</b>
<p :title="[item.phoneNumber, item.postalCode].join(' ')">
{{ [item.phoneNumber, item.postalCode].join(' ') }}
</p>
<el-tag type="success" v-if="item.swDefault">
{{ '默认' }}
</el-tag>
</div>
<p
:title="
[
item.countryName,
// item.countryCode,
item.stateProvince,
// item.stateProvinceAbbr,
item.district,
// item.districtCode,
item.city,
// item.cityCode,
]
.filter((el) => el != null && el !== '')
.join(' ')
"
class="address"
>
{{
[
item.countryName,
// item.countryCode,
item.stateProvince,
// item.stateProvinceAbbr,
item.district,
// item.districtCode,
item.city,
// item.cityCode,
]
.filter((el) => el != null && el !== '')
.join(' ')
}}
</p>
<p
:title="
[item.addressLine1, item.addressLine2, item.addressLine3]
.filter((el) => el != null && el !== '')
.join(' ')
"
class="address address-en"
>
{{
[item.addressLine1, item.addressLine2, item.addressLine3]
.filter((el) => el != null && el !== '')
.join(' ')
}}
</p>
</div>
<div class="action">
<el-icon
size="24"
title="编辑"
color="#EF6C00"
style="cursor: pointer; vertical-align: middle"
@click="editAddress(item)"
>
<Edit />
</el-icon>
<el-icon
size="24"
title="删除"
color="#f56c6c"
style="cursor: pointer; vertical-align: middle"
@click="deleteAddress(item)"
>
<Delete />
</el-icon>
<el-icon
size="24"
title="日志"
color="#008aff"
style="cursor: pointer; vertical-align: middle"
@click="showLog(item)"
>
<List />
</el-icon>
</div>
</div>
</el-col>
</el-row>
</div>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[100, 200, 300, 400, 500]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin: 10px auto 0; text-align: right"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></ElPagination>
</div>
</div>
<LogDialog
:title="editForm.id ? '编辑发货地址' : '新增发货地址'"
v-model="dialogVisible"
dialogWidth="1000px"
@close="cancelFn"
>
<CustomizeForm
ref="editFormRef"
v-model="editForm"
:config="formConfig"
formItemWidth="50%"
>
</CustomizeForm>
<template #footer>
<div style="text-align: center">
<ElButton @click="cancelFn">取消</ElButton>
<ElButton type="primary" @click="save">保存</ElButton>
</div>
</template>
</LogDialog>
<LogDialog
title="日志"
v-model="logDialogVisible"
@close="logDialogVisible = false"
>
<div v-for="item in logList" :key="item.id" style="margin-bottom: 8px">
<span style="margin-right: 10px">{{ item.createTime }}</span>
<span>{{ item.description }}</span>
</div>
</LogDialog>
</template>
<script setup lang="tsx">
defineOptions({
name: 'ShippingAddress',
})
import {
getAddressByIdList,
addAddress,
updateAddress,
getLogisticsLog,
deleteAddressByIds,
} from '@/api/logistics'
import { IFormConfig } from '@/components/CustomizeForm.tsx'
import LogDialog from './components/LogDialog.tsx'
import CustomizeForm from '@/components/CustomizeForm.tsx'
import { Edit, Delete, List } from '@element-plus/icons-vue'
import { debounce } from 'lodash-es'
import { ShippingAddressObj } from './types/shippingAddress.ts'
import usePageList from '@/utils/hooks/usePageList'
import { useValue } from './hooks/useValue'
import { showConfirm } from '@/utils/ui'
const [editForm, resetEditForm] = useValue<ShippingAddressObj>({
swDefault: false,
})
const {
currentPage,
pageSize,
total,
data: tableData,
refresh: search,
onCurrentPageChange: handleCurrentChange,
onPageSizeChange: handleSizeChange,
} = usePageList({
query: (page, pageSize) =>
getAddressByIdList({
pageSize: pageSize,
currentPage: page,
}).then(({ data }) => {
console.log(130, data)
return data
}),
})
const dialogVisible = ref(false)
const logDialogVisible = ref(false)
const editFormRef = ref<InstanceType<typeof CustomizeForm> | null>(null)
const formConfig = ref<IFormConfig[]>([
{
prop: 'shipperName',
type: 'input',
label: '发货人姓名',
attrs: {
placeholder: '请输入发货人姓名',
},
rules: [{ required: true, message: '请输入发货人姓名' }],
},
{
prop: 'addressLine1',
type: 'input',
label: '地址 1',
attrs: {
width: '100%',
placeholder: '请输入地址 1',
},
rules: [{ required: true, message: '请输入地址1' }],
},
{
prop: 'addressLine2',
type: 'input',
label: '地址 2',
attrs: {
width: '100%',
placeholder: '请输入地址 2',
},
},
{
prop: 'addressLine3',
type: 'input',
label: '地址 3',
attrs: {
width: '100%',
placeholder: '请输入地址 3',
},
},
{
prop: 'city',
type: 'input',
label: '城市',
attrs: {
// options: [],
placeholder: '请输入城市',
},
rules: [{ required: true, message: '请输入城市' }],
},
{
prop: 'cityCode',
type: 'input',
label: '城市编码',
attrs: {
// options: [],
placeholder: '请输入城市编码',
},
},
{
prop: 'district',
type: 'input',
label: '区',
attrs: {
// options: [],
placeholder: '请选择区',
},
},
{
prop: 'districtCode',
type: 'input',
label: '区编码',
attrs: {
// options: [],
placeholder: '请选择区',
},
},
{
prop: 'postalCode',
type: 'input',
label: '邮编',
attrs: {
placeholder: '请输入邮编',
},
},
{
prop: 'stateProvince',
type: 'input',
label: '州/省',
attrs: {
// options: [],
placeholder: '请输入州/省',
},
},
{
prop: 'stateProvinceAbbr',
type: 'input',
label: '州/省简称',
attrs: {
// options: [],
placeholder: '请输入州/省简称',
},
},
{
prop: 'countryName',
type: 'input',
label: '国家名称',
attrs: {
// options: [],
placeholder: '请输入国家名称',
},
rules: [{ required: true, message: '请输入国家名称' }],
},
{
prop: 'countryCode',
type: 'input',
label: '国家代码',
attrs: {
// options: [],
placeholder: '请输入国家代码',
},
},
{
prop: 'phoneNumber',
type: 'input',
label: '电话',
attrs: {
placeholder: '请输入电话',
},
},
{
prop: 'swDefault',
type: 'switch',
label: '是否默认',
},
])
const loading = ref(false)
/**
* @description: 取消按钮
*/
function cancelFn() {
dialogVisible.value = false
editFormRef.value?.resetFields()
resetEditForm()
}
/**
* @description: 删除按钮
*/
async function deleteAddress(item: ShippingAddressObj) {
try {
await showConfirm('是否删除发货地址', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const ids = { ids: [item.id].join(',') }
console.log(381, ids)
await deleteAddressByIds(ids)
ElMessage({
message: '删除成功',
type: 'success',
})
search()
} catch (e) {
search()
// showError(e)
}
}
/**
* @description: 编辑按钮
*/
async function editAddress(item: ShippingAddressObj) {
try {
editForm.value = { ...item }
console.log(395, editForm.value)
dialogVisible.value = true
} catch (e) {
console.log(e)
}
}
/**
* @description: 检查数据
*/
async function checkData() {
const [isValid, postData] = await Promise.all([
new Promise<boolean>((resolve) => {
editFormRef.value
?.validate()
.then(() => resolve(true))
.catch((err) => {
resolve(false)
console.log(err)
})
}),
new Promise<ShippingAddressObj>((resolve) => {
const params = { ...editForm.value }
resolve(params)
}),
])
console.log(isValid, postData)
return { isValid, postData }
}
/**
* @description: 保存按钮
*/
const save = debounce(async () => {
const { isValid, postData } = await checkData()
if (isValid) {
try {
if (!postData.id) {
await addAddress({
...postData,
})
} else {
await updateAddress({
...postData,
})
}
ElMessage({
message: postData.id ? '更新成功' : '新增成功',
type: 'success',
offset: window.innerHeight / 2,
})
cancelFn()
search()
} catch (e) {
return
}
}
}, 400)
/**
* @description: 新增按钮打开弹窗
*/
function addDialog() {
dialogVisible.value = true
}
/**
* @description: 多选删除按钮
*/
async function deleteSection(arr: number[]) {
try {
await showConfirm('是否删除发货地址', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const ids = { ids: arr.join(',') }
await deleteAddressByIds(ids)
ElMessage({
message: '删除成功',
type: 'success',
})
search()
} catch (e) {
search()
// showError(e)
}
}
/**
* @description: 日志弹窗
*/
interface LogList {
id?: number
createTime?: string
description?: string
}
const logList = ref<LogList[]>([])
async function showLog(row: ShippingAddressObj) {
logDialogVisible.value = true
try {
const { data } = await getLogisticsLog({
logType: 'logistics_address',
relaId: row.id as number,
})
logList.value = data
} catch (error) {
console.log(error)
}
}
</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;
}
:deep() {
.rowClass {
background-color: red;
}
}
.address-item {
box-sizing: border-box;
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ececec;
display: flex;
align-items: center;
justify-content: space-between;
.check {
margin-right: 10px;
}
.left {
flex: 1;
flex-shrink: 0;
overflow: hidden;
margin-right: 10px;
}
.address {
margin-bottom: 10px;
font-size: 14px;
overflow: hidden;
/* 确保超出的文本被裁剪 */
white-space: nowrap;
/* 确保文本在一行内显示 */
text-overflow: ellipsis;
/* 超出的文本显示为省略号 */
width: 100%;
}
.action {
.iconView {
}
}
.name {
display: flex;
align-items: center;
margin-bottom: 10px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
p {
flex: 1;
flex-shrink: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin: 0 10px;
font-size: 14px;
}
b {
font-size: 17px;
}
span {
margin: 0 10px;
font-size: 14px;
}
}
}
</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 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
ruleId?: string | number
ruleList?: ruleRefObj[]
uinuinWarehouseId?: number | string
}
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
}
export interface platformObj {
platform: string
logisticsName: string | number
showPlatform: (string | number)[]
}
interface ruleRefObj {
ruleId: string | number
ruleName: string | number
}
export interface LogisticsPartitionObj {
zoneName?: string
logistics?: string
codePrefix?: string
logisticsId?: string
}
export interface LogisticsQuotation {
factoryId?: number
id?: number
logistics?: string
logisticsId?: number
rate?: number
rateG?: number
rateKg?: number
rateType?: string
seq?: number
unit?: string
zone1?: string
zone2?: string
zone3?: string
zone4?: string
zone5?: string
zone6?: string
zone7?: string
zone8?: string
zone9?: string
logisticsQuotationList?: LogisticsQuotation[]
}
export interface ShippingAddressObj {
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
checked?: boolean
}
......@@ -971,7 +971,7 @@
</el-dialog>
<right-menu
ref="rightMenuRef"
:show_copy_shop_number="
:show-copy-shop-number="
['IN_PRODUCTION', 'TO_BE_CONFIRMED', 'WAIT_SHIPMENT'].includes(status)
"
@change="rightChange"
......
......@@ -7,8 +7,8 @@
<button @click="$emit('change', 'clear_check')">取消选择</button>
<button @click="$emit('change', 'copy_code')">复制选中生产单号</button>
<button v-if="show_copy_shop_number" @click="$emit('change', 'copy_shopNumber')">复制选中店铺单号</button>
<button v-if="show_copy_shop_number" @click="$emit('change', 'count')">统计数量</button>
<button v-if="showCopyShopNumber" @click="$emit('change', 'copy_shopNumber')">复制选中店铺单号</button>
<button v-if="showCopyShopNumber" @click="$emit('change', 'count')">统计数量</button>
</div>
<!-- </div> -->
</template>
......@@ -20,7 +20,7 @@ interface E{
y:number
}
defineProps({
show_copy_shop_number:{
showCopyShopNumber:{
type:Boolean,
default:true
}
......
......@@ -132,7 +132,7 @@
<span v-if="status === 'EXCEPTION_ORDER'" class="item">
<ElButton type="success" @click="updateOrder"> 更新 </ElButton>
</span>
<span v-if="status === 'TO_BE_CONFIRMED'" class="item">
<span v-if="status === 'PICKING'" class="item">
<ElButton type="primary" @click="printProductionOrder">
打印生产单
</ElButton>
......@@ -179,9 +179,23 @@
转至待确认
</ElButton>
</span>
<span class="item">
<ElButton type="primary" @click="downloadMaterial">下载素材</ElButton>
</span>
<span
v-if="
status === 'TO_BE_CONFIRMED' ||
status === 'PICKING' ||
status === 'STOCK_OUT' ||
status === 'EXCEPTION_ORDER'
"
class="item"
>
<ElButton type="success" @click="refreshMaterial">
刷新素材图
</ElButton>
</span>
</div>
<div
v-if="status !== 'IN_PRODUCTION'"
......@@ -197,7 +211,6 @@
:selectionable="true"
:paginated-data="tableData"
:cell-style="onCellStyle"
:row-style="getRowStyle"
@selection-change="handleSelectionChange"
>
<template #goods="{ row }">
......@@ -223,10 +236,8 @@
<span class="goods-item-info-item-value">
{{ item.baseSku }}
</span>
<el-icon class="icon" @click="copy(item.baseSku || '')"
>
<DocumentCopy
/>
<el-icon class="icon" @click="copy(item.baseSku || '')">
<DocumentCopy />
</el-icon>
</div>
<div class="goods-item-info-item">
......@@ -234,10 +245,11 @@
<span class="goods-item-info-item-value">
{{ item.variantSku }}
</span>
<el-icon class="icon" @click="copy(item.variantSku || '')"
<el-icon
class="icon"
@click="copy(item.variantSku || '')"
>
<DocumentCopy
/>
<DocumentCopy />
</el-icon>
</div>
<div class="goods-item-info-item">
......@@ -260,8 +272,7 @@
style="color: #e6a23c"
@click="handleUpdateRemark(item)"
>
<EditPen
/>
<EditPen />
</el-icon>
</div>
</div>
......@@ -278,13 +289,12 @@
class="icon"
@click="copy(item.factorySubOrderNumber || '')"
>
<DocumentCopy
/>
<DocumentCopy />
</el-icon>
</div>
<div class="goods-item-info-item">
<span class="goods-item-info-item-label"
>第三方生产单号:</span
>第三方生产单号:</span
>
<span
class="goods-item-info-item-value"
......@@ -296,8 +306,7 @@
class="icon"
@click="copy(item.thirdSubOrderNumber || '')"
>
<DocumentCopy
/>
<DocumentCopy />
</el-icon>
</div>
<div class="goods-item-info-item">
......@@ -315,7 +324,7 @@
</div>
<div class="goods-item-info">
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">单价:</span>
<span class="goods-item-info-item-label">价格:</span>
<span class="goods-item-info-item-value">
{{ item.productPrice }}()
</span>
......@@ -341,22 +350,33 @@
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">{{
status === 'TO_BE_CONFIRMED'
? '未生产数量:'
: status === 'EXCEPTION_ORDER'
? '数量:'
: '已生产数量:'
}}</span>
status === 'EXCEPTION_ORDER' ||
status === 'PICKING' ||
status === 'TO_BE_CONFIRMED' ||
status === 'STOCK_OUT'
? '数量:'
: '已生产数量:'
}}</span>
<span class="goods-item-info-item-value">
{{
status === 'TO_BE_CONFIRMED'
? item.notPassNum
: status === 'EXCEPTION_ORDER'
? item.num
: item.passNum
status === 'EXCEPTION_ORDER' ||
status === 'PICKING' ||
status === 'TO_BE_CONFIRMED' ||
status === 'STOCK_OUT'
? item.num
: item.passNum
}}
</span>
</div>
<div class="goods-item-info-item">
<span class="goods-item-info-item-label">克重:</span>
<span
v-if="item.weight"
class="goods-item-info-item-value"
>
{{ item.weight }}g
</span>
</div>
</div>
</div>
</div>
......@@ -376,8 +396,7 @@
class="icon"
@click="copy(row.factoryOrderNumber || '')"
>
<DocumentCopy
/>
<DocumentCopy />
</el-icon>
</div>
<div class="order-detail-item">
......@@ -388,10 +407,8 @@
>
{{ row.thirdOrderNumber }}
</span>
<el-icon class="icon" @click="copy(row.thirdOrderNumber || '')"
>
<DocumentCopy
/>
<el-icon class="icon" @click="copy(row.thirdOrderNumber || '')">
<DocumentCopy />
</el-icon>
</div>
<div class="order-detail-item">
......@@ -399,13 +416,59 @@
<span class="order-detail-item-value" :title="row.shopNumber">
{{ row.shopNumber }}
</span>
<el-icon class="icon" @click="copy(row.shopNumber || '')"
>
<DocumentCopy
/>
<el-icon class="icon" @click="copy(row.shopNumber || '')">
<DocumentCopy />
</el-icon>
</div>
<div class="order-detail-item">
<span class="order-detail-item-label">总克重:</span>
<span v-if="row.weight" class="order-detail-item-value">
{{ row.weight }}g
</span>
</div>
<div
v-if="row.status !== 'TO_BE_CONFIRMED'"
class="order-detail-item"
>
<span class="order-detail-item-label">生产端:</span>
<span
class="order-detail-item-value"
:title="row.productionClient"
>
{{ row.productionClient }}
</span>
</div>
<div
v-if="
row.productionClient === 'JOMALL' &&
row.status !== 'TO_BE_CONFIRMED'
"
class="order-detail-item"
>
<span class="order-detail-item-label">发货仓库:</span>
<span
class="order-detail-item-value"
:title="row.warehouseName"
>
{{ row.warehouseName }}
</span>
</div>
<div
v-if="
row.productionClient === 'JOMALL' &&
row.status !== 'TO_BE_CONFIRMED'
"
class="order-detail-item"
>
<span class="order-detail-item-label">物流方式:</span>
<span
class="order-detail-item-value"
:title="row.logisticsWayName"
>
{{ row.logisticsWayName }}
</span>
</div>
<div class="order-detail-item">
<span class="order-detail-item-label">收货人:</span>
<span class="order-detail-item-value" :title="row.receiverName">
{{ row.receiverName }}
......@@ -443,11 +506,20 @@
<template #price="{ row }">
<div class="order-price-box">
<div class="order-price-item">
<span class="order-price-item-label">总价:</span>
<span class="order-price-item-label">商品总价:</span>
<span class="order-price-item-value">
{{ row.totalAmount }}()
</span>
</div>
<div
v-if="row.status !== 'TO_BE_CONFIRMED'"
class="order-price-item"
>
<span class="order-price-item-label">物流运费:</span>
<span class="order-price-item-value">
{{ row.payFreight }}($)
</span>
</div>
</div>
</template>
<template #time="{ row }">
......@@ -497,6 +569,7 @@
<div class="operate-box">
<span class="operate-item">
<ElButton
v-if="!row.expressSheet && row.status === 'TO_BE_CONFIRMED'"
link
type="primary"
@click="confirm(row)"
......@@ -585,7 +658,7 @@
<div class="grid-container">
<div class="grid-item" title="商品名称">
<span class="grid-item-value"
>{{ cardItem?.productName }}
>{{ cardItem?.productName }}
</span>
</div>
<div class="grid-item" title="未生产数量">
......@@ -652,14 +725,26 @@
></ElPagination>
</div>
</div>
<right-menu ref="rightMenuRef" @change="rightChange" />
<el-dialog v-model="confirmDialogShow" :close-on-click-modal="false" title="确认">
<div class="production-client" style="margin-bottom:10px;display: flex;align-items: center;gap: 8px">
<label style="white-space: nowrap;" for="">生产端:</label>
<right-menu
ref="rightMenuRef"
:show-copy-shop-number="false"
@change="rightChange"
/>
<el-dialog
v-model="confirmDialogShow"
:close-on-click-modal="false"
title="确认"
>
<div
class="production-client"
style="margin-bottom: 10px; display: flex; align-items: center; gap: 8px"
>
<label style="white-space: nowrap" for="">生产端:</label>
<ElSelect
v-model="productionClientValue"
clearable
placeholder="请选择生产端"
@change="handleProductionClientChange"
>
<ElOption
v-for="(item, index) in productionClient"
......@@ -669,27 +754,55 @@
></ElOption>
</ElSelect>
</div>
<el-table @selection-change="selectionChange" height="400px" :data="confirmData" border>
<el-table-column label="" width="60" align="center" type="selection"></el-table-column>
<el-table-column label="序号" width="60" align="center" type="index"></el-table-column>
<el-table-column label="物流名称" align="center" prop="logisticsWayName"></el-table-column>
<el-table-column label="发货仓库" align="center" prop="warehouseName"></el-table-column>
<el-table-column label="物流编码" align="center" prop="logisticsWayCode"></el-table-column>
<el-table-column label="所在分区" align="center" prop="partition"></el-table-column>
<el-table
v-if="productionClientValue === 'JOMALL'"
height="400px"
class="production-client-table"
:data="confirmData"
border
highlight-current-row
@current-change="currentChange"
>
<el-table-column
label="序号"
width="60"
align="center"
type="index"
></el-table-column>
<el-table-column
label="物流名称"
align="center"
prop="logisticsWayName"
></el-table-column>
<el-table-column
label="发货仓库"
align="center"
prop="warehouseName"
></el-table-column>
<el-table-column
label="物流编码"
align="center"
prop="logisticsWayCode"
></el-table-column>
<el-table-column
label="所在分区"
align="center"
prop="partition"
></el-table-column>
<el-table-column label="状态" align="center">
<template #default="{row}">
<template #default="{ row }">
<b v-if="row.status" style="color: green">成功</b>
<b v-else-if="!row.status" style="color: red">失败</b>
</template>
</el-table-column>
<el-table-column label="预计运费($)" align="center" prop="payFreight">
<!-- <template #default="{row}">-->
<!-- {{row.payFreight || '0.00'}}-->
<!-- </template>-->
<!-- <template #default="{row}">-->
<!-- {{row.payFreight || '0.00'}}-->
<!-- </template>-->
</el-table-column>
</el-table>
<template #footer>
<el-button @click="confirmDialogShow=false">取消</el-button>
<el-button @click="confirmDialogShow = false">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</template>
</el-dialog>
......@@ -702,7 +815,7 @@
<LogList :log-list="logList" />
</el-dialog>
<FastProduction
v-model:detailVisible="detailVisible"
v-model:detail-visible="detailVisible"
:current-status="status"
:detail-data="detailData"
:fast-key="fastKey"
......@@ -724,7 +837,7 @@
>
<template #header>
<div class="dialog-header">
<span style="margin-right: 8px;font-size: 18px">确认</span>
<span style="margin-right: 8px; font-size: 18px">确认</span>
<el-tooltip content="默认最低运费分派">
<el-icon color="red">
<WarningFilled />
......@@ -732,8 +845,11 @@
</el-tooltip>
</div>
</template>
<div class="production-client" style="display: flex;align-items: center;gap: 8px">
<label style="white-space: nowrap;" for="">生产端:</label>
<div
class="production-client"
style="display: flex; align-items: center; gap: 8px"
>
<label style="white-space: nowrap" for="">生产端:</label>
<ElSelect
v-model="productionClientValue"
clearable
......@@ -750,7 +866,7 @@
<template #footer>
<div class="dialog-footer">
<ElButton @click="productionClientVisible = false">取消</ElButton>
<ElButton type="primary" @click="submit">确认</ElButton>
<ElButton type="primary" @click="submitConfirm">确认</ElButton>
</div>
</template>
</ElDialog>
......@@ -776,7 +892,10 @@ import {
printPrintOrderApi,
stockOutCheckApi,
toBeConfirmedApi,
loadWarehouseListApi, getLogisticsCalculation, LogisticsData,
loadWarehouseListApi,
getLogisticsCalculation,
refreshMaterialApi,
LogisticsData,
} from '@/api/podUsOrder'
import TableView from '@/components/TableView.vue'
import {
......@@ -801,11 +920,12 @@ import PodMakeOrder from './PodMakeOrder.vue'
import { OrderData } from '@/types/api/podMakeOrder'
import useLodop, { LODOPObject } from '@/utils/hooks/useLodop'
import dayjs from 'dayjs'
import rightMenu from '../pod/rightMenu.vue'
declare global {
interface Window {
ActiveXObject: {
new(type: string): XMLHttpRequest
new (type: string): XMLHttpRequest
}
VBS_BinaryToArray: {
(data: unknown): { toArray(): number[] }
......@@ -1050,35 +1170,52 @@ const {
}
},
})
const currentChange = (row: LogisticsData) => {
if (row) {
confirmSelectionData.value = [row]
}
}
const handleProductionClientChange = async (v: string) => {
if (v !== 'RIIN') {
if (confirmRowData.value) {
const { data } = await getLogisticsCalculation(confirmRowData.value?.id)
confirmData.value = data
}
}
}
const confirm = async (row: ProductList) => {
confirmDialogShow.value = true
productionClientValue.value = ''
confirmRowData.value = row
confirmData.value = []
confirmSelectionData.value = []
const { data } = await getLogisticsCalculation(row.id)
confirmData.value = data
}
const selectionChange = (data:LogisticsData[]) => {
confirmSelectionData.value = data
}
const handleConfirm = async () => {
if (!productionClientValue.value) {
return ElMessage.warning('请选择生产端')
}
if (confirmSelectionData.value.length!==1) {
return ElMessage.warning('请选择一条物流方式')
}
if(confirmSelectionData.value.some(el=>!el.status)){
return ElMessage.warning('请选择状态为成功的物流方式')
if (productionClientValue.value !== 'RIIN') {
if (confirmSelectionData.value.length !== 1) {
return ElMessage.warning('请选择一条物流方式')
}
if (confirmSelectionData.value.some((el) => !el.status)) {
return ElMessage.warning('请选择状态为成功的物流方式')
}
}
const id = confirmRowData.value?.id
const res = await confirmOrderApi([Number(id)], productionClientValue.value, 'customize',confirmSelectionData.value[0])
const res = await confirmOrderApi(
[Number(id)],
productionClientValue.value,
'customize',
confirmSelectionData.value[0] || null,
)
if (res.code !== 200) return
ElMessage.success('操作成功')
confirmDialogShow.value = false
search()
loadTabData()
await loadTabData()
}
const copy = (text: string) => {
navigator.clipboard.writeText(text)
......@@ -1104,16 +1241,10 @@ const handleUpdateRemark = async (item: ProductList) => {
const productionClientValue = ref('')
const productionClient = ref<ProductionClient[]>()
const productionClientVisible = ref(false)
let confirmBtn = false
let pickingBtn = false
let printBtn = false
const confirmOrder = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择数据')
}
confirmBtn = true
pickingBtn = false
printBtn = false
productionClientValue.value = ''
productionClientVisible.value = true
}
......@@ -1126,23 +1257,17 @@ const loadProductionClient = async () => {
console.error(e)
}
}
const submit = async () => {
if (confirmBtn) {
await submitConfirm()
} else if (printBtn) {
await submitPrintPickingOrder()
} else if (pickingBtn) {
await submitPickingOrder()
}
}
const submitConfirm = async () => {
const ids = selection.value.map((item) => item.id)
try {
const res = await confirmOrderApi(ids, productionClientValue.value, 'minimumCost')
const res = await confirmOrderApi(
ids,
productionClientValue.value,
'minimumCost',
)
if (res.code !== 200) return
ElMessage.success('操作成功')
productionClientVisible.value = false
confirmBtn = false
search()
loadTabData()
} catch (e) {
......@@ -1191,46 +1316,36 @@ const printPickingOrder = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择数据')
}
pickingBtn = false
printBtn = true
confirmBtn = false
productionClientVisible.value = true
productionClientValue.value = ''
}
const pickingComplete = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择数据')
}
pickingBtn = true
printBtn = false
confirmBtn = false
productionClientVisible.value = true
productionClientValue.value = ''
}
const submitPrintPickingOrder = async () => {
const orderIds = selection.value.map((item) => item.id)
try {
const res = await printPrintOrderApi(orderIds, productionClientValue.value)
const res = await printPrintOrderApi(orderIds)
if (res.code !== 200) return
ElMessage.success('操作成功')
window.open(filePath + res.message)
printBtn = false
} catch (e) {
console.error(e)
}
}
const submitPickingOrder = async () => {
const pickingComplete = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择数据')
}
try {
await ElMessageBox.confirm('确定完成拣胚完成吗?', '提示', {
cancelButtonText: '取消',
confirmButtonText: '确认',
type: 'warning',
})
} catch {
return
}
const orderIds = selection.value.map((item) => item.id)
try {
const res = await printPickingOrderApi(
orderIds,
productionClientValue.value,
)
const res = await printPickingOrderApi(orderIds)
if (res.code !== 200) return
ElMessage.success('操作成功')
search()
loadTabData()
printBtn = false
} catch (e) {
console.error(e)
}
......@@ -1332,10 +1447,10 @@ const downloadMaterial = async () => {
.map((item: ProductList) => item.id)
.filter((id): id is number => id !== undefined)
} else {
selectedIds = selection.value
.map((item: PodUsOrderListData) => item.productList)
selectedIds = (selection.value as PodUsOrderListData[])
.map((item) => item?.productList)
.flat()
.map((e) => e?.id)
.map((item) => item?.id)
.filter((id): id is number => id !== undefined)
}
if (selectedIds.length === 0) {
......@@ -1642,16 +1757,16 @@ const onFastRefresh = () => {
loadTabData()
search()
}
// 修改行样式方法
const getRowStyle = ({ row }: { row: PodUsOrderListData }) => {
if (stockOutSuccessIds.value.includes(row.id)) {
return {
backgroundColor: '#f0f9eb',
color: '#67c23a',
}
}
return {}
}
// // 修改行样式方法
// const getRowStyle = ({ row }: { row: PodUsOrderListData }) => {
// if (stockOutSuccessIds.value.includes(row.id)) {
// return {
// backgroundColor: '#f0f9eb',
// color: '#67c23a',
// }
// }
// return {}
// }
// 添加表格ref
const tableRef = ref()
const warehouseList = ref<WarehouseListData[]>([])
......@@ -1664,6 +1779,21 @@ const loadWarehouseList = async () => {
console.error(e)
}
}
const refreshMaterial = async () => {
if (selection.value.length === 0) {
return ElMessage.warning('请选择数据')
}
try {
const res = await refreshMaterialApi(
selection.value.map((item) => item.id).join(','),
)
if (res.code !== 200) return
ElMessage.success('刷新成功')
search()
} catch (e) {
console.error(e)
}
}
onMounted(() => {
loadTabData()
getUserMark()
......@@ -1794,4 +1924,16 @@ onMounted(() => {
.factory-sub-order-number {
font-size: 12px;
}
.production-client-table {
::v-deep(.current-row > td.el-table__cell) {
background-color: green;
color: white;
& > td.el-table__cell {
color: white !important;
background-color: green;
}
}
}
</style>
......@@ -566,7 +566,6 @@ getWarehouse()
::v-deep(.el-tabs) {
height: 100%;
display: flex;
flex-direction: column;
.el-tabs__content {
flex: 1;
......
......@@ -3,13 +3,16 @@ import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
base: '/',
plugins: [
vue(),
vueJsx(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
resolvers: [ElementPlusResolver()],
}),
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