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

This commit is contained in:
2024-06-13 19:49:27 +08:00
35 changed files with 1570 additions and 1100 deletions

11
src/api/clue/clueCache.js Normal file
View File

@@ -0,0 +1,11 @@
import request from '@/config/axios'
// 查询用户配置
export const getClueCache = async (params) => {
return await request.get({ url: '/admin-api/crm/param-user-setting/get-by-user', params })
}
// 保存用户配置
export const setClueCache = async (data) => {
return await request.post({ url: '/admin-api/crm/param-user-setting/save', data })
}

40
src/api/clue/clueField.js Normal file
View File

@@ -0,0 +1,40 @@
import request from '@/config/axios'
// 创建
export const createField = (data) => {
return request.post({ url: '/admin-api/crm/clue-param/create', data })
}
// 更新
export const updateField = (data) => {
return request.put({ url: '/admin-api/crm/clue-param/update', data })
}
// 删除
export const deleteField = (id) => {
return request.delete({ url: `/admin-api/crm/clue-param/delete?id=${id}` })
}
// 获得
export const getField = (id) => {
return request.get({ url: `/admin-api/crm/clue-param/get?id=${id}` })
}
// 获得精简信息列表
export const getSimpleFieldList = () => {
return request.get({ url: '/admin-api/crm/clue-param/simple-list' })
}
// 获取自定义字段
export const getDiyFieldList = () => {
return request.get({ url: '/admin-api/crm/clue-param/get-diy-param' })
}
// 状态修改
export const updateFieldStatus = (signParamId, status) => {
const data = {
signParamId,
status
}
return request.put({ url: '/admin-api/crm/clue-param/status/update', data: data })
}

View File

@@ -0,0 +1,21 @@
import request from '@/config/axios'
// 线索获取规则
export const getClueGainRuleList = () => {
return request.get({ url: '/admin-api/crm/sch-clue-gain-rule/list' })
}
// 删除
export const deleteClueGainRule = (id) => {
return request.delete({ url: `/admin-api/crm/sch-clue-gain-rule/delete?id=${id}` })
}
// 线索规则
export const getClueDistributeRuleList = () => {
return request.get({ url: '/admin-api/crm/sch-clue-gain-rule/list' })
}
// 删除
export const deleteClueDistributeRule = (id) => {
return request.delete({ url: `/admin-api/crm/sch-clue-gain-rule/delete?id=${id}` })
}

View File

@@ -0,0 +1,16 @@
import request from '@/config/axios'
// 查询(精简)列表
export const getFollowList = async (params) => {
return await request.get({ url: '/admin-api/crm/clue-follow-record/list', params })
}
// 新增
export const createFollow = async (data) => {
return await request.post({ url: '/admin-api/crm/clue-follow-record/create', data: data })
}
// 删除
export const deleteFollow = async (id) => {
return await request.delete({ url: '/admin-api/crm/clue-follow-record/delete?id=' + id })
}

46
src/api/clue/index.js Normal file
View File

@@ -0,0 +1,46 @@
import request from '@/config/axios'
// 查询(精简)列表
export const getSimpleClueList = async () => {
return await request.get({ url: '/admin-api/crm/sch-clue/list-all-simple' })
}
// 查询列表
export const getCluePage = async (params) => {
return await request.get({ url: '/admin-api/crm/sch-clue/page', params })
}
// 查询详情
export const getClue = async (id) => {
return await request.get({ url: '/admin-api/crm/sch-clue/get?id=' + id })
}
// 新增
export const createClue = async (data) => {
return await request.post({ url: '/admin-api/crm/sch-clue/create', data: data })
}
// 修改
export const updateClue = async (params) => {
return await request.put({ url: '/admin-api/crm/sch-clue/update', data: params })
}
// 删除
export const deleteClue = async (id) => {
return await request.delete({ url: '/admin-api/crm/sch-clue/delete?id=' + id })
}
// 释放
export const releaseClue = async (data) => {
return await request.put({ url: '/admin-api/crm/sch-clue/public/save', data })
}
// 通用查询数量
export const getClueCount = async () => {
return await request.get({ url: '/admin-api/crm/sch-clue/get-clue-num' })
}
// 获取操作记录
export const getOpearateRecord = async (params) => {
return await request.get({ url: '/admin-api/crm/clue-operate-record/list', params })
}

View File

@@ -0,0 +1,40 @@
import request from '@/config/axios'
// 创建
export const createField = (data) => {
return request.post({ url: '/admin-api/crm/sign-param/create', data })
}
// 更新
export const updateField = (data) => {
return request.put({ url: '/admin-api/crm/sign-param/update', data })
}
// 删除
export const deleteField = (id) => {
return request.delete({ url: `/admin-api/crm/sign-param/delete?id=${id}` })
}
// 获得
export const getField = (id) => {
return request.get({ url: `/admin-api/crm/sign-param/get?id=${id}` })
}
// 获得精简信息列表
export const getSimpleFieldList = () => {
return request.get({ url: '/admin-api/crm/sign-param/simple-list' })
}
// 获取自定义字段
export const getDiyFieldList = () => {
return request.get({ url: '/admin-api/crm/sign-param/get-diy-param' })
}
// 状态修改
export const updateFieldStatus = (signParamId, status) => {
const data = {
signParamId,
status
}
return request.put({ url: '/admin-api/crm/sign-param/status/update', data: data })
}

26
src/api/clue/skill.js Normal file
View File

@@ -0,0 +1,26 @@
import request from '@/config/axios'
// 获得列表
export const getSkillPage = (params) => {
return request.get({ url: '/admin-api/crm/skill/page', params })
}
// 创建
export const createSkill = (data) => {
return request.post({ url: '/admin-api/crm/skill/create', data })
}
// 更新
export const updateSkill = (data) => {
return request.put({ url: '/admin-api/crm/skill/update', data })
}
// 删除
export const deleteSkill = (id) => {
return request.delete({ url: `/admin-api/crm/skill/delete?id=${id}` })
}
// 获得
export const getSkill = (id) => {
return request.get({ url: `/admin-api/crm/skill/get?id=${id}` })
}

31
src/api/clue/source.js Normal file
View File

@@ -0,0 +1,31 @@
import request from '@/config/axios'
// 查询(精简)列表
export const getSimpleSourceList = async () => {
return await request.get({ url: '/admin-api/crm/source/list' })
}
// 查询列表
export const getSourcePage = async (params) => {
return await request.get({ url: '/admin-api/crm/source/list', params })
}
// 查询详情
export const getSource = async (id) => {
return await request.get({ url: '/admin-api/crm/source/get?id=' + id })
}
// 新增
export const createSource = async (data) => {
return await request.post({ url: '/admin-api/crm/source/create', data: data })
}
// 修改
export const updateSource = async (params) => {
return await request.put({ url: '/admin-api/crm/source/update', data: params })
}
// 删除
export const deleteSource = async (id) => {
return await request.delete({ url: '/admin-api/crm/source/delete?id=' + id })
}

View File

