CRM 场地管理详情

pull/853/head
wersd 2025-06-16 22:23:43 +08:00
parent ae637966a4
commit 3ee254dce0
7 changed files with 114 additions and 29 deletions

View File

@ -1,4 +1,5 @@
import request from '@/config/axios' import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/permission'
// 场地信息 VO // 场地信息 VO
export interface StationSiteVO { export interface StationSiteVO {
@ -13,6 +14,8 @@ export interface StationSiteVO {
estimatedPiles: number // 可建充电桩数 estimatedPiles: number // 可建充电桩数
siteType: number // 场地类型 siteType: number // 场地类型
siteStatus: number // 场地状态 siteStatus: number // 场地状态
ownerUserId: number // 责任人编号
ownerUserName?: string // 责任人名称
ownerId: number // 关联的业主方 ownerId: number // 关联的业主方
ownerName: string // 业主方名称 ownerName: string // 业主方名称
managementId: number // 关联的物管方 managementId: number // 关联的物管方
@ -24,7 +27,6 @@ export interface StationSiteVO {
parkingSpaces: number // 车位数 parkingSpaces: number // 车位数
rentPrice: number // 租金价格 rentPrice: number // 租金价格
creator: string // 填报人 creator: string // 填报人
creatorName?: string // 填报人名称
params: string // 参数 params: string // 参数
} }
@ -58,5 +60,12 @@ export const StationSiteApi = {
// 导出场地信息 Excel // 导出场地信息 Excel
exportStationSite: async (params) => { exportStationSite: async (params) => {
return await request.download({ url: `/crm/station-site/export-excel`, params }) return await request.download({ url: `/crm/station-site/export-excel`, params })
}, }
} }
// ======================= 业务操作 =======================
// 转移场地信息
export const transferStationSite = async (data: TransferReqVO) => {
return await request.put({ url: `/crm/station-site/transfer`, data })
}

View File

@ -32,10 +32,9 @@
<Icon icon="ep:refresh" class="mr-5px" />重置 <Icon icon="ep:refresh" class="mr-5px" />重置
</el-button> </el-button>
<el-button v-if="queryParams.bizId !== undefined" v-hasPermi="['crm:follow-up-record:create']" @click="openForm('create')"> <el-button v-if="queryParams.bizId !== undefined" v-hasPermi="['crm:follow-up-record:create']" @click="openForm('create')">
<Icon icon="ep:plus" class="mr-5px" />添加{{ bizLabel }}记录 <Icon icon="ep:plus" class="mr-5px" />添加{{ bizLabel }}
</el-button> </el-button>
</el-form-item> </el-form-item>
{{ queryParams }}
</el-form> </el-form>
@ -48,7 +47,7 @@
placement="top" placement="top"
> >
<span>{{ bizLabel }}</span>{{ item.creatorName }} <span>{{ bizLabel }}</span>{{ item.creatorName }}
<span v-if="Array.isArray(item.customer) && item.customer.length > 0 && item.customer[0].name" style="margin-left: 8px;"> <span v-if="queryParams.bizId !== undefined && Array.isArray(item.customer) && item.customer.length > 0 && item.customer[0].name" style="margin-left: 8px;">
客户名称{{ item.customer[0].name }} 客户名称{{ item.customer[0].name }}
</span> </span>
@ -72,11 +71,11 @@
</el-link> </el-link>
<span style="margin: 0 8px;"> </span> <span style="margin: 0 8px;"> </span>
</template> </template>
<span class="meta-label">{{ bizLabel }}</span><dict-tag :type="DICT_TYPE.CRM_FOLLOW_UP_TYPE" :value="item.type" class="mr-10px" /> <span v-if="queryParams.bizType === BizTypeEnum.CRM_CUSTOMER" class="meta-label">{{ bizLabel }}</span><dict-tag :type="DICT_TYPE.CRM_FOLLOW_UP_TYPE" :value="item.type" class="mr-10px" />
</div> </div>
<!-- 操作按钮统一放右上角 --> <!-- 操作按钮统一放右上角 -->
<div class="followup-action-btns"> <div class="followup-action-btns">
<el-button v-hasPermi="['crm:follow-up-record:update']" link class="followup-action-btn edit" @click="openForm('update')"> <el-button v-hasPermi="['crm:follow-up-record:update']" link class="followup-action-btn edit" @click="openForm('update', item.id)">
<Icon icon="ep:edit" class="mr-3px" /> 编辑 <Icon icon="ep:edit" class="mr-3px" /> 编辑
</el-button> </el-button>
<el-button v-hasPermi="['crm:follow-up-record:delete']" link class="followup-action-btn delete" @click="handleDelete(item.id)"> <el-button v-hasPermi="['crm:follow-up-record:delete']" link class="followup-action-btn delete" @click="handleDelete(item.id)">
@ -126,7 +125,7 @@
/> />
<!-- 表单弹窗添加/修改 --> <!-- 表单弹窗添加/修改 -->
<FollowUpRecordForm ref="formRef" @success="getList" :biz-label="bizLabel" /> <FollowUpRecordForm ref="formRef" @success="getList" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -178,8 +177,8 @@ const getList = async () => {
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref<InstanceType<typeof FollowUpRecordForm>>() const formRef = ref<InstanceType<typeof FollowUpRecordForm>>()
// //
const openForm = (type: 'create' | 'update') => { const openForm = (type: 'create' | 'update', id?: number) => {
formRef.value?.open(type, props.bizType, props.bizId) formRef.value?.open(type, props.bizType, props.bizId, id)
} }
@ -198,11 +197,11 @@ const handleDelete = async (id: number) => {
} }
const bizLabel = computed(() => { const bizLabel = computed(() => {
switch (formRef.value?.bizType) { switch (queryParams.bizType) {
case BizTypeEnum.CRM_CUSTOMER: case BizTypeEnum.CRM_CUSTOMER:
return '跟进' return '跟进'
case BizTypeEnum.CRM_STATION_SITE: case BizTypeEnum.CRM_STATION_SITE:
return '评审' return '记录'
default: default:
return '跟进' return '跟进'
} }

View File

@ -8,11 +8,10 @@
:rules="formRules" :rules="formRules"
label-width="120px" label-width="120px"
> >
{{ formData }}
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item :label="`${bizLabel}方式`" prop="type" :required="bizLabel !== '9'"> <el-form-item v-if="formData.bizType === BizTypeEnum.CRM_CUSTOMER" :label="`${bizLabel}方式`" prop="type">
<el-select v-model="formData.type" :placeholder="`请选择${bizLabel}`" :clearable="bizLabel === '9'"> <el-select v-model="formData.type" :placeholder="`请选择${bizLabel}`">
<el-option <el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_UP_TYPE)" v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_UP_TYPE)"
:key="dict.value" :key="dict.value"
@ -52,6 +51,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup' import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup'
import { BizTypeEnum } from '@/api/crm/permission' import { BizTypeEnum } from '@/api/crm/permission'
import * as ContactApi from '@/api/crm/contact' import * as ContactApi from '@/api/crm/contact'
import { F } from 'dist-test/assets/form-create-DBMZ6B7Y'
defineOptions({ name: 'FollowUpRecordForm' }) defineOptions({ name: 'FollowUpRecordForm' })
@ -63,6 +63,7 @@ const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 12 const formLoading = ref(false) // 12
// //
interface FollowUpFormData { interface FollowUpFormData {
id?: number; // id
bizType?: number; bizType?: number;
bizId?: number; bizId?: number;
type?: number; type?: number;
@ -75,6 +76,7 @@ interface FollowUpFormData {
contactIds?: number[]; contactIds?: number[];
} }
const formData = ref<FollowUpFormData>({ const formData = ref<FollowUpFormData>({
id: undefined, // id
bizType: undefined, bizType: undefined,
bizId: undefined, bizId: undefined,
type: undefined, type: undefined,
@ -94,7 +96,7 @@ const bizLabel = computed(() => {
case BizTypeEnum.CRM_CUSTOMER: case BizTypeEnum.CRM_CUSTOMER:
return '跟进' return '跟进'
case BizTypeEnum.CRM_STATION_SITE: case BizTypeEnum.CRM_STATION_SITE:
return '评审' return '记录'
default: default:
return '跟进' return '跟进'
} }
@ -103,13 +105,13 @@ const bizLabel = computed(() => {
const formRules = reactive({ const formRules = reactive({
type: [ type: [
{ {
required: bizLabel.value !== '评审', required: computed(() => formData.value.bizType === BizTypeEnum.CRM_CUSTOMER),
message: `${bizLabel.value}类型不能为空`, message: `${bizLabel.value}方式不能为空`,
trigger: 'change' trigger: 'change'
} }
], ],
content: [{ required: true, message: `${bizLabel.value}内容不能为空`, trigger: 'blur' }], content: [{ required: true, message: `${bizLabel.value}内容不能为空`, trigger: 'blur' }],
contactIds: [{ required: bizLabel.value !== '评审', message: '联系人不能为空', trigger: 'change' }] contactIds: [{ required: computed(() => formData.value.bizType === BizTypeEnum.CRM_CUSTOMER ), message: `${bizLabel.value}人不能为空`, trigger: 'change' }]
}) })
const contactOptions = ref<ContactApi.ContactVO[]>([]) const contactOptions = ref<ContactApi.ContactVO[]>([])
@ -132,21 +134,26 @@ watch(
) )
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, bizType: number, bizId: number) => { const open = async (type: string, bizType: number, bizId: number, id?: number) => {
console.log(formData.value) console.log(formData.value)
dialogVisible.value = true dialogVisible.value = true
resetForm() resetForm()
formType.value = type formType.value = type
formData.value.bizType = bizType formData.value.bizType = bizType
formData.value.bizId = bizId formData.value.bizId = bizId
formData.value.id = id || undefined // idundefined
if (formType.value === 'update') { if (formType.value === 'update') {
if (bizId === undefined || bizId === null) { if (bizId === undefined || bizId === null) {
message.warning(t('记录ID不能为空')) message.warning(t('记录ID不能为空'))
return return
} }
if (id === undefined || id === null) {
message.warning(t('记录ID不能为空'))
return
}
formLoading.value = true formLoading.value = true
try { try {
const data = await FollowUpRecordApi.getFollowUpRecord(bizId) const data = await FollowUpRecordApi.getFollowUpRecord(id)
Object.assign(formData.value, data) Object.assign(formData.value, data)
} finally { } finally {
formLoading.value = false formLoading.value = false

View File

@ -50,6 +50,7 @@ import * as ClueApi from '@/api/crm/clue'
import * as ContactApi from '@/api/crm/contact' import * as ContactApi from '@/api/crm/contact'
import * as CustomerApi from '@/api/crm/customer' import * as CustomerApi from '@/api/crm/customer'
import * as ContractApi from '@/api/crm/contract' import * as ContractApi from '@/api/crm/contract'
import * as StationSiteApi from '@/api/crm/stationsite'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { BizTypeEnum, PermissionLevelEnum, TransferReqVO } from '@/api/crm/permission' import { BizTypeEnum, PermissionLevelEnum, TransferReqVO } from '@/api/crm/permission'
@ -122,6 +123,8 @@ const transfer = async (data: TransferReqVO) => {
return await BusinessApi.transferBusiness(data) return await BusinessApi.transferBusiness(data)
case BizTypeEnum.CRM_CONTRACT: case BizTypeEnum.CRM_CONTRACT:
return await ContractApi.transferContract(data) return await ContractApi.transferContract(data)
case BizTypeEnum.CRM_STATION_SITE:
return await StationSiteApi.transferStationSite(data)
default: default:
message.error('【转移失败】没有转移接口') message.error('【转移失败】没有转移接口')
throw new Error('【转移失败】没有转移接口') throw new Error('【转移失败】没有转移接口')
@ -139,6 +142,8 @@ const getDialogTitle = () => {
return '商机转移' return '商机转移'
case BizTypeEnum.CRM_CONTRACT: case BizTypeEnum.CRM_CONTRACT:
return '合同转移' return '合同转移'
case BizTypeEnum.CRM_STATION_SITE:
return '场地信息转移'
default: default:
return '转移' return '转移'
} }

View File

@ -8,6 +8,10 @@
</el-row> </el-row>
</el-col> </el-col>
</div> </div>
<div>
<!-- 右上按钮 -->
<slot></slot>
</div>
</div> </div>
</div> </div>
<ContentWrap class="mt-10px"> <ContentWrap class="mt-10px">
@ -30,6 +34,9 @@
<el-descriptions-item label="物管方:"> <el-descriptions-item label="物管方:">
{{ stationSite.managementName || '-' }} {{ stationSite.managementName || '-' }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="责任人:">
{{ stationSite.ownerUserName || '-' }}
</el-descriptions-item>
<el-descriptions-item label="推荐人姓名:"> <el-descriptions-item label="推荐人姓名:">
{{ stationSite.recommenderName || '-' }} {{ stationSite.recommenderName || '-' }}
</el-descriptions-item> </el-descriptions-item>
@ -37,13 +44,14 @@
{{ stationSite.recommenderContact || '-' }} {{ stationSite.recommenderContact || '-' }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</ContentWrap> </ContentWrap>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { StationSiteVO } from '@/api/crm/stationsite' import { StationSiteVO } from '@/api/crm/stationsite'
import { formatDate } from '@/utils/formatTime'
defineOptions({ name: 'CrmStationSiteDetailsHeader' }) defineOptions({ name: 'CrmStationSiteDetailsHeader' })
defineProps<{ defineProps<{

View File

@ -1,8 +1,20 @@
<template> <template>
<StationSiteDetailsHeader :station-site="stationSite" :loading="loading" /> <StationSiteDetailsHeader :station-site="stationSite" :loading="loading">
<el-button
type="primary"
@click="openForm('update', stationSite.id)"
>
编辑
</el-button>
<el-button
@click="transfer"
>
转移
</el-button>
</StationSiteDetailsHeader>
<el-col> <el-col>
<el-tabs> <el-tabs>
<el-tab-pane label="评审记录"> <!-- v-if="checkPermi(['crm:follow-up-record:query'])" --> <el-tab-pane label="动态记录"> <!--v-hasPermi="['crm:station-site:update']" v-hasPermi="['crm:station-site:transfor']" v-if="checkPermi(['crm:follow-up-record:query'])" -->
<FollowUpList :biz-id="stationSite.id" :biz-type="BizTypeEnum.CRM_STATION_SITE" /> <FollowUpList :biz-id="stationSite.id" :biz-type="BizTypeEnum.CRM_STATION_SITE" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -15,6 +27,12 @@
</el-tab-pane> </el-tab-pane>
</el-tabs> --> </el-tabs> -->
</el-col> </el-col>
<!-- 表单弹窗添加/修改 -->
<StationSiteForm ref="formRef" @success="getStationSite" />
<CrmTransferForm ref="transferFormRef" :biz-type="BizTypeEnum.CRM_STATION_SITE" @success="close" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
@ -27,12 +45,13 @@ import { BizTypeEnum } from '@/api/crm/permission'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import { checkPermi } from "@/utils/permission"; // import { checkPermi } from "@/utils/permission"; //
import FollowUpList from '@/views/crm/followup/components/FollowUpList.vue' import FollowUpList from '@/views/crm/followup/components/FollowUpList.vue'
import StationSiteForm from '../StationSiteForm.vue'
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
const route = useRoute() const route = useRoute()
const stationSiteId = ref<number>() const stationSiteId = ref<number>()
const loading = ref(true) const loading = ref(true)
const stationSite = ref<StationSiteVO>({} as StationSiteVO)
const message = useMessage() // const message = useMessage() //
const fileUrl = ref() // URL const fileUrl = ref() // URL
const { delView } = useTagsViewStore() // const { delView } = useTagsViewStore() //
@ -54,6 +73,15 @@ const getOperateLog = async () => {
logList.value = data.list logList.value = data.list
} }
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 获取详情 */
const stationSite = ref<StationSiteVO>({} as StationSiteVO)
const getStationSite = async () => { const getStationSite = async () => {
loading.value = true loading.value = true
try { try {
@ -66,6 +94,12 @@ const getStationSite = async () => {
} }
} }
/** 客户转移 */
const transferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // ref
const transfer = () => {
transferFormRef.value?.open(Number(stationSiteId.value))
}
const close = () => { const close = () => {
delView(unref(currentRoute)) delView(unref(currentRoute))
push({ name: 'CrmStaionSite' }) push({ name: 'CrmStaionSite' })

View File

@ -43,6 +43,23 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="责任人">
<el-select
v-model="queryParams.ownerUserId"
filterable
remote
clearable
placeholder="请选择"
style="width: 150px"
>
<el-option
v-for="user in userOptions"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
@ -94,7 +111,7 @@
<dict-tag :type="DICT_TYPE.CRM_STATION_SITE_STATUS" :value="scope.row.siteStatus" /> <dict-tag :type="DICT_TYPE.CRM_STATION_SITE_STATUS" :value="scope.row.siteStatus" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="填报人" align="center" prop="creatorName" width="150px" /> <el-table-column label="责任人" align="center" prop="ownerUserName" width="150px" />
<el-table-column label="操作" fixed="right" align="center" min-width="150px"> <el-table-column label="操作" fixed="right" align="center" min-width="150px">
<template #default="scope"> <template #default="scope">
<el-button <el-button
@ -142,13 +159,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict' import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import { StationSiteApi, StationSiteVO } from '@/api/crm/stationsite' import { StationSiteApi, StationSiteVO } from '@/api/crm/stationsite'
import StationSiteForm from './StationSiteForm.vue' import StationSiteForm from './StationSiteForm.vue'
import * as AreaApi from '@/api/system/area' import * as AreaApi from '@/api/system/area'
import { defaultProps } from '@/utils/tree' import { defaultProps } from '@/utils/tree'
import { useUserStore } from '@/store/modules/user' import * as UserApi from '@/api/system/user'
/** 场地信息 列表 */ /** 场地信息 列表 */
defineOptions({ name: 'CrmStationSite' }) defineOptions({ name: 'CrmStationSite' })
@ -164,9 +180,11 @@ const queryParams = reactive({
pageSize: 10, pageSize: 10,
name: undefined, name: undefined,
areaId: undefined, areaId: undefined,
ownerUserId: undefined,
siteStatus: undefined, siteStatus: undefined,
createTime: [], createTime: [],
}) })
const userOptions = ref<any[]>([])
const queryFormRef = ref() // const queryFormRef = ref() //
const exportLoading = ref(false) // const exportLoading = ref(false) //
const areaList = ref([]) const areaList = ref([])
@ -267,7 +285,12 @@ watch(
} }
) )
onMounted(() => { onMounted(() => {
//
UserApi.getSimpleUserList().then(res => {
userOptions.value = res
})
getList() getList()
AreaApi.getAreaTree().then(res => { AreaApi.getAreaTree().then(res => {
areaList.value = res areaList.value = res