Commit 13088808 by linjinhong Committed by qinjianhui

添加物流方式

parent 7ebb4e13
...@@ -82,3 +82,9 @@ declare global { ...@@ -82,3 +82,9 @@ declare global {
const watchPostEffect: typeof import('vue')['watchPostEffect'] const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect'] 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,45 +7,10 @@ export {} ...@@ -7,45 +7,10 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
AmountInput: typeof import('./src/components/Form.vue/AmountInput.vue')['default']
CommonCard: typeof import('./src/components/CommonCard.vue')['default'] CommonCard: typeof import('./src/components/CommonCard.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton'] DatePicker: typeof import('./src/components/Form.vue/DatePicker.vue')['default']
ElCard: typeof import('element-plus/es')['ElCard'] DateRangePicker: typeof import('./src/components/Form.vue/DateRangePicker.vue')['default']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCol: typeof import('element-plus/es')['ElCol']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
Icon: typeof import('./src/components/Icon.vue')['default'] Icon: typeof import('./src/components/Icon.vue')['default']
ImageView: typeof import('./src/components/ImageView.vue')['default'] ImageView: typeof import('./src/components/ImageView.vue')['default']
LogList: typeof import('./src/components/LogList.vue')['default'] LogList: typeof import('./src/components/LogList.vue')['default']
...@@ -53,6 +18,8 @@ declare module 'vue' { ...@@ -53,6 +18,8 @@ declare module 'vue' {
RenderColumn: typeof import('./src/components/RenderColumn.vue')['default'] RenderColumn: typeof import('./src/components/RenderColumn.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
SearchForm: typeof import('./src/components/SearchForm.vue')['default']
Select: typeof import('./src/components/Form.vue/Select.vue')['default']
ShipmentOrderDetail: typeof import('./src/components/ShipmentOrderDetail.vue')['default'] ShipmentOrderDetail: typeof import('./src/components/ShipmentOrderDetail.vue')['default']
SplitDiv: typeof import('./src/components/splitDiv/splitDiv.vue')['default'] SplitDiv: typeof import('./src/components/splitDiv/splitDiv.vue')['default']
TableRightMenu: typeof import('./src/components/TableRightMenu.vue')['default'] TableRightMenu: typeof import('./src/components/TableRightMenu.vue')['default']
...@@ -61,7 +28,4 @@ declare module 'vue' { ...@@ -61,7 +28,4 @@ declare module 'vue' {
UploadImage: typeof import('./src/components/UploadImage.vue')['default'] UploadImage: typeof import('./src/components/UploadImage.vue')['default']
WangEditor: typeof import('./src/components/WangEditor.vue')['default'] WangEditor: typeof import('./src/components/WangEditor.vue')['default']
} }
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
} }
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
"@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1", "@typescript-eslint/parser": "^7.1.1",
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^4.1.0", "@vitejs/plugin-vue-jsx": "^4.2.0",
"@vue/eslint-config-typescript": "^12.0.0", "@vue/eslint-config-typescript": "^12.0.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
......
<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>
</el-input>
</template>
<script lang="ts" setup>
import { isNumFloat } from '@/utils/validate'
const props = withDefaults(
defineProps<{
modelValue?: string | number
suffix?: string
placeholder?: string
size?: '' | 'default' | 'small' | 'large'
hasSuffix?: boolean
isDisabled?: boolean
}>(),
{
modelValue: '',
suffix: '元',
placeholder: '请输入费用',
size: 'default',
hasSuffix: true,
isDisabled: false,
},
)
const emits = defineEmits<{
(e: 'update:modelValue', data: string | number)
(e: 'input', data: string | number)
}>()
const val = ref<string | number>(props.modelValue)
const iptFn = (e) => {
const bool = isNumFloat(e) // 只允许输入数字和最多一个小数点
if (e === '') {
val.value = null
} else {
if (bool) {
const parts = e.split('.') // 将输入的数字按小数点分割成整数部分和小数部分
if (parts[1] && parts[1].length > 2) {
// 如果小数部分超过两位,则只保留两位小数
val.value = `${parts[0]}.${parts[1].slice(0, 2)}`
} else {
val.value = e
}
} else {
const decimalCount = (e.match(/\./g) || []).length // 计算小数点的个数
if (decimalCount > 1) {
// 如果小数点个数大于1,则将多余的小数点替换为空字符串
val.value = e.replace(/\./g, (match, offset) => {
return offset === e.lastIndexOf('.') ? '.' : ''
})
} else {
val.value = e.replace(/[^\d\.]/g, '')
}
}
}
emits('input', val.value)
}
watch(
() => val.value,
(newVal) => {
if (newVal === '') {
emits('update:modelValue', null)
} else {
emits('update:modelValue', newVal)
}
},
)
watch(
() => props.modelValue,
(newVal: string | number) => {
if (newVal !== undefined) {
val.value = newVal
}
},
{
immediate: true,
},
)
</script>
<style lang="scss" scoped>
:deep() {
.el-form-item__content {
line-height: 0 !important;
}
}
</style>
<template>
<el-date-picker
ref="dateRef"
:type="props.type"
:placeholder="props.placeholder"
value-format="YYYY-MM-DD"
class="datePicker"
:popper-class="!props.isNeedTime ? '' : 'popperClass'"
:teleported="true"
:disabled-date="disabledDate"
/>
</template>
<script lang="ts" setup>
const dateRef = ref(null)
const props = withDefaults(
defineProps<{
placeholder?: string
type?: string
isNeedTime?: boolean
rowData?: any
isrowDate?: boolean
newDisableDate?: (...arg) => boolean
}>(),
{
placeholder: '请选择日期',
type: 'date',
isNeedTime: false,
isrowDate: false,
rowData: null,
newDisableDate: null,
},
)
// 用于存储动态的禁用日期逻辑
function disabledDate(time) {
if (props.isrowDate && props.newDisableDate) {
return props.newDisableDate(time, props.rowData)
}
}
</script>
<style lang="scss" scoped>
.popperClass .el-time-spinner__wrapper {
width: 100% !important;
}
.popperClass .el-scrollbar:nth-of-type(2) {
display: none !important;
}
</style>
<template>
<el-date-picker
ref="dateRangePickerRef"
class="dateRangePicker"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 220px"
:popper-class="!props.isNeedTime ? '' : 'popperClass'"
:teleported="true"
v-bind="attrs"
/>
</template>
<script lang="ts" setup>
const props = defineProps({
isNeedTime: {
type: Boolean,
default: false,
},
})
const attrs = useAttrs()
function disabledDate(time) {
// 获取当前日期
const today = new Date()
// 将时间戳转换为年月日格式的字符串
const year = today.getFullYear()
const month = today.getMonth()
const day = today.getDate()
const currentDate = new Date(year, month, day)
// 禁止选择同一天
return time.getTime() === currentDate.getTime()
}
const dateRangePickerRef = ref(null)
defineExpose({
dateRangePickerRef,
})
</script>
<style lang="scss">
.popperClass .el-time-spinner__wrapper {
width: 100% !important;
}
.popperClass .el-scrollbar:nth-of-type(2) {
display: none !important;
}
</style>
<template>
<!-- <el-row style="flex-wrap: nowrap"> -->
<el-select
v-model="model"
class="select"
placeholder="请选择"
:empty-values="[null, undefined, '']"
loading-text="加载中..."
v-bind="{
...getEvent,
}"
:loading="loading"
@change="changeFn"
>
<el-option
v-for="item in getOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<!-- </el-row> -->
</template>
<script lang="ts" setup>
import { cloneDeep, debounce } from 'lodash-es'
interface IOption {
[key: string]: any
}
const props = withDefaults(
defineProps<{
options: Array<IOption>
value?: string
label?: string
hasAllOptions?: boolean
labelIsValue?: boolean // value 使用label字段
initChange?: boolean // 首次是否触发change事件
modelValue?: string | number
isRefresh?: boolean
load?: (arg: (val: boolean) => void) => void // focus后再加载数据的
}>(),
{
options: () => [],
value: 'value', // 值字段名 默认字段为value
label: 'label', // 描述字段名 默认字段为label
hasAllOptions: false, // 使用全部下拉选项
labelIsValue: false, // value 是 label
initChange: false,
modelValue: '',
isRefresh: false,
load: null,
},
)
const emits = defineEmits<{
(e: 'change', option: IOption, value: string | number)
(e: 'update:modelValue', data: string | number | undefined)
(e: 'updatedOption')
}>()
const loading = ref<boolean>(false)
const model = ref(props.modelValue)
const allOption = {
value: '',
label: '全部',
}
const fields = reactive({
value: props.value,
label: props.label,
})
// 选项处理
const getOptions = computed(() => {
const value = props.labelIsValue ? fields.label : fields.value
const arr = cloneDeep(props.options)
const data = arr.map((item) => {
!item.value && (item.value = eval(`item.${value}`))
!item.label && (item.label = eval(`item.${fields.label}`))
return item
})
if (props.hasAllOptions) {
data.unshift(allOption)
return data
}
return data
})
const changeFn = (id) => {
const value = props.labelIsValue ? fields.label : fields.value
const findItem = props.options.find((item) => item[value] === id)
if (findItem) {
emits('change', findItem, id)
}
}
watch(model, (val) => {
emits('update:modelValue', val)
})
if (props.initChange) {
watch([() => props.modelValue, getOptions], ([val, getOptionsVal]) => {
if (val !== 0) {
model.value = val
getOptionsVal.length && changeFn(val)
}
})
} else {
watch(
() => props.modelValue,
(val) => {
// if (val !== 0) {
model.value = val
// }
},
)
}
const handleClickFn = debounce(() => {
model.value = null
emits('updatedOption')
}, 500)
const FocusFn = () => {
try {
props.load &&
props.load((val: boolean) => {
loading.value = val
})
} catch (err) {
console.log(err)
}
}
const getEvent = computed(() => {
let result = {}
if (props.load) {
result = {
onFocus: FocusFn,
}
}
return result
})
</script>
<style lang="scss" scoped>
.select {
width: 100%;
height: 100%;
}
.iConFont {
margin-left: 8px;
cursor: pointer;
&:hover {
color: #2f9bff;
}
}
</style>
import { defineComponent, PropType, ref, computed, reactive, watch, h } from 'vue'
import type { Component } from 'vue'
import AmountInput from './Form.vue/AmountInput.vue' // 金额输入框
import DateRangePicker from './Form.vue/DateRangePicker.vue' // 时间范围选择器
import DatePicker from './Form.vue/DatePicker.vue' // 单个日期选择器
import Select from './Form.vue/Select.vue' // 普通下拉选择框
import { ElInput, ElForm, ElFormItem, ElButton } from 'element-plus'
import {
ISearchForm,
EComponenetType,
ISeachFormConfig,
EDataType,
} from '@/types/searchType'
// 组件 - 数据类型 映射
const componentDataTypeMap = new Map([
[EComponenetType.amountInput, EDataType.string],
[EComponenetType.input, EDataType.string],
[EComponenetType.dateRangePicker, EDataType.array],
[EComponenetType.datePicker, EDataType.string],
[EComponenetType.select, EDataType.undefined],
])
type ComponentType = Component
const componentConfig: Record<EComponenetType, ComponentType> = {
[EComponenetType.amountInput]: AmountInput,
[EComponenetType.input]: ElInput,
[EComponenetType.dateRangePicker]: DateRangePicker,
[EComponenetType.datePicker]: DatePicker,
[EComponenetType.select]: Select,
}
const styles = {
searchForm: {
position: 'relative',
},
searchFormHeader: {
display: 'flex', marginBottom: '10px'
},
wrapper: {
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-start',
justifyContent: 'space-between',
},
} as const
export default defineComponent({
name: 'SearchForm',
props: {
config: {
type: Array as PropType<ISeachFormConfig[]>,
default: () => []
},
labelWidth: {
type: [String, Number],
default: 58
},
modelValue: {
type: Object as PropType<ISearchForm>,
default: () => ({})
},
getTablelist: {
type: Function as PropType<(...args: unknown[]) => Promise<unknown>>,
default: null
},
size: {
type: String as PropType<'large' | 'default' | 'small'>,
default: 'default'
},
addPermissionName: {
type: [String, Array] as PropType<string | string[]>,
default: ''
},
isSearchBtn: {
type: Boolean,
default: true
},
isResetBtn: {
type: Boolean,
default: true
},
isAddBtn: {
type: Boolean,
default: true
},
isDeleteBtn: {
type: Boolean,
default: true
}
},
emits: [
'search',
'reset',
'fold',
'headerPrimaryBtn',
'headerDefaultBtn',
'update:modelValue',
'clear',
'update:tableFieldForm',
'getExcelData'
],
setup(props, { emit, slots, attrs }) {
const formRef = ref()
const formWrapperRef = ref()
const searchForm = reactive<ISearchForm>(props.modelValue || {})
const selectResult = ref<ISeachFormConfig[]>([])
const componentWidth = computed(() => props.size === 'default' ? 200 : 220)
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 searchFn = () => {
emit('search', searchForm)
}
const resetFn = () => {
emit('reset', {})
}
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
{...getFormAttrs.value}
ref={formRef}
class="w-full"
inline
model={searchForm}
labelPosition="left"
size={props.size}
{...{
onSubmit: handleSubmit
}}
>
<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[item.prop],
'onUpdate:modelValue': (value: unknown) => {
searchForm[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={searchFn}>
查询
</ElButton>
)}
{props.isResetBtn && (
<ElButton class="btn" onClick={resetFn}>
重置
</ElButton>
)}
{props.isAddBtn && (
<ElButton
class="btn"
type="success"
onClick={() => emit('headerPrimaryBtn')}
>
新增
</ElButton>
)}
{props.isDeleteBtn && (
<ElButton
class="btn"
type="danger"
onClick={() => emit('headerDefaultBtn')}
>
删除
</ElButton>
)}
</div>
{slots.ontherBtn?.()}
</div>
</div>
)
}
})
\ No newline at end of file
<!--
配置说明:
hasHeader 控制是否使用头部额外的区域
插槽 header-content 头部内容区域
插槽 header-btn 头部左侧按钮区域 默认: 新增、导出按钮
新增按钮事件: @headerPrimaryBtn
导出按钮事件:@headerDefaultBtn
搜索事件: @search 接收一个搜索对象
重置事件: @reset 接收一个空对象
折叠事件: @fold 接收一个布尔值 true折叠 false 展开
表单组件类型:
input 普通输入框
amountInput 金额输入框
dateRangePicker 时间范围选择器
datePicker 单个日期选择器
select 普通下拉选择框
必传属性:
prop 字段名称
type 表单组件类型
label 该项标题名称
可选属性:
defaultValue 设置初始默认值 v-model传入的时候是无效的,用户可以自行设置默认值
attrs 表单组件的配置属性
表单组件 input 可以配置el-input 的属性 如:placeholder
注意:amountRangeInput 不要设置attrs 属性配置对象
selectV2 和 select 必须要在attrs 中传递一个 options 选项数组
selectV2 和 select 还有三个可选选项:
hasAllOptions 在选项第一个,配置一个全部选项 true为使用,默认为false
value 为选项值的字段名称 默认字段名称为 'value'
label 为选项描述的字段名称 默认字段名称为 'label'
** 注意:select的options也可以通过组件属性的形式进行传入,字段名称为prop+options 的字符串,
如果options是异步的,推荐作为组件的属性进行传入,第一因为响应式数据的更新通过组件的属性会自动传给子组件,
二是可以避免因为频繁修改配置文件中的值而导致组件被重复渲染。
** 可以通过v-model传递一个对象,但是不太推荐,因为即时使用了v-model还是需要指明配置对应的prop,否则prop为undefined
-->
<template>
<div class="searchForm">
<div class="searchForm-header pb-15">
<slot v-if="$slots['header-content']" name="header-content"></slot>
<template v-else>
<div class="wrapper mt-15">
<el-form
v-bind="getFormAttrs"
ref="formRef"
class="w-full"
inline
:model="searchForm"
label-position="left"
:size="props.size"
@submit.prevent
>
<div ref="formWrapperRef" class="formItemWrapper w-full">
<div class="mt--18 flex align-center flex-wrap">
<el-form-item
v-for="(item, index) in selectResult"
:key="index"
ref="formItemRef"
:prop="item.prop"
:label="item.label"
>
<template v-if="item.render">
{{ item.render() }}
</template>
<template v-else>
<component
:is="getConfigComponent(item.type)"
v-model="searchForm[item.prop]"
v-bind="{
...getComponentAttrs(item),
}"
:style="`width: ${componentWidth}px`"
/>
</template>
</el-form-item>
</div>
</div>
</el-form>
</div>
</template>
<div class="flex align-center">
<el-button
v-if="isSearchBtn"
class="btn"
type="primary"
@click="searchFn"
>
查询
</el-button>
<el-button v-if="isResetBtn" class="btn" @click="searchFn">
重置
</el-button>
<el-button
v-if="isDeleteBtn"
class="btn"
type="success"
@click="searchFn"
>
新增
</el-button>
<el-button v-if="deleteBtn" class="btn" type="danger" @click="searchFn">
删除
</el-button>
</div>
<slot name="ontherBtn"></slot>
</div>
</div>
</template>
<script setup lang="tsx" name="SearchForm">
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 } from 'element-plus'
const attrs = useAttrs()
import {
ISearchForm,
EComponenetType,
ISeachFormConfig,
EDataType,
} from '@/types/searchType.ts'
import { IConfig } from '@/components/TenantTable/types/index'
defineOptions({
name: 'SearchForm',
inheritAttrs: false,
})
// 组件 - 数据类型 映射
const componentDataTypeMap = new Map([
[EComponenetType.amountInput, EDataType.string],
[EComponenetType.input, EDataType.string],
[EComponenetType.dateRangePicker, EDataType.array],
[EComponenetType.datePicker, EDataType.string],
[EComponenetType.select, EDataType.undefined],
])
const props = withDefaults(
defineProps<{
config: Array<ISeachFormConfig>
labelWidth?: string | number
modelValue?: ISearchForm
getTablelist?: (...args: any[]) => Promise<any>
size?: 'large' | 'default' | 'small'
addPermissionName?: string | Array<string> // 新增按钮的权限名称
additionalConfig?: Array<IConfig>
isSearchBtn?: boolean
isResetBtn?: boolean
isAddBtn?: boolean
isDeleteBtn?: boolean
}>(),
{
config: () => [],
labelWidth: 58,
modelValue: () => ({} as ISearchForm),
getTablelist: null,
size: 'default',
addPermissionName: '',
additionalConfig: null,
isSearchBtn: true,
isResetBtn: true,
isAddBtn: true,
isDeleteBtn: true,
},
)
// 获取组件
const getConfigComponent = (type: string) => {
const component = componentConfig[type]
if (component) {
return component
} else {
return null
}
}
const searchForm = reactive(props.modelValue || {})
const selectResult = ref([])
const componentWidth = props.size === 'default' ? 200 : 220 // 组件宽度
selectResult.value = props.config
const componentConfig = {
amountInput: AmountInput,
input: ElInput,
dateRangePicker: DateRangePicker,
datePicker: DatePicker,
select: Select,
}
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()
}
},
{
immediate: true,
},
)
// 获取form表单的属性
const getFormAttrs = computed(() => {
const paramsAttrs = {
...attrs,
}
return paramsAttrs
})
function searchFn(params) {}
// 获取组件项传进来的attrs
const getComponentAttrs = (item: ISeachFormConfig) => {
const params = item.attrs
? {
...item.attrs,
}
: {}
return Object.assign({}, params)
}
const emits = defineEmits<{
(e: 'search', data: ISearchForm)
(e: 'reset', dataL: ISearchForm)
(e: 'fold', data: boolean)
(e: 'headerPrimaryBtn') // 头部新增按钮
(e: 'headerDefaultBtn', data, data2, searchData) // 头部导出按钮
(e: 'update:modelValue', data: ISearchForm)
(e: 'clear', data: ISearchForm) // 点击clearable触发的
(e: 'update:tableFieldForm', data: ISearchForm)
(e: 'getExcelData', data) // 头部新增按钮
}>()
</script>
<style lang="scss" scoped>
.ml-12 {
margin-left: 12px;
}
.searchForm {
position: relative;
&-header {
display: flex;
}
.filterBtn {
// position: absolute;
// top: -15px;
// z-index: 100;
}
}
.setTableFields {
margin-left: 12px;
}
.wrapper {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
}
.handle {
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
margin-left: 10px;
// width: 32px;
// height: 32px;
& > div {
display: flex;
align-items: center;
}
.filterBtn {
&:hover {
opacity: 0.6;
}
}
}
// .el-form--inline {
// .el-form-item {
// & > .el-input, .el-select {
// width: v-bind(componentWidth)px;
// }
// }
// }
</style>
...@@ -154,6 +154,20 @@ const router = createRouter({ ...@@ -154,6 +154,20 @@ const router = createRouter({
}, },
component: WarehousePosition, component: WarehousePosition,
}, },
{
path: '/logistics/logisticsMethod',
meta: {
title: '物流公司',
},
component: () => import('@/views/logistics/logisticsMethod.vue'),
},
{
path: '/logistics/logisticsCompany',
meta: {
title: '物流方式',
},
component: () => import('@/views/logistics/logisticsCompany.vue'),
},
], ],
}, },
// 登录 // 登录
......
...@@ -124,6 +124,23 @@ const menu: MenuItem[] = [ ...@@ -124,6 +124,23 @@ const menu: MenuItem[] = [
}, },
], ],
}, },
{
index: '4',
id: 7,
label: '物流',
children: [
{
index: '/logistics/logisticsMethod',
id: 1,
label: '物流方式',
},
{
index: '/logistics/logisticsCompany',
id: 2,
label: '物流公司',
},
],
},
// { // {
// index: '', // index: '',
// id: 3, // id: 3,
......
/*
* @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
}
<template>
<div class="user-page flex-column card h-100 overflow-hidden">
<div class="header-filter-form">
<SearchForm :config="searchConfig"></SearchForm>
</div>
<div class="user-content flex-1 flex-column overflow-hidden">
<div class="user-list flex-1 overflow-hidden">
<ElTable
:data="tableData"
default-expand-all
style="width: 100%; height: 100%"
@selection-change="handleSelectionChange"
>
<ElTableColumn
type="selection"
header-align="center"
align="center"
width="55"
/>
<ElTableColumn
type="index"
header-align="center"
align="center"
label="序号"
width="55"
/>
<ElTableColumn prop="account" header-align="center" label="用户名">
<template #default="scope">
<div>
<span
>{{ scope.row.account }}
<ElTooltip
v-if="scope.row.supperMark === 1"
content="超级管理员"
placement="top"
>
<Icon
v-if="scope.row.supperMark === 1"
name="zu54"
style="vertical-align: super; cursor: pointer"
>
</Icon>
</ElTooltip>
</span>
</div>
</template>
</ElTableColumn>
<ElTableColumn
prop="factoryCode"
header-align="center"
label="工厂"
align="center"
/>
<ElTableColumn
prop="status"
header-align="center"
align="center"
label="状态"
>
<template #default="scope">
<el-tooltip
:content="scope.row.status ? '启用' : '禁用'"
placement="top"
>
<ElSwitch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="(e:number) => onChangeStatus(e, scope.row)"
></ElSwitch>
</el-tooltip>
</template>
</ElTableColumn>
<ElTableColumn
label="操作"
width="130"
header-align="center"
align="center"
>
<template #default="scope">
<el-icon
size="24"
title="编辑"
color="#EF6C00"
style="cursor: pointer; vertical-align: middle"
@click="editUser(scope.row)"
>
<Edit />
</el-icon>
</template>
</ElTableColumn>
</ElTable>
</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>
<ElDialog
v-model="dialogVisible"
:title="editId ? '编辑用户' : '新增用户'"
width="600px"
:close-on-click-modal="false"
@opened="onOpenedUserForm"
>
<div class="dialog-form">
<ElForm
ref="editFormRef"
:model="editForm"
:rules="rules"
label-width="90px"
>
<ElFormItem label="用户名" prop="account">
<ElInput
v-model="editForm.account"
placeholder="请输入用户名"
clearable
/>
</ElFormItem>
<ElFormItem v-if="!editId" label="密码" prop="password">
<ElInput
v-model="editForm.password"
placeholder="请输入密码"
clearable
type="password"
autocomplete="off"
:show-password="true"
/>
</ElFormItem>
<ElFormItem label="状态" prop="status">
<ElCheckbox
v-model="editForm.status"
label="启用"
true-value="1"
false-value="0"
></ElCheckbox>
</ElFormItem>
<ElFormItem label="角色" prop="supperMark">
<ElCheckbox
v-model="editForm.supperMark"
label="超级管理员"
true-value="1"
false-value="0"
></ElCheckbox>
</ElFormItem>
</ElForm>
</div>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false"> 取消 </ElButton>
<ElButton type="primary" @click="save">保存</ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="tsx">
import {
getUserList,
addUserApi,
updateUserApi,
deleteUserApi,
getDetailsByIdApi,
changeUserStatusApi,
} from '@/api/auth'
import Icon from '@/components/Icon.vue'
import SearchForm from '@/components/SearchForm.tsx'
import { UserEditForm, userData, userSearchForm } from '@/types/api/user'
import usePageList from '@/utils/hooks/usePageList'
import { useValue } from '@/utils/hooks/useValue'
import { showConfirm } from '@/utils/ui'
import { Edit } from '@element-plus/icons-vue'
import type { FormRules } from 'element-plus'
import { reactive, ref } from 'vue'
const [searchForm, resetSearchForm] = useValue<userSearchForm>({})
const [editForm, resetEditForm] = useValue<UserEditForm>({
account: '',
password: '',
supperMark: '0',
status: '1',
})
const {
currentPage,
pageSize,
total,
data: tableData,
refresh: search,
onCurrentPageChange: handleCurrentChange,
onPageSizeChange: handleSizeChange,
} = usePageList({
query: (page, pageSize) =>
getUserList(searchForm.value, page, pageSize).then((res) => res.data),
})
const dialogVisible = ref(false)
const editFormRef = ref()
const selection = ref<userData[]>([])
const searchConfig = ref([
{
prop: 'project_id',
type: 'select',
label: '调入项目',
attrs: {
placeholder: '请选择调入项目名称',
label: 'project_name',
value: 'id',
options: [],
},
},
{
prop: 'name_and_spce',
type: 'input',
label: '材料名称及规格',
attrs: {
placeholder: '请输入材料名称及规格',
},
},
{
prop: 'status',
type: 'datePicker',
label: '审批状态',
attrs: {
hasAllOptions: true,
isList: true,
options: [],
},
render: (item) => <span>{11111}</span>,
},
])
const rules = reactive<FormRules<UserEditForm>>({
account: [
{
required: true,
message: '请输入用户名',
},
],
password: [
{
required: true,
message: '请输入密码',
},
],
})
const editId = ref<number | undefined>(undefined)
const addUser = () => {
editId.value = undefined
dialogVisible.value = true
resetEditForm()
}
const deleteUser = async () => {
if (!selection.value.length) {
return ElMessage({
message: '请选择用户',
type: 'warning',
offset: window.innerHeight / 2,
})
}
try {
await showConfirm('是否删除用户', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const ids = selection.value.map((item) => item.id).join(',')
await deleteUserApi(ids)
ElMessage({
message: '删除成功',
type: 'success',
offset: window.innerHeight / 2,
})
search()
} catch (e) {
search()
// showError(e)
}
}
const editUser = async (item: userData) => {
editId.value = item.id
try {
const res = await getDetailsByIdApi(item.id)
res.data.supperMark = res.data.supperMark + ''
res.data.status = res.data.status + ''
editForm.value = res.data
dialogVisible.value = true
} catch (e) {
//showError(e)
}
}
const save = async () => {
try {
await editFormRef.value.validate()
} catch {
return
}
try {
if (!editId.value) {
await addUserApi({
...editForm.value,
supperMark: Number(editForm.value.supperMark),
status: Number(editForm.value.status),
})
} else {
await updateUserApi({
...editForm.value,
supperMark: Number(editForm.value.supperMark),
status: Number(editForm.value.status),
})
}
ElMessage({
message: '保存成功',
type: 'success',
offset: window.innerHeight / 2,
})
dialogVisible.value = false
search()
} catch (e) {
return
}
}
const onOpenedUserForm = async () => {
editFormRef.value?.clearValidate()
}
const handleSelectionChange = (s: userData[]) => {
selection.value = s
}
const onChangeStatus = async (value: number, item: userData) => {
try {
const res = await changeUserStatusApi(value, item.id)
ElMessage({
message: res.message,
type: 'success',
offset: window.innerHeight / 2,
})
search()
} catch (e) {
//showError(e)
}
}
</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>
/*
* @Author: linjinhong linjinhong@jomalls.com
* @Date: 2025-04-14 15:21:58
* @LastEditors: linjinhong linjinhong@jomalls.com
* @LastEditTime: 2025-04-14 16:36:32
* @FilePath: \factory_front\vite.config.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite' import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
base: '/', base: '/',
server: { server: {
port: 9803, port: 9527,
host: true, host: true,
proxy: { proxy: {
"/api": { "/api": {
...@@ -26,7 +20,9 @@ export default defineConfig({ ...@@ -26,7 +20,9 @@ export default defineConfig({
}, },
plugins: [ plugins: [
vue(), vue(),
vueJsx(),
AutoImport({ AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
resolvers: [ElementPlusResolver()], resolvers: [ElementPlusResolver()],
}), }),
Components({ Components({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment