This commit is contained in:
wuweihua
2026-09-15 16:55:16 +08:00
20 changed files with 2904 additions and 2025 deletions
@@ -81,7 +81,11 @@ public class BmsCustomerController extends BaseController
@PostMapping
public AjaxResult add(@RequestBody BmsCustomer bmsCustomer)
{
return toAjax(bmsCustomerService.insertBmsCustomer(bmsCustomer));
bmsCustomerService.insertBmsCustomer(bmsCustomer);
// 返回新主键,供前端新增成功后停留在编辑页继续维护子表/绑定产品
AjaxResult ajax = AjaxResult.success("新增成功");
ajax.put("customerId", bmsCustomer.getCustomerId());
return ajax;
}
/**
@@ -84,7 +84,7 @@ public class CustomerMoneyController extends BaseController
/**
* 审核通过收款单:确认后变动客户余额并生成余额流水
*/
@PreAuthorize("@ss.hasPermi('customersupplier:customerinfo:edit')")
@PreAuthorize("@ss.hasPermi('customer:balance:review')")
@Log(title = "客户收款审核", businessType = BusinessType.UPDATE)
@PutMapping("/receipt/confirm/{id}")
public AjaxResult confirmReceipt(@PathVariable("id") Long id)
@@ -74,7 +74,11 @@ public class BmsSupplierController extends BaseController
@PostMapping
public AjaxResult add(@RequestBody BmsSupplier bmsSupplier)
{
return toAjax(bmsSupplierService.insertBmsSupplier(bmsSupplier));
bmsSupplierService.insertBmsSupplier(bmsSupplier);
// 返回新主键,供前端新增成功后停留在编辑页继续维护子表
AjaxResult ajax = AjaxResult.success("新增成功");
ajax.put("supplierId", bmsSupplier.getSupplierId());
return ajax;
}
/** 修改供应商 */
@@ -112,4 +116,20 @@ public class BmsSupplierController extends BaseController
List<BmsSupplierCompany> list = bmsSupplierService.selectBmsSupplierCompanyList(bmsSupplierCompany);
return getDataTable(list);
}
/** 删除供应商公司主体 */
@Log(title = "供应商公司主体", businessType = BusinessType.DELETE)
@DeleteMapping("/company/{companyIds}")
public AjaxResult removeCompany(@PathVariable Long[] companyIds)
{
return toAjax(bmsSupplierService.deleteBmsSupplierCompanyByCompanyIds(companyIds));
}
/** 删除供应商联系人 */
@Log(title = "供应商联系人", businessType = BusinessType.DELETE)
@DeleteMapping("/contact/{contactIds}")
public AjaxResult removeContact(@PathVariable Long[] contactIds)
{
return toAjax(bmsSupplierService.deleteBmsSupplierContactByContactIds(contactIds));
}
}
@@ -59,6 +59,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectBmsCustomerList" parameterType="BmsCustomer" resultMap="BmsCustomerResult">
<include refid="selectBmsCustomerVo"/>
<where>
c.del_flag = '0'
<if test="customerCode != null and customerCode != ''"> and c.customer_code = #{customerCode}</if>
<if test="customerName != null and customerName != ''"> and c.customer_name like concat('%', #{customerName}, '%')</if>
<if test="customerCodeDw != null and customerCodeDw != ''"> and c.customer_code_dw = #{customerCodeDw}</if>
@@ -265,14 +266,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="creditCode != null">credit_code = #{creditCode},</if>
<if test="customerType != null">customer_type = #{customerType},</if>
<if test="isEnable != null">is_enable = #{isEnable},</if>
<if test="salesUserId != null">sales_user_id = #{salesUserId},</if>
<if test="salesUser != null">sales_user = #{salesUser},</if>
sales_user_id = #{salesUserId},
sales_user = #{salesUser},
<if test="ed != null">ed = #{ed},</if>
<if test="zq != null">zq = #{zq},</if>
<if test="settlementUserId != null">settlement_user_id = #{settlementUserId},</if>
<if test="settlementUser != null">settlement_user = #{settlementUser},</if>
<if test="serviceUserId != null">service_user_id = #{serviceUserId},</if>
<if test="serviceUser != null">service_user = #{serviceUser},</if>
settlement_user_id = #{settlementUserId},
settlement_user = #{settlementUser},
service_user_id = #{serviceUserId},
service_user = #{serviceUser},
<if test="settlementType != null">settlement_type = #{settlementType},</if>
<if test="tempEd != null">temp_ed = #{tempEd},</if>
<if test="accountBalance != null">account_balance = #{accountBalance},</if>
@@ -139,12 +139,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="creditCode != null">credit_code = #{creditCode},</if>
<if test="supplierType != null">supplier_type = #{supplierType},</if>
<if test="isEnable != null">is_enable = #{isEnable},</if>
<if test="purchaserUserId != null">purchaser_user_id = #{purchaserUserId},</if>
<if test="purchaserUser != null">purchaser_user = #{purchaserUser},</if>
<if test="settlementUserId != null">settlement_user_id = #{settlementUserId},</if>
<if test="settlementUser != null">settlement_user = #{settlementUser},</if>
<if test="serviceUserId != null">service_user_id = #{serviceUserId},</if>
<if test="serviceUser != null">service_user = #{serviceUser},</if>
purchaser_user_id = #{purchaserUserId},
purchaser_user = #{purchaserUser},
settlement_user_id = #{settlementUserId},
settlement_user = #{settlementUser},
service_user_id = #{serviceUserId},
service_user = #{serviceUser},
<if test="ed != null">ed = #{ed},</if>
<if test="zq != null">zq = #{zq},</if>
<if test="settlementType != null">settlement_type = #{settlementType},</if>
@@ -0,0 +1,104 @@
-- ----------------------------
-- 客户管理 / 供应商管理 菜单按钮权限补全 + 普通角色(role_id=2)初始授权
-- 幂等:可重复执行;已有记录不会重复插入
-- 菜单结构:
-- 2309 客户/供应商管理(M)
-- 2313 客户管理(C) perms -> customersupplier:customerinfo:list
-- 2422 客户余额管理(C,隐藏,静态路由页面,仅作收款审核按钮挂载点)
-- 2423 收款审核(F) customer:balance:review
-- [新增] 客户查询/新增/修改/删除/导出 5个F按钮
-- 2314 供应商管理(C) perms -> customersupplier:supplierinfo:list
-- [新增] 供应商查询/新增/修改/删除/导出 5个F按钮
-- ----------------------------
-- 1. 两个业务菜单补 list 权限标识(后端列表接口 @PreAuthorize 要求)
UPDATE `sys_menu` SET `perms` = 'customersupplier:customerinfo:list'
WHERE `menu_id` = 2313 AND (`perms` IS NULL OR `perms` = '');
UPDATE `sys_menu` SET `perms` = 'customersupplier:supplierinfo:list'
WHERE `menu_id` = 2314 AND (`perms` IS NULL OR `perms` = '');
-- 2. 客户余额管理为静态路由页面(/customersupplier/customer-money),侧边栏不应显示,避免死链
UPDATE `sys_menu` SET `visible` = '1'
WHERE `menu_id` = 2422 AND `visible` = '0';
-- 3. 补全客户管理 5 个按钮(F),按 perms 幂等
INSERT INTO `sys_menu`
(`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`,
`menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`)
SELECT '客户查询', 2313, 1, '', '', '', 1, 0, 'F', '0', '0', 'customersupplier:customerinfo:query', '#', 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `perms` = 'customersupplier:customerinfo:query' AND `parent_id` = 2313);
INSERT INTO `sys_menu`
(`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`,
`menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`)
SELECT '客户新增', 2313, 2, '', '', '', 1, 0, 'F', '0', '0', 'customersupplier:customerinfo:add', '#', 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `perms` = 'customersupplier:customerinfo:add' AND `parent_id` = 2313);
INSERT INTO `sys_menu`
(`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`,
`menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`)
SELECT '客户修改', 2313, 3, '', '', '', 1, 0, 'F', '0', '0', 'customersupplier:customerinfo:edit', '#', 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `perms` = 'customersupplier:customerinfo:edit' AND `parent_id` = 2313);
INSERT INTO `sys_menu`
(`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`,
`menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`)
SELECT '客户删除', 2313, 4, '', '', '', 1, 0, 'F', '0', '0', 'customersupplier:customerinfo:remove', '#', 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `perms` = 'customersupplier:customerinfo:remove' AND `parent_id` = 2313);
INSERT INTO `sys_menu`
(`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`,
`menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`)
SELECT '客户导出', 2313, 5, '', '', '', 1, 0, 'F', '0', '0', 'customersupplier:customerinfo:export', '#', 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `perms` = 'customersupplier:customerinfo:export' AND `parent_id` = 2313);
-- 4. 补全供应商管理 5 个按钮(F),按 perms 幂等
INSERT INTO `sys_menu`
(`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`,
`menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`)
SELECT '供应商查询', 2314, 1, '', '', '', 1, 0, 'F', '0', '0', 'customersupplier:supplierinfo:query', '#', 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `perms` = 'customersupplier:supplierinfo:query' AND `parent_id` = 2314);
INSERT INTO `sys_menu`
(`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`,
`menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`)
SELECT '供应商新增', 2314, 2, '', '', '', 1, 0, 'F', '0', '0', 'customersupplier:supplierinfo:add', '#', 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `perms` = 'customersupplier:supplierinfo:add' AND `parent_id` = 2314);
INSERT INTO `sys_menu`
(`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`,
`menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`)
SELECT '供应商修改', 2314, 3, '', '', '', 1, 0, 'F', '0', '0', 'customersupplier:supplierinfo:edit', '#', 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `perms` = 'customersupplier:supplierinfo:edit' AND `parent_id` = 2314);
INSERT INTO `sys_menu`
(`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`,
`menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`)
SELECT '供应商删除', 2314, 4, '', '', '', 1, 0, 'F', '0', '0', 'customersupplier:supplierinfo:remove', '#', 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `perms` = 'customersupplier:supplierinfo:remove' AND `parent_id` = 2314);
INSERT INTO `sys_menu`
(`menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`,
`menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`)
SELECT '供应商导出', 2314, 5, '', '', '', 1, 0, 'F', '0', '0', 'customersupplier:supplierinfo:export', '#', 'admin', NOW(), ''
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `perms` = 'customersupplier:supplierinfo:export' AND `parent_id` = 2314);
-- 5. 普通角色(role_id=2) 授权:目录 + 2个菜单 + 余额隐藏菜单 + 收款审核 + 10个新按钮(幂等)
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`)
SELECT 2, m.`menu_id`
FROM `sys_menu` m
WHERE (
m.`menu_id` IN (2309, 2313, 2314, 2422, 2423)
OR m.`perms` IN (
'customersupplier:customerinfo:query', 'customersupplier:customerinfo:add',
'customersupplier:customerinfo:edit', 'customersupplier:customerinfo:remove',
'customersupplier:customerinfo:export',
'customersupplier:supplierinfo:query', 'customersupplier:supplierinfo:add',
'customersupplier:supplierinfo:edit', 'customersupplier:supplierinfo:remove',
'customersupplier:supplierinfo:export'
)
)
AND NOT EXISTS (
SELECT 1 FROM `sys_role_menu` rm WHERE rm.`role_id` = 2 AND rm.`menu_id` = m.`menu_id`
);
@@ -42,3 +42,19 @@ export function delCustomer(customerId) {
method: 'delete'
})
}
// 删除客户公司主体
export function delCustomerCompany(companyId) {
return request({
url: '/customersupplier/customerinfo/company/' + companyId,
method: 'delete'
})
}
// 删除客户联系人
export function delCustomerContact(contactId) {
return request({
url: '/customersupplier/customerinfo/contact/' + contactId,
method: 'delete'
})
}
@@ -42,3 +42,19 @@ export function delSupplier(supplierId) {
method: 'delete'
})
}
// 删除供应商公司主体
export function delSupplierCompany(companyId) {
return request({
url: '/customersupplier/supplierinfo/company/' + companyId,
method: 'delete'
})
}
// 删除供应商联系人
export function delSupplierContact(contactId) {
return request({
url: '/customersupplier/supplierinfo/contact/' + contactId,
method: 'delete'
})
}
@@ -0,0 +1,228 @@
<template>
<div class="product-select">
<el-button type="primary" plain icon="Connection" @click="openDialog">
{{ selectedIds.length ? '重新选择产品' : '选择产品' }}
</el-button>
<span class="ps-summary">
已绑定 <b>{{ selectedIds.length }}</b> 个产品
<el-button v-if="selectedIds.length" link type="danger" @click="clearSelection">清空</el-button>
</span>
<el-dialog
v-model="dialogVisible"
title="选择绑定产品"
width="960px"
append-to-body
:close-on-click-modal="false"
@open="handleOpen"
>
<el-form :inline="true" class="ps-filter" @submit.prevent>
<el-form-item label="产品代码">
<el-input v-model="queryParams.productCode" placeholder="如 WSP037" clearable style="width: 150px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="产品名称">
<el-input v-model="queryParams.productName" placeholder="产品名称" clearable style="width: 160px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="目的区域">
<el-input v-model="queryParams.destRegion" placeholder="如 欧盟" clearable style="width: 120px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="走货属性">
<el-input v-model="queryParams.cargoAttr" placeholder="普货/带电等" clearable style="width: 120px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 110px">
<el-option label="待生效" value="0" />
<el-option label="已生效" value="1" />
<el-option label="已失效" value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
ref="tableRef"
v-loading="loading"
:data="filteredList"
row-key="productId"
height="380"
border
@select="handleSelect"
@select-all="handleSelectAll"
>
<el-table-column type="selection" width="48" align="center" />
<el-table-column label="产品代码" prop="productCode" width="110" show-overflow-tooltip />
<el-table-column label="产品名称" prop="productName" min-width="160" show-overflow-tooltip />
<el-table-column label="时效等级" prop="serviceLevel" width="90" show-overflow-tooltip />
<el-table-column label="走货属性" prop="cargoAttr" width="90" show-overflow-tooltip />
<el-table-column label="目的区域" prop="destRegion" width="90" show-overflow-tooltip />
<el-table-column label="包税" width="80" align="center">
<template #default="{ row }">
<el-tag v-if="row.taxMode === 'TAX_INCLUDED'" type="success" size="small">包税</el-tag>
<el-tag v-else-if="row.taxMode === 'TAX_EXCLUDED'" type="warning" size="small">不包税</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" width="80" align="center">
<template #default="{ row }">
<el-tag :type="statusTagType[row.status] || 'info'" size="small">{{ statusMap[row.status] || '未知' }}</el-tag>
</template>
</el-table-column>
<div slot="empty" class="ps-empty">暂无产品数据</div>
</el-table>
<template #footer>
<div class="ps-footer">
<span>已选 <b>{{ draftIds.size }}</b> 个产品</span>
<div>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmSelection">确定</el-button>
</div>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="ProductSelect">
import { ref, reactive, computed, watch, nextTick } from 'vue'
import { useProducts } from './useProducts'
const props = defineProps({
/** v-model 绑定值:逗号分隔的产品ID串,如 "3,6" */
modelValue: { type: [String, Number], default: '' }
})
const emit = defineEmits(['update:modelValue', 'change'])
const { productList, ensureProducts } = useProducts()
const dialogVisible = ref(false)
const loading = ref(false)
const tableRef = ref(null)
const draftIds = reactive(new Set())
const queryParams = reactive({
productCode: '',
productName: '',
destRegion: '',
cargoAttr: '',
status: ''
})
const selectedIds = computed(() =>
String(props.modelValue || '').split(',').map(s => s.trim()).filter(Boolean)
)
const statusMap = { 0: '待生效', 1: '已生效', 2: '已失效' }
const statusTagType = { 0: 'info', 1: 'success', 2: 'danger' }
const filteredList = computed(() => {
return productList.value.filter(p => {
if (queryParams.productCode && !(p.productCode || '').toLowerCase().includes(queryParams.productCode.trim().toLowerCase())) return false
if (queryParams.productName && !(p.productName || '').includes(queryParams.productName.trim())) return false
if (queryParams.destRegion && !(p.destRegion || '').includes(queryParams.destRegion.trim())) return false
if (queryParams.cargoAttr && !(p.cargoAttr || '').includes(queryParams.cargoAttr.trim())) return false
if (queryParams.status !== '' && queryParams.status !== null && queryParams.status !== undefined && String(p.status) !== String(queryParams.status)) return false
return true
})
})
function openDialog() {
dialogVisible.value = true
}
function handleOpen() {
loading.value = true
draftIds.clear()
selectedIds.value.forEach(id => draftIds.add(id))
resetQuery(false)
// 拉取最新产品后,按 draftIds 全量同步一次勾选(覆盖上次弹窗遗留状态)
ensureProducts(true).finally(() => {
loading.value = false
nextTick(syncSelection)
})
}
/** 按 draftIds 同步当前渲染行的勾选状态(draftIds 为唯一数据源,筛选切换不丢选择) */
function syncSelection() {
if (!tableRef.value) return
filteredList.value.forEach(row => {
tableRef.value.toggleRowSelection(row, draftIds.has(String(row.productId)))
})
}
/** 数据/筛选变化后同步表格勾选状态 */
watch(filteredList, () => {
if (!dialogVisible.value) return
nextTick(syncSelection)
}, { flush: 'post' })
function handleSelect(selection, row) {
const id = String(row.productId)
if (draftIds.has(id)) draftIds.delete(id)
else draftIds.add(id)
}
function handleSelectAll(selection) {
// selection 为当前过滤后页面被选中的行;长度>0 表示全选,否则取消全选
if (selection.length) {
filteredList.value.forEach(row => draftIds.add(String(row.productId)))
} else {
filteredList.value.forEach(row => draftIds.delete(String(row.productId)))
}
}
function handleQuery() {
// 纯前端过滤(全量已缓存),watch filteredList 自动同步勾选
}
function resetQuery(reload = true) {
queryParams.productCode = ''
queryParams.productName = ''
queryParams.destRegion = ''
queryParams.cargoAttr = ''
queryParams.status = ''
if (reload) ensureProducts(true)
}
function confirmSelection() {
const ids = Array.from(draftIds)
const value = ids.join(',')
emit('update:modelValue', value)
emit('change', ids)
dialogVisible.value = false
}
function clearSelection() {
emit('update:modelValue', '')
emit('change', [])
}
/** 供父组件预加载产品缓存(tab 内明细表格使用) */
defineExpose({ ensureProducts })
</script>
<style scoped>
.product-select {
display: flex;
align-items: center;
gap: 12px;
}
.ps-summary {
color: #606266;
font-size: 14px;
}
.ps-summary b {
color: #409eff;
font-size: 15px;
margin: 0 2px;
}
.ps-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.ps-footer b {
color: #409eff;
}
</style>
@@ -0,0 +1,67 @@
import { ref } from 'vue'
import { listProduct } from '@/api/price/product'
/**
* 产品列表模块级共享缓存:全应用只拉取一次
* 用于客户绑定产品的选择与已绑产品名称/明细回显
*/
const productList = ref([])
let loadingPromise = null
/** 逗号分隔 id 串 -> 字符串 id 数组 */
export function parseProductIds(value) {
if (Array.isArray(value)) return value.map(v => String(v)).filter(Boolean)
if (value === null || value === undefined) return []
return String(value).split(',').map(s => s.trim()).filter(Boolean)
}
/** id 数组 -> 逗号分隔串(去重,保持原顺序) */
export function joinProductIds(ids) {
const seen = new Set()
const out = []
;(ids || []).forEach(id => {
const key = String(id)
if (key && !seen.has(key)) {
seen.add(key)
out.push(key)
}
})
return out.join(',')
}
export function useProducts() {
/** 确保产品列表已加载(force=true 强制刷新),拉取全部状态以便回显已失效产品 */
function ensureProducts(force = false) {
if (!force && loadingPromise) return loadingPromise
if (!force && productList.value.length) return Promise.resolve(productList.value)
loadingPromise = listProduct({ pageNum: 1, pageSize: 1000 })
.then(res => {
productList.value = res.rows || []
return productList.value
})
.finally(() => {
loadingPromise = null
})
return loadingPromise
}
/** 按产品ID查产品对象 */
function findProduct(productId) {
if (productId === null || productId === undefined || productId === '') return null
return productList.value.find(p => String(p.productId) === String(productId)) || null
}
/** 产品展示名:代码 + 名称 */
function getProductLabel(productId) {
const p = findProduct(productId)
if (!p) return String(productId)
return p.productCode + ' ' + p.productName
}
return {
productList,
ensureProducts,
findProduct,
getProductLabel
}
}
@@ -0,0 +1,23 @@
<template>{{ displayName }}</template>
<script setup name="UserName">
import { computed, onMounted } from 'vue'
import { useUsers } from '@/components/UserSelect/useUsers'
const props = defineProps({
/** 用户ID */
userId: {
type: [String, Number],
default: null
}
})
const { ensureUsers, getUserName } = useUsers()
// getUserName 内部读取共享 userList,列表加载完成后计算属性自动刷新
const displayName = computed(() => getUserName(props.userId))
onMounted(() => {
ensureUsers()
})
</script>
@@ -0,0 +1,110 @@
<template>
<el-select
v-model="innerValue"
:placeholder="placeholder"
:clearable="clearable"
:filterable="filterable"
:disabled="disabled"
:loading="loading"
:size="size"
:multiple="false"
value-on-clear="null"
style="width: 100%;"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.userId"
:label="item.nickName || item.userName"
:value="String(item.userId)"
>
<span>{{ item.nickName || item.userName }}</span>
<span
v-if="showCode && item.userName"
class="user-select-sub"
>{{ item.userName }}</span>
</el-option>
</el-select>
</template>
<script setup name="UserSelect">
import { computed, onMounted, ref, watch } from 'vue'
import { useUsers } from './useUsers'
const props = defineProps({
/** 绑定值:用户ID(字符串/数字),清空时为 null */
modelValue: {
type: [String, Number],
default: null
},
placeholder: {
type: String,
default: '请选择用户'
},
clearable: {
type: Boolean,
default: true
},
filterable: {
type: Boolean,
default: true
},
disabled: {
type: Boolean,
default: false
},
size: {
type: String,
default: ''
},
/** 选项右侧是否显示登录账号 */
showCode: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['update:modelValue', 'change'])
const { userList, ensureUsers, findUser } = useUsers()
const loading = ref(false)
const innerValue = ref(props.modelValue === null || props.modelValue === undefined ? null : String(props.modelValue))
watch(() => props.modelValue, (val) => {
innerValue.value = val === null || val === undefined || val === '' ? null : String(val)
})
/** 兜底:当前值在用户列表中不存在(停用/未加载到)时补占位项,避免显示空白 */
const options = computed(() => {
const list = userList.value
if (innerValue.value && !findUser(innerValue.value)) {
return [{ userId: innerValue.value, userName: '', nickName: String(innerValue.value) }, ...list]
}
return list
})
function handleChange(val) {
const value = val === '' ? null : val
emit('update:modelValue', value)
emit('change', value, value === null ? null : findUser(value))
}
onMounted(() => {
loading.value = true
ensureUsers().finally(() => {
loading.value = false
})
})
defineExpose({
reload: () => ensureUsers(true)
})
</script>
<style scoped>
.user-select-sub {
float: right;
color: var(--el-text-color-secondary);
font-size: 12px;
}
</style>
@@ -0,0 +1,45 @@
import { ref } from 'vue'
import { listUser } from '@/api/system/user'
/**
* 用户列表模块级共享缓存:整个应用只拉取一次,UserSelect / UserName 共用
*/
const userList = ref([])
let loadingPromise = null
export function useUsers() {
/** 确保用户列表已加载(force=true 强制刷新) */
function ensureUsers(force = false) {
if (!force && loadingPromise) return loadingPromise
if (!force && userList.value.length) return Promise.resolve(userList.value)
loadingPromise = listUser({ pageNum: 1, pageSize: 1000 })
.then(res => {
userList.value = res.rows || []
return userList.value
})
.finally(() => {
loadingPromise = null
})
return loadingPromise
}
/** 按用户ID查用户对象 */
function findUser(userId) {
if (userId === null || userId === undefined || userId === '') return null
return userList.value.find(u => String(u.userId) === String(userId)) || null
}
/** 按用户ID取显示名(nickName 优先,找不到时回退显示ID本身) */
function getUserName(userId) {
if (userId === null || userId === undefined || userId === '') return ''
const user = findUser(userId)
return user ? (user.nickName || user.userName || String(userId)) : String(userId)
}
return {
userList,
ensureUsers,
findUser,
getUserName
}
}
+8
View File
@@ -50,6 +50,11 @@ import BasicTable from '@/components/BasicTable'
import SimpleTable from '@/components/SimpleTable'
// 客户/供应商下拉选择组件
import CustomerSelect from '@/components/CustomerSelect'
// 用户选择/用户名称展示组件
import UserSelect from '@/components/UserSelect'
import UserName from '@/components/UserName'
// 产品多选绑定组件
import ProductSelect from '@/components/ProductSelect'
const app = createApp(App)
@@ -76,6 +81,9 @@ app.component('BasicForm', BasicForm)
app.component('BasicTable', BasicTable)
app.component('SimpleTable', SimpleTable)
app.component('CustomerSelect', CustomerSelect)
app.component('UserSelect', UserSelect)
app.component('UserName', UserName)
app.component('ProductSelect', ProductSelect)
app.use(router)
app.use(store)
+29 -1
View File
@@ -169,7 +169,35 @@ export const dynamicRoutes = [
path: 'index',
component: () => import('@/views/customersuppliermanagement/customerinfo/customermoney/index.vue'),
name: 'CustomerMoney',
meta: { title: '客户余额', activeMenu: '/customersupplier/customerinfo' }
meta: { title: '客户余额', activeMenu: '/customersuppliermanagement/customerinfo' }
}
]
},
{
path: '/customersupplier/customer-edit',
component: Layout,
hidden: true,
permissions: ['customersupplier:customerinfo:list'],
children: [
{
path: 'index',
component: () => import('@/views/customersuppliermanagement/customerinfo/customeredit/index.vue'),
name: 'CustomerEdit',
meta: { title: '客户编辑', activeMenu: '/customersuppliermanagement/customerinfo' }
}
]
},
{
path: '/customersupplier/supplier-edit',
component: Layout,
hidden: true,
permissions: ['customersupplier:supplierinfo:list'],
children: [
{
path: 'index',
component: () => import('@/views/customersuppliermanagement/supplierinfo/supplieredit/index.vue'),
name: 'SupplierEdit',
meta: { title: '供应商编辑', activeMenu: '/customersuppliermanagement/supplierinfo' }
}
]
}
@@ -84,7 +84,8 @@
<el-form-item>
<el-button type="primary" icon="Search" @click="getReceiptList">搜索</el-button>
<el-button icon="Refresh" @click="resetReceiptQuery">重置</el-button>
<el-button type="success" icon="Plus" @click="handleAddReceipt">登记收款</el-button>
<el-button type="success" icon="Plus" @click="handleAddReceipt"
v-hasPermi="['customersupplier:customerinfo:add']">登记收款</el-button>
</el-form-item>
</el-form>
@@ -132,9 +133,9 @@
<el-table-column label="登记人" align="center" prop="createBy" width="100" />
<el-table-column label="操作" align="center" width="200" fixed="right">
<template #default="scope">
<el-button v-if="scope.row.status === 'PENDING'" link type="success" size="small" @click="handleConfirmReceipt(scope.row)">审核通过</el-button>
<el-button v-if="scope.row.status === 'PENDING'" link type="warning" size="small" @click="handleCancelReceipt(scope.row)">取消</el-button>
<el-button link type="primary" size="small" @click="handleViewReceipt(scope.row)">查看</el-button>
<el-button v-if="scope.row.status === 'PENDING'" link type="success" size="small" @click="handleConfirmReceipt(scope.row)" v-hasPermi="['customer:balance:review']">审核通过</el-button>
<el-button v-if="scope.row.status === 'PENDING'" link type="warning" size="small" @click="handleCancelReceipt(scope.row)" v-hasPermi="['customersupplier:customerinfo:edit']">取消</el-button>
<el-button link type="primary" size="small" @click="handleViewReceipt(scope.row)" v-hasPermi="['customersupplier:customerinfo:query']">查看</el-button>
</template>
</el-table-column>
</el-table>
@@ -392,6 +393,7 @@
v-if="viewDialog.data && viewDialog.data.status === 'PENDING'"
type="success"
@click="handleConfirmFromView"
v-hasPermi="['customer:balance:review']"
>审核通过</el-button>
<el-button @click="viewDialog.visible = false">关 闭</el-button>
</template>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff