salary
qsh 4 months ago
parent 5b3e02b447
commit e35a32e985
  1. 11
      src/api/clue/clueCache.js
  2. 36
      src/api/clue/index.js
  3. 8
      src/components/Form/src/Form.vue
  4. 6
      src/components/Form/src/components/useRenderCheckbox.tsx
  5. 6
      src/components/Form/src/components/useRenderRadio.tsx
  6. 10
      src/components/Form/src/components/useRenderSelect.tsx
  7. 73
      src/components/SSTable/index.vue
  8. 87
      src/components/Search/src/Search.vue
  9. 6
      src/hooks/web/useCrudSchemas.ts
  10. 11
      src/utils/index.ts
  11. 27
      src/views/Clue/Pool/Comp/DrawerClue.vue
  12. 93
      src/views/Clue/Pool/index.vue

@ -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 })
}

@ -0,0 +1,36 @@
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 getClueCount = async () => {
return await request.get({ url: '/admin-api/crm/sch-clue/get-clue-num' })
}

@ -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

@ -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']}>

@ -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']}>

@ -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

@ -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 })

@ -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>

@ -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
}
}
}

@ -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
}

@ -15,8 +15,8 @@
<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>
@ -177,6 +177,7 @@
</template>
<script setup>
import * as ClueApi from '@/api/clue'
import DialogFollow from './DialogFollow.vue'
import ImgFlag from '@/assets/imgs/flag/position_blue.png'
import AMapLoader from '@amap/amap-jsapi-loader'
@ -214,9 +215,9 @@ const schema = ref([
}
])
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 followContent = ``
const followContent2 = `<p style="color: red;">这是本次跟进的内容。</p>`
const followContent2 = ``
const followList = ref([
{
@ -237,13 +238,17 @@ const followList = ref([
const dialogMap = ref(null)
const aMap = ref(null)
function open(row) {
info.value = row
show.value = true
if (!dialogMap.value) {
nextTick(() => {
initMap()
})
async function open(id) {
try {
info.value = await ClueApi.getClue(id)
show.value = true
if (!dialogMap.value) {
nextTick(() => {
initMap()
})
}
} catch (error) {
console.log(error)
}
}

@ -1,30 +1,33 @@
<template>
<div>
<div class="relative">
<el-tabs v-model="searchForm.mode" size="small">
<el-tabs v-model="queryType" size="small">
<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>
@ -116,27 +123,44 @@
</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'
const searchForm = ref({
mode: '2'
})
import { removeNullField } from '@/utils'
import { formatDate } from '@/utils/formatTime'
import * as ClueApi from '@/api/clue'
const searchRef = ref()
const queryType = ref('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 +175,39 @@ 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
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
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
})
}
//
@ -172,7 +220,7 @@ function handleEdit(row) {
}
//
function handleDetail(row) {
drawerRef.value.open(row)
drawerRef.value.open(row.clueId)
}
function handleFollow(row) {
@ -187,6 +235,11 @@ async function makeCall(phone) {
function handleSuccess(row) {
successRef.value.open(row)
}
onMounted(() => {
getSearchCount()
getCurdSchemas()
})
</script>
<style lang="scss" scoped></style>

Loading…
Cancel
Save