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

This commit is contained in:
owen 2024-01-05 15:26:41 +08:00
commit 1fb7701e6e
64 changed files with 1121 additions and 569 deletions

View File

@ -83,7 +83,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"

View File

@ -44,7 +44,7 @@ export const exportTask = async (params) => {
// 获取所有可回退的节点
export const getReturnList = async (params) => {
return await request.get({ url: '/bpm/task/get-return-list', params })
return await request.get({ url: '/bpm/task/return-list', params })
}
// 回退

View File

@ -1,3 +1,10 @@
/*
* @Author: zyna
* @Date: 2023-12-02 13:08:56
* @LastEditTime: 2023-12-17 16:28:20
* @FilePath: \yudao-ui-admin-vue3\src\api\crm\business\index.ts
* @Description:
*/
import request from '@/config/axios'
export interface BusinessVO {
@ -55,3 +62,8 @@ export const deleteBusiness = async (id: number) => {
export const exportBusiness = async (params) => {
return await request.download({ url: `/crm/business/export-excel`, params })
}
//联系人关联商机列表
export const getBusinessPageByContact = async (params) => {
return await request.get({ url: `/crm/business/page-by-contact`, params })
}

View File

@ -8,7 +8,7 @@ export interface ContactVO {
email: string
post: string
customerId: number
address: string
detailAddress: string
remark: string
ownerUserId: string
lastTime: Date
@ -26,6 +26,11 @@ export interface ContactVO {
ownerUserName: string
}
export interface ContactBusinessReqVO {
contactId: number
businessIds: number[]
}
// 查询 CRM 联系人列表
export const getContactPage = async (params) => {
return await request.get({ url: `/crm/contact/page`, params })
@ -65,3 +70,13 @@ export const exportContact = async (params) => {
export const getSimpleContactList = async () => {
return await request.get({ url: `/crm/contact/simple-all-list` })
}
// 批量新增联系人商机关联
export const createContactBusinessList = async (data: ContactBusinessReqVO) => {
return await request.post({ url: `/crm/contact/create-business-list`, data })
}
// 解除联系人商机关联
export const deleteContactBusinessList = async (data: ContactBusinessReqVO) => {
return await request.delete({ url: `/crm/contact/delete-business-list`, data })
}

View File

@ -1,47 +0,0 @@
import request from '@/config/axios'
export interface ContactBusinessLinkVO {
id: number
contactId: number
businessId: number
}
// 查询联系人商机关联分页
export const getContactBusinessLinkPage = async (params) => {
return await request.get({ url: `/crm/contact-business-link/page`, params })
}
// 查询联系人商机关联详情
export const getContactBusinessLink = async (id: number) => {
return await request.get({ url: `/crm/contact-business-link/get?id=` + id })
}
// 新增联系人商机关联
export const createContactBusinessLink = async (data: ContactBusinessLinkVO) => {
return await request.post({ url: `/crm/contact-business-link/create`, data })
}
// 修改联系人商机关联
export const updateContactBusinessLink = async (data: ContactBusinessLinkVO) => {
return await request.put({ url: `/crm/contact-business-link/update`, data })
}
// 删除联系人商机关联
export const deleteContactBusinessLink = async (data: ContactBusinessLinkVO) => {
return await request.delete({ url: `/crm/contact-business-link/delete-batch`, data })
}
// 导出联系人商机关联 Excel
export const exportContactBusinessLink = async (params) => {
return await request.download({ url: `/crm/contact-business-link/export-excel`, params })
}
//批量新增联系人商机关联
export const createContactBusinessLinkBatch = async (data: ContactBusinessLinkVO[]) => {
return await request.post({ url: `/crm/contact-business-link/create-batch`, data })
}
// 查询联系人关联商机列表
export const getBusinessByContactPage = async (params) => {
return await request.get({ url: `/crm/contact-business-link/page-by-contact`, params })
}
// TODO @zyna根据后端调整下请求

View File

@ -59,7 +59,7 @@ export const deleteCustomer = async (id: number) => {
}
// 导出客户 Excel
export const exportCustomer = async (params) => {
export const exportCustomer = async (params: any) => {
return await request.download({ url: `/crm/customer/export-excel`, params })
}
@ -67,3 +67,13 @@ export const exportCustomer = async (params) => {
export const queryAllList = async () => {
return await request.get({ url: `/crm/customer/query-all-list` })
}
// 查询客户操作日志
export const getOperateLogPage = async (params: any) => {
return await request.get({ url: '/crm/customer/operate-log-page', params })
}
// 锁定/解锁客户
export const lockCustomer = async (id: number, lockStatus: boolean) => {
return await request.put({ url: `/crm/customer/lock`, data: { id, lockStatus } })
}

View File

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

View File

@ -0,0 +1,39 @@
import request from '@/config/axios'
export interface UReportDataVO {
id: number
name: string
status: number
content: string
remark: string
}
// 查询Ureport2报表分页
export const getUReportDataPage = async (params) => {
return await request.get({ url: `/report/ureport-data/page`, params })
}
// 查询Ureport2报表详情
export const getUReportData = async (id: number) => {
return await request.get({ url: `/report/ureport-data/get?id=` + id })
}
// 新增Ureport2报表
export const createUReportData = async (data: UReportDataVO) => {
return await request.post({ url: `/report/ureport-data/create`, data })
}
// 修改Ureport2报表
export const updateUReportData = async (data: UReportDataVO) => {
return await request.put({ url: `/report/ureport-data/update`, data })
}
// 删除Ureport2报表
export const deleteUReportData = async (id: number) => {
return await request.delete({ url: `/report/ureport-data/delete?id=` + id })
}
// 导出Ureport2报表 Excel
export const exportUReportData = async (params) => {
return await request.download({ url: `/report/ureport-data/export-excel`, params })
}

View File

@ -23,6 +23,32 @@ export type OperateLogVO = {
resultData: string
}
export type OperateLogV2VO = {
id: number
userNickname: string
traceId: string
userType: number
userId: number
userName: string
type: string
subType: string
bizId: number
action: string
extra: string
requestMethod: string
requestUrl: string
userIp: string
userAgent: string
creator: string
creatorName: string
createTime: Date
// 数据扩展,渲染时使用
title: string // 操作标题(如果为空则取 name 值)
colSize: number // 变更记录行数
contentStrList: string[]
tagsContentList: string[]
}
// 查询操作日志列表
export const getOperateLogPage = (params: PageParam) => {
return request.get({ url: '/system/operate-log/page', params })

View File

@ -35,8 +35,6 @@ export const enum APP_LINK_TYPE_ENUM {
PRODUCT_DETAIL_NORMAL,
// 拼团商品详情
PRODUCT_DETAIL_COMBINATION,
// 积分商品详情
PRODUCT_DETAIL_POINT,
// 秒杀商品详情
PRODUCT_DETAIL_SECKILL
}
@ -80,10 +78,6 @@ export const APP_LINK_GROUP_LIST = [
name: '系统设置',
path: '/pages/public/setting'
},
{
name: '问题反馈',
path: '/pages/public/feedback'
},
{
name: '常见问题',
path: '/pages/public/faq'
@ -112,11 +106,6 @@ export const APP_LINK_GROUP_LIST = [
name: '秒杀商品详情',
path: '/pages/goods/seckill',
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_SECKILL
},
{
name: '积分商品详情',
path: '/pages/goods/score',
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_POINT
}
]
},
@ -145,10 +134,6 @@ export const APP_LINK_GROUP_LIST = [
name: '签到中心',
path: '/pages/app/sign'
},
{
name: '积分商城',
path: '/pages/app/score-shop'
},
{
name: '优惠券中心',
path: '/pages/coupon/list'
@ -172,10 +157,6 @@ export const APP_LINK_GROUP_LIST = [
name: '分销中心',
path: '/pages/commission/index'
},
{
name: '申请分销商',
path: '/pages/commission/apply'
},
{
name: '推广商品',
path: '/pages/commission/goods'
@ -184,10 +165,6 @@ export const APP_LINK_GROUP_LIST = [
name: '分销订单',
path: '/pages/commission/order'
},
{
name: '分享记录',
path: '/pages/commission/share-log'
},
{
name: '我的团队',
path: '/pages/commission/team'
@ -208,10 +185,6 @@ export const APP_LINK_GROUP_LIST = [
{
name: '申请提现',
path: '/pages/pay/withdraw'
},
{
name: '提现记录',
path: '/pages/pay/withdraw-log'
}
]
},
@ -242,10 +215,6 @@ export const APP_LINK_GROUP_LIST = [
name: '地址管理',
path: '/pages/user/address/list'
},
{
name: '发票管理',
path: '/pages/user/invoice/list'
},
{
name: '用户佣金',
path: '/pages/user/wallet/commission'

View File

@ -8,11 +8,11 @@
<component :is="component.id" :property="component.property" />
</div>
<div class="component-wrap">
<!-- 左侧组件名 -->
<!-- 左侧组件名悬浮的小贴条 -->
<div class="component-name" v-if="component.name">
{{ component.name }}
</div>
<!-- 组件操作工具栏 -->
<!-- 组件操作工具栏 -->
<div class="component-toolbar" v-if="showToolbar && component.name && active">
<VerticalButtonGroup type="primary">
<el-tooltip content="上移" placement="right">
@ -54,7 +54,7 @@ import { propTypes } from '@/utils/propTypes'
import { object } from 'vue-types'
/**
* 组件容器
* 组件容器目前在中间部分
* 用于包裹组件为组件提供 背景外边距内边距边框等样式
*/
defineOptions({ name: 'ComponentContainer' })
@ -100,6 +100,7 @@ const emits = defineEmits<{
(e: 'copy'): void
(e: 'delete'): void
}>()
/**
* 移动组件
* @param direction 移动方向
@ -107,12 +108,14 @@ const emits = defineEmits<{
const handleMoveComponent = (direction: number) => {
emits('move', direction)
}
/**
* 复制组件
*/
const handleCopyComponent = () => {
emits('copy')
}
/**
* 删除组件
*/

View File

@ -1,8 +1,11 @@
<template>
<el-tabs stretch>
<!-- 每个组件的自定义内容 -->
<el-tab-pane label="内容" v-if="$slots.default">
<slot></slot>
</el-tab-pane>
<!-- 每个组件的通用内容 -->
<el-tab-pane label="样式" lazy>
<el-card header="组件样式" class="property-group">
<el-form :model="formData" label-width="80px">
@ -51,7 +54,7 @@
import { ComponentStyle, usePropertyForm } from '@/components/DiyEditor/util'
/**
* 组件容器属性
* 组件容器属性目前右边部分
* 用于包裹组件为组件提供 背景外边距内边距边框等样式
*/
defineOptions({ name: 'ComponentContainer' })

View File

@ -41,16 +41,19 @@ import { componentConfigs } from '../components/mobile/index'
import { cloneDeep } from 'lodash-es'
import { DiyComponent, DiyComponentLibrary } from '@/components/DiyEditor/util'
/** 组件库 */
/** 组件库:目前左侧的【基础组件】、【图文组件】部分 */
defineOptions({ name: 'ComponentLibrary' })
//
const props = defineProps<{
list: DiyComponentLibrary[]
}>()
//
const groups = reactive<any[]>([])
//
const extendGroups = reactive<string[]>([])
// list DiyComponentLibrary name
watch(
() => props.list,
() => {

View File

@ -4,6 +4,7 @@ import { floatToFixed2 } from '@/utils'
import { formatDate } from '@/utils/formatTime'
// 优惠值
// TODO @疯狂idea 有告警
export const CouponDiscount = defineComponent({
name: 'CouponDiscount',
props: {

View File

@ -24,6 +24,7 @@ export interface CouponCardProperty {
}
// 定义组件
// TODO @疯狂idea 有告警
export const component = {
id: 'CouponCard',
name: '优惠券',

View File

@ -9,6 +9,7 @@ export interface HotZoneProperty {
// 组件样式
style: ComponentStyle
}
/** 热区项目属性 */
export interface HotZoneItemProperty {
// 链接的名称

View File

@ -13,6 +13,7 @@ export interface MagicCubeProperty {
// 组件样式
style: ComponentStyle
}
/** 广告魔方项目属性 */
export interface MagicCubeItemProperty {
// 图标链接
@ -30,6 +31,7 @@ export interface MagicCubeItemProperty {
}
// 定义组件
// TODO @疯狂:有 idea 爆红告警
export const component = {
id: 'MagicCube',
name: '广告魔方',

View File

@ -10,6 +10,7 @@ export interface MenuGridProperty {
// 组件样式
style: ComponentStyle
}
/** 宫格导航项目属性 */
export interface MenuGridItemProperty {
// 图标链接

View File

@ -8,6 +8,7 @@ export interface MenuListProperty {
// 组件样式
style: ComponentStyle
}
/** 列表导航项目属性 */
export interface MenuListItemProperty {
// 图标链接

View File

@ -12,7 +12,6 @@
<el-form-item label="文字颜色" prop="文字颜色">
<ColorInput v-model="formData.textColor" />
</el-form-item>
<el-card header="公告内容" class="property-group" shadow="never">
<Draggable v-model="formData.contents">
<template #default="{ element }">

View File

@ -59,6 +59,7 @@ export interface ProductCardFieldProperty {
}
// 定义组件
// TODO @疯狂idea 有告警
export const component = {
id: 'ProductCard',
name: '商品卡片',

View File

@ -134,6 +134,7 @@ watch(
deep: true
}
)
/**
* 计算商品的间距
* @param index 商品索引

View File

@ -38,6 +38,7 @@ export interface ProductListFieldProperty {
}
// 定义组件
// TODO @疯狂idea 有告警
export const component = {
id: 'ProductList',
name: '商品栏',

View File

@ -6,6 +6,7 @@ import { PromotionArticleProperty } from './config'
import * as ArticleApi from '@/api/mall/promotion/article/index'
/** 营销文章 */
// TODO @idea
defineOptions({ name: 'PromotionArticle' })
//
const props = defineProps<{ property: PromotionArticleProperty }>()

View File

@ -29,6 +29,7 @@ export interface PromotionCombinationProperty {
// 组件样式
style: ComponentStyle
}
// 商品字段
export interface PromotionCombinationFieldProperty {
// 是否显示
@ -38,6 +39,7 @@ export interface PromotionCombinationFieldProperty {
}
// 定义组件
// TODO @疯狂idea 有告警
export const component = {
id: 'PromotionCombination',
name: '拼团',

View File

@ -17,6 +17,7 @@ export interface SearchProperty {
export type PlaceholderPosition = 'left' | 'center'
// 定义组件
// TODO @疯狂idea 这里爆红可以卡看咋优化下哇is missing the following properties from type DiyComponent<SearchProperty>: uid, position
export const component = {
id: 'SearchBar',
name: '搜索框',

View File

@ -19,6 +19,7 @@ export interface VideoPlayerStyle extends ComponentStyle {
}
// 定义组件
// TODO @疯狂idea 有告警
export const component = {
id: 'VideoPlayer',
name: '视频播放',

View File

@ -27,11 +27,12 @@
</el-tooltip>
</el-button-group>
</el-header>
<!-- 中心区域 -->
<el-container class="editor-container">
<!-- 左侧组件库 -->
<!-- 左侧组件库ComponentLibrary -->
<ComponentLibrary ref="componentLibrary" :list="libs" v-if="libs && libs.length > 0" />
<!-- 中心设计区域 -->
<!-- 中心设计区域ComponentContainer -->
<div class="editor-center page-prop-area" @click="handlePageSelected">
<!-- 手机顶部 -->
<div class="editor-design-top">
@ -132,7 +133,7 @@
</template>
</div>
</div>
<!-- 右侧属性面板 -->
<!-- 右侧属性面板ComponentContainerProperty -->
<el-aside class="editor-right" width="350px" v-if="selectedComponent?.property">
<el-card
shadow="never"
@ -160,6 +161,7 @@
</el-aside>
</el-container>
</el-container>
<!-- 预览弹框 -->
<Dialog v-model="previewDialogVisible" title="预览" width="700">
<div class="flex justify-around">
@ -231,6 +233,7 @@ const props = defineProps({
})
//
// pageConfigComponent navigationBarComponentpageComponentstabBarComponent
watch(
() => props.modelValue,
() => {
@ -251,6 +254,7 @@ watch(
immediate: true
}
)
//
const handleSave = () => {
const pageConfig = {
@ -303,7 +307,7 @@ const handleTabBarSelected = () => {
handleComponentSelected(unref(tabBarComponent))
}
//
//
const handleComponentChange = (dragEvent: any) => {
//
if (dragEvent.added) {
@ -327,19 +331,21 @@ const swapComponent = (oldIndex: number, newIndex: number) => {
selectedComponentIndex.value = newIndex
}
/** 移动组件 */
/** 移动组件(上移、下移) */
const handleMoveComponent = (index: number, direction: number) => {
const newIndex = index + direction
if (newIndex < 0 || newIndex >= pageComponents.value.length) return
swapComponent(index, newIndex)
}
/** 复制组件 */
const handleCopyComponent = (index: number) => {
const component = cloneDeep(pageComponents.value[index])
component.uid = new Date().getTime()
pageComponents.value.splice(index + 1, 0, component)
}
/**
* 删除组件
* @param index 当前组件index
@ -371,6 +377,7 @@ const handleReset = () => {
if (reload) reload()
emits('reset')
}
//
const previewDialogVisible = ref(false)
const handlePreview = () => {
@ -388,10 +395,12 @@ const setDefaultSelectedComponent = () => {
selectedComponent.value = unref(tabBarComponent)
}
}
watch(
() => [props.showPageConfig, props.showNavigationBar, props.showTabBar],
() => setDefaultSelectedComponent()
)
onMounted(() => setDefaultSelectedComponent())
</script>
<style lang="scss" scoped>

View File

@ -103,6 +103,7 @@ export function usePropertyForm<T>(modelValue: T, emit: Function): { formData: R
}
)
// TODO @疯狂:这个 idea 爆红,看看怎么可以解决哈
return { formData }
}

View File

@ -0,0 +1,3 @@
import OperateLogV2 from './src/OperateLogV2.vue'
export { OperateLogV2 }

View File

@ -0,0 +1,175 @@
<template>
<div class="p-20px">
<el-timeline>
<el-timeline-item
v-for="(log, index) in logDataList"
:key="index"
:timestamp="formatDate(log.createTime)"
placement="top"
>
<div class="el-timeline-right-content">
<el-row>
<el-col :span="24" class="mb-10px">
=======================
<el-tag class="mr-10px" type="success">{{ log.userName }}</el-tag>
<span>{{ log.title }}</span>
=======================
</el-col>
<!-- 先处理一下有几行-->
<template v-for="colNum in log.colSize" :key="colNum + 'col'">
<el-col :span="24" class="mb-10px">
<!-- 处理每一行-->
<template
v-for="(tagVal, index2) in log.tagsContentList.slice(
(colNum - 1) * 3,
3 * colNum
)"
:key="index2"
>
<el-tag class="mx-10px"> {{ tagVal }}</el-tag>
<span>{{ log.contentStrList[index2] }}</span>
</template>
</el-col>
</template>
</el-row>
</div>
<template #dot>
<span :style="{ backgroundColor: getUserTypeColor(log.userType) }" class="dot-node-style">
{{ getDictLabel(DICT_TYPE.USER_TYPE, log.userType)[0] }}
</span>
</template>
</el-timeline-item>
</el-timeline>
</div>
</template>
<script lang="ts" setup>
import { OperateLogV2VO } from '@/api/system/operatelog'
import { formatDate } from '@/utils/formatTime'
import { DICT_TYPE, getDictLabel, getDictObj } from '@/utils/dict'
import { ElTag } from 'element-plus'
defineOptions({ name: 'OperateLogV2' })
const props = defineProps<{
logList: OperateLogV2VO[] //
}>()
const logDataList = ref<OperateLogV2VO[]>([]) //
/** 获得 userType 颜色 */
const getUserTypeColor = (type: number) => {
const dict = getDictObj(DICT_TYPE.USER_TYPE, type)
switch (dict?.colorType) {
case 'success':
return '#67C23A'
case 'info':
return '#909399'
case 'warning':
return '#E6A23C'
case 'danger':
return '#F56C6C'
}
return '#409EFF'
}
// tag
const renderTags = (content: string) => {
let newStr = unref(content).slice() //
newStr = newStr.replaceAll('【】', '【空】').replaceAll('', '') //
const regex = /【([^【】]+)】/g
const fg = '|' //
let match: any[] | null
let matchStr: string[] = []
let oldStr: string[] = []
while ((match = regex.exec(newStr)) !== null) {
matchStr.push(match[1]) //
oldStr.push(match[0]) //
}
// while match
oldStr.forEach((item) => {
newStr = newStr.replace(item, fg)
})
return [newStr.split(fg), matchStr]
}
const initLog = () => {
logDataList.value = props.logList.map((logItem) => {
const keyValue = renderTags(logItem.action)
//
logItem.contentStrList = keyValue[0]
if (keyValue[0][0] === '从') {
logItem.title = logItem.subType
} else {
logItem.title = keyValue[0][0]
logItem.contentStrList.splice(0, 1)
}
logItem.colSize = keyValue[0].length / 3 //
logItem.tagsContentList = keyValue[1]
return logItem
})
}
watch(
() => props.logList.length,
(newObj) => {
if (newObj) {
initLog()
console.log(logDataList.value)
}
},
{
immediate: true,
deep: true
}
)
</script>
<style lang="scss" scoped>
// 线
:deep(.el-timeline) {
margin: 10px 0 0 160px;
.el-timeline-item__wrapper {
position: relative;
top: -20px;
.el-timeline-item__timestamp {
position: absolute !important;
top: 10px;
left: -150px;
}
}
.el-timeline-right-content {
display: flex;
align-items: center;
min-height: 30px;
padding: 10px;
background-color: #fff;
&::before {
position: absolute;
top: 10px;
left: 13px; /* 将伪元素水平居中 */
border-color: transparent #fff transparent transparent; /* 尖角颜色,左侧朝向 */
border-style: solid;
border-width: 8px; /* 调整尖角大小 */
content: ''; /* 必须设置 content 属性 */
}
}
}
.dot-node-style {
position: absolute;
left: -5px;
display: flex;
width: 20px;
height: 20px;
font-size: 10px;
color: #fff;
border-radius: 50%;
justify-content: center;
align-items: center;
}
</style>

View File

@ -2,25 +2,27 @@
<div class="upload-file">
<el-upload
ref="uploadRef"
:multiple="props.limit > 1"
name="file"
v-model="valueRef"
v-model:file-list="fileList"
:show-file-list="true"
:auto-upload="autoUpload"
:action="updateUrl"
:auto-upload="autoUpload"
:before-upload="beforeUpload"
:drag="drag"
:headers="uploadHeaders"
:limit="props.limit"
:drag="drag"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
:on-success="handleFileSuccess"
:multiple="props.limit > 1"
:on-error="excelUploadError"
:on-remove="handleRemove"
:on-exceed="handleExceed"
:on-preview="handlePreview"
:on-remove="handleRemove"
:on-success="handleFileSuccess"
:show-file-list="true"
class="upload-file-uploader"
name="file"
>
<el-button type="primary"><Icon icon="ep:upload-filled" />选取文件</el-button>
<el-button type="primary">
<Icon icon="ep:upload-filled" />
选取文件
</el-button>
<template v-if="isShowTip" #tip>
<div style="font-size: 8px">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
@ -35,8 +37,8 @@
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { getAccessToken, getTenantId } from '@/utils/auth'
import type { UploadInstance, UploadUserFile, UploadProps, UploadRawFile } from 'element-plus'
import { isArray, isString } from '@/utils/is'
import type { UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
import { isString } from '@/utils/is'
defineOptions({ name: 'UploadFile' })
@ -54,8 +56,8 @@ const props = defineProps({
drag: propTypes.bool.def(false), //
isShowTip: propTypes.bool.def(true) //
})
// ========== ==========
const valueRef = ref(props.modelValue)
const uploadRef = ref<UploadInstance>()
const uploadList = ref<UploadUserFile[]>([])
const fileList = ref<UploadUserFile[]>([])
@ -64,6 +66,7 @@ const uploadHeaders = ref({
Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId()
})
//
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
if (fileList.value.length >= props.limit) {
@ -97,12 +100,10 @@ const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
//
const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
message.success('上传成功')
const fileListNew = fileList.value
fileListNew.pop()
fileList.value = fileListNew
fileList.value.shift()
uploadList.value.push({ name: res.data, url: res.data })
if (uploadList.value.length == uploadNumber.value) {
fileList.value = fileList.value.concat(uploadList.value)
fileList.value.push(...uploadList.value)
uploadList.value = []
uploadNumber.value = 0
emitUpdateModelValue()
@ -131,29 +132,25 @@ const handlePreview: UploadProps['onPreview'] = (uploadFile) => {
//
watch(
() => props.modelValue,
() => {
const files: string[] = []
// 1
if (isString(props.modelValue)) {
// 1.1
if (props.modelValue.includes(',')) {
files.concat(props.modelValue.split(','))
} else if (props.modelValue.length > 0) {
files.push(props.modelValue)
}
} else if (isArray(props.modelValue)) {
// 2
files.concat(props.modelValue)
} else if (props.modelValue == null) {
// 3undefined
} else {
throw new Error('不支持的 modelValue 类型')
(val: string | string[]) => {
if (!val) {
fileList.value = [] // fix
return
}
fileList.value = files.map((url: string) => {
return { url, name: url.substring(url.lastIndexOf('/') + 1) } as UploadUserFile
})
fileList.value = [] //
// 1
if (isString(val)) {
fileList.value.push(
...val.split(',').map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
)
}
// 2
fileList.value.push(
...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
)
},
{ immediate: true }
{ immediate: true, deep: true }
)
//
const emitUpdateModelValue = () => {
@ -166,7 +163,7 @@ const emitUpdateModelValue = () => {
emit('update:modelValue', result)
}
</script>
<style scoped lang="scss">
<style lang="scss" scoped>
.upload-file-uploader {
margin-bottom: 5px;
}

View File

@ -88,7 +88,7 @@ service.interceptors.request.use(
// response 拦截器
service.interceptors.response.use(
async (response: AxiosResponse<any>) => {
const { data } = response
let { data } = response
const config = response.config
if (!data) {
// 返回“[HTTP]请求没有返回值”;
@ -96,14 +96,18 @@ service.interceptors.response.use(
}
const { t } = useI18n()
// 未设置状态码则默认成功状态
const code = data.code || result_code
// 二进制数据则直接返回
// 二进制数据则直接返回,例如说 Excel 导出
if (
response.request.responseType === 'blob' ||
response.request.responseType === 'arraybuffer'
) {
return response.data
// 注意:如果导出的响应为 json说明可能失败了不直接返回进行下载
if (response.data.type !== 'application/json') {
return response.data
}
data = await new Response(response.data).json()
}
const code = data.code || result_code
// 获取错误信息
const msg = data.msg || errorCode[code] || errorCode['default']
if (ignoreMsgs.indexOf(msg) !== -1) {

View File

@ -472,7 +472,9 @@ const remainingRouter: AppRouteRecordRaw[] = [
meta: {
title: '模板装修',
noCache: true,
hidden: true
hidden: true,
// TODO @疯狂:建议 menu 那的 /mall/promotion/diy-template/diy-template 改成 /mall/promotion/diy/template
activeMenu: '/mall/promotion/diy-template/diy-template'
},
component: () => import('@/views/mall/promotion/diy/template/decorate.vue')
},
@ -482,7 +484,9 @@ const remainingRouter: AppRouteRecordRaw[] = [
meta: {
title: '页面装修',
noCache: true,
hidden: true
hidden: true,
// TODO @疯狂:建议 menu 那的 /mall/promotion/diy-template/diy-page 改成 /mall/promotion/diy/page
activeMenu: '/mall/promotion/diy-template/diy-page'
},
component: () => import('@/views/mall/promotion/diy/page/decorate.vue')
}

View File

@ -1,13 +1,13 @@
<template>
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="请假类型" prop="type">
<el-select v-model="formData.type" placeholder="请选择请假类型" clearable>
<el-select v-model="formData.type" clearable placeholder="请选择请假类型">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)"
:key="dict.value"
@ -18,27 +18,27 @@
</el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker
clearable
v-model="formData.startTime"
clearable
placeholder="请选择开始时间"
type="datetime"
value-format="x"
placeholder="请选择开始时间"
/>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker
clearable
v-model="formData.endTime"
clearable
placeholder="请选择结束时间"
type="datetime"
value-format="x"
placeholder="请选择结束时间"
/>
</el-form-item>
<el-form-item label="原因" prop="reason">
<el-input v-model="formData.reason" type="textarea" placeholder="请输请假原因" />
<el-input v-model="formData.reason" placeholder="请输请假原因" type="textarea" />
</el-form-item>
<el-form-item>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
</el-form-item>
</el-form>
</template>
@ -50,9 +50,6 @@ import { useTagsViewStore } from '@/store/modules/tagsView'
defineOptions({ name: 'BpmOALeaveCreate' })
const message = useMessage() //
const { delView } = useTagsViewStore() //
const { currentRoute } = useRouter() //
const formLoading = ref(false) // 12
const formData = ref({
type: undefined,
@ -67,7 +64,8 @@ const formRules = reactive({
endTime: [{ required: true, message: '请假结束时间不能为空', trigger: 'change' }]
})
const formRef = ref() // Ref
const { delView } = useTagsViewStore() //
const { push, currentRoute } = useRouter() //
/** 提交表单 */
const submitForm = async () => {
//
@ -82,6 +80,7 @@ const submitForm = async () => {
message.success('发起成功')
// Tab
delView(unref(currentRoute))
await push({ name: 'BpmOALeave' })
} finally {
formLoading.value = false
}

View File

@ -2,18 +2,18 @@
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="68px"
>
<el-form-item label="请假类型" prop="type">
<el-select
v-model="queryParams.type"
placeholder="请选择请假类型"
clearable
class="!w-240px"
clearable
placeholder="请选择请假类型"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)"
@ -26,16 +26,16 @@
<el-form-item label="申请时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="结果" prop="result">
<el-select v-model="queryParams.result" placeholder="请选择结果" clearable class="!w-240px">
<el-select v-model="queryParams.result" class="!w-240px" clearable placeholder="请选择结果">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)"
:key="dict.value"
@ -47,17 +47,24 @@
<el-form-item label="原因" prop="reason">
<el-input
v-model="queryParams.reason"
placeholder="请输入原因"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
clearable
placeholder="请输入原因"
@keyup.enter="handleQuery"
/>
</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" plain @click="handleCreate()">
<Icon icon="ep:plus" class="mr-5px" /> 发起请假
<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 plain type="primary" @click="handleCreate()">
<Icon class="mr-5px" icon="ep:plus" />
发起请假
</el-button>
</el-form-item>
</el-form>
@ -66,63 +73,63 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="申请编号" align="center" prop="id" />
<el-table-column label="状态" align="center" prop="result">
<el-table-column align="center" label="申请编号" prop="id" />
<el-table-column align="center" label="状态" prop="result">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
</template>
</el-table-column>
<el-table-column
label="开始时间"
:formatter="dateFormatter"
align="center"
label="开始时间"
prop="startTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column
label="结束时间"
:formatter="dateFormatter"
align="center"
label="结束时间"
prop="endTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="请假类型" align="center" prop="type">
<el-table-column align="center" label="请假类型" prop="type">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="原因" align="center" prop="reason" />
<el-table-column align="center" label="原因" prop="reason" />
<el-table-column
label="申请时间"
:formatter="dateFormatter"
align="center"
label="申请时间"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center" width="200">
<el-table-column align="center" label="操作" width="200">
<template #default="scope">
<el-button
v-hasPermi="['bpm:oa-leave:query']"
link
type="primary"
@click="handleDetail(scope.row)"
v-hasPermi="['bpm:oa-leave:query']"
>
详情
</el-button>
<el-button
v-hasPermi="['bpm:oa-leave:query']"
link
type="primary"
@click="handleProcessDetail(scope.row)"
v-hasPermi="['bpm:oa-leave:query']"
>
进度
</el-button>
<el-button
v-if="scope.row.result === 1"
v-hasPermi="['bpm:oa-leave:create']"
link
type="danger"
@click="cancelLeave(scope.row)"
v-hasPermi="['bpm:oa-leave:create']"
v-if="scope.row.result === 1"
>
取消
</el-button>
@ -131,9 +138,9 @@
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
@ -228,6 +235,14 @@ const handleProcessDetail = (row) => {
})
}
// fix:
watch(
() => router.currentRoute.value,
() => {
getList()
}
)
/** 初始化 **/
onMounted(() => {
getList()

View File

@ -62,12 +62,12 @@
</el-row>
</el-popover>
</el-form-item>
<!-- TODO @ljlleoidea 红色的报错可以解决下 -->
<el-form-item label="商机状态类型" prop="statusTypeId">
<el-select
v-model="formData.statusTypeId"
placeholder="请选择商机状态类型"
clearable
size="small"
@change="changeBusinessStatusType"
>
<el-option
@ -79,7 +79,7 @@
</el-select>
</el-form-item>
<el-form-item label="商机状态" prop="statusId">
<el-select v-model="formData.statusId" placeholder="请选择商机状态" clearable size="small">
<el-select v-model="formData.statusId" placeholder="请选择商机状态" clearable>
<el-option
v-for="item in businessStatusList"
:key="item.id"
@ -242,7 +242,8 @@ const queryParams = reactive({
mobile: null,
industryId: null,
level: null,
source: null
source: null,
pool: false
})
//
const showCustomer = ref(false)
@ -251,12 +252,12 @@ const openCustomerSelect = () => {
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 {

View File

@ -5,11 +5,32 @@
<Icon class="mr-5px" icon="ep:opportunity" />
创建商机
</el-button>
<el-button
@click="openBusinessModal"
v-hasPermi="['crm:contact:create-business']"
v-if="queryParams.contactId"
>
<Icon class="mr-5px" icon="ep:circle-plus" />关联
</el-button>
<el-button
@click="deleteContactBusinessList"
v-hasPermi="['crm:contact:delete-business']"
v-if="queryParams.contactId"
>
<Icon class="mr-5px" icon="ep:remove" />解除关联
</el-button>
</el-row>
<!-- 列表 -->
<ContentWrap class="mt-10px">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table
ref="businessRef"
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
>
<el-table-column type="selection" width="55" v-if="queryParams.contactId" />
<el-table-column label="商机名称" fixed="left" align="center" prop="name">
<template #default="scope">
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
@ -33,17 +54,28 @@
<!-- 表单弹窗添加 -->
<BusinessForm ref="formRef" @success="getList" />
<!-- 关联商机选择弹框 -->
<BusinessListModal
ref="businessModalRef"
:customer-id="props.customerId"
@success="createContactBusinessList"
/>
</template>
<script setup lang="ts">
import * as BusinessApi from '@/api/crm/business'
import * as ContactApi from '@/api/crm/contact'
import BusinessForm from './../BusinessForm.vue'
import { BizTypeEnum } from '@/api/crm/permission'
import { fenToYuanFormat } from '@/utils/formatter'
import BusinessListModal from './BusinessListModal.vue'
const message = useMessage() //
defineOptions({ name: 'CrmBusinessList' })
const props = defineProps<{
bizType: number //
bizId: number //
customerId?: number // customerId
}>()
const loading = ref(true) //
@ -52,7 +84,8 @@ const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
customerId: undefined as unknown // undefined + number
customerId: undefined as unknown, // undefined + number
contactId: undefined as unknown // undefined + number
})
/** 查询列表 */
@ -61,6 +94,7 @@ const getList = async () => {
try {
//
queryParams.customerId = undefined
queryParams.contactId = undefined
//
let data = { list: [], total: 0 }
switch (props.bizType) {
@ -68,6 +102,10 @@ const getList = async () => {
queryParams.customerId = props.bizId
data = await BusinessApi.getBusinessPageByCustomer(queryParams)
break
case BizTypeEnum.CRM_CONTACT:
queryParams.contactId = props.bizId
data = await BusinessApi.getBusinessPageByContact(queryParams)
break
default:
return
}
@ -96,6 +134,41 @@ const openDetail = (id: number) => {
push({ name: 'CrmBusinessDetail', params: { id } })
}
/** 打开联系人与商机的关联弹窗 */
const businessModalRef = ref()
const openBusinessModal = () => {
businessModalRef.value.open()
}
const createContactBusinessList = async (businessIds: number[]) => {
const data = {
contactId: props.bizId,
businessIds: businessIds
} as ContactApi.ContactBusinessReqVO
businessRef.value.getSelectionRows().forEach((row: BusinessApi.BusinessVO) => {
data.businessIds.push(row.id)
})
await ContactApi.createContactBusinessList(data)
//
message.success('关联商机成功')
handleQuery()
}
/** 解除联系人与商机的关联 */
const businessRef = ref()
const deleteContactBusinessList = async () => {
const data = {
contactId: props.bizId,
businessIds: businessRef.value.getSelectionRows().map((row: BusinessApi.BusinessVO) => row.id)
} as ContactApi.ContactBusinessReqVO
if (data.businessIds.length === 0) {
return message.error('未选择商机')
}
await ContactApi.deleteContactBusinessList(data)
//
message.success('取关商机成功')
handleQuery()
}
/** 监听打开的 bizId + bizType从而加载最新的列表 */
watch(
() => [props.bizId, props.bizType],

View File

@ -1,5 +1,5 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<Dialog title="关联商机" v-model="dialogVisible">
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
@ -29,7 +29,6 @@
</ContentWrap>
<!-- 列表 -->
<!-- TODO @zyna字段按照他们对齐下 -->
<ContentWrap class="mt-10px">
<el-table
v-loading="loading"
@ -75,16 +74,16 @@
</template>
<script setup lang="ts">
import * as BusinessApi from '@/api/crm/business'
import BusinessForm from '../../business/BusinessForm.vue'
import BusinessForm from '../BusinessForm.vue'
import { fenToYuanFormat } from '@/utils/formatter'
// TODO @zyna
import * as ContactbusinesslinkApi from '@/api/crm/contactbusinesslink'
const message = useMessage() //
defineOptions({ name: 'CrmBusinessLinkContactList' })
const message = useMessage() //
const props = defineProps<{
customerId: number
}>()
defineOptions({ name: 'BusinessListModal' })
const dialogVisible = ref(false) //
const dialogTitle = ref('') // TODO @zyna
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
@ -93,17 +92,14 @@ const formLoading = ref(false) // 表单的加载中1修改时的数据加
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
// TODO @zyna customerId
name: undefined
name: undefined,
customerId: props.customerId
})
const contactIdProp = ref(0) //
/** 打开弹窗 */
const open = async (contactId: number) => {
const open = async () => {
dialogVisible.value = true
contactIdProp.value = contactId
// TODO @zyna await idea
getList()
await getList()
}
defineExpose({ open }) // open
@ -111,7 +107,7 @@ defineExpose({ open }) // 提供 open 方法,用于打开弹窗
const getList = async () => {
loading.value = true
try {
const data = await BusinessApi.getBusinessPage(queryParams)
const data = await BusinessApi.getBusinessPageByCustomer(queryParams)
list.value = data.list
total.value = data.total
} finally {
@ -141,25 +137,14 @@ const openForm = () => {
const emit = defineEmits(['success']) // success
const businessRef = ref()
const submitForm = async () => {
// TODO @zyna if return
if (businessRef.value.getSelectionRows().length === 0) {
message.success('未选择商机')
} else {
// TODO @zyna postData ref
const postData = ref<ContactbusinesslinkApi.ContactBusinessLinkVO[]>([])
businessRef.value.getSelectionRows().forEach((element) => {
// TODO @zyna push data
let data = {
id: undefined,
businessId: element.id,
contactId: contactIdProp.value
}
postData.value.push(data)
})
await ContactbusinesslinkApi.createContactBusinessLinkBatch(postData.value)
dialogVisible.value = false
emit('success')
const businessIds = businessRef.value
.getSelectionRows()
.map((row: BusinessApi.BusinessVO) => row.id)
if (businessIds.length === 0) {
return message.error('未选择商机')
}
dialogVisible.value = false
emit('success', businessIds)
}
/** 打开联系人详情 */

View File

@ -49,8 +49,8 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="12"
><el-form-item label="性别" prop="sex">
<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)"
@ -58,8 +58,9 @@
:label="dict.label"
:value="dict.value"
/>
</el-select> </el-form-item
></el-col>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
@ -72,8 +73,8 @@
</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 label="电话" prop="telephone">
<el-input v-model="formData.telephone" placeholder="请输入电话" style="width: 215px" />
</el-form-item>
</el-col>
</el-row>
@ -104,10 +105,10 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下次联系时间" prop="nextTime">
<el-form-item label="下次联系时间" prop="contactNextTime">
<el-date-picker
v-model="formData.nextTime"
type="date"
v-model="formData.contactNextTime"
type="datetime"
value-format="x"
placeholder="选择下次联系时间"
/>
@ -126,15 +127,17 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="地址" prop="address">
<el-form-item label="地址" prop="detailAddress">
<el-input
input-style="width:190px;"
v-model="formData.address"
v-model="formData.detailAddress"
placeholder="请输入地址"
/>
</el-form-item>
</el-col> </el-row
><el-row>
</el-col>
</el-row>
<!-- TODO @zyna解决下 ide 报错 -->
<el-row>
<el-col :span="12">
<el-form-item label="直属上级" prop="parentId">
<el-select v-model="formData.parentId" placeholder="请选择">
@ -152,10 +155,11 @@
<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-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)"
@ -169,8 +173,8 @@
</el-col>
</el-row>
<el-row>
<el-col :span="24"
><el-form-item label="备注" prop="remark">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
</el-col>
@ -192,19 +196,20 @@ import { defaultProps } from '@/utils/tree'
const { t } = useI18n() //
const message = useMessage() //
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,
contactNextTime: undefined,
mobile: undefined,
telephone: undefined,
email: undefined,
customerId: undefined,
customerName: undefined,
address: undefined,
detailAddress: undefined,
remark: undefined,
ownerUserId: undefined,
lastTime: undefined,
@ -279,13 +284,14 @@ const submitForm = async () => {
/** 重置表单 */
const resetForm = () => {
// TODO zynaide
formData.value = {
nextTime: undefined,
contactNextTime: undefined,
mobile: undefined,
telephone: undefined,
email: undefined,
customerId: undefined,
address: undefined,
detailAddress: undefined,
remark: undefined,
ownerUserId: undefined,
lastTime: undefined,

View File

@ -1,14 +1,5 @@
<!--
* @Author: zyna
* @Date: 2023-12-02 13:08:57
* @LastEditTime: 2023-12-03 13:47:16
* @FilePath: \yudao-ui-admin-vue3\src\views\crm\contact\detail\ContactDetailsHeader.vue
* @Description:
-->
<!-- TODO @zyna上面这个不加哈 -->
<template>
<!-- TODO @zynaloading 缺了 -->
<div v-loading="loading">
<div>
<div class="flex items-start justify-between">
<div>
<el-col>
@ -48,7 +39,6 @@
import * as ContactApi from '@/api/crm/contact'
import ContactForm from '@/views/crm/contact/ContactForm.vue'
import { formatDate } from '@/utils/formatTime'
//
const formRef = ref()
const openForm = (type: string, id?: number) => {

View File

@ -1,4 +1,5 @@
<template>
<!-- TODO @zyna少了一个外边框 -->
<el-collapse v-model="activeNames">
<el-collapse-item name="basicInfo">
<template #title>

View File

@ -10,18 +10,22 @@
<PermissionList :biz-id="contact.id!" :biz-type="BizTypeEnum.CRM_CONTACT" />
</el-tab-pane>
<el-tab-pane label="商机" lazy>
<BusinessList :biz-id="contact.id!" :biz-type="BizTypeEnum.CRM_CONTACT" />
<BusinessList
:biz-id="contact.id!"
:biz-type="BizTypeEnum.CRM_CONTACT"
:customer-id="contact.customerId"
/>
</el-tab-pane>
</el-tabs>
</el-col>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { ElMessage } from 'element-plus' // TODO @zyna使 hook message
import { useTagsViewStore } from '@/store/modules/tagsView'
import * as ContactApi from '@/api/crm/contact'
import ContactDetailsHeader from '@/views/crm/contact/detail/ContactDetailsHeader.vue'
import ContactDetailsInfo from '@/views/crm/contact/detail/ContactDetailsInfo.vue'
import BusinessList from '@/views/crm/contactBusinessLink/components/BusinessListByContact.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'

View File

@ -11,6 +11,7 @@
<el-form-item label="客户" prop="customerId">
<el-select
v-model="queryParams.customerId"
class="!w-240px"
placeholder="请选择客户"
value-key="id"
lable-key="name"
@ -28,56 +29,55 @@
<el-form-item label="姓名" prop="name">
<el-input
v-model="queryParams.name"
class="!w-240px"
placeholder="请输入姓名"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input
v-model="queryParams.mobile"
class="!w-240px"
placeholder="请输入手机号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="座机" prop="telephone">
<el-form-item label="电话" prop="telephone">
<el-input
v-model="queryParams.telephone"
class="!w-240px"
placeholder="请输入电话"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="QQ" prop="qq">
<el-input
v-model="queryParams.qq"
class="!w-240px"
placeholder="请输入QQ"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="微信" prop="wechat">
<el-input
v-model="queryParams.wechat"
class="!w-240px"
placeholder="请输入微信"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="电子邮箱" prop="email">
<el-input
v-model="queryParams.email"
class="!w-240px"
placeholder="请输入电子邮箱"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
@ -102,53 +102,57 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="姓名" fixed="left" align="center" prop="name">
<el-table-column label="姓名" fixed="left" align="center" prop="name" width="140">
<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="客户" fixed="left" align="center" prop="customerName" />
<el-table-column label="客户名称" fixed="left" align="center" prop="customerName" width="120">
<template #default="scope">
<el-link
type="primary"
:underline="false"
@click="openCustomerDetail(scope.row.customerId)"
>
{{ scope.row.customerName }}
</el-link>
</template>
</el-table-column>
<el-table-column label="手机" align="center" prop="mobile" width="120" />
<el-table-column label="电话" align="center" prop="telephone" width="120" />
<el-table-column label="邮箱" align="center" prop="email" width="120" />
<el-table-column label="职位" align="center" prop="post" width="120" />
<el-table-column label="地址" align="center" prop="detailAddress" width="120" />
<el-table-column
:formatter="dateFormatter"
align="center"
label="下次联系时间"
prop="contactNextTime"
width="180px"
/>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="关键决策人" align="center" prop="master" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
</template>
</el-table-column>
<el-table-column label="直属上级" align="center" prop="parentName" width="140" />
<el-table-column
label="最后跟进时间"
align="center"
prop="contactLastTime"
:formatter="dateFormatter"
width="180px"
/>
<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="master">
<template #default="scope">
<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="wechat" />
<el-table-column label="邮箱" align="center" prop="email" />
<el-table-column label="地址" align="center" prop="address" />
<el-table-column
label="下次联系时间"
align="center"
prop="nextTime"
width="180px"
:formatter="dateFormatter"
/>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column
label="最后跟进时间"
align="center"
prop="lastTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="负责人" align="center" prop="ownerUserId">
<template #default="scope">
{{ scope.row.ownerUserName }}
</template>
</el-table-column>
<!-- <el-table-column label="所属部门" align="center" prop="ownerUserId" /> -->
<el-table-column label="负责人" align="center" prop="ownerUserName" width="120" />
<el-table-column label="创建人" align="center" prop="creatorName" width="120" />
<el-table-column
label="更新时间"
align="center"
@ -163,21 +167,10 @@
:formatter="dateFormatter"
width="180px"
/>
<!-- <el-table-column
label="创建人"
align="center"
prop="creator"
:formatter="dateFormatter"
width="180px"
>
<template #default="scope">
{{ userList.find((user) => user.id === scope.row.creator)?.nickname }}
</template>
</el-table-column> -->
<el-table-column label="操作" align="center" fixed="right" width="200">
<template #default="scope">
<el-button
plain
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['crm:contact:update']"
@ -185,7 +178,7 @@
编辑
</el-button>
<el-button
plain
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['crm:contact:delete']"
@ -228,23 +221,13 @@ const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
nextTime: [],
mobile: null,
telephone: null,
email: null,
customerId: null,
address: null,
remark: null,
ownerUserId: null,
createTime: [],
lastTime: [],
parentId: null,
name: null,
post: null,
qq: null,
wechat: null,
sex: null,
policyMakers: null
mobile: undefined,
telephone: undefined,
email: undefined,
customerId: undefined,
name: undefined,
qq: undefined,
wechat: undefined
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
@ -307,11 +290,15 @@ const handleExport = async () => {
}
}
/** 打开客户详情 */
/** 打开联系人详情 */
const { push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'CrmContactDetail', params: { id } })
}
/** 打开客户详情 */
const openCustomerDetail = (id: number) => {
push({ name: 'CrmCustomerDetail', params: { id } })
}
/** 初始化 **/
onMounted(async () => {

View File

@ -1,143 +0,0 @@
<template>
<!-- 操作栏 -->
<el-row justify="end">
<el-button @click="openForm">
<Icon class="mr-5px" icon="ep:opportunity" />
创建商机
</el-button>
<el-button @click="openBusinessLink"> 关联 </el-button>
<el-button @click="deleteBusinessLink"> 解除关联 </el-button>
</el-row>
<!-- 列表 -->
<ContentWrap class="mt-10px">
<el-table
v-loading="loading"
ref="businessRef"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
>
<el-table-column type="selection" width="55" />
<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" />
<!---->
<BusinessLink ref="businessLinkRef" @success="getList" />
</template>
<script setup lang="ts">
import * as ContactBusinessLinkApi from '@/api/crm/contactbusinesslink'
import BusinessForm from '../../business/BusinessForm.vue'
import { BizTypeEnum } from '@/api/crm/permission'
import { fenToYuanFormat } from '@/utils/formatter'
import BusinessLink from './BusinessLinkContactList.vue'
const message = useMessage() //
// TODO @zyna BusinessList
defineOptions({ name: 'CrmBusinessContactList' })
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,
contactId: undefined as unknown // undefined + number
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
//
queryParams.contactId = undefined
//
let data = { list: [], total: 0 }
switch (props.bizType) {
case BizTypeEnum.CRM_CONTACT:
queryParams.contactId = props.bizId
data = await ContactBusinessLinkApi.getBusinessByContactPage(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 businessLinkRef = ref()
const openBusinessLink = () => {
businessLinkRef.value.open(props.bizId)
}
/**解除关联 */
const businessRef = ref()
const deleteBusinessLink = async () => {
if (businessRef.value.getSelectionRows().length === 0) {
message.success('未选择商机')
} else {
const postData = ref<ContactBusinessLinkApi.ContactBusinessLinkVO[]>([])
businessRef.value.getSelectionRows().forEach((element) => {
let data = {
id: undefined,
businessId: element.id,
contactId: props.bizId
}
postData.value.push(data)
})
await ContactBusinessLinkApi.deleteContactBusinessLink(postData.value)
handleQuery()
}
}
/** 打开联系人详情 */
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

@ -1,11 +1,11 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<Dialog v-model="dialogVisible" :title="dialogTitle">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-row>
<el-col :span="12">
@ -17,7 +17,7 @@
<el-form-item label="所属行业" prop="industryId">
<el-select v-model="formData.industryId" placeholder="请选择所属行业">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -31,7 +31,7 @@
<el-form-item label="客户来源" prop="source">
<el-select v-model="formData.source" placeholder="请选择客户来源">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -43,7 +43,7 @@
<el-form-item label="客户等级" prop="level">
<el-select v-model="formData.level" placeholder="请选择客户等级">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -120,9 +120,9 @@
<el-form-item label="下次联系时间" prop="contactNextTime">
<el-date-picker
v-model="formData.contactNextTime"
type="date"
value-format="x"
placeholder="选择下次联系时间"
type="datetime"
value-format="x"
/>
</el-form-item>
</el-col>
@ -139,13 +139,13 @@
</el-col>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as CustomerApi from '@/api/crm/customer'
import * as AreaApi from '@/api/system/area'
import { defaultProps } from '@/utils/tree'

View File

@ -11,10 +11,19 @@
</div>
<div>
<!-- 右上按钮 -->
<el-button v-hasPermi="['crm:customer:update']" @click="openForm(customer.id)">
<el-button
type="primary"
v-hasPermi="['crm:customer:update']"
@click="openForm(customer.id)"
>
编辑
</el-button>
<!-- TODO @puhui999转移的操作接入 -->
<el-button type="primary" @click="transfer">转移</el-button>
<!-- TODO @puhui999修改成交状态的接入 -->
<el-button>更改成交状态</el-button>
<el-button v-if="customer.lockStatus" @click="handleUnlock(customer.id!)">解锁</el-button>
<el-button v-else @click="handleLock(customer.id!)">锁定</el-button>
</div>
</div>
</div>
@ -26,26 +35,29 @@
<el-descriptions-item label="成交状态">
{{ customer.dealStatus ? '已成交' : '未成交' }}
</el-descriptions-item>
<el-descriptions-item label="负责人">{{ customer.ownerUserName }} </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-item label="首要联系人电话">{{ customer.mobile }}</el-descriptions-item>
</el-descriptions>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<CustomerForm ref="formRef" @success="emit('refresh')" />
</template>
<script setup lang="ts">
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import * as CustomerApi from '@/api/crm/customer'
import CustomerForm from '../CustomerForm.vue'
defineOptions({ name: 'CustomerDetailsHeader' })
const { customer, loading } = defineProps<{
customer: CustomerApi.CustomerVO //
loading: boolean //
}>()
const message = useMessage() //
/** 修改操作 */
const formRef = ref()
@ -53,5 +65,20 @@ const openForm = (id?: number) => {
formRef.value.open('update', id)
}
/** 锁定操作 */
const handleLock = async (id: number) => {
await CustomerApi.lockCustomer(id, true)
message.success('锁定成功')
emit('refresh')
}
/** 解锁操作 */
const handleUnlock = async (id: number) => {
console.log(customer, '=======')
await CustomerApi.lockCustomer(id, false)
message.success('解锁成功')
emit('refresh')
}
const emit = defineEmits(['refresh']) // success
</script>

View File

@ -1,6 +1,6 @@
<template>
<ContentWrap>
<el-collapse class="" v-model="activeNames">
<el-collapse v-model="activeNames" class="">
<el-collapse-item name="basicInfo">
<template #title>
<span class="text-base font-bold">基本信息</span>
@ -20,11 +20,11 @@
</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.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>
@ -38,8 +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">
@ -47,8 +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>
@ -60,15 +60,16 @@
</el-collapse>
</ContentWrap>
</template>
<script setup lang="ts">
<script lang="ts" setup>
import * as CustomerApi from '@/api/crm/customer'
import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
defineOptions({ name: 'CustomerDetailsInfo' })
const { customer } = defineProps<{
customer: CustomerApi.CustomerVO //
}>()
const activeNames = ref(['basicInfo', 'systemInfo']) //
</script>
<style scoped lang="scss"></style>
<style lang="scss" scoped></style>

View File

@ -5,7 +5,9 @@
<el-tab-pane label="详细资料">
<CustomerDetailsInfo :customer="customer" />
</el-tab-pane>
<el-tab-pane label="操作日志" lazy>TODO 待开发</el-tab-pane>
<el-tab-pane label="操作日志">
<OperateLogV2 :log-list="logList" />
</el-tab-pane>
<el-tab-pane label="联系人" lazy>
<ContactList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
</el-tab-pane>
@ -38,24 +40,39 @@ import ReceivableList from '@/views/crm/receivable/components/ReceivableList.vue
import ReceivablePlanList from '@/views/crm/receivable/plan/components/ReceivablePlanList.vue' //
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' //
import { BizTypeEnum } from '@/api/crm/permission'
import { OperateLogV2VO } from '@/api/system/operatelog'
defineOptions({ name: 'CrmCustomerDetail' })
const route = useRoute()
const id = Number(route.params.id) //
const loading = ref(true) //
/** 获取详情 */
const customer = ref<CustomerApi.CustomerVO>({} as CustomerApi.CustomerVO) //
const getCustomer = async (id: number) => {
loading.value = true
try {
customer.value = await CustomerApi.getCustomer(id)
await getOperateLog(id)
} finally {
loading.value = false
}
}
const logList = ref<OperateLogV2VO[]>([]) //
/**
* 获取操作日志
*/
const getOperateLog = async (customerId: number) => {
if (!customerId) {
return
}
const data = await CustomerApi.getOperateLogPage({
pageNo: 1,
pageSize: 10,
bizId: customerId
})
logList.value = data.list
}
/** 初始化 */
const { delView } = useTagsViewStore() //
const { currentRoute } = useRouter() //

View File

@ -101,38 +101,24 @@
</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" />
</template>
</el-table-column>
<el-table-column align="center" label="手机" prop="mobile" width="120" />
<el-table-column align="center" label="电话" prop="telephone" width="120" />
<el-table-column align="center" label="客户来源" 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 align="center" label="所属行业" prop="industryId" width="120">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
</template>
</el-table-column>
<el-table-column align="center" label="客户等级" 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-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="ownerUserDeptName" />
<el-table-column align="center" label="创建人" prop="creatorName" />
<el-table-column
:formatter="dateFormatter"
align="center"
label="创建时间"
prop="createTime"
width="180px"
/>
<el-table-column align="center" label="成交状态" prop="dealStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" />
</template>
</el-table-column>
<el-table-column align="center" label="网址" prop="website" width="200" />
<el-table-column
:formatter="dateFormatter"
align="center"
@ -140,6 +126,13 @@
prop="contactNextTime"
width="180px"
/>
<el-table-column align="center" label="备注" prop="remark" width="200" />
<el-table-column align="center" label="成交状态" prop="dealStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" />
</template>
</el-table-column>
<!-- TODO @puhui999距进入公海天数 -->
<el-table-column
:formatter="dateFormatter"
align="center"
@ -147,12 +140,23 @@
prop="contactLastTime"
width="180px"
/>
<el-table-column align="center" label="锁定状态" prop="lockStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.lockStatus" />
</template>
</el-table-column>
<!-- TODO @wanwan 距进入公海天数 -->
<el-table-column
:formatter="dateFormatter"
align="center"
label="创建时间"
prop="updateTime"
width="180px"
/>
<el-table-column
:formatter="dateFormatter"
align="center"
label="创建时间"
prop="createTime"
width="180px"
/>
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
<el-table-column align="center" fixed="right" label="操作" min-width="150">
<template #default="scope">
<el-button

View File

@ -56,12 +56,12 @@
</Dialog>
</template>
<script setup lang="ts">
import * as CustomerLimitConfigApi from '@/api/crm/customerLimitConfig'
import * as CustomerLimitConfigApi from '@/api/crm/customer/limitConfig'
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'
import { LimitConfType } from '@/api/crm/customer/limitConfig'
const { t } = useI18n() //
const message = useMessage() //
@ -72,7 +72,7 @@ const formLoading = ref(false) // 表单的加载中1修改时的数据加
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
type: undefined,
type: LimitConfType.CUSTOMER_LOCK_LIMIT, // IDE
userIds: undefined,
deptIds: undefined,
maxCount: undefined,
@ -88,15 +88,11 @@ const deptTree = ref() // 部门树形结构
const userTree = ref() //
/** 打开弹窗 */
const open = async (type: string, id?: number, limitConfType?: LimitConfType) => {
const open = async (type: string, limitConfType: LimitConfType, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
await getDeptTree()
//
await getUserTree()
//
if (id) {
formLoading.value = true
@ -108,6 +104,10 @@ const open = async (type: string, id?: number, limitConfType?: LimitConfType) =>
} else {
formData.value.type = limitConfType
}
//
await getDeptTree()
//
await getUserTree()
}
defineExpose({ open }) // open
@ -141,7 +141,7 @@ const submitForm = async () => {
const resetForm = () => {
formData.value = {
id: undefined,
type: undefined,
type: LimitConfType.CUSTOMER_LOCK_LIMIT,
userIds: undefined,
deptIds: undefined,
maxCount: undefined,

View File

@ -1,7 +1,5 @@
<template>
<el-button type="primary" plain @click="handleQuery">
<Icon icon="ep:refresh" class="mr-5px" /> 刷新
</el-button>
<el-button plain @click="handleQuery"> <Icon icon="ep:refresh" class="mr-5px" /> 刷新 </el-button>
<el-button
type="primary"
plain
@ -87,10 +85,10 @@
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import * as CustomerLimitConfigApi from '@/api/crm/customerLimitConfig'
import CustomerLimitConfigForm from '@/views/crm/config/customerLimitConfig/CustomerLimitConfigForm.vue'
import * as CustomerLimitConfigApi from '@/api/crm/customer/limitConfig'
import CustomerLimitConfigForm from './CustomerLimitConfigForm.vue'
import { DICT_TYPE } from '@/utils/dict'
import { LimitConfType } from '@/api/crm/customerLimitConfig'
import { LimitConfType } from '@/api/crm/customer/limitConfig'
defineOptions({ name: 'CustomerLimitConfigList' })
@ -107,8 +105,6 @@ const queryParams = reactive({
pageSize: 10,
type: confType
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
@ -125,7 +121,7 @@ const getList = async () => {
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id, confType)
formRef.value.open(type, confType, id)
}
/** 删除按钮操作 */

View File

@ -12,8 +12,8 @@
</ContentWrap>
</template>
<script setup lang="ts">
import CustomerLimitConfigList from '@/views/crm/config/customerLimitConfig/CustomerLimitConfigList.vue'
import { LimitConfType } from '@/api/crm/customerLimitConfig'
import CustomerLimitConfigList from './CustomerLimitConfigList.vue'
import { LimitConfType } from '@/api/crm/customer/limitConfig'
defineOptions({ name: 'CrmCustomerLimitConfig' })
</script>

View File

@ -56,7 +56,7 @@
</ContentWrap>
</template>
<script setup lang="ts">
import * as CustomerPoolConfigApi from '@/api/crm/customerPoolConfig'
import * as CustomerPoolConfigApi from '@/api/crm/customer/poolConfig'
import { CardTitle } from '@/components/Card'
defineOptions({ name: 'CrmCustomerPoolConfig' })
@ -67,10 +67,10 @@ const { t } = useI18n() // 国际化
const formLoading = ref(false)
const formData = ref({
enabled: false,
contactExpireDays: 0,
dealExpireDays: 0,
contactExpireDays: undefined,
dealExpireDays: undefined,
notifyEnabled: false,
notifyDays: 0
notifyDays: undefined
})
const formRules = reactive({
enabled: [{ required: true, message: '是否启用客户公海不能为空', trigger: 'blur' }]
@ -100,7 +100,7 @@ const onSubmit = async () => {
//
formLoading.value = true
try {
const data = formData.value as unknown as CustomerPoolConfigApi.CustomerPoolConfigVO
const data = formData.value as CustomerPoolConfigApi.CustomerPoolConfigVO
await CustomerPoolConfigApi.saveCustomerPoolConfig(data)
message.success(t('common.updateSuccess'))
await getConfig()

View File

@ -17,6 +17,7 @@
/>
</el-select>
</el-form-item>
<!-- TODO @puhui999编辑时level 没带过来 -->
<el-form-item label="权限级别" prop="level">
<el-radio-group v-model="formData.level">
<template

View File

@ -24,6 +24,7 @@
:stripe="true"
@selection-change="handleSelectionChange"
>
<!-- TODO @puhui999负责人不允许选中 -->
<el-table-column type="selection" width="55" />
<el-table-column align="center" label="姓名" prop="nickname" />
<el-table-column align="center" label="部门" prop="deptName" />

View File

@ -197,7 +197,7 @@ defineOptions({ name: 'ProductSpuBasicInfoForm' })
const ruleConfig: RuleConfig[] = [
{
name: 'stock',
rule: (arg) => arg >= 1,
rule: (arg) => arg >= 0,
message: '商品库存必须大于等于 1 '
},
{
@ -213,7 +213,7 @@ const ruleConfig: RuleConfig[] = [
{
name: 'costPrice',
rule: (arg) => arg >= 0.01,
message: '商品成本价格必须大于等于 0.01 元!!!'
message: '商品成本价格必须大于等于 0.00 元!!!'
}
]

View File

@ -30,6 +30,7 @@ const getPageDetail = async (id: any) => {
formLoading.value = false
}
}
//
const submitForm = async () => {
//

View File

@ -27,10 +27,11 @@
</DiyEditor>
</template>
<script setup lang="ts">
// TODO @ decorate index.vue
import * as DiyTemplateApi from '@/api/mall/promotion/diy/template'
import * as DiyPageApi from '@/api/mall/promotion/diy/page'
import { useTagsViewStore } from '@/store/modules/tagsView'
import { DiyComponentLibrary, PAGE_LIBS } from '@/components/DiyEditor/util'
import { DiyComponentLibrary, PAGE_LIBS } from '@/components/DiyEditor/util' // DIY DiyEditor
import { toNumber } from 'lodash-es'
/** 装修模板表单 */
@ -51,7 +52,7 @@ const formData = ref<DiyTemplateApi.DiyTemplatePropertyVO>()
const formRef = ref() // Ref
//
const currentFormData = ref<DiyTemplateApi.DiyTemplatePropertyVO | DiyPageApi.DiyPageVO>()
// H5
// H5
const previewUrl = ref('')
//

View File

@ -205,7 +205,7 @@ const handleUse = async (row: DiyTemplateApi.DiyTemplateVO) => {
// 使
await message.confirm(`是否使用模板“${row.name}”?`)
//
await DiyTemplateApi.useDiyTemplate(row.id)
await DiyTemplateApi.useDiyTemplate(row.id!)
message.success('使用成功')
//
await getList()

View File

@ -0,0 +1,113 @@
<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="status">
<el-select v-model="formData.status" placeholder="请选择状态">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="文件内容" prop="content">
<Editor v-model="formData.content" height="150px" />
</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 { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import * as UReportDataApi from '@/api/report/ureport'
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,
status: undefined,
content: undefined,
remark: undefined,
})
const formRules = reactive({
name: [{ required: true, message: '文件名称不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
})
const formRef = ref() // 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 UReportDataApi.getUReportData(id)
} finally {
formLoading.value = false
}
}
}
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 UReportDataApi.UReportDataVO
if (formType.value === 'create') {
await UReportDataApi.createUReportData(data)
message.success(t('common.createSuccess'))
} else {
await UReportDataApi.updateUReportData(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
status: undefined,
content: undefined,
remark: undefined,
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,13 @@
<template>
<ContentWrap>
<IFrame :src="src" />
</ContentWrap>
</template>
<script lang="ts" setup>
import { getAccessToken } from '@/utils/auth'
defineOptions({ name: 'UReportData' })
const BASE_URL = import.meta.env.VITE_BASE_URL
const src = ref(BASE_URL + '/ureport/designer?token=' + getAccessToken())
</script>

View File

@ -0,0 +1,220 @@
<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 label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="queryParams.remark"
placeholder="请输入备注"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-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="['report:ureport-data:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['report:ureport-data: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="ID" align="center" prop="id" />
<el-table-column label="文件名称" align="center" prop="name" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="文件内容" align="center" prop="content" />
<el-table-column label="备注" align="center" prop="remark" />
<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="['report:ureport-data:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['report:ureport-data: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>
<!-- 表单弹窗添加/修改 -->
<UReportDataForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as UReportDataApi from '@/api/report/ureport'
import UReportDataForm from './UReportDataForm.vue'
defineOptions({ name: 'UReportData' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
status: null,
remark: null,
createTime: [],
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await UReportDataApi.getUReportDataPage(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 UReportDataApi.deleteUReportData(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await UReportDataApi.exportUReportData(queryParams)
download.excel(data, 'Ureport2报表.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>