@@ -55,6 +55,8 @@ const dialogStyle = computed(() => {
height: unref(dialogHeight)
}
})
const emit = defineEmits(['close'])
</script>
<template>
@@ -66,6 +68,7 @@ const dialogStyle = computed(() => {
draggable
lock-scroll
v-bind="getBindValue"
@close="emit('close')"
>
<template #header>
<div class="flex justify-between">

View File

@@ -181,11 +181,7 @@ export default defineComponent({
const slotsMap: Recordable = {
...setItemComponentSlots(slots, item?.componentProps?.slots, item.field)
}
if (
item?.component !== 'SelectV2' &&
item?.component !== 'Cascader' &&
item?.componentProps?.options
) {
if (item?.component !== 'SelectV2' && item?.component !== 'Cascader' && item?.options) {
slotsMap.default = () => renderOptions(item)
}
@@ -236,6 +232,8 @@ export default defineComponent({
vModel={formModel.value[item.field]}
{...(autoSetPlaceholder && setTextPlaceholder(item))}
{...setComponentProps(item)}
format={item.component == 'DatePicker' ? 'YYYY-MM-DD' : null}
value-format={item.component == 'DatePicker' ? 'YYYY-MM-DD' : null}
style={baseSty + item.componentProps?.style}
// eslint-disable-next-line prettier/prettier
{...(notRenderOptions.includes(item?.component as string) && item?.componentProps?.options

View File

@@ -5,12 +5,12 @@ import { defineComponent } from 'vue'
export const useRenderCheckbox = () => {
const renderCheckboxOptions = (item: FormSchema) => {
// 如果有别名,就取别名
const labelAlias = item?.componentProps?.optionsAlias?.labelField
const valueAlias = item?.componentProps?.optionsAlias?.valueField
const labelAlias = item?.componentProps?.optionsAlias?.labelField || 'id'
const valueAlias = item?.componentProps?.optionsAlias?.valueField || 'name'
const Com = (item.component === 'Checkbox' ? ElCheckbox : ElCheckboxButton) as ReturnType<
typeof defineComponent
>
return item?.componentProps?.options?.map((option) => {
return item?.options?.map((option) => {
const { ...other } = option
return (
<Com {...other} label={option[valueAlias || 'value']}>

View File

@@ -5,12 +5,12 @@ import { defineComponent } from 'vue'
export const useRenderRadio = () => {
const renderRadioOptions = (item: FormSchema) => {
// 如果有别名,就取别名
const labelAlias = item?.componentProps?.optionsAlias?.labelField
const valueAlias = item?.componentProps?.optionsAlias?.valueField
const labelAlias = item?.componentProps?.optionsAlias?.labelField || 'id'
const valueAlias = item?.componentProps?.optionsAlias?.valueField || 'name'
const Com = (item.component === 'Radio' ? ElRadio : ElRadioButton) as ReturnType<
typeof defineComponent
>
return item?.componentProps?.options?.map((option) => {
return item?.options?.map((option) => {
const { ...other } = option
return (
<Com {...other} label={option[valueAlias || 'value']}>

View File

@@ -9,12 +9,12 @@ export const useRenderSelect = (slots: Slots) => {
const renderSelectOptions = (item: FormSchema) => {
// 如果有别名,就取别名
const labelAlias = item?.componentProps?.optionsAlias?.labelField
return item?.componentProps?.options?.map((option) => {
if (option?.options?.length) {
return item?.options?.map((option) => {
if (option?.length) {
return (
<ElOptionGroup label={option[labelAlias || 'label']}>
{() => {
return option?.options?.map((v) => {
return option?.map((v) => {
return renderSelectOptionItem(item, v)
})
}}
@@ -29,8 +29,8 @@ export const useRenderSelect = (slots: Slots) => {
// 渲染 select option item
const renderSelectOptionItem = (item: FormSchema, option: ComponentOptions) => {
// 如果有别名,就取别名
const labelAlias = item?.componentProps?.optionsAlias?.labelField
const valueAlias = item?.componentProps?.optionsAlias?.valueField
const labelAlias = item?.componentProps?.optionsAlias?.labelField || 'id'
const valueAlias = item?.componentProps?.optionsAlias?.valueField || 'name'
const { label, value, ...other } = option

View File

@@ -28,7 +28,7 @@
@end="onDragEnd"
>
<template #item="{ element: item }">
<el-checkbox :key="item.field" :label="item.field">
<el-checkbox :key="item.id" :label="item.id">
{{ item.label }}
</el-checkbox>
</template>
@@ -48,9 +48,8 @@
<script setup>
import draggable from 'vuedraggable'
import { useUserStore } from '@/store/modules/user'
import * as ClueCacheApi from '@/api/clue/clueCache'
import { useRoute } from 'vue-router'
import cache from '@/plugins/cache'
const props = defineProps({
tableObject: { type: Object, default: () => ({ tableList: [] }) },
@@ -59,7 +58,6 @@ const props = defineProps({
const emit = defineEmits(['update:tableObject', 'getList', 'getCheckedColumns'])
const route = useRoute()
const { id: userId } = useUserStore().user //取用户ID
const pageNo = ref(props.tableObject?.pageNo || 1)
@@ -68,7 +66,7 @@ const pageSize = ref(props.tableObject?.pageSize || 20)
const ColumnSetting = ref()
const TableColumnPop = ref()
// 展示在设置里的所有表格列,由于会排序,所以使用新属性,不直接修改原值
const allColumns = ref({})
const allColumns = ref([...props.tableColumns])
// 已勾选的选项
const checkedColumns = ref([])
@@ -85,67 +83,50 @@ const clickSetting = () => {
unref(TableColumnPop).TableColumnPop?.delayHide?.()
}
// 获取所有的表格列,注意如果本地有缓存使用缓存数据,因为用户可能已对表格列排序,并且不依赖网络请求,可更快渲染表头
function getAllColumns() {
// 1. 先获取缓存表头
const localData = getColumn('TableColumnAll')[route.name] || []
// 2. 如果有缓存的表头,直接使用,并将新增的标题加入,如果没有缓存,那就得用获取到的表头
if (localData && localData) {
const newColumns = props.tableColumns.filter(
(item) => !localData.some((it) => it.field == item.field)
)
allColumns.value = [...localData, ...newColumns]
} else {
allColumns.value = [...props.tableColumns]
}
}
// 获取缓存的表头
function getColumn(name = 'shitTable') {
return cache.local.get(`${name}-${userId}`) || {}
}
// 设置表头缓存
function setColumn(val, name = 'shitTable') {
cache.local.set(`${name}-${userId}`, val)
const routeMap = {
CluePool: 1,
ClueOrder: 2
}
// 获取用户已勾选的表头
function getUserCheckedColumns() {
// 1. 先获取缓存
const localData = getColumn('shitTable')[route.name]
// 2. 如果有缓存,使用缓存表头,否则使用默认的所有表头
if (localData && localData.length) {
checkedColumns.value = localData
} else {
checkedColumns.value = allColumns.value.map((it) => it.field)
}
// 3. 回显到表格中
async function getUserCheckedColumns() {
checkedColumns.value = await ClueCacheApi.getClueCache({
settingType: 2,
model: routeMap[route.name]
})
// 回显到表格中
emitColumns()
}
// 表格列排序
function onDragEnd() {
const obj = getColumn('TableColumnAll')
obj[route.name] = allColumns.value
// 1. 设置缓存
setColumn(obj, 'TableColumnAll')
// 2. 表格回显
ClueCacheApi.setClueCache({
settingType: 2,
model: routeMap[route.name],
clueParamId: checkedColumns.value
})
// 表格回显
emitColumns()
}
// 勾选确认
function confirm() {
const obj = getColumn()
obj[route.name] = checkedColumns.value
setColumn(obj, 'shitTable')
ClueCacheApi.setClueCache({
settingType: 2,
model: routeMap[route.name],
clueParamId: checkedColumns.value
})
// usedSchema.value = allColumns.value.filter((it) => checkedColumns.value.includes(it.id))
emitColumns()
}
// 将表头数据返回至父组件
function emitColumns() {
const arr = allColumns.value.filter((item) => checkedColumns.value.includes(item.field))
const arr = allColumns.value.filter((item) => checkedColumns.value.includes(item.id))
emit('getCheckedColumns', arr)
}
getAllColumns()
getUserCheckedColumns()
defineExpose({ getUserCheckedColumns })

View File

@@ -7,12 +7,10 @@ import { findIndex } from '@/utils'
import { cloneDeep } from 'lodash-es'
import { FormSchema } from '@/types/form'
import { useUserStore } from '@/store/modules/user'
import { useRoute } from 'vue-router'
import cache from '@/plugins/cache'
import * as ClueCacheApi from '@/api/clue/clueCache'
const route = useRoute()
const { id: userId } = useUserStore().user //取用户ID
const { t } = useI18n()
@@ -46,7 +44,7 @@ const props = defineProps({
}
})
const emit = defineEmits(['search', 'reset'])
// const emit = defineEmits(['search', 'reset'])
const visible = ref(true)
@@ -80,37 +78,26 @@ const newSchema = computed(() => {
return schema
})
function initSearch() {
const routeMap = {
CluePool: 1,
ClueOrder: 2
}
async function initSearch() {
reset()
// 1. 先获取缓存
const localData = getColumn('Schema')[route.name]
// 2. 如果有缓存,使用缓存表头,否则使用默认的所有表头
if (localData && localData.length) {
usedSchema.value = localData
} else {
const obj = getColumn('Schema')
obj[route.name] = [props.schema[0]]
setSchema(obj)
usedSchema.value = [props.schema[0]]
}
checkedSchema.value = usedSchema.value.map((it) => it.field)
checkedSchema.value = await ClueCacheApi.getClueCache({
settingType: 1,
model: routeMap[route.name]
})
usedSchema.value = props.schema.filter((it) => checkedSchema.value.includes(it.id))
}
function changeSearch() {
const obj = getColumn('Schema')
obj[route.name] = props.schema.filter((item) => checkedSchema.value.includes(item.field))
setSchema(obj)
initSearch()
}
// 获取缓存的查询条件
function getColumn(name = 'Schema') {
return cache.local.get(`${name}-${userId}`) || {}
}
// 设置查询条件缓存
function setSchema(val: Array<Object>, name = 'Schema') {
cache.local.set(`${name}-${userId}`, val)
ClueCacheApi.setClueCache({
settingType: 1,
model: routeMap[route.name],
clueParamId: checkedSchema.value
})
usedSchema.value = props.schema.filter((it) => checkedSchema.value.includes(it.id))
}
function setSearch() {
@@ -121,23 +108,34 @@ const { register, elFormRef, methods } = useForm({
model: props.model || {}
})
const search = async () => {
await unref(elFormRef)?.validate(async (isValid) => {
if (isValid) {
const { getFormData } = methods
const model = await getFormData()
emit('search', model)
}
})
}
// const search = async () => {
// await unref(elFormRef)?.validate(async (isValid) => {
// if (isValid) {
// const { getFormData } = methods
// const model = await getFormData()
// emit('search', model)
// }
// })
// }
const reset = async () => {
unref(elFormRef)?.resetFields()
// const { getFormData } = methods
// const model = await getFormData()
// emit('reset', model)
}
async function getFormModel() {
const { getFormData } = methods
const model = await getFormData()
emit('reset', model)
return model
}
defineExpose({
getFormModel,
reset
})
const bottonButtonStyle = computed(() => {
return {
textAlign: props.buttomPosition as unknown as 'left' | 'center' | 'right'
@@ -168,10 +166,7 @@ initSearch()
>
<template #action>
<div v-if="layout === 'inline'">
<ElButton ref="SchemaSetting" @click="setSearch">
<!-- <Icon class="mr-5px" icon="ep:setting" /> -->
查询设置
</ElButton>
<ElButton ref="SchemaSetting" @click="setSearch"> 查询设置 </ElButton>
<el-popover
ref="SettingPop"
:virtual-ref="SchemaSetting"
@@ -181,7 +176,7 @@ initSearch()
virtual-triggering
>
<el-checkbox-group v-model="checkedSchema" @change="changeSearch">
<el-checkbox v-for="item in schema" :key="item.field" :label="item.field">
<el-checkbox v-for="item in schema" :key="item.id" :label="item.id">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>

View File

@@ -179,7 +179,7 @@ service.interceptors.response.use(
})
}
} else if (code === 500) {
ElMessage.error(t('sys.api.errMsg500'))
ElMessage.error(t(msg || 'sys.api.errMsg500'))
return Promise.reject(new Error(msg))
} else if (code === 901) {
ElMessage.error({

View File

@@ -119,7 +119,8 @@ const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): F
componentProps: comonentProps,
...schemaItem.search,
field: schemaItem.field,
label: schemaItem.search?.label || schemaItem.label
label: schemaItem.search?.label || schemaItem.label,
id: schemaItem.clueParamId
}
if (searchSchemaItem.api) {
searchRequestTask.push(async () => {
@@ -165,7 +166,8 @@ const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
}
return {
...schema.table,
...schema
...schema,
id: schema.clueParamId
}
}
}

View File

@@ -209,3 +209,14 @@ export const yuanToFen = (amount: string | number): number => {
export const fenToYuan = (amount: string | number): number => {
return Number((Number(amount) / 100).toFixed(2))
}
export const removeNullField = (obj: Object) => {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (obj[key] == '') {
delete obj[key]
}
}
}
return obj
}

View File

@@ -1,46 +1,52 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<Dialog :title="dialogTitle" v-model="dialogVisible" width="800px" @close="destroyMap">
<el-tabs v-model="tabName">
<el-tab-pane label="线索信息" name="info">
<Form
ref="formRef"
v-loading="formLoading"
:rules="rules"
isCol
:schema="allSchemas.formSchema"
/>
<Form ref="formRef" v-loading="formLoading" :rules="rules" isCol :schema="formSchema" />
</el-tab-pane>
<el-tab-pane label="跟进信息" name="follow">
<el-button type="primary" @click="handleAppendFollow">新增跟进人</el-button>
<el-table :data="followList">
<el-table-column label="跟进人">
<template #default="{ row }">
<el-select v-model="row.followUser" placeholder="选择跟进人" filterable>
<el-select
v-model="row.userId"
placeholder="选择跟进人"
filterable
:disabled="!row.editable"
>
<el-option
v-for="item in userOptions"
:key="item.value"
:label="item.label"
:value="item.value"
v-for="item in props.userOptions"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</template>
</el-table-column>
<el-table-column prop="nextFollowTime" label="下次跟进时间">
<el-table-column prop="followTime" label="下次跟进时间">
<template #default="{ row }">
<el-date-picker v-model="row.nextFollowTime" type="date" placeholder="选择日期时间" />
<el-date-picker
v-model="row.followTime"
type="date"
placeholder="选择日期时间"
:disabled="!row.editable"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="{ $index }">
<Icon icon="ep:remove-filled" class="text-red-500" @click="handleRemove($index)" />
<!-- <el-button @click="handleRemove(row, $index)">
</el-button> -->
<template #default="{ row, $index }">
<Icon
v-if="row.editable"
icon="ep:remove-filled"
class="text-red-500"
@click="handleRemove($index)"
/>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="位置信息" name="map">
<el-tab-pane v-if="appStore.getAppInfo?.instanceType == 1" label="位置信息" name="map">
<div class="flex justify-between">
<el-select
v-model="areaValue"
@@ -64,11 +70,15 @@
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.district }}</span>
</el-option>
</el-select>
<el-checkbox v-model="showSchool" :label="true" @change="handleShowSchool"
>展示场地</el-checkbox
>
<el-checkbox v-model="showSchool" :label="true" @change="handleShowSchool">
展示场地
</el-checkbox>
</div>
<div id="dialogMap" class="mt-20px" style="height: 400px; width: 100%"></div>
<div class="flex items-center mt-10px mb-10px">
<div class="w-100px">线索位置</div>
<el-input v-model="address" placeholder="请输入线索位置" clearable />
</div>
</el-tab-pane>
</el-tabs>
<template #footer>
@@ -80,12 +90,58 @@
</Dialog>
</template>
<script setup>
import { allSchemas, rules } from '../cluePool.data'
<script setup name="DialogClue">
import { useAppStore } from '@/store/modules/app'
import { getPlaceList } from '@/api/school/place'
import * as ClueApi from '@/api/clue'
import { getDiyFieldList } from '@/api/clue/clueField'
import { formatDate } from '@/utils/formatTime'
import AMapLoader from '@amap/amap-jsapi-loader'
import ImgPostion from '@/assets/imgs/flag/flag_red.png'
import ImgFlag from '@/assets/imgs/flag/position_blue.png'
import FlagRed from '@/assets/imgs/flag/flag_red.png'
import FlagYellow from '@/assets/imgs/flag/flag_yellow.png'
import FlagPurple from '@/assets/imgs/flag/flag_purple.png'
import FlagGreen from '@/assets/imgs/flag/flag_green.png'
import FlagBlue from '@/assets/imgs/flag/flag_blue.png'
import FlagBlack from '@/assets/imgs/flag/flag_black.png'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const appStore = useAppStore()
const props = defineProps({
schema: {
type: Array
},
userOptions: {
type: Array
}
})
const formSchema = computed(() => {
return [
...props.schema,
{
component: 'Input',
label: '诉求',
field: 'requirement',
componentProps: {
type: 'textarea'
},
colProps: {
span: 24
}
},
{
component: 'Editor',
label: '备注',
field: 'remark',
colProps: {
span: 24
}
}
]
})
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
@@ -93,47 +149,130 @@ const formLoading = ref(false) // 表单的加载中1修改时的数据加
const formType = ref('') // 表单的类型create - 新增update - 修改
const formRef = ref() // 表单 Ref
const rules = {
name: { required: true, message: '线索名称不可为空', trigger: 'blur' },
phone: { required: true, message: '联系方式不可为空', trigger: 'blur' },
source: { required: true, message: '线索来源不可为空', trigger: 'change' },
intentionState: { required: true, message: '意向状态不可为空', trigger: 'change' },
consultTime: { required: true, message: '咨询日期不可为空', trigger: 'change' }
}
const tabName = ref('info')
const followList = ref([])
const userOptions = ref([])
const areaValue = ref('新天地国际')
const areaValue = ref('')
const areaList = ref([])
const address = ref('')
const defaultLatLng = ref({
lat: 31.86119,
lng: 117.283042
})
const info = ref({})
const open = async (type, info) => {
const diyFieldArr = ref([])
const open = async (type, id) => {
dialogVisible.value = true
tabName.value = 'info'
dialogTitle.value = type == 'create' ? '新增线索' : '修改线索'
formType.value = type
resetForm()
// 修改时,设置数据
if (info) {
if (id) {
formLoading.value = true
try {
const data = { ...info }
const data = await ClueApi.getClue(id)
info.value = { ...data, ...data.diyParams }
nextTick(() => {
formRef.value.setValues(data)
followList.value = data.followUser
address.value = data.address || ''
formRef.value.setValues(info.value)
})
} finally {
formLoading.value = false
}
} else {
followList.value = []
address.value = ''
defaultLatLng.value = {
lat: 31.86119,
lng: 117.283042
}
}
if (!dialogMap.value) {
if (appStore.getAppInfo?.instanceType == 1 && !dialogMap.value) {
nextTick(() => {
initMap()
remoteMethod('新天地国际')
getSchoolPlace()
initMap(info.value)
remoteMethod(address.value)
})
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
function handleSave() {
console.log('保存')
function resetForm() {
info.value.address = undefined
info.value.lat = undefined
info.value.lng = undefined
info.value.followUsers = []
info.value.diyParams = {}
}
const placeList = ref([])
function getSchoolPlace() {
getPlaceList().then((data) => {
placeList.value = data.placeList
})
}
const emit = defineEmits(['success'])
async function handleSave() {
// 校验表单
if (!formRef.value) return
const valid = await formRef.value.getElFormRef().validate()
if (!valid) return
if (followList.value && followList.value.length && followList.value.some((it) => !it.userId)) {
message.info('请将跟进人填写完整!')
return
}
// 提交请求
formLoading.value = true
try {
let params = { ...formRef.value.formModel, address: address.value }
params.lat = defaultLatLng.value.lat
params.lng = defaultLatLng.value.lng
params.followUsers = [...followList.value]
params.diyParams = {}
diyFieldArr.value.map((it) => {
params.diyParams[it.field] = undefined
})
for (const key in params.diyParams) {
if (Object.hasOwnProperty.call(params, key)) {
params.diyParams[key] = params[key]
}
}
if (formType.value === 'create') {
await ClueApi.createClue(params)
message.success(t('common.createSuccess'))
} else {
await ClueApi.updateClue(params)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
function handleAppendFollow() {
followList.value.push({
followUser: undefined,
nextFollowTime: formatDate(new Date())
userId: undefined,
followTime: formatDate(new Date()),
editable: true
})
}
function handleRemove(index) {
@@ -145,28 +284,57 @@ const dialogMap = ref(null)
const aMap = ref(null)
let AutoComplete = ref(null)
let geoCoder = ref(null)
function initMap() {
function initMap(data) {
AMapLoader.load({
key: '2ffb0e2ea90b1df0b8be48ed66e18fc8', //设置您的key
version: '2.0',
plugins: ['AMap.Geocoder', 'AMap.AutoComplete']
}).then((AMap) => {
aMap.value = AMap
if (data.lng || data.lat) {
defaultLatLng.value = {
lng: data.lng,
lat: data.lat
}
}
dialogMap.value = new AMap.Map('dialogMap', {
zoom: 12,
zooms: [2, 22],
center: [117.283042, 31.86119]
center: [defaultLatLng.value.lng, defaultLatLng.value.lat]
})
if (data.lng || data.lat) {
addmark(data.lng, data.lat, AMap)
}
AutoComplete.value = new AMap.AutoComplete({
city: '全国'
})
geoCoder.value = new AMap.Geocoder()
dialogMap.value.on('click', (e) => {
defaultLatLng.value = {
lng: e.lnglat.getLng(),
lat: e.lnglat.getLat()
}
addmark(e.lnglat.getLng(), e.lnglat.getLat(), AMap)
regeoCode(e.lnglat.getLng(), e.lnglat.getLat())
})
})
}
function regeoCode(lng, lat) {
try {
geoCoder.value.getAddress([lng, lat], (status, result) => {
if (status === 'complete' && result.regeocode) {
address.value = result.regeocode.formattedAddress
} else {
message.error('根据经纬度查询地址失败')
}
})
} catch (error) {
console.log(error)
}
}
let marker = ref(null)
function addmark(lat, lng, AMap) {
marker.value && removeMarker()
@@ -186,29 +354,30 @@ const showSchool = ref(false)
const schoolMarkers = ref([])
function handleShowSchool() {
if (showSchool.value) {
let marker1 = new aMap.value.Marker({
map: dialogMap.value,
position: [117.258001, 31.895216],
label: {
content: '慧安驾校桃花社区训练基地',
direction: 'left'
},
icon: ImgFlag,
// extData: element,
clickable: true
})
let marker2 = new aMap.value.Marker({
map: dialogMap.value,
position: [117.286731, 31.902396],
label: {
content: '皖西瑞星驾校总校D',
direction: 'left'
},
icon: ImgFlag,
// extData: element,
clickable: true
})
schoolMarkers.value = [marker1, marker2]
const flagMap = {
red: FlagRed,
yellow: FlagYellow,
purple: FlagPurple,
green: FlagGreen,
blue: FlagBlue,
black: FlagBlack
}
schoolMarkers.value = []
for (let i = 0; i < placeList.value.length; i++) {
const place = placeList.value[i]
const marker = new aMap.value.Marker({
map: dialogMap.value,
position: [place.lng, place.lat],
label: {
content: place.name,
direction: 'left'
},
icon: flagMap[place.flagColor || 'red'],
extData: place,
clickable: true
})
schoolMarkers.value.push(marker)
}
} else {
dialogMap.value.remove(schoolMarkers.value)
}
@@ -231,8 +400,24 @@ function currentSelect(val) {
if (area) {
addmark(area.location?.lng, area.location?.lat, aMap.value)
dialogMap.value.setCenter([area.location?.lng, area.location?.lat], '', 500)
regeoCode(area.location?.lng, area.location?.lat)
}
}
function destroyMap() {
dialogMap.value = null
aMap.value = null
}
function getDiyList() {
getDiyFieldList().then((data) => {
diyFieldArr.value = data
})
}
onMounted(() => {
getDiyList()
})
</script>
<style scoped>

View File

@@ -1,24 +1,56 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1000px">
<div class="flex">
<Form
style="flex: 1"
ref="formRef"
v-loading="formLoading"
isCol
:rules="rules"
:schema="allSchemas.formSchema"
/>
<el-form style="flex: 1" :model="form" ref="formRef" :rules="rules" label-width="120px">
<el-row :gutter="20">
<el-col :span="12" :offset="0">
<el-form-item label="本次跟进时间" prop="operateTime">
<el-date-picker
v-model="form.operateTime"
type="datetime"
format="YYYY-MM-DD HH:mm"
valueFormat="YYYY-MM-DD HH:mm"
placeholder="本次跟进时间"
/>
</el-form-item>
</el-col>
<el-col :span="12" :offset="0">
<el-form-item label="下次跟进时间" prop="nextFollowTime">
<el-date-picker
v-model="form.nextFollowTime"
type="date"
format="YYYY-MM-DD"
valueFormat="YYYY-MM-DD"
placeholder="下次跟进时间"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24" :offset="0">
<el-form-item label="跟进内容" prop="content">
<el-input
v-model="form.content"
type="textarea"
:autosize="{ minRows: 4 }"
placeholder="请输入跟进内容"
clearable
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="ml-20px" style="width: 300px">
<el-input
v-model="keyword"
v-model="skillSearch.question"
placeholder="请输入关键字查询关键话术"
clearable
@keyup.enter="filterList"
@keyup.enter="getSkillList"
/>
<el-collapse v-model="activeQues" :accordion="false">
<el-collapse-item
v-for="item in showList"
v-for="item in skillList"
:key="item.skillId"
:title="item.question"
:name="item.skillId"
@@ -26,247 +58,100 @@
<div v-dompurify-html="item.content"></div>
</el-collapse-item>
</el-collapse>
<Pagination
v-model:limit="skillSearch.pageSize"
v-model:page="skillSearch.pageNo"
:total="skillCount"
small
layout="total, prev, pager, next, jumper"
@pagination="getSkillList"
/>
</div>
</div>
<template #footer>
<span>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleSave"> </el-button>
<el-button :disabled="formLoading" type="primary" @click="handleSave"> </el-button>
</span>
</template>
</Dialog>
</template>
<script setup>
import { allSchemas, rules } from './follow.data'
<script setup name="DialogFollow">
import { getSkillPage } from '@/api/clue/skill'
import { createFollow } from '@/api/clue/followRecord'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const formRef = ref() // 表单 Ref
const open = async (type, info) => {
const form = ref({})
const rules = {
operateTime: { required: true, message: '本次跟进时间不可为空', trigger: 'change' },
nextFollowTime: { required: true, message: '下次跟进时间不可为空', trigger: 'change' },
content: { required: true, message: '跟进内容不可为空', trigger: 'blur' }
}
const open = async (clueId) => {
dialogVisible.value = true
dialogTitle.value = type == 'create' ? '新增跟进记录' : '修改跟进记录'
formType.value = type
// 修改时,设置数据
if (info) {
formLoading.value = true
try {
const data = { ...info }
formRef.value.setValues(data)
} finally {
formLoading.value = false
}
}
dialogTitle.value = '新增跟进记录'
resetForm(clueId)
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
const resultList = [
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 1,
deptId: 167,
question: '暂时先看题的',
skillKey: '先看科目一',
keyList: ['先看科目一'],
content:
'<p>科目一的题目不难的,等会可以加个微信给您发一些做题技巧,我们驾校离你也很近,后面您计划找驾校报名了可以接您来我们驾校实地看看的</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 2,
deptId: 167,
question: '你们怎么练车的',
skillKey: '练车人数',
keyList: ['练车人数'],
content:
'<p>我们驾校这边练车是自己提前在微信上跟教练约时间来练车的,科二练车的时候到你练车了是你一个人一辆车,教练一对一教学的,也可以一对二,不用排队等的</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 3,
deptId: 167,
question: '教练怎么样 凶嘛',
skillKey: '教练凶',
keyList: ['教练凶'],
content:
'<p>我们驾校教练都是驾校本部教练,也都是经过正规培训上岗的,脾气比较温和幽默的,不存在凶学员的情况,学车氛围也比较好;学员也不需要给教练送礼的</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 4,
deptId: 167,
question: '驾校通过率咋样',
skillKey: '通过率',
keyList: ['通过率'],
content:
'<p>我们驾校在合肥也是老牌驾校的了,在合肥干了十几年了,并且拥有自家考场,科二科三考试都在我们自家考场考试,所以通过率一直非常高</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 5,
deptId: 167,
question: '可以晚上练车吗?',
skillKey: '晚上练车',
keyList: ['晚上练车'],
content:
'<p>我们驾校练车时间是根据学员的时间来安排的,但是晚上练车视线不太好,因为考试的时候也是白天嘛,建议最好是白天过来练车,然后等熟练了后期可以让教练加加班晚上</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 6,
deptId: 167,
question: '大学生有优惠嘛',
skillKey: '学生优惠',
keyList: ['学生优惠'],
content: '<p>学生我们驾校是有优惠的,而且我们驾校有专设学生班,练车不用排队,随到随练的</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 7,
deptId: 167,
question: '拿证时间多久',
skillKey: '拿证时间',
keyList: ['拿证时间'],
content: '<p>C1手动挡正常拿证时间45天左右拿证C2自动挡正常35天左右拿证</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 8,
deptId: 167,
question: '驾校有熟人',
skillKey: '熟人',
keyList: ['熟人'],
content:
'<p>你朋友认识的人是教练还是驾校里面工作人员呢,驾校场地在哪呢 学车肯定是要对比的 要个近的合适的,我们驾校离你很近,而且学费目前也在做活动比较优惠,您可以对比一下的,找驾校也是可以自己来现场看过后再决定的</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 9,
deptId: 167,
question: '考试地点在哪',
skillKey: '考试地点',
keyList: ['考试地点'],
content:
'<p>科一和科四是统一要去滨湖车管所考试的,科二科三合肥市总共六个考场,科二和科三考试是就近安排考场考试的,这样考试也方便些</p>',
status: 2
},
{
searchValue: null,
createBy: null,
createTime: null,
updateBy: null,
updateTime: null,
remark: null,
params: {},
orderName: null,
orderType: null,
skillId: 10,
deptId: 167,
question: '没时间练车',
skillKey: '没时间学车',
keyList: ['没时间学车'],
content:
'<p>我们驾校练车时间根据学员时间来安排,并且也不用每天都来的,一周抽时间来练个三四次就可以的,每次来一个小时这样,科二来个十来次 科三来个十来次就行,耽误不了你太多时间的</p>',
status: 2
function resetForm(clueId) {
form.value = {
clueId,
operateTime: undefined,
nextFollowTime: undefined,
content: undefined
}
]
}
const showList = ref(resultList)
const keyword = ref('')
const skillList = ref([])
const activeQues = ref('')
function filterList() {
showList.value = resultList.filter((it) => it.question.includes(keyword.value))
const skillSearch = ref({
pageNo: 1,
pageSize: 10,
question: undefined
})
const skillCount = ref(0)
function getSkillList() {
getSkillPage(skillSearch.value).then((data) => {
skillList.value = data.list
skillCount.value = data.total
})
}
function handleSave() {
console.log('保存成功')
dialogVisible.value = false
async function handleSave() {
// 校验表单
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
await createFollow(form.value)
message.success(t('common.createSuccess'))
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
onMounted(() => {
getSkillList()
})
</script>
<style lang="scss" scoped></style>

View File

@@ -15,12 +15,12 @@
<div class="flex justify-between" style="height: 32px">
<div class="flex" style="align-items: center">
<b class="mr-5px text-24px">{{ info.name }}</b>
<div class="mr-5px text-16px">1888888888</div>
<el-tag type="success">A高意向</el-tag>
<div class="mr-5px text-16px">{{ info.phone }}</div>
<el-tag type="success">{{ info.intentionState }}</el-tag>
</div>
<div>
<el-button type="primary" plain>修改</el-button>
<el-button type="danger" plain>删除</el-button>
<el-button type="primary" plain @click="handleUpdate">修改</el-button>
<el-button type="danger" plain @click="handleRemove">删除</el-button>
</div>
</div>
</el-skeleton>
@@ -39,55 +39,26 @@
<el-tab-pane label="跟进记录" name="followRecord">
<el-button class="mb-10px" type="primary" @click="addFollow">添加跟进记录</el-button>
<el-timeline>
<el-timeline-item timestamp="2024-04-01" placement="top">
<el-timeline-item
v-for="item in followRecordList"
:key="item.recordId"
:timestamp="item.operateDate"
placement="top"
>
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div>
<div class="flex justify-between" style="align-items: center">
<div class="flex align-baseline">
<b class="text-18px">张三</b>
<span class="text-14 ml-10px">2024-04-01 09:00:00</span>
</div>
<div>
<el-button type="primary" plain @click="updateFollow()">修改</el-button>
<el-button type="danger" plain>删除</el-button>
</div>
<div>
<b class="text-18px" style="line-height: 36px">{{ item.operateUserName }}</b>
</div>
<div>{{ followContent }}</div>
<div>{{ item.content }}</div>
<div class="flex mt-10px" style="align-items: center">
<div class="flex" style="color: #666; align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>本次跟进时间2024-04-05 10:57</span>
<span>本次跟进时间{{ item.followTime }}</span>
</div>
<div class="flex ml-50px" style="color: #666; align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>下次跟进时间2024-04-10 10:00</span>
</div>
</div>
</div>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2024-02-01" placement="top">
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div>
<div class="flex justify-between" style="align-items: center">
<div class="flex align-baseline">
<b class="text-18px">李四</b>
<span class="text-14 ml-10px">2024-02-01 09:00:00</span>
</div>
<div>
<el-button type="primary" plain>修改</el-button>
<el-button type="danger" plain>删除</el-button>
</div>
</div>
<div>{{ followContent2 }}</div>
<div class="flex mt-10px" style="align-items: center">
<div class="flex" style="color: #666; align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>本次跟进时间2024-02-05 10:57</span>
</div>
<div class="flex ml-50px" style="color: #666; align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>下次跟进时间2024-02-10 10:00</span>
<span>下次跟进时间{{ item.nextFollowTime }}</span>
</div>
</div>
</div>
@@ -96,74 +67,33 @@
</el-timeline>
</el-tab-pane>
<el-tab-pane label="详细信息" name="infoDetail">
<Descriptions :data="info" :schema="schema" :columns="2" />
<el-checkbox v-model="showSchool" :label="true" @change="handleShowSchool"
>展示场地</el-checkbox
>
<div id="dialogMap" class="mt-20px" style="height: 400px; width: 100%"></div>
<Descriptions :data="info" :schema="showSchema" :columns="2" />
<div v-if="appStore.getAppInfo?.instanceType == 1">
<el-checkbox v-model="showSchool" :label="true" @change="handleShowSchool">
展示场地
</el-checkbox>
<div id="dialogMap" class="mt-20px" style="height: 400px; width: 100%"></div>
</div>
</el-tab-pane>
<el-tab-pane label="操作记录" name="operateRecord">
<el-timeline>
<el-timeline-item timestamp="2024-04-01" placement="top">
<el-timeline-item
v-for="item in operateRecordList"
:key="item.recordId"
:timestamp="item.operateDate"
placement="top"
>
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div style="color: #666">
<div class="pt-5px">
<span>操作人机器人1号</span>
<span>操作人{{ item.operateUserName }}</span>
</div>
<div class="pt-5px pb-5px">
<span>成交线索</span>
<span>{{ item.content }}</span>
</div>
<div class="flex" style="align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>操作时间2024-04-05 10:57</span>
</div>
</div>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2024-02-01" placement="top">
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div style="color: #666">
<div class="pt-5px">
<span>操作人机器人2号</span>
</div>
<div class="pt-5px pb-5px">
<span>修改意向状态为高意向</span>
</div>
<div class="flex" style="align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>操作时间2024-04-1 10:57</span>
</div>
</div>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2024-02-01" placement="top">
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div style="color: #666">
<div class="pt-5px">
<span>操作人机器人1号</span>
</div>
<div class="pt-5px pb-5px">
<span>跟进线索</span>
</div>
<div class="flex" style="align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>操作时间2024-04-1 10:57</span>
</div>
</div>
</el-card>
</el-timeline-item>
<el-timeline-item timestamp="2024-02-01" placement="top">
<el-card shadow="always" :body-style="{ padding: '10px' }">
<div style="color: #666">
<div class="pt-5px">
<span>操作人机器人2号</span>
</div>
<div class="pt-5px pb-5px">
<span>创建线索</span>
</div>
<div class="flex" style="align-items: center">
<Icon icon="ep:clock" class="mr-5px" />
<span>操作时间2024-04-1 10:57</span>
<span>操作时间{{ item.followTime }}</span>
</div>
</div>
</el-card>
@@ -172,51 +102,57 @@
</el-tab-pane>
</el-tabs>
<!-- 新建编辑跟进信息 -->
<DialogFollow ref="followRef" />
<DialogFollow ref="followRef" @success="getFollowList" />
</el-drawer>
</template>
<script setup>
<script setup name="DrawerClue">
import { useAppStore } from '@/store/modules/app'
import * as ClueApi from '@/api/clue'
import * as FollowApi from '@/api/clue/followRecord'
import { getPlaceList } from '@/api/school/place'
import DialogFollow from './DialogFollow.vue'
import ImgFlag from '@/assets/imgs/flag/position_blue.png'
import AMapLoader from '@amap/amap-jsapi-loader'
import { formatDate } from '@/utils/formatTime'
import ImgPostion from '@/assets/imgs/flag/position_blue.png'
import FlagRed from '@/assets/imgs/flag/flag_red.png'
import FlagYellow from '@/assets/imgs/flag/flag_yellow.png'
import FlagPurple from '@/assets/imgs/flag/flag_purple.png'
import FlagGreen from '@/assets/imgs/flag/flag_green.png'
import FlagBlue from '@/assets/imgs/flag/flag_blue.png'
import FlagBlack from '@/assets/imgs/flag/flag_black.png'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const appStore = useAppStore()
const show = ref(false)
const info = ref(null)
const loading = ref(false)
const schema = ref([
{
field: 'name',
label: '线索名称'
},
{
field: 'contact',
label: '联系方式'
},
{
field: 'supplier',
label: '意向状态'
},
{
field: 'supplier',
label: '创建时间'
},
{
field: 'purchaseCount',
label: '诉求',
span: 2
},
{
field: 'remark',
label: '备注',
isEditor: true,
span: 2
const props = defineProps({
schema: {
type: Array
}
])
})
const followContent = `<p style="color: red;">这是本次跟进的内容。</p><br/><p>我还能放图片,但需要你自己排版:</p><br/><img style="width: 200px;" src="https://q6.itc.cn/images01/20240407/0e6be21aebc847648109304f20370790.jpeg">`
const followContent2 = `<p style="color: red;">这是本次跟进的内容。</p>`
const showSchema = computed(() => {
const arr = [
{
field: 'requirement',
label: '诉求',
span: 2
},
{
field: 'remark',
label: '备注',
span: 2
}
]
return [...props.schema, ...arr]
})
const followList = ref([
{
@@ -233,67 +169,138 @@ const followList = ref([
}
])
const followRecordList = ref([])
const operateRecordList = ref([])
// 地图相关
const dialogMap = ref(null)
const aMap = ref(null)
function open(row) {
info.value = row
show.value = true
if (!dialogMap.value) {
nextTick(() => {
initMap()
const clueId = ref('')
function getFollowList() {
FollowApi.getFollowList({ clueId: clueId.value }).then((data) => {
followRecordList.value = data.map((item) => ({
operateUserName: item.operateUserName,
content: item.content,
operateDate: formatDate(item.operateTime),
followTime: formatDate(item.operateTime, 'YYYY-MM-DD HH:mm'),
nextFollowTime: formatDate(item.nextFollowTime)
}))
})
}
async function open(id) {
clueId.value = id
try {
getFollowList()
ClueApi.getOpearateRecord({ clueId: id }).then((data) => {
operateRecordList.value = data.map((item) => ({
operateUserName: item.operateUserName,
content: item.content,
operateDate: formatDate(item.operateTime),
followTime: formatDate(item.operateTime, 'YYYY-MM-DD HH:mm')
}))
})
const data = await ClueApi.getClue(id)
info.value = { ...data, ...data.diyParams }
show.value = true
infoIndex.value = 'followRecord'
if (appStore.getAppInfo?.instanceType == 1 && !dialogMap.value) {
nextTick(() => {
getSchoolPlace()
initMap(info.value)
})
}
} catch (error) {
console.log(error)
}
}
function initMap() {
const placeList = ref([])
function getSchoolPlace() {
getPlaceList().then((data) => {
placeList.value = data.placeList
})
}
const defaultLatLng = ref({
lat: 31.86119,
lng: 117.283042
})
function initMap(data) {
AMapLoader.load({
key: '2ffb0e2ea90b1df0b8be48ed66e18fc8', //设置您的key
version: '2.0'
}).then((AMap) => {
aMap.value = AMap
if (data.lng || data.lat) {
defaultLatLng.value = {
lng: data.lng,
lat: data.lat
}
}
dialogMap.value = new AMap.Map('dialogMap', {
zoom: 12,
zooms: [2, 22],
center: [117.283042, 31.86119]
center: [defaultLatLng.value.lng, defaultLatLng.value.lat]
})
if (data.lng || data.lat) {
addmark(data.lng, data.lat, AMap)
}
})
}
const showSchool = ref(false)
const schoolMarkers = ref([])
function handleShowSchool() {
if (showSchool.value) {
let marker1 = new aMap.value.Marker({
map: dialogMap.value,
position: [117.258001, 31.895216],
label: {
content: '慧安驾校桃花社区训练基地',
direction: 'left'
},
icon: ImgFlag,
// extData: element,
clickable: true
})
let marker2 = new aMap.value.Marker({
map: dialogMap.value,
position: [117.286731, 31.902396],
label: {
content: '皖西瑞星驾校总校D',
direction: 'left'
},
icon: ImgFlag,
// extData: element,
clickable: true
})
schoolMarkers.value = [marker1, marker2]
const flagMap = {
red: FlagRed,
yellow: FlagYellow,
purple: FlagPurple,
green: FlagGreen,
blue: FlagBlue,
black: FlagBlack
}
schoolMarkers.value = []
for (let i = 0; i < placeList.value.length; i++) {
const place = placeList.value[i]
const marker = new aMap.value.Marker({
map: dialogMap.value,
position: [place.lng, place.lat],
label: {
content: place.name,
direction: 'left'
},
icon: flagMap[place.flagColor || 'red'],
extData: place,
clickable: true
})
schoolMarkers.value.push(marker)
}
} else {
dialogMap.value.remove(schoolMarkers.value)
}
}
let marker = ref(null)
function addmark(lat, lng, AMap) {
marker.value && removeMarker()
marker.value = new AMap.Marker({
position: new AMap.LngLat(lat, lng),
zoom: 13,
icon: ImgPostion
})
dialogMap.value.add(marker.value)
dialogMap.value.setCenter([lat, lng], '', 500)
}
function removeMarker() {
dialogMap.value.remove(marker.value)
}
const infoIndex = ref('followRecord')
defineExpose({
@@ -302,16 +309,36 @@ defineExpose({
const followRef = ref()
function addFollow() {
followRef.value.open('create', null)
}
function updateFollow() {
followRef.value.open('update', { nextFollowTime: '2024-04-01 12:12' })
followRef.value.open(info.value.clueId)
}
function destroyMap() {
dialogMap.value = null
aMap.value = null
}
const emit = defineEmits(['update', 'getList'])
// 修改
function handleUpdate() {
emit('update', info.value)
show.value = false
}
// 删除
async function handleRemove() {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ClueApi.deleteClue(info.value.clueId)
message.success(t('common.delSuccess'))
// 刷新列表
emit('getList')
show.value = false
} catch (err) {
console.log(err)
}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -1,59 +0,0 @@
import { dateFormatter } from '@/utils/formatTime'
// 表单校验
export const rules = reactive({
username: [required],
password: [required]
})
// CrudSchemahttps://doc.iocoder.cn/vue3/crud-schema/
const crudSchemas = reactive([
{
label: '本次跟进时间',
field: 'createTime',
formatter: dateFormatter,
form: {
component: 'DatePicker',
componentProps: {
type: 'datetime',
format: 'YYYY-MM-DD HH:mm',
valueFormat: 'YYYY-MM-DD HH:mm',
placeholder: '本次跟进时间'
}
}
},
{
label: '下次跟进时间',
field: 'nextTime',
isForm: true,
formatter: dateFormatter,
detail: {
dateFormat: 'YYYY-MM-DD HH:mm'
},
form: {
component: 'DatePicker',
componentProps: {
type: 'datetime',
format: 'YYYY-MM-DD HH:mm',
valueFormat: 'YYYY-MM-DD HH:mm',
placeholder: '下次跟进时间'
}
}
},
{
label: '跟进内容',
field: 'remark',
isTable: true,
form: {
component: 'Input',
componentProps: {
type: 'textarea',
autosize: { minRows: 5, maxRows: 10 }
},
colProps: {
span: 24
}
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -1,200 +0,0 @@
// import { CrudSchema } from '@/hooks/web/useCrudSchemas'
import { dateFormatter } from '@/utils/formatTime'
// import * as MailAccountApi from '@/api/system/mail/account'
// 表单校验
export const rules = reactive({
username: [required],
password: [required],
host: [required],
port: [required],
sslEnable: [required]
})
// const userList = await MailAccountApi.getSimpleMailAccountList()
const userList = []
// CrudSchemahttps://doc.iocoder.cn/vue3/crud-schema/
const crudSchemas = reactive([
{
label: '线索名称',
field: 'name',
isSearch: true,
isTable: true
},
{
label: '联系方式',
field: 'contact',
isSearch: true,
isTable: true
},
{
label: '线索位置',
field: 'address',
isSearch: true,
isTable: true,
isForm: false
},
{
label: '线索来源',
field: 'resource',
isSearch: true,
isTable: true,
search: {
component: 'Select',
api: () => userList,
componentProps: {
optionsAlias: {
labelField: 'name',
valueField: 'id'
}
}
},
form: {
component: 'Select',
api: () => userList,
componentProps: {
optionsAlias: {
labelField: 'name',
valueField: 'id'
}
}
}
},
{
label: '意向状态',
field: 'intention',
isSearch: true,
isTable: true,
table: {
fixed: 'left'
},
search: {
component: 'Select',
api: () => userList,
componentProps: {
optionsAlias: {
labelField: 'name',
valueField: 'id'
}
}
},
form: {
component: 'Select',
api: () => userList,
componentProps: {
optionsAlias: {
labelField: 'name',
valueField: 'id'
}
}
}
},
{
label: '跟进人员',
field: 'userId',
isSearch: true,
isTable: true,
isForm: false,
search: {
component: 'Select',
api: () => userList,
componentProps: {
optionsAlias: {
labelField: 'name',
valueField: 'id'
}
}
}
},
{
label: '下次跟进时间',
field: 'nextTime',
isSearch: true,
isTable: true,
isForm: false,
formatter: dateFormatter,
detail: {
dateFormat: 'YYYY-MM-DD'
},
search: {
component: 'DatePicker',
componentProps: {
type: 'daterange',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
startPlaceholder: '下次跟进时间',
endPlaceholder: '下次跟进时间'
}
}
},
{
label: '诉求',
field: 'need',
isTable: true,
form: {
component: 'Input',
componentProps: {
type: 'textarea'
},
colProps: {
span: 24
}
}
},
{
label: '最新跟进时间',
field: 'latestFollowTime',
isTable: true,
isForm: false
},
{
label: '创建时间',
field: 'createTime',
isSearch: true,
isTable: true,
table: {
fixed: 'left'
},
formatter: dateFormatter,
detail: {
dateFormat: 'YYYY-MM-DD'
},
search: {
component: 'DatePicker',
componentProps: {
type: 'daterange',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
startPlaceholder: '创建时间',
endPlaceholder: '创建时间'
}
},
form: {
component: 'DatePicker',
componentProps: {
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
placeholder: '创建时间'
}
}
},
{
label: '跟进记录',
field: 'followRecord',
isTable: true
},
{
label: '备注',
field: 'remark',
isTable: true,
form: {
component: 'Editor',
colProps: {
span: 24
}
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -1,30 +1,33 @@
<template>
<div>
<div class="relative">
<el-tabs v-model="searchForm.mode" size="small">
<el-tabs v-model="queryType" size="small" @tab-change="getTableList">
<el-tab-pane label="全部" name="0" />
<el-tab-pane name="1">
<template #label>
<Tooltip message="除了无效线索和已成交的线索" />
<el-badge :value="123" :max="9999">
<el-badge v-if="clueCount.unSignNum" :value="clueCount.unSignNum" :max="9999">
<span class="ml-3px">未成交</span>
</el-badge>
<span v-else class="ml-3px">未成交</span>
</template>
</el-tab-pane>
<el-tab-pane name="2">
<template #label>
<Tooltip message="下次跟进时间在今日之前的未成交线索" />
<el-badge :value="234" :max="9999">
<el-badge v-if="clueCount.followNum" :value="clueCount.followNum" :max="9999">
<span class="ml-3px">待跟进</span>
</el-badge>
<span v-else class="ml-3px">待跟进</span>
</template>
</el-tab-pane>
<el-tab-pane name="3">
<template #label>
<Tooltip message="只有创建时间,无下次跟进时间的未成交线索" />
<el-badge :value="423" :max="9999">
<el-badge v-if="clueCount.newNum" :value="clueCount.newNum" :max="9999">
<span class="ml-3px">新线索</span>
</el-badge>
<span v-else class="ml-3px">新线索</span>
</template>
</el-tab-pane>
<el-tab-pane label="公海" name="4" />
@@ -37,7 +40,7 @@
</div>
</div>
<!-- 搜索工作栏 -->
<Search :schema="allSchemas.searchSchema" labelWidth="0">
<Search v-if="!loading" ref="searchRef" :schema="allSchemas.searchSchema" labelWidth="0">
<template #actionMore>
<el-button @click="getTableList" v-hasPermi="['clue:pool:search']"> 搜索 </el-button>
<el-button @click="resetQuery" v-hasPermi="['clue:pool:reset']"> 重置 </el-button>
@@ -45,6 +48,7 @@
</Search>
<!-- 列表 -->
<SSTable
v-if="!loading"
class="mt-20px"
v-model:tableObject="tableObject"
:tableColumns="allSchemas.tableColumns"
@@ -74,6 +78,9 @@
<span>{{ row[item.field] }}</span>
<Icon class="ml-5px" icon="ep:phone" @click="makeCall(row.contact)" />
</div>
<div v-else-if="item.form?.component == 'DatePicker'">
<span>{{ formatDate(row[item.field]) }}</span>
</div>
<span v-else>{{ row[item.field] }}</span>
</template>
</el-table-column>
@@ -103,40 +110,79 @@
>
登记
</el-button>
<el-button type="primary" link v-hasPermi="['clue:pool:release']"> 释放 </el-button>
<el-button
type="primary"
link
v-hasPermi="['clue:pool:release']"
@click="handleRelease(scope.row.clueId)"
>
释放
</el-button>
</template>
</el-table-column>
</SSTable>
<DialogClue ref="formRef" />
<DrawerClue ref="drawerRef" />
<DialogClue
v-if="!loading"
ref="formRef"
:userOptions="userOptions"
:schema="allSchemas.formSchema"
@sucess="getTableList"
/>
<DrawerClue
v-if="!loading"
ref="drawerRef"
:schema="allSchemas.formSchema"
@get-list="getTableList"
@update="handleEdit"
/>
<DialogSuccess ref="successRef" />
<DialogFollow ref="followRef" />
<DialogFollow ref="followRef" @success="getTableList" />
</div>
</template>
<script setup name="CluePool">
import { allSchemas } from './cluePool.data'
import { getSimpleFieldList } from '@/api/clue/clueField'
import DialogClue from './Comp/DialogClue.vue'
import DrawerClue from './Comp/DrawerClue.vue'
import DialogSuccess from './Comp/DialogSuccess.vue'
import DialogFollow from './Comp/DialogFollow.vue'
import { getSimpleUserList as getUserOption } from '@/api/system/user'
import { removeNullField } from '@/utils'
import { formatDate } from '@/utils/formatTime'
import * as ClueApi from '@/api/clue'
const message = useMessage() // 消息弹窗
const searchRef = ref()
const queryType = ref('2')
const searchForm = ref({
mode: '2'
})
const formRef = ref()
const drawerRef = ref()
const successRef = ref()
const followRef = ref()
// const { tableObject, tableMethods } = useTable({
// getListApi: MailTemplateApi.getMailTemplatePage, // 分页接口
// delListApi: MailTemplateApi.deleteMailTemplate // 删除接口
// })
const loading = ref(true)
const allSchemas = ref({})
async function getCurdSchemas() {
loading.value = true
try {
const data = await getSimpleFieldList()
allSchemas.value = useCrudSchemas(data).allSchemas
} finally {
loading.value = false
nextTick(() => {
getTableList()
})
}
}
const tableObject = ref({
tableList: [{ name: '测试', contact: '17318531354' }],
tableList: [],
loading: false,
total: 1,
pageSize: 20,
@@ -151,15 +197,40 @@ function getCheckedColumns(list) {
}
function resetQuery() {
searchForm.value = {
pageNo: 1,
pageSize: 10
}
searchRef.value.reset()
tableObject.value.currentPage = 1
getTableList()
}
function getTableList() {
async function getTableList() {
// 查询
tableObject.value.loading = true
getSearchCount()
try {
const queryParams = await searchRef.value.getFormModel()
const params = {
...queryParams,
pageNo: tableObject.value.currentPage,
pageSize: tableObject.value.pageSize,
queryType: queryType.value
}
const data = await ClueApi.getCluePage(removeNullField(params))
tableObject.value.tableList = data.list.map((it) => ({ ...it, ...it.diyParams }))
tableObject.value.total = data.total
} finally {
tableObject.value.loading = false
}
}
const clueCount = ref({
unSignNum: 0,
followNum: 0,
newNum: 0
})
function getSearchCount() {
ClueApi.getClueCount().then((data) => {
clueCount.value = data
})
}
// 新增
@@ -168,15 +239,15 @@ function handleInsert() {
}
// 编辑
function handleEdit(row) {
formRef.value.open('update', row)
formRef.value.open('update', row.clueId)
}
// 详情
function handleDetail(row) {
drawerRef.value.open(row)
drawerRef.value.open(row.clueId)
}
function handleFollow(row) {
followRef.value.open('create', row)
followRef.value.open(row.clueId)
}
async function makeCall(phone) {
@@ -187,6 +258,35 @@ async function makeCall(phone) {
function handleSuccess(row) {
successRef.value.open(row)
}
// 释放
function handleRelease(id) {
message.prompt('请先输入释放原因', '是否确认释放线索?').then((res) => {
if (res.value) {
try {
ClueApi.releaseClue({
clueId: id,
publicClue: true,
discardReason: res.value
}).then(() => {
message.success('释放成功')
})
} finally {
getTableList()
}
} else {
message.info('请将释放原因填写完整!')
}
})
}
const userOptions = ref([])
onMounted(() => {
getUserOption().then((data) => {
userOptions.value = data
})
getCurdSchemas()
})
</script>
<style lang="scss" scoped></style>

View File

@@ -1,10 +1,10 @@
<template>
<div>
<el-table :data="list" border>
<el-table v-loading="loading" :data="list" border>
<el-table-column type="index" width="50" />
<el-table-column label="来源名称" width="200px">
<template #default="{ row }">
<el-input v-model="row.name" placeholder="请输入" :clearable="false" />
<el-input v-model="row.source" placeholder="请输入" :clearable="false" />
</template>
</el-table-column>
<el-table-column label="渠道" width="300px">
@@ -24,14 +24,14 @@
:value="item.value"
/>
</el-select>
<span>{{ row.resourceName }}</span>
<span>{{ row.channel }}</span>
</template>
</el-table-column>
<el-table-column label="获取连接" prop="link" />
<el-table-column label="获取连接" prop="url" />
<el-table-column label="参数详情" prop="params" />
<el-table-column label="是否启用" width="100px">
<template #default="{ row }">
<el-switch v-model="row.inEnable" :active-value="true" :inactive-value="false" />
<el-switch v-model="row.status" :active-value="0" :inactive-value="1" />
</template>
</el-table-column>
<el-table-column label="操作" width="100px">
@@ -49,56 +49,60 @@
</div>
</template>
<script setup>
const list = ref([
{
name: '一点通账号1',
resourceName: '驾校一点通',
link: 'https://sscrm.ahduima.com/clue/get?cid=1001&aid=1001&res=2',
params: '参数详情'
},
{
name: '一点通账号2',
resourceName: '驾校一点通',
link: 'https://sscrm.ahduima.com/clue/get?cid=1001&aid=1001&res=2',
params: '参数详情'
},
{
name: '宝典账号',
resourceName: '驾考宝典',
link: 'https://sscrm.ahduima.com/clue/get?cid=1001&aid=1001&res=1',
params: '参数详情'
},
{
name: '抖音',
resourceName: '抖音/开心学车',
link: 'https://sscrm.ahduima.com/clue/get?cid=1001&aid=1001&res=3',
params: '参数详情'
}
])
<script setup name="ClueSet">
import { getClueGainRuleList, deleteClueGainRule } from '@/api/clue/clueGetSet'
const resourceOptions = ref([
{ value: 1, label: '驾考宝典' },
{ value: 2, label: '一点通' },
{ value: 3, label: '抖音' }
])
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const list = ref([])
const loading = ref(false)
const resourceOptions = ref([])
async function getList() {
loading.value = true
try {
const data = await getClueGainRuleList()
list.value = data
} finally {
loading.value = false
}
}
function handleInsert() {
list.value.push({ name: '', link: '', edit: true, inEnable: true })
list.value.push({ source: '', url: '', edit: true, status: 0 })
}
function onSubmit() {
console.log('保存成功')
}
function handleRemove(idx) {
list.value.splice(idx, 1)
async function handleRemove(idx) {
if (list.value[idx].ruleId) {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await deleteClueGainRule(list.value[idx].ruleId)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch (err) {
console.log(err)
}
} else {
list.value.splice(idx, 1)
}
}
function handleChange(row) {
row.link = `https://sscrm.ahduima.com/clue/get?cid=1001&aid=1001&res=${row.resource}`
row.url = `https://sscrm.ahduima.com/clue/get?cid=1001&aid=1001&res=${row.resource}`
row.params = '参数详情'
}
onMounted(() => {
getList()
})
</script>
<style lang="scss" scoped></style>

View File

@@ -2,22 +2,33 @@
<div class="flex">
<div class="mr-20px" style="width: 500px">
<el-input
v-model="searchForm.keyword"
v-model="searchForm.nickname"
placeholder="请输入关键字查询"
clearable
class="mb-10px"
@keyup.enter="getUserList"
/>
<el-table :data="userList" @cell-click="selectUser">
<el-table-column prop="name" label="员工姓名" />
<el-table-column prop="phone" label="电话" />
<el-table-column prop="workNum" label="工号" />
<el-table
v-loading="loading"
:data="userList"
:row-class-name="setRowClass"
@row-click="handleRowClick"
>
<el-table-column prop="nickname" label="员工姓名" />
<el-table-column prop="mobile" label="电话" />
<el-table-column prop="status" label="状态">
<template #default="{ row }">
{{ ['在职', '离职'][row.status] }}
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="searchForm.pageSize"
v-model:page="searchForm.pageNum"
v-model:page="searchForm.pageNo"
small
layout="total, prev, pager, next, jumper"
:total="total"
@pagination="getUserList"
/>
@@ -65,16 +76,27 @@
</div>
</template>
<script setup>
<script setup name="ClueSend">
import { getUserPage } from '@/api/system/user'
// const message = useMessage() // 消息弹窗
// const { t } = useI18n() // 国际化
const searchForm = ref({
keyword: '',
nickname: '',
pageSize: 20,
pageNum: 1
pageNo: 1
})
const total = ref(0)
const loading = ref(false)
const userList = ref([])
const userList = ref([{ name: '张三', phone: '1888888888', workNum: '202101030001' }])
function setRowClass({ row }) {
return row.field == currentRowId.value ? 'current-row' : ''
}
const currentRowId = ref('')
const form = ref({
isAuto: 1,
@@ -84,8 +106,18 @@ const form = ref({
})
const rules = ref({})
function getUserList() {
console.log('获取列表')
async function getUserList() {
loading.value = true
try {
const data = await getUserPage(searchForm.value)
userList.value = data.list
if (userList.value.length) {
handleRowClick(userList.value[0])
}
total.value = data.total
} finally {
loading.value = false
}
}
function onSubmit() {
@@ -111,9 +143,14 @@ function resourceCheckedChange(val) {
resourceIndeterminate.value = checkedCount > 0 && checkedCount < resourceOptions.value.length
}
function selectUser(row) {
console.log(row)
function handleRowClick(row) {
currentRowId.value = row.ruleId
form.value = { ...row }
}
onMounted(() => {
getUserList()
})
</script>
<style lang="scss" scoped></style>

View File

@@ -1,9 +1,9 @@
<template>
<div>
<el-form ref="queryForm" :model="searchForm" label-width="0" inline>
<el-form ref="queryForm" :model="searchForm" label-width="0" inline @submit.prevent>
<el-form-item>
<el-input
v-model="searchForm.name"
v-model="searchForm.sourceName"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
@@ -22,84 +22,89 @@
:tree-props="{ children: 'children' }"
>
<el-table-column prop="sourceName" label="来源名称" />
<el-table-column prop="orderNum" label="排序" width="100px" />
<el-table-column prop="sort" label="排序" width="100px" />
<el-table-column prop="remark" label="备注" />
<el-table-column label="创建时间" prop="createTime" width="180px" />
<el-table-column label="创建人" prop="createUser" width="150px" />
<el-table-column
label="创建时间"
prop="createTime"
width="180px"
:formatter="dateFormatter"
/>
<el-table-column label="状态">
<template #default="{ row }">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button type="primary" text @click="openForm('update', scope.row)">修改</el-button>
<el-button type="primary" text @click="openForm('createChildren', scope.row)"
>新增</el-button
>
<el-button type="danger" text @click="handleDelete(scope.row)">删除</el-button>
<template #default="{ row }">
<el-button type="primary" text @click="openForm('update', row)">修改</el-button>
<el-button type="primary" text @click="openForm('createChildren', row)"> 新增 </el-button>
<el-button type="danger" text @click="handleDelete(row.sourceId)">删除</el-button>
</template>
</el-table-column>
</el-table>
<Pagination
v-model:limit="searchForm.pageSize"
v-model:page="searchForm.pageNum"
:total="total"
@pagination="handleQuery"
/>
<DialogSource ref="sourceDialog" @success="handleQuery" />
</div>
</template>
<script setup name="ClueSource">
import { handleTree } from '@/utils/tree'
import * as SourceApi from '@/api/clue/source'
import { dateFormatter } from '@/utils/formatTime'
import DialogSource from './DialogSource.vue'
import { DICT_TYPE } from '@/utils/dict'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const searchForm = ref({
pageNum: 1,
pageSize: 20
sourceName: undefined
})
const total = ref(0)
const sourceDialog = ref()
const tableList = ref([])
const loading = ref(false)
function handleQuery() {
searchForm.value.pageNum = 1
getList()
}
function resetQuery() {
searchForm.value = {
question: '',
pageSize: 20,
pageNum: 1
sourceName: ''
}
getList()
}
function getList() {
tableList.value = [
{
sourceId: 1,
sourceName: '测试',
level: 1,
children: [{ sourceId: 1001, sourceName: '二级来源', level: 2, parentSource: '测试' }]
}
]
async function getList() {
loading.value = true
try {
const data = await SourceApi.getSourcePage(searchForm.value)
tableList.value = handleTree(data, 'sourceId')
} finally {
loading.value = false
}
}
function openForm(type, info) {
sourceDialog.value.open(type, info)
}
async function handleDelete(row) {
async function handleDelete(id) {
try {
console.log(row)
// 删除的二次确认
await message.delConfirm()
// 发起删除
// await UserApi.deleteUser(row.id)
await SourceApi.deleteSource(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
onMounted(() => {
getList()
})
</script>
<style lang="scss" scoped></style>

View File

@@ -7,14 +7,20 @@
:rules="formRules"
label-width="80px"
>
<el-form-item v-if="formData.level > 1" label="上级来源">
<el-form-item v-if="formData.parentSource" label="上级来源">
<el-input v-model="formData.parentSource" disabled />
</el-form-item>
<el-form-item label="来源名称" prop="sourceName">
<el-input v-model="formData.sourceName" placeholder="请输入来源名称" />
</el-form-item>
<el-form-item label="排序" prop="orderNum">
<el-input v-model="formData.orderNum" placeholder="请输入排序" type="number" :min="0" />
<el-form-item label="排序" prop="sort">
<el-input v-model="formData.sort" placeholder="请输入排序" type="number" :min="0" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="0"> 启用 </el-radio>
<el-radio :label="1"> 禁用 </el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
@@ -32,6 +38,8 @@
</Dialog>
</template>
<script name="DialogSource" setup>
import * as SourceApi from '@/api/clue/source'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
@@ -41,7 +49,7 @@ const formLoading = ref(false) // 表单的加载中1修改时的数据加
const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({
sourceName: '',
orderNum: 1,
sort: 1,
remark: ''
})
const formRules = reactive({
@@ -56,16 +64,15 @@ const open = async (type, info) => {
formType.value = type
resetForm()
// 修改时,设置数据
if (info.sourceId) {
if (info?.sourceId) {
formLoading.value = true
try {
if (type == 'update') {
formData.value = { ...info }
formData.value = await SourceApi.getSource(info.sourceId)
} else {
formData.value.level = info.level + 1
formData.value.parentSource = info.sourceName
formData.value.parentId = info.sourceId
}
// formData.value = await UserApi.getUser(id)
} finally {
formLoading.value = false
}
@@ -83,13 +90,12 @@ const submitForm = async () => {
// 提交请求
formLoading.value = true
try {
// const data = formData.value as unknown as UserApi.UserVO
if (formType.value === 'create') {
// await UserApi.createUser(data)
message.success(t('common.createSuccess'))
} else {
// await UserApi.updateUser(data)
if (formType.value === 'update') {
await SourceApi.updateSource(formData.value)
message.success(t('common.updateSuccess'))
} else {
await SourceApi.createSource(formData.value)
message.success(t('common.createSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
@@ -103,7 +109,8 @@ const submitForm = async () => {
const resetForm = () => {
formData.value = {
sourceName: '',
orderNum: 1,
status: 0,
sort: 1,
remark: ''
}
formRef.value?.resetFields()

View File

@@ -2,45 +2,55 @@
<el-row :gutter="80">
<el-col :span="10" :offset="0">
<el-button class="mb-10px" type="primary" @click="handleInsert">新增属性</el-button>
<el-table :data="tableList">
<el-table-column prop="name" label="名称" />
<el-table-column label="启用状态">
<el-table :data="tableList" :row-class-name="setRowClass" @row-click="handleRowClick">
<el-table-column prop="label" label="名称" />
<el-table-column prop="field" label="属性编码" />
<el-table-column prop="component" label="类型" width="200px">
<template #default="{ row }">
{{ typeOptions.find((it) => it.value == row.component).label }}
</template>
</el-table-column>
<el-table-column label="启用状态" width="100">
<template #default="{ row }">
<el-switch
v-model="row.status"
:active-value="1"
:inactive-value="0"
:disabled="!row.canUpdate"
:active-value="0"
:inactive-value="1"
@change="changeStatus(row)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="80px">
<template #default="{ row }">
<el-button
type="primary"
text
:disabled="!row.canUpdate"
style="padding: 0"
@click="remove(row)"
<el-button type="primary" text style="padding: 0" @click="remove(row.clueParamId)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</el-col>
<el-col :span="14" :offset="0">
<el-form :model="form" ref="fieldForm" :rules="rules" label-width="80px" :inline="false">
<el-form-item label="属性名称" prop="name">
<el-input v-model="form.name" placeholder="请输入属性名称" />
<el-col :span="14" :offset="0" v-if="tableList.length || formType == 'create'">
<el-form :model="form" ref="fieldForm" :rules="rules" label-width="100px" :inline="false">
<el-form-item label="属性名称" prop="label">
<el-input v-model="form.label" placeholder="请输入属性名称" />
</el-form-item>
<el-form-item label="属性类型" prop="type">
<el-form-item prop="field">
<template #label>
<div class="flex justify-center" style="align-items: center">
<span>属性编码</span>
<Tooltip message="请输入字母或数字,必须以字母开头" />
</div>
</template>
<el-input v-model="form.field" placeholder="请输入属性编码" />
</el-form-item>
<el-form-item label="属性类型" prop="component">
<el-select
v-model="form.type"
v-model="form.component"
placeholder="请选择属性类型"
clearable
filterable
style="width: 100%"
@change="form.options = []"
>
<el-option
v-for="item in typeOptions"
@@ -51,72 +61,174 @@
</el-select>
</el-form-item>
<el-form-item
v-if="['Checkbox', 'Radio', 'Select'].includes(form.type)"
v-if="['Checkbox', 'Radio', 'Select'].includes(form.component)"
label="选项"
prop="option"
key="option"
prop="options"
key="options"
>
<div>
<el-button type="primary" @click="optionList.push([])"> 新增选项 </el-button>
<el-button type="primary" @click="handleAddOption"> 新增选项 </el-button>
<div
class="flex justify-between mt-10px"
v-for="(item, index) in optionList"
v-for="(item, index) in form.options"
:key="index"
>
<el-input v-model="item.label" placeholder="请输入选项内容" clearable />
<el-button type="primary" text @click="optionList.splice(index, 1)">删除</el-button>
<el-input
v-model="item.name"
placeholder="请输入选项内容"
clearable
style="width: 300px !important"
@blur="item.id = item.name"
/>
<el-button type="primary" text @click="form.options.splice(index, 1)">删除</el-button>
</div>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button type="primary" :disabled="formLoading" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>
<script setup>
const tableList = ref([{ name: '咨询车型', status: 0, canUpdate: false }])
<script setup name="FieldClue">
import * as FieldApi from '@/api/clue/clueField'
import { getDictOptions } from '@/utils/dict'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const tableList = ref([])
const currentRowId = ref('')
function setRowClass({ row }) {
return row.field == currentRowId.value ? 'current-row' : ''
}
function handleRowClick(row) {
formType.value = 'update'
currentRowId.value = row.field
form.value = { ...row }
}
const form = ref({
name: undefined,
type: undefined,
option: undefined
label: undefined,
field: '',
component: undefined,
options: [],
status: 0,
isCustom: true,
isForm: true,
isSearch: true,
isTable: true
})
const typeOptions = ref([
{ label: '输入框', value: 'Input' },
{ label: '多选', value: 'Checkbox' },
{ label: '单选', value: 'Radio' },
{ label: '下拉选', value: 'Select' },
{ label: '开关', value: 'Switch' },
{ label: '日期选择', value: 'DatePicker' },
{ label: '时间选择', value: 'TimePicker' },
{ label: '富文本', value: 'Editor' },
{ label: '图片', value: 'UploadImg' },
{ label: '文件', value: 'UploadFile' }
])
const loading = ref(false)
const rules = {}
const typeOptions = ref([])
const optionList = ref([])
const rules = {
label: { required: true, message: '名称不可为空', trigger: 'blur' },
field: { required: true, message: '编码不可为空', trigger: 'blur' },
component: { required: true, message: '类型不可为空', trigger: 'change' }
}
async function getList() {
loading.value = true
try {
const data = await FieldApi.getDiyFieldList()
tableList.value = data
if (data.length) {
handleRowClick(data[0])
}
} finally {
loading.value = false
}
}
const formType = ref('')
function handleInsert() {
console.log('新增')
formType.value = 'create'
form.value = {
label: undefined,
field: '',
component: undefined,
options: [],
status: 0,
isCustom: true,
isForm: true,
isSearch: true,
isTable: true
}
}
function changeStatus(row) {
console.log(row.status)
async function remove(id) {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await FieldApi.deleteField(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch (err) {
console.log(err)
}
}
function remove(row) {
console.log(row.status)
async function changeStatus(row) {
try {
// 二次确认
await message.confirm('是否确认修改状态')
await FieldApi.updateFieldStatus(row.clueParamId, row.status)
message.success('修改成功')
// 刷新列表
await getList()
} catch {
// 取消后,进行恢复按钮
row.status = row.status == 0 ? 1 : 0
}
}
function onSubmit() {
console.log('保存')
const fieldForm = ref()
const formLoading = ref(false)
function handleAddOption() {
if (form.value.options) {
form.value.options.push({})
} else {
form.value.options = []
}
}
async function onSubmit() {
// 校验表单
if (!fieldForm.value) return
const valid = await fieldForm.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
if (formType.value === 'create') {
await FieldApi.createField(form.value)
message.success(t('common.createSuccess'))
} else {
await FieldApi.updateField(form.value)
message.success(t('common.updateSuccess'))
}
// 发送操作成功的事件
getList()
} finally {
formLoading.value = false
}
}
onMounted(() => {
typeOptions.value = getDictOptions('attribute_type')
getList()
})
</script>
<style lang="scss" scoped></style>

View File

@@ -2,45 +2,55 @@
<el-row :gutter="80">
<el-col :span="10" :offset="0">
<el-button class="mb-10px" type="primary" @click="handleInsert">新增属性</el-button>
<el-table :data="tableList">
<el-table-column prop="name" label="名称" />
<el-table-column label="启用状态">
<el-table :data="tableList" :row-class-name="setRowClass" @row-click="handleRowClick">
<el-table-column prop="label" label="名称" />
<el-table-column prop="field" label="属性编码" />
<el-table-column prop="component" label="类型" width="200px">
<template #default="{ row }">
{{ typeOptions.find((it) => it.value == row.component).label }}
</template>
</el-table-column>
<el-table-column label="启用状态" width="100">
<template #default="{ row }">
<el-switch
v-model="row.status"
:active-value="1"
:inactive-value="0"
:disabled="!row.canUpdate"
:active-value="0"
:inactive-value="1"
@change="changeStatus(row)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="80px">
<template #default="{ row }">
<el-button
type="primary"
text
:disabled="!row.canUpdate"
style="padding: 0"
@click="remove(row)"
<el-button type="primary" text style="padding: 0" @click="remove(row.clueParamId)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</el-col>
<el-col :span="14" :offset="0">
<el-form :model="form" ref="fieldForm" :rules="rules" label-width="80px" :inline="false">
<el-form-item label="属性名称" prop="name">
<el-input v-model="form.name" placeholder="请输入属性名称" />
<el-col :span="14" :offset="0" v-if="tableList.length || formType == 'create'">
<el-form :model="form" ref="fieldForm" :rules="rules" label-width="100px" :inline="false">
<el-form-item label="属性名称" prop="label">
<el-input v-model="form.label" placeholder="请输入属性名称" />
</el-form-item>
<el-form-item label="属性类型" prop="type">
<el-form-item prop="field">
<template #label>
<div class="flex justify-center" style="align-items: center">
<span>属性编码</span>
<Tooltip message="请输入字母或数字,必须以字母开头" />
</div>
</template>
<el-input v-model="form.field" placeholder="请输入属性编码" />
</el-form-item>
<el-form-item label="属性类型" prop="component">
<el-select
v-model="form.type"
v-model="form.component"
placeholder="请选择属性类型"
clearable
filterable
style="width: 100%"
@change="form.options = []"
>
<el-option
v-for="item in typeOptions"
@@ -51,72 +61,174 @@
</el-select>
</el-form-item>
<el-form-item
v-if="['Checkbox', 'Radio', 'Select'].includes(form.type)"
v-if="['Checkbox', 'Radio', 'Select'].includes(form.component)"
label="选项"
prop="option"
key="option"
prop="options"
key="options"
>
<div>
<el-button type="primary" @click="optionList.push([])"> 新增选项 </el-button>
<el-button type="primary" @click="handleAddOption"> 新增选项 </el-button>
<div
class="flex justify-between mt-10px"
v-for="(item, index) in optionList"
v-for="(item, index) in form.options"
:key="index"
>
<el-input v-model="item.label" placeholder="请输入选项内容" clearable />
<el-button type="primary" text @click="optionList.splice(index, 1)">删除</el-button>
<el-input
v-model="item.name"
placeholder="请输入选项内容"
clearable
style="width: 300px !important"
@blur="item.id = item.name"
/>
<el-button type="primary" text @click="form.options.splice(index, 1)">删除</el-button>
</div>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button type="primary" :disabled="formLoading" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>
<script setup>
const tableList = ref([{ name: '额外支出费用', status: 1, canUpdate: true }])
<script setup name="FieldOrder">
import * as FieldApi from '@/api/clue/orderField.js'
import { getDictOptions } from '@/utils/dict'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const tableList = ref([])
const currentRowId = ref('')
function setRowClass({ row }) {
return row.field == currentRowId.value ? 'current-row' : ''
}
function handleRowClick(row) {
formType.value = 'update'
currentRowId.value = row.field
form.value = { ...row }
}
const form = ref({
name: undefined,
type: undefined,
option: undefined
label: undefined,
field: '',
component: undefined,
options: [],
status: 0,
isCustom: true,
isForm: true,
isSearch: true,
isTable: true
})
const typeOptions = ref([
{ label: '输入框', value: 'Input' },
{ label: '多选', value: 'Checkbox' },
{ label: '单选', value: 'Radio' },
{ label: '下拉选', value: 'Select' },
{ label: '开关', value: 'Switch' },
{ label: '日期选择', value: 'DatePicker' },
{ label: '时间选择', value: 'TimePicker' },
{ label: '富文本', value: 'Editor' },
{ label: '图片', value: 'UploadImg' },
{ label: '文件', value: 'UploadFile' }
])
const loading = ref(false)
const rules = {}
const typeOptions = ref([])
const optionList = ref([])
const rules = {
label: { required: true, message: '名称不可为空', trigger: 'blur' },
field: { required: true, message: '编码不可为空', trigger: 'blur' },
component: { required: true, message: '类型不可为空', trigger: 'change' }
}
async function getList() {
loading.value = true
try {
const data = await FieldApi.getDiyFieldList()
tableList.value = data
if (data.length) {
handleRowClick(data[0])
}
} finally {
loading.value = false
}
}
const formType = ref('')
function handleInsert() {
console.log('新增')
formType.value = 'create'
form.value = {
label: undefined,
field: '',
component: undefined,
options: [],
status: 0,
isCustom: true,
isForm: true,
isSearch: true,
isTable: true
}
}
function changeStatus(row) {
console.log(row.status)
async function remove(id) {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await FieldApi.deleteField(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch (err) {
console.log(err)
}
}
function remove(row) {
console.log(row.status)
async function changeStatus(row) {
try {
// 二次确认
await message.confirm('是否确认修改状态')
await FieldApi.updateFieldStatus(row.clueParamId, row.status)
message.success('修改成功')
// 刷新列表
await getList()
} catch {
// 取消后,进行恢复按钮
row.status = row.status == 0 ? 1 : 0
}
}
function onSubmit() {
console.log('保存')
const fieldForm = ref()
const formLoading = ref(false)
function handleAddOption() {
if (form.value.options) {
form.value.options.push({})
} else {
form.value.options = []
}
}
async function onSubmit() {
// 校验表单
if (!fieldForm.value) return
const valid = await fieldForm.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
if (formType.value === 'create') {
await FieldApi.createField(form.value)
message.success(t('common.createSuccess'))
} else {
await FieldApi.updateField(form.value)
message.success(t('common.updateSuccess'))
}
// 发送操作成功的事件
getList()
} finally {
formLoading.value = false
}
}
onMounted(() => {
typeOptions.value = getDictOptions('attribute_type')
getList()
})
</script>
<style lang="scss" scoped></style>

View File

@@ -1,27 +1,35 @@
<template>
<div>
<el-tabs v-model="tabIndex" type="border-card">
<el-tab-pane label="线索属性" :name="0">
<FieldClue />
<el-tab-pane label="线索属性" :name="0" v-if="checkPermi(['clue:setting:clue-props'])">
<FieldClue v-if="tabIndex == 0" />
</el-tab-pane>
<el-tab-pane label="成交属性" :name="10">
<FieldOrder />
<el-tab-pane label="成交属性" :name="10" v-if="checkPermi(['clue:setting:order-props'])">
<FieldOrder v-if="tabIndex == 10" />
</el-tab-pane>
<el-tab-pane label="线索来源" :name="15">
<ClueSource />
<el-tab-pane label="线索来源" :name="15" v-if="checkPermi(['clue:setting:clue-resource'])">
<ClueSource v-if="tabIndex == 15" />
</el-tab-pane>
<el-tab-pane label="线索获取规则" :name="20">
<ClueGet />
</el-tab-pane>
<el-tab-pane label="线索分配规则" :name="30">
<ClueSend />
</el-tab-pane>
<el-tab-pane label="常规设置" :name="40">
<GeneralSet />
</el-tab-pane>
<el-tab-pane label="消息通知" :name="50">
<MsgSend />
<!-- <el-tab-pane
label="线索获取规则"
:name="20"
v-if="checkPermi(['clue:setting:clue-get-rules'])"
>
<ClueGet v-if="tabIndex == 20" />
</el-tab-pane> -->
<!-- <el-tab-pane
label="线索分配规则"
:name="30"
v-if="checkPermi(['clue:setting:clue-send-rules'])"
>
<ClueSend v-if="tabIndex == 30" />
</el-tab-pane> -->
<el-tab-pane label="常规设置" :name="40" v-if="checkPermi(['clue:setting:general-setting'])">
<GeneralSet v-if="tabIndex == 40" />
</el-tab-pane>
<!-- <el-tab-pane label="消息通知" :name="50" v-if="checkPermi(['mall:setting:prod'])">
<MsgSend v-if="tabIndex == 50" />
</el-tab-pane> -->
</el-tabs>
</div>
</template>
@@ -30,10 +38,11 @@
import FieldClue from './Comp/FieldClue.vue'
import FieldOrder from './Comp/FieldOrder.vue'
import ClueSource from './Comp/ClueSource.vue'
import ClueGet from './Comp/ClueGet.vue'
import ClueSend from './Comp/ClueSend.vue'
import MsgSend from './Comp/MsgSend.vue'
// import ClueGet from './Comp/ClueGet.vue'
// import ClueSend from './Comp/ClueSend.vue'
// import MsgSend from './Comp/MsgSend.vue'
import GeneralSet from './Comp/GeneralSet.vue'
import { checkPermi } from '@/utils/permission'
const tabIndex = ref(0)
</script>

View File

@@ -11,9 +11,13 @@
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button @click="resetQuery">重置</el-button>
<el-button type="primary" plain @click="handleAdd">新增</el-button>
<el-button type="primary" @click="handleQuery" v-hasPermi="['clue:skill:search']">
搜索
</el-button>
<el-button @click="resetQuery" v-hasPermi="['clue:skill:reset']">重置</el-button>
<el-button type="primary" plain @click="handleAdd" v-hasPermi="['clue:skill:add']">
新增
</el-button>
</el-form-item>
</el-form>
<el-table :data="tableList">
@@ -27,8 +31,22 @@
<el-table-column label="关键词" align="center" prop="skillKey" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="primary" text @click="handleUpdate(scope.row)">修改</el-button>
<el-button type="primary" text @click="handleDelete(scope.row)">删除</el-button>
<el-button
type="primary"
text
@click="handleUpdate(scope.row)"
v-hasPermi="['clue:skill:update']"
>
修改
</el-button>
<el-button
type="primary"
text
@click="handleDelete(scope.row)"
v-hasPermi="['clue:skill:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>

View File

@@ -51,8 +51,8 @@
<el-table class="mt-20px" :data="tableList" border>
<el-table-column type="index" width="50px" />
<el-table-column prop="" label="产品名称" />
<el-table-column prop="" label="规格" />
<el-table-column prop="productName" label="产品名称" />
<el-table-column prop="specsName" label="规格" />
<el-table-column label="变动类型">
<template #default="{ row }">
{{ ['', '入库', '出库'][row.changeType] }}
@@ -61,8 +61,8 @@
<el-table-column prop="num" label="数量" />
<el-table-column prop="money" label="金额" />
<el-table-column prop="changeTime" label="变动时间" :formatter="dateFormatter" />
<el-table-column prop="changeUser" label="变动人员" />
<el-table-column prop="" label="所属仓库" />
<el-table-column prop="changeUserName" label="变动人员" />
<el-table-column prop="warehouseName" label="所属仓库" />
<el-table-column prop="remark" label="备注" />
</el-table>
<Pagination

View File

@@ -25,12 +25,19 @@
<el-button
type="primary"
style="padding: 0"
:disabled="row.type == 1"
text
@click="openForm('update', row.warehouseId)"
>
修改
</el-button>
<el-button type="danger" style="padding: 0" text @click="handleRemove(row.warehouseId)">
<el-button
type="danger"
style="padding: 0"
:disabled="row.type == 1"
text
@click="handleRemove(row.warehouseId)"
>
删除
</el-button>
</template>

View File

@@ -15,16 +15,16 @@
<el-tab-pane label="常规设置" :name="4" v-if="checkPermi(['mall:setting:general'])">
<GeneralSet v-if="tabIndex == 4" />
</el-tab-pane>
<el-tab-pane label="消息通知" :name="9" v-if="checkPermi(['mall:setting:msg'])">
<!-- <el-tab-pane label="消息通知" :name="9" v-if="checkPermi(['mall:setting:msg'])">
<MsgSend v-if="tabIndex == 9" />
</el-tab-pane>
</el-tab-pane> -->
</el-tabs>
</template>
<script setup>
import GeneralSet from './Comp/GeneralSet.vue'
import FieldProduct from './Comp/FieldProduct.vue'
import MsgSend from './Comp/MsgSend.vue'
// import MsgSend from './Comp/MsgSend.vue'
import CategorySet from './Comp/CategorySet.vue'
import BrandSet from './Comp/BrandSet.vue'
import SupplierSet from './Comp/SupplierSet.vue'