增加供应商模块

main
郭强 2024-09-16 00:11:42 +08:00
parent cf10261ecf
commit 0a7e873c66
9 changed files with 867 additions and 0 deletions

View File

@ -0,0 +1,28 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/base/supplier/` + url, ...arg)
/**
* 供应商Api接口管理器
*
* @author Luck
* @date 2024/09/13 22:24
**/
export default {
// 获取供应商分页
supplierPage(data) {
return request('page', data, 'get')
},
// 提交供应商表单 edit为true时为编辑默认为新增
supplierSubmitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除供应商
supplierDelete(data) {
return request('delete', data)
},
// 获取供应商详情
supplierDetail(data) {
return request('detail', data, 'get')
}
}

View File

@ -0,0 +1,32 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/base/supplierCategory/` + url, ...arg)
/**
* 供应商分类Api接口管理器
*
* @author Luck
* @date 2024/09/13 22:18
**/
export default {
// 获取供应商分类分页
supplierCategoryPage(data) {
return request('page', data, 'get')
},
// 获取树结构
supplierCategoryTree(data) {
return request('tree', data, 'get')
},
// 提交供应商分类表单 edit为true时为编辑默认为新增
supplierCategorySubmitForm(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除供应商分类
supplierCategoryDelete(data) {
return request('delete', data)
},
// 获取供应商分类详情
supplierCategoryDetail(data) {
return request('detail', data, 'get')
}
}

View File

@ -0,0 +1,52 @@
export const tableColumns = [
{
title: '编码',
dataIndex: 'number',
align: 'center',
resizable: true,
width: 300,
ellipsis: true
},
{
title: '名称',
dataIndex: 'name',
align: 'center',
resizable: true,
width: 300,
ellipsis: true
},
{
title: '分类',
dataIndex: 'categoryName',
align: 'center',
resizable: true,
width: 300,
ellipsis: true
},
{
title: '可用状态',
dataIndex: 'enabledState',
align: 'center',
resizable: true,
width: 100,
ellipsis: true
},
{
title: '联系人',
dataIndex: 'contacts',
align: 'center',
resizable: true,
width: 100,
ellipsis: true
},
{
title: '创建时间',
dataIndex: 'createTime',
sorter: true,
sortDirections: ['descend', 'ascend'],
align: 'center',
resizable: true,
width: 300,
ellipsis: true
}
]

View File

@ -0,0 +1,182 @@
<template>
<a-page-header style="padding: 10px; font-size: 20px" @back="handleBack">
<template #extra>
<a-button v-if="route.query.type !== 'SEARCH'" key="1" type="primary" @click="onSubmitForm"></a-button>
</template>
</a-page-header>
<a-card :bordered="false" title="供应商">
<DynamicForm
:allDisabled="route.query.type === 'SEARCH'"
:formItems="formItems"
:model="formData"
:rules="formRules"
ref="formRef1"
>
<template #provinceSlot="{ model, item }">
<a-select
:filter-option="filterOption"
show-search
placeholder="请选择省"
:options="cityOptions"
v-model:value="model[item.name]"
@change="onChangeProvince"
>
</a-select>
</template>
<template #citySlot="{ model, item }">
<a-select
:filter-option="filterOption"
show-search
placeholder="请选择市"
:options="cityOptionsCity"
v-model:value="model[item.name]"
@change="onChangeCity"
>
</a-select>
</template>
<template #countySlot="{ model, item }">
<a-select
:filter-option="filterOption"
show-search
placeholder="请选择区"
:options="cityOptionsCounty"
v-model:value="model[item.name]"
>
</a-select>
</template>
</DynamicForm>
</a-card>
<a-card :bordered="false" class="mt-4">
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="1" tab="联系信息">
<DynamicForm
:allDisabled="route.query.type === 'SEARCH'"
:formItems="baseFormItems"
:model="formData"
:rules="formRules"
ref="formRef2"
/>
</a-tab-pane>
<a-tab-pane key="2" tab="扩展字段" force-render>
</a-tab-pane>
<a-tab-pane key="3" tab="操作信息" v-if="route.query.type !== 'ADD'">
<OperationalInformation :detailData="detailData" :colSpan="6"></OperationalInformation>
</a-tab-pane>
</a-tabs>
</a-card>
</template>
<script setup name="clientDetail">
import sysBrandApi from '@/api/base/brand/sysBrandApi'
import supplierApi from '@/api/base/supplier/supplierApi'
import supplierCategoryApi from '@/api/base/supplier/supplierCategoryApi'
import useFormHandler from '@/hook/useFormHandler'
import { useRoute } from 'vue-router'
import cityOptions from '@/utils/cityOptions'
import {
formItems,
baseFormItems,
formRules
} from '@/views/productionBusiness/basicData/supplier/formFields/detailFields'
const route = useRoute()
let detailData = reactive({})
//
let cityOptionsCity = ref([])
let cityOptionsCounty = ref([])
const onChangeProvince = (value, options) => {
cityOptionsCity.value = options.children
formData.city = null
formData.county = null
}
const onChangeCity = (value, options) => {
cityOptionsCounty.value = options.children
formData.county = null
}
const filterOption = (input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
const formRef1 = ref(null)
const formRef2 = ref(null)
const { formData, formRefs, onSubmit, handleBack, fetchData } = useFormHandler(
[...formItems, ...baseFormItems],
{
submitForm: supplierApi.supplierSubmitForm,
getDetail: supplierApi.supplierDetail
}
)
const onSubmitForm = () => {
onSubmit({
isDeep: true,
...formData
})
}
onMounted(async () => {
formRefs.value = [formRef1.value, formRef2.value]
fetchData(route.query.type).then(async (res) => {
if (res) {
getCityOptions()
}
})
//
sysBrandApi
.sysBrandList({
enabledState: 'ENABLE'
})
.then((res) => {
formItems.forEach((item) => {
if (item.name === 'brandId') {
item.attrs.options = res
}
})
})
//
supplierCategoryApi.supplierCategoryTree({
enabledState: 'ENABLE'
})
.then((res) => {
formItems.forEach((item) => {
if (item.name === 'categoryId') {
item.attrs.treeData = res || []
}
})
})
})
let activeKey = ref('1')
//
const cityOptionsObj = cityOptions.reduce((acc, item) => ((acc[item.value] = item.children), acc), {})
const getCityOptions = () => {
cityOptionsCity.value = cityOptionsObj[formData.province]
cityOptionsCounty.value = getCityChildren(cityOptionsObj[formData.province], formData.city)
}
function getCityChildren(arr, value) {
let children = []
if (arr && value) {
let data = arr.filter((item) => item.value === value).map((item) => item.children)
if (data.length > 0) {
children = data[0]
}
}
return children
}
</script>

View File

@ -0,0 +1,90 @@
<template>
<xn-form-container
:title="title +'供应商分类'"
:width="700"
:visible="visible"
:destroy-on-close="true"
@close="onClose"
>
<DynamicForm :formItems="drawerForm" :model="formData" :rules="formRules" ref="formRef1" >
<template #numberSlot="{ model, item }">
<a-input
:disabled="formData.number"
v-model:value="model[item.name]"
placeholder="请输入编码"
allow-clear
/>
</template>
</DynamicForm>
<OperationalInformation v-if="recordData.id" :detailData="recordData" :colSpan="12"></OperationalInformation>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose"></a-button>
<a-button type="primary" @click="onSubmitForm"></a-button>
</template>
</xn-form-container>
</template>
<script setup>
import supplierCategoryApi from '@/api/base/supplier/supplierCategoryApi'
import { cloneDeep } from 'lodash-es'
import { drawerForm, formRules } from '@/views/productionBusiness/basicData/supplier/formFields/drawerForm'
import useFormHandler from '@/hook/useFormHandler'
//
let title;
const visible = ref(false)
const emit = defineEmits({ successful: null })
const formRef1 = ref(null)
let recordData = reactive({})
//
const onOpen = (record) => {
title = (record && record.id) ? "编辑" : "增加";
visible.value = true
formRules.value = [formRef1.value]
if (record) {
recordData = cloneDeep(record)
fetchData().then(() => {
populateFormData(recordData)
})
} else {
fetchData()
recordData = {}
}
supplierCategoryApi.supplierCategoryTree({
extId: record && record.id
}).then((res) => {
drawerForm.forEach((item) => {
if (item.name === 'parentId') {
item.attrs.treeData = res
}
})
})
}
//
const onClose = () => {
visible.value = false
}
//
const onSubmitForm = () => {
onSubmit({
isEnable: true,
id: recordData.id
}).then(() => {
onClose()
emit('successful')
})
}
let { formData, onSubmit, populateFormData, fetchData } = useFormHandler([...drawerForm], {
getDetail: supplierCategoryApi.supplierCategoryDetail,
submitForm: supplierCategoryApi.supplierCategorySubmitForm
})
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,213 @@
import { required } from '@/utils/formRules'
import tool from '@/utils/tool'
export const formRules = {
name: [required('请输入名称')]
}
export const formItems = reactive([
{
label: '编码:',
name: 'number',
type: 'a-input',
span: 8,
attrs: {
placeholder: '请输入编码若留空由系统自动生成',
allowClear: true
}
},
{
label: '名称:',
name: 'name',
type: 'a-input',
span: 8,
rules: [required('请输入名称')],
attrs: {
placeholder: '请输入名称',
allowClear: true
}
},
{
label: '简称:',
name: 'shortName',
type: 'a-input',
span: 8,
attrs: {
placeholder: '请输入简称',
allowClear: true
}
},
{
label: '可用状态:',
name: 'enabledState',
type: 'a-select',
span: 8,
attrs: {
placeholder: '请选择可用状态',
options: tool.dictList('COMMON_STATUS')
},
defaultValue: 'ENABLE'
},
{
label: '品牌:',
name: 'brandId',
type: 'a-select',
span: 8,
// rules: [required('请选择品牌')],
attrs: {
placeholder: '请选择品牌',
options: tool.dictList('COMMON_STATUS'),
fieldNames: {
label: 'name',
value: 'id'
}
}
},
{
label: '供应商分类:',
name: 'categoryId',
type: 'a-tree-select',
span: 8,
rules: [required('请选择分类')],
attrs: {
placeholder: '请选择分类',
allowClear: true,
fieldNames: {
children: 'children',
label: 'name',
value: 'id'
}
}
},
{
label: '省:',
name: 'province',
span: 8,
// rules: [required('请选择省')],
isUseSlot: true,
slotName: 'provinceSlot',
attrs: {
placeholder: '请选择省'
}
},
{
label: '市:',
name: 'city',
span: 8,
// rules: [required('请选择市')],
isUseSlot: true,
slotName: 'citySlot'
},
{
label: '区:',
name: 'county',
// rules: [required('请选择区')],
isUseSlot: true,
slotName: 'countySlot',
span: 8
},
{
label: '详细地址:',
name: 'address',
type: 'a-textarea',
span: 8,
attrs: {
placeholder: '请输入详细地址',
allowClear: true,
fieldNames: {
children: 'children',
label: 'name',
value: 'id'
}
}
},
{
label: '备注:',
name: 'remarks',
type: 'a-textarea',
span: 8,
attrs: {
placeholder: '请输入备注',
allowClear: true
}
}
])
export const baseFormItems = [
{
label: '联系人:',
name: 'contacts',
type: 'a-input',
span: 6,
attrs: {
placeholder: '请选择类型'
}
},
{
label: '手机:',
name: 'phone',
type: 'a-input',
span: 6,
attrs: {
placeholder: '请选择类型'
}
},
{
label: '固话:',
name: 'tel',
type: 'a-input',
span: 6,
attrs: {
placeholder: '请选择类型'
}
},
{
label: '传真:',
name: 'fax',
type: 'a-input',
span: 6,
attrs: {
placeholder: '请选择类型'
}
},
{
label: '电子邮箱:',
name: 'email',
type: 'a-input',
span: 6,
attrs: {
placeholder: '请输入备注',
allowClear: true
}
},
{
label: 'QQ',
name: 'qq',
type: 'a-input',
span: 6,
attrs: {
placeholder: '请输入备注',
allowClear: true
}
},
{
label: '微信:',
name: 'wechat',
type: 'a-input',
span: 6,
attrs: {
placeholder: '请输入备注',
allowClear: true
}
},
{
label: '联系地址:',
name: 'contactAddress',
type: 'a-input',
span: 6,
attrs: {
placeholder: '请输入备注',
allowClear: true
}
}
]

View File

@ -0,0 +1,58 @@
import { required } from '@/utils/formRules'
export const drawerForm = reactive([
{
label: '名称:',
name: 'name',
type: 'a-input',
span: 12,
attrs: {
placeholder: '请输入名称',
allowClear: true
}
},
{
label: '编码:',
name: 'number',
type: 'a-input',
span: 12,
isUseSlot: true,
slotName: 'numberSlot',
attrs: {
placeholder: '请输入编码',
allowClear: true
}
},
{
label: '上级分类:',
name: 'parentId',
type: 'a-tree-select',
span: 12,
attrs: {
placeholder: '请选择上级客户',
allowClear: true,
treeData: [],
fieldNames: {
children: 'children',
label: 'name',
value: 'id'
}
}
},
{
label: '排序码:',
name: 'sortCode',
type: 'a-input-number',
span: 12,
attrs: {
placeholder: '请输入排序码',
allowClear: true
},
defaultValue: 10
}
])
export const formRules = {
name: [required('请输入名称')],
parentId: [required('请选择上级')]
}

View File

@ -0,0 +1,13 @@
import tool from '@/utils/tool'
export const searchFields = [
{ name: 'name', label: '名称', component: 'a-input', props: { placeholder: '请输入名称' } },
{ name: 'number', label: '编码', component: 'a-input', props: { placeholder: '请输入编码' } },
{
name: 'enabledState',
label: '可用状态',
component: 'a-select',
props: { placeholder: '请选择状态', options: tool.dictList('COMMON_STATUS') }
}
]

View File

@ -0,0 +1,199 @@
<template>
<AdvancedSearchForm
:formState="searchFormState"
:formFields="searchFields"
@search="tableRef.refresh()"
@reset="tableRef.refresh()"
/>
<a-card :bordered="false" class="mt-4" style="height: 100%">
<a-row :gutter="24">
<a-col :span="6">
<dynamic-tree
ref="dynamicTreeRef"
treeTitle="供应商分类"
:tableRef="tableRef"
:openFormRef="supplierCategoryFormRef"
:apiModel="{
getTree: supplierCategoryApi.supplierCategoryTree,
delTree: supplierCategoryApi.supplierCategoryDelete
}"
@selectTree="selectTree"
@delTree="delTree"
@treeRefresh="treeRefresh"
:toolConfig="{
plus: hasPerm('supplierCategoryAdd'),
edit: hasPerm('supplierCategoryEdit'),
delete: hasPerm('supplierCategoryDelete'),
refresh: true
}"
></dynamic-tree>
</a-col>
<a-col :span="18">
<s-table
ref="tableRef"
:columns="columns"
:data="loadData"
:alert="options.alert.show"
bordered
:row-key="(record) => record.id"
:tool-config="options.toolConfig"
:row-selection="options.rowSelection"
:scroll="{
x: 100
}"
>
<template #operator>
<a-space>
<a-button
type="primary"
@click="
navigateTo('/basicData/supplier/detail', {
type: 'ADD'
})
"
v-if="hasPerm('supplierAdd')"
>
<template #icon><plus-outlined /></template>
新增
</a-button>
<xn-batch-delete
v-if="hasPerm('supplierBatchDelete')"
:selectedRowKeys="selectedRowKeys"
@batchDelete="deleteBatchRecords"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'number'">
<span style="color: #0d84ff">{{ record.number }}</span>
</template>
<template v-if="column.dataIndex === 'enabledState'">
<a-tag color="#87d068" v-if="record.enabledState === 'ENABLE'"></a-tag>
<a-tag color="#f50" v-if="record.enabledState === 'DISABLED'"></a-tag>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a-tooltip title="查看">
<a
@click="
navigateTo('/basicData/supplier/detail', {
type: 'SEARCH',
id: record.id
})
"
v-if="hasPerm('supplierEdit')"
>
<EyeOutlined />
<!-- 查看-->
</a>
</a-tooltip>
<a-divider type="vertical" v-if="hasPerm(['supplierEdit', 'supplierDelete'], 'and')" />
<a-tooltip title="编辑">
<a
@click="
navigateTo('/basicData/supplier/detail', {
type: 'EDIT',
id: record.id
})
"
v-if="hasPerm('supplierEdit')"
>
<FormOutlined />
<!-- 编辑-->
</a>
</a-tooltip>
<a-divider type="vertical" v-if="hasPerm(['supplierEdit', 'supplierDelete'], 'and')" />
<a-popconfirm title="确定要删除吗?" @confirm="deleteRecord(record)">
<a-button type="link" danger size="small" v-if="hasPerm('supplierDelete')">
<DeleteOutlined />
</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</s-table>
</a-col>
</a-row>
<supplierCategoryForm ref="supplierCategoryFormRef" @successful="successful"></supplierCategoryForm>
</a-card>
</template>
<script setup name="basicDataClient">
import supplierApi from '@/api/base/supplier/supplierApi'
import supplierCategoryApi from '@/api/base/supplier/supplierCategoryApi'
import supplierCategoryForm from '@/views/productionBusiness/basicData/supplier/detail/supplierCategoryForm.vue'
import { useTableManagement } from '@/hook/useTableManagement'
import { searchFields } from '@/views/productionBusiness/basicData/supplier/formFields/searchFields'
import { tableColumns } from '@/views/productionBusiness/basicData/supplier/columns/supplierColumn'
const {
searchFormState,
tableRef,
selectedRowKeys,
columns,
loadData,
deleteRecord,
deleteBatchRecords,
options,
navigateTo
} = useTableManagement(
{
page: supplierApi.supplierPage,
delete: supplierApi.supplierDelete
},
tableColumns,
['customerEdit', 'customerDelete']
)
const dynamicTreeRef = ref(null)
const supplierCategoryFormRef = ref(null)
const selectTree = (value) => {
searchFormState.value.categoryId = value.id
tableRef.value.refresh()
}
const delTree = () => {
searchFormState.value.categoryId = null
tableRef.value.refresh()
}
const treeRefresh = () => {
searchFormState.value.categoryId = null
tableRef.value.refresh()
}
const successful = () => {
searchFormState.value.categoryId = null
tableRef.value.refresh()
dynamicTreeRef.value.loadTreeData()
}
onMounted(() => {
dynamicTreeRef.value.loadTreeData()
})
</script>
<style lang="less" scoped>
.s-table-tool {
display: flex;
margin-bottom: 16px;
.s-table-tool-left {
flex: 1;
}
.s-table-tool-right {
.s-tool-item {
font-size: 16px;
@apply ml-4;
cursor: pointer;
}
}
}
</style>