Compare commits

6 Commits

Author SHA1 Message Date
028883975d Merge branch 'main' of http://114.55.169.15:3000/qiushanhe/ss-crm-manage-web into dev-cl
# Conflicts:
#	vite.config.js
2024-05-19 23:20:51 +08:00
qsh
0dcc807b34 分类/品牌 2024-05-16 17:57:21 +08:00
qsh
1ec084debe 配置 2024-05-16 16:33:20 +08:00
qsh
2468a0c8a5 驾校管理 2024-05-16 11:50:50 +08:00
qsh
555cb4d27a 驾校关联 2024-05-14 11:45:46 +08:00
qsh
8a5ae3948a 线索 2024-05-10 16:21:07 +08:00
48 changed files with 3572 additions and 8833 deletions

View File

@@ -8,7 +8,7 @@
"source.fixAll.eslint": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "Vue.volar"
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"

13
src/api/call/index.js Normal file
View File

@@ -0,0 +1,13 @@
import request from '@/config/axios'
export default {
callLogin(data) {
return request.post({ url: '/call-api/openapi/V2.0.4/agentLogin', data })
},
callUserStatus(data) {
return request.post({ url: '/call-api/openapi/V2.0.4/getAgentStatus', data })
},
callNumber(data) {
return request.post({ url: '/call-api/openapi/V2.0.4/callNumber', data })
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

View File

@@ -18,7 +18,7 @@ i18nChangeLanguage(unref(currentLocale).lang)
const props = defineProps({
editorId: propTypes.string.def('wangeEditor-1'),
height: propTypes.oneOfType([Number, String]).def('500px'),
height: propTypes.oneOfType([Number, String]).def('40vh'),
editorConfig: {
type: Object as PropType<Partial<IEditorConfig>>,
default: () => undefined
@@ -154,6 +154,23 @@ const editorStyle = computed(() => {
}
})
const toolbarConfig = ref({
excludeKeys: [
'insertVideo', // 网络视频
'insertImage', // 网络图片
'insertLink', // 链接
'codeBlock', // 代码块
'headerSelect', // 标题
'blockquote', // 引用
'fontFamily', // 字体
'todo', // 代办
'group-indent', // 缩进
'emotion', // 表情
'undo', // 撤销
'redo' // 重做
]
})
// 回调函数
const handleChange = (editor: IDomEditor) => {
emit('change', editor)
@@ -184,6 +201,7 @@ defineExpose({
<Toolbar
:editor="editorRef"
:editorId="editorId"
:defaultConfig="toolbarConfig"
class="border-bottom-1 border-solid border-[var(--tags-view-border-color)]"
/>
<!-- 编辑器 -->

View File

@@ -0,0 +1,68 @@
**「工具栏key」**
[
"headerSelect",// 标题
"blockquote", // 引用
"bold", // 加粗
"underline", // 下划线
"italic", // 斜体
// 删除线、清除格式等
"group-more-style",
{
key: "group-more-style",
title: "更多",
iconSvg:
'<svg viewBox="0 0 1024 1024"><path d="M204.8 505.6…0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path></svg>',
menuKeys: Array(5)
},
"color", // 文字颜色
"bgColor", // 背景色
"fontSize", // 字号
"fontFamily", // 字体
"lineHeight", // 行高
"bulletedList", // 无序列表
"numberedList", // 有序列表
"todo", // 代办
// 对齐
"group-justify",
{
key: "group-justify",
title: "对齐",
iconSvg:
'<svg viewBox="0 0 1024 1024"><path d="M768 793.6v1…72.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>',
menuKeys: Array(4)
},
// 缩进
"group-indent",
{
key: "group-indent",
title: "缩进",
iconSvg:
'<svg viewBox="0 0 1024 1024"><path d="M0 64h1024v1…32h1024v128H0z m0-128V320l256 192z"></path></svg>',
menuKeys: Array(2)
},
"emotion",// 表情
"insertLink",// 插入链接
"group-image",// 上传图片
{
key: "group-image",
title: "图片",
iconSvg:
'<svg viewBox="0 0 1024 1024"><path d="M959.877 128…l224.01-384 256 320h64l224.01-192z"></path></svg>',
menuKeys: Array(2)
},
"group-video",// 上传视频
{
key: "group-video",
title: "视频",
iconSvg:
'<svg viewBox="0 0 1024 1024"><path d="M981.184 160….904zM384 704V320l320 192-320 192z"></path></svg>',
menuKeys: Array(2)
},
"insertTable",// 插入表格
"codeBlock", // 代码块
"divider", // 分割线
"undo", // 撤销
"redo", // 重做
"fullScreen" // 全屏
]

View File

@@ -19,10 +19,20 @@
trigger="click"
virtual-triggering
>
<el-checkbox-group v-model="checkedColumns">
<el-checkbox v-for="item in tableColumns" :key="item.field" :label="item.field">
{{ item.label }}
</el-checkbox>
<el-checkbox-group v-model="checkedColumns" @change="confirm">
<draggable
v-model="allColumns"
item-key="field"
ghost-class="draggable-ghost"
:animation="400"
@end="onDragEnd"
>
<template #item="{ element: item }">
<el-checkbox :key="item.field" :label="item.field">
{{ item.label }}
</el-checkbox>
</template>
</draggable>
</el-checkbox-group>
</el-popover>
</div>
@@ -37,12 +47,19 @@
</template>
<script setup>
import draggable from 'vuedraggable'
import { useUserStore } from '@/store/modules/user'
import { useRoute } from 'vue-router'
import cache from '@/plugins/cache'
const props = defineProps({
tableObject: { type: Object, default: () => ({ tableList: [] }) },
tableColumns: { type: Array, default: () => [] }
})
const emit = defineEmits(['update:tableObject', 'getList'])
const emit = defineEmits(['update:tableObject', 'getList', 'getCheckedColumns'])
const route = useRoute()
const { id: userId } = useUserStore().user //取用户ID
const currentPage = ref(props.tableObject?.currentPage || 1)
@@ -50,9 +67,12 @@ const pageSize = ref(props.tableObject?.pageSize || 20)
const ColumnSetting = ref()
const TableColumnPop = ref()
// 展示在设置里的所有表格列,由于会排序,所以使用新属性,不直接修改原值
const allColumns = ref({})
// 已勾选的选项
const checkedColumns = ref([])
// 调用获取数据的接口,分页时需要使用
function getList({ page, limit }) {
emit('update:tableObject', { ...props.tableObject, currentPage: page, pageSize: limit })
nextTick(() => {
@@ -60,9 +80,75 @@ function getList({ page, limit }) {
})
}
// 点击"表格列控制"按钮,出现设置页面
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)
}
// 获取用户已勾选的表头
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. 回显到表格中
emitColumns()
}
// 表格列排序
function onDragEnd() {
const obj = getColumn('TableColumnAll')
obj[route.name] = allColumns.value
// 1. 设置缓存
setColumn(obj, 'TableColumnAll')
// 2. 表格回显
emitColumns()
}
// 勾选确认
function confirm() {
const obj = getColumn()
obj[route.name] = checkedColumns.value
setColumn(obj, 'shitTable')
emitColumns()
}
// 将表头数据返回至父组件
function emitColumns() {
const arr = allColumns.value.filter((item) => checkedColumns.value.includes(item.field))
emit('getCheckedColumns', arr)
}
getAllColumns()
getUserCheckedColumns()
defineExpose({ getUserCheckedColumns })
</script>
<style lang="scss" scoped></style>

View File

@@ -7,6 +7,13 @@ 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'
const route = useRoute()
const { id: userId } = useUserStore().user //取用户ID
const { t } = useI18n()
const props = defineProps({
@@ -43,8 +50,16 @@ const emit = defineEmits(['search', 'reset'])
const visible = ref(true)
const SchemaSetting = ref()
const SettingPop = ref()
const checkedSchema = ref([])
// 用户使用的查询条件
const usedSchema = ref([])
const newSchema = computed(() => {
let schema: FormSchema[] = cloneDeep(props.schema)
let schema: FormSchema[] = cloneDeep(usedSchema.value)
if (props.expand && props.expandField && !unref(visible)) {
const index = findIndex(schema, (v: FormSchema) => v.field === props.expandField)
if (index > -1) {
@@ -65,6 +80,43 @@ const newSchema = computed(() => {
return schema
})
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)
}
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)
}
function setSearch() {
unref(SettingPop).SettingPop?.delayHide?.()
}
const { register, elFormRef, methods } = useForm({
model: props.model || {}
})
@@ -96,6 +148,8 @@ const setVisible = () => {
unref(elFormRef)?.resetFields()
visible.value = !unref(visible)
}
initSearch()
</script>
<template>
@@ -114,21 +168,40 @@ const setVisible = () => {
>
<template #action>
<div v-if="layout === 'inline'">
<ElButton ref="SchemaSetting" @click="setSearch">
<!-- <Icon class="mr-5px" icon="ep:setting" /> -->
查询设置
</ElButton>
<el-popover
ref="SettingPop"
:virtual-ref="SchemaSetting"
placement="bottom"
width="120px"
trigger="click"
virtual-triggering
>
<el-checkbox-group v-model="checkedSchema" @change="changeSearch">
<el-checkbox v-for="item in schema" :key="item.field" :label="item.field">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</el-popover>
<!-- update by 芋艿去除搜索的 type="primary"颜色变淡一点 -->
<ElButton v-if="showSearch" @click="search">
<Icon class="mr-5px" icon="ep:search" />
<!-- <Icon class="mr-5px" icon="ep:search" /> -->
{{ t('common.query') }}
</ElButton>
<!-- update by 芋艿 icon="ep:refresh-right" 修改成 icon="ep:refresh" ruoyi-vue 搜索保持一致 -->
<ElButton v-if="showReset" @click="reset">
<Icon class="mr-5px" icon="ep:refresh" />
<!-- <Icon class="mr-5px" icon="ep:refresh" /> -->
{{ t('common.reset') }}
</ElButton>
<!-- add by 芋艿补充在搜索后的按钮 -->
<slot name="actionMore"></slot>
<ElButton v-if="expand" text @click="setVisible">
{{ t(visible ? 'common.shrink' : 'common.expand') }}
<Icon :icon="visible ? 'ep:arrow-up' : 'ep:arrow-down'" />
<!-- <Icon :icon="visible ? 'ep:arrow-up' : 'ep:arrow-down'" /> -->
</ElButton>
</div>
</template>
@@ -140,11 +213,11 @@ const setVisible = () => {
<template v-if="layout === 'bottom'">
<div :style="bottonButtonStyle">
<ElButton v-if="showSearch" type="primary" @click="search">
<Icon class="mr-5px" icon="ep:search" />
<!-- <Icon class="mr-5px" icon="ep:search" /> -->
{{ t('common.query') }}
</ElButton>
<ElButton v-if="showReset" @click="reset">
<Icon class="mr-5px" icon="ep:refresh-right" />
<!-- <Icon class="mr-5px" icon="ep:refresh-right" /> -->
{{ t('common.reset') }}
</ElButton>
<ElButton v-if="expand" text @click="setVisible">

View File

@@ -52,7 +52,13 @@ const toDocument = () => {
<ElDropdown :class="prefixCls" trigger="click">
<div class="flex items-center">
<img :src="avatar" alt="" class="w-[calc(var(--logo-height)-25px)] rounded-[50%]" />
<span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">
<span
v-if="userName"
class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]"
>
莳松科技管理员
</span>
<span v-else class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">
{{ userName }}
</span>
</div>

View File

@@ -1,5 +1,5 @@
import router from '@/router'
import { name as appName } from '../../../../package.json'
import { name as appName } from '../../../package.json'
let name = `${appName}-${import.meta.env.VITE_APP_ENV}`

View File

@@ -295,6 +295,17 @@ const remainingRouter: AppRouteRecordRaw[] = [
noCache: false,
affix: true
}
},
{
path: 'generalSet',
component: () => import('@/views/Basic/GeneralSet/index.vue'),
name: 'GeneralSet',
meta: {
title: '通用配置',
icon: 'ep:home-filled',
noCache: false,
affix: true
}
}
]
},

View File

@@ -17,7 +17,10 @@
}
.el-dialog__body {
overflow-y: auto;
padding-top: 0;
padding-bottom: 0;
// max-height: calc(100% - 118px);
}
/* nprogress 适配 element-plus 的主题色 */
@@ -48,3 +51,20 @@
.crud-form-item .el-input__wrapper {
width: 100%;
}
.el-dialog {
margin-top: 5vh;
// max-height: 90vh;
}
.el-form--inline .el-form-item {
margin-right: 15px;
}
.el-row .el-form-item {
.el-input,
.el-select,
.el-input-number {
width: 100%;
}
}

View File

@@ -0,0 +1,45 @@
<template>
<el-form :model="form" ref="formRef" label-width="auto">
<el-form-item>
<template #label>
<Tooltip message="请联系客服开通或关闭,客服电话:15955599959" />使用外呼
</template>
<el-radio-group v-model="form.callEnable" disabled>
<el-radio :label="1"> 使用 </el-radio>
<el-radio :label="0"> 不使用 </el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="跟进信息">
<el-radio-group v-model="form.followType">
<el-radio :label="1"> 展示所有 </el-radio>
<el-radio :label="2"> 仅展示本人 </el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button @click="getData">刷新</el-button>
</el-form-item>
</el-form>
</template>
<script setup name="BasicSettingClue">
const message = useMessage()
const form = ref({
followType: 1,
callEnable: 1
})
function getData() {
form.value = {
followType: 1,
callEnable: 1
}
}
function onSubmit() {
message.success('保存成功')
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,69 @@
<template>
<el-form :model="form" ref="formRef" label-width="auto">
<el-form-item label="是否关联成交率">
<el-radio-group v-model="form.withSuccess">
<el-radio :label="1"> </el-radio>
<el-radio :label="0"> </el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="关联规则" v-if="form.withSuccess">
<div>
<el-button @click="form.rules.push({})">新增规则</el-button>
<div
v-for="(item, index) in form.rules"
:key="index"
class="mt-10px flex justify-center items-center"
>
<span>成交率达</span>
<el-input
class="ml-10px"
v-model="item.successRate"
placeholder="成交率"
style="width: 100px"
type="number"
:min="0"
>
<template #suffix>%</template>
</el-input>
<span>可结算</span>
<el-input
class="ml-10px"
v-model="item.comissionRate"
placeholder="提成率"
style="width: 100px"
type="number"
:min="0"
>
<template #suffix>%</template>
</el-input>
<span>提成</span>
</div>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button @click="getData">刷新</el-button>
</el-form-item>
</el-form>
</template>
<script setup name="BasicSettingClue">
const message = useMessage()
const form = ref({
withSuccess: 1,
rules: []
})
function getData() {
form.value = {
withSuccess: 1
}
}
function onSubmit() {
message.success('保存成功')
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,21 @@
<template>
<div>
<el-tabs v-model="tabIndex" type="border-card">
<el-tab-pane label="线索管理" :name="0">
<BSClue v-if="tabIndex == 0" />
</el-tab-pane>
<el-tab-pane label="销售提成" :name="10">
<BSSalerComission v-if="tabIndex == 10" />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup name="GeneralSetting">
import BSClue from './Comp/BSClue.vue'
import BSSalerComission from './Comp/BSSalerComission.vue'
const tabIndex = ref(0)
</script>
<style lang="scss" scoped></style>

View File

@@ -28,6 +28,12 @@
</el-collapse>
</div>
</div>
<template #footer>
<span>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleSave"> </el-button>
</span>
</template>
</el-dialog>
</template>
@@ -256,6 +262,11 @@ const activeQues = ref('')
function filterList() {
showList.value = resultList.filter((it) => it.question.includes(keyword.value))
}
function handleSave() {
console.log('保存成功')
dialogVisible.value = false
}
</script>
<style lang="scss" scoped></style>

View File

@@ -8,6 +8,7 @@
:destroy-on-close="true"
:show-close="true"
:wrapperClosable="true"
@close="destroyMap"
>
<!-- header -->
<el-skeleton :loading="loading" animated>
@@ -51,7 +52,7 @@
<el-button type="danger" plain>删除</el-button>
</div>
</div>
<div v-dompurify-html="followContent"></div>
<div>{{ followContent }}</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" />
@@ -78,7 +79,7 @@
<el-button type="danger" plain>删除</el-button>
</div>
</div>
<div key="followContent" v-dompurify-html="followContent2"></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" />
@@ -95,22 +96,11 @@
</el-timeline>
</el-tab-pane>
<el-tab-pane label="详细信息" name="infoDetail">
<el-descriptions :column="2" border>
<el-descriptions-item min-width="150px" label="线索名称">{{
info.name
}}</el-descriptions-item>
<el-descriptions-item min-width="150px" label="联系方式"
>18888888888</el-descriptions-item
>
<el-descriptions-item min-width="150px" label="线索来源">驾考宝典</el-descriptions-item>
<el-descriptions-item min-width="150px" label="意向状态">高意向</el-descriptions-item>
<el-descriptions-item min-width="150px" :span="2" label="诉求"
>这是诉求内容这是诉求内容这是诉求内容这是诉求内容这是诉求内容这是诉求内容这是诉求内容</el-descriptions-item
>
<el-descriptions-item min-width="150px" :span="2" label="备注"
>这是备注内容</el-descriptions-item
>
</el-descriptions>
<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>
</el-tab-pane>
<el-tab-pane label="操作记录" name="operateRecord">
<el-timeline>
@@ -188,10 +178,42 @@
<script setup>
import DialogFollow from './DialogFollow.vue'
import ImgFlag from '@/assets/imgs/flag/position_blue.png'
import AMapLoader from '@amap/amap-jsapi-loader'
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 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>`
@@ -211,9 +233,65 @@ 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()
})
}
}
function initMap() {
AMapLoader.load({
key: '2ffb0e2ea90b1df0b8be48ed66e18fc8', //设置您的key
version: '2.0'
}).then((AMap) => {
aMap.value = AMap
dialogMap.value = new AMap.Map('dialogMap', {
zoom: 12,
zooms: [2, 22],
center: [117.283042, 31.86119]
})
})
}
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]
} else {
dialogMap.value.remove(schoolMarkers.value)
}
}
const infoIndex = ref('followRecord')
@@ -229,6 +307,11 @@ function addFollow() {
function updateFollow() {
followRef.value.open('update', { nextFollowTime: '2024-04-01 12:12' })
}
function destroyMap() {
dialogMap.value = null
aMap.value = null
}
</script>
<style lang="scss" scoped></style>

View File

@@ -18,7 +18,7 @@ const crudSchemas = reactive([
type: 'datetime',
format: 'YYYY-MM-DD HH:mm',
valueFormat: 'YYYY-MM-DD HH:mm',
placeholder: '创建时间'
placeholder: '本次跟进时间'
}
}
},
@@ -45,7 +45,11 @@ const crudSchemas = reactive([
field: 'remark',
isTable: true,
form: {
component: 'Editor',
component: 'Input',
componentProps: {
type: 'textarea',
autosize: { minRows: 5, maxRows: 10 }
},
colProps: {
span: 24
}

View File

@@ -28,6 +28,13 @@ const crudSchemas = reactive([
isSearch: true,
isTable: true
},
{
label: '线索位置',
field: 'address',
isSearch: true,
isTable: true,
isForm: false
},
{
label: '线索来源',
field: 'resource',
@@ -141,17 +148,6 @@ const crudSchemas = reactive([
isTable: true,
isForm: false
},
{
label: '备注',
field: 'remark',
isTable: true,
form: {
component: 'Editor',
colProps: {
span: 24
}
}
},
{
label: '创建时间',
field: 'createTime',
@@ -183,6 +179,22 @@ const crudSchemas = reactive([
placeholder: '创建时间'
}
}
},
{
label: '跟进记录',
field: 'followRecord',
isTable: true
},
{
label: '备注',
field: 'remark',
isTable: true,
form: {
component: 'Editor',
colProps: {
span: 24
}
}
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@@ -31,7 +31,7 @@
</el-tabs>
<div class="absolute" style="right: 10px; top: 0">
<el-button plain>导入</el-button>
<el-button type="primary" @click="handleInsert">新增</el-button>
<el-button type="primary" @click="handleInsert">新增线索</el-button>
</div>
</div>
<!-- 搜索工作栏 -->
@@ -47,14 +47,28 @@
v-model:tableObject="tableObject"
:tableColumns="allSchemas.tableColumns"
@get-list="getTableList"
@get-checked-columns="getCheckedColumns"
>
<el-table-column
v-for="item in allSchemas.tableColumns"
v-for="item in showColumns"
:key="item.field"
:prop="item.field"
:label="item.label"
min-width="120px"
/>
>
<template #default="{ row }">
<div v-if="item.field == 'followRecord'">
<el-button type="primary" text style="padding: 0" @click="handleFollow(row)"
>快速新增</el-button
>
</div>
<div v-else-if="item.field == 'contact'">
<span>{{ row[item.field] }}</span>
<Icon class="ml-5px" icon="ep:phone" @click="makeCall(row.contact)" />
</div>
<span v-else>{{ row[item.field] }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="200px" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="handleDetail(scope.row)">详情</el-button>
@@ -68,6 +82,7 @@
<DialogClue ref="formRef" />
<DrawerClue ref="drawerRef" />
<DialogSuccess ref="successRef" />
<DialogFollow ref="followRef" />
</div>
</template>
@@ -76,6 +91,7 @@ import { allSchemas } from './cluePool.data'
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'
@@ -83,6 +99,7 @@ const searchForm = ref({
const formRef = ref()
const drawerRef = ref()
const successRef = ref()
const followRef = ref()
// const { tableObject, tableMethods } = useTable({
// getListApi: MailTemplateApi.getMailTemplatePage, // 分页接口
@@ -90,12 +107,20 @@ const successRef = ref()
// })
const tableObject = ref({
tableList: [{ name: '测试', contact: '18888888888' }],
tableList: [{ name: '测试', contact: '17318531354' }],
loading: false,
total: 1,
pageSize: 20,
currentPage: 1
})
const showColumns = ref([])
// 初始化表格
function getCheckedColumns(list) {
showColumns.value = list
}
const setSearchParams = function () {
// 方法体
}
@@ -117,6 +142,14 @@ function handleDetail(row) {
drawerRef.value.open(row)
}
function handleFollow(row) {
followRef.value.open('create', row)
}
async function makeCall(phone) {
console.log('打电话:' + phone)
}
// 登记
function handleSuccess(row) {
successRef.value.open(row)

View File

@@ -1,15 +1,37 @@
<template>
<div>
<el-table :data="list" border stripe>
<el-table :data="list" border>
<el-table-column type="index" width="50" />
<el-table-column label="来源名称">
<el-table-column label="来源名称" width="200px">
<template #default="{ row }">
<el-input v-model="row.name" placeholder="请输入" :clearable="false" />
</template>
</el-table-column>
<el-table-column label="获取连接">
<el-table-column label="渠道" width="300px">
<template #default="{ row }">
<el-input v-model="row.link" placeholder="请输入" :clearable="false" />
<el-select
v-if="row.edit"
v-model="row.resource"
placeholder="选择渠道"
clearable
filterable
@change="handleChange(row)"
>
<el-option
v-for="item in resourceOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<span>{{ row.resourceName }}</span>
</template>
</el-table-column>
<el-table-column label="获取连接" prop="link" />
<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" />
</template>
</el-table-column>
<el-table-column label="操作" width="100px">
@@ -23,21 +45,46 @@
<div class="mt-20px flex justify-center">
<el-button type="primary" @click="handleInsert">新增规则</el-button>
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button>重置</el-button>
</div>
</div>
</template>
<script setup>
const list = ref([
{ name: '一点通账号1', link: 'http://baidu.com' },
{ name: '一点通账号2', link: 'http://baidu.com' },
{ name: '宝典账号', link: 'http://baidu.com' },
{ name: '抖音', link: 'http://baidu.com' }
{
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: '参数详情'
}
])
const resourceOptions = ref([
{ value: 1, label: '驾考宝典' },
{ value: 2, label: '一点通' },
{ value: 3, label: '抖音' }
])
function handleInsert() {
list.value.push({ name: '', link: '' })
list.value.push({ name: '', link: '', edit: true, inEnable: true })
}
function onSubmit() {
@@ -47,6 +94,11 @@ function onSubmit() {
function handleRemove(idx) {
list.value.splice(idx, 1)
}
function handleChange(row) {
row.link = `https://sscrm.ahduima.com/clue/get?cid=1001&aid=1001&res=${row.resource}`
row.params = '参数详情'
}
</script>
<style lang="scss" scoped></style>

View File

@@ -1,82 +1,81 @@
<template>
<el-form :model="form" ref="sendForm" :rules="rules" label-width="100px" :inline="false">
<el-form-item label="是否自动分配">
<el-radio-group v-model="form.isAuto">
<el-radio :label="1"> 自动分配 </el-radio>
<el-radio :label="0"> 手动分配 </el-radio>
</el-radio-group>
</el-form-item>
<div v-if="form.isAuto">
<el-form-item label="分配对象">
<div>
<el-checkbox
v-model="checkUserAll"
:indeterminate="userIndeterminate"
@change="userCheckAllChange"
>
全选
</el-checkbox>
<el-checkbox-group v-model="form.users" @change="userCheckedChange">
<el-checkbox
v-for="(item, index) in userOptions"
:key="index"
:label="item.value"
:value="item.value"
>
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</el-form-item>
<el-form-item label="线索来源">
<div>
<el-checkbox
v-model="checkResourceAll"
:indeterminate="resourceIndeterminate"
@change="resourceCheckAllChange"
>
全选
</el-checkbox>
<el-checkbox-group v-model="form.resource" @change="resourceCheckedChange">
<el-checkbox
v-for="(item, index) in resourceOptions"
:key="index"
:label="item.value"
:value="item.value"
>
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</el-form-item>
<el-form-item label="权重配置">
<div>
<div v-for="(item, index) in intentionOptions" :key="index" class="flex mb-10px">
<div class="mr-15px" style="width: 100px">{{ item.label }}</div>
<el-input v-model="item.value" type="number" placeholder="请输入权重">
<template #suffix> % </template>
</el-input>
</div>
</div>
</el-form-item>
<el-form-item label="分配时间">
<el-time-picker
v-model="form.sendTime"
placeholder="任意时间点"
format="HH:mm"
value-format="HH:mm"
:clearable="false"
/>
</el-form-item>
<div class="flex">
<div class="mr-20px" style="width: 500px">
<el-input
v-model="searchForm.keyword"
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>
<!-- 分页 -->
<Pagination
v-model:limit="searchForm.pageSize"
v-model:page="searchForm.pageNum"
:total="total"
@pagination="getUserList"
/>
</div>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button>重置</el-button>
</el-form-item>
</el-form>
<el-form :model="form" ref="sendForm" :rules="rules" label-width="100px" :inline="false">
<el-form-item label="是否自动分配">
<el-radio-group v-model="form.isAuto">
<el-radio :label="1"> 自动分配 </el-radio>
<el-radio :label="0"> 手动分配 </el-radio>
</el-radio-group>
</el-form-item>
<div v-if="form.isAuto">
<el-form-item label="线索来源">
<div>
<el-checkbox
v-model="checkResourceAll"
:indeterminate="resourceIndeterminate"
@change="resourceCheckAllChange"
>
全选
</el-checkbox>
<el-checkbox-group v-model="form.resource" @change="resourceCheckedChange">
<el-checkbox
v-for="(item, index) in resourceOptions"
:key="index"
:label="item.value"
:value="item.value"
>
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</div>
</el-form-item>
<el-form-item label="权重配置">
<el-input v-model="form.value" type="number" placeholder="请输入权重">
<template #suffix> % </template>
</el-input>
</el-form-item>
</div>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button>重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
const searchForm = ref({
keyword: '',
pageSize: 20,
pageNum: 1
})
const total = ref(0)
const userList = ref([{ name: '张三', phone: '1888888888', workNum: '202101030001' }])
const form = ref({
isAuto: 1,
users: [1, 2],
@@ -85,23 +84,8 @@ const form = ref({
})
const rules = ref({})
const checkUserAll = ref(false)
const userIndeterminate = ref(true)
const userOptions = ref([
{ label: '张三', value: 1 },
{ label: '李四', value: 2 },
{ label: '王二', value: 3 }
])
function userCheckAllChange(val) {
form.value.users = val ? userOptions.value.map((it) => it.value) : []
userIndeterminate.value = false
}
function userCheckedChange(val) {
const checkedCount = val.length
checkUserAll.value = checkedCount == userOptions.value.length
userIndeterminate.value = checkedCount > 0 && checkedCount < userOptions.value.length
function getUserList() {
console.log('获取列表')
}
function onSubmit() {
@@ -127,12 +111,9 @@ function resourceCheckedChange(val) {
resourceIndeterminate.value = checkedCount > 0 && checkedCount < resourceOptions.value.length
}
const intentionOptions = ref([
{ label: '高意向', value: 20 },
{ label: '中意向', value: 20 },
{ label: '低意向', value: 20 },
{ label: '未知意向', value: 40 }
])
function selectUser(row) {
console.log(row)
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,105 @@
<template>
<div>
<el-form ref="queryForm" :model="searchForm" label-width="0" inline>
<el-form-item>
<el-input
v-model="searchForm.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">搜索</el-button>
<el-button @click="resetQuery">重置</el-button>
<el-button type="primary" @click="openForm('create', null)">新增</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="tableList"
row-key="sourceId"
:tree-props="{ children: 'children' }"
>
<el-table-column prop="sourceName" label="来源名称" />
<el-table-column prop="orderNum" 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="操作">
<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>
</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 DialogSource from './DialogSource.vue'
const searchForm = ref({
pageNum: 1,
pageSize: 20
})
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
}
getList()
}
function getList() {
tableList.value = [
{
sourceId: 1,
sourceName: '测试',
level: 1,
children: [{ sourceId: 1001, sourceName: '二级来源', level: 2, parentSource: '测试' }]
}
]
}
function openForm(type, info) {
sourceDialog.value.open(type, info)
}
async function handleDelete(row) {
try {
console.log(row)
// 删除的二次确认
await message.delConfirm()
// 发起删除
// await UserApi.deleteUser(row.id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,111 @@
<template>
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="80px"
>
<el-form-item v-if="formData.level > 1" 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>
<el-form-item label="备注" prop="remark">
<el-input
type="textarea"
v-model="formData.remark"
placeholder="请输入备注"
:autosize="{ minRows: 4, maxRows: 8 }"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</el-dialog>
</template>
<script name="DialogSource" setup>
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 formData = ref({
sourceName: '',
orderNum: 1,
remark: ''
})
const formRules = reactive({
sourceName: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type, info) => {
dialogVisible.value = true
dialogTitle.value = type == 'update' ? '修改来源' : '新增来源'
formType.value = type
resetForm()
// 修改时,设置数据
if (info.sourceId) {
formLoading.value = true
try {
if (type == 'update') {
formData.value = { ...info }
} else {
formData.value.level = info.level + 1
formData.value.parentSource = info.sourceName
}
// formData.value = await UserApi.getUser(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
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)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
sourceName: '',
orderNum: 1,
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@@ -4,16 +4,19 @@
<el-tab-pane label="线索属性" :name="0">
<FieldClue />
</el-tab-pane>
<el-tab-pane label="成交属性" :name="1">
<el-tab-pane label="成交属性" :name="10">
<FieldOrder />
</el-tab-pane>
<el-tab-pane label="线索获取规则" :name="2">
<el-tab-pane label="线索来源" :name="15">
<ClueSource />
</el-tab-pane>
<el-tab-pane label="线索获取规则" :name="20">
<ClueGet />
</el-tab-pane>
<el-tab-pane label="线索分配规则" :name="3">
<el-tab-pane label="线索分配规则" :name="30">
<ClueSend />
</el-tab-pane>
<el-tab-pane label="消息通知" :name="4">
<el-tab-pane label="消息通知" :name="40">
<MsgSend />
</el-tab-pane>
</el-tabs>
@@ -23,6 +26,7 @@
<script setup name="ClueSetting">
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'

View File

@@ -0,0 +1,98 @@
<template>
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="80px"
>
<el-form-item label="问题" prop="question">
<el-input v-model="formData.question" placeholder="请输入问题" />
</el-form-item>
<el-form-item label="关键词" prop="keyword">
<el-input v-model="formData.keyword" placeholder="请输入关键词" />
</el-form-item>
<el-form-item label="答案" prop="answer">
<Editor v-model:modelValue="formData.answer" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</el-dialog>
</template>
<script name="DialogSkill" setup>
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 formData = ref({
question: '',
keyword: [],
answer: ''
})
const formRules = reactive({
question: [{ required: true, message: '问题不能为空', trigger: 'blur' }],
answer: [{ required: true, message: '答案不能为空', trigger: 'blur,change' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type, id) => {
dialogVisible.value = true
dialogTitle.value = type == 'create' ? '新增话术' : '修改话术'
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
// formData.value = await UserApi.getUser(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
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)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
question: '',
keyword: [],
answer: ''
}
formRef.value?.resetFields()
}
</script>

View File

@@ -1,7 +1,102 @@
<template>
<div> 关键话术 </div>
<div>
<el-form :model="searchForm" inline label-width="0">
<el-form-item>
<el-input
v-model="searchForm.question"
placeholder="请输入问题"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</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-form-item>
</el-form>
<el-table :data="tableList">
<el-table-column type="index" width="55" align="center" />
<el-table-column label="问题" align="center" prop="question" />
<el-table-column label="答案" align="center" prop="content">
<template #default="scope">
<p v-dompurify-html="scope.row.content"></p>
</template>
</el-table-column>
<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>
</template>
</el-table-column>
</el-table>
<Pagination
v-model:limit="searchForm.pageSize"
v-model:page="searchForm.pageNum"
:total="total"
@pagination="handleQuery"
/>
<DialogSkill ref="skillDialog" @success="handleQuery" />
</div>
</template>
<script setup></script>
<script setup name="ClueSkill">
import DialogSkill from './Comp/DialogSkill.vue'
const skillDialog = ref()
const message = useMessage() // 消息弹窗
const searchForm = ref({
question: '',
pageSize: 20,
pageNum: 1
})
const total = ref(0)
const tableList = ref([])
function resetQuery() {
searchForm.value = {
question: '',
pageSize: 20,
pageNum: 1
}
getList()
}
function handleQuery() {
searchForm.value.pageNum = 1
getList()
}
function getList() {
tableList.value = [{ question: '测试' }]
}
function handleAdd() {
skillDialog.value.open('create', null)
}
function handleUpdate(row) {
skillDialog.value.open('update', row)
}
async function handleDelete(row) {
try {
console.log(row)
// 删除的二次确认
await message.delConfirm()
// 发起删除
// await UserApi.deleteUser(row.id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -68,10 +68,10 @@
</el-skeleton>
</el-card>
<el-row class="mt-10px" :gutter="10" justify="space-between">
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24" class="mb-10px">
<!-- <el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24" class="mb-10px">
<el-card shadow="never">
<template #header>
<div class="flex justify-between h-3">
<div class="flex justify-between ">
<span>本月成交来源</span>
</div>
</template>
@@ -79,11 +79,11 @@
<Echart :options="pieOptionsData" :height="280" />
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24" class="mb-10px">
</el-col> -->
<el-col :xl="8" :lg="8" :md="12" :sm="12" :xs="24" class="mb-10px">
<el-card shadow="never">
<template #header>
<div class="flex justify-between h-3">
<div class="flex justify-between">
<span>成交率</span>
</div>
</template>
@@ -94,10 +94,10 @@
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24" class="mb-10px">
<el-col :xl="8" :lg="8" :md="12" :sm="12" :xs="24" class="mb-10px">
<el-card shadow="never">
<template #header>
<div class="flex justify-between h-3">
<div class="flex justify-between items-center">
<span>跟进榜Top10</span>
<el-radio-group v-model="followDate" size="small">
<el-radio label="day">本日</el-radio>
@@ -123,18 +123,16 @@
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24" class="mb-10px">
<el-col :xl="8" :lg="8" :md="12" :sm="12" :xs="24" class="mb-10px">
<el-card shadow="never">
<template #header>
<div class="flex justify-between h-3">
<span>本月成交榜Top10</span>
</div>
<div class="flex justify-between"> 本月成交榜Top10 </div>
</template>
<el-skeleton :loading="loading" animated>
<el-table :data="followList" size="small">
<el-table-column prop="sort" label="排名" width="50" />
<el-table-column prop="name" label="姓名" width="70" />
<el-table-column prop="count" label="跟进数量" width="70" />
<el-table-column prop="count" label="成交数量" width="70" />
<el-table-column prop="orgName" label="所属组织" />
</el-table>
</el-skeleton>
@@ -255,4 +253,8 @@ getAllApi()
.number-font {
font-family: numberFont !important;
}
:deep(.el-card__header) {
padding: 10px;
}
</style>

View File

@@ -0,0 +1,95 @@
<template>
<div>
<el-form ref="queryForm" :model="searchForm" label-width="0" inline>
<el-form-item>
<el-input
v-model="searchForm.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">搜索</el-button>
<el-button @click="resetQuery">重置</el-button>
<el-button type="primary" @click="openForm('create', null)">新增</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="tableList">
<el-table-column prop="brandName" label="品牌名称" />
<el-table-column prop="orderNum" 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="操作">
<template #default="scope">
<el-button type="primary" text @click="openForm('update', scope.row)">修改</el-button>
<el-button type="danger" text @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<Pagination
v-model:limit="searchForm.pageSize"
v-model:page="searchForm.pageNum"
:total="total"
@pagination="handleQuery"
/>
<DialogBrand ref="brandDialog" @success="handleQuery" />
</div>
</template>
<script setup name="BrandSet">
import DialogBrand from './DialogBrand.vue'
const searchForm = ref({
pageNum: 1,
pageSize: 20
})
const total = ref(0)
const brandDialog = ref()
const tableList = ref([])
const loading = ref(false)
function handleQuery() {
searchForm.value.pageNum = 1
getList()
}
function resetQuery() {
searchForm.value = {
name: '',
pageSize: 20,
pageNum: 1
}
getList()
}
function getList() {
tableList.value = [
{
brandId: 1,
brandName: '测试'
}
]
}
function openForm(type, info) {
brandDialog.value.open(type, info)
}
async function handleDelete(row) {
try {
console.log(row)
// 删除的二次确认
await message.delConfirm()
// 发起删除
// await UserApi.deleteUser(row.id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,105 @@
<template>
<div>
<el-form ref="queryForm" :model="searchForm" label-width="0" inline>
<el-form-item>
<el-input
v-model="searchForm.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">搜索</el-button>
<el-button @click="resetQuery">重置</el-button>
<el-button type="primary" @click="openForm('create', null)">新增</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="loading"
:data="tableList"
row-key="categoryId"
:tree-props="{ children: 'children' }"
>
<el-table-column prop="categoryName" label="分类名称" />
<el-table-column prop="orderNum" 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="操作">
<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>
</el-table-column>
</el-table>
<Pagination
v-model:limit="searchForm.pageSize"
v-model:page="searchForm.pageNum"
:total="total"
@pagination="handleQuery"
/>
<DialogCategory ref="categoryDialog" @success="handleQuery" />
</div>
</template>
<script setup name="CategorySet">
import DialogCategory from './DialogCategory.vue'
const searchForm = ref({
pageNum: 1,
pageSize: 20
})
const total = ref(0)
const categoryDialog = ref()
const tableList = ref([])
const loading = ref(false)
function handleQuery() {
searchForm.value.pageNum = 1
getList()
}
function resetQuery() {
searchForm.value = {
name: '',
pageSize: 20,
pageNum: 1
}
getList()
}
function getList() {
tableList.value = [
{
categoryId: 1,
categoryName: '测试',
level: 1,
children: [{ categoryId: 1001, categoryName: '二级分类', level: 2, parentCategory: '测试' }]
}
]
}
function openForm(type, info) {
categoryDialog.value.open(type, info)
}
async function handleDelete(row) {
try {
console.log(row)
// 删除的二次确认
await message.delConfirm()
// 发起删除
// await UserApi.deleteUser(row.id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,103 @@
<template>
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="80px"
>
<el-form-item label="品牌名称" prop="brandName">
<el-input v-model="formData.brandName" placeholder="请输入品牌名称" />
</el-form-item>
<el-form-item label="排序" prop="orderNum">
<el-input v-model="formData.orderNum" placeholder="请输入排序" type="number" :min="0" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
type="textarea"
v-model="formData.remark"
placeholder="请输入备注"
:autosize="{ minRows: 4, maxRows: 8 }"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</el-dialog>
</template>
<script name="DialogBrand" setup>
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 formData = ref({
brandName: '',
orderNum: 1,
remark: ''
})
const formRules = reactive({
brandName: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type, info) => {
dialogVisible.value = true
dialogTitle.value = type == 'update' ? '修改品牌' : '新增品牌'
formType.value = type
resetForm()
// 修改时,设置数据
if (info.brandId) {
formLoading.value = true
try {
formData.value = { ...info }
// formData.value = await UserApi.getUser(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
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)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
brandName: '',
orderNum: 1,
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@@ -0,0 +1,111 @@
<template>
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="80px"
>
<el-form-item v-if="formData.level > 1" label="上级分类">
<el-input v-model="formData.parentCategory" disabled />
</el-form-item>
<el-form-item label="分类名称" prop="categoryName">
<el-input v-model="formData.categoryName" placeholder="请输入分类名称" />
</el-form-item>
<el-form-item label="排序" prop="orderNum">
<el-input v-model="formData.orderNum" placeholder="请输入排序" type="number" :min="0" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
type="textarea"
v-model="formData.remark"
placeholder="请输入备注"
:autosize="{ minRows: 4, maxRows: 8 }"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</el-dialog>
</template>
<script name="DialogCategory" setup>
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 formData = ref({
categoryName: '',
orderNum: 1,
remark: ''
})
const formRules = reactive({
categoryName: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type, info) => {
dialogVisible.value = true
dialogTitle.value = type == 'update' ? '修改分类' : '新增分类'
formType.value = type
resetForm()
// 修改时,设置数据
if (info.categoryId) {
formLoading.value = true
try {
if (type == 'update') {
formData.value = { ...info }
} else {
formData.value.level = info.level + 1
formData.value.parentCategory = info.categoryName
}
// formData.value = await UserApi.getUser(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
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)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
categoryName: '',
orderNum: 1,
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@@ -3,6 +3,12 @@
<el-tab-pane label="产品属性" :name="0">
<FieldProduct />
</el-tab-pane>
<el-tab-pane label="分类设置" :name="1">
<CategorySet />
</el-tab-pane>
<el-tab-pane label="品牌设置" :name="2">
<BrandSet />
</el-tab-pane>
<el-tab-pane label="消息通知" :name="9">
<MsgSend />
</el-tab-pane>
@@ -12,6 +18,8 @@
<script setup>
import FieldProduct from './Comp/FieldProduct.vue'
import MsgSend from './Comp/MsgSend.vue'
import CategorySet from './Comp/CategorySet.vue'
import BrandSet from './Comp/BrandSet.vue'
const tabIndex = ref(0)
</script>

View File

@@ -30,15 +30,15 @@
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
<!-- <Icon class="mr-5px" icon="ep:search" /> -->
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
<!-- <Icon class="mr-5px" icon="ep:refresh" /> -->
重置
</el-button>
<el-button plain type="primary" @click="openForm">
<Icon class="mr-5px" icon="ep:plus" />
<!-- <Icon class="mr-5px" icon="ep:plus" /> -->
新增
</el-button>
</el-form-item>

View File

@@ -0,0 +1,377 @@
<template>
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="1000px">
<el-tabs v-model="currentTab">
<el-tab-pane label="基础信息" name="base">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="80px"
>
<el-row :gutter="20">
<el-col :span="12" :offset="0">
<el-form-item label="驾校">
<el-input v-model="formData.schoolName" disabled />
</el-form-item>
</el-col>
<el-col :span="12" :offset="0">
<el-form-item label="场地">
<el-input v-model="formData.placeName" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12" :offset="0">
<el-form-item label="班型名称" prop="typeName">
<el-input v-model="formData.typeName" placeholder="请输入班型" />
</el-form-item>
</el-col>
<el-col :span="12" :offset="0">
<el-form-item label="驾照类型" prop="licenseType">
<el-select v-model="formData.licenseType" placeholder="选择驾照类型">
<el-option
v-for="item in licenseTypeOption"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12" :offset="0">
<el-form-item label="排序" prop="orderNum">
<el-input-number v-model="formData.orderNum" />
</el-form-item>
</el-col>
<el-col :span="12" :offset="0">
<el-form-item label="状态" prop="currentPrice">
<el-radio-group v-model="formData.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-form-item label="备注" prop="remark">
<Editor v-model:modelValue="formData.remark" />
</el-form-item>
</el-row>
</el-form>
</el-tab-pane>
<el-tab-pane label="报价信息" name="price">
<el-button type="primary" @click="handleAddPrice" class="mb-10px">新增报价</el-button>
<el-table :data="formData.priceList">
<el-table-column width="160px">
<template #header> <Tooltip message="市场价,给客户的报价" />指导价 </template>
<template #default="{ row }">
<el-input v-if="row.edit" v-model="row.markerPrice" type="number" :min="0" />
<span v-else>{{ row.markerPrice }}</span>
</template>
</el-table-column>
<el-table-column width="160px">
<template #header> <Tooltip message="员工底价,用于核算毛利及提成" />底价 </template>
<template #default="{ row }">
<el-input v-if="row.edit" v-model="row.employeePrice" type="number" :min="0" />
<span v-else>{{ row.employeePrice }}</span>
</template>
</el-table-column>
<el-table-column width="160px">
<template #header> <Tooltip message="公司采购价,用于计算公司利润" />成本价 </template>
<template #default="{ row }">
<el-input v-if="row.edit" v-model="row.costPrice" type="number" :min="0" />
<span v-else>{{ row.costPrice }}</span>
</template>
</el-table-column>
<el-table-column label="生效日期">
<template #default="{ row }">
<el-date-picker
v-if="row.edit"
v-model="row.startDate"
type="date"
placeholder="选择日期时间"
/>
<span v-else>{{ row.startDate }}</span>
</template>
</el-table-column>
<el-table-column label="报价日期" width="180px" prop="createTime" />
<el-table-column label="操作" width="80px">
<template #default="{ row, $index }">
<el-button
type="danger"
text
style="padding: 0"
@click="handleDeletePrice(row, $index)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="提成结算" name="comission">
<el-button v-if="!comissionShow" type="primary" @click="handleAddComission">
新增提成结算规则
</el-button>
<el-form
v-if="comissionShow"
:model="comissionForm"
ref="FormComission"
class="mt-10px"
label-width="80px"
inline
>
<el-row :gutter="20">
<el-col :span="12" :offset="0">
<el-form-item label="生效日期" prop="startDate">
<el-date-picker
v-model="comissionForm.startDate"
type="date"
placeholder="选择日期时间"
/>
</el-form-item>
</el-col>
<el-col :span="12" :offset="0">
<el-form-item label="结算方式">
<el-radio-group v-model="comissionForm.type">
<el-radio :label="1">
<Tooltip message="可配置多级,命中某档位后,按照档位分级结算" />阶梯结算
</el-radio>
<el-radio :label="2">
<Tooltip message="可配置多级,命中某档位后所有金额全部按照该档位结算" />常规结算
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-button type="primary" class="mb-10px" plain @click="handleAddRules"
>添加提成档位</el-button
>
<el-row :gutter="20" class="mb-10px">
<el-col
:span="24"
:offset="0"
class="flex"
v-for="(item, index) in comissionForm.rules"
:key="index"
>
<el-select v-model="item.type1" style="width: 100px">
<el-option label="单笔" :value="1" />
<el-option label="月总" :value="2" />
</el-select>
<el-select class="ml-5px" v-model="item.type2" style="width: 100px">
<el-option label="成交额" :value="1" />
<el-option label="毛利" :value="2" />
</el-select>
<span class="ml-5px"></span>
<el-input
class="ml-5px"
v-model="item.type3"
placeholder="金额"
type="number"
:min="0"
style="width: 100px"
/>
<span class="ml-5px"></span>
<el-select class="ml-5px" v-model="item.type4" style="width: 120px">
<el-option label="按比例" :value="1" />
<el-option label="按固定金额" :value="2" />
</el-select>
<div v-if="item.type4 == 2" class="inline-flex items-center">
<el-input
class="ml-5px"
v-model="item.type7"
placeholder="金额"
type="number"
:min="0"
style="width: 100px"
/>
<span class="ml-5px">元结算</span>
</div>
<div v-else class="inline-flex items-center">
<span class="ml-5px"></span>
<el-select class="ml-5px" v-model="item.type5" style="width: 100px">
<el-option label="按成交额" :value="1" />
<el-option label="按底价" :value="2" />
<el-option label="按毛利" :value="3" />
</el-select>
<span class="ml-5px">结算</span>
<el-input
class="ml-5px"
v-model="item.type6"
placeholder="比例"
type="number"
:min="1"
style="width: 100px"
>
<template #suffix> % </template>
</el-input>
</div>
</el-col>
</el-row>
<el-row :gutter="20" class="mb-10px">
<el-col :span="24" :offset="0">
<el-button type="primary" @click="sureAddComission">确认新增</el-button>
<el-button @click="comissionShow = false">取消</el-button>
</el-col>
</el-row>
</el-form>
<el-divider>历史结算规则</el-divider>
<el-table :data="formData.comissionList" border>
<el-table-column prop="startDate" label="生效时间" width="200px" />
<el-table-column prop="typeName" label="结算方式" width="120px" />
<el-table-column prop="ruleDesc" label="结算规则" />
</el-table>
</el-tab-pane>
</el-tabs>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</el-dialog>
</template>
<script name="DialogClass" setup>
import { formatDate } from '@/utils/formatTime'
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 formData = ref({
name: '',
leader: '',
phone: '',
status: 1,
remark: ''
})
const formRules = reactive({
name: [{ required: true, message: '驾校名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const currentTab = ref('base')
const licenseTypeOption = ref([])
/** 打开弹窗 */
const open = async (type, id) => {
dialogVisible.value = true
dialogTitle.value = type == 'create' ? '新增班型' : '修改班型'
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
// formData.value = await UserApi.getUser(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
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)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
name: '',
leader: '',
phone: '',
status: 1,
remark: '',
priceList: [],
comissionList: [
{
startDate: '2024-01-01',
typeName: '阶梯结算',
ruleDesc: '月总成交额满0元按比例取成交额1%'
}
]
}
formRef.value?.resetFields()
}
function handleAddPrice() {
formData.value.priceList.push({
edit: true,
markerPrice: undefined,
employeePrice: undefined,
costPrice: undefined,
startDate: undefined,
createTime: '2024-05-12'
})
}
function handleDeletePrice(row, index) {
if (row.edit) {
formData.value.priceList.splice(index, 1)
}
message.success('删除成功')
}
const comissionForm = ref({
rules: []
})
const comissionShow = ref(false)
function handleAddComission() {
comissionShow.value = true
comissionForm.value = {
startDate: formatDate(new Date(), 'YYYY-MM-DD'),
type: 1,
rules: [
{
type1: 1,
type2: 1,
type3: 0,
type4: 1,
type5: 1,
type6: 10
}
]
}
}
function handleAddRules() {
comissionForm.value.rules.push({
type1: 1,
type2: 1,
type3: 0,
type4: 1,
type5: 1,
type6: 1
})
}
function sureAddComission() {
message.success('新增成功')
}
</script>

View File

@@ -1,7 +1,628 @@
<template>
<div> 班型管理 </div>
<div>
<el-form :model="searchForm" ref="searchRef" inline label-width="0">
<el-form-item>
<el-input
v-model="searchForm.schoolName"
placeholder="请输入驾校名称"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-input
v-model="searchForm.placeName"
placeholder="请输入场地名称"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-input
v-model="searchForm.className"
placeholder="请输入班型名称"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-select
v-model="searchForm.cartypeId"
placeholder="请选择驾照类型"
clearable
filterable
@change="handleQuery"
>
<el-option
v-for="item in cartypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button plain @click="handleQuery">查询</el-button>
<el-button plain @click="handleReset">重置</el-button>
<el-button type="primary" plain @click="handleOpenDialog('create')">新增</el-button>
<el-button type="danger" @click="handleBatchDelete">批量删除</el-button>
<el-button type="warning" @click="handleBatchStatus">批量启/停用</el-button>
</el-form-item>
</el-form>
<el-row :gutter="20">
<el-col :span="6" :offset="0">
<div class="head-container" style="max-height: 700px; overflow-y: auto">
<el-tree
ref="tree"
:data="schoolOption.filter((item) => item.label.includes(searchForm.schoolName))"
accordion
node-key="id"
:default-expanded-keys="selectNodes.map((item) => item.id)"
@node-click="handleNodeClick"
/>
</div>
</el-col>
<el-col :span="18" :offset="0">
<el-table
:data="tableList"
v-loading="loading"
border
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="50" />
<el-table-column type="index" label="序号" width="60" />
<el-table-column
v-for="col in columns"
:prop="col.props"
:key="col.props"
:label="col.label"
:width="col.width"
show-overflow-tooltip
/>
<el-table-column label="状态" width="80">
<template #default="{ row }">
<el-switch
v-model="row.status"
:active-value="1"
:inactive-value="0"
@change="handleChangeStatus(row)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="120px">
<template #default="{ row }">
<el-button
type="primary"
style="padding: 0"
text
@click="handleOpenDialog('update', row)"
>
修改
</el-button>
<el-button type="danger" style="padding: 0" text @click="handleRemove(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="searchForm.pageSize"
v-model:page="searchForm.pageNum"
:total="total"
@pagination="getList"
/>
</el-col>
</el-row>
<el-dialog title="批量修改" v-model="batchStatusDialogShow" width="400px">
<el-form :model="statusForm" ref="statusRef" label-width="80px">
<el-form-item label="选择状态" prop="status">
<el-radio-group v-model="statusForm.status">
<el-radio :label="1"> 启用 </el-radio>
<el-radio :label="2"> 禁用 </el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<span>
<el-button @click="batchStatusDialogShow = false">取 消</el-button>
<el-button type="primary" @click="handleSureStatus">确 认</el-button>
</span>
</template>
</el-dialog>
<DialogClass ref="ClassDialog" @success="getList" />
</div>
</template>
<script setup></script>
<script setup name="Class">
import DialogClass from './Comp/DialogClass.vue'
const message = useMessage() // 消息弹窗
const loading = ref(false)
const total = ref(0)
const tableList = ref([])
// const schoolDialog = ref()
const searchForm = ref({
schoolName: '',
placeName: undefined,
className: undefined,
cartypeId: undefined,
pageNum: 1,
pageSize: 20
})
const cartypeOptions = ref([])
const schoolOption = ref([])
const selectNodes = ref([])
function getSchoolList() {
schoolOption.value = [
{
id: 179,
label: '安审驾校',
level: 1,
children: [{ id: 308, label: '(皖西)安审驾校已备案', level: 2 }]
},
{
id: 141,
label: '通顺驾校',
level: 1,
children: [
{ id: 173, label: '(皖北)通顺驾校信地报名点', level: 2 },
{ id: 320, label: '(皖北)通顺驾校包河花园训练基地《备案》', level: 2 },
{ id: 351, label: '皖西通顺驾校总校D已备案', level: 2 },
{ id: 359, label: '(皖北)通顺驾校龙岗训练基地《备案》', level: 2 }
]
},
{
id: 178,
label: '新宇驾校',
level: 1,
children: [{ id: 243, label: '(皖北)新宇驾校磨店校区《备案》', level: 2 }]
},
{
id: 176,
label: '鑫缘驾校',
level: 1,
children: [{ id: 385, label: '(皖北)鑫缘驾校南二环校区', level: 2 }]
},
{ id: 194, label: '环宇驾校', level: 1, children: [] },
{
id: 193,
label: '舒城同兴驾校',
level: 1,
children: [
{ id: 414, label: '舒城同兴驾校南港训练场', level: 2 },
{ id: 415, label: '同兴驾校杭埠分校训练场', level: 2 },
{ id: 416, label: '同兴驾校城区训练场', level: 2 }
]
},
{ id: 195, label: '新快捷驾校', level: 1, children: [] },
{
id: 187,
label: '鑫金陵驾校',
level: 1,
children: [{ id: 405, label: '(皖南)鑫金陵建大校区', level: 2 }]
},
{ id: 186, label: '途安驾校', level: 1, children: [] },
{ id: 163, label: '金灵驾校', level: 1, children: [] },
{ id: 185, label: '金湖驾校', level: 1, children: [] },
{
id: 172,
label: '精越驾校',
level: 1,
children: [
{ id: 111, label: '(皖北)精越驾校恒大校区 《夜训》《备案》', level: 2 },
{ id: 386, label: '(皖北)精越驾校岗集校区已备案', level: 2 }
]
},
{
id: 184,
label: '宏昌驾校',
level: 1,
children: [{ id: 394, label: '(皖西)宏昌驾校海棠校区已备案', level: 2 }]
},
{
id: 183,
label: '启点驾校',
level: 1,
children: [
{ id: 390, label: '(皖南)启点驾校(高新校区)', level: 2 },
{ id: 391, label: '(皖南)启点驾校(蜀山校区)', level: 2 }
]
},
{
id: 182,
label: '泓运驾校',
level: 1,
children: [
{ id: 401, label: '(皖南)泓运驾校临湖校区', level: 2 },
{ id: 402, label: '(皖南)泓运始信路总校', level: 2 }
]
},
{
id: 171,
label: '三联驾校',
level: 1,
children: [
{ id: 136, label: '(皖西)三联泰科驾校十里庙校区已备案', level: 2 },
{ id: 380, label: '(皖南)三联泰科经开校区', level: 2 }
]
},
{
id: 181,
label: '金冈山驾校',
level: 1,
children: [{ id: 161, label: '(皖北)金冈山驾校宁国路训练场《备案》', level: 2 }]
},
{ id: 167, label: '菜鸟学车', level: 1, children: [{ id: 218, label: '众顺驾校', level: 2 }] },
{ id: 101, label: '金马驾校', level: 1, children: [] },
{
id: 161,
label: '瑞星驾校',
level: 1,
children: [{ id: 221, label: '(皖西)瑞星驾校花峰路校区已备案', level: 2 }]
},
{
id: 165,
label: '久安驾校',
level: 1,
children: [{ id: 325, label: '(皖北)久安驾校环湖路训练基地 《备案》', level: 2 }]
},
{
id: 146,
label: '金新驾校',
level: 1,
children: [
{ id: 112, label: '(皖北)金新驾校(悠然)京商报名点', level: 2 },
{ id: 172, label: '皖北金新驾校天水路校区D《备案》', level: 2 },
{ id: 229, label: '(皖北)金新驾校磨店分校《备案》', level: 2 },
{ id: 232, label: '皖北金新驾校新蚌埠路训练场D,B2《备案》', level: 2 },
{ id: 233, label: '(皖北)金新驾校澥河路校区《备案》', level: 2 },
{ id: 387, label: '(皖北)金新驾校东二环校区《备案》', level: 2 },
{ id: 397, label: '(皖北)金新驾校北京路校区《备案》', level: 2 }
]
},
{
id: 115,
label: '畅通驾校',
level: 1,
children: [{ id: 350, label: '(皖西)畅通临泉路', level: 2 }]
},
{ id: 117, label: '一级合伙教练场地', level: 1, children: [] },
{
id: 125,
label: '金武联驾校',
level: 1,
children: [
{ id: 163, label: '(皖南)金武联驾校柳树塘校区(已备案)', level: 2 },
{ id: 190, label: '皖北金武联驾校大店报名点B2 D', level: 2 },
{
id: 222,
label: '(皖北)金武联总校训练场(客货运网约车出租车资格证)《备案》',
level: 2
},
{ id: 248, label: '(皖西)金武联驾校四里河校区已备案', level: 2 },
{ id: 291, label: '(皖南)金冈山李河湾校区', level: 2 },
{ id: 301, label: '(皖北)金武联裕溪路训练场《备案》', level: 2 },
{ id: 315, label: '皖北金新。金武联驾校杨岗报名大厅DB2', level: 2 },
{ id: 346, label: '(皖南)金武联高速时代广场训练基地', level: 2 },
{ id: 378, label: '(皖北)金武联大店训练场《备案》', level: 2 }
]
},
{
id: 126,
label: '巢湖顺达驾校',
level: 1,
children: [
{ id: 194, label: '顺达驾校半汤分校', level: 2 },
{ id: 265, label: '顺达驾校裕溪路训练基地', level: 2 }
]
},
{ id: 128, label: '悠然驾校', level: 1, children: [] },
{
id: 140,
label: '徽安驾校',
level: 1,
children: [{ id: 162, label: '(皖南)徽安驾校科技训练基地', level: 2 }]
},
{
id: 142,
label: '新安驾校',
level: 1,
children: [
{ id: 263, label: '(皖南)新安驾校(已备案)', level: 2 },
{ id: 393, label: '(皖南)新安黄山路校区', level: 2 }
]
},
{
id: 144,
label: '万锦驾校',
level: 1,
children: [
{ id: 258, label: '(皖北)万锦驾校磨店《备案》', level: 2 },
{ id: 327, label: '(皖南)万锦驾校冬梅路校区(已备案)', level: 2 }
]
},
{ id: 123, label: '康庄驾校', level: 1, children: [] },
{
id: 147,
label: '晨泰驾校',
level: 1,
children: [{ id: 245, label: '(皖北)晨泰驾校金梅路校区《备案》', level: 2 }]
},
{ id: 158, label: '金岗山驾校(摩托车)', level: 1, children: [] },
{ id: 160, label: '陪练陪驾', level: 1, children: [] },
{ id: 112, label: '浩宇驾校', level: 1, children: [] },
{
id: 139,
label: '合顺驾校',
level: 1,
children: [{ id: 332, label: '皖西合顺驾校临泉路校区D已备案', level: 2 }]
},
{
id: 152,
label: '新桥驾校',
level: 1,
children: [{ id: 389, label: '(皖北)新桥驾校东七训练场《备案》', level: 2 }]
},
{
id: 130,
label: '金路驾校',
level: 1,
children: [{ id: 208, label: '(皖南)金路名邦报名训练基地', level: 2 }]
},
{ id: 133, label: '众安驾校', level: 1, children: [] },
{
id: 116,
label: '顺通驾校',
level: 1,
children: [{ id: 410, label: '顺通驾校总校', level: 2 }]
},
{
id: 148,
label: '国安驾校',
level: 1,
children: [
{ id: 400, label: '(皖南)国安包公路校区', level: 2 },
{ id: 403, label: '(皖南)国安三河校区', level: 2 }
]
},
{ id: 135, label: '安申驾校', level: 1, children: [] },
{ id: 110, label: '九号驾校', level: 1, children: [{ id: 349, label: '九号驾校', level: 2 }] },
{ id: 129, label: '新地驾校', level: 1, children: [] },
{
id: 113,
label: '世纪驾校',
level: 1,
children: [
{ id: 146, label: '(皖西)世纪驾校晨光训练场已备案', level: 2 },
{ id: 209, label: '(皖西)世纪驾校桂王路六中训练基地已备案', level: 2 },
{ id: 223, label: '(皖西)世纪驾校撮镇义和训练场已备案', level: 2 },
{ id: 246, label: '(皖西)世纪驾校包公像训练场已备案', level: 2 }
]
},
{ id: 103, label: '起航驾校', level: 1, children: [] },
{
id: 137,
label: '庐州驾校',
level: 1,
children: [{ id: 155, label: '(皖西)庐州驾校北五里井校区已备案', level: 2 }]
},
{
id: 143,
label: '长安驾校',
level: 1,
children: [
{ id: 101, label: '(皖西)长安驾校蜀山校区已备案', level: 2 },
{ id: 104, label: '(皖北)长安驾校明光路训练基地《备案》', level: 2 },
{ id: 199, label: '(皖北)长安驾校龙岗报名点', level: 2 },
{ id: 204, label: '(皖西)长安驾校临泉路总校已备案', level: 2 },
{ id: 259, label: '(皖南)长安经开校区(已备案)', level: 2 },
{ id: 266, label: '长安驾校名人馆训练基地C1 C2 已备案)', level: 2 },
{ id: 290, label: '(皖北)长安驾校高铁南站报名训练场《备案》', level: 2 },
{ id: 300, label: '(皖北)长安驾校龙岗训练场《备案》', level: 2 },
{ id: 316, label: '长安驾校万达城报名点C2 早晚)', level: 2 },
{ id: 396, label: '(皖南)长安南七分校', level: 2 }
]
},
{
id: 153,
label: '肥东金荣驾校',
level: 1,
children: [
{ id: 107, label: '金荣驾校', level: 2 },
{ id: 181, label: '金荣驾校店埠镇训练基地', level: 2 },
{ id: 358, label: '(皖南)金荣驾校报名点', level: 2 }
]
},
{ id: 170, label: '和安驾校', level: 1, children: [] },
{
id: 119,
label: '平安驾校',
level: 1,
children: [
{ id: 250, label: '平安驾校总部报名点', level: 2 },
{ id: 267, label: '巢湖市平安驾校夏阁训练场', level: 2 },
{ id: 312, label: '巢湖平安驾校市区训练场', level: 2 },
{ id: 353, label: '巢湖市平安驾校巢湖学院训练场', level: 2 }
]
},
{ id: 168, label: '心诚驾校', level: 1, children: [] },
{
id: 132,
label: '新亚驾校',
level: 1,
children: [{ id: 164, label: '新亚驾校汽车站训练基地C2', level: 2 }]
},
{
id: 138,
label: '五星驾校',
level: 1,
children: [
{ id: 134, label: '(皖西)五星驾岗集校训练场', level: 2 },
{ id: 192, label: '(皖西)五星驾校岗集报名点', level: 2 },
{ id: 269, label: '皖南五星驾校总校合作化路训练基地D已备案', level: 2 },
{ id: 280, label: '(皖南)五星华府训练基地 ', level: 2 },
{ id: 388, label: '(皖西)五星驾校黄山路校区', level: 2 },
{ id: 399, label: '(皖南)五星明珠校区', level: 2 }
]
},
{
id: 121,
label: '永泰驾校',
level: 1,
children: [
{ id: 234, label: '(皖北)永泰驾校蒙河路校区《备案》', level: 2 },
{ id: 314, label: '(皖北)永泰驾校下塘校区《备案》', level: 2 },
{ id: 352, label: '(皖北)永泰驾校魏武路校区《备案》', level: 2 }
]
},
{
id: 111,
label: '宏运驾校',
level: 1,
children: [
{ id: 189, label: '(皖西)宏运驾校界首路校区已备案', level: 2 },
{ id: 404, label: '(皖北)宏运红旗建材校区《备案》', level: 2 }
]
},
{ id: 105, label: '众顺驾校', level: 1, children: [] },
{ id: 114, label: '东方驾校', level: 1, children: [] },
{ id: 104, label: '信达驾校', level: 1, children: [] },
{
id: 109,
label: '摩托车培训驾校',
level: 1,
children: [
{ id: 374, label: '(抖音)摩托车报名登记用', level: 2 },
{ id: 375, label: '非抖音学员摩托车报名登记用', level: 2 },
{ id: 413, label: '领航机车(运城滨湖)训练基地', level: 2 }
]
},
{
id: 145,
label: '运程驾校',
level: 1,
children: [
{ id: 139, label: '(皖南)运程蜀山校区', level: 2 },
{ id: 150, label: '(皖北)运程驾校北京路校区《备案》', level: 2 }
]
}
]
}
function handleNodeClick(data, node) {
if (data.level === 1) {
selectNodes.value = [{ id: data.id, name: data.label }]
} else {
selectNodes.value = [
{ id: node.parent.data.id, name: node.parent.data.label },
{ id: data.id, name: data.label }
]
}
handleQuery()
}
const columns = [
{ props: 'schoolName', label: '驾校', width: '100px' },
{ props: 'placeName', label: '场地' },
{ props: 'className', label: '班型名称' },
{ props: 'cartypeName', label: '驾照类型', width: '100px' },
{ props: '', label: '最新报价', width: '100px' },
{ props: '', label: '最新底价', width: '100px' },
{ props: 'remark', label: '备注', width: '100px' }
]
function handleQuery() {
searchForm.value.pageNum = 1
getList()
}
function getList() {
tableList.value = [
{ schoolName: '测试驾校', placeName: '测试场地', className: '测试', status: 1 }
]
}
const ClassDialog = ref()
function handleOpenDialog(type, row = null) {
ClassDialog.value.open(type, row)
}
function handleReset() {
searchForm.value = {
schoolName: '',
placeName: undefined,
className: undefined,
cartypeId: undefined,
pageSize: 20,
pageNum: 1
}
getList()
}
async function handleBatchDelete(arr = []) {
if (!arr.length || !selectRows.value.length) {
message.info('请选择表格行!')
return
}
try {
console.log(arr)
// 删除的二次确认
await message.delConfirm()
// 发起删除
// await UserApi.deleteUser(row.id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
const selectRows = ref([])
function handleSelectionChange(selection) {
selectRows.value = selection.map((item) => item.classId)
}
function handleRemove(row) {
handleBatchDelete([row])
}
async function handleChangeStatus(row) {
try {
console.log(row)
// 删除的二次确认
await message.confirm('是否确认修改状态')
// 发起删除
// await UserApi.deleteUser(row.id)
message.success('修改成功')
// 刷新列表
await getList()
} catch {}
}
const batchStatusDialogShow = ref(false)
const statusForm = ref({ status: 1 })
function handleBatchStatus() {
if (!selectRows.value.length) {
message.info('请选择表格行!')
return
}
batchStatusDialogShow.value = true
}
function handleSureStatus() {
console.log(statusForm.value)
message.success('保存成功')
batchStatusDialogShow.value = false
}
onMounted(() => {
getSchoolList()
})
</script>
<style lang="scss" scoped></style>

View File

@@ -1,7 +1,727 @@
<template>
<div> 驾校管理 </div>
<div class="absolute top-0 left-0 h-full w-full" style="height: calc(100vh - 85px)">
<div id="dialogMap" style="height: 100%"></div>
<!-- 右侧驾校列表 -->
<div class="asider" :class="showSchool ? '' : 'hidden-school'">
<el-card class="box-card" :body-style="{ flex: 1, 'overflow-y': 'scroll', padding: 0 }">
<div style="margin: 10px">
<el-input v-model="searchValue" placeholder="请输入驾校名" />
</div>
<template #header>
<div class="clearfix">
<div class="map-card-title">驾校列表</div>
<el-switch
v-model="showAllSchool"
:active-value="true"
:inactive-value="false"
active-text="隐藏所有"
inactive-text="展示所有"
@change="showAllSchoolChange"
/>
</div>
</template>
<div
v-for="school in schoolList.filter(
(item) => searchValue == undefined || item.schoolName.includes(searchValue)
)"
:key="school.schoolId"
style="margin: 10px"
:class="currentdeptId == school.schoolId ? 'actived-school' : ''"
>
<el-card :body-style="{ padding: '10px' }">
<template #header>
<div class="clearfix">
<div class="map-card-title">{{ school.schoolName }}</div>
<el-switch
v-model="school.showInMap"
class="add-icon"
active-text="展示"
inactive-text="隐藏"
@change="changeSchoolStatus(school)"
/>
</div>
</template>
<el-button @click="handleClickSchool(school)">{{
`数据管理(${getCount(school.schoolId)})`
}}</el-button>
<el-tooltip content="新增场地" placement="left" effect="dark">
<el-button class="add-place-btn" @click="handleInsertPlace(school.schoolId)">
<Icon icon="ep:plus" />
</el-button>
</el-tooltip>
</el-card>
</div>
</el-card>
<div class="asider-sub">
<el-tooltip
:content="`${showSchool ? '折叠' : '展开'}驾校列表`"
placement="left"
effect="dark"
>
<el-button class="is-circle" @click="toggleSchool">
<Icon class="text-12px" :icon="`ep:d-arrow-${showSchool ? 'right' : 'left'}`" />
</el-button>
</el-tooltip>
</div>
</div>
<!-- 左侧场地弹框 -->
<el-card v-if="placeDialogShow" class="place-dialog" :body-style="{ padding: '10px' }">
<template #header>
<div class="clearfix">
<div class="map-card-title">场地设置</div>
<el-tooltip content="取点" placement="right" effect="dark">
<el-button class="add-icon" @click="isPointing = !isPointing">
<Icon icon="ep:location" />
</el-button>
</el-tooltip>
</div>
</template>
<el-tabs v-model="placeTab">
<el-tab-pane label="场地信息" name="info">
<el-form ref="FormPlace" :model="placeForm" label-width="70px">
<el-form-item label="所属驾校" prop="schoolId">
<el-select v-model="placeForm.schoolId" placeholder="请选择" clearable class="w-full">
<el-option
v-for="dict in schoolList"
:key="dict.schoolId"
:label="dict.schoolName"
:value="dict.schoolId"
/>
</el-select>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="placeForm.name" placeholder="输入名称" />
</el-form-item>
<el-form-item label="旗子颜色" prop="flagColor">
<el-radio-group v-model="placeForm.flagColor">
<el-radio v-for="(item, index) in colorOptions" :key="index" :label="item">
<img :src="flagMap[item]" style="width: 20px" />
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="placeForm.address" placeholder="输入地址" />
</el-form-item>
<el-form-item label="经度" prop="lng">
<el-input v-model="placeForm.lng" placeholder="输入经度" />
</el-form-item>
<el-form-item label="纬度" prop="lat">
<el-input v-model="placeForm.lat" placeholder="输入纬度" />
</el-form-item>
<el-form-item label="所属区域" prop="area">
<el-select v-model="placeForm.area" placeholder="请选择" clearable class="w-full">
<el-option
v-for="dict in areaOptions"
:key="dict.dictValue"
:label="dict.dictLabel"
:value="dict.dictValue"
/>
</el-select>
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="placeForm.phone" placeholder="输入电话" />
</el-form-item>
<el-form-item label="负责人" prop="contact">
<el-input v-model="placeForm.contact" placeholder="输入负责人" />
</el-form-item>
<el-form-item label="是否推荐" prop="contact">
<el-radio v-model="placeForm.recommend" :label="true">是</el-radio>
<el-radio v-model="placeForm.recommend" :label="false">否</el-radio>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="描述" name="desc">
<Editor v-model:modelValue="placeForm.remark" />
</el-tab-pane>
</el-tabs>
<div class="flex items-center justify-center mt-10px">
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button @click="closePlaceDialog">取消</el-button>
</div>
</el-card>
<!-- 底部驾校场地列表 -->
<el-card
:class="placeListDialogShow ? '' : 'hidden-place-list'"
class="place-list-dialog"
:style="{ right: showSchool ? '300px' : '0', top: fullScreenPlaceList ? '0px' : '420px' }"
:body-style="{ padding: '10px', height: 'calc(100% - 52px)' }"
>
<template #header>
<div class="clearfix">
<div class="map-card-title">
{{ placeListDialogTitle }}
<el-input v-model="tableSearch" placeholder="请输入场地名称" clearable />
</div>
<el-tooltip content="全屏" placement="top" effect="dark">
<el-button class="add-icon" @click="fullScreenPlaceList = !fullScreenPlaceList">
<Icon icon="ep:full-screen" />
</el-button>
</el-tooltip>
<el-tooltip content="关闭" placement="top" effect="dark">
<el-button class="add-icon" @click="closePlaceList">
<Icon icon="ep:close" />
</el-button>
</el-tooltip>
</div>
</template>
<el-table
:data="placeTableData"
border
stripe
class="place-table-list"
size="small"
height="100%"
>
<el-table-column label="序号" type="index" fixed="left" width="50" />
<el-table-column prop="name" label="名称" min-width="100" />
<el-table-column prop="phone" label="电话" width="120" />
<el-table-column prop="contact" label="负责人" width="120" />
<el-table-column prop="address" label="地址" min-width="100" />
<el-table-column prop="lng" label="经度" width="110" />
<el-table-column prop="lat" label="纬度" width="110" />
<el-table-column prop="area" label="所属区域" width="110" />
<el-table-column label="启用" width="100">
<template #default="scope">
<el-switch v-model="scope.row.showInMap" @change="changePlaceStatus(scope.row)" />
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template #default="scope">
<el-tooltip content="编辑" placement="top" effect="dark">
<el-button
type="primary"
style="padding: 4px 8px"
@click="handleEditPlace(scope.row)"
>
<Icon icon="ep:edit" />
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<div
v-if="isPointing"
class="map-tip"
:style="{
transform: 'translate3D(' + (tipPostion.x + 15) + 'px,' + (tipPostion.y - 10) + 'px, 0)'
}"
>{{ mapHelpText }}</div
>
<Icon
v-if="isPointing"
icon="ep:s-flag"
class="circle"
:style="{ transform: 'translate3D(' + tipPostion.x + 'px,' + tipPostion.y + 'px, 0)' }"
/>
</div>
</template>
<script setup></script>
<script setup name="Place">
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'
import AMapLoader from '@amap/amap-jsapi-loader'
<style lang="scss" scoped></style>
const message = useMessage() // 消息弹窗
// 地图相关
const dialogMap = ref(null)
const aMap = ref(null)
const geoCoder = ref(null)
const locationMarker = ref(null)
const flagMap = {
red: FlagRed,
yellow: FlagYellow,
purple: FlagPurple,
green: FlagGreen,
blue: FlagBlue,
black: FlagBlack
}
const colorOptions = ['red', 'yellow', 'blue', 'green', 'purple', 'black']
const areaOptions = ref([])
const mapHelpText = ref('')
const tipPostion = ref({})
const placeTab = ref('info')
function initMap() {
AMapLoader.load({
key: '2ffb0e2ea90b1df0b8be48ed66e18fc8', //设置您的key
version: '2.0',
plugins: ['AMap.Geocoder']
}).then((AMap) => {
aMap.value = AMap
geoCoder.value = new AMap.Geocoder()
dialogMap.value = new AMap.Map('dialogMap', {
zoom: 12,
zooms: [2, 22],
center: [117.283042, 31.86119]
})
locationMarker.value = new AMap.Marker({
icon: FlagRed
})
getPageData()
dialogMap.value.on('click', (ev) => {
if (isPointing.value) {
placeForm.value.lat = ev.lnglat.lat
placeForm.value.lng = ev.lnglat.lng
regeoCode()
locationMarker.value.setPosition([placeForm.value.lng, placeForm.value.lat])
dialogMap.value.add(locationMarker.value)
isPointing.value = false
}
})
dialogMap.value.on('mousemove', (ev) => {
// if (isRanging.value) {
// mapHelpText.value =
// '左键单击选点,双击/右键单击完成选点,再次点击测距按钮可退出测距模式,并清除测距结果'
// tipPostion.value = {
// x: ev.pixel.x,
// y: ev.pixel.y
// }
// } else if (isPointing.value) {
mapHelpText.value = '点击地图添加标注'
tipPostion.value = {
x: ev.pixel.x,
y: ev.pixel.y
}
// }
})
})
}
function regeoCode() {
geoCoder.value.getAddress([placeForm.value.lng, placeForm.value.lat], (status, result) => {
if (status === 'complete' && result.regeocode) {
placeForm.value.address = result.regeocode.formattedAddress
} else {
message.error('根据经纬度查询地址失败')
}
})
}
function getPageData() {
// getMapData().then((resp) => {
// if (resp.code == 200) {
schoolList.value = [{ schoolName: '测试', showInMap: true, schoolId: '0001' }]
tableData.value = [
{
schoolName: '测试',
showInMap: true,
schoolId: '0001',
name: '训练场1',
lat: 31.86119,
lng: 117.283042,
schoolShow: true,
placeId: 'p0001'
},
{
schoolName: '测试',
showInMap: false,
schoolId: '0001',
name: '训练场2',
lat: 31.86219,
lng: 117.281042,
schoolShow: true,
placeId: 'p0002'
}
]
currentdeptId.value = schoolList.value[0].schoolId
createMarkersInMap()
// }
// });
}
const searchValue = ref('')
const schoolList = ref([])
const placeListDialogShow = ref(false)
const currentdeptId = ref('')
function changeSchoolStatus(school) {
tableData.value.forEach((item) => {
if (school.schoolId == item.schoolId) {
item.schoolShow = school.showInMap
}
})
resetMarkers()
}
function handleClickSchool(item) {
placeListDialogShow.value = true
placeListDialogTitle.value = `数据管理 [${item.schoolName}]`
currentdeptId.value = item.schoolId
}
// 生成markers
function createMarkersInMap() {
for (let i = 0; i < tableData.value.length; i++) {
const element = tableData.value[i]
if (!element.schoolShow || !element.showInMap) {
continue
}
const markerIcon = flagMap[element.flagColor || 'red']
const tmpMarker = new aMap.value.Marker({
map: dialogMap.value,
position: [element.lng, element.lat],
icon: markerIcon,
label: {
content: element.name,
direction: 'right'
},
extData: element
})
tmpMarker.on('click', handleClickMarker)
placeMarkerList.value.push(tmpMarker)
}
}
function handleInsertPlace(schoolId) {
placeDialogShow.value = true
dialogMap.value.setDefaultCursor('default')
// isRanging.value = false
placeForm.value = {
lat: undefined,
lng: undefined,
name: undefined,
address: undefined,
remark: undefined,
phone: undefined,
schoolId: schoolId,
showInMap: true,
flagColor: 'red'
}
}
// 关闭场地弹窗
function closePlaceDialog() {
placeDialogShow.value = false
isPointing.value = false
dialogMap.value.remove(locationMarker.value)
}
const isPointing = ref(false)
function checkPlaceFormValidate() {
const valid = []
if (!placeForm.value.name) {
valid.push('名称')
}
if (!placeForm.value.address) {
valid.push('地址')
}
if (!placeForm.value.lng) {
valid.push('经度')
}
if (!placeForm.value.lat) {
valid.push('纬度')
}
if (!placeForm.value.phone) {
valid.push('电话')
}
if (valid.length == 0) {
return true
} else {
message.error(`请将以下填写完整: ${valid.join(',')}`)
return false
}
}
function handleClickMarker(ev) {
placeForm.value = ev.target.getExtData()
placeDialogShow.value = true
}
async function onSubmit() {
// 保存接口
if (checkPlaceFormValidate()) {
// 先访问接口返回id插入placeForm
// const resp = savePlace(placeForm.value);
const resp = { code: 200, data: 'p00001' }
if (resp.code != 200) {
return
} else {
message.success('操作成功')
}
if (!placeForm.value.placeId && resp.data) {
placeForm.value.placeId = resp.data
}
// 移除选点用 的标记
dialogMap.value.remove(locationMarker.value)
// 根据form创建新marker 并添加到地图上
const tmpMarker = new aMap.value.Marker({
map: dialogMap.value,
position: [placeForm.value.lng, placeForm.value.lat],
icon: flagMap[placeForm.value.flagColor],
label: {
content: placeForm.value.name,
direction: 'right'
},
extData: placeForm.value
})
// 新marker事件
tmpMarker.on('click', handleClickMarker)
// 如果当前选择的marker点存在编辑
// if (selectMarker.value) {
// // 地图上 移除选择的点
// dialogMap.value.remove(selectMarker.value)
// selectMarker.value = null
// }
// 关闭场地弹窗
placeDialogShow.value = false
isPointing.value = false
// 场地列表 移除原列表中操作的场地数据
const tmpArr = tableData.value.filter((item) => item.placeId !== placeForm.value.placeId)
// 新增新的场地
tmpArr.push(placeForm.value)
// 重置场地数组
tableData.value = tmpArr
// 地图marker列表 移除操作的原marker 添加新marker进数组
const tmpArr1 = placeMarkerList.value.filter(
(item) => item.getExtData().placeId !== placeForm.value.placeId
)
tmpArr1.push(tmpMarker)
placeMarkerList.value = tmpArr1
}
}
function getCount(schoolId) {
return tableData.value.filter((item) => item.schoolId === schoolId).length
}
const showSchool = ref(true)
const showAllSchool = ref(false)
const fullScreenPlaceList = ref(false)
const placeListDialogTitle = ref('')
const tableSearch = ref('')
const tableData = ref([])
const placeTableData = computed(() => {
if (tableSearch.value) {
return tableData.value.filter(
(dataNews) =>
dataNews.schoolId === currentdeptId.value && dataNews.name.includes(tableSearch.value)
)
}
return tableData.value.filter((dataNews) => dataNews.schoolId === currentdeptId.value)
})
function showAllSchoolChange() {
schoolList.value.forEach((item) => (item.showInMap = !showAllSchool.value))
tableData.value.forEach((item) => (item.schoolShow = !showAllSchool.value))
resetMarkers()
}
function toggleSchool() {
showSchool.value = !showSchool.value
}
function closePlaceList() {
placeListDialogShow.value = false
fullScreenPlaceList.value = false
}
async function changePlaceStatus() {
// const resp = await updatePlace(item);
// if (resp.code == 200) {
resetMarkers()
// }
}
// const isRanging = ref(false)
const placeForm = ref({
lat: undefined,
lng: undefined,
name: undefined,
address: undefined,
remark: undefined,
phone: undefined,
flagColor: 'red'
})
// const selectMarker = ref(null)
const placeMarkerList = ref([])
// 编辑场地
function handleEditPlace(item) {
placeDialogShow.value = true
dialogMap.value.setDefaultCursor('default')
// 方法已废弃
// if (selectMarker.value) {
// selectMarker.value.setAnimation('AMAP_ANIMATION_NONE')
// }
// isRanging.value = false
placeForm.value = Object.assign({}, item)
// selectMarker.value = placeMarkerList.value.find(
// (marker) => marker.getExtData().placeId === item.placeId
// )
// 方法已废弃
// selectMarker.value && selectMarker.value.setAnimation('AMAP_ANIMATION_BOUNCE')
dialogMap.value.setCenter([item.lng, item.lat])
}
const placeDialogShow = ref(false)
// 重置markers
function resetMarkers() {
dialogMap.value.clearMap()
createMarkersInMap()
}
onMounted(() => {
initMap()
})
</script>
<style lang="scss" scoped>
.asider {
position: absolute;
right: 0;
top: 0;
width: 300px;
height: 100%;
transition: 0.3s;
z-index: 9;
}
.box-card {
display: flex;
flex-direction: column;
height: 100%;
}
::v-deep(.el-card__header) {
padding: 10px 15px;
}
.clearfix {
display: flex;
}
.clearfix .map-card-title {
flex: 1;
line-height: 30px;
}
.clearfix .add-icon {
width: auto;
height: 30px;
}
.asider-sub {
position: absolute;
top: 40px;
left: -45px;
padding: 10px 10px 0 0;
z-index: 900;
}
.asider-sub .is-circle {
// display: block;
margin: 0;
padding: 10px;
color: #464646;
border-radius: 0;
font-size: 16px;
box-shadow: 2px 2px 2px rgba(80, 80, 80, 0.67);
}
.mt10 {
margin-top: 10px;
}
.hidden-school {
transform: translateX(300px);
}
.search-body {
position: absolute;
top: 20px;
left: 20px;
width: 400px;
}
::v-deep(.place-dialog) {
position: absolute;
left: 20px;
top: 20px;
width: 350px;
}
.map-tip {
position: absolute;
left: 0;
top: 0;
max-width: 150px;
padding: 5px;
border-radius: 2px;
background: #000;
color: #fff;
opacity: 0.7;
font-size: 12px;
transition-duration: 1ms;
}
.circle {
position: absolute;
left: -8px;
top: -25px;
font-size: 24px;
color: red;
transition-duration: 1ms;
}
.add-place-btn {
float: right;
border: none;
font-size: 16px;
color: #409eff;
}
.place-dialog .el-form .el-form-item {
margin-bottom: 8px;
}
.place-list-dialog {
position: absolute;
top: 420px;
left: 0;
bottom: 0;
transition: 0.3s;
z-index: 151;
background: #e2e5ea;
}
.place-list-dialog .add-icon {
font-size: 18px;
border: none;
}
.hidden-place-list {
transform: translateY(100%);
}
.place-list-dialog .clearfix .map-card-title {
font-weight: bold;
}
.place-list-dialog .clearfix .map-card-title .el-input {
margin-left: 20px;
width: 240px;
}
.actived-school {
border: 2px solid #409eff !important;
}
::v-deep(.el-radio__label) {
vertical-align: middle;
}
</style>

View File

@@ -0,0 +1,124 @@
<template>
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="80px"
>
<el-row :gutter="20">
<el-col :span="12" :offset="0">
<el-form-item label="驾校名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入驾校名称" />
</el-form-item>
</el-col>
<el-col :span="12" :offset="0">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1"> 正常 </el-radio>
<el-radio :label="0"> 停用 </el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12" :offset="0">
<el-form-item label="负责人" prop="leader">
<el-input v-model="formData.leader" placeholder="请输入负责人" />
</el-form-item>
</el-col>
<el-col :span="12" :offset="0">
<el-form-item label="联系方式" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入联系方式" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-form-item label="备注" prop="remark">
<Editor v-model:modelValue="formData.remark" />
</el-form-item>
</el-row>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</el-dialog>
</template>
<script name="DialogSchool" setup>
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 formData = ref({
name: '',
leader: '',
phone: '',
status: 1,
remark: ''
})
const formRules = reactive({
name: [{ required: true, message: '驾校名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type, id) => {
dialogVisible.value = true
dialogTitle.value = type == 'create' ? '新增驾校' : '修改驾校'
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
// formData.value = await UserApi.getUser(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef.value) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
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)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
name: '',
leader: '',
phone: '',
status: 1,
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@@ -1,7 +1,108 @@
<template>
<div> 场地管理 </div>
<div>
<el-form :model="searchForm" ref="searchForm" label-width="0" inline>
<el-form-item>
<el-input
v-model="searchForm.name"
class="!w-240px"
placeholder="请输入驾校名称"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"> 搜索 </el-button>
<el-button @click="resetQuery"> 重置 </el-button>
<el-button plain type="primary" @click="openForm('create')"> 新增 </el-button>
</el-form-item>
</el-form>
<!-- 列表 -->
<el-table v-loading="loading" :data="tableList">
<el-table-column label="驾校名称" prop="name" />
<el-table-column label="负责人" prop="" />
<el-table-column label="联系方式" prop="" />
<el-table-column label="状态">
<template #default="{ row }">
<el-switch
v-model="row.status"
:active-value="true"
:inactive-value="false"
@change="changeStatus(row)"
/>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="" />
<el-table-column fixed="right" label="操作" width="150">
<template #default="{ row }">
<el-button link type="primary" @click="openForm(update, row)"> 修改 </el-button>
<el-button link type="danger" @click="handleDelete(row.id)"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="searchForm.pageSize"
v-model:page="searchForm.pageNum"
:total="total"
@pagination="getList"
/>
<DialogSchool ref="schoolDialog" @success="handleQuery" />
</div>
</template>
<script setup></script>
<script setup name="School">
import DialogSchool from './Comp/DialogSchool.vue'
const message = useMessage() // 消息弹窗
const searchForm = ref({
name: '',
pageNum: 1,
pageSize: 20
})
const loading = ref(false)
const total = ref(0)
const tableList = ref([])
const schoolDialog = ref()
function resetQuery() {
searchForm.value = {
name: '',
pageSize: 20,
pageNum: 1
}
getList()
}
function handleQuery() {
searchForm.value.pageNum = 1
getList()
}
function getList() {
tableList.value = [{ name: '测试' }]
}
function openForm(type, row = null) {
schoolDialog.value.open(type, row)
}
async function handleDelete(row) {
try {
console.log(row)
// 删除的二次确认
await message.delConfirm()
// 发起删除
// await UserApi.deleteUser(row.id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
function changeStatus(row) {
console.log(row)
}
</script>
<style lang="scss" scoped></style>

File diff suppressed because it is too large Load Diff