Commit 1997abcf by wuqian

feat:九猫客户管理新增、开通及关闭ERP#ID1010699

parent 9508944f
...@@ -154,4 +154,21 @@ export function post(url, params, headers) { ...@@ -154,4 +154,21 @@ export function post(url, params, headers) {
}) })
} }
export function remove(url, params) {
return new Promise((resolve, reject) => {
axios
.delete(url, { params })
.then((res) => {
if (res.status === 200 && res.data) {
resolve(res.data)
} else {
resolve(res)
}
})
.catch((err) => {
reject(err)
})
})
}
export default axios export default axios
...@@ -7,21 +7,32 @@ ...@@ -7,21 +7,32 @@
--> -->
<template> <template>
<div class="img_main" :style="!isFloat ? 'display:block' : ''"> <div class="img_main" :style="!isFloat ? 'display:block' : ''">
<draggable v-model="list" group="people" @start="drag = true" @end="drag = false" <draggable
:style="!isFloat ? 'display:block' : ''" :move="onMove" class="draggable"> v-model="list"
group="people"
@start="drag = true"
@end="drag = false"
:style="!isFloat ? 'display:block' : ''"
:move="onMove"
class="draggable">
<template v-if="isFloat"> <template v-if="isFloat">
<div class="img_img" v-for="(item, i) in fileList" :key="i"> <div class="img_img" v-for="(item, i) in fileList" :key="i">
<div class="img_item" :class="{ high: highLength > i }"> <div class="img_item" :class="{ high: highLength > i }">
<div class="img"> <div class="img">
<img width="58" height="58" :src="item.imagePath <img
? setimgUrl(formatUrl(item.imagePath, 75), { width="58"
w: width, height="58"
f: 'png', :src="
isCustom: isCustom, item.imagePath
hasWidth: hasWidth ? setimgUrl(formatUrl(item.imagePath, 75), {
}) w: width,
: formatUrl(item.filePath, 75) f: 'png',
" alt="" /> isCustom: isCustom,
hasWidth: hasWidth
})
: formatUrl(item.filePath, 75)
"
alt="" />
<span style="font-size: 12px">{{ item.id }}</span> <span style="font-size: 12px">{{ item.id }}</span>
</div> </div>
...@@ -29,30 +40,41 @@ ...@@ -29,30 +40,41 @@
<button type="button" @click="fullscreen(i)"> <button type="button" @click="fullscreen(i)">
<i class="el-icon-zoom-in"></i> <i class="el-icon-zoom-in"></i>
</button> </button>
<button class="remove" v-if="isdel" type="button" @click="remove(i)"> <button
class="remove"
v-if="isdel"
type="button"
@click="remove(i)">
<i class="el-icon-close"></i> <i class="el-icon-close"></i>
</button> </button>
</div> </div>
<slot name="mark" :index="i" :item="item"></slot> <slot name="mark" :index="i" :item="item"></slot>
<template> <template>
<span class="select-icon" v-if="item.type" :style="item.type === 'positively' <span
? 'background-color:#67C23A' class="select-icon"
: item.type === 'negative' v-if="item.type"
? 'background-color:#409EFF' :style="
: 'background-color:#B79860' item.type === 'positively'
"> ? 'background-color:#67C23A'
: item.type === 'negative'
? 'background-color:#409EFF'
: 'background-color:#B79860'
">
{{ {{
item.type === 'positively' item.type === 'positively'
? 'F' ? 'F'
: item.type === 'negative' : item.type === 'negative'
? 'B' ? 'B'
: 'D' : 'D'
}} }}
</span> </span>
</template> </template>
</div> </div>
</div> </div>
<div v-show="add && fileList.length < length" class="img_wrap" @click="upload"> <div
v-show="add && fileList.length < length"
class="img_wrap"
@click="upload">
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
</div> </div>
</template> </template>
...@@ -60,21 +82,26 @@ ...@@ -60,21 +82,26 @@
<div v-for="(item, i) in fileList" :key="i" class="include-other-info"> <div v-for="(item, i) in fileList" :key="i" class="include-other-info">
<div class="img_item" :class="{ high: highLength > i }"> <div class="img_item" :class="{ high: highLength > i }">
<div class="img"> <div class="img">
<img width="58" height="58" :src="item.imagePath <img
? setimgUrl(item.imagePath, { width="58"
w: width, height="58"
f: 'png', :src="
hasWidth: hasWidth, item.imagePath
isCustom: isCustom, ? setimgUrl(item.imagePath, {
hasOss: hasOss w: width,
}) f: 'png',
: setimgUrl(item.filePath, { hasWidth: hasWidth,
w: width, isCustom: isCustom,
f: 'png', hasOss: hasOss
hasWidth: hasWidth, })
hasOss: hasOss : setimgUrl(item.filePath, {
}) w: width,
" alt="" /> f: 'png',
hasWidth: hasWidth,
hasOss: hasOss
})
"
alt="" />
<span style="font-size: 12px">{{ item.id }}</span> <span style="font-size: 12px">{{ item.id }}</span>
</div> </div>
...@@ -82,20 +109,32 @@ ...@@ -82,20 +109,32 @@
<button type="button" @click="fullscreen(i)"> <button type="button" @click="fullscreen(i)">
<i class="el-icon-zoom-in"></i> <i class="el-icon-zoom-in"></i>
</button> </button>
<button class="remove" v-if="isdel" type="button" @click="remove(i)"> <button
class="remove"
v-if="isdel"
type="button"
@click="remove(i)">
<i class="el-icon-close"></i> <i class="el-icon-close"></i>
</button> </button>
</div> </div>
</div> </div>
<slot name="other" :index="i" :item="item"></slot> <slot name="other" :index="i" :item="item"></slot>
</div> </div>
<div v-show="add && fileList.length < length" class="img_wrap" @click="upload"> <div
v-show="add && fileList.length < length"
class="img_wrap"
@click="upload">
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
</div> </div>
</template> </template>
</draggable> </draggable>
<el-image-viewer class="image_viewer" :initialIndex="initialIndex" :zIndex="9999" :urlList="urlList" <el-image-viewer
v-if="imageVisible" :on-close="closeViewer"></el-image-viewer> class="image_viewer"
:initialIndex="initialIndex"
:zIndex="9999"
:urlList="urlList"
v-if="imageVisible"
:on-close="closeViewer"></el-image-viewer>
</div> </div>
</template> </template>
<script> <script>
...@@ -231,9 +270,9 @@ export default { ...@@ -231,9 +270,9 @@ export default {
return this.list.map((item) => { return this.list.map((item) => {
return item.imagePath return item.imagePath
? this.setimgUrl(item.imagePath, { ? this.setimgUrl(item.imagePath, {
f: 'png', f: 'png',
isCustom: this.isCustom isCustom: this.isCustom
}) })
: item.filePath : item.filePath
}) })
} }
...@@ -310,21 +349,21 @@ export default { ...@@ -310,21 +349,21 @@ export default {
if (this.joinBefore) { if (this.joinBefore) {
data.imagePath data.imagePath
? list.unshift({ ? list.unshift({
imagePath: data.imagePath imagePath: data.imagePath
}) })
: list.unshift({ : list.unshift({
imagePath: data.filePath imagePath: data.filePath
}) })
} else { } else {
data.imagePath data.imagePath
? list.push({ ? list.push({
imagePath: data.imagePath, imagePath: data.imagePath,
type: this.isMark ? 'positively' : '' type: this.isMark ? 'positively' : ''
}) })
: list.push({ : list.push({
imagePath: data.filePath, imagePath: data.filePath,
type: this.isMark ? 'positively' : '' type: this.isMark ? 'positively' : ''
}) })
} }
} }
this.$emit('selectImg', this.deduplication(list)) this.$emit('selectImg', this.deduplication(list))
...@@ -349,7 +388,7 @@ export default { ...@@ -349,7 +388,7 @@ export default {
const [imgobj] = this.fileList.splice(i, 1) const [imgobj] = this.fileList.splice(i, 1)
this.$emit('remove', imgobj) this.$emit('remove', imgobj)
}) })
.catch(() => { }) .catch(() => {})
} else { } else {
const [imgobj] = this.fileList.splice(i, 1) const [imgobj] = this.fileList.splice(i, 1)
this.$emit('remove', imgobj) this.$emit('remove', imgobj)
...@@ -410,13 +449,17 @@ export default { ...@@ -410,13 +449,17 @@ export default {
.img_item { .img_item {
position: relative; position: relative;
width: 100%; width: 58px;
height: auto; height: 58px;
text-align: center; text-align: center;
border: 1px solid #ddd; border: 1px solid #ddd;
margin-bottom: 5px; margin-bottom: 5px;
} }
.img_item .img {
width: 100%;
height: 100%;
position: relative;
}
.img_img:last-child { .img_img:last-child {
margin-right: 6px; margin-right: 6px;
} }
...@@ -484,7 +527,7 @@ export default { ...@@ -484,7 +527,7 @@ export default {
cursor: pointer; cursor: pointer;
} }
.image_viewer>>>.el-image-viewer__close { .image_viewer >>> .el-image-viewer__close {
color: #fff; color: #fff;
} }
......
...@@ -43,7 +43,7 @@ const routes = [ ...@@ -43,7 +43,7 @@ const routes = [
name: 'saasMenu', name: 'saasMenu',
meta: { title: 'ERP菜单管理' } meta: { title: 'ERP菜单管理' }
}, },
{ {
path: '/saas/factoryMenu', path: '/saas/factoryMenu',
component: factoryMenuPage, component: factoryMenuPage,
name: 'factoryMenu', name: 'factoryMenu',
...@@ -112,10 +112,15 @@ const routes = [ ...@@ -112,10 +112,15 @@ const routes = [
{ {
path: '/saas/user/management', path: '/saas/user/management',
component: () => import('@/views/user/management.vue'), component: () => import('@/views/user/management.vue'),
name: 'system_management', name: 'user_management',
meta: { title: '客户管理' } meta: { title: '潜在客户管理' }
},
{
path: '/saas/user/jm-management',
component: () => import('@/views/user/jmManagement.vue'),
name: 'user_jm_management',
meta: { title: '九猫客户管理' }
}, },
{ {
path: '/saas/user/recharge-record', path: '/saas/user/recharge-record',
component: () => import('@/views/user/rechargeRecord.vue'), component: () => import('@/views/user/rechargeRecord.vue'),
...@@ -167,7 +172,7 @@ const routes = [ ...@@ -167,7 +172,7 @@ const routes = [
{ {
path: '/operation/lossOrder', path: '/operation/lossOrder',
component: () => import('@/views/operation/lossOrder/index.vue'), component: () => import('@/views/operation/lossOrder/index.vue'),
name: 'system_management', name: 'operation_loss_order',
meta: { title: '丢单管理' } meta: { title: '丢单管理' }
}, },
{ {
...@@ -184,7 +189,8 @@ const routes = [ ...@@ -184,7 +189,8 @@ const routes = [
}, },
{ {
path: '/operation/amazonAttributeGrouping', path: '/operation/amazonAttributeGrouping',
component: () => import('@/views/operation/amazon/amazonAttributeGrouping.vue'), component: () =>
import('@/views/operation/amazon/amazonAttributeGrouping.vue'),
name: 'amazonAttributeGrouping', name: 'amazonAttributeGrouping',
meta: { title: '亚马逊属性分类' } meta: { title: '亚马逊属性分类' }
}, },
...@@ -203,7 +209,7 @@ const routes = [ ...@@ -203,7 +209,7 @@ const routes = [
{ {
path: '/dynamicForm', path: '/dynamicForm',
component: () => import('@/components/dynamicForms/index.vue'), component: () => import('@/components/dynamicForms/index.vue'),
name: 'system_management', name: 'dynamic_form',
meta: { title: '动态表单' } meta: { title: '动态表单' }
} }
] ]
......
...@@ -385,9 +385,17 @@ export default { ...@@ -385,9 +385,17 @@ export default {
index: '/user', index: '/user',
children: [ children: [
{ {
id: 4,
path: '',
label: '九猫客户管理',
icon: 'el-icon-user',
index: '/saas/user/jm-management',
children: []
},
{
id: 1, id: 1,
path: '', path: '',
label: '客户管理', label: '潜在客户管理',
icon: 'el-icon-s-custom', icon: 'el-icon-s-custom',
index: '/saas/user/management', index: '/saas/user/management',
children: [] children: []
......
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
查询 查询
</el-button> </el-button>
</el-form-item> </el-form-item>
<el-form-item> <!-- <el-form-item>
<el-button <el-button
type="success" type="success"
size="small" size="small"
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
@click="addData"> @click="addData">
开通erp 开通erp
</el-button> </el-button>
</el-form-item> </el-form-item> -->
<el-form-item> <el-form-item>
<el-button <el-button
type="danger" type="danger"
......
<template>
<el-dialog
:visible.sync="dialogVisible"
:title="titleName"
width="50%"
:close-on-click-modal="false"
:before-close="() => $emit('update:editDialog', false)">
<el-form
:inline="true"
size="mini"
:rules="editRules"
:model="editForm"
ref="editFormRef"
label-width="120px">
<div class="form-center">
<h3>基础信息</h3>
<div class="grid-from">
<el-form-item label="公司名称" prop="companyName">
<el-input
style="width: 250px"
v-model="editForm.companyName"
placeholder="请输入公司名称"></el-input>
</el-form-item>
<el-form-item label="所属行业" prop="industry">
<el-input
style="width: 250px"
v-model="editForm.industry"
placeholder="请输入所属行业"></el-input>
</el-form-item>
</div>
<div class="grid-from">
<el-form-item label="注册地址" prop="registeredAddress">
<el-input
style="width: 250px"
v-model="editForm.registeredAddress"
clearable
placeholder="请输入注册地址"></el-input>
</el-form-item>
<el-form-item label="办公地址" prop="officeAddress">
<el-input
style="width: 250px"
v-model="editForm.officeAddress"
clearable
placeholder="请输入办公地址"></el-input>
</el-form-item>
</div>
<div class="grid-from">
<el-form-item label="业务对接人" prop="contactUserName">
<el-input
style="width: 250px"
v-model="editForm.contactUserName"
clearable
placeholder="请输入业务对接人"></el-input>
</el-form-item>
<el-form-item label="对接人职位" prop="contactUserTitle">
<el-input
style="width: 250px"
v-model="editForm.contactUserTitle"
clearable
placeholder="请输入对接人职位"></el-input>
</el-form-item>
</div>
<div class="grid-from">
<el-form-item label="对接人电话" prop="contactUserPhone">
<el-input
style="width: 250px"
v-model="editForm.contactUserPhone"
clearable
placeholder="请输入对接人电话"></el-input>
</el-form-item>
<el-form-item label="对接人邮箱" prop="contactUserEmail">
<el-input
style="width: 250px"
v-model="editForm.contactUserEmail"
clearable
placeholder="请输入对接人邮箱"></el-input>
</el-form-item>
</div>
<div class="grid-from">
<el-form-item label="客户运营" prop="operationUserId">
<el-select
v-model="editForm.operationUserId"
placeholder="请选择客户运营"
style="width: 250px">
<el-option
v-for="item in userLists"
:key="item.id"
:label="item.realName"
:value="item.id"></el-option>
</el-select>
</el-form-item>
</div>
<div class="grid-from">
<el-form-item label="备注" prop="remark">
<el-input
v-model="editForm.remark"
clearable
style="width: 250px"
type="textarea"
placeholder="请输入备注" />
</el-form-item>
<el-form-item label="营业执照" prop="businessLicenseUri">
<div class="my-flex-1">
<upload-images
ref="uploadRef"
sortField="sort"
businessType="other"
uploadApiUrl="upload/oss"
:type="limitFileType"
:isFloat="false"
:width="250"
:hasWidth="true"
:hasOss="true"
:length="1"
v-model="businessLicenseFileList"
@selectImg="selectImg"
@remove="onImgRemove"></upload-images>
</div>
</el-form-item>
</div>
</div>
<div class="form-center" v-if="isFullValidate">
<h3>ERP信息</h3>
<div class="grid-from">
<el-form-item label="域名" prop="domain">
<el-input
style="width: 250px"
v-model.trim="editForm.domain"
clearable
:disabled="
['已开通', '开通失败', '已关闭'].includes(erpStatus) &&
commandTitle === 'edit'
"
placeholder="请输入域名"
@change="onDomainChange">
<template slot="append">.jomalls.com</template>
</el-input>
</el-form-item>
<el-form-item label="套餐" prop="packages">
<el-select
style="width: 250px"
v-model="editForm.packages"
size="small"
@change="changeCharges"
placeholder="请选择套餐">
<el-option
v-for="item in paymentPackages"
:key="item.id"
:value="item.key"
:label="item.name"></el-option>
</el-select>
</el-form-item>
</div>
<div class="grid-from">
<el-form-item label="资费" prop="charges">
<el-input
style="width: 250px"
v-model="editForm.charges"
disabled
clearable
placeholder="请输入资费"></el-input>
</el-form-item>
</div>
</div>
</el-form>
<span slot="footer">
<el-button size="mini" @click="$emit('update:editDialog', false)">
取 消
</el-button>
<el-button size="mini" type="primary" @click="onSubmit">确 定</el-button>
</span>
</el-dialog>
</template>
<script>
import UploadImages from '@/components/base/image-list.vue'
const TITLE_MAP = {
add: '新增客户',
openErp: '开通ERP',
edit: '编辑'
}
export default {
data() {
return {
limitFileType: ['jpg', 'png'],
paymentPackages: [
{
id: 0,
cny: 'CNY',
price: 10,
name: '内测版',
key: 'INNERBATE',
o: 0.2,
oName: '0',
orders: 10000
},
{
id: 1,
cny: 'CNY',
price: 10000,
name: '基础版',
key: 'PAYBASE',
o: 0.2,
oName: '0.2元/单',
orders: 50000
},
{
id: 2,
cny: 'CNY',
price: 30000,
name: '进阶版',
key: 'PAYADVANCED',
o: 0.16,
oName: '0.16元/单',
orders: 187500
},
{
id: 3,
cny: 'CNY',
price: 9800,
p: 9800,
name: '尊享版',
key: 'PAYXVCVT',
month: 1,
oName: '9800元/月',
m: '按月收费'
},
{
id: 4,
cny: 'CNY',
price: 29800,
p: 29800,
name: '至尊版',
key: 'PAYULTIMATE',
month: 1,
oName: '29800元/月',
m: '按月收费'
}
]
}
},
components: {
UploadImages
},
props: {
editForm: {
type: Object,
default: () => ({})
},
editDialog: {
type: Boolean,
default: false
},
commandTitle: {
type: String,
default: ''
},
erpStatus: {
type: String,
default: ''
},
userLists: {
type: Array,
default: () => []
}
},
computed: {
dialogVisible: {
get() {
return this.editDialog
},
set(val) {
this.$emit('update:editDialog', val)
}
},
titleName() {
return TITLE_MAP[this.commandTitle] || ''
},
// 开通ERP|编辑时 + ERP服务是已开通、开通失败和已关闭时,全部必填,出现ERP信息表单
// 新增客户|编辑 + ERP服务是未开通时,只校验"业务对接人"必填,不出现ERP信息表单
isFullValidate() {
return (
this.commandTitle === 'openErp' ||
(this.erpStatus !== '未开通' && this.commandTitle !== 'add')
)
},
editRules() {
const requiredRule = (message, trigger = 'blur') => [
{ required: true, message, trigger }
]
// 仅"业务对接人"必填
const baseRules = {
contactUserName: requiredRule('请输入业务对接人')
}
// 其他情况:维持原有全部必填
if (this.isFullValidate) {
return {
companyName: requiredRule('请输入公司名称'),
officeAddress: requiredRule('请输入办公地址'),
contactUserName: requiredRule('请输入业务对接人'),
contactUserTitle: requiredRule('请输入对接人职位'),
contactUserPhone: requiredRule('请输入对接人电话'),
contactUserEmail: requiredRule('请输入对接人邮箱'),
businessLicenseUri: requiredRule('请上传营业执照', 'change'),
domain: requiredRule('请输入域名'),
packages: requiredRule('请输入套餐'),
charges: requiredRule('请输入资费')
}
}
return baseRules
},
// 把 editForm.businessLicenseUri(逗号分隔字符串) 与上传组件所需的数组双向桥接
businessLicenseFileList: {
get() {
const uri = this.editForm.businessLicenseUri
if (!uri) return []
return String(uri)
.split(',')
.map((u) => u.trim())
.filter((u) => !!u)
.map((url, index) => ({
imagePath: url,
sort: index + 1,
type: ''
}))
},
set(val) {
const list = Array.isArray(val) ? val : []
const imagePath = list.map((ee) => ee.imagePath || '').filter(Boolean).join(',')
this.$set(this.editForm, 'businessLicenseUri', imagePath)
}
}
},
methods: {
onDomainChange(e) {
this.$set(this.editForm, 'apiName', e)
this.$set(this.editForm, 'databaseName', 'saas_' + e)
},
open() {
// 弹窗打开时,若套餐为空则默认为基础版;编辑场景下保留已有套餐
if (!this.editForm.packages) {
this.$set(this.editForm, 'packages', 'PAYBASE')
}
},
changeCharges(v) {
if (!v) return
const charges = this.paymentPackages.find((item) => item.key === v)
if (charges) {
this.$set(this.editForm, 'charges', charges.oName)
}
},
// 上传/重排后同步到 editForm.businessLicenseUri(v-model change 已自动同步,这里再做一次防御性赋值)
selectImg(file) {
const list = Array.isArray(file) ? file : []
const imagePath = list.map((ee) => ee.imagePath || '').filter(Boolean).join(',')
this.$set(this.editForm, 'businessLicenseUri', imagePath)
},
// 删除图片:上传组件 splice 后不会 emit change,从组件读取当前 fileList 并手动同步
onImgRemove() {
this.$nextTick(() => {
const ref = this.$refs.uploadRef
const fileList = (ref && Array.isArray(ref.fileList)) ? ref.fileList : []
const imagePath = fileList.map((ee) => ee.imagePath || '').filter(Boolean).join(',')
this.$set(this.editForm, 'businessLicenseUri', imagePath)
})
},
handleClose() {
this.dialogVisible = false
},
async onSubmit() {
try {
if (!(await this.$refs.editFormRef.validate())) return
} catch {
return
}
this.$emit('onSubmit', this.editForm)
},
clearForm() {
this.$refs.editFormRef?.clearValidate()
}
}
}
</script>
<style scoped lang="scss">
// 表单整体
.el-form {
:deep(.el-form-item__content) {
align-items: baseline !important;
}
:deep(.el-form-item) {
margin-bottom: 16px;
.el-form-item__label {
font-size: 13px;
color: #606266;
font-weight: 500;
}
// 禁用输入框视觉处理
:deep(.el-input.is-disabled .el-input__wrapper),
:deep(.el-textarea.is-disabled .el-textarea__inner),
:deep(.el-date-editor.is-disabled) {
background-color: #f5f7fa !important;
box-shadow: 0 0 0 1px #e4e7ed inset !important;
}
}
}
.form-center {
background: #fff;
border-radius: 8px;
padding: 18px 20px;
margin-bottom: 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
border: 1px solid #ebeef5;
transition: box-shadow 0.2s ease;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
&:last-child {
margin-bottom: 0;
}
h3 {
margin: 0 0 14px;
padding-left: 10px;
font-size: 14px;
font-weight: 600;
color: #303133;
line-height: 1.4;
border-left: 3px solid #409eff;
}
.grid-from {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px 20px;
}
}
</style>
<template>
<div class="wraper">
<div class="page_right">
<el-form
:model="searchForm"
size="mini"
v-enter-submit="getTableList"
:inline="true"
ref="form"
class="search_form">
<el-form-item label="公司名称">
<el-input
style="width: 130px"
v-model="searchForm.companyName"
placeholder="请输入公司名称"
clearable></el-input>
</el-form-item>
<el-form-item label="业务对接人">
<el-input
style="width: 130px"
v-model="searchForm.contactUserName"
placeholder="请输入业务对接人"
clearable></el-input>
</el-form-item>
<el-form-item label="ERP服务">
<el-select
v-model="searchForm.erpServiceStatus"
style="width: 130px"
value-key=""
placeholder="请选择服务状态"
clearable
filterable>
<el-option
v-for="item in serviceStatusLists"
:key="item.code"
:label="item.remark"
:value="item.code">
<span :style="{ color: item.color, fontWeight: 600 }">
{{ item.remark }}
</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="域名">
<el-input
style="width: 130px"
v-model="searchForm.domain"
placeholder="请输入域名"
clearable></el-input>
</el-form-item>
<el-form-item label="客户编码">
<el-input
style="width: 130px"
v-model="searchForm.erpUserMark"
placeholder="请输入客户编码"
clearable></el-input>
</el-form-item>
<el-form-item label>
<el-button @click="getTableList()" type="primary" size="mini">
查询
</el-button>
</el-form-item>
<el-form-item>
<el-button type="success" size="mini" @click="addCustomerInfo">
新增客户
</el-button>
</el-form-item>
</el-form>
<div class="table-wrap" v-loading="loading">
<table-view
:tableColumns="tableColumns"
:sourceData="sourceData"
@selectionChange="handleSelectionChange"
:selection="true"></table-view>
</div>
<div class="pagination">
<el-pagination
layout="sizes, total, prev, pager, next, jumper"
background
:total="centerPageOptions.total"
:page-size="centerPageOptions.pageSize"
:current-page="centerPageOptions.currentPage"
@size-change="sizeChange"
@current-change="onCurrentChange"></el-pagination>
</div>
</div>
<editCustmerDialog
:editDialog.sync="editDialog"
@onSubmit="onSubmit"
:editForm="editForm"
:userLists="userLists"
:commandTitle="commandTitle"
:erpStatus="erpStatus"
ref="customerRef" />
<el-dialog
title="操作日志"
:close-on-click-modal="false"
:visible.sync="logVisible"
width="700px">
<ul style="color: #333; font-size: 14px; height: 600px; overflow: auto">
<li v-for="(item, index) in logList" :key="index">
{{ index + 1 }}{{ item.createTime + ':' + item.description }}
</li>
</ul>
</el-dialog>
<el-dialog
title="赠送订单"
:visible.sync="giftOrderVisible"
:close-on-click-modal="false"
width="400px">
<el-form
:model="giftOrderForm"
ref="giftOrderForm"
label-width="100px"
label-position="right">
<el-form-item
label="订单数量"
prop="quantity"
:rules="[
{ required: true, message: '请输入订单数量' },
{ pattern: /^(0|-[1-9]\d*|[1-9]\d*)$/, message: '请输入整数' }
]">
<el-input
style="width: 200px"
v-model="giftOrderForm.quantity"
size="small"
clearable
placeholder="请输入订单数量" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
class="remark_input"
style="width: 200px"
v-model="giftOrderForm.remark"
size="small"
type="textarea"
rows="4"
maxlength="100"
show-word-limit
placeholder="请输入备注" />
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="giftOrderVisible = false" size="small">
取消
</el-button>
<el-button
@click="submitGiftOrder"
type="primary"
size="small"
:loading="submitLoading">
确定
</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import tableView from '@/common/components/base/tableView.vue'
import pagination from '../../mixins/pagination'
import editCustmerDialog from './editCustmerDialog.vue'
import { get, post, remove } from '@/common/api/axios'
import { mapState } from 'vuex'
import { addGiftOrders } from '@/common/api/manage/index'
export default {
name: 'user_jm_management',
mixins: [pagination],
components: {
tableView,
editCustmerDialog
},
data() {
return {
submitLoading: false,
userLists: [],
giftOrderForm: {
quantity: '',
remark: ''
},
giftRules: {
orderQuantity: [
{ required: true, message: '请输入订单数量', trigger: 'blur' }
]
},
logVisible: false,
giftOrderVisible: false,
logList: [],
searchForm: {
companyName: '',
contactUserName: '',
erpServiceStatus: '',
domain: '',
erpUserMark: ''
},
serviceStatusLists: [],
sourceData: [],
centerPageOptions: {
pageSize: 20,
currentPage: 1,
total: 0
},
selections: [],
editDialog: false,
commandTitle: 'add',
editForm: {
companyName: '',
industry: '',
registeredAddress: '',
officeAddress: '',
contactUserName: '',
contactUserTitle: '',
contactUserPhone: '',
contactUserEmail: '',
operationUserId: '',
operationUserName: '',
remark: '',
businessLicenseUri: '',
domain: '',
packages: '',
charges: ''
},
loading: false,
erpStatus: '',
currentRow: {},
baseDomain: '.jomalls.com',
cloneEditForm: {}
}
},
mounted() {
this.cloneEditForm = JSON.parse(JSON.stringify(this.editForm))
this.getTableList()
this.getStatusList()
this.getUserList()
},
computed: {
...mapState(['userInfo']),
tableColumns() {
const statusMap = this.serviceStatusLists.reduce((map, item) => {
map[item.remark] = { color: item.color }
return map
}, {})
return [
{
label: '公司名称',
key: 'companyName',
width: 130,
align: 'center'
},
{
label: '所属行业',
key: 'industry',
width: 130,
align: 'center'
},
{
label: '注册地址',
key: 'registeredAddress',
width: 130,
align: 'center'
},
{
label: '办公地址',
key: 'officeAddress',
width: 130,
align: 'center'
},
{
label: '业务对接人',
key: 'contactUserName',
width: 130,
align: 'center'
},
{
label: '对接人职位',
key: 'contactUserTitle',
width: 130,
align: 'center'
},
{
label: '对接人电话',
key: 'contactUserPhone',
width: 130,
align: 'center'
},
{
label: '对接人邮箱',
key: 'contactUserEmail',
width: 160,
align: 'center'
},
{
label: '营业执照',
key: 'businessLicenseUri',
width: 100,
align: 'center',
render: (row) => {
if (!row.businessLicenseUri) return <span>-</span>
return (
<el-image
src={row.businessLicenseUri}
style="width:30px;height:30px"
fit="cover"
preview-src-list={[row.businessLicenseUri]}
preview-teleported
/>
)
}
},
{
label: 'ERP服务',
key: 'erpServiceStatusName',
width: 100,
align: 'center',
render: (item) => {
const status = statusMap[item.erpServiceStatusName]
if (!status) return <span>未知</span>
return (
<el-tag
size="small"
style={{
color: status.color,
fontWeight: 600,
backgroundColor: status.color + '20',
borderColor: status.color + '50'
}}>
{item.erpServiceStatusName}
</el-tag>
)
}
},
{
label: '域名',
key: 'domain',
width: 180,
align: 'center',
render: (row) => {
if (!row.domain) return <span>-</span>
// const fullDomain = row.domain + '.jomalls.com'
const linkUrl = `https://${row.domain}`
return (
<a href={linkUrl} target="_blank" style="color: #409EFF">
{row.domain}
</a>
)
}
},
{
label: '客户编码',
key: 'erpUserMark',
width: 100,
align: 'center'
},
{
label: '套餐',
key: 'packages',
width: 130,
align: 'center'
},
{
label: '资费',
key: 'charges',
width: 130,
align: 'center'
},
{
label: '备注',
key: 'remark',
width: 130,
align: 'center'
},
{
label: '客户运营',
key: 'operationUserName',
width: 130,
align: 'center'
},
{
label: '创建时间',
key: 'createTime',
width: 130
},
{
label: '操作',
fixed: 'right',
align: 'center',
width: 120,
render: (item) => {
return (
<el-dropdown
trigger="click"
onCommand={(cmd) => this.handleOperation(cmd, item)}>
<span class="el-dropdown-link">
相关操作<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
{['未开通', '开通失败'].includes(
item.erpServiceStatusName
) && (
<el-dropdown-item command="openErp">
<el-button
type="text"
style={{ color: '#2776ce', fontWeight: 'bold' }}>
开通ERP
</el-button>
</el-dropdown-item>
)}
{item.erpServiceStatusName === '已开通' && (
<el-dropdown-item command="closeErp">
<el-button
type="text"
style={{ color: '#909399', fontWeight: 'bold' }}>
关闭ERP
</el-button>
</el-dropdown-item>
)}
{item.erpServiceStatusName === '已开通' && (
<el-dropdown-item command="giftOrder">
<el-button
type="text"
style={{ color: '#9B59B6', fontWeight: 'bold' }}>
赠送订单
</el-button>
</el-dropdown-item>
)}
<el-dropdown-item command="edit">
<el-button
type="text"
disabled={item.erpServiceStatusName === '开通中'}
style={{ color: '#E6A23C', fontWeight: 'bold' }}>
编辑
</el-button>
</el-dropdown-item>
{['已关闭', '开通失败'].includes(
item.erpServiceStatusName
) && (
<el-dropdown-item command="delete">
<el-button
type="text"
style={{ color: '#F56C6C', fontWeight: 'bold' }}>
删除
</el-button>
</el-dropdown-item>
)}
<el-dropdown-item command="log">
<el-button
type="text"
style={{ color: '#00B4D8', fontWeight: 'bold' }}>
操作日志
</el-button>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
)
}
}
]
}
},
methods: {
getUserList() {
get('platform/user/list')
.then((res) => {
if (res.code !== 200) return
this.userLists = res.data || []
})
.catch((error) => {
console.error('获取用户列表失败:', error)
})
},
getStatusList() {
get('customer/info/serviceStatusList')
.then((res) => {
if (res.code !== 200) return
this.serviceStatusLists = res.data || []
})
.catch((error) => {
console.error('获取状态列表失败:', error)
})
},
handleOperation(command, row) {
this.commandTitle = command
this.erpStatus = row.erpServiceStatusName
switch (command) {
case 'openErp':
this.openCustomerInfo(row)
break
case 'edit':
this.updateCustomerInfo(row)
break
case 'closeErp':
this.closeCustomerInfo(row)
break
case 'giftOrder':
this.giftOrderInfo(row)
break
case 'delete':
this.deleteSection(row)
break
case 'log':
this.logInfo(row)
break
}
},
sizeChange(pageSize) {
this.centerPageOptions.pageSize = pageSize
this.getTableList()
},
onCurrentChange(currentPage) {
this.centerPageOptions.currentPage = currentPage
this.getTableList()
},
async getTableList() {
this.loading = true
const { pageSize, currentPage } = this.centerPageOptions
post('customer/info/pageList', {
pageSize,
currentPage,
...this.searchForm
})
.then((res) => {
if (res.code !== 200) return
this.sourceData = res.data.records
this.centerPageOptions.total = res.data.total
})
.finally(() => {
this.loading = false
})
},
handleSelectionChange(selection) {
this.selections = selection
},
addCustomerInfo() {
this.commandTitle = 'add'
this.editForm.operationUserId = this.userInfo?.id || ''
this.editForm = JSON.parse(JSON.stringify(this.cloneEditForm))
this.editDialog = true
this.$nextTick(() => {
this.$refs.customerRef && this.$refs.customerRef.clearForm()
})
},
updateCustomerInfo(v) {
this.getListInfo(v.id)
.then((res) => {
if (res.code === 200) {
const parts = /^(.*)(\.(([^.]+)\.([^.]+)))$/.exec(
res.data?.customerInfo.domain
)
if (parts) {
const name = parts[1]
const tld = '.' + parts[3]
res.data.customerInfo.domain = name
this.baseDomain = tld
}
this.editForm = res.data?.customerInfo
this.editDialog = true
this.$nextTick(() => {
this.$refs.customerRef && this.$refs.customerRef.clearForm()
})
}
})
.catch((err) => {
console.error(err)
this.$message.error('加载详情失败')
})
},
async onSubmit(editForm) {
try {
// 匹配操作人员姓名
const targetUser = this.userLists.find(
(item) => item.id === editForm.operationUserId
)
editForm.operationUserName = targetUser?.realName || ''
// 拼接完整域名
const fullDomain = editForm.domain + this.baseDomain
let url = ''
const params = { ...editForm }
const commandMap = {
add: () => {
url = 'customer/info/add'
},
edit: () => {
url = 'customer/info/update'
params.domain = fullDomain
},
openErp: () => {
url =
this.erpStatus === '未开通'
? 'customer/info/openService'
: 'customer/info/reOpenService'
params.serviceType = 1
params.customerId = editForm.id
params.domain = fullDomain
}
}
const handler = commandMap[this.commandTitle]
if (!handler) {
this.$message.warning('未知操作类型')
return
}
handler()
const res = await post(url, params)
if (res.code === 200) {
this.$message.success('操作成功!')
this.editDialog = false
this.getTableList()
}
} catch (err) {
// 捕获网络、接口异常
console.error('提交接口异常:', err)
this.$message.error('网络异常,请稍后重试')
}
},
async getListInfo(id) {
return await get(`customer/info/get/${id}`)
},
openCustomerInfo(v) {
const DEFAULT_FORM = {
domain: '',
packages: 'PAYBASE',
charges: '0.2元/单'
}
this.getListInfo(v.id)
.then((res) => {
const parts = /^(.*)(\.(([^.]+)\.([^.]+)))$/.exec(
res.data?.customerInfo.domain
)
if (parts) {
const name = parts[1]
const tld = '.' + parts[3]
res.data.customerInfo.domain = name
this.baseDomain = tld
}
const customerData = res.data?.customerInfo
this.editForm = {
...DEFAULT_FORM,
...customerData
}
this.editDialog = true
this.$refs.customerRef && this.$refs.customerRef.clearForm()
})
.catch((err) => {
console.error(err)
this.$message.error('加载详情失败')
})
},
closeCustomerInfo(v) {
this.$confirm('确定对当前客户进行关闭ERP操作?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await post('customer/info/closeService', {
customerId: v.id,
serviceType: 1
})
if (res.code !== 200) return
this.$message.success('关闭ERP成功!')
this.getTableList()
} catch (e) {
console.error(e)
}
})
},
giftOrderInfo(v) {
this.currentRow = v
this.giftOrderVisible = true
this.giftOrderForm = {
quantity: '',
remark: ''
}
this.$nextTick(() => {
this.$refs.giftOrderForm?.clearValidate()
})
},
async submitGiftOrder() {
if (this.submitLoading) return
this.$refs.giftOrderForm.validate(async (valid) => {
if (!valid) return
// 开启加载锁
this.submitLoading = true
try {
const res = await addGiftOrders({
apiName: this.currentRow.erpApiName,
giftOrderQuantity: Number(this.giftOrderForm.quantity),
remark: this.giftOrderForm.remark
})
if (res.code === 200) {
this.$message.success(res.message)
this.getTableList()
this.giftOrderVisible = false
}
} catch (err) {
this.$message.error('提交失败,请重试')
} finally {
// 无论成功失败,释放锁
this.submitLoading = false
}
})
},
deleteSection(v) {
this.$confirm('确定删除选中的信息?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await remove(`customer/info/delete/${v.id}`)
if (res.code !== 200) return
this.$message.success('删除成功!')
this.getTableList()
} catch (e) {
console.error(e)
}
})
},
async logInfo(v) {
try {
const res = await get(`customer/info/operationLog/${v.id}`)
if (res.code !== 200) return
this.logList = res.data || []
this.logVisible = true
} catch (e) {
console.error(e)
}
}
}
}
</script>
<style lang="scss" scoped>
.wraper {
height: 100%;
display: flex;
overflow: hidden;
}
.page_right {
flex: 1;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 5px;
overflow: hidden;
.el-dropdown-link {
cursor: pointer;
color: #409eff;
font-weight: bold;
}
.search_form {
margin-top: 5px;
margin-left: 10px;
}
.search_form > .el-form-item {
margin-bottom: 5px;
}
.table-wrap {
background: #fff;
flex: 1;
overflow: hidden;
width: 100%;
padding: 10px;
}
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment