Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
owen 2023-12-01 00:39:55 +08:00
commit 08be359578
54 changed files with 2241 additions and 1131 deletions

View File

@ -4,10 +4,10 @@ NODE_ENV=development
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://localhost:48080'
VITE_BASE_URL='http://127.0.0.1:48080'
# 上传路径
VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
VITE_UPLOAD_URL='http://127.0.0.1:48080/admin-api/infra/file/upload'
# 接口前缀
VITE_API_BASEPATH=/dev-api

View File

@ -0,0 +1,57 @@
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 })
}

View File

@ -0,0 +1,48 @@
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 })
}

View File

@ -1,10 +1,3 @@
/*
* @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'
export interface ContactVO {
@ -22,44 +15,53 @@ export interface ContactVO {
id: number
parentId: number
qq: number
webchat: string
wechat: string
sex: number
policyMakers: boolean
master: boolean
creatorName: string
updateTime?: Date
createTime?: Date
customerName: string
areaName: string
ownerUserName: string
}
// 查询crm联系人列表
// 查询 CRM 联系人列表
export const getContactPage = async (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) => {
return await request.get({ url: `/crm/contact/get?id=` + id })
}
// 新增crm联系人
// 新增 CRM 联系人
export const createContact = async (data: ContactVO) => {
return await request.post({ url: `/crm/contact/create`, data })
}
// 修改crm联系人
// 修改 CRM 联系人
export const updateContact = async (data: ContactVO) => {
return await request.put({ url: `/crm/contact/update`, data })
}
// 删除crm联系人
// 删除 CRM 联系人
export const deleteContact = async (id: number) => {
return await request.delete({ url: `/crm/contact/delete?id=` + id })
}
// 导出crm联系人 Excel
// 导出 CRM 联系人 Excel
export const exportContact = async (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` })
}

View File

@ -22,32 +22,37 @@ export interface ContractVO {
remark: string
}
// 查询合同列表
// 查询 CRM 合同列表
export const getContractPage = async (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) => {
return await request.get({ url: `/crm/contract/get?id=` + id })
}
// 新增合同
// 新增 CRM 合同
export const createContract = async (data: ContractVO) => {
return await request.post({ url: `/crm/contract/create`, data })
}
// 修改合同
// 修改 CRM 合同
export const updateContract = async (data: ContractVO) => {
return await request.put({ url: `/crm/contract/update`, data })
}
// 删除合同
// 删除 CRM 合同
export const deleteContract = async (id: number) => {
return await request.delete({ url: `/crm/contract/delete?id=` + id })
}
// 导出合同 Excel
// 导出 CRM 合同 Excel
export const exportContract = async (params) => {
return await request.download({ url: `/crm/contract/export-excel`, params })
}

View File

@ -62,3 +62,8 @@ export const deleteCustomer = async (id: number) => {
export const exportCustomer = async (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` })
}

View File

@ -9,6 +9,20 @@ export interface CustomerLimitConfigVO {
dealCountEnabled?: boolean
}
/**
*
*/
export enum LimitConfType {
/**
*
*/
CUSTOMER_QUANTITY_LIMIT = 1,
/**
*
*/
CUSTOMER_LOCK_LIMIT = 2
}
// 查询客户限制配置列表
export const getCustomerLimitConfigPage = async (params) => {
return await request.get({ url: `/crm/customer-limit-config/page`, params })

View File

@ -1,4 +1,5 @@
import request from '@/config/axios'
import { ConfigVO } from '@/api/infra/config'
export interface CustomerPoolConfigVO {
enabled?: boolean
@ -14,6 +15,6 @@ export const getCustomerPoolConfig = async () => {
}
// 更新客户公海规则设置
export const updateCustomerPoolConfig = async (data: ConfigVO) => {
return await request.put({ url: `/crm/customer-pool-config/update`, data })
export const saveCustomerPoolConfig = async (data: ConfigVO) => {
return await request.put({ url: `/crm/customer-pool-config/save`, data })
}

View File

@ -6,42 +6,66 @@ export interface PermissionVO {
bizType: number | undefined // Crm 类型
bizId: number | undefined // Crm 类型数据编号
level: number | undefined // 权限级别
deptName?: string // 部门名称 // 岗位名称数组 TODO @puhui999数组
deptName?: string // 部门名称
nickname?: string // 用户昵称
postNames?: string // 岗位名称数组 TODO @puhui999数组
postNames?: string[] // 岗位名称数组
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) => {
return await request.get({ url: `/crm/permission/list`, params })
}
// 新增团队成员
// 创建数据权限(新增团队成员
export const createPermission = async (data: PermissionVO) => {
return await request.post({ url: `/crm/permission/add`, data })
return await request.post({ url: `/crm/permission/create`, data })
}
// 修改团队成员权限级别
// 编辑数据权限(修改团队成员权限级别
export const updatePermission = async (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 })
}
// 退出团队
export const quitTeam = async (id) => {
// 删除自己的数据权限(退出团队
export const deleteSelfPermission = async (id) => {
return await request.delete({ url: '/crm/permission/quit-team?id=' + id })
}
// TODO @puhui999调整下位置
// 领取公海数据
export const receive = async (data: { bizType: number; bizId: number }) => {
return await request.put({ url: `/crm/permission/receive`, data })
}
// TODO @puhui999调整下位置
// 数据放入公海
export const putPool = async (data: { bizType: number; bizId: number }) => {
return await request.put({ url: `/crm/permission/put-pool`, data })

View File

@ -0,0 +1,12 @@
import request from '@/config/axios'
export interface Favorite {
id?: number
userId?: string // 用户编号
spuId?: number | null // 商品 SPU 编号
}
// 获得 ProductFavorite 列表
export const getFavoritePage = (params: PageParam) => {
return request.get({ url: '/product/favorite/page', params })
}

View File

@ -26,6 +26,6 @@ export const getUserPage = (query) => {
// 同步公众号粉丝
export const syncUser = (accountId) => {
return request.post({
url: '/mp/tag/sync?accountId=' + accountId
url: '/mp/user/sync?accountId=' + accountId
})
}

View File

@ -35,3 +35,8 @@ export const updateNotice = (data: NoticeVO) => {
export const deleteNotice = (id: number) => {
return request.delete({ url: '/system/notice/delete?id=' + id })
}
// 推送公告
export const pushNotice = (id: number) => {
return request.post({ url: '/system/notice/push?id=' + id })
}

View File

@ -15,8 +15,6 @@ export interface SmsLogVO {
userType: number | null
sendStatus: number | null
sendTime: Date | null
sendCode: number | null
sendMsg: string
apiSendCode: string
apiSendMsg: string
apiRequestId: string

View File

@ -1,5 +1,5 @@
<template>
<ElDialog v-model="showSearch" :show-close="false" title="菜单搜索">
<ElDialog v-if="isModal" v-model="showSearch" :show-close="false" title="菜单搜索">
<el-select
filterable
:reserve-keyword="false"
@ -17,11 +17,34 @@
/>
</el-select>
</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>
<script lang="ts" setup>
defineProps({
isModal: {
type: Boolean,
default: true
}
})
const router = useRouter() //
const showSearch = ref(false) //
const showTopSearch = ref(false) //
const value: Ref = ref('') //
const routers = router.getRoutes() //
@ -50,14 +73,21 @@ function remoteMethod(data) {
function handleChange(path) {
router.push({ path })
hiddenTopSearch();
}
function hiddenTopSearch() {
showTopSearch.value = false
}
onMounted(() => {
window.addEventListener('keydown', listenKey)
window.addEventListener('click', hiddenTopSearch)
})
onUnmounted(() => {
window.removeEventListener('keydown', listenKey)
window.removeEventListener('click', hiddenTopSearch)
})
// ctrl + k

View File

@ -7,6 +7,7 @@ import { Screenfull } from '@/layout/components/Screenfull'
import { Breadcrumb } from '@/layout/components/Breadcrumb'
import { SizeDropdown } from '@/layout/components/SizeDropdown'
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
import RouterSearch from '@/components/RouterSearch/index.vue'
import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign'
@ -25,6 +26,9 @@ const hamburger = computed(() => appStore.getHamburger)
//
const screenfull = computed(() => appStore.getScreenfull)
//
const search = computed(() => appStore.search)
//
const size = computed(() => appStore.getSize)
@ -61,6 +65,7 @@ export default defineComponent({
{screenfull.value ? (
<Screenfull class="custom-hover" color="var(--top-header-text-color)"></Screenfull>
) : undefined}
{search.value ? (<RouterSearch isModal={false} />) : undefined}
{size.value ? (
<SizeDropdown class="custom-hover" color="var(--top-header-text-color)"></SizeDropdown>
) : undefined}

View File

@ -437,5 +437,6 @@ export default {
btn_zoom_in: '放大',
btn_zoom_out: '缩小',
preview: '预览'
}
},
'OAuth 2.0': 'OAuth 2.0' // 避免菜单名是 OAuth 2.0 时,一直 warn 报错
}

View File

@ -16,6 +16,7 @@ interface AppState {
uniqueOpened: boolean
hamburger: boolean
screenfull: boolean
search: boolean
size: boolean
locale: boolean
message: boolean
@ -52,6 +53,7 @@ export const useAppStore = defineStore('app', {
uniqueOpened: true, // 是否只保持一个子菜单的展开
hamburger: true, // 折叠图标
screenfull: true, // 全屏图标
search: true, // 搜索图标
size: true, // 尺寸图标
locale: true, // 多语言图标
message: true, // 消息图标

View File

@ -190,10 +190,12 @@ export enum DICT_TYPE {
PROMOTION_BANNER_POSITION = 'promotion_banner_position', // banner 定位
// ========== CRM - 客户管理模块 ==========
CRM_RECEIVABLE_CHECK_STATUS = 'crm_receivable_check_status',
CRM_AUDIT_STATUS = 'crm_audit_status', // CRM 审批状态
CRM_BIZ_TYPE = 'crm_biz_type', // CRM 业务类型
CRM_RETURN_TYPE = 'crm_return_type',
CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry',
CRM_CUSTOMER_LEVEL = 'crm_customer_level',
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 数据权限的级别
}

View File

@ -0,0 +1,279 @@
<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) // 12
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>

View File

@ -0,0 +1,107 @@
<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>

View File

@ -0,0 +1,207 @@
<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>

View File

@ -0,0 +1,167 @@
<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) // 12
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>

View File

@ -0,0 +1,171 @@
<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>

View File

@ -10,9 +10,16 @@
<el-form-item label="线索名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入线索名称" />
</el-form-item>
<!-- TODO wanwan 客户选择 -->
<!-- TODO 芋艿后续客户的选择 -->
<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 label="下次联系时间" prop="contactNextTime">
<el-date-picker
@ -47,6 +54,7 @@
</template>
<script setup lang="ts">
import * as ClueApi from '@/api/crm/clue'
import * as CustomerApi from '@/api/crm/customer'
const { t } = useI18n() //
const message = useMessage() //
@ -55,6 +63,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const customerList = ref([]) //
const formData = ref({
id: undefined,
name: undefined,
@ -79,6 +88,12 @@ const open = async (type: string, id?: number) => {
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
const customerData = await CustomerApi.getCustomerPage({
pageNo: 1,
pageSize: 100,
pool: false
})
customerList.value = customerData.list
//
if (id) {
formLoading.value = true

View File

@ -1,156 +0,0 @@
<template>
<!-- 操作栏 -->
<el-row justify="end">
<el-button type="primary" @click="handleAdd">
<Icon class="mr-5px" icon="ep:plus" />
新增
</el-button>
<el-button @click="handleEdit">
<Icon class="mr-5px" icon="ep:edit" />
编辑
</el-button>
<el-button @click="handleRemove">
<Icon class="mr-5px" icon="ep:delete" />
移除
</el-button>
<el-button type="danger" @click="handleQuit"> 退出团队</el-button>
</el-row>
<!-- 团队成员展示 -->
<el-table
v-loading="loading"
:data="list"
:show-overflow-tooltip="true"
:stripe="true"
class="mt-20px"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column align="center" label="姓名" prop="nickname" />
<el-table-column align="center" label="部门" prop="deptName" />
<el-table-column align="center" label="岗位" prop="postNames" />
<el-table-column align="center" label="权限级别" prop="level">
<template #default="{ row }">
<el-tag>{{ getLevelName(row.level) }}</el-tag>
</template>
</el-table-column>
<el-table-column :formatter="dateFormatter" align="center" label="加入时间" prop="createTime" />
</el-table>
<CrmPermissionForm ref="crmPermissionFormRef" />
</template>
<script lang="ts" setup>
// TODO @puhui999 CrmPermissionList
import { dateFormatter } from '@/utils/formatTime'
import { ElTable } from 'element-plus'
import * as PermissionApi from '@/api/crm/permission'
import { useUserStoreWithOut } from '@/store/modules/user'
import CrmPermissionForm from './CrmPermissionForm.vue'
import { CrmPermissionLevelEnum } from './index'
defineOptions({ name: 'CrmTeam' })
const message = useMessage() //
const props = defineProps<{
bizType: number
bizId: number
}>()
const loading = ref(true) //
const list = ref<PermissionApi.PermissionVO[]>([
// TODO
{
id: 1, //
userId: 1, //
bizType: 1, // Crm
bizId: 1, // Crm
level: 1, //
deptName: '研发部门', //
nickname: '芋道源码', //
postNames: '全栈开发工程师', //
createTime: new Date()
}
]) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await PermissionApi.getPermissionList({
bizType: props.bizType,
bizId: props.bizId
})
list.value = data
} finally {
loading.value = false
}
}
// TODO @puhui999
/**
* 获得权限级别名称
* @param level 权限级别
*/
const getLevelName = computed(() => (level: number) => {
switch (level) {
case CrmPermissionLevelEnum.OWNER:
return '负责人'
case CrmPermissionLevelEnum.READ:
return '只读'
case CrmPermissionLevelEnum.WRITE:
return '读写'
default:
break
}
})
// TODO @puhui999
const multipleSelection = ref<PermissionApi.PermissionVO[]>([])
const handleSelectionChange = (val: PermissionApi.PermissionVO[]) => {
multipleSelection.value = val
}
// TODO @puhui999 index.vue
const crmPermissionFormRef = ref<InstanceType<typeof CrmPermissionForm>>()
const handleEdit = () => {
if (multipleSelection.value?.length === 0) {
message.warning('请先选择团队成员后操作!')
return
}
const ids = multipleSelection.value?.map((item) => item.id)
crmPermissionFormRef.value?.open('update', props.bizType, props.bizId, ids)
}
const handleRemove = async () => {
if (multipleSelection.value?.length === 0) {
message.warning('请先选择团队成员后操作!')
return
}
await message.delConfirm()
const ids = multipleSelection.value?.map((item) => item.id)
await PermissionApi.deletePermission({
bizType: props.bizType,
bizId: props.bizId,
ids
})
}
const handleAdd = () => {
crmPermissionFormRef.value?.open('create', props.bizType, props.bizId)
}
const userStore = useUserStoreWithOut()
const handleQuit = async () => {
const permission = list.value.find(
(item) => item.userId === userStore.getUser.id && item.level === CrmPermissionLevelEnum.OWNER
)
if (permission) {
message.warning('负责人不能退出团队!')
return
}
const userPermission = list.value.find((item) => item.userId === userStore.getUser.id)
await PermissionApi.quitTeam(userPermission?.id)
}
watch(
() => props.bizId,
() => {
getList()
},
{ immediate: true, deep: true }
)
</script>

View File

@ -1,17 +0,0 @@
import CrmTeam from './CrmTeamList.vue'
enum CrmBizTypeEnum {
CRM_LEADS = 1, // 线索
CRM_CUSTOMER = 2, // 客户
CRM_CONTACTS = 3, // 联系人
CRM_BUSINESS = 5, // 商机
CRM_CONTRACT = 6 // 合同
}
enum CrmPermissionLevelEnum {
OWNER = 1, // 负责人
READ = 2, // 读
WRITE = 3 // 写
}
export { CrmTeam, CrmBizTypeEnum, CrmPermissionLevelEnum }

View File

@ -57,11 +57,11 @@
</template>
<script setup lang="ts">
import * as CustomerLimitConfigApi from '@/api/crm/customerLimitConfig'
import { LimitConfType } from '@/views/crm/customerLimitConfig/customerLimitConf'
import * as DeptApi from '@/api/system/dept'
import { defaultProps, handleTree } from '@/utils/tree'
import * as UserApi from '@/api/system/user'
import { cloneDeep } from 'lodash-es'
import { LimitConfType } from '@/api/crm/customerLimitConfig'
const { t } = useI18n() //
const message = useMessage() //

View File

@ -19,8 +19,16 @@
>
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="规则类型" align="center" prop="type" />
<el-table-column label="规则适用人群" align="center" prop="userNames" />
<el-table-column label="规则适用部门" align="center" prop="deptNames" />
<el-table-column
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
:label="
confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT ? '拥有客户数上限' : '锁定客户数上限'
@ -80,11 +88,11 @@
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import * as CustomerLimitConfigApi from '@/api/crm/customerLimitConfig'
import CustomerLimitConfigForm from '@/views/crm/customerLimitConfig/CustomerLimitConfigForm.vue'
import { LimitConfType } from '@/views/crm/customerLimitConfig/customerLimitConf'
import CustomerLimitConfigForm from '@/views/crm/config/customerLimitConfig/CustomerLimitConfigForm.vue'
import { DICT_TYPE } from '@/utils/dict'
import { LimitConfType } from '@/api/crm/customerLimitConfig'
defineOptions({ name: 'CustomerLimitConfDetails' })
defineOptions({ name: 'CustomerLimitConfigList' })
const message = useMessage() //
const { t } = useI18n() //

View File

@ -0,0 +1,19 @@
<template>
<!-- 列表 -->
<ContentWrap>
<el-tabs>
<el-tab-pane label="拥有客户数限制">
<CustomerLimitConfigList :confType="LimitConfType.CUSTOMER_QUANTITY_LIMIT" />
</el-tab-pane>
<el-tab-pane label="锁定客户数限制">
<CustomerLimitConfigList :confType="LimitConfType.CUSTOMER_LOCK_LIMIT" />
</el-tab-pane>
</el-tabs>
</ContentWrap>
</template>
<script setup lang="ts">
import CustomerLimitConfigList from '@/views/crm/config/customerLimitConfig/CustomerLimitConfigList.vue'
import { LimitConfType } from '@/api/crm/customerLimitConfig'
defineOptions({ name: 'CrmCustomerLimitConfig' })
</script>

View File

@ -23,7 +23,7 @@
</template>
<!-- 表单 -->
<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="true" size="large">启用</el-radio>
</el-radio-group>
@ -36,7 +36,11 @@
天未成交
</el-form-item>
<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="true" size="large">提醒</el-radio>
</el-radio-group>
@ -52,11 +56,10 @@
</ContentWrap>
</template>
<script setup lang="ts">
import * as CustomerPoolConfApi from '@/api/crm/customerPoolConf'
import * as CustomerPoolConfigApi from '@/api/crm/customerPoolConfig'
import { CardTitle } from '@/components/Card'
// TODO @wanwanCustomerPoolConf = CustomerPoolConfig crm config customerPoolConfig customerLimitConfig
defineOptions({ name: 'CustomerPoolConf' })
defineOptions({ name: 'CrmCustomerPoolConfig' })
const message = useMessage() //
const { t } = useI18n() //
@ -78,7 +81,7 @@ const formRef = ref() // 表单 Ref
const getConfig = async () => {
try {
formLoading.value = true
const data = await CustomerPoolConfApi.getCustomerPoolConfig()
const data = await CustomerPoolConfigApi.getCustomerPoolConfig()
if (data === null) {
return
}
@ -97,8 +100,8 @@ const onSubmit = async () => {
//
formLoading.value = true
try {
const data = formData.value as unknown as CustomerPoolConfApi.CustomerPoolConfigVO
await CustomerPoolConfApi.updateCustomerPoolConfig(data)
const data = formData.value as unknown as CustomerPoolConfigApi.CustomerPoolConfigVO
await CustomerPoolConfigApi.saveCustomerPoolConfig(data)
message.success(t('common.updateSuccess'))
await getConfig()
formLoading.value = false
@ -107,27 +110,22 @@ const onSubmit = async () => {
}
}
// TODO @wanwanel-radio-group
watch(
() => formData.value.enabled,
(val: boolean) => {
if (!val) {
formData.value.contactExpireDays = undefined
formData.value.dealExpireDays = undefined
formData.value.notifyEnabled = false
formData.value.notifyDays = undefined
}
/** 更改客户公海规则设置 */
const changeEnable = () => {
if (!formData.value.enabled) {
formData.value.contactExpireDays = undefined
formData.value.dealExpireDays = undefined
formData.value.notifyEnabled = false
formData.value.notifyDays = undefined
}
)
// TODO @wanwanel-radio-group
watch(
() => formData.value.notifyEnabled,
(val: boolean) => {
if (!val) {
formData.value.notifyDays = undefined
}
}
/** 更改提前提醒设置 */
const changeNotifyEnable = () => {
if (!formData.value.notifyEnabled) {
formData.value.notifyDays = undefined
}
)
}
onMounted(() => {
getConfig()

View File

@ -1,170 +1,194 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" :width="800">
<Dialog :title="dialogTitle" v-model="dialogVisible" :width="820">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="130px"
label-width="110px"
v-loading="formLoading"
:inline="true"
>
<el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="负责人" prop="ownerUserId">
<el-select
v-model="ownerUserList"
placeholder="请选择负责人"
multiple
value-key="id"
lable-key="nickname"
@click="openOwerForm('open')"
>
<el-option
v-for="item in ownerUserList"
:key="item.id"
:label="item.nickname"
:value="item"
/>
</el-select>
</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="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-row :gutter="20">
<el-col :span="12">
<el-form-item label="姓名" prop="name">
<el-input input-style="width:190px;" v-model="formData.name" placeholder="请输入姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="负责人" prop="ownerUserId">
<el-select
v-model="formData.ownerUserId"
placeholder="请选择负责人"
value-key="id"
lable-key="nickname"
>
<el-option
v-for="item in userList"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</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="sex">
<el-select v-model="formData.sex" placeholder="请选择">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input v-model="formData.mobile" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="座机" prop="telephone">
<el-input v-model="formData.telephone" placeholder="请输入座机" style="width: 215px" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="QQ" prop="qq">
<el-input v-model="formData.qq" placeholder="请输入QQ" style="width: 215px" />
</el-form-item>
<el-form-item label="微信" prop="webchat">
<el-input v-model="formData.webchat" placeholder="请输入微信" />
</el-form-item>
<el-form-item label="下次联系时间" prop="nextTime">
<el-date-picker
v-model="formData.nextTime"
type="date"
value-format="x"
placeholder="选择下次联系时间"
/>
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="formData.address" placeholder="请输入地址" />
</el-form-item>
<el-form-item label="直属上级" prop="parentId">
<el-select v-model="formData.parentId" placeholder="请选择">
<el-option
v-for="item in allContactList"
:key="item.id"
:label="item.name"
:value="item.id"
:disabled="item.id == formData.id"
/>
</el-select>
</el-form-item>
<el-form-item label="职位" prop="post">
<el-input v-model="formData.post" placeholder="请输入职位" />
</el-form-item>
<el-form-item label="是否关键决策人" prop="policyMakers" style="width: 400px">
<el-radio-group v-model="formData.policyMakers">
<el-radio
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="客户名称" prop="customerName">
<el-select
v-model="formData.customerId"
placeholder="请选择客户"
value-key="id"
lable-key="name"
>
<el-option
v-for="item in customerList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12"
><el-form-item label="性别" prop="sex">
<el-select v-model="formData.sex" placeholder="请选择">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select> </el-form-item
></el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="手机号" prop="mobile">
<el-input
input-style="width:190px;"
v-model="formData.mobile"
placeholder="请输入手机号"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="座机" prop="telephone">
<el-input v-model="formData.telephone" placeholder="请输入座机" style="width: 215px" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input
input-style="width:190px;"
v-model="formData.email"
placeholder="请输入邮箱"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="QQ" prop="qq">
<el-input v-model="formData.qq" placeholder="请输入QQ" style="width: 215px" />
</el-form-item>
</el-col>
</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-col>
<el-col :span="12">
<el-form-item label="下次联系时间" prop="nextTime">
<el-date-picker
v-model="formData.nextTime"
type="date"
value-format="x"
placeholder="选择下次联系时间"
/>
</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-input
input-style="width:190px;"
v-model="formData.address"
placeholder="请输入地址"
/>
</el-form-item>
</el-col> </el-row
><el-row>
<el-col :span="12">
<el-form-item label="直属上级" prop="parentId">
<el-select v-model="formData.parentId" placeholder="请选择">
<el-option
v-for="item in allContactList"
:key="item.id"
:label="item.name"
:value="item.id"
:disabled="item.id == formData.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职位" prop="post">
<el-input input-style="width:190px;" v-model="formData.post" placeholder="请输入职位" />
</el-form-item>
</el-col> </el-row
><el-row>
<el-col :span="12"
><el-form-item label="是否关键决策人" prop="master" style="width: 400px">
<el-radio-group v-model="formData.master">
<el-radio
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24"
><el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
<OwerSelect
ref="owerRef"
@confirmOwerSelect="owerSelectValue"
:initOwerUser="formData.ownerUserId"
/>
</template>
<script setup lang="ts">
import * as ContactApi from '@/api/crm/contact'
import { DICT_TYPE, getIntDictOptions, getBoolDictOptions } from '@/utils/dict'
import OwerSelect from './OwerSelect.vue'
import * as UserApi from '@/api/system/user'
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 message = useMessage() //
@ -172,6 +196,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const areaList = ref([]) //
const formData = ref({
nextTime: undefined,
mobile: undefined,
@ -188,21 +213,10 @@ const formData = ref({
name: undefined,
post: undefined,
qq: undefined,
webchat: undefined,
wechat: undefined,
sex: undefined,
policyMakers: 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
master: false,
areaId: undefined
})
const formRules = reactive({
name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
@ -212,56 +226,34 @@ const formRules = reactive({
const formRef = ref() // Ref
const ownerUserList = ref<any[]>([])
const userList = ref<UserApi.UserVO[]>([]) //
// TODO
const customerList = ref<CustomerApi.CustomerVO[]>([]) //
const allContactList = ref([]) //
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
allContactList.value = await ContactApi.simpleAlllist()
resetForm()
allContactList.value = await ContactApi.getSimpleContactList()
userList.value = await UserApi.getSimpleUserList()
customerList.value = await CustomerApi.queryAllList()
areaList.value = await AreaApi.getAreaTree()
//
if (id) {
formLoading.value = true
try {
formData.value = await ContactApi.getContact(id)
userList.value = await UserApi.getSimpleUserList()
await gotOwnerUser(formData.value.ownerUserId)
} finally {
formLoading.value = false
}
}
}
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 submitForm = async () => {
owerSelectValue(ownerUserList)
// owerSelectValue(ownerUserList)
//
if (!formRef) return
const valid = await formRef.value.validate()
@ -302,52 +294,11 @@ const resetForm = () => {
name: undefined,
post: undefined,
qq: undefined,
webchat: undefined,
wechat: undefined,
sex: undefined,
policyMakers: undefined
master: false
}
formRef.value?.resetFields()
ownerUserList.value = []
}
/** 添加/修改操作 */
// TODO @zynaowner
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>

View File

@ -0,0 +1,111 @@
<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>
</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>

View File

@ -8,7 +8,7 @@
<el-descriptions-item label="姓名">
{{ contact.name }}
</el-descriptions-item>
<el-descriptions-item label="客户名称">
<el-descriptions-item label="客户">
{{ contact.customerName }}
</el-descriptions-item>
<el-descriptions-item label="手机">
@ -24,14 +24,17 @@
{{ contact.qq }}
</el-descriptions-item>
<el-descriptions-item label="微信">
{{ contact.webchat }}
</el-descriptions-item>
<el-descriptions-item label="详细地址">
{{ contact.address }}
{{ contact.wechat }}
</el-descriptions-item>
<el-descriptions-item label="下次联系时间">
{{ contact.nextTime ? formatDate(contact.nextTime) : '空' }}
</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="性别">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="contact.sex" />
</el-descriptions-item>
@ -46,7 +49,7 @@
</template>
<el-descriptions :column="2">
<el-descriptions-item label="负责人">
{{ gotOwnerUser(contact.ownerUserId) }}
{{ contact.ownerUserName }}
</el-descriptions-item>
<el-descriptions-item label="创建人">
{{ contact.creatorName }}
@ -66,29 +69,9 @@
import * as ContactApi from '@/api/crm/contact'
import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import * as UserApi from '@/api/system/user'
const { contact } = defineProps<{ contact: ContactApi.ContactVO }>()
//
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>
<style scoped lang="scss"></style>

View File

@ -46,7 +46,7 @@
</div>
<ContentWrap class="mt-10px">
<el-descriptions :column="5" direction="vertical">
<el-descriptions-item label="客户名称">
<el-descriptions-item label="客户">
{{ contact.customerName }}
</el-descriptions-item>
<el-descriptions-item label="职务">
@ -63,33 +63,18 @@
<!-- TODO wanwan这个 tab 拉满哈可以更好看 -->
<el-col :span="18">
<el-tabs>
<el-tab-pane label="详细资料">
<el-tab-pane label="基本信息">
<!-- TODO wanwan这个 ml-2 是不是可以优化下不要整个左移而是里面的内容有个几 px 的偏移不顶在框里 -->
<ContactDetails class="ml-2" :contact="contact" />
</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>
<template #label> 合同<el-badge :value="3" class="item" type="primary" /> </template>
合同
<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="4" 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-tabs>
</el-col>
@ -105,10 +90,10 @@ import ContactBasicInfo from '@/views/crm/contact/detail/ContactBasicInfo.vue'
import ContactDetails from '@/views/crm/contact/detail/ContactDetails.vue'
import ContactForm from '@/views/crm/contact/ContactForm.vue'
import { formatDate } from '@/utils/formatTime'
import * as CustomerApi from '@/api/crm/customer'
// TODO review
defineOptions({ name: 'ContactDetail' })
defineOptions({ name: 'CrmContactDetail' })
const { delView } = useTagsViewStore() //
const route = useRoute()
const { currentRoute } = useRouter() //

View File

@ -1,7 +1,6 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<!-- TODO zyna筛选项按照需求简化下 -->
<el-form
class="-mb-15px"
:model="queryParams"
@ -9,14 +8,22 @@
:inline="true"
label-width="68px"
>
<el-form-item label="客户编号" prop="customerId">
<el-input
<el-form-item label="客户" prop="customerId">
<el-select
v-model="queryParams.customerId"
placeholder="请输入客户编号"
clearable
placeholder="请选择客户"
value-key="id"
lable-key="name"
@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 label="姓名" prop="name">
<el-input
@ -55,9 +62,9 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item label="微信" prop="webchat">
<el-form-item label="微信" prop="wechat">
<el-input
v-model="queryParams.webchat"
v-model="queryParams.wechat"
placeholder="请输入微信"
clearable
@keyup.enter="handleQuery"
@ -74,8 +81,8 @@
/>
</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 @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:contact:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
@ -97,32 +104,28 @@
<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>
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
{{ scope.row.name }}
</el-link>
</template>
</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">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
</template>
</el-table-column>
<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">
{{ allContactList.find((contact) => contact.id === scope.row.parentId)?.name }}
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
</template>
</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="telephone" />
<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="address" />
<el-table-column
@ -142,7 +145,7 @@
/>
<el-table-column label="负责人" align="center" prop="ownerUserId">
<template #default="scope">
{{ gotOwnerUser(scope.row.ownerUserId) }}
{{ scope.row.ownerUserName }}
</template>
</el-table-column>
<!-- <el-table-column label="所属部门" align="center" prop="ownerUserId" /> -->
@ -211,7 +214,6 @@ import download from '@/utils/download'
import * as ContactApi from '@/api/crm/contact'
import ContactForm from './ContactForm.vue'
import { DICT_TYPE } from '@/utils/dict'
import * as UserApi from '@/api/system/user'
import * as CustomerApi from '@/api/crm/customer'
defineOptions({ name: 'CrmContact' })
@ -222,6 +224,7 @@ const { t } = useI18n() // 国际化
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const customerList = ref<CustomerApi.CustomerVO[]>([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
@ -239,13 +242,12 @@ const queryParams = reactive({
name: null,
post: null,
qq: null,
webchat: null,
wechat: null,
sex: null,
policyMakers: null
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const userList = ref<UserApi.UserVO[]>([]) //
/** 查询列表 */
const getList = 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 openDetail = (id: number) => {
push({ name: 'CrmContactDetail', params: { id } })
}
// TODO @zyna
const allContactList = ref([]) //
const allCustomerList = ref([]) //
/** 初始化 **/
onMounted(async () => {
await getList()
userList.value = await UserApi.getSimpleUserList()
allContactList.value = await ContactApi.simpleAlllist()
customerList.value = await CustomerApi.queryAllList()
})
</script>

View File

@ -0,0 +1,132 @@
<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>

View File

@ -1,228 +0,0 @@
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)

View File

@ -8,44 +8,6 @@
: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 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
v-model="queryParams.businessId"
placeholder="请输入商机编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="下单日期" prop="orderDate">
<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
v-model="queryParams.no"
@ -55,6 +17,15 @@
class="!w-240px"
/>
</el-form-item>
<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>
@ -75,6 +46,7 @@
</ContentWrap>
<!-- 列表 -->
<!-- TODO 芋艿各种字段要调整 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="合同编号" align="center" prop="id" />
@ -125,7 +97,6 @@
width="180px"
/>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" width="120px">
<template #default="scope">
<el-button
@ -159,7 +130,6 @@
<!-- 表单弹窗添加/修改 -->
<ContractForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'

View File

@ -1,16 +0,0 @@
<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>

View File

@ -0,0 +1,57 @@
<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>

View File

@ -18,29 +18,15 @@
<el-descriptions-item label="客户等级">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="customer.level" />
</el-descriptions-item>
<el-descriptions-item label="手机">
{{ customer.mobile }}
</el-descriptions-item>
<el-descriptions-item label="电话">
{{ customer.telephone }}
</el-descriptions-item>
<el-descriptions-item label="邮箱">
{{ customer.email }}
</el-descriptions-item>
<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 label="手机">{{ customer.mobile }}</el-descriptions-item>
<el-descriptions-item label="电话">{{ customer.telephone }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ customer.email }} </el-descriptions-item>
<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 label="下次联系时间">
{{
@ -52,12 +38,8 @@
</el-descriptions-item>
</el-descriptions>
<el-descriptions :column="1">
<el-descriptions-item label="客户描述">
{{ customer.description }}
</el-descriptions-item>
<el-descriptions-item label="备注">
{{ customer.remark }}
</el-descriptions-item>
<el-descriptions-item label="客户描述">{{ customer.description }} </el-descriptions-item>
<el-descriptions-item label="备注">{{ customer.remark }} </el-descriptions-item>
</el-descriptions>
</el-collapse-item>
<el-collapse-item name="systemInfo">
@ -65,12 +47,8 @@
<span class="text-base font-bold">系统信息</span>
</template>
<el-descriptions :column="2">
<el-descriptions-item label="负责人">
{{ customer.ownerUserName }}
</el-descriptions-item>
<el-descriptions-item label="创建人">
{{ customer.creatorName }}
</el-descriptions-item>
<el-descriptions-item label="负责人">{{ customer.ownerUserName }} </el-descriptions-item>
<el-descriptions-item label="创建人">{{ customer.creatorName }} </el-descriptions-item>
<el-descriptions-item label="创建时间">
{{ customer.createTime ? formatDate(customer.createTime) : '空' }}
</el-descriptions-item>
@ -87,9 +65,10 @@ import * as CustomerApi from '@/api/crm/customer'
import { DICT_TYPE } from '@/utils/dict'
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>
<style scoped lang="scss"></style>

View File

@ -1,129 +1,48 @@
<template>
<!-- TODO @wanwan要不要把上面这一整块搞成一个组件就是把 下面 + Details + BasitcInfo 合并成一个 -->
<div v-loading="loading">
<div class="flex items-start justify-between">
<div>
<!-- 左上客户基本信息 -->
<CustomerBasicInfo :customer="customer" />
</div>
<div>
<!-- 右上按钮 -->
<el-button @click="openForm('update', customer.id)" v-hasPermi="['crm:customer:update']">
编辑
</el-button>
<el-button>更改成交状态</el-button>
</div>
</div>
<el-row class="mt-10px">
<el-button>
<Icon icon="ph:calendar-fill" class="mr-5px" />
创建任务
</el-button>
<el-button>
<Icon icon="carbon:email" class="mr-5px" />
发送邮件
</el-button>
<el-button>
<Icon icon="system-uicons:contacts" class="mr-5px" />
创建联系人
</el-button>
<el-button>
<Icon icon="ep:opportunity" class="mr-5px" />
创建商机
</el-button>
<el-button>
<Icon icon="clarity:contract-line" class="mr-5px" />
创建合同
</el-button>
<el-button>
<Icon icon="icon-park:income-one" class="mr-5px" />
创建回款
</el-button>
<el-button>
<Icon icon="fluent:people-team-add-20-filled" class="mr-5px" />
添加团队成员
</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>
<CustomerDetailsHeader :customer="customer" :loading="loading" @refresh="getCustomer(id)" />
<el-col>
<el-tabs>
<el-tab-pane label="详细资料">
<CustomerDetails :customer="customer" />
<CustomerDetailsInfo :customer="customer" />
</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>TODO 待开发</el-tab-pane>
<el-tab-pane label="联系人" lazy>
<template #label> 联系人<el-badge class="item" type="primary" /> </template>
联系人
<ContactList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
</el-tab-pane>
<el-tab-pane label="团队成员" lazy>
<template #label> 团队成员<el-badge class="item" type="primary" /> </template>
团队成员
<PermissionList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
</el-tab-pane>
<el-tab-pane label="商机" lazy>
<BusinessList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
</el-tab-pane>
<el-tab-pane label="商机" lazy> 商机</el-tab-pane>
<el-tab-pane label="合同" lazy>
<template #label> 合同<el-badge class="item" type="primary" /> </template>
合同
<ContractList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
</el-tab-pane>
<el-tab-pane label="回款" lazy>
<template #label> 回款<el-badge 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>TODO待开发 </el-tab-pane>
<el-tab-pane label="回访" lazy>TODO 待开发</el-tab-pane>
</el-tabs>
</el-col>
<!-- 表单弹窗添加/修改 -->
<CustomerForm ref="formRef" @success="getCustomerData(id)" />
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
<script lang="ts" setup>
import { useTagsViewStore } from '@/store/modules/tagsView'
import * as CustomerApi from '@/api/crm/customer'
import CustomerBasicInfo from '@/views/crm/customer/detail/CustomerBasicInfo.vue'
import { DICT_TYPE } from '@/utils/dict'
import CustomerDetails from '@/views/crm/customer/detail/CustomerDetails.vue'
import CustomerForm from '@/views/crm/customer/CustomerForm.vue'
import CustomerDetailsInfo from './CustomerDetailsInfo.vue' // -
import CustomerDetailsHeader from './CustomerDetailsHeader.vue' // -
import ContactList from '@/views/crm/contact/components/ContactList.vue' //
import ContractList from '@/views/crm/contract/components/ContractList.vue' //
import BusinessList from '@/views/crm/business/components/BusinessList.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 { currentRoute } = useRouter() //
const id = Number(route.params.id)
const id = Number(route.params.id) //
const loading = ref(true) //
/**
* 获取详情
*
* @param id
*/
/** 获取详情 */
const customer = ref<CustomerApi.CustomerVO>({} as CustomerApi.CustomerVO) //
const getCustomerData = async (id: number) => {
const getCustomer = async (id: number) => {
loading.value = true
try {
customer.value = await CustomerApi.getCustomer(id)
@ -132,20 +51,15 @@ const getCustomerData = async (id: number) => {
}
}
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/**
* 初始化
*/
/** 初始化 */
const { delView } = useTagsViewStore() //
const { currentRoute } = useRouter() //
onMounted(() => {
if (!id) {
ElMessage.warning('参数错误,客户不能为空!')
delView(unref(currentRoute))
return
}
getCustomerData(id)
getCustomer(id)
})
</script>

View File

@ -72,17 +72,10 @@
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
<el-button @click="handleQuery"> <Icon class="mr-5px" icon="ep:search" /> 搜索 </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')">
<Icon class="mr-5px" icon="ep:plus" />
新增
<Icon class="mr-5px" icon="ep:plus" /> 新增
</el-button>
<el-button
v-hasPermi="['crm:customer:export']"
@ -91,8 +84,7 @@
type="success"
@click="handleExport"
>
<Icon class="mr-5px" icon="ep:download" />
导出
<Icon class="mr-5px" icon="ep:download" /> 导出
</el-button>
</el-form-item>
</el-form>
@ -102,7 +94,13 @@
<ContentWrap>
<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="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">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
@ -121,7 +119,7 @@
<el-table-column align="center" label="手机" prop="mobile" width="120" />
<el-table-column align="center" label="详细地址" prop="detailAddress" width="200" />
<el-table-column align="center" label="负责人" prop="ownerUserName" />
<el-table-column align="center" label="所属部门" prop="ownerUserDept" />
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" />
<el-table-column align="center" label="创建人" prop="creatorName" />
<el-table-column
:formatter="dateFormatter"
@ -157,7 +155,6 @@
<!-- TODO @wanwan 距进入公海天数 -->
<el-table-column align="center" fixed="right" label="操作" min-width="150">
<template #default="scope">
<el-button link type="primary" @click="openDetail(scope.row.id)">详情</el-button>
<el-button
v-hasPermi="['crm:customer:update']"
link
@ -185,8 +182,6 @@
@pagination="getList"
/>
</ContentWrap>
<!-- TODO 方便查看效果 TODO 芋艿先注释了避免演示环境报错 -->
<!-- <CrmTeam :biz-id="1" :biz-type="CrmBizTypeEnum.CRM_CUSTOMER" />-->
<!-- 表单弹窗添加/修改 -->
<CustomerForm ref="formRef" @success="getList" />
@ -198,7 +193,6 @@ import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as CustomerApi from '@/api/crm/customer'
import CustomerForm from './CustomerForm.vue'
import { CrmBizTypeEnum, CrmTeam } from '@/views/crm/components'
defineOptions({ name: 'CrmCustomer' })
@ -211,11 +205,12 @@ const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
mobile: null,
industryId: null,
level: null,
source: null
pool: false,
name: '',
mobile: '',
industryId: undefined,
level: undefined,
source: undefined
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
@ -241,6 +236,7 @@ const handleQuery = () => {
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
queryParams.pool = false
handleQuery()
}

View File

@ -1,14 +0,0 @@
// TODO 可以挪到它对应的 api.ts 文件里哈
/**
*
*/
export enum LimitConfType {
/**
*
*/
CUSTOMER_QUANTITY_LIMIT = 1,
/**
*
*/
CUSTOMER_LOCK_LIMIT = 2
}

View File

@ -1,20 +0,0 @@
<template>
<!-- 列表 -->
<ContentWrap>
<el-tabs>
<el-tab-pane label="拥有客户数限制">
<!-- TODO @wanwanCustomerLimitConfigList因为它是列表哈 -->
<CustomerLimitConfDetails :confType="LimitConfType.CUSTOMER_QUANTITY_LIMIT" />
</el-tab-pane>
<el-tab-pane label="锁定客户数限制">
<CustomerLimitConfDetails :confType="LimitConfType.CUSTOMER_LOCK_LIMIT" />
</el-tab-pane>
</el-tabs>
</ContentWrap>
</template>
<script setup lang="ts">
import CustomerLimitConfDetails from '@/views/crm/customerLimitConfig/CustomerLimitConfDetails.vue'
import { LimitConfType } from '@/views/crm/customerLimitConfig/customerLimitConf'
defineOptions({ name: 'CrmCustomerLimitConfig' })
</script>

View File

@ -19,11 +19,17 @@
</el-form-item>
<el-form-item label="权限级别" prop="level">
<el-radio-group v-model="formData.level">
<!-- TODO @puhui999搞个字典配置然后这里 remove 掉负责人 -->
<el-radio :label="CrmPermissionLevelEnum.READ">只读</el-radio>
<el-radio :label="CrmPermissionLevelEnum.WRITE">读写</el-radio>
<template
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PERMISSION_LEVEL)"
:key="dict.value"
>
<el-radio v-if="dict.value != PermissionLevelEnum.OWNER" :label="dict.value">
{{ dict.label }}
</el-radio>
</template>
</el-radio-group>
</el-form-item>
<!-- TODO @puhui999同时添加至 -->
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
@ -34,7 +40,8 @@
<script lang="ts" setup>
import * as UserApi from '@/api/system/user'
import * as PermissionApi from '@/api/crm/permission'
import { CrmPermissionLevelEnum } from './index'
import { PermissionLevelEnum } from '@/api/crm/permission'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
defineOptions({ name: 'CrmPermissionForm' })
@ -48,8 +55,8 @@ const formType = ref('') // 表单的类型create - 新增update - 修改
const userOptions = ref<UserApi.UserVO[]>([]) //
const formData = ref<PermissionApi.PermissionVO & { ids?: number[] }>({
userId: undefined, //
bizType: undefined, // Crm
bizId: undefined, // Crm
bizType: undefined, // CRM
bizId: undefined, // CRM
level: undefined //
})
const formRules = reactive({

View File

@ -0,0 +1,140 @@
<template>
<!-- 操作栏 -->
<el-row justify="end">
<el-button @click="openForm">
<Icon class="mr-5px" icon="fluent:people-team-add-20-filled" /> 添加团队成员
</el-button>
<el-button @click="handleUpdate">
<Icon class="mr-5px" icon="ep:edit" />
编辑
</el-button>
<el-button @click="handleDelete">
<Icon class="mr-5px" icon="ep:delete" />
移除
</el-button>
<el-button type="danger" @click="handleQuit"> 退出团队</el-button>
</el-row>
<!-- 列表 -->
<ContentWrap class="mt-10px">
<el-table
v-loading="loading"
:data="list"
:show-overflow-tooltip="true"
:stripe="true"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column align="center" label="姓名" prop="nickname" />
<el-table-column align="center" label="部门" prop="deptName" />
<el-table-column align="center" label="岗位" prop="postNames" />
<el-table-column align="center" label="权限" prop="level">
<template #default="{ row }">
<dict-tag :type="DICT_TYPE.CRM_PERMISSION_LEVEL" :value="row.level" />
</template>
</el-table-column>
<el-table-column
:formatter="dateFormatter"
align="center"
label="加入时间"
prop="createTime"
/>
</el-table>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<CrmPermissionForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import * as PermissionApi from '@/api/crm/permission'
import { PermissionLevelEnum } from '@/api/crm/permission'
import { useUserStoreWithOut } from '@/store/modules/user'
import CrmPermissionForm from './PermissionForm.vue'
defineOptions({ name: 'CrmPermissionList' })
const props = defineProps<{
bizType: number //
bizId: number //
}>()
const message = useMessage() //
const loading = ref(true) //
const list = ref<PermissionApi.PermissionVO[]>([]) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
list.value = await PermissionApi.getPermissionList({
bizType: props.bizType,
bizId: props.bizId
})
} finally {
loading.value = false
}
}
/** 选中团队成员 */
const multipleSelection = ref<PermissionApi.PermissionVO[]>([]) //
const handleSelectionChange = (val: PermissionApi.PermissionVO[]) => {
multipleSelection.value = val
}
/** 添加团队成员 */
const formRef = ref<InstanceType<typeof CrmPermissionForm>>() // Ref
const openForm = () => {
formRef.value?.open('create', props.bizType, props.bizId)
}
/** 编辑团队成员 */
const handleUpdate = () => {
if (multipleSelection.value?.length === 0) {
message.warning('请先选择团队成员后操作!')
return
}
const ids = multipleSelection.value?.map((item) => item.id) as number[]
formRef.value?.open('update', props.bizType, props.bizId, ids)
}
/** 移除团队成员 */
const handleDelete = async () => {
if (multipleSelection.value?.length === 0) {
message.warning('请先选择团队成员后操作!')
return
}
// TODO @puhui999
await message.delConfirm()
const ids = multipleSelection.value?.map((item) => item.id)
await PermissionApi.deletePermissionBatch({
bizType: props.bizType,
bizId: props.bizId,
ids
})
}
/** 退出团队 */
const userStore = useUserStoreWithOut() //
const handleQuit = async () => {
const permission = list.value.find(
(item) => item.userId === userStore.getUser.id && item.level === PermissionLevelEnum.OWNER
)
if (permission) {
message.warning('负责人不能退出团队!')
return
}
// TODO @puhui999
const userPermission = list.value.find((item) => item.userId === userStore.getUser.id)
await PermissionApi.deleteSelfPermission(userPermission?.id)
}
/** 监听打开的 bizId + bizType从而加载最新的列表 */
watch(
() => [props.bizId, props.bizType],
() => {
getList()
},
{ immediate: true, deep: true }
)
</script>

View File

@ -52,7 +52,7 @@
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_RECEIVABLE_CHECK_STATUS)"
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_AUDIT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -194,7 +194,7 @@
<el-table-column label="合同" align="center" prop="contractId" />
<el-table-column label="审批状态" align="center" prop="checkStatus" width="130px">
<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>
</el-table-column>
<!-- <el-table-column label="工作流编号" align="center" prop="processInstanceId" />-->

View File

@ -49,7 +49,7 @@
class="!w-240px"
>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.CRM_RECEIVABLE_CHECK_STATUS)"
v-for="dict in getStrDictOptions(DICT_TYPE.CRM_AUDIT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -171,7 +171,7 @@
</el-table-column>
<el-table-column label="审批状态" align="center" prop="checkStatus" width="130px">
<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>
</el-table-column>
<!--<el-table-column label="工作流编号" align="center" prop="processInstanceId" />-->

View File

@ -1,5 +1,6 @@
<template>
<div class="flex">
<!-- 左侧建立连接发送消息 -->
<el-card :gutter="12" class="w-1/2" shadow="always">
<template #header>
<div class="card-header">
@ -11,28 +12,38 @@
<el-tag :color="getTagColor">{{ status }}</el-tag>
</div>
<hr class="my-4" />
<div class="flex">
<el-input v-model="server" disabled>
<template #prepend> 服务地址</template>
<template #prepend>服务地址</template>
</el-input>
<el-button :type="getIsOpen ? 'danger' : 'primary'" @click="toggle">
<el-button :type="getIsOpen ? 'danger' : 'primary'" @click="toggleConnectStatus">
{{ getIsOpen ? '关闭连接' : '开启连接' }}
</el-button>
</div>
<p class="mt-4 text-lg font-medium">设置</p>
<p class="mt-4 text-lg font-medium">消息输入框</p>
<hr class="my-4" />
<el-input
v-model="sendValue"
v-model="sendText"
:autosize="{ minRows: 2, maxRows: 4 }"
:disabled="!getIsOpen"
clearable
type="textarea"
placeholder="请输入你要发送的消息"
/>
<el-button :disabled="!getIsOpen" block class="mt-4" type="primary" @click="handlerSend">
<el-select v-model="sendUserId" class="mt-4" placeholder="请选择发送人">
<el-option key="" label="所有人" value="" />
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
<el-button :disabled="!getIsOpen" block class="ml-2 mt-4" type="primary" @click="handlerSend">
发送
</el-button>
</el-card>
<!-- 右侧消息记录 -->
<el-card :gutter="12" class="w-1/2" shadow="always">
<template #header>
<div class="card-header">
@ -41,13 +52,13 @@
</template>
<div class="max-h-80 overflow-auto">
<ul>
<li v-for="item in getList" :key="item.time" class="mt-2">
<li v-for="msg in messageList.reverse()" :key="msg.time" class="mt-2">
<div class="flex items-center">
<span class="text-primary mr-2 font-medium">收到消息:</span>
<span>{{ formatDate(item.time) }}</span>
<span>{{ formatDate(msg.time) }}</span>
</div>
<div>
{{ item.res }}
{{ msg.text }}
</div>
</li>
</ul>
@ -57,62 +68,113 @@
</template>
<script lang="ts" setup>
import { formatDate } from '@/utils/formatTime'
import { useUserStore } from '@/store/modules/user'
import { useWebSocket } from '@vueuse/core'
import { getAccessToken } from '@/utils/auth'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'InfraWebSocket' })
const userStore = useUserStore()
const sendValue = ref('')
const message = useMessage() //
const server = ref(
(import.meta.env.VITE_BASE_URL + '/websocket/message').replace('http', 'ws') +
'?userId=' +
userStore.getUser.id
)
const state = reactive({
recordList: [] as { id: number; time: number; res: string }[]
})
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') + '?token=' + getAccessToken()
) // WebSocket
const getIsOpen = computed(() => status.value === 'OPEN') // WebSocket
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) // WebSocket
/** 发起 WebSocket 连接 */
const { status, data, send, close, open } = useWebSocket(server.value, {
autoReconnect: false,
heartbeat: true
})
/** 监听接收到的数据 */
const messageList = ref([] as { time: number; text: string }[]) //
watchEffect(() => {
if (data.value) {
try {
const res = JSON.parse(data.value)
state.recordList.push(res)
} catch (error) {
state.recordList.push({
res: data.value,
id: Math.ceil(Math.random() * 1000),
if (!data.value) {
return
}
try {
// 1.
if (data.value === 'pong') {
// state.recordList.push({
// text: '',
// time: new Date().getTime()
// })
return
}
// 2.1 type
const jsonMessage = JSON.parse(data.value)
const type = jsonMessage.type
const content = JSON.parse(jsonMessage.content)
if (!type) {
message.error('未知的消息类型:' + data.value)
return
}
// 2.2 demo-message-receive
if (type === 'demo-message-receive') {
const single = content.single
if (single) {
messageList.value.push({
text: `【单发】用户编号(${content.fromUserId})${content.text}`,
time: new Date().getTime()
})
} else {
messageList.value.push({
text: `【群发】用户编号(${content.fromUserId})${content.text}`,
time: new Date().getTime()
})
}
return
}
// 2.3 notice-push
if (type === 'notice-push') {
messageList.value.push({
text: `【系统通知】:${content.title}`,
time: new Date().getTime()
})
return
}
message.error('未处理消息:' + data.value)
} catch (error) {
message.error('处理消息发生异常:' + data.value)
console.error(error)
}
})
const getIsOpen = computed(() => status.value === 'OPEN')
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red'))
const getList = computed(() => {
return [...state.recordList].reverse()
})
function handlerSend() {
send(sendValue.value)
sendValue.value = ''
/** 发送消息 */
const sendText = ref('') //
const sendUserId = ref('') //
const handlerSend = () => {
// 1.1 JSON message
const messageContent = JSON.stringify({
text: sendText.value,
toUserId: sendUserId.value
})
// 1.2 JSON
const jsonMessage = JSON.stringify({
type: 'demo-message-send',
content: messageContent
})
// 2.
send(jsonMessage)
sendText.value = ''
}
function toggle() {
/** 切换 websocket 连接状态 */
const toggleConnectStatus = () => {
if (getIsOpen.value) {
close()
} else {
open()
}
}
/** 初始化 **/
const userList = ref<any[]>([]) //
onMounted(async () => {
//
userList.value = await UserApi.getSimpleUserList()
})
</script>

View File

@ -0,0 +1,96 @@
<template>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column key="id" align="center" label="商品编号" width="180" prop="id" />
<el-table-column label="商品图" min-width="80">
<template #default="{ row }">
<el-image :src="row.picUrl" class="h-30px w-30px" @click="imagePreview(row.picUrl)" />
</template>
</el-table-column>
<el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
<el-table-column align="center" label="商品售价" min-width="90" prop="price">
<template #default="{ row }"> {{ floatToFixed2(row.price) }}</template>
</el-table-column>
<el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
<el-table-column
:formatter="dateFormatter"
align="center"
label="收藏时间"
prop="createTime"
width="180"
/>
<el-table-column align="center" label="状态" min-width="80">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PRODUCT_SPU_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</template>
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as FavoriteApi from '@/api/mall/product/favorite'
import { floatToFixed2 } from '@/utils'
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,
createTime: [],
userId: NaN
})
const queryFormRef = ref() //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await FavoriteApi.getFavoritePage(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 { userId } = defineProps({
userId: {
type: Number,
required: true
}
})
/** 初始化 **/
onMounted(() => {
queryParams.userId = userId
getList()
})
</script>

View File

@ -48,7 +48,9 @@
<UserOrderList :user-id="id" />
</el-tab-pane>
<el-tab-pane label="售后管理" lazy>售后管理(WIP)</el-tab-pane>
<el-tab-pane label="收藏记录" lazy>收藏记录(WIP)</el-tab-pane>
<el-tab-pane label="收藏记录" lazy>
<UserFavoriteList :user-id="id" />
</el-tab-pane>
<el-tab-pane label="优惠劵" lazy>
<UserCouponList :user-id="id" />
</el-tab-pane>
@ -76,6 +78,7 @@ import UserExperienceRecordList from './UserExperienceRecordList.vue'
import UserOrderList from './UserOrderList.vue'
import UserPointList from './UserPointList.vue'
import UserSignList from './UserSignList.vue'
import UserFavoriteList from './UserFavoriteList.vue'
import { CardTitle } from '@/components/Card/index'
import { ElMessage } from 'element-plus'

View File

@ -87,6 +87,9 @@
>
删除
</el-button>
<el-button link @click="handlePush(scope.row.id)" v-hasPermi="['system:notice:update']">
推送
</el-button>
</template>
</el-table-column>
</el-table>
@ -168,6 +171,17 @@ const handleDelete = async (id: number) => {
} catch {}
}
/** 推送按钮操作 */
const handlePush = async (id: number) => {
try {
//
await message.confirm('是否推送所选中通知?')
//
await NoticeApi.pushNotice(id)
message.success(t('推送成功'))
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()

View File

@ -37,9 +37,6 @@
<el-descriptions-item label="发送时间">
{{ formatDate(detailData.sendTime) }}
</el-descriptions-item>
<el-descriptions-item label="发送结果">
{{ detailData.sendCode }} | {{ detailData.sendMsg }}
</el-descriptions-item>
<el-descriptions-item label="API 发送结果">
{{ detailData.apiSendCode }} | {{ detailData.apiSendMsg }}
</el-descriptions-item>