Commit d9ed3d1a by liuhongfeng
parents 0b9cf35b 2f10f0ff
import request from '@/config/axios'
export interface BusinessVO {
id: number
name: string
statusTypeId: number
statusId: number
contactNextTime: Date
customerId: number
dealTime: Date
price: number
discountPercent: number
productPrice: number
remark: string
ownerUserId: number
roUserIds: string
rwUserIds: string
endStatus: number
endRemark: string
contactLastTime: Date
followUpStatus: number
}
// 查询 CRM 商机列表
export const getBusinessPage = async (params) => {
return await request.get({ url: `/crm/business/page`, params })
}
// 查询 CRM 商机列表,基于指定客户
export const getBusinessPageByCustomer = async (params) => {
return await request.get({ url: `/crm/business/page-by-customer`, params })
}
// 查询 CRM 商机详情
export const getBusiness = async (id: number) => {
return await request.get({ url: `/crm/business/get?id=` + id })
}
// 新增 CRM 商机
export const createBusiness = async (data: BusinessVO) => {
return await request.post({ url: `/crm/business/create`, data })
}
// 修改 CRM 商机
export const updateBusiness = async (data: BusinessVO) => {
return await request.put({ url: `/crm/business/update`, data })
}
// 删除 CRM 商机
export const deleteBusiness = async (id: number) => {
return await request.delete({ url: `/crm/business/delete?id=` + id })
}
// 导出 CRM 商机 Excel
export const exportBusiness = async (params) => {
return await request.download({ url: `/crm/business/export-excel`, params })
}
import request from '@/config/axios'
export interface BusinessStatusTypeVO {
id: number
name: string
deptIds: number[]
status: boolean
}
// 查询商机状态类型列表
export const getBusinessStatusTypePage = async (params) => {
return await request.get({ url: `/crm/business-status-type/page`, params })
}
// 查询商机状态类型详情
export const getBusinessStatusType = async (id: number) => {
return await request.get({ url: `/crm/business-status-type/get?id=` + id })
}
// 新增商机状态类型
export const createBusinessStatusType = async (data: BusinessStatusTypeVO) => {
return await request.post({ url: `/crm/business-status-type/create`, data })
}
// 修改商机状态类型
export const updateBusinessStatusType = async (data: BusinessStatusTypeVO) => {
return await request.put({ url: `/crm/business-status-type/update`, data })
}
// 删除商机状态类型
export const deleteBusinessStatusType = async (id: number) => {
return await request.delete({ url: `/crm/business-status-type/delete?id=` + id })
}
// 导出商机状态类型 Excel
export const exportBusinessStatusType = async (params) => {
return await request.download({ url: `/crm/business-status-type/export-excel`, params })
}
// 获取商机状态类型信息列表
export const getBusinessStatusTypeList = async () => {
return await request.get({ url: `/crm/business-status-type/get-simple-list` })
}
// 根据类型ID获取商机状态信息列表
export const getBusinessStatusListByTypeId = async (typeId: number) => {
return await request.get({ url: `/crm/business-status-type/get-status-list?typeId=` + typeId })
}
/*
* @Author: zyna
* @Date: 2023-11-05 13:34:41
* @LastEditTime: 2023-11-11 16:20:19
* @FilePath: \yudao-ui-admin-vue3\src\api\crm\contact\index.ts
* @Description:
*/
import request from '@/config/axios' import request from '@/config/axios'
export interface ContactVO { export interface ContactVO {
...@@ -22,44 +15,53 @@ export interface ContactVO { ...@@ -22,44 +15,53 @@ export interface ContactVO {
id: number id: number
parentId: number parentId: number
qq: number qq: number
webchat: string wechat: string
sex: number sex: number
policyMakers: boolean master: boolean
creatorName: string creatorName: string
updateTime?: Date updateTime?: Date
createTime?: Date createTime?: Date
customerName: string customerName: string
areaName: string
ownerUserName: string
} }
// 查询crm联系人列表 // 查询 CRM 联系人列表
export const getContactPage = async (params) => { export const getContactPage = async (params) => {
return await request.get({ url: `/crm/contact/page`, params }) return await request.get({ url: `/crm/contact/page`, params })
} }
// 查询crm联系人详情 // 查询 CRM 联系人列表,基于指定客户
export const getContactPageByCustomer = async (params: any) => {
return await request.get({ url: `/crm/contact/page-by-customer`, params })
}
// 查询 CRM 联系人详情
export const getContact = async (id: number) => { export const getContact = async (id: number) => {
return await request.get({ url: `/crm/contact/get?id=` + id }) return await request.get({ url: `/crm/contact/get?id=` + id })
} }
// 新增crm联系人 // 新增 CRM 联系人
export const createContact = async (data: ContactVO) => { export const createContact = async (data: ContactVO) => {
return await request.post({ url: `/crm/contact/create`, data }) return await request.post({ url: `/crm/contact/create`, data })
} }
// 修改crm联系人 // 修改 CRM 联系人
export const updateContact = async (data: ContactVO) => { export const updateContact = async (data: ContactVO) => {
return await request.put({ url: `/crm/contact/update`, data }) return await request.put({ url: `/crm/contact/update`, data })
} }
// 删除crm联系人 // 删除 CRM 联系人
export const deleteContact = async (id: number) => { export const deleteContact = async (id: number) => {
return await request.delete({ url: `/crm/contact/delete?id=` + id }) return await request.delete({ url: `/crm/contact/delete?id=` + id })
} }
// 导出crm联系人 Excel // 导出 CRM 联系人 Excel
export const exportContact = async (params) => { export const exportContact = async (params) => {
return await request.download({ url: `/crm/contact/export-excel`, params }) return await request.download({ url: `/crm/contact/export-excel`, params })
} }
export const simpleAlllist = async () => {
return await request.get({ url: `/crm/contact/simpleAlllist` }) // 获得 CRM 联系人列表(精简)
export const getSimpleContactList = async () => {
return await request.get({ url: `/crm/contact/simple-all-list` })
} }
...@@ -22,32 +22,37 @@ export interface ContractVO { ...@@ -22,32 +22,37 @@ export interface ContractVO {
remark: string remark: string
} }
// 查询合同列表 // 查询 CRM 合同列表
export const getContractPage = async (params) => { export const getContractPage = async (params) => {
return await request.get({ url: `/crm/contract/page`, params }) return await request.get({ url: `/crm/contract/page`, params })
} }
// 查询合同详情 // 查询 CRM 联系人列表,基于指定客户
export const getContractPageByCustomer = async (params: any) => {
return await request.get({ url: `/crm/contract/page-by-customer`, params })
}
// 查询 CRM 合同详情
export const getContract = async (id: number) => { export const getContract = async (id: number) => {
return await request.get({ url: `/crm/contract/get?id=` + id }) return await request.get({ url: `/crm/contract/get?id=` + id })
} }
// 新增合同 // 新增 CRM 合同
export const createContract = async (data: ContractVO) => { export const createContract = async (data: ContractVO) => {
return await request.post({ url: `/crm/contract/create`, data }) return await request.post({ url: `/crm/contract/create`, data })
} }
// 修改合同 // 修改 CRM 合同
export const updateContract = async (data: ContractVO) => { export const updateContract = async (data: ContractVO) => {
return await request.put({ url: `/crm/contract/update`, data }) return await request.put({ url: `/crm/contract/update`, data })
} }
// 删除合同 // 删除 CRM 合同
export const deleteContract = async (id: number) => { export const deleteContract = async (id: number) => {
return await request.delete({ url: `/crm/contract/delete?id=` + id }) return await request.delete({ url: `/crm/contract/delete?id=` + id })
} }
// 导出合同 Excel // 导出 CRM 合同 Excel
export const exportContract = async (params) => { export const exportContract = async (params) => {
return await request.download({ url: `/crm/contract/export-excel`, params }) return await request.download({ url: `/crm/contract/export-excel`, params })
} }
...@@ -62,3 +62,8 @@ export const deleteCustomer = async (id: number) => { ...@@ -62,3 +62,8 @@ export const deleteCustomer = async (id: number) => {
export const exportCustomer = async (params) => { export const exportCustomer = async (params) => {
return await request.download({ url: `/crm/customer/export-excel`, params }) return await request.download({ url: `/crm/customer/export-excel`, params })
} }
// 客户列表
export const queryAllList = async () => {
return await request.get({ url: `/crm/customer/query-all-list` })
}
...@@ -9,6 +9,20 @@ export interface CustomerLimitConfigVO { ...@@ -9,6 +9,20 @@ export interface CustomerLimitConfigVO {
dealCountEnabled?: boolean dealCountEnabled?: boolean
} }
/**
* 客户限制配置类型
*/
export enum LimitConfType {
/**
* 拥有客户数限制
*/
CUSTOMER_QUANTITY_LIMIT = 1,
/**
* 锁定客户数限制
*/
CUSTOMER_LOCK_LIMIT = 2
}
// 查询客户限制配置列表 // 查询客户限制配置列表
export const getCustomerLimitConfigPage = async (params) => { export const getCustomerLimitConfigPage = async (params) => {
return await request.get({ url: `/crm/customer-limit-config/page`, params }) return await request.get({ url: `/crm/customer-limit-config/page`, params })
......
import request from '@/config/axios' import request from '@/config/axios'
import { ConfigVO } from '@/api/infra/config'
export interface CustomerPoolConfigVO { export interface CustomerPoolConfigVO {
enabled?: boolean enabled?: boolean
...@@ -14,6 +15,6 @@ export const getCustomerPoolConfig = async () => { ...@@ -14,6 +15,6 @@ export const getCustomerPoolConfig = async () => {
} }
// 更新客户公海规则设置 // 更新客户公海规则设置
export const updateCustomerPoolConfig = async (data: ConfigVO) => { export const saveCustomerPoolConfig = async (data: ConfigVO) => {
return await request.put({ url: `/crm/customer-pool-config/update`, data }) return await request.put({ url: `/crm/customer-pool-config/save`, data })
} }
...@@ -12,36 +12,60 @@ export interface PermissionVO { ...@@ -12,36 +12,60 @@ export interface PermissionVO {
createTime?: Date createTime?: Date
} }
// 查询团队成员列表 /**
* CRM 业务类型枚举
*
* @author HUIHUI
*/
export enum BizTypeEnum {
CRM_LEADS = 1, // 线索
CRM_CUSTOMER = 2, // 客户
CRM_CONTACT = 3, // 联系人
CRM_BUSINESS = 5, // 商机
CRM_CONTRACT = 6 // 合同
}
/**
* CRM 数据权限级别枚举
*/
export enum PermissionLevelEnum {
OWNER = 1, // 负责人
READ = 2, // 只读
WRITE = 3 // 读写
}
// 获得数据权限列表(查询团队成员列表)
export const getPermissionList = async (params) => { export const getPermissionList = async (params) => {
return await request.get({ url: `/crm/permission/list`, params }) return await request.get({ url: `/crm/permission/list`, params })
} }
// 新增团队成员 // 创建数据权限(新增团队成员)
export const createPermission = async (data: PermissionVO) => { export const createPermission = async (data: PermissionVO) => {
return await request.post({ url: `/crm/permission/create`, data }) return await request.post({ url: `/crm/permission/create`, data })
} }
// 修改团队成员权限级别 // 编辑数据权限(修改团队成员权限级别)
export const updatePermission = async (data) => { export const updatePermission = async (data) => {
return await request.put({ url: `/crm/permission/update`, data }) return await request.put({ url: `/crm/permission/update`, data })
} }
// 删除团队成员 // 删除数据权限(删除团队成员)
export const deletePermission = async (params) => { export const deletePermissionBatch = async (params) => {
return await request.delete({ url: '/crm/permission/delete', params }) return await request.delete({ url: '/crm/permission/delete', params })
} }
// 退出团队 // 删除自己的数据权限(退出团队)
export const quitTeam = async (id) => { export const deleteSelfPermission = async (id) => {
return await request.delete({ url: '/crm/permission/quit-team?id=' + id }) return await request.delete({ url: '/crm/permission/quit-team?id=' + id })
} }
// TODO @puhui999:调整下位置
// 领取公海数据 // 领取公海数据
export const receive = async (data: { bizType: number; bizId: number }) => { export const receive = async (data: { bizType: number; bizId: number }) => {
return await request.put({ url: `/crm/permission/receive`, data }) return await request.put({ url: `/crm/permission/receive`, data })
} }
// TODO @puhui999:调整下位置
// 数据放入公海 // 数据放入公海
export const putPool = async (data: { bizType: number; bizId: number }) => { export const putPool = async (data: { bizType: number; bizId: number }) => {
return await request.put({ url: `/crm/permission/put-pool`, data }) return await request.put({ url: `/crm/permission/put-pool`, data })
......
...@@ -6,46 +6,47 @@ export interface ReceivableVO { ...@@ -6,46 +6,47 @@ export interface ReceivableVO {
planId: number planId: number
customerId: number customerId: number
contractId: number contractId: number
checkStatus: number auditStatus: number
processInstanceId: number processInstanceId: number
returnTime: Date returnTime: Date
returnType: string returnType: string
price: number price: number
ownerUserId: number ownerUserId: number
batchId: number
sort: number sort: number
dataScope: number
dataScopeDeptIds: string
status: number
remark: string remark: string
} }
// 查询回款管理列表 // 查询回款列表
export const getReceivablePage = async (params) => { export const getReceivablePage = async (params) => {
return await request.get({ url: `/crm/receivable/page`, params }) return await request.get({ url: `/crm/receivable/page`, params })
} }
// 查询回款管理详情 // 查询回款列表
export const getReceivablePageByCustomer = async (params) => {
return await request.get({ url: `/crm/receivable/page-by-customer`, params })
}
// 查询回款详情
export const getReceivable = async (id: number) => { export const getReceivable = async (id: number) => {
return await request.get({ url: `/crm/receivable/get?id=` + id }) return await request.get({ url: `/crm/receivable/get?id=` + id })
} }
// 新增回款管理 // 新增回款
export const createReceivable = async (data: ReceivableVO) => { export const createReceivable = async (data: ReceivableVO) => {
return await request.post({ url: `/crm/receivable/create`, data }) return await request.post({ url: `/crm/receivable/create`, data })
} }
// 修改回款管理 // 修改回款
export const updateReceivable = async (data: ReceivableVO) => { export const updateReceivable = async (data: ReceivableVO) => {
return await request.put({ url: `/crm/receivable/update`, data }) return await request.put({ url: `/crm/receivable/update`, data })
} }
// 删除回款管理 // 删除回款
export const deleteReceivable = async (id: number) => { export const deleteReceivable = async (id: number) => {
return await request.delete({ url: `/crm/receivable/delete?id=` + id }) return await request.delete({ url: `/crm/receivable/delete?id=` + id })
} }
// 导出回款管理 Excel // 导出回款 Excel
export const exportReceivable = async (params) => { export const exportReceivable = async (params) => {
return await request.download({ url: `/crm/receivable/export-excel`, params }) return await request.download({ url: `/crm/receivable/export-excel`, params })
} }
...@@ -23,6 +23,11 @@ export const getReceivablePlanPage = async (params) => { ...@@ -23,6 +23,11 @@ export const getReceivablePlanPage = async (params) => {
return await request.get({ url: `/crm/receivable-plan/page`, params }) return await request.get({ url: `/crm/receivable-plan/page`, params })
} }
// 查询回款计划列表
export const getReceivablePlanPageByCustomer = async (params) => {
return await request.get({ url: `/crm/receivable-plan/page-by-customer`, params })
}
// 查询回款计划详情 // 查询回款计划详情
export const getReceivablePlan = async (id: number) => { export const getReceivablePlan = async (id: number) => {
return await request.get({ url: `/crm/receivable-plan/get?id=` + id }) return await request.get({ url: `/crm/receivable-plan/get?id=` + id })
......
...@@ -42,12 +42,16 @@ export const exportDemo03Student = async (params) => { ...@@ -42,12 +42,16 @@ export const exportDemo03Student = async (params) => {
// 获得学生课程列表 // 获得学生课程列表
export const getDemo03CourseListByStudentId = async (studentId) => { export const getDemo03CourseListByStudentId = async (studentId) => {
return await request.get({ url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId }) return await request.get({
url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId
})
} }
// ==================== 子表(学生班级) ==================== // ==================== 子表(学生班级) ====================
// 获得学生班级 // 获得学生班级
export const getDemo03GradeByStudentId = async (studentId) => { export const getDemo03GradeByStudentId = async (studentId) => {
return await request.get({ url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId }) return await request.get({
url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId
})
} }
...@@ -42,12 +42,16 @@ export const exportDemo03Student = async (params) => { ...@@ -42,12 +42,16 @@ export const exportDemo03Student = async (params) => {
// 获得学生课程列表 // 获得学生课程列表
export const getDemo03CourseListByStudentId = async (studentId) => { export const getDemo03CourseListByStudentId = async (studentId) => {
return await request.get({ url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId }) return await request.get({
url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId
})
} }
// ==================== 子表(学生班级) ==================== // ==================== 子表(学生班级) ====================
// 获得学生班级 // 获得学生班级
export const getDemo03GradeByStudentId = async (studentId) => { export const getDemo03GradeByStudentId = async (studentId) => {
return await request.get({ url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId }) return await request.get({
url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId
})
} }
...@@ -17,7 +17,7 @@ export interface FileClientConfig { ...@@ -17,7 +17,7 @@ export interface FileClientConfig {
export interface FileConfigVO { export interface FileConfigVO {
id: number id: number
name: string name: string
storage: any storage?: number
master: boolean master: boolean
visible: boolean visible: boolean
config: FileClientConfig config: FileClientConfig
......
...@@ -7,8 +7,8 @@ export interface JobLogVO { ...@@ -7,8 +7,8 @@ export interface JobLogVO {
handlerParam: string handlerParam: string
cronExpression: string cronExpression: string
executeIndex: string executeIndex: string
beginTime: string beginTime: Date
endTime: string endTime: Date
duration: string duration: string
status: number status: number
createTime: string createTime: string
......
...@@ -17,7 +17,7 @@ export interface ArticleVO { ...@@ -17,7 +17,7 @@ export interface ArticleVO {
} }
// 查询文章管理列表 // 查询文章管理列表
export const getArticlePage = async (params) => { export const getArticlePage = async (params: any) => {
return await request.get({ url: `/promotion/article/page`, params }) return await request.get({ url: `/promotion/article/page`, params })
} }
......
...@@ -26,6 +26,6 @@ export const getUserPage = (query) => { ...@@ -26,6 +26,6 @@ export const getUserPage = (query) => {
// 同步公众号粉丝 // 同步公众号粉丝
export const syncUser = (accountId) => { export const syncUser = (accountId) => {
return request.post({ return request.post({
url: '/mp/tag/sync?accountId=' + accountId url: '/mp/user/sync?accountId=' + accountId
}) })
} }
...@@ -14,7 +14,7 @@ export interface DeptVO { ...@@ -14,7 +14,7 @@ export interface DeptVO {
// 查询部门(精简)列表 // 查询部门(精简)列表
export const getSimpleDeptList = async (): Promise<DeptVO[]> => { export const getSimpleDeptList = async (): Promise<DeptVO[]> => {
return await request.get({ url: '/system/dept/list-all-simple' }) return await request.get({ url: '/system/dept/simple-list' })
} }
// 查询部门列表 // 查询部门列表
......
...@@ -14,8 +14,8 @@ export type DictDataVO = { ...@@ -14,8 +14,8 @@ export type DictDataVO = {
} }
// 查询字典数据(精简)列表 // 查询字典数据(精简)列表
export const listSimpleDictData = () => { export const getSimpleDictDataList = () => {
return request.get({ url: '/system/dict-data/list-all-simple' }) return request.get({ url: '/system/dict-data/simple-list' })
} }
// 查询字典数据列表 // 查询字典数据列表
...@@ -45,5 +45,5 @@ export const deleteDictData = (id: number) => { ...@@ -45,5 +45,5 @@ export const deleteDictData = (id: number) => {
// 导出字典类型数据 // 导出字典类型数据
export const exportDictData = (params) => { export const exportDictData = (params) => {
return request.get({ url: '/system/dict-data/export', params }) return request.download({ url: '/system/dict-data/export', params })
} }
...@@ -40,5 +40,5 @@ export const deleteDictType = (id: number) => { ...@@ -40,5 +40,5 @@ export const deleteDictType = (id: number) => {
} }
// 导出字典类型 // 导出字典类型
export const exportDictType = (params) => { export const exportDictType = (params) => {
return request.get({ url: '/system/dict-type/export', params }) return request.download({ url: '/system/dict-type/export', params })
} }
...@@ -7,6 +7,7 @@ export interface LoginLogVO { ...@@ -7,6 +7,7 @@ export interface LoginLogVO {
userId: number userId: number
userType: number userType: number
username: string username: string
result: number
status: number status: number
userIp: string userIp: string
userAgent: string userAgent: string
......
...@@ -37,5 +37,5 @@ export const deleteMailAccount = async (id: number) => { ...@@ -37,5 +37,5 @@ export const deleteMailAccount = async (id: number) => {
// 获得邮箱账号精简列表 // 获得邮箱账号精简列表
export const getSimpleMailAccountList = async () => { export const getSimpleMailAccountList = async () => {
return request.get({ url: '/system/mail-account/list-all-simple' }) return request.get({ url: '/system/mail-account/simple-list' })
} }
...@@ -20,7 +20,7 @@ export interface MenuVO { ...@@ -20,7 +20,7 @@ export interface MenuVO {
// 查询菜单(精简)列表 // 查询菜单(精简)列表
export const getSimpleMenusList = () => { export const getSimpleMenusList = () => {
return request.get({ url: '/system/menu/list-all-simple' }) return request.get({ url: '/system/menu/simple-list' })
} }
// 查询菜单列表 // 查询菜单列表
......
...@@ -13,6 +13,7 @@ export interface NotifyMessageVO { ...@@ -13,6 +13,7 @@ export interface NotifyMessageVO {
templateParams: string templateParams: string
readStatus: boolean readStatus: boolean
readTime: Date readTime: Date
createTime: Date
} }
// 查询站内信消息列表 // 查询站内信消息列表
......
...@@ -6,7 +6,7 @@ export interface NotifyTemplateVO { ...@@ -6,7 +6,7 @@ export interface NotifyTemplateVO {
nickname: string nickname: string
code: string code: string
content: string content: string
type: number type?: number
params: string params: string
status: number status: number
remark: string remark: string
......
...@@ -17,7 +17,7 @@ export const getPostPage = async (params: PageParam) => { ...@@ -17,7 +17,7 @@ export const getPostPage = async (params: PageParam) => {
// 获取岗位精简信息列表 // 获取岗位精简信息列表
export const getSimplePostList = async (): Promise<PostVO[]> => { export const getSimplePostList = async (): Promise<PostVO[]> => {
return await request.get({ url: '/system/post/list-all-simple' }) return await request.get({ url: '/system/post/simple-list' })
} }
// 查询岗位详情 // 查询岗位详情
......
...@@ -24,7 +24,7 @@ export const getRolePage = async (params: PageParam) => { ...@@ -24,7 +24,7 @@ export const getRolePage = async (params: PageParam) => {
// 查询角色(精简)列表 // 查询角色(精简)列表
export const getSimpleRoleList = async (): Promise<RoleVO[]> => { export const getSimpleRoleList = async (): Promise<RoleVO[]> => {
return await request.get({ url: '/system/role/list-all-simple' }) return await request.get({ url: '/system/role/simple-list' })
} }
// 查询角色详情 // 查询角色详情
......
...@@ -19,7 +19,7 @@ export const getSmsChannelPage = (params: PageParam) => { ...@@ -19,7 +19,7 @@ export const getSmsChannelPage = (params: PageParam) => {
// 获得短信渠道精简列表 // 获得短信渠道精简列表
export function getSimpleSmsChannelList() { export function getSimpleSmsChannelList() {
return request.get({ url: '/system/sms-channel/list-all-simple' }) return request.get({ url: '/system/sms-channel/simple-list' })
} }
// 查询短信渠道详情 // 查询短信渠道详情
......
import request from '@/config/axios' import request from '@/config/axios'
export interface SmsTemplateVO { export interface SmsTemplateVO {
id: number | null id?: number
type: number | null type?: number
status: number status: number
code: string code: string
name: string name: string
content: string content: string
remark: string remark: string
apiTemplateId: string apiTemplateId: string
channelId: number | null channelId?: number
channelCode?: string channelCode?: string
params?: string[] params?: string[]
createTime?: Date createTime?: Date
......
...@@ -38,5 +38,5 @@ export const deleteTenantPackage = (id: number) => { ...@@ -38,5 +38,5 @@ export const deleteTenantPackage = (id: number) => {
} }
// 获取租户套餐精简信息列表 // 获取租户套餐精简信息列表
export const getTenantPackageList = () => { export const getTenantPackageList = () => {
return request.get({ url: '/system/tenant-package/get-simple-list' }) return request.get({ url: '/system/tenant-package/simple-list' })
} }
...@@ -77,5 +77,5 @@ export const updateUserStatus = (id: number, status: number) => { ...@@ -77,5 +77,5 @@ export const updateUserStatus = (id: number, status: number) => {
// 获取用户精简信息列表 // 获取用户精简信息列表
export const getSimpleUserList = (): Promise<UserVO[]> => { export const getSimpleUserList = (): Promise<UserVO[]> => {
return request.get({ url: '/system/user/list-all-simple' }) return request.get({ url: '/system/user/simple-list' })
} }
import request from '@/config/axios' import request from '@/config/axios'
export interface ProfileDept { export interface ProfileVO {
id: number id: number
name: string username: string
} nickname: string
export interface ProfileRole { dept: {
id: number id: number
name: string name: string
} }
export interface ProfilePost { roles: {
id: number id: number
name: string name: string
} }[]
export interface SocialUser { posts: {
id: number id: number
name: string
}[]
socialUsers: {
type: number type: number
openid: string openid: string
token: string }[]
rawTokenInfo: string
nickname: string
avatar: string
rawUserInfo: string
code: string
state: string
}
export interface ProfileVO {
id: number
username: string
nickname: string
dept: ProfileDept
roles: ProfileRole[]
posts: ProfilePost[]
socialUsers: SocialUser[]
email: string email: string
mobile: string mobile: string
sex: number sex: number
......
<template>
<Dialog v-model="dialogVisible" title="选择链接" width="65%">
<div class="h-500px flex gap-8px">
<!-- 左侧分组列表 -->
<el-scrollbar wrap-class="h-full" ref="groupScrollbar" view-class="flex flex-col">
<el-button
v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
:key="groupIndex"
:class="[
'm-r-16px m-l-0px! justify-start! w-90px',
{ active: activeGroup === group.name }
]"
ref="groupBtnRefs"
:text="activeGroup !== group.name"
:type="activeGroup === group.name ? 'primary' : 'default'"
@click="handleGroupSelected(group.name)"
>
{{ group.name }}
</el-button>
</el-scrollbar>
<!-- 右侧链接列表 -->
<el-scrollbar class="h-full flex-1" @scroll="handleScroll" ref="linkScrollbar">
<div v-for="(group, groupIndex) in APP_LINK_GROUP_LIST" :key="groupIndex">
<!-- 分组标题 -->
<div class="font-bold" ref="groupTitleRefs">{{ group.name }}</div>
<!-- 链接列表 -->
<el-tooltip
v-for="(appLink, appLinkIndex) in group.links"
:key="appLinkIndex"
:content="appLink.path"
placement="bottom"
>
<el-button
class="m-b-8px m-r-8px m-l-0px!"
:type="isSameLink(appLink.path, activeAppLink) ? 'primary' : 'default'"
@click="handleAppLinkSelected(appLink)"
>
{{ appLink.name }}
</el-button>
</el-tooltip>
</div>
</el-scrollbar>
</div>
<!-- 底部对话框操作按钮 -->
<template #footer>
<el-button type="primary" @click="handleSubmit">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
<Dialog v-model="detailSelectDialog.visible" title="" width="50%">
<el-form class="min-h-200px">
<el-form-item
label="选择分类"
v-if="detailSelectDialog.type === APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST"
>
<ProductCategorySelect
v-model="detailSelectDialog.id"
:parent-id="0"
@update:model-value="handleProductCategorySelected"
/>
</el-form-item>
</el-form>
</Dialog>
</template>
<script lang="ts" setup>
import { APP_LINK_GROUP_LIST, APP_LINK_TYPE_ENUM } from './data'
import { ButtonInstance, ScrollbarInstance } from 'element-plus'
import { split } from 'lodash-es'
import ProductCategorySelect from '@/views/mall/product/category/components/ProductCategorySelect.vue'
import { getUrlNumberValue } from '@/utils'
// APP 链接选择弹框
defineOptions({ name: 'AppLinkSelectDialog' })
// 选中的分组,默认选中第一个
const activeGroup = ref(APP_LINK_GROUP_LIST[0].name)
// 选中的 APP 链接
const activeAppLink = ref('')
/** 打开弹窗 */
const dialogVisible = ref(false)
const open = (link: string) => {
activeAppLink.value = link
dialogVisible.value = true
// 滚动到当前的链接
const group = APP_LINK_GROUP_LIST.find((group) =>
group.links.some((linkItem) => isSameLink(linkItem.path, link))
)
if (group) {
// 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
nextTick(() => handleGroupSelected(group.name))
}
}
defineExpose({ open })
// 处理 APP 链接选中
const handleAppLinkSelected = (appLink: any) => {
if (!isSameLink(appLink.path, activeAppLink.value)) {
activeAppLink.value = appLink.path
}
switch (appLink.type) {
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST:
detailSelectDialog.value.visible = true
detailSelectDialog.value.type = appLink.type
// 返显
detailSelectDialog.value.id =
getUrlNumberValue('id', 'http://127.0.0.1' + activeAppLink.value) || undefined
break
default:
break
}
}
// 处理绑定值更新
const emit = defineEmits<{
change: [link: string]
}>()
const handleSubmit = () => {
dialogVisible.value = false
emit('change', activeAppLink.value)
}
// 分组标题引用列表
const groupTitleRefs = ref<HTMLInputElement[]>([])
/**
* 处理右侧链接列表滚动
* @param scrollTop 滚动条的位置
*/
const handleScroll = ({ scrollTop }: { scrollTop: number }) => {
const titleEl = groupTitleRefs.value.find((titleEl) => {
// 获取标题的位置信息
const { offsetHeight, offsetTop } = titleEl
// 判断标题是否在可视范围内
return scrollTop >= offsetTop && scrollTop < offsetTop + offsetHeight
})
// 只需处理一次
if (titleEl && activeGroup.value !== titleEl.textContent) {
activeGroup.value = titleEl.textContent || ''
// 同步左侧的滚动条位置
scrollToGroupBtn(activeGroup.value)
}
}
// 右侧滚动条
const linkScrollbar = ref<ScrollbarInstance>()
// 处理分组选中
const handleGroupSelected = (group: string) => {
activeGroup.value = group
const titleRef = groupTitleRefs.value.find((item) => item.textContent === group)
if (titleRef) {
// 滚动分组标题
linkScrollbar.value?.setScrollTop(titleRef.offsetTop)
}
}
// 分组滚动条
const groupScrollbar = ref<ScrollbarInstance>()
// 分组引用列表
const groupBtnRefs = ref<ButtonInstance[]>([])
// 自动滚动分组按钮,确保分组按钮保持在可视区域内
const scrollToGroupBtn = (group: string) => {
const groupBtn = groupBtnRefs.value
.map((btn) => btn['ref'])
.find((ref) => ref.textContent === group)
if (groupBtn) {
groupScrollbar.value?.setScrollTop(groupBtn.offsetTop)
}
}
// 是否为相同的链接(不比较参数,只比较链接)
const isSameLink = (link1: string, link2: string) => {
return split(link1, '?', 1)[0] === split(link2, '?', 1)[0]
}
// 详情选择对话框
const detailSelectDialog = ref<{
visible: boolean
id?: number
type?: APP_LINK_TYPE_ENUM
}>({
visible: false,
id: undefined,
type: undefined
})
// 处理详情选择
const handleProductCategorySelected = (id: number) => {
const url = new URL(activeAppLink.value, 'http://127.0.0.1')
// 修改 id 参数
url.searchParams.set('id', `${id}`)
// 排除域名
activeAppLink.value = `${url.pathname}${url.search}`
// 关闭对话框
detailSelectDialog.value.visible = false
// 重置 id
detailSelectDialog.value.id = undefined
}
</script>
<style lang="scss" scoped></style>
// APP 链接类型(需要特殊处理,例如商品详情)
export const enum APP_LINK_TYPE_ENUM {
// 拼团活动
ACTIVITY_COMBINATION,
// 秒杀活动
ACTIVITY_SECKILL,
// 文章详情
ARTICLE_DETAIL,
// 优惠券详情
COUPON_DETAIL,
// 自定义页面详情
DIY_PAGE_DETAIL,
// 品类列表
PRODUCT_CATEGORY_LIST,
// 商品列表
PRODUCT_LIST,
// 商品详情
PRODUCT_DETAIL_NORMAL,
// 拼团商品详情
PRODUCT_DETAIL_COMBINATION,
// 积分商品详情
PRODUCT_DETAIL_POINT,
// 秒杀商品详情
PRODUCT_DETAIL_SECKILL
}
// APP 链接列表(做一下持久化?)
export const APP_LINK_GROUP_LIST = [
{
name: '商城',
links: [
{
name: '首页',
path: '/pages/index/index'
},
{
name: '商品分类',
path: '/pages/index/category',
type: APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST
},
{
name: '购物车',
path: '/pages/index/cart'
},
{
name: '个人中心',
path: '/pages/index/user'
},
{
name: '商品搜索',
path: '/pages/index/search'
},
{
name: '自定义页面',
path: '/pages/index/page',
type: APP_LINK_TYPE_ENUM.DIY_PAGE_DETAIL
},
{
name: '客服',
path: '/pages/chat/index'
},
{
name: '系统设置',
path: '/pages/public/setting'
},
{
name: '问题反馈',
path: '/pages/public/feedback'
},
{
name: '常见问题',
path: '/pages/public/faq'
}
]
},
{
name: '商品',
links: [
{
name: '商品列表',
path: '/pages/goods/list',
type: APP_LINK_TYPE_ENUM.PRODUCT_LIST
},
{
name: '商品详情',
path: '/pages/goods/index',
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_NORMAL
},
{
name: '拼团商品详情',
path: '/pages/goods/groupon',
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_COMBINATION
},
{
name: '秒杀商品详情',
path: '/pages/goods/seckill',
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_SECKILL
},
{
name: '积分商品详情',
path: '/pages/goods/score',
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_POINT
}
]
},
{
name: '营销活动',
links: [
{
name: '拼团订单',
path: '/pages/activity/groupon/order'
},
{
name: '营销商品',
path: '/pages/activity/index'
},
{
name: '拼团活动',
path: '/pages/activity/groupon/list',
type: APP_LINK_TYPE_ENUM.ACTIVITY_COMBINATION
},
{
name: '秒杀活动',
path: '/pages/activity/seckill/list',
type: APP_LINK_TYPE_ENUM.ACTIVITY_SECKILL
},
{
name: '签到中心',
path: '/pages/app/sign'
},
{
name: '积分商城',
path: '/pages/app/score-shop'
},
{
name: '优惠券中心',
path: '/pages/coupon/list'
},
{
name: '优惠券详情',
path: '/pages/coupon/detail',
type: APP_LINK_TYPE_ENUM.COUPON_DETAIL
},
{
name: '文章详情',
path: '/pages/public/richtext',
type: APP_LINK_TYPE_ENUM.ARTICLE_DETAIL
}
]
},
{
name: '分销商城',
links: [
{
name: '分销中心',
path: '/pages/commission/index'
},
{
name: '申请分销商',
path: '/pages/commission/apply'
},
{
name: '推广商品',
path: '/pages/commission/goods'
},
{
name: '分销订单',
path: '/pages/commission/order'
},
{
name: '分享记录',
path: '/pages/commission/share-log'
},
{
name: '我的团队',
path: '/pages/commission/team'
}
]
},
{
name: '支付',
links: [
{
name: '充值余额',
path: '/pages/pay/recharge'
},
{
name: '充值记录',
path: '/pages/pay/recharge-log'
},
{
name: '申请提现',
path: '/pages/pay/withdraw'
},
{
name: '提现记录',
path: '/pages/pay/withdraw-log'
}
]
},
{
name: '用户中心',
links: [
{
name: '用户信息',
path: '/pages/user/info'
},
{
name: '用户订单',
path: '/pages/order/list'
},
{
name: '售后订单',
path: '/pages/order/aftersale/list'
},
{
name: '商品收藏',
path: '/pages/user/goods-collect'
},
{
name: '浏览记录',
path: '/pages/user/goods-log'
},
{
name: '地址管理',
path: '/pages/user/address/list'
},
{
name: '发票管理',
path: '/pages/user/invoice/list'
},
{
name: '用户佣金',
path: '/pages/user/wallet/commission'
},
{
name: '用户余额',
path: '/pages/user/wallet/money'
},
{
name: '用户积分',
path: '/pages/user/wallet/score'
}
]
}
]
<template>
<el-input v-model="appLink" placeholder="输入或选择链接">
<template #append>
<el-button @click="handleOpenDialog">选择</el-button>
</template>
</el-input>
<AppLinkSelectDialog ref="dialogRef" @change="handleLinkSelected" />
</template>
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
// APP 链接输入框
defineOptions({ name: 'AppLinkInput' })
// 定义属性
const props = defineProps({
// 当前选中的链接
modelValue: propTypes.string.def('')
})
// 当前的链接
const appLink = ref('')
// 选择对话框
const dialogRef = ref()
// 处理打开对话框
const handleOpenDialog = () => dialogRef.value?.open(appLink.value)
// 处理 APP 链接选中
const handleLinkSelected = (link: string) => (appLink.value = link)
// getter
watch(
() => props.modelValue,
() => (appLink.value = props.modelValue),
{ immediate: true }
)
// setter
const emit = defineEmits<{
'update:modelValue': [link: string]
}>()
watch(
() => appLink,
() => emit('update:modelValue', appLink.value)
)
</script>
<template> <template>
<el-tabs stretch> <el-tabs stretch>
<el-tab-pane label="内容"> <el-tab-pane label="内容" v-if="$slots.default">
<slot></slot> <slot></slot>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="样式" lazy> <el-tab-pane label="样式" lazy>
......
...@@ -103,7 +103,7 @@ ...@@ -103,7 +103,7 @@
</el-form-item> </el-form-item>
</template> </template>
<el-form-item label="链接" class="m-b-8px!" label-width="50px"> <el-form-item label="链接" class="m-b-8px!" label-width="50px">
<el-input placeholder="链接" v-model="element.url" /> <AppLinkInput v-model="element.url" />
</el-form-item> </el-form-item>
</div> </div>
</template> </template>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
</UploadImg> </UploadImg>
</el-form-item> </el-form-item>
<el-form-item label="链接" prop="url"> <el-form-item label="链接" prop="url">
<el-input placeholder="链接" v-model="formData.url" /> <AppLinkInput v-model="formData.url" />
</el-form-item> </el-form-item>
</el-form> </el-form>
</ComponentContainerProperty> </ComponentContainerProperty>
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<UploadImg v-model="hotArea.imgUrl" height="80px" width="80px" /> <UploadImg v-model="hotArea.imgUrl" height="80px" width="80px" />
</el-form-item> </el-form-item>
<el-form-item label="链接" :prop="`list[${index}].url`"> <el-form-item label="链接" :prop="`list[${index}].url`">
<el-input v-model="hotArea.url" placeholder="请输入链接" /> <AppLinkInput v-model="hotArea.url" />
</el-form-item> </el-form-item>
</template> </template>
</template> </template>
......
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" /> <InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
</el-form-item> </el-form-item>
<el-form-item label="链接" prop="url"> <el-form-item label="链接" prop="url">
<el-input v-model="element.url" /> <AppLinkInput v-model="element.url" />
</el-form-item> </el-form-item>
<el-form-item label="显示角标" prop="badge.show"> <el-form-item label="显示角标" prop="badge.show">
<el-switch v-model="element.badge.show" /> <el-switch v-model="element.badge.show" />
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" /> <InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
</el-form-item> </el-form-item>
<el-form-item label="链接" prop="url"> <el-form-item label="链接" prop="url">
<el-input v-model="element.url" /> <AppLinkInput v-model="element.url" />
</el-form-item> </el-form-item>
</div> </div>
</template> </template>
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
<InputWithColor v-model="element.title" v-model:color="element.titleColor" /> <InputWithColor v-model="element.title" v-model:color="element.titleColor" />
</el-form-item> </el-form-item>
<el-form-item label="链接" prop="url"> <el-form-item label="链接" prop="url">
<el-input v-model="element.url" /> <AppLinkInput v-model="element.url" />
</el-form-item> </el-form-item>
<el-form-item label="显示角标" prop="badge.show"> <el-form-item label="显示角标" prop="badge.show">
<el-switch v-model="element.badge.show" /> <el-switch v-model="element.badge.show" />
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
</div> </div>
<div class="w-full flex flex-col gap-8px"> <div class="w-full flex flex-col gap-8px">
<el-input v-model="element.text" placeholder="请输入公告" /> <el-input v-model="element.text" placeholder="请输入公告" />
<el-input v-model="element.url" placeholder="请输入链接" /> <AppLinkInput v-model="element.url" />
</div> </div>
</div> </div>
</template> </template>
......
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util' import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 商品卡片属性 */ /** 商品属性 */
export interface ProductListProperty { export interface ProductListProperty {
// 布局类型:双列 | 三列 | 水平滑动 // 布局类型:双列 | 三列 | 水平滑动
layoutType: 'twoCol' | 'threeCol' | 'horizSwiper' layoutType: 'twoCol' | 'threeCol' | 'horizSwiper'
......
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
import { ProductListProperty } from './config' import { ProductListProperty } from './config'
import * as ProductSpuApi from '@/api/mall/product/spu' import * as ProductSpuApi from '@/api/mall/product/spu'
/** 商品卡片 */ /** 商品 */
defineOptions({ name: 'ProductList' }) defineOptions({ name: 'ProductList' })
// 定义属性 // 定义属性
const props = defineProps<{ property: ProductListProperty }>() const props = defineProps<{ property: ProductListProperty }>()
......
...@@ -88,7 +88,7 @@ import { ProductListProperty } from './config' ...@@ -88,7 +88,7 @@ import { ProductListProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util' import { usePropertyForm } from '@/components/DiyEditor/util'
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue' import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
// 商品卡片属性面板 // 商品属性面板
defineOptions({ name: 'ProductListProperty' }) defineOptions({ name: 'ProductListProperty' })
const props = defineProps<{ modelValue: ProductListProperty }>() const props = defineProps<{ modelValue: ProductListProperty }>()
......
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 营销文章属性 */
export interface PromotionArticleProperty {
// 文章编号
id: number
// 组件样式
style: ComponentStyle
}
// 定义组件
export const component = {
id: 'PromotionArticle',
name: '营销文章',
icon: 'ph:article-medium',
property: {
style: {
bgType: 'color',
bgColor: '',
marginLeft: 8,
marginRight: 8,
marginBottom: 8
} as ComponentStyle
}
} as DiyComponent<PromotionArticleProperty>
<template>
<div class="min-h-30px" v-html="article.content"></div>
</template>
<script setup lang="ts">
import { PromotionArticleProperty } from './config'
import * as ArticleApi from '@/api/mall/promotion/article/index'
/** 营销文章 */
defineOptions({ name: 'PromotionArticle' })
// 定义属性
const props = defineProps<{ property: PromotionArticleProperty }>()
// 商品列表
const article = ref<ArticleApi.ArticleVO[]>({})
watch(
() => props.property.id,
async () => {
if (props.property.id) {
article.value = await ArticleApi.getArticle(props.property.id)
}
},
{
immediate: true
}
)
</script>
<style scoped lang="scss"></style>
<template>
<ComponentContainerProperty v-model="formData.style">
<el-form label-width="40px" :model="formData">
<el-form-item label="文章" prop="id">
<el-select
v-model="formData.id"
placeholder="请选择文章"
class="w-full"
filterable
remote
:remote-method="queryArticleList"
:loading="loading"
>
<el-option
v-for="article in articles"
:key="article.id"
:label="article.title"
:value="article.id"
/>
</el-select>
</el-form-item>
</el-form>
</ComponentContainerProperty>
</template>
<script setup lang="ts">
import { PromotionArticleProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
import * as ArticleApi from '@/api/mall/promotion/article/index'
// 营销文章属性面板
defineOptions({ name: 'PromotionArticleProperty' })
const props = defineProps<{ modelValue: PromotionArticleProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
// 文章列表
const articles = ref<ArticleApi.ArticleVO>([])
// 加载中
const loading = ref(false)
// 查询文章列表
const queryArticleList = async (title?: string) => {
loading.value = true
const { list } = await ArticleApi.getArticlePage({ title, pageSize: 10 })
articles.value = list
loading.value = false
}
// 初始化
onMounted(() => {
queryArticleList()
})
</script>
<style scoped lang="scss"></style>
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 拼团属性 */
export interface PromotionCombinationProperty {
// 布局类型:单列 | 三列
layoutType: 'oneCol' | 'threeCol'
// 商品字段
fields: {
// 商品名称
name: PromotionCombinationFieldProperty
// 商品价格
price: PromotionCombinationFieldProperty
}
// 角标
badge: {
// 是否显示
show: boolean
// 角标图片
imgUrl: string
}
// 上圆角
borderRadiusTop: number
// 下圆角
borderRadiusBottom: number
// 间距
space: number
// 拼团活动编号
activityId: number
// 组件样式
style: ComponentStyle
}
// 商品字段
export interface PromotionCombinationFieldProperty {
// 是否显示
show: boolean
// 颜色
color: string
}
// 定义组件
export const component = {
id: 'PromotionCombination',
name: '拼团',
icon: 'mdi:account-group',
property: {
activityId: undefined,
layoutType: 'oneCol',
fields: {
name: { show: true, color: '#000' },
price: { show: true, color: '#ff3000' }
},
badge: { show: false, imgUrl: '' },
borderRadiusTop: 8,
borderRadiusBottom: 8,
space: 8,
style: {
bgType: 'color',
bgColor: '',
marginLeft: 8,
marginRight: 8,
marginBottom: 8
} as ComponentStyle
}
} as DiyComponent<PromotionCombinationProperty>
<template>
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
<!-- 商品网格 -->
<div
class="grid overflow-x-auto"
:style="{
gridGap: `${property.space}px`,
gridTemplateColumns,
width: scrollbarWidth
}"
>
<!-- 商品 -->
<div
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
:style="{
borderTopLeftRadius: `${property.borderRadiusTop}px`,
borderTopRightRadius: `${property.borderRadiusTop}px`,
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
borderBottomRightRadius: `${property.borderRadiusBottom}px`
}"
v-for="(spu, index) in spuList"
:key="index"
>
<!-- 角标 -->
<div
v-if="property.badge.show"
class="absolute left-0 top-0 z-1 items-center justify-center"
>
<el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
</div>
<!-- 商品封面图 -->
<el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" />
<div
:class="[
'flex flex-col gap-8px p-8px box-border',
{
'w-[calc(100%-64px)]': columns === 2,
'w-full': columns === 3
}
]"
>
<!-- 商品名称 -->
<div
v-if="property.fields.name.show"
class="truncate text-12px"
:style="{ color: property.fields.name.color }"
>
{{ spu.name }}
</div>
<div>
<!-- 商品价格 -->
<span
v-if="property.fields.price.show"
class="text-12px"
:style="{ color: property.fields.price.color }"
>
{{ spu.price }}
</span>
</div>
</div>
</div>
</div>
</el-scrollbar>
</template>
<script setup lang="ts">
import { PromotionCombinationProperty } from './config'
import * as ProductSpuApi from '@/api/mall/product/spu'
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
/** 拼团 */
defineOptions({ name: 'PromotionCombination' })
// 定义属性
const props = defineProps<{ property: PromotionCombinationProperty }>()
// 商品列表
const spuList = ref<ProductSpuApi.Spu[]>([])
watch(
() => props.property.activityId,
async () => {
if (!props.property.activityId) return
const activity = await CombinationActivityApi.getCombinationActivity(props.property.activityId)
if (!activity?.spuId) return
spuList.value = [await ProductSpuApi.getSpu(activity.spuId)]
},
{
immediate: true,
deep: true
}
)
// 手机宽度
const phoneWidth = ref(375)
// 容器
const containerRef = ref()
// 商品的列数
const columns = ref(2)
// 滚动条宽度
const scrollbarWidth = ref('100%')
// 商品图大小
const imageSize = ref('0')
// 商品网络列数
const gridTemplateColumns = ref('')
// 计算布局参数
watch(
() => [props.property, phoneWidth, spuList.value.length],
() => {
// 计算列数
columns.value = props.property.layoutType === 'oneCol' ? 1 : 3
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
const productWidth =
(phoneWidth.value - props.property.space * (columns.value - 1)) / columns.value
// 商品图布局:2列时,左右布局 3列时,上下布局
imageSize.value = columns.value === 2 ? '64px' : `${productWidth}px`
// 指定列数
gridTemplateColumns.value = `repeat(${columns.value}, auto)`
// 不滚动
scrollbarWidth.value = '100%'
},
{ immediate: true, deep: true }
)
onMounted(() => {
// 提取手机宽度
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375
})
</script>
<style scoped lang="scss"></style>
<template>
<ComponentContainerProperty v-model="formData.style">
<el-form label-width="80px" :model="formData">
<el-card header="拼团活动" class="property-group" shadow="never">
<el-form-item label="拼团活动" prop="activityId">
<el-select v-model="formData.activityId">
<el-option
v-for="activity in activityList"
:key="activity.id"
:label="activity.name"
:value="activity.id"
/>
</el-select>
</el-form-item>
</el-card>
<el-card header="商品样式" class="property-group" shadow="never">
<el-form-item label="布局" prop="type">
<el-radio-group v-model="formData.layoutType">
<el-tooltip class="item" content="单列" placement="bottom">
<el-radio-button label="oneCol">
<Icon icon="fluent:text-column-one-24-filled" />
</el-radio-button>
</el-tooltip>
<el-tooltip class="item" content="三列" placement="bottom">
<el-radio-button label="threeCol">
<Icon icon="fluent:text-column-three-24-filled" />
</el-radio-button>
</el-tooltip>
</el-radio-group>
</el-form-item>
<el-form-item label="商品名称" prop="fields.name.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.name.color" />
<el-checkbox v-model="formData.fields.name.show" />
</div>
</el-form-item>
<el-form-item label="商品价格" prop="fields.price.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.price.color" />
<el-checkbox v-model="formData.fields.price.show" />
</div>
</el-form-item>
</el-card>
<el-card header="角标" class="property-group" shadow="never">
<el-form-item label="角标" prop="badge.show">
<el-switch v-model="formData.badge.show" />
</el-form-item>
<el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
<template #tip> 建议尺寸:36 * 22 </template>
</UploadImg>
</el-form-item>
</el-card>
<el-card header="商品样式" class="property-group" shadow="never">
<el-form-item label="上圆角" prop="borderRadiusTop">
<el-slider
v-model="formData.borderRadiusTop"
:max="100"
:min="0"
show-input
input-size="small"
:show-input-controls="false"
/>
</el-form-item>
<el-form-item label="下圆角" prop="borderRadiusBottom">
<el-slider
v-model="formData.borderRadiusBottom"
:max="100"
:min="0"
show-input
input-size="small"
:show-input-controls="false"
/>
</el-form-item>
<el-form-item label="间隔" prop="space">
<el-slider
v-model="formData.space"
:max="100"
:min="0"
show-input
input-size="small"
:show-input-controls="false"
/>
</el-form-item>
</el-card>
</el-form>
</ComponentContainerProperty>
</template>
<script setup lang="ts">
import { PromotionCombinationProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
import { CommonStatusEnum } from '@/utils/constants'
// 拼团属性面板
defineOptions({ name: 'PromotionCombinationProperty' })
const props = defineProps<{ modelValue: PromotionCombinationProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
// 活动列表
const activityList = ref<CombinationActivityApi.CombinationActivityVO>([])
onMounted(async () => {
const { list } = await CombinationActivityApi.getCombinationActivityPage({
status: CommonStatusEnum.ENABLE
})
activityList.value = list
})
</script>
<style scoped lang="scss"></style>
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 秒杀属性 */
export interface PromotionSeckillProperty {
// 布局类型:单列 | 三列
layoutType: 'oneCol' | 'threeCol'
// 商品字段
fields: {
// 商品名称
name: PromotionSeckillFieldProperty
// 商品价格
price: PromotionSeckillFieldProperty
}
// 角标
badge: {
// 是否显示
show: boolean
// 角标图片
imgUrl: string
}
// 上圆角
borderRadiusTop: number
// 下圆角
borderRadiusBottom: number
// 间距
space: number
// 秒杀活动编号
activityId: number
// 组件样式
style: ComponentStyle
}
// 商品字段
export interface PromotionSeckillFieldProperty {
// 是否显示
show: boolean
// 颜色
color: string
}
// 定义组件
export const component = {
id: 'PromotionSeckill',
name: '秒杀',
icon: 'mdi:calendar-time',
property: {
activityId: undefined,
layoutType: 'oneCol',
fields: {
name: { show: true, color: '#000' },
price: { show: true, color: '#ff3000' }
},
badge: { show: false, imgUrl: '' },
borderRadiusTop: 8,
borderRadiusBottom: 8,
space: 8,
style: {
bgType: 'color',
bgColor: '',
marginLeft: 8,
marginRight: 8,
marginBottom: 8
} as ComponentStyle
}
} as DiyComponent<PromotionSeckillProperty>
<template>
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
<!-- 商品网格 -->
<div
class="grid overflow-x-auto"
:style="{
gridGap: `${property.space}px`,
gridTemplateColumns,
width: scrollbarWidth
}"
>
<!-- 商品 -->
<div
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
:style="{
borderTopLeftRadius: `${property.borderRadiusTop}px`,
borderTopRightRadius: `${property.borderRadiusTop}px`,
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
borderBottomRightRadius: `${property.borderRadiusBottom}px`
}"
v-for="(spu, index) in spuList"
:key="index"
>
<!-- 角标 -->
<div
v-if="property.badge.show"
class="absolute left-0 top-0 z-1 items-center justify-center"
>
<el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
</div>
<!-- 商品封面图 -->
<el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" />
<div
:class="[
'flex flex-col gap-8px p-8px box-border',
{
'w-[calc(100%-64px)]': columns === 2,
'w-full': columns === 3
}
]"
>
<!-- 商品名称 -->
<div
v-if="property.fields.name.show"
class="truncate text-12px"
:style="{ color: property.fields.name.color }"
>
{{ spu.name }}
</div>
<div>
<!-- 商品价格 -->
<span
v-if="property.fields.price.show"
class="text-12px"
:style="{ color: property.fields.price.color }"
>
{{ spu.price }}
</span>
</div>
</div>
</div>
</div>
</el-scrollbar>
</template>
<script setup lang="ts">
import { PromotionSeckillProperty } from './config'
import * as ProductSpuApi from '@/api/mall/product/spu'
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
/** 秒杀 */
defineOptions({ name: 'PromotionSeckill' })
// 定义属性
const props = defineProps<{ property: PromotionSeckillProperty }>()
// 商品列表
const spuList = ref<ProductSpuApi.Spu[]>([])
watch(
() => props.property.activityId,
async () => {
if (!props.property.activityId) return
const activity = await SeckillActivityApi.getSeckillActivity(props.property.activityId)
if (!activity?.spuId) return
spuList.value = [await ProductSpuApi.getSpu(activity.spuId)]
},
{
immediate: true,
deep: true
}
)
// 手机宽度
const phoneWidth = ref(375)
// 容器
const containerRef = ref()
// 商品的列数
const columns = ref(2)
// 滚动条宽度
const scrollbarWidth = ref('100%')
// 商品图大小
const imageSize = ref('0')
// 商品网络列数
const gridTemplateColumns = ref('')
// 计算布局参数
watch(
() => [props.property, phoneWidth, spuList.value.length],
() => {
// 计算列数
columns.value = props.property.layoutType === 'oneCol' ? 1 : 3
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
const productWidth =
(phoneWidth.value - props.property.space * (columns.value - 1)) / columns.value
// 商品图布局:2列时,左右布局 3列时,上下布局
imageSize.value = columns.value === 2 ? '64px' : `${productWidth}px`
// 指定列数
gridTemplateColumns.value = `repeat(${columns.value}, auto)`
// 不滚动
scrollbarWidth.value = '100%'
},
{ immediate: true, deep: true }
)
onMounted(() => {
// 提取手机宽度
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375
})
</script>
<style scoped lang="scss"></style>
<template>
<ComponentContainerProperty v-model="formData.style">
<el-form label-width="80px" :model="formData">
<el-card header="秒杀活动" class="property-group" shadow="never">
<el-form-item label="秒杀活动" prop="activityId">
<el-select v-model="formData.activityId">
<el-option
v-for="activity in activityList"
:key="activity.id"
:label="activity.name"
:value="activity.id"
/>
</el-select>
</el-form-item>
</el-card>
<el-card header="商品样式" class="property-group" shadow="never">
<el-form-item label="布局" prop="type">
<el-radio-group v-model="formData.layoutType">
<el-tooltip class="item" content="单列" placement="bottom">
<el-radio-button label="oneCol">
<Icon icon="fluent:text-column-one-24-filled" />
</el-radio-button>
</el-tooltip>
<el-tooltip class="item" content="三列" placement="bottom">
<el-radio-button label="threeCol">
<Icon icon="fluent:text-column-three-24-filled" />
</el-radio-button>
</el-tooltip>
</el-radio-group>
</el-form-item>
<el-form-item label="商品名称" prop="fields.name.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.name.color" />
<el-checkbox v-model="formData.fields.name.show" />
</div>
</el-form-item>
<el-form-item label="商品价格" prop="fields.price.show">
<div class="flex gap-8px">
<ColorInput v-model="formData.fields.price.color" />
<el-checkbox v-model="formData.fields.price.show" />
</div>
</el-form-item>
</el-card>
<el-card header="角标" class="property-group" shadow="never">
<el-form-item label="角标" prop="badge.show">
<el-switch v-model="formData.badge.show" />
</el-form-item>
<el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
<template #tip> 建议尺寸:36 * 22 </template>
</UploadImg>
</el-form-item>
</el-card>
<el-card header="商品样式" class="property-group" shadow="never">
<el-form-item label="上圆角" prop="borderRadiusTop">
<el-slider
v-model="formData.borderRadiusTop"
:max="100"
:min="0"
show-input
input-size="small"
:show-input-controls="false"
/>
</el-form-item>
<el-form-item label="下圆角" prop="borderRadiusBottom">
<el-slider
v-model="formData.borderRadiusBottom"
:max="100"
:min="0"
show-input
input-size="small"
:show-input-controls="false"
/>
</el-form-item>
<el-form-item label="间隔" prop="space">
<el-slider
v-model="formData.space"
:max="100"
:min="0"
show-input
input-size="small"
:show-input-controls="false"
/>
</el-form-item>
</el-card>
</el-form>
</ComponentContainerProperty>
</template>
<script setup lang="ts">
import { PromotionSeckillProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
import { CommonStatusEnum } from '@/utils/constants'
// 秒杀属性面板
defineOptions({ name: 'PromotionSeckillProperty' })
const props = defineProps<{ modelValue: PromotionSeckillProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
// 活动列表
const activityList = ref<SeckillActivityApi.SeckillActivityVO>([])
onMounted(async () => {
const { list } = await SeckillActivityApi.getSeckillActivityPage({
status: CommonStatusEnum.ENABLE
})
activityList.value = list
})
</script>
<style scoped lang="scss"></style>
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
<el-input v-model="element.text" placeholder="请输入文字" /> <el-input v-model="element.text" placeholder="请输入文字" />
</el-form-item> </el-form-item>
<el-form-item prop="url" label-width="0" class="m-b-0!"> <el-form-item prop="url" label-width="0" class="m-b-0!">
<el-input v-model="element.url" placeholder="请选择链接" /> <AppLinkInput v-model="element.url" />
</el-form-item> </el-form-item>
</div> </div>
</div> </div>
......
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
<el-input v-model="formData.more.text" /> <el-input v-model="formData.more.text" />
</el-form-item> </el-form-item>
<el-form-item label="跳转链接" prop="more.url"> <el-form-item label="跳转链接" prop="more.url">
<el-input v-model="formData.more.url" placeholder="请输入跳转链接" /> <AppLinkInput v-model="formData.more.url" />
</el-form-item> </el-form-item>
</template> </template>
</el-form> </el-form>
......
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 用户卡片属性 */
export interface UserCardProperty {
// 组件样式
style: ComponentStyle
}
// 定义组件
export const component = {
id: 'UserCard',
name: '用户卡片',
icon: 'mdi:user-card-details',
property: {
style: {
bgType: 'color',
bgColor: '',
marginBottom: 8
} as ComponentStyle
}
} as DiyComponent<UserCardProperty>
<template>
<div class="flex flex-col">
<div class="flex items-center justify-between p-x-18px p-y-24px">
<div class="flex flex-1 items-center gap-16px">
<el-avatar :size="60">
<Icon icon="ep:avatar" :size="60" />
</el-avatar>
<span class="text-18px font-bold">芋道源码</span>
</div>
<Icon icon="tdesign:qrcode" :size="20" />
</div>
<div
class="flex items-center justify-between justify-between bg-white p-x-20px p-y-8px text-12px"
>
<span class="color-#ff690d">点击绑定手机号</span>
<span class="rounded-26px bg-#ff6100 p-x-8px p-y-5px color-white">去绑定</span>
</div>
</div>
</template>
<script setup lang="ts">
import { UserCardProperty } from './config'
/** 用户卡片 */
defineOptions({ name: 'UserCard' })
// 定义属性
defineProps<{ property: UserCardProperty }>()
</script>
<style scoped lang="scss"></style>
<template>
<ComponentContainerProperty v-model="formData.style" />
</template>
<script setup lang="ts">
import { UserCardProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
// 用户卡片属性面板
defineOptions({ name: 'UserCardProperty' })
const props = defineProps<{ modelValue: UserCardProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
</script>
<style scoped lang="scss"></style>
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 用户卡券属性 */
export interface UserCouponProperty {
// 组件样式
style: ComponentStyle
}
// 定义组件
export const component = {
id: 'UserCoupon',
name: '用户卡券',
icon: 'ep:ticket',
property: {
style: {
bgType: 'color',
bgColor: '',
marginLeft: 8,
marginRight: 8,
marginBottom: 8
} as ComponentStyle
}
} as DiyComponent<UserCouponProperty>
<template>
<el-image
src="https://shopro.sheepjs.com/admin/static/images/shop/decorate/couponCardStyle.png"
/>
</template>
<script setup lang="ts">
import { UserCouponProperty } from './config'
/** 用户卡券 */
defineOptions({ name: 'UserCoupon' })
// 定义属性
defineProps<{ property: UserCouponProperty }>()
</script>
<style scoped lang="scss"></style>
<template>
<ComponentContainerProperty v-model="formData.style" />
</template>
<script setup lang="ts">
import { UserCouponProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
// 用户卡券属性面板
defineOptions({ name: 'UserCouponProperty' })
const props = defineProps<{ modelValue: UserCouponProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
</script>
<style scoped lang="scss"></style>
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 用户订单属性 */
export interface UserOrderProperty {
// 组件样式
style: ComponentStyle
}
// 定义组件
export const component = {
id: 'UserOrder',
name: '用户订单',
icon: 'ep:list',
property: {
style: {
bgType: 'color',
bgColor: '',
marginLeft: 8,
marginRight: 8,
marginBottom: 8
} as ComponentStyle
}
} as DiyComponent<UserOrderProperty>
<template>
<el-image src="https://shopro.sheepjs.com/admin/static/images/shop/decorate/orderCardStyle.png" />
</template>
<script setup lang="ts">
import { UserOrderProperty } from './config'
/** 用户订单 */
defineOptions({ name: 'UserOrder' })
// 定义属性
defineProps<{ property: UserOrderProperty }>()
</script>
<style scoped lang="scss"></style>
<template>
<ComponentContainerProperty v-model="formData.style" />
</template>
<script setup lang="ts">
import { UserOrderProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
// 用户订单属性面板
defineOptions({ name: 'UserOrderProperty' })
const props = defineProps<{ modelValue: UserOrderProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
</script>
<style scoped lang="scss"></style>
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 用户资产属性 */
export interface UserWalletProperty {
// 组件样式
style: ComponentStyle
}
// 定义组件
export const component = {
id: 'UserWallet',
name: '用户资产',
icon: 'ep:wallet-filled',
property: {
style: {
bgType: 'color',
bgColor: '',
marginLeft: 8,
marginRight: 8,
marginBottom: 8
} as ComponentStyle
}
} as DiyComponent<UserWalletProperty>
<template>
<el-image
src="https://shopro.sheepjs.com/admin/static/images/shop/decorate/walletCardStyle.png"
/>
</template>
<script setup lang="ts">
import { UserWalletProperty } from './config'
/** 用户资产 */
defineOptions({ name: 'UserWallet' })
// 定义属性
defineProps<{ property: UserWalletProperty }>()
</script>
<style scoped lang="scss"></style>
<template>
<ComponentContainerProperty v-model="formData.style" />
</template>
<script setup lang="ts">
import { UserWalletProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
// 用户资产属性面板
defineOptions({ name: 'UserWalletProperty' })
const props = defineProps<{ modelValue: UserWalletProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
</script>
<style scoped lang="scss"></style>
...@@ -109,13 +109,19 @@ export const PAGE_LIBS = [ ...@@ -109,13 +109,19 @@ export const PAGE_LIBS = [
}, },
{ name: '商品组件', extended: true, components: ['ProductCard', 'ProductList'] }, { name: '商品组件', extended: true, components: ['ProductCard', 'ProductList'] },
{ {
name: '会员组件', name: '用户组件',
extended: true, extended: true,
components: ['UserCard', 'UserOrder', 'UserWallet', 'UserCoupon'] components: ['UserCard', 'UserOrder', 'UserWallet', 'UserCoupon']
}, },
{ {
name: '营销组件', name: '营销组件',
extended: true, extended: true,
components: ['CombinationCard', 'SeckillCard', 'PointCard', 'CouponCard'] components: [
'PromotionCombination',
'PromotionSeckill',
'PromotionPoint',
'CouponCard',
'PromotionArticle'
]
} }
] as DiyComponentLibrary[] ] as DiyComponentLibrary[]
<template> <template>
<ElDialog v-model="showSearch" :show-close="false" title="菜单搜索"> <ElDialog v-if="isModal" v-model="showSearch" :show-close="false" title="菜单搜索">
<el-select <el-select
filterable filterable
:reserve-keyword="false" :reserve-keyword="false"
...@@ -17,11 +17,39 @@ ...@@ -17,11 +17,39 @@
/> />
</el-select> </el-select>
</ElDialog> </ElDialog>
<div v-else class="custom-hover" @click.stop="showTopSearch = !showTopSearch">
<Icon icon="ep:search" />
<el-select
filterable
:reserve-keyword="false"
remote
placeholder="请输入菜单内容"
:remote-method="remoteMethod"
class="overflow-hidden transition-all-600"
:class="showTopSearch ? 'w-220px ml2' : 'w-0'"
@change="handleChange"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
defineProps({
isModal: {
type: Boolean,
default: true
}
})
const router = useRouter() // 路由对象 const router = useRouter() // 路由对象
const showSearch = ref(false) // 是否显示弹框 const showSearch = ref(false) // 是否显示弹框
const showTopSearch = ref(false) // 是否显示顶部搜索框
const value: Ref = ref('') // 用户输入的值 const value: Ref = ref('') // 用户输入的值
const routers = router.getRoutes() // 路由对象 const routers = router.getRoutes() // 路由对象
...@@ -50,14 +78,21 @@ function remoteMethod(data) { ...@@ -50,14 +78,21 @@ function remoteMethod(data) {
function handleChange(path) { function handleChange(path) {
router.push({ path }) router.push({ path })
hiddenTopSearch()
}
function hiddenTopSearch() {
showTopSearch.value = false
} }
onMounted(() => { onMounted(() => {
window.addEventListener('keydown', listenKey) window.addEventListener('keydown', listenKey)
window.addEventListener('click', hiddenTopSearch)
}) })
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('keydown', listenKey) window.removeEventListener('keydown', listenKey)
window.removeEventListener('click', hiddenTopSearch)
}) })
// 监听 ctrl + k // 监听 ctrl + k
......
<template> <template>
<div class="upload-box"> <div class="upload-box">
<el-upload <el-upload
:action="updateUrl"
:id="uuid" :id="uuid"
:accept="fileType.join(',')"
:action="updateUrl"
:before-upload="beforeUpload"
:class="['upload', drag ? 'no-border' : '']" :class="['upload', drag ? 'no-border' : '']"
:multiple="false" :drag="drag"
:show-file-list="false"
:headers="uploadHeaders" :headers="uploadHeaders"
:before-upload="beforeUpload" :multiple="false"
:on-success="uploadSuccess"
:on-error="uploadError" :on-error="uploadError"
:drag="drag" :on-success="uploadSuccess"
:accept="fileType.join(',')" :show-file-list="false"
> >
<template v-if="modelValue"> <template v-if="modelValue">
<img :src="modelValue" class="upload-image" /> <img :src="modelValue" class="upload-image" />
...@@ -20,11 +20,11 @@ ...@@ -20,11 +20,11 @@
<Icon icon="ep:edit" /> <Icon icon="ep:edit" />
<span v-if="showBtnText">{{ t('action.edit') }}</span> <span v-if="showBtnText">{{ t('action.edit') }}</span>
</div> </div>
<div class="handle-icon" @click="imgViewVisible = true"> <div class="handle-icon" @click="imagePreview(modelValue)">
<Icon icon="ep:zoom-in" /> <Icon icon="ep:zoom-in" />
<span v-if="showBtnText">{{ t('action.detail') }}</span> <span v-if="showBtnText">{{ t('action.detail') }}</span>
</div> </div>
<div class="handle-icon" @click="deleteImg" v-if="showDelete"> <div v-if="showDelete" class="handle-icon" @click="deleteImg">
<Icon icon="ep:delete" /> <Icon icon="ep:delete" />
<span v-if="showBtnText">{{ t('action.del') }}</span> <span v-if="showBtnText">{{ t('action.del') }}</span>
</div> </div>
...@@ -42,11 +42,6 @@ ...@@ -42,11 +42,6 @@
<div class="el-upload__tip"> <div class="el-upload__tip">
<slot name="tip"></slot> <slot name="tip"></slot>
</div> </div>
<el-image-viewer
v-if="imgViewVisible"
@close="imgViewVisible = false"
:url-list="[modelValue]"
/>
</div> </div>
</template> </template>
...@@ -56,6 +51,7 @@ import type { UploadProps } from 'element-plus' ...@@ -56,6 +51,7 @@ import type { UploadProps } from 'element-plus'
import { generateUUID } from '@/utils' import { generateUUID } from '@/utils'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { getAccessToken, getTenantId } from '@/utils/auth' import { getAccessToken, getTenantId } from '@/utils/auth'
import { createImageViewer } from '@/components/ImageViewer'
defineOptions({ name: 'UploadImg' }) defineOptions({ name: 'UploadImg' })
...@@ -92,7 +88,12 @@ const message = useMessage() // 消息弹窗 ...@@ -92,7 +88,12 @@ const message = useMessage() // 消息弹窗
// 生成组件唯一id // 生成组件唯一id
const uuid = ref('id-' + generateUUID()) const uuid = ref('id-' + generateUUID())
// 查看图片 // 查看图片
const imgViewVisible = ref(false) const imagePreview = (imgUrl: string) => {
createImageViewer({
zIndex: 9999999,
urlList: [imgUrl]
})
}
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
...@@ -130,7 +131,7 @@ const uploadError = () => { ...@@ -130,7 +131,7 @@ const uploadError = () => {
message.notifyError('图片上传失败,请您重新上传!') message.notifyError('图片上传失败,请您重新上传!')
} }
</script> </script>
<style scoped lang="scss"> <style lang="scss" scoped>
.is-error { .is-error {
.upload { .upload {
:deep(.el-upload), :deep(.el-upload),
......
...@@ -7,6 +7,7 @@ import { Screenfull } from '@/layout/components/Screenfull' ...@@ -7,6 +7,7 @@ import { Screenfull } from '@/layout/components/Screenfull'
import { Breadcrumb } from '@/layout/components/Breadcrumb' import { Breadcrumb } from '@/layout/components/Breadcrumb'
import { SizeDropdown } from '@/layout/components/SizeDropdown' import { SizeDropdown } from '@/layout/components/SizeDropdown'
import { LocaleDropdown } from '@/layout/components/LocaleDropdown' import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
import RouterSearch from '@/components/RouterSearch/index.vue'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
...@@ -25,6 +26,9 @@ const hamburger = computed(() => appStore.getHamburger) ...@@ -25,6 +26,9 @@ const hamburger = computed(() => appStore.getHamburger)
// 全屏图标 // 全屏图标
const screenfull = computed(() => appStore.getScreenfull) const screenfull = computed(() => appStore.getScreenfull)
// 搜索图片
const search = computed(() => appStore.search)
// 尺寸图标 // 尺寸图标
const size = computed(() => appStore.getSize) const size = computed(() => appStore.getSize)
...@@ -61,6 +65,7 @@ export default defineComponent({ ...@@ -61,6 +65,7 @@ export default defineComponent({
{screenfull.value ? ( {screenfull.value ? (
<Screenfull class="custom-hover" color="var(--top-header-text-color)"></Screenfull> <Screenfull class="custom-hover" color="var(--top-header-text-color)"></Screenfull>
) : undefined} ) : undefined}
{search.value ? <RouterSearch isModal={false} /> : undefined}
{size.value ? ( {size.value ? (
<SizeDropdown class="custom-hover" color="var(--top-header-text-color)"></SizeDropdown> <SizeDropdown class="custom-hover" color="var(--top-header-text-color)"></SizeDropdown>
) : undefined} ) : undefined}
......
...@@ -16,6 +16,7 @@ interface AppState { ...@@ -16,6 +16,7 @@ interface AppState {
uniqueOpened: boolean uniqueOpened: boolean
hamburger: boolean hamburger: boolean
screenfull: boolean screenfull: boolean
search: boolean
size: boolean size: boolean
locale: boolean locale: boolean
message: boolean message: boolean
...@@ -52,6 +53,7 @@ export const useAppStore = defineStore('app', { ...@@ -52,6 +53,7 @@ export const useAppStore = defineStore('app', {
uniqueOpened: true, // 是否只保持一个子菜单的展开 uniqueOpened: true, // 是否只保持一个子菜单的展开
hamburger: true, // 折叠图标 hamburger: true, // 折叠图标
screenfull: true, // 全屏图标 screenfull: true, // 全屏图标
search: true, // 搜索图标
size: true, // 尺寸图标 size: true, // 尺寸图标
locale: true, // 多语言图标 locale: true, // 多语言图标
message: true, // 消息图标 message: true, // 消息图标
......
...@@ -4,7 +4,7 @@ import { store } from '../index' ...@@ -4,7 +4,7 @@ import { store } from '../index'
import { DictDataVO } from '@/api/system/dict/types' import { DictDataVO } from '@/api/system/dict/types'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache' import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache('sessionStorage') const { wsCache } = useCache('sessionStorage')
import { listSimpleDictData } from '@/api/system/dict/dict.data' import { getSimpleDictDataList } from '@/api/system/dict/dict.data'
export interface DictValueType { export interface DictValueType {
value: any value: any
...@@ -45,7 +45,7 @@ export const useDictStore = defineStore('dict', { ...@@ -45,7 +45,7 @@ export const useDictStore = defineStore('dict', {
this.dictMap = dictMap this.dictMap = dictMap
this.isSetDict = true this.isSetDict = true
} else { } else {
const res = await listSimpleDictData() const res = await getSimpleDictDataList()
// 设置数据 // 设置数据
const dictDataMap = new Map<string, any>() const dictDataMap = new Map<string, any>()
res.forEach((dictData: DictDataVO) => { res.forEach((dictData: DictDataVO) => {
...@@ -75,7 +75,7 @@ export const useDictStore = defineStore('dict', { ...@@ -75,7 +75,7 @@ export const useDictStore = defineStore('dict', {
}, },
async resetDict() { async resetDict() {
wsCache.delete(CACHE_KEY.DICT_CACHE) wsCache.delete(CACHE_KEY.DICT_CACHE)
const res = await listSimpleDictData() const res = await getSimpleDictDataList()
// 设置数据 // 设置数据
const dictDataMap = new Map<string, any>() const dictDataMap = new Map<string, any>()
res.forEach((dictData: DictDataVO) => { res.forEach((dictData: DictDataVO) => {
......
/** /**
* 数据字典工具类 * 数据字典工具类
*/ */
import {useDictStoreWithOut} from '@/store/modules/dict' import { useDictStoreWithOut } from '@/store/modules/dict'
import {ElementPlusInfoType} from '@/types/elementPlus' import { ElementPlusInfoType } from '@/types/elementPlus'
const dictStore = useDictStoreWithOut() const dictStore = useDictStoreWithOut()
...@@ -20,13 +20,20 @@ export interface DictDataType { ...@@ -20,13 +20,20 @@ export interface DictDataType {
cssClass: string cssClass: string
} }
export interface NumberDictDataType extends DictDataType {
value: number
}
export const getDictOptions = (dictType: string) => { export const getDictOptions = (dictType: string) => {
return dictStore.getDictByType(dictType) || [] return dictStore.getDictByType(dictType) || []
} }
export const getIntDictOptions = (dictType: string): DictDataType[] => { export const getIntDictOptions = (dictType: string): NumberDictDataType[] => {
const dictOption: DictDataType[] = [] // 获得通用的 DictDataType 列表
const dictOptions: DictDataType[] = getDictOptions(dictType) const dictOptions: DictDataType[] = getDictOptions(dictType)
// 转换成 number 类型的 NumberDictDataType 类型
// why 需要特殊转换:避免 IDEA 在 v-for="dict in getIntDictOptions(...)" 时,el-option 的 key 会告警
const dictOption: NumberDictDataType[] = []
dictOptions.forEach((dict: DictDataType) => { dictOptions.forEach((dict: DictDataType) => {
dictOption.push({ dictOption.push({
...dict, ...dict,
...@@ -190,14 +197,12 @@ export enum DICT_TYPE { ...@@ -190,14 +197,12 @@ export enum DICT_TYPE {
PROMOTION_BANNER_POSITION = 'promotion_banner_position', // banner 定位 PROMOTION_BANNER_POSITION = 'promotion_banner_position', // banner 定位
// ========== CRM - 客户管理模块 ========== // ========== CRM - 客户管理模块 ==========
CRM_RECEIVABLE_CHECK_STATUS = 'crm_receivable_check_status', CRM_AUDIT_STATUS = 'crm_audit_status', // CRM 审批状态
CRM_RETURN_TYPE = 'crm_return_type', CRM_BIZ_TYPE = 'crm_biz_type', // CRM 业务类型
CRM_RECEIVABLE_RETURN_TYPE = 'crm_receivable_return_type', // CRM 回款的还款方式
CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry', CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry',
CRM_CUSTOMER_LEVEL = 'crm_customer_level', CRM_CUSTOMER_LEVEL = 'crm_customer_level',
CRM_CUSTOMER_SOURCE = 'crm_customer_source', CRM_CUSTOMER_SOURCE = 'crm_customer_source',
CRM_PRODUCT_STATUS = 'crm_product_status', CRM_PRODUCT_STATUS = 'crm_product_status',
CRM_PERMISSION_LEVEL = 'crm_permission_level' // CRM 数据权限的级别
// ========== CRM - 数据权限模块 ==========
CRM_BIZ_TYPE = 'crm_biz_type', // 数据模块类型
CRM_PERMISSION_LEVEL = 'crm_permission_level' // 用户数据权限类型
} }
...@@ -62,7 +62,7 @@ export const defaultShortcuts = [ ...@@ -62,7 +62,7 @@ export const defaultShortcuts = [
* @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ" * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
* @returns 返回拼接后的时间字符串 * @returns 返回拼接后的时间字符串
*/ */
export function formatDate(date: dayjs.ConfigType, format?: string): string { export function formatDate(date: Date, format?: string): string {
// 日期不存在,则返回空 // 日期不存在,则返回空
if (!date) { if (!date) {
return '' return ''
...@@ -200,9 +200,9 @@ export function formatPast2(ms) { ...@@ -200,9 +200,9 @@ export function formatPast2(ms) {
* @param cellValue 字段值 * @param cellValue 字段值
*/ */
// @ts-ignore // @ts-ignore
export const dateFormatter = (row, column, cellValue) => { export const dateFormatter = (row, column, cellValue): string => {
if (!cellValue) { if (!cellValue) {
return return ''
} }
return formatDate(cellValue) return formatDate(cellValue)
} }
......
import { toNumber } from 'lodash-es'
/** /**
* *
* @param component 需要注册的组件 * @param component 需要注册的组件
...@@ -263,3 +265,23 @@ export const calculateRelativeRate = (value?: number, reference?: number) => { ...@@ -263,3 +265,23 @@ export const calculateRelativeRate = (value?: number, reference?: number) => {
return ((100 * ((value || 0) - reference)) / reference).toFixed(0) return ((100 * ((value || 0) - reference)) / reference).toFixed(0)
} }
/**
* 获取链接的参数值
* @param key 参数键名
* @param urlStr 链接地址,默认为当前浏览器的地址
*/
export const getUrlValue = (key: string, urlStr: string = location.href): string => {
if (!urlStr || !key) return ''
const url = new URL(decodeURIComponent(urlStr))
return url.searchParams.get(key) ?? ''
}
/**
* 获取链接的参数值(值类型)
* @param key 参数键名
* @param urlStr 链接地址,默认为当前浏览器的地址
*/
export const getUrlNumberValue = (key: string, urlStr: string = location.href): number => {
return toNumber(getUrlValue(key, urlStr))
}
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
<li class="list-group-item"> <li class="list-group-item">
<Icon class="mr-5px" icon="ep:calendar" /> <Icon class="mr-5px" icon="ep:calendar" />
{{ t('profile.user.createTime') }} {{ t('profile.user.createTime') }}
<div class="pull-right">{{ formatDate(userInfo?.createTime) }}</div> <div class="pull-right">{{ formatDate(userInfo.createTime) }}</div>
</li> </li>
</ul> </ul>
</div> </div>
...@@ -55,7 +55,7 @@ import { getUserProfile, ProfileVO } from '@/api/system/user/profile' ...@@ -55,7 +55,7 @@ import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
defineOptions({ name: 'ProfileUser' }) defineOptions({ name: 'ProfileUser' })
const { t } = useI18n() const { t } = useI18n()
const userInfo = ref<ProfileVO>() const userInfo = ref({} as ProfileVO)
const getUserInfo = async () => { const getUserInfo = async () => {
const users = await getUserProfile() const users = await getUserProfile()
userInfo.value = users userInfo.value = users
......
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="商机名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入商机名称" />
</el-form-item>
<!-- TODO 芋艿:客户列表的组件 -->
<el-form-item label="客户名称" prop="customerName">
<el-popover
placement="bottom"
:width="600"
trigger="click"
:teleported="false"
:visible="showCustomer"
:offset="10"
>
<template #reference>
<el-input
placeholder="请选择客户"
@click="openCustomerSelect"
v-model="formData.customerName"
/>
</template>
<el-table :data="customerList" ref="multipleTableRef" @select="handleSelectionChange">
<el-table-column width="55" label="选择" type="selection" />
<el-table-column width="100" label="编号" property="id" />
<el-table-column width="150" label="客户名称" property="name" />
<el-table-column width="100" label="客户来源" prop="source" align="center">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
</template>
</el-table-column>
<el-table-column label="客户等级" align="center" prop="level" width="120">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-row :gutter="20">
<el-col>
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getCustomerList"
layout="sizes, prev, pager, next"
/>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="10" :offset="13">
<el-button @click="selectCustomer">确认</el-button>
<el-button @click="showCustomer = false">取消</el-button>
</el-col>
</el-row>
</el-popover>
</el-form-item>
<el-form-item label="商机状态类型" prop="statusTypeId">
<el-select
v-model="formData.statusTypeId"
placeholder="请选择商机状态类型"
clearable
size="small"
@change="changeBusinessStatusType"
>
<el-option
v-for="item in businessStatusTypeList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="商机状态" prop="statusId">
<el-select v-model="formData.statusId" placeholder="请选择商机状态" clearable size="small">
<el-option
v-for="item in businessStatusList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="预计成交日期" prop="dealTime">
<el-date-picker
v-model="formData.dealTime"
type="date"
value-format="x"
placeholder="选择预计成交日期"
/>
</el-form-item>
<el-form-item label="商机金额" prop="price">
<el-input v-model="formData.price" placeholder="请输入商机金额" />
</el-form-item>
<el-form-item label="整单折扣" prop="discountPercent">
<el-input v-model="formData.discountPercent" placeholder="请输入整单折扣" />
</el-form-item>
<el-form-item label="产品总金额" prop="productPrice">
<el-input v-model="formData.productPrice" placeholder="请输入产品总金额" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as BusinessApi from '@/api/crm/business'
import * as BusinessStatusTypeApi from '@/api/crm/businessStatusType'
import * as CustomerApi from '@/api/crm/customer'
import { DICT_TYPE } from '@/utils/dict'
import { ElTable } from 'element-plus'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
id: undefined,
name: undefined,
statusTypeId: undefined,
statusId: undefined,
contactNextTime: undefined,
customerId: undefined,
dealTime: undefined,
price: undefined,
discountPercent: undefined,
productPrice: undefined,
remark: undefined,
ownerUserId: undefined,
roUserIds: undefined,
rwUserIds: undefined,
endStatus: undefined,
endRemark: undefined,
contactLastTime: undefined,
followUpStatus: undefined
})
const formRules = reactive({
name: [{ required: true, message: '商机名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const businessStatusList = ref([]) // 商机状态列表
const businessStatusTypeList = ref([]) //商机状态类型列表
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const customerList = ref([]) // 客户列表的数据
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await BusinessApi.getBusiness(id)
} finally {
formLoading.value = false
}
}
// 加载商机状态类型列表
businessStatusTypeList.value = await BusinessStatusTypeApi.getBusinessStatusTypeList()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as BusinessApi.BusinessVO
if (formType.value === 'create') {
await BusinessApi.createBusiness(data)
message.success(t('common.createSuccess'))
} else {
await BusinessApi.updateBusiness(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
statusTypeId: undefined,
statusId: undefined,
contactNextTime: undefined,
customerId: undefined,
dealTime: undefined,
price: undefined,
discountPercent: undefined,
productPrice: undefined,
remark: undefined,
ownerUserId: undefined,
roUserIds: undefined,
rwUserIds: undefined,
endStatus: undefined,
endRemark: undefined,
contactLastTime: undefined,
followUpStatus: undefined
}
formRef.value?.resetFields()
}
/** 加载商机状态列表 */
const changeBusinessStatusType = async (typeId: number) => {
businessStatusList.value = await BusinessStatusTypeApi.getBusinessStatusListByTypeId(typeId)
}
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
mobile: null,
industryId: null,
level: null,
source: null
})
// 选择客户
const showCustomer = ref(false)
const openCustomerSelect = () => {
showCustomer.value = !showCustomer.value
queryParams.pageNo = 1
getCustomerList()
}
/** 查询客户列表 */
const getCustomerList = async () => {
loading.value = true
try {
const data = await CustomerApi.getCustomerPage(queryParams)
console.log(JSON.stringify(data))
customerList.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
const multipleTableRef = ref<InstanceType<typeof ElTable>>()
const multipleSelection = ref()
const handleSelectionChange = ({}, row) => {
multipleSelection.value = row
multipleTableRef.value!.clearSelection()
multipleTableRef.value!.toggleRowSelection(row, undefined)
}
const selectCustomer = () => {
formData.value.customerId = multipleSelection.value.id
formData.value.customerName = multipleSelection.value.name
showCustomer.value = !showCustomer.value
}
</script>
<template>
<!-- 操作栏 -->
<el-row justify="end">
<el-button @click="openForm">
<Icon class="mr-5px" icon="ep:opportunity" />
创建商机
</el-button>
</el-row>
<!-- 列表 -->
<ContentWrap class="mt-10px">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="商机名称" fixed="left" align="center" prop="name">
<template #default="scope">
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
{{ scope.row.name }}
</el-link>
</template>
</el-table-column>
<el-table-column label="商机金额" align="center" prop="price" :formatter="fenToYuanFormat" />
<el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column label="商机组" align="center" prop="statusTypeName" />
<el-table-column label="商机阶段" align="center" prop="statusName" />
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加 -->
<BusinessForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import * as BusinessApi from '@/api/crm/business'
import BusinessForm from './../BusinessForm.vue'
import { BizTypeEnum } from '@/api/crm/permission'
import { fenToYuanFormat } from '@/utils/formatter'
defineOptions({ name: 'CrmBusinessList' })
const props = defineProps<{
bizType: number // 业务类型
bizId: number // 业务编号
}>()
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
customerId: undefined as unknown // 允许 undefined + number
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
// 置空参数
queryParams.customerId = undefined
// 执行查询
let data = { list: [], total: 0 }
switch (props.bizType) {
case BizTypeEnum.CRM_CUSTOMER:
queryParams.customerId = props.bizId
data = await BusinessApi.getBusinessPageByCustomer(queryParams)
break
default:
return
}
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 添加操作 */
const formRef = ref()
const openForm = () => {
formRef.value.open('create')
}
/** 打开联系人详情 */
const { push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'CrmBusinessDetail', params: { id } })
}
/** 监听打开的 bizId + bizType,从而加载最新的列表 */
watch(
() => [props.bizId, props.bizType],
() => {
handleQuery()
},
{ immediate: true, deep: true }
)
</script>
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="商机名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入商机名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button type="primary" @click="openForm('create')" v-hasPermi="['crm:business:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['crm:business:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="商机名称" align="center" prop="name" />
<el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column label="商机金额" align="center" prop="price" />
<el-table-column
label="预计成交日期"
align="center"
prop="dealTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="商机状态类型" align="center" prop="statusTypeName" />
<el-table-column label="商机状态" align="center" prop="statusName" />
<el-table-column
label="更新时间"
align="center"
prop="updateTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="负责人" align="center" prop="ownerUserId" />
<el-table-column label="创建人" align="center" prop="creator" />
<el-table-column label="跟进状态" align="center" prop="followUpStatus" />
<el-table-column label="操作" align="center" fixed="right" width="130px">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['crm:business:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['crm:business:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<BusinessForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as BusinessApi from '@/api/crm/business'
import BusinessForm from './BusinessForm.vue'
defineOptions({ name: 'CrmBusiness' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
statusTypeId: null,
statusId: null,
contactNextTime: [],
customerId: null,
dealTime: [],
price: null,
discountPercent: null,
productPrice: null,
remark: null,
ownerUserId: null,
createTime: [],
roUserIds: null,
rwUserIds: null,
endStatus: null,
endRemark: null,
contactLastTime: [],
followUpStatus: null
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await BusinessApi.getBusinessPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await BusinessApi.deleteBusiness(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await BusinessApi.exportBusiness(queryParams)
download.excel(data, '商机.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="状态类型名" prop="name">
<el-input v-model="formData.name" placeholder="请输入状态类型名" />
</el-form-item>
<el-form-item label="应用部门" prop="deptIds">
<el-tree
ref="treeRef"
:data="deptList"
:props="defaultProps"
:check-strictly="!checkStrictly"
node-key="id"
placeholder="请选择归属部门"
show-checkbox
/>
</el-form-item>
<el-form-item label="状态设置" prop="statusList">
<el-table border style="width: 100%" :data="formData.statusList">
<el-table-column align="center" label="状态" width="120" prop="star">
<template #default="scope">
<el-text>状态{{ scope.$index + 1 }}</el-text>
</template>
</el-table-column>
<el-table-column align="center" label="状态名称" width="120" prop="name">
<template #default="{ row }">
<el-input v-model="row.name" placeholder="请输入状态名称" />
</template>
</el-table-column>
<el-table-column width="120" align="center" label="赢单率" prop="percent">
<template #default="{ row }">
<el-input v-model="row.percent" placeholder="请输入赢单率" />
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button link type="primary" @click="addStatusArea(scope.$index)"> 添加 </el-button>
<el-button
link
type="danger"
@click="deleteStatusArea(scope.$index)"
v-show="scope.$index > 0"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as BusinessStatusTypeApi from '@/api/crm/businessStatusType'
import { defaultProps, handleTree } from '@/utils/tree'
import * as DeptApi from '@/api/system/dept'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
id: 0,
name: '',
deptIds: [],
statusList: []
})
const formRules = reactive({
name: [{ required: true, message: '状态类型名不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const deptList = ref<Tree[]>([]) // 树形结构
const treeRef = ref() // 菜单树组件 Ref
const checkStrictly = ref(true) // 是否严格模式,即父子不关联
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await BusinessStatusTypeApi.getBusinessStatusType(id)
treeRef.value.setCheckedKeys(formData.value.deptIds)
if (formData.value.statusList.length == 0) {
addStatusArea(0)
}
} finally {
formLoading.value = false
}
} else {
addStatusArea(0)
}
// 加载部门树
deptList.value = handleTree(await DeptApi.getSimpleDeptList())
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as BusinessStatusTypeApi.BusinessStatusTypeVO
data.deptIds = treeRef.value.getCheckedKeys(false)
if (formType.value === 'create') {
await BusinessStatusTypeApi.createBusinessStatusType(data)
message.success(t('common.createSuccess'))
} else {
await BusinessStatusTypeApi.updateBusinessStatusType(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
checkStrictly.value = true
formData.value = {
id: 0,
name: '',
deptIds: [],
statusList: []
}
treeRef.value?.setCheckedNodes([])
formRef.value?.resetFields()
}
/** 添加状态 */
const addStatusArea = () => {
const data = formData.value
data.statusList.push({
name: '',
percent: ''
})
}
/** 删除状态 */
const deleteStatusArea = (index: number) => {
const data = formData.value
data.statusList.splice(index, 1)
}
</script>
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['crm:business-status-type:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['crm:business-status-type:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="状态类型名" align="center" prop="name" />
<el-table-column label="使用的部门" align="center" prop="deptNames" />
<el-table-column label="创建人" align="center" prop="creator" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['crm:business-status-type:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['crm:business-status-type:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<BusinessStatusTypeForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as BusinessStatusTypeApi from '@/api/crm/businessStatusType'
import BusinessStatusTypeForm from './BusinessStatusTypeForm.vue'
defineOptions({ name: 'BusinessStatusType' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await BusinessStatusTypeApi.getBusinessStatusTypePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 选择客户操作 */
const formCustomerRef = ref()
const openCustomerForm = (id?: number) => {
formCustomerRef.value.open(id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await BusinessStatusTypeApi.deleteBusinessStatusType(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await BusinessStatusTypeApi.exportBusinessStatusType(queryParams)
download.excel(data, '商机状态类型.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
...@@ -10,9 +10,16 @@ ...@@ -10,9 +10,16 @@
<el-form-item label="线索名称" prop="name"> <el-form-item label="线索名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入线索名称" /> <el-input v-model="formData.name" placeholder="请输入线索名称" />
</el-form-item> </el-form-item>
<!-- TODO wanwan 客户选择 --> <!-- TODO 芋艿:后续客户的选择 -->
<el-form-item label="客户" prop="customerId"> <el-form-item label="客户" prop="customerId">
<el-input v-model="formData.customerId" placeholder="请选择客户" /> <el-select v-model="formData.customerId" clearable placeholder="请选择客户">
<el-option
v-for="item in customerList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="下次联系时间" prop="contactNextTime"> <el-form-item label="下次联系时间" prop="contactNextTime">
<el-date-picker <el-date-picker
...@@ -47,6 +54,7 @@ ...@@ -47,6 +54,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import * as ClueApi from '@/api/crm/clue' import * as ClueApi from '@/api/crm/clue'
import * as CustomerApi from '@/api/crm/customer'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
...@@ -55,6 +63,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示 ...@@ -55,6 +63,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题 const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改 const formType = ref('') // 表单的类型:create - 新增;update - 修改
const customerList = ref([]) // 客户列表
const formData = ref({ const formData = ref({
id: undefined, id: undefined,
name: undefined, name: undefined,
...@@ -79,6 +88,12 @@ const open = async (type: string, id?: number) => { ...@@ -79,6 +88,12 @@ const open = async (type: string, id?: number) => {
dialogTitle.value = t('action.' + type) dialogTitle.value = t('action.' + type)
formType.value = type formType.value = type
resetForm() resetForm()
const customerData = await CustomerApi.getCustomerPage({
pageNo: 1,
pageSize: 100,
pool: false
})
customerList.value = customerData.list
// 修改时,设置数据 // 修改时,设置数据
if (id) { if (id) {
formLoading.value = true formLoading.value = true
......
import CrmPermissionList from './CrmPermissionList.vue'
enum CrmBizTypeEnum {
CRM_LEADS = 1, // 线索
CRM_CUSTOMER = 2, // 客户
CRM_CONTACTS = 3, // 联系人
CRM_BUSINESS = 5, // 商机
CRM_CONTRACT = 6 // 合同
}
enum CrmPermissionLevelEnum {
OWNER = 1 // 负责人
}
export { CrmPermissionList, CrmBizTypeEnum, CrmPermissionLevelEnum }
...@@ -12,9 +12,9 @@ ...@@ -12,9 +12,9 @@
v-model="formData.userIds" v-model="formData.userIds"
:data="userTree" :data="userTree"
:props="defaultProps" :props="defaultProps"
check-on-click-node
multiple multiple
filterable filterable
check-on-click-node
node-key="id" node-key="id"
placeholder="请选择规则适用人群" placeholder="请选择规则适用人群"
/> />
...@@ -25,8 +25,8 @@ ...@@ -25,8 +25,8 @@
:data="deptTree" :data="deptTree"
:props="defaultProps" :props="defaultProps"
multiple multiple
check-strictly
filterable filterable
check-strictly
node-key="id" node-key="id"
placeholder="请选择规则适用部门" placeholder="请选择规则适用部门"
/> />
...@@ -57,11 +57,11 @@ ...@@ -57,11 +57,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import * as CustomerLimitConfigApi from '@/api/crm/customerLimitConfig' import * as CustomerLimitConfigApi from '@/api/crm/customerLimitConfig'
import { LimitConfType } from '@/views/crm/customerLimitConfig/customerLimitConf'
import * as DeptApi from '@/api/system/dept' import * as DeptApi from '@/api/system/dept'
import { defaultProps, handleTree } from '@/utils/tree' import { defaultProps, handleTree } from '@/utils/tree'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { LimitConfType } from '@/api/crm/customerLimitConfig'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
......
...@@ -19,8 +19,16 @@ ...@@ -19,8 +19,16 @@
> >
<el-table-column label="编号" align="center" prop="id" /> <el-table-column label="编号" align="center" prop="id" />
<el-table-column label="规则类型" align="center" prop="type" /> <el-table-column label="规则类型" align="center" prop="type" />
<el-table-column label="规则适用人群" align="center" prop="userNames" /> <el-table-column
<el-table-column label="规则适用部门" align="center" prop="deptNames" /> label="规则适用人群"
align="center"
:formatter="(row) => row.users?.map((user: any) => user.nickname).join(',')"
/>
<el-table-column
label="规则适用部门"
align="center"
:formatter="(row) => row.depts?.map((dept: any) => dept.name).join(',')"
/>
<el-table-column <el-table-column
:label=" :label="
confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT ? '拥有客户数上限' : '锁定客户数上限' confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT ? '拥有客户数上限' : '锁定客户数上限'
...@@ -80,11 +88,11 @@ ...@@ -80,11 +88,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import * as CustomerLimitConfigApi from '@/api/crm/customerLimitConfig' import * as CustomerLimitConfigApi from '@/api/crm/customerLimitConfig'
import CustomerLimitConfigForm from '@/views/crm/customerLimitConfig/CustomerLimitConfigForm.vue' import CustomerLimitConfigForm from '@/views/crm/config/customerLimitConfig/CustomerLimitConfigForm.vue'
import { LimitConfType } from '@/views/crm/customerLimitConfig/customerLimitConf'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { LimitConfType } from '@/api/crm/customerLimitConfig'
defineOptions({ name: 'CustomerLimitConfDetails' }) defineOptions({ name: 'CustomerLimitConfigList' })
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
......
...@@ -3,18 +3,17 @@ ...@@ -3,18 +3,17 @@
<ContentWrap> <ContentWrap>
<el-tabs> <el-tabs>
<el-tab-pane label="拥有客户数限制"> <el-tab-pane label="拥有客户数限制">
<!-- TODO @wanwan:CustomerLimitConfigList,因为它是列表哈 --> <CustomerLimitConfigList :confType="LimitConfType.CUSTOMER_QUANTITY_LIMIT" />
<CustomerLimitConfDetails :confType="LimitConfType.CUSTOMER_QUANTITY_LIMIT" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="锁定客户数限制"> <el-tab-pane label="锁定客户数限制">
<CustomerLimitConfDetails :confType="LimitConfType.CUSTOMER_LOCK_LIMIT" /> <CustomerLimitConfigList :confType="LimitConfType.CUSTOMER_LOCK_LIMIT" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</ContentWrap> </ContentWrap>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import CustomerLimitConfDetails from '@/views/crm/customerLimitConfig/CustomerLimitConfDetails.vue' import CustomerLimitConfigList from '@/views/crm/config/customerLimitConfig/CustomerLimitConfigList.vue'
import { LimitConfType } from '@/views/crm/customerLimitConfig/customerLimitConf' import { LimitConfType } from '@/api/crm/customerLimitConfig'
defineOptions({ name: 'CrmCustomerLimitConfig' }) defineOptions({ name: 'CrmCustomerLimitConfig' })
</script> </script>
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
</template> </template>
<!-- 表单 --> <!-- 表单 -->
<el-form-item label="客户公海规则设置" prop="enabled"> <el-form-item label="客户公海规则设置" prop="enabled">
<el-radio-group v-model="formData.enabled" class="ml-4"> <el-radio-group v-model="formData.enabled" @change="changeEnable" class="ml-4">
<el-radio :label="false" size="large">不启用</el-radio> <el-radio :label="false" size="large">不启用</el-radio>
<el-radio :label="true" size="large">启用</el-radio> <el-radio :label="true" size="large">启用</el-radio>
</el-radio-group> </el-radio-group>
...@@ -36,7 +36,11 @@ ...@@ -36,7 +36,11 @@
天未成交 天未成交
</el-form-item> </el-form-item>
<el-form-item label="提前提醒设置" prop="notifyEnabled"> <el-form-item label="提前提醒设置" prop="notifyEnabled">
<el-radio-group v-model="formData.notifyEnabled" class="ml-4"> <el-radio-group
v-model="formData.notifyEnabled"
@change="changeNotifyEnable"
class="ml-4"
>
<el-radio :label="false" size="large">不提醒</el-radio> <el-radio :label="false" size="large">不提醒</el-radio>
<el-radio :label="true" size="large">提醒</el-radio> <el-radio :label="true" size="large">提醒</el-radio>
</el-radio-group> </el-radio-group>
...@@ -52,11 +56,10 @@ ...@@ -52,11 +56,10 @@
</ContentWrap> </ContentWrap>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import * as CustomerPoolConfApi from '@/api/crm/customerPoolConf' import * as CustomerPoolConfigApi from '@/api/crm/customerPoolConfig'
import { CardTitle } from '@/components/Card' import { CardTitle } from '@/components/Card'
// TODO @wanwan:CustomerPoolConf =》 CustomerPoolConfig;另外,我们在 crm 目录下,新建一个 config 目录,然后把 customerPoolConfig 和 customerLimitConfig 都挪进 defineOptions({ name: 'CrmCustomerPoolConfig' })
defineOptions({ name: 'CustomerPoolConf' })
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
...@@ -78,7 +81,7 @@ const formRef = ref() // 表单 Ref ...@@ -78,7 +81,7 @@ const formRef = ref() // 表单 Ref
const getConfig = async () => { const getConfig = async () => {
try { try {
formLoading.value = true formLoading.value = true
const data = await CustomerPoolConfApi.getCustomerPoolConfig() const data = await CustomerPoolConfigApi.getCustomerPoolConfig()
if (data === null) { if (data === null) {
return return
} }
...@@ -97,8 +100,8 @@ const onSubmit = async () => { ...@@ -97,8 +100,8 @@ const onSubmit = async () => {
// 提交请求 // 提交请求
formLoading.value = true formLoading.value = true
try { try {
const data = formData.value as unknown as CustomerPoolConfApi.CustomerPoolConfigVO const data = formData.value as unknown as CustomerPoolConfigApi.CustomerPoolConfigVO
await CustomerPoolConfApi.updateCustomerPoolConfig(data) await CustomerPoolConfigApi.saveCustomerPoolConfig(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
await getConfig() await getConfig()
formLoading.value = false formLoading.value = false
...@@ -107,27 +110,22 @@ const onSubmit = async () => { ...@@ -107,27 +110,22 @@ const onSubmit = async () => {
} }
} }
// TODO @wanwan:el-radio-group 选择后,触发会不会更好哈; /** 更改客户公海规则设置 */
watch( const changeEnable = () => {
() => formData.value.enabled, if (!formData.value.enabled) {
(val: boolean) => {
if (!val) {
formData.value.contactExpireDays = undefined formData.value.contactExpireDays = undefined
formData.value.dealExpireDays = undefined formData.value.dealExpireDays = undefined
formData.value.notifyEnabled = false formData.value.notifyEnabled = false
formData.value.notifyDays = undefined formData.value.notifyDays = undefined
} }
} }
)
// TODO @wanwan:el-radio-group 选择后,触发会不会更好哈; /** 更改提前提醒设置 */
watch( const changeNotifyEnable = () => {
() => formData.value.notifyEnabled, if (!formData.value.notifyEnabled) {
(val: boolean) => {
if (!val) {
formData.value.notifyDays = undefined formData.value.notifyDays = undefined
} }
} }
)
onMounted(() => { onMounted(() => {
getConfig() getConfig()
......
<template> <template>
<Dialog :title="dialogTitle" v-model="dialogVisible" :width="800"> <Dialog :title="dialogTitle" v-model="dialogVisible" :width="820">
<el-form <el-form
ref="formRef" ref="formRef"
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="130px" label-width="110px"
v-loading="formLoading" v-loading="formLoading"
:inline="true"
> >
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="姓名" prop="name"> <el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" placeholder="请输入姓名" /> <el-input input-style="width:190px;" v-model="formData.name" placeholder="请输入姓名" />
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="负责人" prop="ownerUserId"> <el-form-item label="负责人" prop="ownerUserId">
<el-select <el-select
v-model="ownerUserList" v-model="formData.ownerUserId"
placeholder="请选择负责人" placeholder="请选择负责人"
multiple
value-key="id" value-key="id"
lable-key="nickname" lable-key="nickname"
@click="openOwerForm('open')"
> >
<el-option <el-option
v-for="item in ownerUserList" v-for="item in userList"
:key="item.id" :key="item.id"
:label="item.nickname" :label="item.nickname"
:value="item" :value="item.id"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- TODO 芋艿:封装成一个组件 --> </el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="客户名称" prop="customerName"> <el-form-item label="客户名称" prop="customerName">
<el-popover <el-select
placement="bottom" v-model="formData.customerId"
:width="600" placeholder="请选择客户"
trigger="click" value-key="id"
:teleported="false" lable-key="name"
:visible="showCustomer"
:offset="10"
> >
<template #reference> <el-option
<el-input v-for="item in customerList"
placeholder="请选择" :key="item.id"
@click="openCustomerSelect" :label="item.name"
v-model="formData.customerName" :value="item.id"
/>
</template>
<el-table :data="list" ref="multipleTableRef" @select="handleSelectionChange">
<el-table-column label="选择" type="selection" width="55" />
<el-table-column width="100" property="id" label="编号" />
<el-table-column width="150" property="name" label="客户名称" />
<el-table-column label="客户来源" align="center" prop="source" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
</template>
</el-table-column>
<el-table-column label="客户等级" align="center" prop="level" width="120">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-row :gutter="20">
<el-col>
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
layout="sizes, prev, pager, next"
/> />
</el-col> </el-select>
</el-row>
<el-row :gutter="20">
<el-col :span="10" :offset="13">
<el-button @click="selectCustomer">确认</el-button>
<el-button @click="showCustomer = false">取消</el-button>
</el-col>
</el-row>
</el-popover>
</el-form-item> </el-form-item>
<el-form-item label="性别" prop="sex"> </el-col>
<el-col :span="12"
><el-form-item label="性别" prop="sex">
<el-select v-model="formData.sex" placeholder="请选择"> <el-select v-model="formData.sex" placeholder="请选择">
<el-option <el-option
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)" v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
...@@ -88,23 +58,52 @@ ...@@ -88,23 +58,52 @@
:label="dict.label" :label="dict.label"
:value="dict.value" :value="dict.value"
/> />
</el-select> </el-select> </el-form-item
</el-form-item> ></el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="手机号" prop="mobile"> <el-form-item label="手机号" prop="mobile">
<el-input v-model="formData.mobile" placeholder="请输入手机号" /> <el-input
input-style="width:190px;"
v-model="formData.mobile"
placeholder="请输入手机号"
/>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="座机" prop="telephone"> <el-form-item label="座机" prop="telephone">
<el-input v-model="formData.telephone" placeholder="请输入座机" style="width: 215px" /> <el-input v-model="formData.telephone" placeholder="请输入座机" style="width: 215px" />
</el-form-item> </el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="邮箱" prop="email"> <el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入邮箱" /> <el-input
input-style="width:190px;"
v-model="formData.email"
placeholder="请输入邮箱"
/>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="QQ" prop="qq"> <el-form-item label="QQ" prop="qq">
<el-input v-model="formData.qq" placeholder="请输入QQ" style="width: 215px" /> <el-input v-model="formData.qq" placeholder="请输入QQ" style="width: 215px" />
</el-form-item> </el-form-item>
<el-form-item label="微信" prop="webchat"> </el-col>
<el-input v-model="formData.webchat" placeholder="请输入微信" /> </el-row>
<el-row>
<el-col :span="12">
<el-form-item label="微信" prop="wechat">
<el-input
input-style="width:190px;"
v-model="formData.wechat"
placeholder="请输入微信"
/>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下次联系时间" prop="nextTime"> <el-form-item label="下次联系时间" prop="nextTime">
<el-date-picker <el-date-picker
v-model="formData.nextTime" v-model="formData.nextTime"
...@@ -113,9 +112,30 @@ ...@@ -113,9 +112,30 @@
placeholder="选择下次联系时间" placeholder="选择下次联系时间"
/> />
</el-form-item> </el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="所在地" prop="areaId">
<el-tree-select
v-model="formData.areaId"
:data="areaList"
:props="defaultProps"
:render-after-expand="true"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="地址" prop="address"> <el-form-item label="地址" prop="address">
<el-input v-model="formData.address" placeholder="请输入地址" /> <el-input
input-style="width:190px;"
v-model="formData.address"
placeholder="请输入地址"
/>
</el-form-item> </el-form-item>
</el-col> </el-row
><el-row>
<el-col :span="12">
<el-form-item label="直属上级" prop="parentId"> <el-form-item label="直属上级" prop="parentId">
<el-select v-model="formData.parentId" placeholder="请选择"> <el-select v-model="formData.parentId" placeholder="请选择">
<el-option <el-option
...@@ -127,13 +147,16 @@ ...@@ -127,13 +147,16 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职位" prop="post"> <el-form-item label="职位" prop="post">
<el-input v-model="formData.post" placeholder="请输入职位" /> <el-input input-style="width:190px;" v-model="formData.post" placeholder="请输入职位" />
</el-form-item> </el-form-item>
</el-col> </el-row
<el-form-item label="是否关键决策人" prop="policyMakers" style="width: 400px"> ><el-row>
<el-radio-group v-model="formData.policyMakers"> <el-col :span="12"
><el-form-item label="是否关键决策人" prop="master" style="width: 400px">
<el-radio-group v-model="formData.master">
<el-radio <el-radio
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :key="dict.value"
...@@ -143,28 +166,29 @@ ...@@ -143,28 +166,29 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="备注" prop="remark"> </el-col>
</el-row>
<el-row>
<el-col :span="24"
><el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" /> <el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item> </el-form-item>
</el-col>
</el-row>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button> <el-button @click="dialogVisible = false">取 消</el-button>
</template> </template>
</Dialog> </Dialog>
<OwerSelect
ref="owerRef"
@confirmOwerSelect="owerSelectValue"
:initOwerUser="formData.ownerUserId"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import * as ContactApi from '@/api/crm/contact' import * as ContactApi from '@/api/crm/contact'
import { DICT_TYPE, getIntDictOptions, getBoolDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions, getBoolDictOptions } from '@/utils/dict'
import OwerSelect from './OwerSelect.vue'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import * as CustomerApi from '@/api/crm/customer' import * as CustomerApi from '@/api/crm/customer'
import { ElTable } from 'element-plus' import * as AreaApi from '@/api/system/area'
import { defaultProps } from '@/utils/tree'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
...@@ -172,6 +196,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示 ...@@ -172,6 +196,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题 const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改 const formType = ref('') // 表单的类型:create - 新增;update - 修改
const areaList = ref([]) // 地区列表
const formData = ref({ const formData = ref({
nextTime: undefined, nextTime: undefined,
mobile: undefined, mobile: undefined,
...@@ -188,21 +213,10 @@ const formData = ref({ ...@@ -188,21 +213,10 @@ const formData = ref({
name: undefined, name: undefined,
post: undefined, post: undefined,
qq: undefined, qq: undefined,
webchat: undefined, wechat: undefined,
sex: undefined, sex: undefined,
policyMakers: undefined master: false,
}) areaId: undefined
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
mobile: null,
industryId: null,
level: null,
source: null
}) })
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }], name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
...@@ -212,56 +226,34 @@ const formRules = reactive({ ...@@ -212,56 +226,34 @@ const formRules = reactive({
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
const ownerUserList = ref<any[]>([]) const ownerUserList = ref<any[]>([])
const userList = ref<UserApi.UserVO[]>([]) // 用户列表 const userList = ref<UserApi.UserVO[]>([]) // 用户列表
// TODO 芋艿:统一的客户选择面板
const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
const allContactList = ref([]) // 所有联系人列表
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number) => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = t('action.' + type) dialogTitle.value = t('action.' + type)
formType.value = type formType.value = type
allContactList.value = await ContactApi.simpleAlllist()
resetForm() resetForm()
allContactList.value = await ContactApi.getSimpleContactList()
userList.value = await UserApi.getSimpleUserList()
customerList.value = await CustomerApi.queryAllList()
areaList.value = await AreaApi.getAreaTree()
// 修改时,设置数据 // 修改时,设置数据
if (id) { if (id) {
formLoading.value = true formLoading.value = true
try { try {
formData.value = await ContactApi.getContact(id) formData.value = await ContactApi.getContact(id)
userList.value = await UserApi.getSimpleUserList()
await gotOwnerUser(formData.value.ownerUserId)
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
} }
} }
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CustomerApi.getCustomerPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
const gotOwnerUser = (owerUserId: any) => {
if (owerUserId !== null) {
owerUserId.split(',').forEach((item: string) => {
userList.value.find((user: { id: any }) => {
if (user.id == item) {
ownerUserList.value.push(user)
}
})
})
}
}
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => { const submitForm = async () => {
owerSelectValue(ownerUserList) // owerSelectValue(ownerUserList)
// 校验表单 // 校验表单
if (!formRef) return if (!formRef) return
const valid = await formRef.value.validate() const valid = await formRef.value.validate()
...@@ -302,52 +294,11 @@ const resetForm = () => { ...@@ -302,52 +294,11 @@ const resetForm = () => {
name: undefined, name: undefined,
post: undefined, post: undefined,
qq: undefined, qq: undefined,
webchat: undefined, wechat: undefined,
sex: undefined, sex: undefined,
policyMakers: undefined master: false
} }
formRef.value?.resetFields() formRef.value?.resetFields()
ownerUserList.value = [] ownerUserList.value = []
} }
/** 添加/修改操作 */
// TODO @zyna:owner?拼写要注意哈;
const owerRef = ref()
const openOwerForm = (type: string) => {
owerRef.value.open(type, ownerUserList.value)
}
const owerSelectValue = (value) => {
ownerUserList.value = value.value
formData.value.ownerUserId = undefined
value.value.forEach((item, index) => {
if (index != 0) {
formData.value.ownerUserId = formData.value.ownerUserId + ',' + item.id
} else {
formData.value.ownerUserId = item.id
}
})
}
// 选择客户
const showCustomer = ref(false)
const openCustomerSelect = () => {
showCustomer.value = !showCustomer.value
queryParams.pageNo = 1
getList()
}
const multipleTableRef = ref<InstanceType<typeof ElTable>>()
const multipleSelection = ref()
const handleSelectionChange = ({}, row) => {
multipleSelection.value = row
multipleTableRef.value!.clearSelection()
multipleTableRef.value!.toggleRowSelection(row, undefined)
}
const selectCustomer = () => {
formData.value.customerId = multipleSelection.value.id
formData.value.customerName = multipleSelection.value.name
showCustomer.value = !showCustomer.value
}
const allContactList = ref([]) // 所有联系人列表
onMounted(async () => {
allContactList.value = await ContactApi.simpleAlllist()
})
</script> </script>
<template>
<!-- 操作栏 -->
<el-row justify="end">
<el-button @click="openForm">
<Icon class="mr-5px" icon="system-uicons:contacts" />
创建联系人
</el-button>
</el-row>
<!-- 列表 -->
<ContentWrap class="mt-10px">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="姓名" fixed="left" align="center" prop="name">
<template #default="scope">
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
{{ scope.row.name }}
</el-link>
</template>
</el-table-column>
<el-table-column label="手机号" align="center" prop="mobile" />
<el-table-column label="职位" align="center" prop="post" />
<el-table-column label="直属上级" align="center" prop="parentName" />
<el-table-column label="是否关键决策人" align="center" prop="master">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
</template>
</el-table-column>
<!-- TODO 芋艿:【操作:设为首要联系人】 -->
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加 -->
<ContactForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import * as ContactApi from '@/api/crm/contact'
import ContactForm from './../ContactForm.vue'
import { DICT_TYPE } from '@/utils/dict'
import { BizTypeEnum } from '@/api/crm/permission'
defineOptions({ name: 'CrmContactList' })
const props = defineProps<{
bizType: number // 业务类型
bizId: number // 业务编号
}>()
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
customerId: undefined as unknown // 允许 undefined + number
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
// 置空参数
queryParams.customerId = undefined
// 执行查询
let data = { list: [], total: 0 }
switch (props.bizType) {
case BizTypeEnum.CRM_CUSTOMER:
queryParams.customerId = props.bizId
data = await ContactApi.getContactPageByCustomer(queryParams)
break
default:
return
}
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 添加操作 */
const formRef = ref()
const openForm = () => {
formRef.value.open('create')
}
/** 打开联系人详情 */
const { push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'CrmContactDetail', params: { id } })
}
/** 监听打开的 bizId + bizType,从而加载最新的列表 */
watch(
() => [props.bizId, props.bizType],
() => {
handleQuery()
},
{ immediate: true, deep: true }
)
</script>
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<el-descriptions-item label="姓名"> <el-descriptions-item label="姓名">
{{ contact.name }} {{ contact.name }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="客户名称"> <el-descriptions-item label="客户">
{{ contact.customerName }} {{ contact.customerName }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="手机"> <el-descriptions-item label="手机">
...@@ -24,14 +24,17 @@ ...@@ -24,14 +24,17 @@
{{ contact.qq }} {{ contact.qq }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="微信"> <el-descriptions-item label="微信">
{{ contact.webchat }} {{ contact.wechat }}
</el-descriptions-item>
<el-descriptions-item label="详细地址">
{{ contact.address }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="下次联系时间"> <el-descriptions-item label="下次联系时间">
{{ contact.nextTime ? formatDate(contact.nextTime) : '空' }} {{ contact.nextTime ? formatDate(contact.nextTime) : '空' }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="所在地">
{{ contact.areaName }}
</el-descriptions-item>
<el-descriptions-item label="详细地址">
{{ contact.address }}
</el-descriptions-item>
<el-descriptions-item label="性别"> <el-descriptions-item label="性别">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="contact.sex" /> <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="contact.sex" />
</el-descriptions-item> </el-descriptions-item>
...@@ -46,7 +49,7 @@ ...@@ -46,7 +49,7 @@
</template> </template>
<el-descriptions :column="2"> <el-descriptions :column="2">
<el-descriptions-item label="负责人"> <el-descriptions-item label="负责人">
{{ gotOwnerUser(contact.ownerUserId) }} {{ contact.ownerUserName }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="创建人"> <el-descriptions-item label="创建人">
{{ contact.creatorName }} {{ contact.creatorName }}
...@@ -66,29 +69,9 @@ ...@@ -66,29 +69,9 @@
import * as ContactApi from '@/api/crm/contact' import * as ContactApi from '@/api/crm/contact'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
import * as UserApi from '@/api/system/user'
const { contact } = defineProps<{ contact: ContactApi.ContactVO }>() const { contact } = defineProps<{ contact: ContactApi.ContactVO }>()
// 展示的折叠面板 // 展示的折叠面板
const activeNames = ref(['basicInfo', 'systemInfo']) const activeNames = ref(['basicInfo', 'systemInfo'])
const gotOwnerUser = (owerUserId: string) => {
let ownerName = ''
if (owerUserId !== null && owerUserId != undefined) {
owerUserId.split(',').forEach((item: string, index: number) => {
if (index != 0) {
ownerName =
ownerName + ',' + userList.value.find((user: { id: any }) => user.id == item)?.nickname
} else {
ownerName = userList.value.find((user: { id: any }) => user.id == item)?.nickname || ''
}
})
}
return ownerName
}
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
/** 初始化 **/
onMounted(async () => {
userList.value = await UserApi.getSimpleUserList()
})
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
</div> </div>
<ContentWrap class="mt-10px"> <ContentWrap class="mt-10px">
<el-descriptions :column="5" direction="vertical"> <el-descriptions :column="5" direction="vertical">
<el-descriptions-item label="客户名称"> <el-descriptions-item label="客户">
{{ contact.customerName }} {{ contact.customerName }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="职务"> <el-descriptions-item label="职务">
...@@ -63,33 +63,18 @@ ...@@ -63,33 +63,18 @@
<!-- TODO wanwan:这个 tab 拉满哈,可以更好看; --> <!-- TODO wanwan:这个 tab 拉满哈,可以更好看; -->
<el-col :span="18"> <el-col :span="18">
<el-tabs> <el-tabs>
<el-tab-pane label="详细资料"> <el-tab-pane label="基本信息">
<!-- TODO wanwan:这个 ml-2 是不是可以优化下,不要整个左移,而是里面的内容有个几 px 的偏移,不顶在框里 --> <!-- TODO wanwan:这个 ml-2 是不是可以优化下,不要整个左移,而是里面的内容有个几 px 的偏移,不顶在框里 -->
<ContactDetails class="ml-2" :contact="contact" /> <ContactDetails class="ml-2" :contact="contact" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="活动" lazy> 活动</el-tab-pane> <el-tab-pane label="跟进记录" lazy> 跟进记录</el-tab-pane>
<el-tab-pane label="邮件" lazy> 邮件</el-tab-pane>
<el-tab-pane label="工商信息" lazy> 工商信息</el-tab-pane>
<!-- TODO wanwan 以下标签上的数量需要接口统计返回 -->
<el-tab-pane label="客户" lazy>
<template #label> 客户<el-badge :value="12" class="item" type="primary" /> </template>
客户
</el-tab-pane>
<el-tab-pane label="团队成员" lazy>
<template #label> 团队成员<el-badge :value="2" class="item" type="primary" /> </template>
团队成员
</el-tab-pane>
<el-tab-pane label="商机" lazy> 商机</el-tab-pane> <el-tab-pane label="商机" lazy> 商机</el-tab-pane>
<el-tab-pane label="合同" lazy> <el-tab-pane label="附件" lazy> 附件</el-tab-pane>
<template #label> 合同<el-badge :value="3" class="item" type="primary" /> </template> <!-- TODO wanwan 以下标签上的数量需要接口统计返回 -->
合同 <el-tab-pane label="操作记录" lazy>
</el-tab-pane> <template #label> 操作记录<el-badge :value="12" class="item" type="primary" /> </template>
<el-tab-pane label="回款" lazy> 操作记录
<template #label> 回款<el-badge :value="4" class="item" type="primary" /> </template>
回款
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="回访" lazy> 回访</el-tab-pane>
<el-tab-pane label="发票" lazy> 发票</el-tab-pane>
</el-tabs> </el-tabs>
</el-col> </el-col>
...@@ -105,10 +90,10 @@ import ContactBasicInfo from '@/views/crm/contact/detail/ContactBasicInfo.vue' ...@@ -105,10 +90,10 @@ import ContactBasicInfo from '@/views/crm/contact/detail/ContactBasicInfo.vue'
import ContactDetails from '@/views/crm/contact/detail/ContactDetails.vue' import ContactDetails from '@/views/crm/contact/detail/ContactDetails.vue'
import ContactForm from '@/views/crm/contact/ContactForm.vue' import ContactForm from '@/views/crm/contact/ContactForm.vue'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
import * as CustomerApi from '@/api/crm/customer'
// TODO 芋艿:后面在 review 么? // TODO 芋艿:后面在 review 么?
defineOptions({ name: 'ContactDetail' }) defineOptions({ name: 'CrmContactDetail' })
const { delView } = useTagsViewStore() // 视图操作 const { delView } = useTagsViewStore() // 视图操作
const route = useRoute() const route = useRoute()
const { currentRoute } = useRouter() // 路由 const { currentRoute } = useRouter() // 路由
......
<template> <template>
<ContentWrap> <ContentWrap>
<!-- 搜索工作栏 --> <!-- 搜索工作栏 -->
<!-- TODO zyna:筛选项,按照需求简化下 -->
<el-form <el-form
class="-mb-15px" class="-mb-15px"
:model="queryParams" :model="queryParams"
...@@ -9,14 +8,22 @@ ...@@ -9,14 +8,22 @@
:inline="true" :inline="true"
label-width="68px" label-width="68px"
> >
<el-form-item label="客户编号" prop="customerId"> <el-form-item label="客户" prop="customerId">
<el-input <el-select
v-model="queryParams.customerId" v-model="queryParams.customerId"
placeholder="请输入客户编号" placeholder="请选择客户"
clearable value-key="id"
lable-key="name"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
class="!w-240px" clearable
>
<el-option
v-for="item in customerList"
:key="item.id"
:label="item.name"
:value="item.id"
/> />
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="姓名" prop="name"> <el-form-item label="姓名" prop="name">
<el-input <el-input
...@@ -55,9 +62,9 @@ ...@@ -55,9 +62,9 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item label="微信" prop="webchat"> <el-form-item label="微信" prop="wechat">
<el-input <el-input
v-model="queryParams.webchat" v-model="queryParams.wechat"
placeholder="请输入微信" placeholder="请输入微信"
clearable clearable
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
...@@ -74,8 +81,8 @@ ...@@ -74,8 +81,8 @@
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="handleQuery"> <Icon icon="ep:search" class="mr-5px" /> 搜索 </el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> <el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" /> 重置 </el-button>
<el-button type="primary" @click="openForm('create')" v-hasPermi="['crm:contact:create']"> <el-button type="primary" @click="openForm('create')" v-hasPermi="['crm:contact:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
...@@ -97,32 +104,28 @@ ...@@ -97,32 +104,28 @@
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="姓名" fixed="left" align="center" prop="name"> <el-table-column label="姓名" fixed="left" align="center" prop="name">
<template #default="scope"> <template #default="scope">
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">{{ <el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
scope.row.name {{ scope.row.name }}
}}</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="客户名称" fixed="left" align="center" prop="customerName" /> <el-table-column label="客户" fixed="left" align="center" prop="customerName" />
<el-table-column label="性别" align="center" prop="sex"> <el-table-column label="性别" align="center" prop="sex">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" /> <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="职位" align="center" prop="post" /> <el-table-column label="职位" align="center" prop="post" />
<el-table-column label="是否关键决策人" align="center" prop="policyMakers"> <el-table-column label="是否关键决策人" align="center" prop="master">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.policyMakers" />
</template>
</el-table-column>
<el-table-column label="直属上级" align="center" prop="parentId">
<template #default="scope"> <template #default="scope">
{{ allContactList.find((contact) => contact.id === scope.row.parentId)?.name }} <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="直属上级" align="center" prop="parentName" />
<el-table-column label="手机号" align="center" prop="mobile" /> <el-table-column label="手机号" align="center" prop="mobile" />
<el-table-column label="座机" align="center" prop="telephone" /> <el-table-column label="座机" align="center" prop="telephone" />
<el-table-column label="QQ" align="center" prop="qq" /> <el-table-column label="QQ" align="center" prop="qq" />
<el-table-column label="微信" align="center" prop="webchat" /> <el-table-column label="微信" align="center" prop="wechat" />
<el-table-column label="邮箱" align="center" prop="email" /> <el-table-column label="邮箱" align="center" prop="email" />
<el-table-column label="地址" align="center" prop="address" /> <el-table-column label="地址" align="center" prop="address" />
<el-table-column <el-table-column
...@@ -142,7 +145,7 @@ ...@@ -142,7 +145,7 @@
/> />
<el-table-column label="负责人" align="center" prop="ownerUserId"> <el-table-column label="负责人" align="center" prop="ownerUserId">
<template #default="scope"> <template #default="scope">
{{ gotOwnerUser(scope.row.ownerUserId) }} {{ scope.row.ownerUserName }}
</template> </template>
</el-table-column> </el-table-column>
<!-- <el-table-column label="所属部门" align="center" prop="ownerUserId" /> --> <!-- <el-table-column label="所属部门" align="center" prop="ownerUserId" /> -->
...@@ -211,7 +214,6 @@ import download from '@/utils/download' ...@@ -211,7 +214,6 @@ import download from '@/utils/download'
import * as ContactApi from '@/api/crm/contact' import * as ContactApi from '@/api/crm/contact'
import ContactForm from './ContactForm.vue' import ContactForm from './ContactForm.vue'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import * as UserApi from '@/api/system/user'
import * as CustomerApi from '@/api/crm/customer' import * as CustomerApi from '@/api/crm/customer'
defineOptions({ name: 'CrmContact' }) defineOptions({ name: 'CrmContact' })
...@@ -222,6 +224,7 @@ const { t } = useI18n() // 国际化 ...@@ -222,6 +224,7 @@ const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数 const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据 const list = ref([]) // 列表的数据
const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
...@@ -239,13 +242,12 @@ const queryParams = reactive({ ...@@ -239,13 +242,12 @@ const queryParams = reactive({
name: null, name: null,
post: null, post: null,
qq: null, qq: null,
webchat: null, wechat: null,
sex: null, sex: null,
policyMakers: null policyMakers: null
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中 const exportLoading = ref(false) // 导出的加载中
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
...@@ -305,35 +307,15 @@ const handleExport = async () => { ...@@ -305,35 +307,15 @@ const handleExport = async () => {
} }
} }
// TODO @zyna:这个负责人的读取,放在后端好点
const gotOwnerUser = (owerUserId: string) => {
let ownerName = ''
if (owerUserId !== null) {
owerUserId.split(',').forEach((item: string, index: number) => {
if (index != 0) {
ownerName =
ownerName + ',' + userList.value.find((user: { id: any }) => user.id == item)?.nickname
} else {
ownerName = userList.value.find((user: { id: any }) => user.id == item)?.nickname || ''
}
})
}
return ownerName
}
/** 打开客户详情 */ /** 打开客户详情 */
const { push } = useRouter() const { push } = useRouter()
const openDetail = (id: number) => { const openDetail = (id: number) => {
push({ name: 'CrmContactDetail', params: { id } }) push({ name: 'CrmContactDetail', params: { id } })
} }
// TODO @zyna:这个上级的读取,放在后端读取,更合适;因为可能数据量比较大
const allContactList = ref([]) //所有联系人列表
const allCustomerList = ref([]) //客户列表
/** 初始化 **/ /** 初始化 **/
onMounted(async () => { onMounted(async () => {
await getList() await getList()
userList.value = await UserApi.getSimpleUserList() customerList.value = await CustomerApi.queryAllList()
allContactList.value = await ContactApi.simpleAlllist()
}) })
</script> </script>
<template>
<!-- 操作栏 -->
<el-row justify="end">
<el-button @click="openForm">
<Icon class="mr-5px" icon="clarity:contract-line" />
创建合同
</el-button>
</el-row>
<!-- 列表 -->
<ContentWrap class="mt-10px">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="合同名称" fixed="left" align="center" prop="name">
<template #default="scope">
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
{{ scope.row.name }}
</el-link>
</template>
</el-table-column>
<el-table-column label="合同编号" align="center" prop="no" />
<el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column
label="合同金额(元)"
align="center"
prop="price"
:formatter="fenToYuanFormat"
/>
<el-table-column
label="开始时间"
align="center"
prop="startTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column
label="结束时间"
align="center"
prop="endTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column align="center" label="状态" prop="auditStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加 -->
<ContractForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import * as ContractApi from '@/api/crm/contract'
import ContractForm from './../ContractForm.vue'
import { BizTypeEnum } from '@/api/crm/permission'
import { fenToYuanFormat } from '@/utils/formatter'
import { dateFormatter } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
defineOptions({ name: 'CrmContractList' })
const props = defineProps<{
bizType: number // 业务类型
bizId: number // 业务编号
}>()
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
customerId: undefined as unknown // 允许 undefined + number
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
// 置空参数
queryParams.customerId = undefined
// 执行查询
let data = { list: [], total: 0 }
switch (props.bizType) {
case BizTypeEnum.CRM_CUSTOMER:
queryParams.customerId = props.bizId
data = await ContractApi.getContractPageByCustomer(queryParams)
break
default:
return
}
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 添加 */
const formRef = ref()
const openForm = () => {
formRef.value.open('create')
}
/** 打开合同详情 */
const { push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'CrmContractDetail', params: { id } })
}
/** 监听打开的 bizId + bizType,从而加载最新的列表 */
watch(
() => [props.bizId, props.bizType],
() => {
handleQuery()
},
{ immediate: true, deep: true }
)
</script>
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
import { dateFormatter } from '@/utils/formatTime'
// 表单校验
export const rules = reactive({
name: [required]
})
// TODO @dbh52:不使用 crud 模式哈,使用标准的 ep 代码哈;主要后续 crud schema 可能会改
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
const crudSchemas = reactive<CrudSchema[]>([
{
label: '合同编号',
field: 'id',
isForm: false
},
{
label: '合同名称',
field: 'name',
isSearch: true
},
{
label: '客户编号',
field: 'customerId',
isSearch: true,
form: {
component: 'InputNumber',
value: 0
}
},
{
label: '商机编号',
field: 'businessId',
isSearch: true,
form: {
component: 'InputNumber',
value: 0
}
},
{
label: '工作流编号',
field: 'processInstanceId',
isSearch: true,
form: {
component: 'InputNumber',
value: 0
}
},
{
label: '下单日期',
field: 'orderDate',
formatter: dateFormatter,
isSearch: true,
search: {
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD HH:mm:ss',
type: 'daterange',
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
}
},
form: {
component: 'DatePicker',
componentProps: {
type: 'datetime',
valueFormat: 'x'
}
}
},
{
label: '负责人的用户编号',
field: 'ownerUserId',
isSearch: true,
form: {
component: 'InputNumber',
value: 0
}
},
{
label: '创建时间',
field: 'createTime',
formatter: dateFormatter,
isSearch: true,
search: {
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD HH:mm:ss',
type: 'daterange',
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
}
},
isForm: false
},
{
label: '合同编号',
field: 'no',
isSearch: true
},
{
label: '开始时间',
field: 'startTime',
formatter: dateFormatter,
isSearch: true,
search: {
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD HH:mm:ss',
type: 'daterange',
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
}
},
form: {
component: 'DatePicker',
componentProps: {
type: 'datetime',
valueFormat: 'x'
}
}
},
{
label: '结束时间',
field: 'endTime',
formatter: dateFormatter,
isSearch: true,
search: {
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD HH:mm:ss',
type: 'daterange',
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
}
},
form: {
component: 'DatePicker',
componentProps: {
type: 'datetime',
valueFormat: 'x'
}
}
},
{
label: '合同金额',
field: 'price',
isSearch: true,
form: {
component: 'InputNumber',
value: 0
}
},
{
label: '整单折扣',
field: 'discountPercent',
isSearch: true,
form: {
component: 'InputNumber',
value: 0
}
},
{
label: '产品总金额',
field: 'productPrice',
isSearch: true,
form: {
component: 'InputNumber',
value: 0
}
},
{
label: '只读权限的用户编号数组',
field: 'roUserIds',
isSearch: true
},
{
label: '读写权限的用户编号数组',
field: 'rwUserIds',
isSearch: true
},
{
label: '联系人编号',
field: 'contactId',
isSearch: true,
form: {
component: 'InputNumber',
value: 0
}
},
{
label: '备注',
field: 'remark',
isSearch: true
},
{
label: '公司签约人',
field: 'signUserId',
isSearch: true,
form: {
component: 'InputNumber',
value: 0
}
},
{
label: '最后跟进时间',
field: 'contactLastTime',
formatter: dateFormatter,
isSearch: true,
search: {
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD HH:mm:ss',
type: 'daterange',
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
}
},
form: {
component: 'DatePicker',
componentProps: {
type: 'datetime',
valueFormat: 'x'
}
}
},
{
label: '操作',
field: 'action',
isForm: false
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)
...@@ -8,48 +8,19 @@ ...@@ -8,48 +8,19 @@
:inline="true" :inline="true"
label-width="68px" label-width="68px"
> >
<el-form-item label="合同名称" prop="name"> <el-form-item label="合同编号" prop="no">
<el-input
v-model="queryParams.name"
placeholder="请输入合同名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="客户编号" prop="customerId">
<el-input
v-model="queryParams.customerId"
placeholder="请输入客户编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="商机编号" prop="businessId">
<el-input <el-input
v-model="queryParams.businessId" v-model="queryParams.no"
placeholder="请输入商机编号" placeholder="请输入合同编号"
clearable clearable
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item label="下单日期" prop="orderDate"> <el-form-item label="合同名称" prop="name">
<el-date-picker
v-model="queryParams.orderDate"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="合同编号" prop="no">
<el-input <el-input
v-model="queryParams.no" v-model="queryParams.name"
placeholder="请输入合同编号" placeholder="请输入合同名称"
clearable clearable
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
class="!w-240px" class="!w-240px"
...@@ -75,6 +46,7 @@ ...@@ -75,6 +46,7 @@
</ContentWrap> </ContentWrap>
<!-- 列表 --> <!-- 列表 -->
<!-- TODO 芋艿:各种字段要调整 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="合同编号" align="center" prop="id" /> <el-table-column label="合同编号" align="center" prop="id" />
...@@ -125,7 +97,6 @@ ...@@ -125,7 +97,6 @@
width="180px" width="180px"
/> />
<el-table-column label="备注" align="center" prop="remark" /> <el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" width="120px"> <el-table-column label="操作" width="120px">
<template #default="scope"> <template #default="scope">
<el-button <el-button
...@@ -159,7 +130,6 @@ ...@@ -159,7 +130,6 @@
<!-- 表单弹窗:添加/修改 --> <!-- 表单弹窗:添加/修改 -->
<ContractForm ref="formRef" @success="getList" /> <ContractForm ref="formRef" @success="getList" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
......
<template>
<el-col>
<el-row>
<span class="text-xl font-bold">{{ customer.name }}</span>
</el-row>
</el-col>
<el-col class="mt-10px">
<!-- TODO 标签 -->
<!-- <Icon icon="ant-design:tag-filled" />-->
</el-col>
</template>
<script setup lang="ts">
import * as CustomerApi from '@/api/crm/customer'
const { customer } = defineProps<{ customer: CustomerApi.CustomerVO }>()
</script>
<template>
<div v-loading="loading">
<div class="flex items-start justify-between">
<div>
<!-- 左上:客户基本信息 -->
<el-col>
<el-row>
<span class="text-xl font-bold">{{ customer.name }}</span>
</el-row>
</el-col>
</div>
<div>
<!-- 右上:按钮 -->
<el-button v-hasPermi="['crm:customer:update']" @click="openForm(customer.id)">
编辑
</el-button>
<el-button>更改成交状态</el-button>
</div>
</div>
</div>
<ContentWrap class="mt-10px">
<el-descriptions :column="5" direction="vertical">
<el-descriptions-item label="客户级别">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="customer.level" />
</el-descriptions-item>
<el-descriptions-item label="成交状态">
{{ customer.dealStatus ? '已成交' : '未成交' }}
</el-descriptions-item>
<el-descriptions-item label="负责人">{{ customer.ownerUserName }} </el-descriptions-item>
<!-- TODO wanwan 首要联系人? -->
<el-descriptions-item label="首要联系人" />
<!-- TODO wanwan 首要联系人电话? -->
<el-descriptions-item label="首要联系人电话">{{ customer.mobile }} </el-descriptions-item>
</el-descriptions>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<CustomerForm ref="formRef" @success="emit('refresh')" />
</template>
<script setup lang="ts">
import { DICT_TYPE } from '@/utils/dict'
import * as CustomerApi from '@/api/crm/customer'
import CustomerForm from '../CustomerForm.vue'
const { customer, loading } = defineProps<{
customer: CustomerApi.CustomerVO // 客户信息
loading: boolean // 加载中
}>()
/** 修改操作 */
const formRef = ref()
const openForm = (id?: number) => {
formRef.value.open('update', id)
}
const emit = defineEmits(['refresh']) // 定义 success 事件,用于操作成功后的回调
</script>
...@@ -18,29 +18,15 @@ ...@@ -18,29 +18,15 @@
<el-descriptions-item label="客户等级"> <el-descriptions-item label="客户等级">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="customer.level" /> <dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="customer.level" />
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="手机"> <el-descriptions-item label="手机">{{ customer.mobile }}</el-descriptions-item>
{{ customer.mobile }} <el-descriptions-item label="电话">{{ customer.telephone }}</el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="邮箱">{{ customer.email }} </el-descriptions-item>
<el-descriptions-item label="电话"> <el-descriptions-item label="QQ">{{ customer.qq }} </el-descriptions-item>
{{ customer.telephone }} <el-descriptions-item label="微信">{{ customer.wechat }} </el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="网址">{{ customer.website }} </el-descriptions-item>
<el-descriptions-item label="邮箱"> <el-descriptions-item label="所在地">{{ customer.areaName }} </el-descriptions-item>
{{ customer.email }} <el-descriptions-item label="详细地址"
</el-descriptions-item> >{{ customer.detailAddress }}
<el-descriptions-item label="QQ">
{{ customer.qq }}
</el-descriptions-item>
<el-descriptions-item label="微信">
{{ customer.wechat }}
</el-descriptions-item>
<el-descriptions-item label="网址">
{{ customer.website }}
</el-descriptions-item>
<el-descriptions-item label="所在地">
{{ customer.areaName }}
</el-descriptions-item>
<el-descriptions-item label="详细地址">
{{ customer.detailAddress }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="下次联系时间"> <el-descriptions-item label="下次联系时间">
{{ {{
...@@ -52,12 +38,8 @@ ...@@ -52,12 +38,8 @@
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<el-descriptions :column="1"> <el-descriptions :column="1">
<el-descriptions-item label="客户描述"> <el-descriptions-item label="客户描述">{{ customer.description }} </el-descriptions-item>
{{ customer.description }} <el-descriptions-item label="备注">{{ customer.remark }} </el-descriptions-item>
</el-descriptions-item>
<el-descriptions-item label="备注">
{{ customer.remark }}
</el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-collapse-item> </el-collapse-item>
<el-collapse-item name="systemInfo"> <el-collapse-item name="systemInfo">
...@@ -65,12 +47,8 @@ ...@@ -65,12 +47,8 @@
<span class="text-base font-bold">系统信息</span> <span class="text-base font-bold">系统信息</span>
</template> </template>
<el-descriptions :column="2"> <el-descriptions :column="2">
<el-descriptions-item label="负责人"> <el-descriptions-item label="负责人">{{ customer.ownerUserName }} </el-descriptions-item>
{{ customer.ownerUserName }} <el-descriptions-item label="创建人">{{ customer.creatorName }} </el-descriptions-item>
</el-descriptions-item>
<el-descriptions-item label="创建人">
{{ customer.creatorName }}
</el-descriptions-item>
<el-descriptions-item label="创建时间"> <el-descriptions-item label="创建时间">
{{ customer.createTime ? formatDate(customer.createTime) : '空' }} {{ customer.createTime ? formatDate(customer.createTime) : '空' }}
</el-descriptions-item> </el-descriptions-item>
...@@ -87,9 +65,10 @@ import * as CustomerApi from '@/api/crm/customer' ...@@ -87,9 +65,10 @@ import * as CustomerApi from '@/api/crm/customer'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
const { customer } = defineProps<{ customer: CustomerApi.CustomerVO }>() const { customer } = defineProps<{
customer: CustomerApi.CustomerVO // 客户明细
}>()
// 展示的折叠面板 const activeNames = ref(['basicInfo', 'systemInfo']) // 展示的折叠面板
const activeNames = ref(['basicInfo', 'systemInfo'])
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>
<template> <template>
<!-- TODO @wanwan:要不要把上面这一整块,搞成一个组件,就是把 下面 + Details + BasitcInfo 合并成一个 --> <CustomerDetailsHeader :customer="customer" :loading="loading" @refresh="getCustomer(id)" />
<div v-loading="loading">
<div class="flex items-start justify-between">
<div>
<!-- 左上:客户基本信息 -->
<CustomerBasicInfo :customer="customer" />
</div>
<div>
<!-- 右上:按钮 -->
<el-button v-hasPermi="['crm:customer:update']" @click="openForm('update', customer.id)">
编辑
</el-button>
<el-button>更改成交状态</el-button>
</div>
</div>
<el-row class="mt-10px">
<el-button>
<Icon class="mr-5px" icon="ph:calendar-fill" />
创建任务
</el-button>
<el-button>
<Icon class="mr-5px" icon="carbon:email" />
发送邮件
</el-button>
<el-button>
<Icon class="mr-5px" icon="system-uicons:contacts" />
创建联系人
</el-button>
<el-button>
<Icon class="mr-5px" icon="ep:opportunity" />
创建商机
</el-button>
<el-button>
<Icon class="mr-5px" icon="clarity:contract-line" />
创建合同
</el-button>
<el-button>
<Icon class="mr-5px" icon="icon-park:income-one" />
创建回款
</el-button>
<el-button>
<Icon class="mr-5px" icon="fluent:people-team-add-20-filled" />
添加团队成员
</el-button>
</el-row>
</div>
<ContentWrap class="mt-10px">
<el-descriptions :column="5" direction="vertical">
<el-descriptions-item label="客户级别">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="customer.level" />
</el-descriptions-item>
<el-descriptions-item label="成交状态">
{{ customer.dealStatus ? '已成交' : '未成交' }}
</el-descriptions-item>
<el-descriptions-item label="负责人">
{{ customer.ownerUserName }}
</el-descriptions-item>
<!-- TODO wanwan 首要联系人? -->
<el-descriptions-item label="首要联系人" />
<!-- TODO wanwan 首要联系人电话? -->
<el-descriptions-item label="首要联系人电话">
{{ customer.mobile }}
</el-descriptions-item>
</el-descriptions>
</ContentWrap>
<el-col> <el-col>
<el-tabs> <el-tabs>
<el-tab-pane label="详细资料"> <el-tab-pane label="详细资料">
<CustomerDetails :customer="customer" /> <CustomerDetailsInfo :customer="customer" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="活动" lazy> 活动</el-tab-pane> <el-tab-pane label="操作日志" lazy>TODO 待开发</el-tab-pane>
<el-tab-pane label="邮件" lazy> 邮件</el-tab-pane>
<el-tab-pane label="工商信息" lazy> 工商信息</el-tab-pane>
<el-tab-pane label="客户关系" lazy> 客户关系</el-tab-pane>
<!-- TODO wanwan 以下标签上的数量需要接口统计返回 -->
<el-tab-pane label="联系人" lazy> <el-tab-pane label="联系人" lazy>
<template #label> <ContactList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
联系人
<el-badge class="item" type="primary" />
</template>
联系人
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="团队成员" lazy> <el-tab-pane label="团队成员" lazy>
<template #label> <PermissionList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
团队成员 </el-tab-pane>
<el-badge class="item" type="primary" /> <el-tab-pane label="商机" lazy>
</template> <BusinessList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
<CrmPermissionList :biz-id="customer.id" :biz-type="CrmBizTypeEnum.CRM_CUSTOMER" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="商机" lazy> 商机</el-tab-pane>
<el-tab-pane label="合同" lazy> <el-tab-pane label="合同" lazy>
<template #label> <ContractList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
合同
<el-badge class="item" type="primary" />
</template>
合同
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="回款" lazy> <el-tab-pane label="回款" lazy>
<template #label> <ReceivablePlanList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
回款 <ReceivableList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
<el-badge class="item" type="primary" />
</template>
回款
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="回访" lazy> 回访</el-tab-pane> <el-tab-pane label="回访" lazy>TODO 待开发</el-tab-pane>
<el-tab-pane label="发票" lazy> 发票</el-tab-pane>
</el-tabs> </el-tabs>
</el-col> </el-col>
<!-- 表单弹窗:添加/修改 -->
<CustomerForm ref="formRef" @success="getCustomerData(id)" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ElMessage } from 'element-plus'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import * as CustomerApi from '@/api/crm/customer' import * as CustomerApi from '@/api/crm/customer'
import CustomerBasicInfo from '@/views/crm/customer/detail/CustomerBasicInfo.vue' import CustomerDetailsInfo from './CustomerDetailsInfo.vue' // 客户明细 - 详细信息
import { DICT_TYPE } from '@/utils/dict' import CustomerDetailsHeader from './CustomerDetailsHeader.vue' // 客户明细 - 头部
import CustomerDetails from '@/views/crm/customer/detail/CustomerDetails.vue' import ContactList from '@/views/crm/contact/components/ContactList.vue' // 联系人列表
import CustomerForm from '@/views/crm/customer/CustomerForm.vue' import ContractList from '@/views/crm/contract/components/ContractList.vue' // 合同列表
import { CrmBizTypeEnum, CrmPermissionList } from '@/views/crm/components' import BusinessList from '@/views/crm/business/components/BusinessList.vue' // 商机列表
import ReceivableList from '@/views/crm/receivable/components/ReceivableList.vue' // 回款列表
import ReceivablePlanList from '@/views/crm/receivable/plan/components/ReceivablePlanList.vue' // 回款计划列表
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限)
import { BizTypeEnum } from '@/api/crm/permission'
defineOptions({ name: 'CustomerDetail' }) defineOptions({ name: 'CrmCustomerDetail' })
const { delView } = useTagsViewStore() // 视图操作
const route = useRoute() const route = useRoute()
const { currentRoute } = useRouter() // 路由 const id = Number(route.params.id) // 客户编号
const id = Number(route.params.id)
const loading = ref(true) // 加载中 const loading = ref(true) // 加载中
/** /** 获取详情 */
* 获取详情
*
* @param id
*/
const customer = ref<CustomerApi.CustomerVO>({} as CustomerApi.CustomerVO) // 客户详情 const customer = ref<CustomerApi.CustomerVO>({} as CustomerApi.CustomerVO) // 客户详情
const getCustomerData = async (id: number) => { const getCustomer = async (id: number) => {
loading.value = true loading.value = true
try { try {
customer.value = await CustomerApi.getCustomer(id) customer.value = await CustomerApi.getCustomer(id)
...@@ -145,20 +56,15 @@ const getCustomerData = async (id: number) => { ...@@ -145,20 +56,15 @@ const getCustomerData = async (id: number) => {
} }
} }
const formRef = ref() /** 初始化 */
const openForm = (type: string, id?: number) => { const { delView } = useTagsViewStore() // 视图操作
formRef.value.open(type, id) const { currentRoute } = useRouter() // 路由
}
/**
* 初始化
*/
onMounted(() => { onMounted(() => {
if (!id) { if (!id) {
ElMessage.warning('参数错误,客户不能为空!') ElMessage.warning('参数错误,客户不能为空!')
delView(unref(currentRoute)) delView(unref(currentRoute))
return return
} }
getCustomerData(id) getCustomer(id)
}) })
</script> </script>
...@@ -72,17 +72,10 @@ ...@@ -72,17 +72,10 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"> <el-button @click="handleQuery"> <Icon class="mr-5px" icon="ep:search" /> 搜索 </el-button>
<Icon class="mr-5px" icon="ep:search" /> <el-button @click="resetQuery"> <Icon class="mr-5px" icon="ep:refresh" />重置 </el-button>
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
<el-button v-hasPermi="['crm:customer:create']" type="primary" @click="openForm('create')"> <el-button v-hasPermi="['crm:customer:create']" type="primary" @click="openForm('create')">
<Icon class="mr-5px" icon="ep:plus" /> <Icon class="mr-5px" icon="ep:plus" /> 新增
新增
</el-button> </el-button>
<el-button <el-button
v-hasPermi="['crm:customer:export']" v-hasPermi="['crm:customer:export']"
...@@ -91,8 +84,7 @@ ...@@ -91,8 +84,7 @@
type="success" type="success"
@click="handleExport" @click="handleExport"
> >
<Icon class="mr-5px" icon="ep:download" /> <Icon class="mr-5px" icon="ep:download" /> 导出
导出
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
...@@ -102,7 +94,13 @@ ...@@ -102,7 +94,13 @@
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true"> <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" label="编号" prop="id" /> <el-table-column align="center" label="编号" prop="id" />
<el-table-column align="center" label="客户名称" prop="name" width="160" /> <el-table-column align="center" label="客户名称" prop="name" width="160">
<template #default="scope">
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
{{ scope.row.name }}
</el-link>
</template>
</el-table-column>
<el-table-column align="center" label="所属行业" prop="industryId" width="120"> <el-table-column align="center" label="所属行业" prop="industryId" width="120">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" /> <dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
...@@ -157,7 +155,6 @@ ...@@ -157,7 +155,6 @@
<!-- TODO @wanwan 距进入公海天数 --> <!-- TODO @wanwan 距进入公海天数 -->
<el-table-column align="center" fixed="right" label="操作" min-width="150"> <el-table-column align="center" fixed="right" label="操作" min-width="150">
<template #default="scope"> <template #default="scope">
<el-button link type="primary" @click="openDetail(scope.row.id)">详情</el-button>
<el-button <el-button
v-hasPermi="['crm:customer:update']" v-hasPermi="['crm:customer:update']"
link link
......
// TODO 可以挪到它对应的 api.ts 文件里哈
/**
* 客户限制配置类型
*/
export enum LimitConfType {
/**
* 拥有客户数限制
*/
CUSTOMER_QUANTITY_LIMIT = 1,
/**
* 锁定客户数限制
*/
CUSTOMER_LOCK_LIMIT = 2
}
...@@ -23,12 +23,13 @@ ...@@ -23,12 +23,13 @@
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PERMISSION_LEVEL)" v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PERMISSION_LEVEL)"
:key="dict.value" :key="dict.value"
> >
<el-radio v-if="dict.value != CrmPermissionLevelEnum.OWNER" :label="dict.value"> <el-radio v-if="dict.value != PermissionLevelEnum.OWNER" :label="dict.value">
{{ dict.label }} {{ dict.label }}
</el-radio> </el-radio>
</template> </template>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<!-- TODO @puhui999:同时添加至 -->
</el-form> </el-form>
<template #footer> <template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
...@@ -39,8 +40,8 @@ ...@@ -39,8 +40,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import * as PermissionApi from '@/api/crm/permission' import * as PermissionApi from '@/api/crm/permission'
import { PermissionLevelEnum } from '@/api/crm/permission'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { CrmPermissionLevelEnum } from '@/views/crm/components/index'
defineOptions({ name: 'CrmPermissionForm' }) defineOptions({ name: 'CrmPermissionForm' })
...@@ -54,8 +55,8 @@ const formType = ref('') // 表单的类型:create - 新增;update - 修改 ...@@ -54,8 +55,8 @@ const formType = ref('') // 表单的类型:create - 新增;update - 修改
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表 const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
const formData = ref<PermissionApi.PermissionVO & { ids?: number[] }>({ const formData = ref<PermissionApi.PermissionVO & { ids?: number[] }>({
userId: undefined, // 用户编号 userId: undefined, // 用户编号
bizType: undefined, // Crm 类型 bizType: undefined, // CRM 类型
bizId: undefined, // Crm 类型数据编号 bizId: undefined, // CRM 类型数据编号
level: undefined // 权限级别 level: undefined // 权限级别
}) })
const formRules = reactive({ const formRules = reactive({
......
<template> <template>
<!-- 操作栏 --> <!-- 操作栏 -->
<el-row justify="end"> <el-row justify="end">
<el-button type="primary" @click="openForm"> <el-button @click="openForm">
<Icon class="mr-5px" icon="ep:plus" /> <Icon class="mr-5px" icon="fluent:people-team-add-20-filled" /> 添加团队成员
新增
</el-button> </el-button>
<el-button @click="handleEdit"> <el-button @click="handleUpdate">
<Icon class="mr-5px" icon="ep:edit" /> <Icon class="mr-5px" icon="ep:edit" />
编辑 编辑
</el-button> </el-button>
...@@ -15,45 +14,52 @@ ...@@ -15,45 +14,52 @@
</el-button> </el-button>
<el-button type="danger" @click="handleQuit"> 退出团队</el-button> <el-button type="danger" @click="handleQuit"> 退出团队</el-button>
</el-row> </el-row>
<!-- 团队成员展示 -->
<!-- 列表 -->
<ContentWrap class="mt-10px">
<el-table <el-table
v-loading="loading" v-loading="loading"
:data="list" :data="list"
:show-overflow-tooltip="true" :show-overflow-tooltip="true"
:stripe="true" :stripe="true"
class="mt-20px"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
> >
<el-table-column type="selection" width="55" /> <el-table-column type="selection" width="55" />
<el-table-column align="center" label="姓名" prop="nickname" /> <el-table-column align="center" label="姓名" prop="nickname" />
<el-table-column align="center" label="部门" prop="deptName" /> <el-table-column align="center" label="部门" prop="deptName" />
<el-table-column align="center" label="岗位" prop="postNames" /> <el-table-column align="center" label="岗位" prop="postNames" />
<el-table-column align="center" label="权限级别" prop="level"> <el-table-column align="center" label="权限" prop="level">
<template #default="{ row }"> <template #default="{ row }">
<dict-tag :type="DICT_TYPE.CRM_PERMISSION_LEVEL" :value="row.level" /> <dict-tag :type="DICT_TYPE.CRM_PERMISSION_LEVEL" :value="row.level" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :formatter="dateFormatter" align="center" label="加入时间" prop="createTime" /> <el-table-column
:formatter="dateFormatter"
align="center"
label="加入时间"
prop="createTime"
/>
</el-table> </el-table>
<CrmPermissionForm ref="permissionFormRef" @success="getList" /> </ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<CrmPermissionForm ref="formRef" @success="getList" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import { ElTable } from 'element-plus' import { DICT_TYPE } from '@/utils/dict'
import * as PermissionApi from '@/api/crm/permission' import * as PermissionApi from '@/api/crm/permission'
import { PermissionLevelEnum } from '@/api/crm/permission'
import { useUserStoreWithOut } from '@/store/modules/user' import { useUserStoreWithOut } from '@/store/modules/user'
import CrmPermissionForm from './CrmPermissionForm.vue' import CrmPermissionForm from './PermissionForm.vue'
import { CrmPermissionLevelEnum } from './index'
import { DICT_TYPE } from '@/utils/dict'
defineOptions({ name: 'CrmPermissionList' }) defineOptions({ name: 'CrmPermissionList' })
const message = useMessage() // 消息
const props = defineProps<{ const props = defineProps<{
bizType: number // 模块类型 bizType: number // 业务类型
bizId: number // 模块数据编号 bizId: number // 业务编号
}>() }>()
const message = useMessage() // 消息
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
const list = ref<PermissionApi.PermissionVO[]>([]) // 列表的数据 const list = ref<PermissionApi.PermissionVO[]>([]) // 列表的数据
...@@ -61,75 +67,71 @@ const list = ref<PermissionApi.PermissionVO[]>([]) // 列表的数据 ...@@ -61,75 +67,71 @@ const list = ref<PermissionApi.PermissionVO[]>([]) // 列表的数据
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {
const data = await PermissionApi.getPermissionList({ list.value = await PermissionApi.getPermissionList({
bizType: props.bizType, bizType: props.bizType,
bizId: props.bizId bizId: props.bizId
}) })
list.value = data
} finally { } finally {
loading.value = false loading.value = false
} }
} }
/** 选中团队成员 */
const multipleSelection = ref<PermissionApi.PermissionVO[]>([]) // 选择的团队成员 const multipleSelection = ref<PermissionApi.PermissionVO[]>([]) // 选择的团队成员
const handleSelectionChange = (val: PermissionApi.PermissionVO[]) => { const handleSelectionChange = (val: PermissionApi.PermissionVO[]) => {
multipleSelection.value = val multipleSelection.value = val
} }
const permissionFormRef = ref<InstanceType<typeof CrmPermissionForm>>() // 权限表单 Ref /** 添加团队成员 */
/** const formRef = ref<InstanceType<typeof CrmPermissionForm>>() // 权限表单 Ref
* 编辑团队成员 const openForm = () => {
*/ formRef.value?.open('create', props.bizType, props.bizId)
const handleEdit = () => { }
/** 编辑团队成员 */
const handleUpdate = () => {
if (multipleSelection.value?.length === 0) { if (multipleSelection.value?.length === 0) {
message.warning('请先选择团队成员后操作!') message.warning('请先选择团队成员后操作!')
return return
} }
const ids = multipleSelection.value?.map((item) => item.id) const ids = multipleSelection.value?.map((item) => item.id) as number[]
permissionFormRef.value?.open('update', props.bizType, props.bizId, ids) formRef.value?.open('update', props.bizType, props.bizId, ids)
} }
/** /** 移除团队成员 */
* 移除团队成员
*/
const handleDelete = async () => { const handleDelete = async () => {
if (multipleSelection.value?.length === 0) { if (multipleSelection.value?.length === 0) {
message.warning('请先选择团队成员后操作!') message.warning('请先选择团队成员后操作!')
return return
} }
// TODO @puhui999:应该有个提示哈
await message.delConfirm() await message.delConfirm()
const ids = multipleSelection.value?.map((item) => item.id) const ids = multipleSelection.value?.map((item) => item.id)
await PermissionApi.deletePermission({ await PermissionApi.deletePermissionBatch({
bizType: props.bizType, bizType: props.bizType,
bizId: props.bizId, bizId: props.bizId,
ids ids
}) })
} }
/** /** 退出团队 */
* 添加团队成员
*/
const openForm = () => {
permissionFormRef.value?.open('create', props.bizType, props.bizId)
}
const userStore = useUserStoreWithOut() // 用户信息缓存 const userStore = useUserStoreWithOut() // 用户信息缓存
/**
* 退出团队
*/
const handleQuit = async () => { const handleQuit = async () => {
const permission = list.value.find( const permission = list.value.find(
(item) => item.userId === userStore.getUser.id && item.level === CrmPermissionLevelEnum.OWNER (item) => item.userId === userStore.getUser.id && item.level === PermissionLevelEnum.OWNER
) )
if (permission) { if (permission) {
message.warning('负责人不能退出团队!') message.warning('负责人不能退出团队!')
return return
} }
// TODO @puhui999:应该有个提示哈
const userPermission = list.value.find((item) => item.userId === userStore.getUser.id) const userPermission = list.value.find((item) => item.userId === userStore.getUser.id)
await PermissionApi.quitTeam(userPermission?.id) await PermissionApi.deleteSelfPermission(userPermission?.id)
} }
/** 监听打开的 bizId + bizType,从而加载最新的列表 */
watch( watch(
() => props.bizId, () => [props.bizId, props.bizType],
() => { () => {
getList() getList()
}, },
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
<el-form-item label="回款方式" prop="returnType"> <el-form-item label="回款方式" prop="returnType">
<el-select v-model="formData.returnType" placeholder="请选择回款方式"> <el-select v-model="formData.returnType" placeholder="请选择回款方式">
<el-option <el-option
v-for="dict in getStrDictOptions(DICT_TYPE.CRM_RETURN_TYPE)" v-for="dict in getStrDictOptions(DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE)"
:key="dict.value" :key="dict.value"
:label="dict.label" :label="dict.label"
:value="dict.value" :value="dict.value"
......
<template>
<!-- 操作栏 -->
<el-row justify="end">
<el-button @click="openForm">
<Icon class="mr-5px" icon="icon-park:income-one" />
创建回款
</el-button>
</el-row>
<!-- 列表 -->
<ContentWrap class="mt-10px">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="回款编号" fixed="left" align="center" prop="no">
<template #default="scope">
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
{{ scope.row.no }}
</el-link>
</template>
</el-table-column>
<el-table-column label="合同编号" align="center" prop="contractNo" />
<el-table-column
label="回款金额(元)"
align="center"
prop="price"
:formatter="fenToYuanFormat"
/>
<el-table-column label="负责人" align="center" prop="ownerUserName" />
<el-table-column align="center" label="状态" prop="auditStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
</template>
</el-table-column>
<el-table-column
label="回款日期"
align="center"
prop="returnTime"
:formatter="dateFormatter"
width="180px"
/>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加 -->
<ReceivableForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import * as ReceivableApi from '@/api/crm/receivable'
import ReceivableForm from './../ReceivableForm.vue'
import { BizTypeEnum } from '@/api/crm/permission'
import { dateFormatter } from '@/utils/formatTime'
import { fenToYuanFormat } from '@/utils/formatter'
import { DICT_TYPE } from '@/utils/dict'
defineOptions({ name: 'CrmReceivableList' })
const props = defineProps<{
bizType: number // 业务类型
bizId: number // 业务编号
}>()
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
customerId: undefined as unknown // 允许 undefined + number
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
// 置空参数
queryParams.customerId = undefined
// 执行查询
let data = { list: [], total: 0 }
switch (props.bizType) {
case BizTypeEnum.CRM_CUSTOMER:
queryParams.customerId = props.bizId
data = await ReceivableApi.getReceivablePageByCustomer(queryParams)
break
default:
return
}
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 添加 */
const formRef = ref()
const openForm = () => {
formRef.value.open('create')
}
/** 打开合同详情 */
const { push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'CrmReceivableDetail', params: { id } })
}
/** 监听打开的 bizId + bizType,从而加载最新的列表 */
watch(
() => [props.bizId, props.bizType],
() => {
handleQuery()
},
{ immediate: true, deep: true }
)
</script>
...@@ -17,15 +17,6 @@ ...@@ -17,15 +17,6 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<!--<el-form-item label="回款计划ID" prop="planId">
<el-input
v-model="queryParams.planId"
placeholder="请输入回款计划ID"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>-->
<el-form-item label="客户名称" prop="customerId"> <el-form-item label="客户名称" prop="customerId">
<el-input <el-input
v-model="queryParams.customerId" v-model="queryParams.customerId"
...@@ -35,131 +26,6 @@ ...@@ -35,131 +26,6 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item label="合同名称" prop="contractId">
<el-input
v-model="queryParams.contractId"
placeholder="请输入合同名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="审批状态" prop="checkStatus">
<el-select
v-model="queryParams.checkStatus"
placeholder="请选择审批状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_RECEIVABLE_CHECK_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<!--<el-form-item label="工作流编号" prop="processInstanceId">
<el-input
v-model="queryParams.processInstanceId"
placeholder="请输入工作流编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>-->
<el-form-item label="回款日期" prop="returnTime">
<el-date-picker
v-model="queryParams.returnTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="回款方式" prop="returnType">
<el-select
v-model="queryParams.returnType"
placeholder="请选择回款方式"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_RETURN_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<!--<el-form-item label="回款金额" prop="price">
<el-input
v-model="queryParams.price"
placeholder="请输入回款金额"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>-->
<!--<el-form-item label="负责人" prop="ownerUserId">
<el-input
v-model="queryParams.ownerUserId"
placeholder="请输入负责人"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="批次" prop="batchId">
<el-input
v-model="queryParams.batchId"
placeholder="请输入批次"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>-->
<!--<el-form-item label="数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)" prop="dataScope">
<el-input
v-model="queryParams.dataScope"
placeholder="请输入数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="数据范围(指定部门数组)" prop="dataScopeDeptIds">
<el-input
v-model="queryParams.dataScopeDeptIds"
placeholder="请输入数据范围(指定部门数组)"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>-->
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
...@@ -194,7 +60,7 @@ ...@@ -194,7 +60,7 @@
<el-table-column label="合同" align="center" prop="contractId" /> <el-table-column label="合同" align="center" prop="contractId" />
<el-table-column label="审批状态" align="center" prop="checkStatus" width="130px"> <el-table-column label="审批状态" align="center" prop="checkStatus" width="130px">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_RECEIVABLE_CHECK_STATUS" :value="scope.row.checkStatus" /> <dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.checkStatus" />
</template> </template>
</el-table-column> </el-table-column>
<!-- <el-table-column label="工作流编号" align="center" prop="processInstanceId" />--> <!-- <el-table-column label="工作流编号" align="center" prop="processInstanceId" />-->
...@@ -207,7 +73,7 @@ ...@@ -207,7 +73,7 @@
/> />
<el-table-column label="回款方式" align="center" prop="returnType" width="130px"> <el-table-column label="回款方式" align="center" prop="returnType" width="130px">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_RETURN_TYPE" :value="scope.row.returnType" /> <dict-tag :type="DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE" :value="scope.row.returnType" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="回款金额(元)" align="center" prop="price" /> <el-table-column label="回款金额(元)" align="center" prop="price" />
...@@ -266,7 +132,7 @@ ...@@ -266,7 +132,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime' import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import * as ReceivableApi from '@/api/crm/receivable' import * as ReceivableApi from '@/api/crm/receivable'
...@@ -284,22 +150,7 @@ const queryParams = reactive({ ...@@ -284,22 +150,7 @@ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
no: null, no: null,
planId: null, customerId: null
customerId: null,
contractId: null,
checkStatus: null,
processInstanceId: null,
returnTime: [],
returnType: null,
price: null,
ownerUserId: null,
batchId: null,
sort: null,
dataScope: null,
dataScopeDeptIds: null,
status: null,
remark: null,
createTime: []
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中 const exportLoading = ref(false) // 导出的加载中
...@@ -355,7 +206,7 @@ const handleExport = async () => { ...@@ -355,7 +206,7 @@ const handleExport = async () => {
// 发起导出 // 发起导出
exportLoading.value = true exportLoading.value = true
const data = await ReceivableApi.exportReceivable(queryParams) const data = await ReceivableApi.exportReceivable(queryParams)
download.excel(data, '回款管理.xls') download.excel(data, '回款.xls')
} catch { } catch {
} finally { } finally {
exportLoading.value = false exportLoading.value = false
......
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
</Dialog> </Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import * as ReceivablePlanApi from '@/api/crm/receivablePlan' import * as ReceivablePlanApi from '@/api/crm/receivable/plan'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
......
<template>
<!-- 操作栏 -->
<el-row justify="end">
<el-button @click="openForm">
<Icon class="mr-5px" icon="icon-park:income" />
创建回款计划
</el-button>
</el-row>
<!-- 列表 -->
<ContentWrap class="mt-10px">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="期数" fixed="left" align="center" prop="no">
<template #default="scope">
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
{{ scope.row.period }}
</el-link>
</template>
</el-table-column>
<el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column label="合同编号" align="center" prop="contractNo" />
<el-table-column
label="计划还款金额(元)"
align="center"
prop="price"
:formatter="fenToYuanFormat"
/>
<el-table-column
label="计划还款日期"
align="center"
prop="returnTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column align="center" label="计划还款方式" prop="auditStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE" :value="scope.row.returnType" />
</template>
</el-table-column>
<el-table-column label="提前几日提醒" align="center" prop="remindDays" />
<el-table-column label="备注" align="center" prop="remark" />
<!-- TODO 芋艿:新建回款、编辑、删除 -->
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加 -->
<ReceivableForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import * as ReceivablePlanApi from '@/api/crm/receivable/plan'
import ReceivableForm from './../ReceivablePlanForm.vue'
import { BizTypeEnum } from '@/api/crm/permission'
import { dateFormatter } from '@/utils/formatTime'
import { fenToYuanFormat } from '@/utils/formatter'
import { DICT_TYPE } from '@/utils/dict'
defineOptions({ name: 'CrmReceivablePlanList' })
const props = defineProps<{
bizType: number // 业务类型
bizId: number // 业务编号
}>()
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
customerId: undefined as unknown // 允许 undefined + number
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
// 置空参数
queryParams.customerId = undefined
// 执行查询
let data = { list: [], total: 0 }
switch (props.bizType) {
case BizTypeEnum.CRM_CUSTOMER:
queryParams.customerId = props.bizId
data = await ReceivablePlanApi.getReceivablePlanPageByCustomer(queryParams)
break
default:
return
}
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 添加 */
const formRef = ref()
const openForm = () => {
formRef.value.open('create')
}
/** 打开合同详情 */
const { push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'CrmReceivablePlanDetail', params: { id } })
}
/** 监听打开的 bizId + bizType,从而加载最新的列表 */
watch(
() => [props.bizId, props.bizType],
() => {
handleQuery()
},
{ immediate: true, deep: true }
)
</script>
...@@ -26,96 +26,6 @@ ...@@ -26,96 +26,6 @@
class="!w-240px" class="!w-240px"
/> />
</el-form-item> </el-form-item>
<el-form-item label="完成状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择完成状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="审批状态" prop="checkStatus">
<el-select
v-model="queryParams.checkStatus"
placeholder="请选择审批状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.CRM_RECEIVABLE_CHECK_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="回款日期" prop="returnTime">
<el-date-picker
v-model="queryParams.returnTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<!--<el-form-item label="提前几天提醒" prop="remindDays">
<el-input
v-model="queryParams.remindDays"
placeholder="请输入提前几天提醒"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>-->
<!--<el-form-item label="提醒日期" prop="remindTime">
<el-date-picker
v-model="queryParams.remindTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>-->
<!--<el-form-item label="负责人" prop="ownerUserId">
<el-input
v-model="queryParams.ownerUserId"
placeholder="请输入负责人"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="queryParams.remark"
placeholder="请输入备注"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>-->
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
...@@ -171,7 +81,7 @@ ...@@ -171,7 +81,7 @@
</el-table-column> </el-table-column>
<el-table-column label="审批状态" align="center" prop="checkStatus" width="130px"> <el-table-column label="审批状态" align="center" prop="checkStatus" width="130px">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_RECEIVABLE_CHECK_STATUS" :value="scope.row.checkStatus" /> <dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.checkStatus" />
</template> </template>
</el-table-column> </el-table-column>
<!--<el-table-column label="工作流编号" align="center" prop="processInstanceId" />--> <!--<el-table-column label="工作流编号" align="center" prop="processInstanceId" />-->
...@@ -224,10 +134,10 @@ ...@@ -224,10 +134,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime' import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import * as ReceivablePlanApi from '@/api/crm/receivablePlan' import * as ReceivablePlanApi from '@/api/crm/receivable/plan'
import ReceivablePlanForm from './ReceivablePlanForm.vue' import ReceivablePlanForm from './ReceivablePlanForm.vue'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
...@@ -243,17 +153,8 @@ const userList = ref<UserApi.UserVO[]>([]) // 用户列表 ...@@ -243,17 +153,8 @@ const userList = ref<UserApi.UserVO[]>([]) // 用户列表
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
period: null,
status: null,
checkStatus: null,
returnTime: [],
remindDays: null,
remindTime: [],
customerId: null, customerId: null,
contractId: null, contractId: null
ownerUserId: null,
remark: null,
createTime: []
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中 const exportLoading = ref(false) // 导出的加载中
......
...@@ -32,8 +32,8 @@ ...@@ -32,8 +32,8 @@
<el-descriptions-item label="请求耗时">{{ detailData.duration }} ms</el-descriptions-item> <el-descriptions-item label="请求耗时">{{ detailData.duration }} ms</el-descriptions-item>
<el-descriptions-item label="操作结果"> <el-descriptions-item label="操作结果">
<div v-if="detailData.resultCode === 0">正常</div> <div v-if="detailData.resultCode === 0">正常</div>
<div v-else-if="detailData.resultCode > 0" <div v-else-if="detailData.resultCode > 0">
>失败 | {{ detailData.resultCode }} | {{ detailData.resultMsg }} 失败 | {{ detailData.resultCode }} | {{ detailData.resultMsg }}
</div> </div>
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
...@@ -49,7 +49,7 @@ defineOptions({ name: 'ApiAccessLogDetail' }) ...@@ -49,7 +49,7 @@ defineOptions({ name: 'ApiAccessLogDetail' })
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const detailLoading = ref(false) // 表单地加载中 const detailLoading = ref(false) // 表单地加载中
const detailData = ref() // 详情数据 const detailData = ref({} as ApiAccessLog.ApiAccessLogVO) // 详情数据
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (data: ApiAccessLog.ApiAccessLogVO) => { const open = async (data: ApiAccessLog.ApiAccessLogVO) => {
......
...@@ -64,7 +64,7 @@ defineOptions({ name: 'ApiErrorLogDetail' }) ...@@ -64,7 +64,7 @@ defineOptions({ name: 'ApiErrorLogDetail' })
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const detailLoading = ref(false) // 表单的加载中 const detailLoading = ref(false) // 表单的加载中
const detailData = ref() // 详情数据 const detailData = ref({} as ApiErrorLog.ApiErrorLogVO) // 详情数据
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (data: ApiErrorLog.ApiErrorLogVO) => { const open = async (data: ApiErrorLog.ApiErrorLogVO) => {
......
...@@ -8,7 +8,11 @@ ...@@ -8,7 +8,11 @@
<colum-info-form ref="columInfoRef" :columns="formData.columns" /> <colum-info-form ref="columInfoRef" :columns="formData.columns" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="生成信息" name="generateInfo"> <el-tab-pane label="生成信息" name="generateInfo">
<generate-info-form ref="generateInfoRef" :table="formData.table" :columns="formData.columns" /> <generate-info-form
ref="generateInfoRef"
:table="formData.table"
:columns="formData.columns"
/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<el-form> <el-form>
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
<el-radio-group v-model="formData.visible"> <el-radio-group v-model="formData.visible">
<el-radio <el-radio
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :key="dict.value as string"
:label="dict.value" :label="dict.value"
> >
{{ dict.label }} {{ dict.label }}
......
...@@ -57,7 +57,7 @@ watch( ...@@ -57,7 +57,7 @@ watch(
formData.value = [] formData.value = []
// 2. val 非空,则加载数据 // 2. val 非空,则加载数据
if (!val) { if (!val) {
return; return
} }
try { try {
formLoading.value = true formLoading.value = true
......
...@@ -38,11 +38,11 @@ watch( ...@@ -38,11 +38,11 @@ watch(
id: undefined, id: undefined,
studentId: undefined, studentId: undefined,
name: undefined, name: undefined,
teacher: undefined, teacher: undefined
} }
// 2. val 非空,则加载数据 // 2. val 非空,则加载数据
if (!val) { if (!val) {
return; return
} }
try { try {
formLoading.value = true formLoading.value = true
......
...@@ -57,7 +57,7 @@ watch( ...@@ -57,7 +57,7 @@ watch(
formData.value = [] formData.value = []
// 2. val 非空,则加载数据 // 2. val 非空,则加载数据
if (!val) { if (!val) {
return; return
} }
try { try {
formLoading.value = true formLoading.value = true
......
...@@ -38,11 +38,11 @@ watch( ...@@ -38,11 +38,11 @@ watch(
id: undefined, id: undefined,
studentId: undefined, studentId: undefined,
name: undefined, name: undefined,
teacher: undefined, teacher: undefined
} }
// 2. val 非空,则加载数据 // 2. val 非空,则加载数据
if (!val) { if (!val) {
return; return
} }
try { try {
formLoading.value = true formLoading.value = true
......
...@@ -59,6 +59,21 @@ ...@@ -59,6 +59,21 @@
:formatter="fileSizeFormatter" :formatter="fileSizeFormatter"
/> />
<el-table-column label="文件类型" align="center" prop="type" width="180px" /> <el-table-column label="文件类型" align="center" prop="type" width="180px" />
<el-table-column label="文件内容" align="center" prop="url" width="110px">
<template #default="{ row }">
<el-image
v-if="row.type.includes('image')"
class="h-80px w-80px"
lazy
:src="row.url"
:preview-src-list="[row.url]"
preview-teleported
fit="cover"
/>
<el-link v-else-if="row.type.includes('pdf')" type="primary" :href="row.url" :underline="false" target="_blank">预览</el-link>
<el-link v-else type="primary" download :href="row.url" :underline="false" target="_blank">下载</el-link>
</template>
</el-table-column>
<el-table-column <el-table-column
label="上传时间" label="上传时间"
align="center" align="center"
...@@ -110,6 +125,7 @@ const queryParams = reactive({ ...@@ -110,6 +125,7 @@ const queryParams = reactive({
pageSize: 10, pageSize: 10,
name: undefined, name: undefined,
type: undefined, type: undefined,
path: undefined,
createTime: [] createTime: []
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
......
...@@ -101,6 +101,7 @@ ...@@ -101,6 +101,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { DICT_TYPE, getDictOptions } from '@/utils/dict' import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import * as FileConfigApi from '@/api/infra/fileConfig' import * as FileConfigApi from '@/api/infra/fileConfig'
import { FormRules } from 'element-plus'
defineOptions({ name: 'InfraFileConfigForm' }) defineOptions({ name: 'InfraFileConfigForm' })
...@@ -116,9 +117,9 @@ const formData = ref({ ...@@ -116,9 +117,9 @@ const formData = ref({
name: '', name: '',
storage: 0, storage: 0,
remark: '', remark: '',
config: {} config: {} as FileConfigApi.FileClientConfig
}) })
const formRules = reactive({ const formRules = reactive<FormRules>({
name: [{ required: true, message: '配置名不能为空', trigger: 'blur' }], name: [{ required: true, message: '配置名不能为空', trigger: 'blur' }],
storage: [{ required: true, message: '存储器不能为空', trigger: 'change' }], storage: [{ required: true, message: '存储器不能为空', trigger: 'change' }],
config: { config: {
...@@ -133,7 +134,7 @@ const formRules = reactive({ ...@@ -133,7 +134,7 @@ const formRules = reactive({
accessKey: [{ required: true, message: 'accessKey 不能为空', trigger: 'blur' }], accessKey: [{ required: true, message: 'accessKey 不能为空', trigger: 'blur' }],
accessSecret: [{ required: true, message: 'accessSecret 不能为空', trigger: 'blur' }], accessSecret: [{ required: true, message: 'accessSecret 不能为空', trigger: 'blur' }],
domain: [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }] domain: [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }]
} } as FormRules
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
...@@ -186,9 +187,9 @@ const resetForm = () => { ...@@ -186,9 +187,9 @@ const resetForm = () => {
formData.value = { formData.value = {
id: undefined, id: undefined,
name: '', name: '',
storage: 0, storage: undefined!,
remark: '', remark: '',
config: {} config: {} as FileConfigApi.FileClientConfig
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }
......
...@@ -51,7 +51,7 @@ defineOptions({ name: 'InfraJobDetail' }) ...@@ -51,7 +51,7 @@ defineOptions({ name: 'InfraJobDetail' })
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const detailLoading = ref(false) // 表单的加载中 const detailLoading = ref(false) // 表单的加载中
const detailData = ref({}) // 详情数据 const detailData = ref({} as JobApi.JobVO) // 详情数据
const nextTimes = ref([]) // 下一轮执行时间的数组 const nextTimes = ref([]) // 下一轮执行时间的数组
/** 打开弹窗 */ /** 打开弹窗 */
......
...@@ -62,7 +62,10 @@ const formData = ref({ ...@@ -62,7 +62,10 @@ const formData = ref({
name: '', name: '',
handlerName: '', handlerName: '',
handlerParam: '', handlerParam: '',
cronExpression: '' cronExpression: '',
retryCount: undefined,
retryInterval: undefined,
monitorTimeout: undefined
}) })
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }], name: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
...@@ -124,7 +127,10 @@ const resetForm = () => { ...@@ -124,7 +127,10 @@ const resetForm = () => {
name: '', name: '',
handlerName: '', handlerName: '',
handlerParam: '', handlerParam: '',
cronExpression: '' cronExpression: '',
retryCount: undefined,
retryInterval: undefined,
monitorTimeout: undefined
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }
......
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
> >
<Icon icon="ep:download" class="mr-5px" /> 导出 <Icon icon="ep:download" class="mr-5px" /> 导出
</el-button> </el-button>
<el-button type="info" plain @click="handleJobLog" v-hasPermi="['infra:job:query']"> <el-button type="info" plain @click="handleJobLog()" v-hasPermi="['infra:job:query']">
<Icon icon="ep:zoom-in" class="mr-5px" /> 执行日志 <Icon icon="ep:zoom-in" class="mr-5px" /> 执行日志
</el-button> </el-button>
</el-form-item> </el-form-item>
...@@ -292,8 +292,8 @@ const openDetail = (id: number) => { ...@@ -292,8 +292,8 @@ const openDetail = (id: number) => {
} }
/** 跳转执行日志 */ /** 跳转执行日志 */
const handleJobLog = (id: number) => { const handleJobLog = (id?: number) => {
if (id > 0) { if (id && id > 0) {
push('/job/job-log?id=' + id) push('/job/job-log?id=' + id)
} else { } else {
push('/job/job-log') push('/job/job-log')
......
...@@ -40,7 +40,7 @@ defineOptions({ name: 'JobLogDetail' }) ...@@ -40,7 +40,7 @@ defineOptions({ name: 'JobLogDetail' })
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const detailLoading = ref(false) // 表单的加载中 const detailLoading = ref(false) // 表单的加载中
const detailData = ref({}) // 详情数据 const detailData = ref({} as JobLogApi.JobLogVO) // 详情数据
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (id: number) => { const open = async (id: number) => {
......
...@@ -20,8 +20,12 @@ import { propTypes } from '@/utils/propTypes' ...@@ -20,8 +20,12 @@ import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'ProductCategorySelect' }) defineOptions({ name: 'ProductCategorySelect' })
const props = defineProps({ const props = defineProps({
modelValue: oneOfType([propTypes.number.def(undefined), propTypes.array.def([])]).def(undefined), // 选中的ID // 选中的ID
multiple: propTypes.bool.def(false) // 是否多选 modelValue: oneOfType<number | number[]>([Number, Array<Number>]),
// 是否多选
multiple: propTypes.bool.def(false),
// 上级品类的编号
parentId: propTypes.number.def(undefined)
}) })
/** 选中的分类 ID */ /** 选中的分类 ID */
...@@ -38,10 +42,10 @@ const selectCategoryId = computed({ ...@@ -38,10 +42,10 @@ const selectCategoryId = computed({
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
/** 初始化 **/ /** 初始化 **/
const categoryList = ref([]) // 分类树 const categoryList = ref<ProductCategoryApi.CategoryVO[]>([]) // 分类树
onMounted(async () => { onMounted(async () => {
// 获得分类树 // 获得分类树
const data = await ProductCategoryApi.getCategoryList({}) const data = await ProductCategoryApi.getCategoryList({ parentId: props.parentId })
categoryList.value = handleTree(data, 'id', 'parentId') categoryList.value = handleTree(data, 'id', 'parentId')
}) })
</script> </script>
...@@ -33,6 +33,8 @@ const message = useMessage() ...@@ -33,6 +33,8 @@ const message = useMessage()
const props = defineProps<{ type: UploadType }>() const props = defineProps<{ type: UploadType }>()
const accountId = inject<number>('accountId')
const fileList = ref<UploadUserFile[]>([]) const fileList = ref<UploadUserFile[]>([])
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'uploaded', v: void) (e: 'uploaded', v: void)
...@@ -41,7 +43,8 @@ const emit = defineEmits<{ ...@@ -41,7 +43,8 @@ const emit = defineEmits<{
const uploadData: UploadData = reactive({ const uploadData: UploadData = reactive({
type: UploadType.Image, type: UploadType.Image,
title: '', title: '',
introduction: '' introduction: '',
accountId: accountId!
}) })
/** 上传前检查 */ /** 上传前检查 */
......
...@@ -59,6 +59,8 @@ import { HEADERS, UploadData, UPLOAD_URL, UploadType, beforeVideoUpload } from ' ...@@ -59,6 +59,8 @@ import { HEADERS, UploadData, UPLOAD_URL, UploadType, beforeVideoUpload } from '
const message = useMessage() const message = useMessage()
const accountId = inject<number>('accountId')
const uploadRules: FormRules = { const uploadRules: FormRules = {
title: [{ required: true, message: '请输入标题', trigger: 'blur' }], title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }] introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }]
...@@ -89,7 +91,8 @@ const fileList = ref<UploadUserFile[]>([]) ...@@ -89,7 +91,8 @@ const fileList = ref<UploadUserFile[]>([])
const uploadData: UploadData = reactive({ const uploadData: UploadData = reactive({
type: UploadType.Video, type: UploadType.Video,
title: '', title: '',
introduction: '' introduction: '',
accountId: accountId!
}) })
const uploadFormRef = ref<FormInstance | null>(null) const uploadFormRef = ref<FormInstance | null>(null)
......
...@@ -9,6 +9,7 @@ interface UploadData { ...@@ -9,6 +9,7 @@ interface UploadData {
type: UploadType type: UploadType
title: string title: string
introduction: string introduction: string
accountId: number
} }
const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
......
...@@ -99,17 +99,22 @@ const type = ref<UploadType>(UploadType.Image) // 素材类型 ...@@ -99,17 +99,22 @@ const type = ref<UploadType>(UploadType.Image) // 素材类型
const loading = ref(false) // 遮罩层 const loading = ref(false) // 遮罩层
const list = ref<any[]>([]) // 总条数 const list = ref<any[]>([]) // 总条数
const total = ref(0) // 数据列表 const total = ref(0) // 数据列表
const accountId = ref(-1)
provide('accountId', accountId)
// 查询参数 // 查询参数
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
accountId: -1, accountId: accountId,
permanent: true permanent: true
}) })
const showCreateVideo = ref(false) // 是否新建视频的弹窗 const showCreateVideo = ref(false) // 是否新建视频的弹窗
/** 侦听公众号变化 **/ /** 侦听公众号变化 **/
const onAccountChanged = (id: number) => { const onAccountChanged = (id: number) => {
accountId.value = id
queryParams.accountId = id queryParams.accountId = id
queryParams.pageNo = 1 queryParams.pageNo = 1
getList() getList()
......
...@@ -63,6 +63,7 @@ import { defaultProps, handleTree } from '@/utils/tree' ...@@ -63,6 +63,7 @@ import { defaultProps, handleTree } from '@/utils/tree'
import * as DeptApi from '@/api/system/dept' import * as DeptApi from '@/api/system/dept'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
import { FormRules } from 'element-plus'
defineOptions({ name: 'SystemDeptForm' }) defineOptions({ name: 'SystemDeptForm' })
...@@ -84,7 +85,7 @@ const formData = ref({ ...@@ -84,7 +85,7 @@ const formData = ref({
email: undefined, email: undefined,
status: CommonStatusEnum.ENABLE status: CommonStatusEnum.ENABLE
}) })
const formRules = reactive({ const formRules = reactive<FormRules>({
parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }], parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
name: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }], name: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }], sort: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
......
...@@ -58,14 +58,14 @@ ...@@ -58,14 +58,14 @@
:default-expand-all="isExpandAll" :default-expand-all="isExpandAll"
v-if="refreshTable" v-if="refreshTable"
> >
<el-table-column prop="name" label="部门名称" width="260" /> <el-table-column prop="name" label="部门名称" />
<el-table-column prop="leader" label="负责人" width="120"> <el-table-column prop="leader" label="负责人">
<template #default="scope"> <template #default="scope">
{{ userList.find((user) => user.id === scope.row.leaderUserId)?.nickname }} {{ userList.find((user) => user.id === scope.row.leaderUserId)?.nickname }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="sort" label="排序" width="200" /> <el-table-column prop="sort" label="排序" />
<el-table-column prop="status" label="状态" width="100"> <el-table-column prop="status" label="状态">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template> </template>
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
width="180" width="180"
:formatter="dateFormatter" :formatter="dateFormatter"
/> />
<el-table-column label="操作" align="center" class-name="fixed-width"> <el-table-column label="操作" align="center">
<template #default="scope"> <template #default="scope">
<el-button <el-button
link link
...@@ -119,11 +119,10 @@ const { t } = useI18n() // 国际化 ...@@ -119,11 +119,10 @@ const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
const list = ref() // 列表的数据 const list = ref() // 列表的数据
const queryParams = reactive({ const queryParams = reactive({
title: '',
name: undefined,
status: undefined,
pageNo: 1, pageNo: 1,
pageSize: 100 pageSize: 100,
name: undefined,
status: undefined
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const isExpandAll = ref(true) // 是否展开,默认全部展开 const isExpandAll = ref(true) // 是否展开,默认全部展开
......
...@@ -34,7 +34,7 @@ defineOptions({ name: 'SystemLoginLogDetail' }) ...@@ -34,7 +34,7 @@ defineOptions({ name: 'SystemLoginLogDetail' })
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const detailLoading = ref(false) // 表单的加载中 const detailLoading = ref(false) // 表单的加载中
const detailData = ref({}) // 详情数据 const detailData = ref({} as LoginLogApi.LoginLogVO) // 详情数据
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (data: LoginLogApi.LoginLogVO) => { const open = async (data: LoginLogApi.LoginLogVO) => {
......
...@@ -49,7 +49,7 @@ defineOptions({ name: 'SystemNotifyMessageDetail' }) ...@@ -49,7 +49,7 @@ defineOptions({ name: 'SystemNotifyMessageDetail' })
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const detailLoading = ref(false) // 表单的加载中 const detailLoading = ref(false) // 表单的加载中
const detailData = ref() // 详情数据 const detailData = ref({} as NotifyMessageApi.NotifyMessageVO) // 详情数据
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (data: NotifyMessageApi.NotifyMessageVO) => { const open = async (data: NotifyMessageApi.NotifyMessageVO) => {
......
...@@ -31,7 +31,7 @@ defineOptions({ name: 'MyNotifyMessageDetailDetail' }) ...@@ -31,7 +31,7 @@ defineOptions({ name: 'MyNotifyMessageDetailDetail' })
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const detailLoading = ref(false) // 表单的加载中 const detailLoading = ref(false) // 表单的加载中
const detailData = ref() // 详情数据 const detailData = ref({} as NotifyMessageApi.NotifyMessageVO) // 详情数据
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (data: NotifyMessageApi.NotifyMessageVO) => { const open = async (data: NotifyMessageApi.NotifyMessageVO) => {
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
<el-radio <el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value" :key="dict.value"
:label="dict.value as string" :label="dict.value"
> >
{{ dict.label }} {{ dict.label }}
</el-radio> </el-radio>
...@@ -61,12 +61,12 @@ const dialogTitle = ref('') // 弹窗的标题 ...@@ -61,12 +61,12 @@ const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型 const formType = ref('') // 表单的类型
const formData = ref<NotifyTemplateApi.NotifyTemplateVO>({ const formData = ref<NotifyTemplateApi.NotifyTemplateVO>({
id: null, id: undefined,
name: '', name: '',
nickname: '', nickname: '',
code: '', code: '',
content: '', content: '',
type: null, type: undefined,
params: '', params: '',
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
remark: '' remark: ''
...@@ -126,12 +126,12 @@ const submitForm = async () => { ...@@ -126,12 +126,12 @@ const submitForm = async () => {
/** 重置表单 */ /** 重置表单 */
const resetForm = () => { const resetForm = () => {
formData.value = { formData.value = {
id: null, id: undefined,
name: '', name: '',
nickname: '', nickname: '',
code: '', code: '',
content: '', content: '',
type: null, type: undefined,
params: '', params: '',
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
remark: '' remark: ''
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<el-radio <el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)" v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)"
:key="dict.value" :key="dict.value"
:label="dict.value as number" :label="dict.value"
> >
{{ dict.label }} {{ dict.label }}
</el-radio> </el-radio>
...@@ -71,7 +71,7 @@ const formLoading = ref(false) // 表单的加载中:1)修改时的数据加 ...@@ -71,7 +71,7 @@ const formLoading = ref(false) // 表单的加载中:1)修改时的数据加
const formData = ref({ const formData = ref({
content: '', content: '',
params: {}, params: {},
userId: null, userId: undefined,
userType: 1, userType: 1,
templateCode: '', templateCode: '',
templateParams: new Map() templateParams: new Map()
......
...@@ -137,7 +137,7 @@ const queryParams = reactive({ ...@@ -137,7 +137,7 @@ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
name: null, name: null,
status: null status: undefined
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
......
...@@ -115,7 +115,7 @@ const queryParams = reactive({ ...@@ -115,7 +115,7 @@ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
userId: null, userId: null,
userType: null, userType: undefined,
clientId: null clientId: null
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
......
...@@ -65,7 +65,7 @@ defineOptions({ name: 'SystemOperateLogDetail' }) ...@@ -65,7 +65,7 @@ defineOptions({ name: 'SystemOperateLogDetail' })
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const detailLoading = ref(false) // 表单的加载中 const detailLoading = ref(false) // 表单的加载中
const detailData = ref() // 详情数据 const detailData = ref({} as OperateLogApi.OperateLogVO) // 详情数据
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (data: OperateLogApi.OperateLogVO) => { const open = async (data: OperateLogApi.OperateLogVO) => {
......
...@@ -50,8 +50,8 @@ ...@@ -50,8 +50,8 @@
clearable clearable
class="!w-240px" class="!w-240px"
> >
<el-option :key="true" label="成功" :value="true" /> <el-option key="true" label="成功" :value="true" />
<el-option :key="false" label="失败" :value="false" /> <el-option key="false" label="失败" :value="false" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="操作时间" prop="startTime"> <el-form-item label="操作时间" prop="startTime">
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
<el-radio <el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value" :key="dict.value"
:label="parseInt(dict.value as string)" :label="dict.value"
> >
{{ dict.label }} {{ dict.label }}
</el-radio> </el-radio>
...@@ -79,15 +79,15 @@ const dialogTitle = ref('') // 弹窗的标题 ...@@ -79,15 +79,15 @@ const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型 const formType = ref('') // 表单的类型
const formData = ref<SmsTemplateApi.SmsTemplateVO>({ const formData = ref<SmsTemplateApi.SmsTemplateVO>({
id: null, id: undefined,
type: null, type: undefined,
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
code: '', code: '',
name: '', name: '',
content: '', content: '',
remark: '', remark: '',
apiTemplateId: '', apiTemplateId: '',
channelId: null channelId: undefined
}) })
const formRules = reactive({ const formRules = reactive({
type: [{ required: true, message: '短信类型不能为空', trigger: 'change' }], type: [{ required: true, message: '短信类型不能为空', trigger: 'change' }],
...@@ -148,15 +148,15 @@ const submitForm = async () => { ...@@ -148,15 +148,15 @@ const submitForm = async () => {
/** 重置表单 */ /** 重置表单 */
const resetForm = () => { const resetForm = () => {
formData.value = { formData.value = {
id: null, id: undefined,
type: null, type: undefined,
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
code: '', code: '',
name: '', name: '',
content: '', content: '',
remark: '', remark: '',
apiTemplateId: '', apiTemplateId: '',
channelId: null channelId: undefined
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }
......
...@@ -232,12 +232,12 @@ const queryFormRef = ref() // 搜索的表单 ...@@ -232,12 +232,12 @@ const queryFormRef = ref() // 搜索的表单
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
type: null, type: undefined,
status: null, status: undefined,
code: '', code: '',
content: '', content: '',
apiTemplateId: '', apiTemplateId: '',
channelId: null, channelId: undefined,
createTime: [] createTime: []
}) })
const exportLoading = ref(false) // 导出的加载中 const exportLoading = ref(false) // 导出的加载中
......
...@@ -169,11 +169,11 @@ const list = ref([]) // 列表的数据 ...@@ -169,11 +169,11 @@ const list = ref([]) // 列表的数据
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
name: null, name: undefined,
socialType: null, socialType: undefined,
userType: null, userType: undefined,
clientId: null, clientId: undefined,
status: null status: undefined
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
......
...@@ -44,7 +44,7 @@ import * as SocialUserApi from '@/api/system/social/user' ...@@ -44,7 +44,7 @@ import * as SocialUserApi from '@/api/system/social/user'
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const detailLoading = ref(false) // 表单的加载中 const detailLoading = ref(false) // 表单的加载中
const detailData = ref({}) // 详情数据 const detailData = ref({} as SocialUserApi.SocialUserVO) // 详情数据
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (id: number) => { const open = async (id: number) => {
......
...@@ -131,9 +131,6 @@ import { createImageViewer } from '@/components/ImageViewer' ...@@ -131,9 +131,6 @@ import { createImageViewer } from '@/components/ImageViewer'
defineOptions({ name: 'SocialUser' }) defineOptions({ name: 'SocialUser' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数 const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据 const list = ref([]) // 列表的数据
......
...@@ -98,7 +98,10 @@ const formData = ref({ ...@@ -98,7 +98,10 @@ const formData = ref({
accountCount: undefined, accountCount: undefined,
expireTime: undefined, expireTime: undefined,
website: undefined, website: undefined,
status: CommonStatusEnum.ENABLE status: CommonStatusEnum.ENABLE,
// 新增专属
username: undefined,
password: undefined
}) })
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: '租户名不能为空', trigger: 'blur' }], name: [{ required: true, message: '租户名不能为空', trigger: 'blur' }],
...@@ -112,7 +115,7 @@ const formRules = reactive({ ...@@ -112,7 +115,7 @@ const formRules = reactive({
password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }] password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }]
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
const packageList = ref([]) // 租户套餐 const packageList = ref([] as TenantPackageApi.TenantPackageVO[]) // 租户套餐
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number) => {
...@@ -171,7 +174,9 @@ const resetForm = () => { ...@@ -171,7 +174,9 @@ const resetForm = () => {
accountCount: undefined, accountCount: undefined,
expireTime: undefined, expireTime: undefined,
website: undefined, website: undefined,
status: CommonStatusEnum.ENABLE status: CommonStatusEnum.ENABLE,
username: undefined,
password: undefined
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }
......
...@@ -198,7 +198,7 @@ const queryParams = reactive({ ...@@ -198,7 +198,7 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中 const exportLoading = ref(false) // 导出的加载中
const packageList = ref([]) //租户套餐列表 const packageList = ref([] as TenantPackageApi.TenantPackageVO[]) //租户套餐列表
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
<el-radio <el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value" :key="dict.value"
:label="parseInt(dict.value)" :label="dict.value"
> >
{{ dict.label }} {{ dict.label }}
</el-radio> </el-radio>
...@@ -113,7 +113,7 @@ const open = async (type: string, id?: number) => { ...@@ -113,7 +113,7 @@ const open = async (type: string, id?: number) => {
formData.value = res formData.value = res
// 设置选中 // 设置选中
res.menuIds.forEach((menuId: number) => { res.menuIds.forEach((menuId: number) => {
treeRef.value.setChecked(menuId, true, false) treeRef.value!.setChecked(menuId, true, false)
}) })
} finally { } finally {
formLoading.value = false formLoading.value = false
...@@ -134,8 +134,8 @@ const submitForm = async () => { ...@@ -134,8 +134,8 @@ const submitForm = async () => {
try { try {
const data = formData.value as unknown as TenantPackageApi.TenantPackageVO const data = formData.value as unknown as TenantPackageApi.TenantPackageVO
data.menuIds = [ data.menuIds = [
...(treeRef.value.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点 ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点
...(treeRef.value.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点 ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点
] ]
if (formType.value === 'create') { if (formType.value === 'create') {
await TenantPackageApi.createTenantPackage(data) await TenantPackageApi.createTenantPackage(data)
...@@ -171,7 +171,7 @@ const resetForm = () => { ...@@ -171,7 +171,7 @@ const resetForm = () => {
/** 全选/全不选 */ /** 全选/全不选 */
const handleCheckedTreeNodeAll = () => { const handleCheckedTreeNodeAll = () => {
treeRef.value.setCheckedNodes(treeNodeAll.value ? menuOptions.value : []) treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : [])
} }
/** 展开/折叠全部 */ /** 展开/折叠全部 */
......
...@@ -123,9 +123,9 @@ const list = ref([]) // 列表的数据 ...@@ -123,9 +123,9 @@ const list = ref([]) // 列表的数据
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
name: null, name: undefined,
status: null, status: undefined,
remark: null, remark: undefined,
createTime: [] createTime: []
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
...@@ -134,7 +134,7 @@ const queryFormRef = ref() // 搜索的表单 ...@@ -134,7 +134,7 @@ const queryFormRef = ref() // 搜索的表单
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {
const data = await TenantPackageApi.getTenantPackagePage(queryParams.value) const data = await TenantPackageApi.getTenantPackagePage(queryParams)
list.value = data.list list.value = data.list
total.value = data.total total.value = data.total
} finally { } finally {
......
...@@ -32,13 +32,13 @@ const message = useMessage() // 消息弹窗 ...@@ -32,13 +32,13 @@ const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formData = ref({ const formData = ref({
id: undefined, id: -1,
nickname: '', nickname: '',
username: '', username: '',
roleIds: [] roleIds: []
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
const roleList = ref([]) // 角色的列表 const roleList = ref([] as RoleApi.RoleVO[]) // 角色的列表
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (row: UserApi.UserVO) => { const open = async (row: UserApi.UserVO) => {
...@@ -86,7 +86,7 @@ const submitForm = async () => { ...@@ -86,7 +86,7 @@ const submitForm = async () => {
/** 重置表单 */ /** 重置表单 */
const resetForm = () => { const resetForm = () => {
formData.value = { formData.value = {
id: undefined, id: -1,
nickname: '', nickname: '',
username: '', username: '',
roleIds: [] roleIds: []
......
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
v-for="item in postList" v-for="item in postList"
:key="item.id" :key="item.id"
:label="item.name" :label="item.name"
:value="item.id" :value="item.id!"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
...@@ -102,6 +102,7 @@ import { defaultProps, handleTree } from '@/utils/tree' ...@@ -102,6 +102,7 @@ import { defaultProps, handleTree } from '@/utils/tree'
import * as PostApi from '@/api/system/post' import * as PostApi from '@/api/system/post'
import * as DeptApi from '@/api/system/dept' import * as DeptApi from '@/api/system/dept'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import { FormRules } from 'element-plus'
defineOptions({ name: 'SystemUserForm' }) defineOptions({ name: 'SystemUserForm' })
...@@ -126,7 +127,7 @@ const formData = ref({ ...@@ -126,7 +127,7 @@ const formData = ref({
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
roleIds: [] roleIds: []
}) })
const formRules = reactive({ const formRules = reactive<FormRules>({
username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }], username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }], password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }],
...@@ -147,7 +148,7 @@ const formRules = reactive({ ...@@ -147,7 +148,7 @@ const formRules = reactive({
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
const deptList = ref<Tree[]>([]) // 树形结构 const deptList = ref<Tree[]>([]) // 树形结构
const postList = ref([]) // 岗位列表 const postList = ref([] as PostApi.PostVO[]) // 岗位列表
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number) => {
......
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
label="部门" label="部门"
align="center" align="center"
key="deptName" key="deptName"
prop="dept.name" prop="deptName"
:show-overflow-tooltip="true" :show-overflow-tooltip="true"
/> />
<el-table-column label="手机号码" align="center" prop="mobile" width="120" /> <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
......
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 sign in to comment