Commit fc86c70c by 李宁

1

1 parent da7b0e06
Showing 43 changed files with 3232 additions and 2191 deletions
import request from '../../request' import request from '../../request'
/** /**
* * 查询账号列表
*/ */
export function queryAllMissionRecord() { export function queryAccountList(data) {
return request({ return request({
url: 'hallserver/companyJob/queryAllCompanyJob', url: '/compass/api/system/account/page',
data: {} data,
})
}
/**
* 查询角色列表
*/
export function queryRoleList(data) {
return request({
url: '/compass/api/common/enums/account-roles',
method: 'GET'
})
}
/**
* 删除账号
*/
export function deleteRole(data) {
return request({
url: '/compass/api/system/account/delete',
data,
})
}
/**
* 添加/更新账号
*/
export function addAndUpdateRole(data) {
return request({
url: '/compass/api/system/account/create' + (data.id?'/update':''),
data,
}) })
} }
\ No newline at end of file \ No newline at end of file
...@@ -11,6 +11,76 @@ export function queryAllBusi(data) { ...@@ -11,6 +11,76 @@ export function queryAllBusi(data) {
} }
/** /**
* 全部商机-商机审核
*/
export function audioBusi(data) {
return request({
url: '/compass/api/opportunity/audit',
data,
})
}
/**
* 全部商机-商机详情
*/
export function queryBusiDetail(data) {
return request({
url: '/compass/api/opportunity/detail',
data,
})
}
/**
* 全部商机-商机跟进记录查询
*/
export function queryBusiFollowList(data) {
return request({
url: '/compass/api/opportunity/follow/page',
data,
})
}
/**
* 全部商机-关闭商机
*/
export function closeBusi(data) {
return request({
url: '/compass/api/opportunity/close',
data,
})
}
/**
* 全部商机-标记成单
*/
export function dealBusi(data) {
return request({
url: '/compass/api/opportunity/deal',
data,
})
}
/**
* 全部商机-分配商机
*/
export function reassignBusi(data) {
return request({
url: '/compass/api/opportunity/reassign',
data,
})
}
/**
* 全部商机-记录管理员备注
*/
export function updateRemark(data) {
return request({
url: '/compass/api/opportunity/admin-remark',
data,
})
}
/**
* 全部商机-数据统计 * 全部商机-数据统计
*/ */
export function queryAllBusiStatistics(data) { export function queryAllBusiStatistics(data) {
...@@ -21,11 +91,81 @@ export function queryAllBusiStatistics(data) { ...@@ -21,11 +91,81 @@ export function queryAllBusiStatistics(data) {
} }
/** /**
* 全部商机-查询商机标签列表 * 全部商机-查询所有商机标签列表
*/ */
export function queryBusiLabel(data) { export function queryBusiLabelList(data) {
return request({ return request({
url: '/compass/api/opportunity/tag/page', url: '/compass/api/opportunity/tag/page',
data, data,
}) })
}
/**
* 商机标签的开始和关闭
*/
export function updateBusiLabelStatus(data) {
return request({
url: '/compass/api/opportunity/tag/status',
data,
})
}
/**
* 商机标签的删除
*/
export function deleteBusiLabel(data) {
return request({
url: '/compass/api/opportunity/tag/delete',
data,
})
}
/**
* 商机标签的创建和更新
*/
export function createAndUpdateTag(data) {
return request({
url: '/compass/api/opportunity/tag' + (data.id?'/update':''),
data,
})
}
/**
* 商机关闭原因列表查询
*/
export function queryBusiCloseReansonList(data) {
return request({
url: '/compass/api/opportunity/close-reason/page',
data,
})
}
/**
* 商机关闭原因添加和更新
*/
export function busiCloseReasonUpdate(data) {
return request({
url: '/compass/api/opportunity/close-reason'+(data.id?'/update':''),
data,
})
}
/**
* 商机关闭原因删除
*/
export function busiCloseReasonDel(data) {
return request({
url: '/compass/api/opportunity/close-reason/delete',
data,
})
}
/**
* 全部商机-新增商机
*/
export function createBusi(data) {
return request({
url: '/compass/api/opportunity/create',
data,
})
} }
\ No newline at end of file \ No newline at end of file
import request from '../../request' import request from '../../request'
/** /**
* 查询区域列表 * 根据级别查询区域列表
* 1-省级,2-市级,3-区级,4-网格级 * 1-省级,2-市级,3-区级,4-网格级
*/ */
export function queryAllArea(data) { export function queryLevelAllArea(data) {
return request({ return request({
url: '/compass/api/common/areas/level?areaLevel='+data.areaLevel+'&parentAreaCode='+data.parentAreaCode, url: '/compass/api/common/areas/level?areaLevel='+data.areaLevel+'&parentAreaCode='+data.parentAreaCode,
method: 'GET' method: 'GET'
......
import request from '../../request' import request from '../../request'
/** /**
* 资金管理-发票管理-开票订单查询 * 网格列表查询
* @returns {AxiosPromise} * @returns {AxiosPromise}
*/ */
export function queryTicketOrderList() { export function queryAllGridList(data) {
return request({ return request({
url: 'hallserver/v1/invoiceApply/queryCompanyOrder', url: '/compass/api/grid/list',
data: {} data,
})
}
/**
* 获取该账号下可选的网格列表
*/
export function queryGridList(data) {
return request({
url: '/compass/api/grid/options',
data,
}) })
} }
\ No newline at end of file \ No newline at end of file
import request from '../../request' import request from '../../request'
/**
* 查询人员列表
*/
export function queryAllPerson(data) {
return request({
url: '/compass/api/personnel/list',
data,
})
}
/**
* 添加人员
*/
export function addNewPerson(data) {
return request({
url: '/compass/api/personnel/create',
data,
})
}
/** /**
* * 删除人员
*/ */
export function companyInfoQuery(companyId) { export function deletePerson(data) {
return request({ return request({
url: 'hallserver/hallCompanyInfo/getCompanyInfo', url: '/compass/api/personnel/delete',
data: { data,
'companyId': companyId
}
}) })
} }
/**
* 批量导入工维人员
*/
export function importGwPerson(data) {
return request({
url: '/compass/api/personnel/import-maintenance-preview',
data,
})
}
/**
* 批量导入营销人员
*/
export function importYxPerson(data) {
return request({
url: '/compass/api/marketing/import-preview',
data,
})
}
...@@ -5,7 +5,7 @@ import request from './request' ...@@ -5,7 +5,7 @@ import request from './request'
*/ */
export function logout() { export function logout() {
return request({ return request({
url: 'hallserver/admin/logout', url: '/compass/api/auth/logout',
data: {} data: {}
}) })
} }
...@@ -23,24 +23,36 @@ export function login(data) { ...@@ -23,24 +23,36 @@ export function login(data) {
} }
/** /**
* 获取登录验证码 * 手机账号登录
*/
export function pohoneLogin(data) {
return request({
url: '/compass/api/auth/phone-login',
data,
})
}
/**
* 获取图形验证码
* @param loginName * @param loginName
* @param password * @param password
*/ */
export function loginCode(data) { export function getImgCode(data) {
return request({ return request({
url: 'hallserver/admin/loginCode', url: '/compass/api/auth/getCaptCha',
method: 'GET',
data, data,
}) })
} }
/** /**
* 获取路由权限 * 获取短信验证码
* @returns {AxiosPromise} * @param loginName
* @param password
*/ */
export function getMenus() { export function getTelCode(data) {
return request({ return request({
url: 'hallserver/admin/function', url: '/compass/api/auth/send-sms',
data: {} data,
}) })
} }
...@@ -51,7 +51,7 @@ let catchFun = function (msg) { ...@@ -51,7 +51,7 @@ let catchFun = function (msg) {
service.interceptors.response.use( service.interceptors.response.use(
(response) => { (response) => {
console.log(response); console.log(response);
if (response.status === 200) { if (response.status == 200) {
if (response.data.code == "401") { if (response.data.code == "401") {
//登陆失效,重新登陆 //登陆失效,重新登陆
catchFun("账户状态异常"); catchFun("账户状态异常");
...@@ -100,9 +100,9 @@ service.interceptors.response.use( ...@@ -100,9 +100,9 @@ service.interceptors.response.use(
}; };
} }
} }
} else if (response.status === 302) { } else if (response.status == 302) {
catchFun("登陆失效,请重新登陆"); catchFun("登陆失效,请重新登陆");
} else if (response.status === 401 || response.status == 403) { } else if (response.status == 401 || response.status == 403) {
catchFun("账户状态异常"); catchFun("账户状态异常");
} else { } else {
if (sessionStorage.notFirstIn) { if (sessionStorage.notFirstIn) {
......
...@@ -212,9 +212,9 @@ export default { ...@@ -212,9 +212,9 @@ export default {
* @returns * @returns
*/ */
hideIdNumber: function(idCard) { hideIdNumber: function(idCard) {
if (idCard.length === 18) { if (idCard.length == 18) {
return idCard.substr(0, 6) + '********' + idCard.substr(-4, 4) return idCard.substr(0, 6) + '********' + idCard.substr(-4, 4)
} else if (idCard.length === 15) { } else if (idCard.length == 15) {
return idCard.substr(0, 6) + '******' + idCard.substr(-3, 3) return idCard.substr(0, 6) + '******' + idCard.substr(-3, 3)
} else { } else {
return idCard return idCard
......
...@@ -2,45 +2,35 @@ ...@@ -2,45 +2,35 @@
<div class="account-management"> <div class="account-management">
<div class="toolbar"> <div class="toolbar">
<p class="description">管理系统登录账号</p> <p class="description">管理系统登录账号</p>
<el-button @click="isAddDialogOpen = true" type="primary" size="small"> <el-button @click="addAccount" type="primary" size="small">
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
添加账号 添加账号
</el-button> </el-button>
</div> </div>
<div class="account-table"> <div class="account-table">
<el-table :data="accounts" style="width: 100%" border> <el-table :data="tableData" style="width: 100%" border>
<el-table-column prop="username" label="用户名" width="120"></el-table-column> <el-table-column prop="account" label="用户名" width="180"></el-table-column>
<el-table-column prop="name" label="姓名" width="100"></el-table-column> <el-table-column prop="accountName" label="姓名" width="150"></el-table-column>
<el-table-column prop="role" label="角色" width="120"> <el-table-column prop="roleName" label="角色" width="120"></el-table-column>
<template slot-scope="scope"> <el-table-column prop="areaName" label="所属区域" width="220"></el-table-column>
<el-tag size="mini">{{ scope.row.role }}</el-tag> <el-table-column prop="contactPhone" label="手机号" width="220"></el-table-column>
</template> <el-table-column prop="email" label="邮箱" width="250"></el-table-column>
</el-table-column>
<el-table-column prop="region" label="所属区域" width="220"></el-table-column>
<el-table-column label="联系方式" width="150">
<template slot-scope="scope">
<span>{{ scope.row.phone || scope.row.email || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="状态" width="80"> <el-table-column label="状态" width="80">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> <el-tag :type="scope.row.status == '1' ? 'success' : 'info'">
{{ scope.row.status === 'active' ? '启用' : '禁用' }} {{ scope.row.status == '1' ? '启用' : '禁用' }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="lastLogin" label="最近登录" width="220"> <el-table-column prop="lastLoginTime" label="最近登录" width="220" :formatter="timeRender"></el-table-column>
<template slot-scope="scope"> <el-table-column label="操作" min-width="150">
<span>{{ scope.row.lastLogin || '从未登录' }}</span>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button <el-button
type="text" type="text"
size="small" size="small"
@click="editAccount(scope.row)" @click="editAccount(scope.row)"
disabled
> >
编辑 编辑
</el-button> </el-button>
...@@ -56,28 +46,41 @@ ...@@ -56,28 +46,41 @@
</el-table> </el-table>
</div> </div>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageStore.currentPage"
:page-sizes="[20, 50, 100]"
:page-size="pageStore.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pageStore.total"
>
</el-pagination>
</div>
<!-- 添加/编辑账号对话框 --> <!-- 添加/编辑账号对话框 -->
<el-dialog <el-dialog
:title="editingAccount ? '编辑账号' : '添加账号'" :title="editingAccount ? '编辑账号' : '添加账号'"
:visible.sync="isAddDialogOpen" :visible.sync="isAddDialogOpen"
width="800px" width="800px">
>
<el-form :model="accountForm" :rules="accountRules" ref="accountForm" label-width="100px"> <el-form :model="accountForm" :rules="accountRules" ref="accountForm" label-width="100px">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="用户名" prop="username"> <el-form-item label="用户名" prop="account">
<el-input <el-input
v-model="accountForm.username" v-model="accountForm.account"
placeholder="请输入用户名" placeholder="请输入用户名"
:disabled="!!editingAccount" :disabled="!!editingAccount"
></el-input> ></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="姓名" prop="name"> <el-form-item label="密码" prop="password">
<el-input <el-input
v-model="accountForm.name" v-model="accountForm.password"
placeholder="请输入姓名" placeholder="请输入密码"
></el-input> ></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
...@@ -85,20 +88,17 @@ ...@@ -85,20 +88,17 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="角色" prop="role"> <el-form-item label="姓名" prop="accountName">
<el-select v-model="accountForm.role" style="width: 100%;"> <el-input
<el-option label="网格管理员" value="网格管理员"></el-option> v-model="accountForm.accountName"
<el-option label="区县管理员" value="区县管理员"></el-option> placeholder="请输入姓名"
<el-option label="地市管理员" value="地市管理员"></el-option> ></el-input>
<el-option label="省级管理员" value="省级管理员"></el-option>
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="状态" prop="region"> <el-form-item label="角色" prop="roleCode">
<el-select v-model="accountForm.status"> <el-select v-model="accountForm.roleCode" style="width: 100%;">
<el-option label="启用" value="active"></el-option> <el-option v-for="item in roleList" :label="item.description" :value="item.key" :disabled="ifRoleCho(item.key)"></el-option>
<el-option label="禁用" value="inactive"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
...@@ -106,13 +106,23 @@ ...@@ -106,13 +106,23 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="手机号" prop="phone"> <el-form-item label="状态" prop="status">
<el-select v-model="accountForm.status">
<el-option label="启用" value="1"></el-option>
<el-option label="禁用" value="0"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="手机号" prop="contactPhone">
<el-input <el-input
v-model="accountForm.phone" v-model="accountForm.contactPhone"
placeholder="请输入手机号" placeholder="请输入手机号"
></el-input> ></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="邮箱" prop="email"> <el-form-item label="邮箱" prop="email">
<el-input <el-input
...@@ -124,9 +134,9 @@ ...@@ -124,9 +134,9 @@
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20" v-if="accountForm.roleCode">
<el-col :span="30"> <el-col :span="30">
<el-form-item label="所属区域" prop="region"> <el-form-item label="所属区域" prop="city">
<template v-if="accountForm.cityArr.length>0"> <template v-if="accountForm.cityArr.length>0">
<el-select <el-select
v-model="accountForm.city" v-model="accountForm.city"
...@@ -137,13 +147,13 @@ ...@@ -137,13 +147,13 @@
clearable> clearable>
<el-option <el-option
v-for="item in accountForm.cityArr" v-for="item in accountForm.cityArr"
:key="item.value" :key="item.areaCode"
:label="item.name" :label="item.areaName"
:value="item.value" :value="item.areaCode"
></el-option> ></el-option>
</el-select> </el-select>
</template> </template>
<template v-if="accountForm.countyArr.length>0"> <template v-if="accountForm.countyArr.length>0 && accountForm.roleCode!='CITY'">
<el-select <el-select
v-model="accountForm.county" v-model="accountForm.county"
placeholder="选择区县" placeholder="选择区县"
...@@ -153,13 +163,13 @@ ...@@ -153,13 +163,13 @@
clearable> clearable>
<el-option <el-option
v-for="item in accountForm.countyArr" v-for="item in accountForm.countyArr"
:key="item.value" :key="item.areaCode"
:label="item.name" :label="item.areaName"
:value="item.value" :value="item.areaCode"
></el-option> ></el-option>
</el-select> </el-select>
</template> </template>
<template v-if="accountForm.gridArr.length>0"> <template v-if="accountForm.gridArr.length>0 && accountForm.roleCode!='CITY' && accountForm.roleCode!='COUNTY'">
<el-select <el-select
v-model="accountForm.grid" v-model="accountForm.grid"
placeholder="选择网格" placeholder="选择网格"
...@@ -167,9 +177,9 @@ ...@@ -167,9 +177,9 @@
clearable> clearable>
<el-option <el-option
v-for="item in accountForm.gridArr" v-for="item in accountForm.gridArr"
:key="item.value" :key="item.gridCode"
:label="item.name" :label="item.gridName"
:value="item.value" :value="item.gridCode"
></el-option> ></el-option>
</el-select> </el-select>
</template> </template>
...@@ -187,85 +197,152 @@ ...@@ -187,85 +197,152 @@
</template> </template>
<script> <script>
// 模拟账号数据
const mockAccounts = [
{ id: 'ACC001', username: 'grid001', name: '李网格长', role: '网格管理员', region: '南京市玄武区A网格', phone: '13543210987', status: 'active', lastLogin: '2025-09-28 15:30:00', createTime: '2025-09-01 09:00:00' },
{ id: 'ACC002', username: 'county001', name: '王区长', role: '区县管理员', region: '南京市玄武区', phone: '13432109876', status: 'active', lastLogin: '2025-09-28 14:20:00', createTime: '2025-09-01 09:00:00' },
{ id: 'ACC003', username: 'city001', name: '张市长', role: '地市管理员', region: '南京市', phone: '13321098765', status: 'active', lastLogin: '2025-09-28 10:15:00', createTime: '2025-09-01 09:00:00' }
]
export default { export default {
name: 'AccountManagement', name: 'AccountManagement',
data() { data() {
return { return {
accounts: mockAccounts, tableData: [],
isAddDialogOpen: false, pageStore:{
editingAccount: null, currentPage: 1,
pageSize: 20,
total: 0
},
accountForm: { accountForm: {
username: '', account: '',
name: '', password: '',
role: '网格管理员', accountName: '',
region: '', roleCode: '',
phone: '', status: '',
grid: '',
contactPhone: '',
email: '', email: '',
city: '', city: '',
cityArr: this.addressStoreData, cityArr: [],
county: '', county: '',
countyArr: [], countyArr: [],
grid: '', grid: '',
gridArr: [], gridArr: []
status: 'active'
}, },
getData:{
city: '',
county: '',
grid: '',
},
roleList: [],
accountRules: { accountRules: {
username: [ account: [
{ required: true, message: '请输入用户名', trigger: 'blur' } { required: true, message: '请输入用户名', trigger: 'blur' }
], ],
name: [ password: [
{ required: true, message: '请输入密码', trigger: 'blur' }
],
accountName: [
{ required: true, message: '请输入姓名', trigger: 'blur' } { required: true, message: '请输入姓名', trigger: 'blur' }
], ],
region: [ city: [
{ required: true, message: '请输入所属区域', trigger: 'blur' } { required: true, message: '请输入所属区域', trigger: 'blur' }
] ],
roleCode: [
{ required: true, message: '请选择角色', trigger: 'blur' }
],
status: [
{ required: true, message: '请选择状态', trigger: 'blur' }
],
}, },
getData:{ isAddDialogOpen: false,
city: '', editingAccount: null,
cityName: '',
county: '',
countyName: '',
grid: '',
gridName: '',
},
} }
}, },
created(){ created(){
this.setAddressShow() let pa = JSON.parse(localStorage.getItem('accountInfo'))
this.getData.city = pa.cityCode||''
this.getData.county = pa.countyCode||''
this.getData.grid = pa.gridCode||''
this.queryArea()
this.handleFilter()
this.queryRole()
}, },
methods: { methods: {
ifRoleCho(key){
let gd = this.getData
if(gd.county){
if(key == 'GRID'){
return false
}
return true
}
if(gd.city){
if(key=='GRID' || key=='COUNTY'){
return false
}
return true
}
if(key != 'PROVINCE'){
return false
}
return true
},
queryRole(){
this.apiReq.queryRoleList().then(res=>{
if(res.code == 200){
this.roleList = res.data
}
})
},
timeRender(row,column){
return this.common.detailTime(new Date(row[column.property]).getTime())
},
handleFilter(){
this.pageStore.currentPage = 1
this.queryAccount()
},
queryAccount(){
this.apiReq.queryAccountList({
pageNum: this.pageStore.currentPage,
pageSize: this.pageStore.pageSize
}).then(res=>{
if(res.code == 200){
this.tableData = res.data.records
this.pageStore.total = res.data.total
}else{
}
})
},
queryArea(){
this.apiReq.queryLevelAllArea({
areaLevel: 2,
parentAreaCode: '320000'
}).then(res=>{
if(res.code == 200){
this.accountForm.cityArr = res.data
this.setAddressShow()
}
})
},
setAddressShow(){ setAddressShow(){
let ad = this.accountForm let ad = this.accountForm
let gd = this.getData let gd = this.getData
if(gd.city){ if(gd.city){
ad.city = gd.city ad.city = gd.city
ad.cityArr.forEach(item=>{ this.cityChange(ad.city)
if(item.value == ad.city){
ad.countyArr = item.children
}
})
if(gd.county){ if(gd.county){
ad.county = gd.county ad.county = gd.county
ad.countyArr.forEach(item=>{ this.countyChange(ad.county)
if(item.value == ad.county){
ad.gridArr = item.children
}
})
if(ad.gridArr.length == 1){
gd.grid = 'moren'
}
if(gd.grid){ if(gd.grid){
ad.grid = gd.grid ad.grid = gd.grid
...@@ -279,9 +356,12 @@ export default { ...@@ -279,9 +356,12 @@ export default {
ad.county = '' ad.county = ''
ad.grid = '' ad.grid = ''
ad.cityArr.forEach(item=>{ this.apiReq.queryLevelAllArea({
if(item.value == ad.city){ areaLevel: 3,
ad.countyArr = item.children parentAreaCode: value
}).then(res=>{
if(res.code == 200){
ad.countyArr = res.data
} }
}) })
}, },
...@@ -290,14 +370,55 @@ export default { ...@@ -290,14 +370,55 @@ export default {
ad.county = value ad.county = value
ad.grid = '' ad.grid = ''
ad.countyArr.forEach(item=>{ this.apiReq.queryGridList({
if(item.value == ad.county){ areaCode:value
ad.gridArr = item.children }).then(res=>{
if(res.code == 200){
ad.gridArr = res.data
} }
}) })
}, },
handleSizeChange(size) {
this.pageStore.pageSize = size
this.handleFilter()
},
handleCurrentChange(page) {
this.pageStore.currentPage = page
this.queryAccount()
},
addAccount(){
let ad = this.accountForm
this.editingAccount = null
ad = {
account: '',
password: '',
accountName: '',
roleCode: '',
status: '',
contactPhone: '',
email: ''
}
if(!this.getData.city){
ad.city = ''
ad.county = ''
ad.countyArr = []
ad.grid = ''
ad.gridArr = []
}else if(!this.getData.county){
ad.county = ''
ad.grid = ''
ad.gridArr = []
}else if(!this.getData.grid){
ad.grid = ''
}
this.isAddDialogOpen = true
},
editAccount(account) { editAccount(account) {
debugger
this.editingAccount = account this.editingAccount = account
this.accountForm = { ...account } this.accountForm = { ...account }
this.isAddDialogOpen = true this.isAddDialogOpen = true
...@@ -308,8 +429,21 @@ export default { ...@@ -308,8 +429,21 @@ export default {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.accounts = this.accounts.filter(a => a.id !== id) this.apiReq.deleteRole({
this.$message.success('账号删除成功') accountId: id
}).then(res=>{
if(res.code == '200'){
let __this = this
this.$alert('删除成功', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.handleFilter()
}
});
}else{
this.$message.error(res.message)
}
})
}).catch(() => { }).catch(() => {
// 用户取消删除 // 用户取消删除
}) })
...@@ -317,41 +451,44 @@ export default { ...@@ -317,41 +451,44 @@ export default {
submitAccountForm() { submitAccountForm() {
this.$refs.accountForm.validate((valid) => { this.$refs.accountForm.validate((valid) => {
if (valid) { if (valid) {
if (this.editingAccount) { let ud = this.accountForm
// 更新账号信息
const index = this.accounts.findIndex(a => a.id === this.editingAccount.id) if(ud.roleCode=='GRID' && !ud.grid){
if (index !== -1) { this.$message.error('请补全区域信息')
this.$set(this.accounts, index, { ...this.editingAccount, ...this.accountForm }) return
this.$message.success('账号信息更新成功') }
} if(ud.roleCode=='COUNTY' && !ud.county){
} else { this.$message.error('请补全区域信息')
// 添加新账号 return
const newAccount = {
...this.accountForm,
id: `ACC${Date.now()}`,
createTime: new Date().toLocaleString('zh-CN')
}
this.accounts.push(newAccount)
this.$message.success('账号添加成功')
} }
// 重置表单和状态 this.apiReq.addAndUpdateRole({
this.resetAccountForm() id: this.editingAccount?this.editingAccount.id:'',
this.isAddDialogOpen = false account: ud.account,
this.editingAccount = null password: ud.password,
accountName: ud.accountName,
roleCode: ud.roleCode,
status: ud.status,
contactPhone: ud.contactPhone,
email: ud.email,
areaCode: ud.roleCode=='CITY'?ud.city:ud.county,
gridCode: ud.roleCode=='CITY'||ud.roleCode=='COUNTY'?'':ud.grid
}).then(res=>{
if(res.code == '200'){
let __this = this
this.$alert('添加成功', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.handleFilter()
__this.isAddDialogOpen = false
}
});
}else{
this.$message.error(res.message)
}
})
} }
}) })
},
resetAccountForm() {
this.accountForm = {
username: '',
name: '',
role: '网格管理员',
region: '',
phone: '',
email: '',
status: 'active'
}
} }
} }
} }
......
...@@ -2,21 +2,21 @@ ...@@ -2,21 +2,21 @@
<div class="close-reason-management"> <div class="close-reason-management">
<div class="toolbar"> <div class="toolbar">
<p class="description">管理商机关闭时可选择的原因</p> <p class="description">管理商机关闭时可选择的原因</p>
<el-button @click="isAddDialogOpen = true" type="primary" size="small"> <el-button @click="addReason" type="primary" size="small">
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
添加原因 添加原因
</el-button> </el-button>
</div> </div>
<div class="reason-table"> <div class="reason-table">
<el-table :data="reasons" style="width: 100%"> <el-table :data="reasonsList" style="width: 100%" border>
<el-table-column prop="reason" label="关闭原因" width="300"></el-table-column> <el-table-column prop="closeReason" label="关闭原因" width="300"></el-table-column>
<el-table-column prop="category" label="分类" width="200"> <el-table-column prop="category" label="分类" width="200">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag size="mini">{{ scope.row.category }}</el-tag> <el-tag size="mini">{{ scope.row.category }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="150"> <el-table-column label="操作" min-width="150">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button <el-button
type="text" type="text"
...@@ -37,6 +37,20 @@ ...@@ -37,6 +37,20 @@
</el-table> </el-table>
</div> </div>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageStore.currentPage"
:page-sizes="[20, 50, 100]"
:page-size="pageStore.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pageStore.total"
>
</el-pagination>
</div>
<!-- 添加/编辑原因对话框 --> <!-- 添加/编辑原因对话框 -->
<el-dialog <el-dialog
:title="editingReason ? '编辑关闭原因' : '添加关闭原因'" :title="editingReason ? '编辑关闭原因' : '添加关闭原因'"
...@@ -44,9 +58,9 @@ ...@@ -44,9 +58,9 @@
width="500px" width="500px"
> >
<el-form :model="reasonForm" :rules="reasonRules" ref="reasonForm" label-width="100px"> <el-form :model="reasonForm" :rules="reasonRules" ref="reasonForm" label-width="100px">
<el-form-item label="关闭原因" prop="reason"> <el-form-item label="关闭原因" prop="closeReason">
<el-input <el-input
v-model="reasonForm.reason" v-model="reasonForm.closeReason"
placeholder="请输入关闭原因" placeholder="请输入关闭原因"
></el-input> ></el-input>
</el-form-item> </el-form-item>
...@@ -68,21 +82,18 @@ ...@@ -68,21 +82,18 @@
</template> </template>
<script> <script>
// 模拟关闭原因数据
const mockCloseReasons = [
{ id: '1', reason: '客户价格不接受', category: '价格因素' },
{ id: '2', reason: '客户暂无需求', category: '需求因素' },
{ id: '3', reason: '竞品已安装', category: '竞争因素' },
{ id: '4', reason: '客户联系不上', category: '联系因素' },
{ id: '5', reason: '技术不支持', category: '技术因素' },
{ id: '6', reason: '其他原因', category: '其他' }
]
export default { export default {
name: 'CloseReasonManagement', name: 'CloseReasonManagement',
data() { data() {
return { return {
reasons: mockCloseReasons, reasonsList: [],
pageStore:{
currentPage: 1,
pageSize: 20,
total: 0
},
isAddDialogOpen: false, isAddDialogOpen: false,
editingReason: null, editingReason: null,
reasonForm: { reasonForm: {
...@@ -90,7 +101,7 @@ export default { ...@@ -90,7 +101,7 @@ export default {
category: '' category: ''
}, },
reasonRules: { reasonRules: {
reason: [ closeReason: [
{ required: true, message: '请输入关闭原因', trigger: 'blur' } { required: true, message: '请输入关闭原因', trigger: 'blur' }
], ],
category: [ category: [
...@@ -99,7 +110,46 @@ export default { ...@@ -99,7 +110,46 @@ export default {
} }
} }
}, },
created(){
this.handleFilter()
},
methods: { methods: {
handleSizeChange(size) {
this.pageStore.pageSize = size
this.handleFilter()
},
handleCurrentChange(page) {
this.pageStore.currentPage = page
this.queryCloseList()
},
handleFilter(){
this.pageStore.currentPage = 1
this.queryCloseList()
},
queryCloseList(){
this.apiReq.queryBusiCloseReansonList({
pageNum: this.pageStore.currentPage,
pageSize: this.pageStore.pageSize
}).then(res=>{
if(res.code == 200){
this.reasonsList = res.data.records
this.pageStore.total = res.data.total
}
})
},
addReason(){
this.editingReason = null
this.reasonForm = {
category: '',
closeReason: ''
}
this.isAddDialogOpen = true
},
editReason(reason) { editReason(reason) {
this.editingReason = reason this.editingReason = reason
this.reasonForm = { ...reason } this.reasonForm = { ...reason }
...@@ -111,44 +161,46 @@ export default { ...@@ -111,44 +161,46 @@ export default {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.reasons = this.reasons.filter(r => r.id !== id) this.apiReq.busiCloseReasonDel({
this.$message.success('关闭原因删除成功') id,
}).catch(() => { }).then(res=>{
// 用户取消删除 if(res.code == 200){
let __this = this
this.$alert('删除成功', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.handleFilter()
}
});
}else{
this.$message.error(res.message)
}
})
}) })
}, },
submitReasonForm() { submitReasonForm() {
this.$refs.reasonForm.validate((valid) => { this.$refs.reasonForm.validate((valid) => {
if (valid) { if (valid) {
if (this.editingReason) { this.apiReq.busiCloseReasonUpdate({
// 更新原因信息 id: this.editingReason?this.editingReason.id: '',
const index = this.reasons.findIndex(r => r.id === this.editingReason.id) category: this.reasonForm.category,
if (index !== -1) { closeReason: this.reasonForm.closeReason
this.$set(this.reasons, index, { ...this.editingReason, ...this.reasonForm }) }).then(res=>{
this.$message.success('关闭原因更新成功') if(res.code == '200'){
let __this = this
this.$alert(this.editingReason?'修改成功':'添加成功', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.handleFilter()
__this.isAddDialogOpen = false
}
});
}else{
this.$message.error(res.message)
} }
} else { })
// 添加新原因
const newReason = {
...this.reasonForm,
id: `REASON${Date.now()}`
}
this.reasons.push(newReason)
this.$message.success('关闭原因添加成功')
}
// 重置表单和状态
this.resetReasonForm()
this.isAddDialogOpen = false
this.editingReason = null
} }
}) })
},
resetReasonForm() {
this.reasonForm = {
reason: '',
category: ''
}
} }
} }
} }
......
...@@ -5,43 +5,60 @@ ...@@ -5,43 +5,60 @@
<div class="toolbar-left"> <div class="toolbar-left">
<div class="search-wrapper"> <div class="search-wrapper">
<el-input <el-input
v-model="searchQuery" v-model="formData.personnelCode"
placeholder="请输入工号查询" placeholder="请输入工号查询"
class="search-input" class="search-input"
/> />
</div> </div>
<div class="search-wrapper"> <div class="search-wrapper">
<el-input <el-input
v-model="searchPhone" v-model="formData.phone"
placeholder="请输入手机号查询" placeholder="请输入手机号查询"
class="search-input" class="search-input"
/> />
</div> </div>
<el-cascader <div class="search-wrapper" style="width: auto;">
v-if="hasProvincePermission" <template v-if="addressStore.cityArr.length>0">
v-model="selectedRegion" <el-select
:options="regionOptions" v-model="addressStore.city"
:props="{ checkStrictly: true, value: 'name', label: 'name' }" class="filter-select"
placeholder="所属区域筛选" placeholder="选择地市"
clearable @change="cityChange"
class="region-cascader" :disabled="getData.city!=''"
></el-cascader> style="margin-right: 20px;"
<el-select clearable>
v-else <el-option
v-model="selectedRegionDistrict" v-for="item in addressStore.cityArr"
placeholder="所属区域筛选" :key="item.areaCode"
clearable :label="item.areaName"
class="region-select" :value="item.areaCode"
> ></el-option>
<el-option label="玄武区" value="玄武区"></el-option> </el-select>
<el-option label="秦淮区" value="秦淮区"></el-option> </template>
<el-option label="建邺区" value="建邺区"></el-option> <template v-if="addressStore.countyArr.length>0">
<el-option label="鼓楼区" value="鼓楼区"></el-option> <el-select
</el-select> v-model="addressStore.county"
class="filter-select"
placeholder="选择区县"
:disabled="getData.county!=''"
clearable>
<el-option
v-for="item in addressStore.countyArr"
:key="item.areaCode"
:label="item.areaName"
:value="item.areaCode"
></el-option>
</el-select>
</template>
</div>
</div> </div>
<div class="toolbar-right"> <div class="toolbar-right">
<el-button @click="isAddDialogOpen = true" type="primary" size="small"> <el-button size="small" @click="handleFilter">
<i class="el-icon-search"></i>
查询
</el-button>
<el-button @click="updatePerson" type="primary" size="small">
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
添加人员 添加人员
</el-button> </el-button>
...@@ -50,25 +67,26 @@ ...@@ -50,25 +67,26 @@
<!-- 表格 --> <!-- 表格 -->
<div class="personnel-table"> <div class="personnel-table">
<el-table :data="paginatedPersonnel" border> <el-table :data="tableData" border>
<el-table-column prop="name" label="姓名" width="100"></el-table-column> <el-table-column prop="personnelName" label="姓名" width="100"></el-table-column>
<el-table-column prop="workId" label="工号" width="100"></el-table-column> <el-table-column prop="personnelCode" label="工号" width="100"></el-table-column>
<el-table-column prop="phone" label="手机号" width="150"></el-table-column> <el-table-column prop="phone" label="手机号" width="150"></el-table-column>
<el-table-column prop="region" label="所属区域" width="250"></el-table-column> <el-table-column prop="areaName" label="所属区域" width="250"></el-table-column>
<el-table-column label="状态" width="120"> <el-table-column label="状态" width="120">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag :type="scope.row.status === 'enabled' ? 'success' : 'info'"> <el-tag :type="scope.row.status == '1' ? 'success' : 'info'">
{{ scope.row.status === 'enabled' ? '启用' : '关闭' }} {{ scope.row.status == '1' ? '启用' : '关闭' }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="createTime" label="创建时间" width="220"></el-table-column> <el-table-column prop="createTime" label="创建时间" width="220" :formatter="timeRender"></el-table-column>
<el-table-column label="操作" min-width="120"> <el-table-column label="操作" min-width="120">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button <el-button
type="text" type="text"
size="small" size="small"
@click="editPersonnel(scope.row)" @click="editPersonnel(scope.row)"
disabled
> >
编辑 编辑
</el-button> </el-button>
...@@ -89,35 +107,34 @@ ...@@ -89,35 +107,34 @@
<el-pagination <el-pagination
@size-change="handleSizeChange" @size-change="handleSizeChange"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
:current-page="currentPage" :current-page="pageStore.currentPage"
:page-sizes="[20, 50, 100]" :page-sizes="[20, 50, 100]"
:page-size="pageSize" :page-size="pageStore.pageSize"
layout="total, sizes, prev, pager, next, jumper" layout="total, sizes, prev, pager, next, jumper"
:total="filteredPersonnel.length" :total="pageStore.total"
> >
</el-pagination> </el-pagination>
</div> </div>
<!-- 添加/编辑人员对话框 --> <!-- 添加/编辑人员对话框 -->
<el-dialog <el-dialog
:title="editingPersonnel ? '编辑政企业务营销人员' : '新增政企业务营销人员'" :title="editingPersonnel ? '编辑政企业务支撑人员' : '新增政企业务支撑人员'"
:visible.sync="isAddDialogOpen" :visible.sync="updatePersonStore.isShow"
width="600px" width="800px">
> <el-form :model="updatePersonStore" :rules="personnelRules" ref="personnelForm" label-width="100px">
<el-form :model="personnelForm" :rules="personnelRules" ref="personnelForm" label-width="100px">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="姓名" prop="name"> <el-form-item label="姓名" prop="personnelName">
<el-input <el-input
v-model="personnelForm.name" v-model="updatePersonStore.personnelName"
placeholder="请输入姓名" placeholder="请输入姓名"
></el-input> ></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="工号" prop="workId"> <el-form-item label="工号" prop="personnelCode">
<el-input <el-input
v-model="personnelForm.workId" v-model="updatePersonStore.personnelCode"
placeholder="请输入工号" placeholder="请输入工号"
:disabled="!!editingPersonnel" :disabled="!!editingPersonnel"
></el-input> ></el-input>
...@@ -130,33 +147,65 @@ ...@@ -130,33 +147,65 @@
<el-col :span="12"> <el-col :span="12">
<el-form-item label="手机号" prop="phone"> <el-form-item label="手机号" prop="phone">
<el-input <el-input
v-model="personnelForm.phone" v-model="updatePersonStore.phone"
placeholder="请输入手机号" placeholder="请输入手机号"
></el-input> ></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-select v-model="updatePersonStore.status" style="width: 100%;">
<el-option label="启用" value="1"></el-option>
<el-option label="关闭" value="0"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="30">
<el-form-item label="所属区域" prop="grid">
<template v-if="updatePersonStore.cityArr.length>0">
<el-select
v-model="updatePersonStore.city"
placeholder="选择地市"
@change="(value)=>{cityChange(value,'person')}"
:disabled="getData.city!=''"
style="margin-right: 10px"
clearable>
<el-option
v-for="item in updatePersonStore.cityArr"
:key="item.areaCode"
:label="item.areaName"
:value="item.areaCode"
></el-option>
</el-select>
</template>
<template v-if="updatePersonStore.countyArr.length>0">
<el-select
v-model="updatePersonStore.county"
placeholder="选择区县"
@change="(value)=>{countyChange(value,'person')}"
:disabled="getData.county!=''"
style="margin-right: 10px"
clearable>
<el-option
v-for="item in updatePersonStore.countyArr"
:key="item.areaCode"
:label="item.areaName"
:value="item.areaCode"
></el-option>
</el-select>
</template>
</el-form-item>
</el-col>
</el-row> </el-row>
<el-form-item label="所属区域" prop="region">
<el-cascader
v-model="personnelForm.region"
:options="regionOptions"
:props="{ checkStrictly: true, value: 'name', label: 'name' }"
placeholder="请选择区域"
style="width: 100%;"
></el-cascader>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="personnelForm.status" style="width: 100%;">
<el-option label="启用" value="enabled"></el-option>
<el-option label="关闭" value="disabled"></el-option>
</el-select>
</el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button @click="isAddDialogOpen = false">取消</el-button> <el-button @click="updatePersonStore.isShow = false">取消</el-button>
<el-button type="primary" @click="submitPersonnelForm">确定</el-button> <el-button type="primary" @click="submitPersonnelForm">确定</el-button>
</div> </div>
</el-dialog> </el-dialog>
...@@ -164,128 +213,204 @@ ...@@ -164,128 +213,204 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
// 模拟政企业务营销人员数据
const mockEnterprisePersonnel = [
{ id: 'EP001', name: '刘政企', phone: '13912345678', workId: 'E001', type: '装维师傅', region: '江苏省-南京市-玄武区', gridName: 'A网格', associatedSales: 'SALE001', status: 'enabled', createTime: '2025-09-01 09:00:00' },
{ id: 'EP002', name: '赵政企', phone: '13923456789', workId: 'E002', type: '装维师傅', region: '江苏省-南京市-秦淮区', gridName: 'B网格', associatedSales: 'SALE002', status: 'enabled', createTime: '2025-09-02 10:00:00' },
{ id: 'EP003', name: '孙政企', phone: '13934567890', workId: 'E003', type: '装维师傅', region: '江苏省-南京市-建邺区', gridName: 'C网格', associatedSales: 'SALE003', status: 'disabled', createTime: '2025-09-03 11:00:00' },
{ id: 'EP004', name: '周政企', phone: '13945678901', workId: 'E004', type: '装维师傅', region: '江苏省-南京市-鼓楼区', gridName: 'D网格', associatedSales: 'SALE004', status: 'enabled', createTime: '2025-09-04 12:00:00' },
{ id: 'EP005', name: '吴政企', phone: '13956789012', workId: 'E005', type: '装维师傅', region: '江苏省-南京市-浦口区', gridName: 'E网格', associatedSales: 'SALE005', status: 'enabled', createTime: '2025-09-05 13:00:00' },
{ id: 'EP006', name: '郑政企', phone: '13967890123', workId: 'E006', type: '装维师傅', region: '江苏省-南通市-崇川区', gridName: 'F网格', associatedSales: 'SALE006', status: 'enabled', createTime: '2025-09-06 14:00:00' },
{ id: 'EP007', name: '王政企', phone: '13978901234', workId: 'E007', region: '江苏省-南通市-港闸区', status: 'disabled', createTime: '2025-09-07 15:00:00' },
{ id: 'EP008', name: '冯政企', phone: '13989012345', workId: 'E008', region: '江苏省-苏州市-姑苏区', status: 'enabled', createTime: '2025-09-08 16:00:00' },
{ id: 'EP009', name: '陈政企', phone: '13990123456', workId: 'E009', region: '江苏省-苏州市-虎丘区', status: 'enabled', createTime: '2025-09-09 17:00:00' },
{ id: 'EP010', name: '褚政企', phone: '13901234567', workId: 'E010', region: '江苏省-南京市-栖霞区', status: 'enabled', createTime: '2025-09-10 18:00:00' }
]
// 地区数据
const regionOptions = [
{
name: '江苏省',
children: [
{
name: '南京市',
children: [
{ name: '玄武区' },
{ name: '秦淮区' },
{ name: '建邺区' },
{ name: '鼓楼区' },
{ name: '浦口区' },
{ name: '栖霞区' },
{ name: '雨花台区' },
{ name: '江宁区' },
{ name: '六合区' },
{ name: '溧水区' },
{ name: '高淳区' }
]
},
{
name: '南通市',
children: [
{ name: '崇川区' },
{ name: '港闸区' }
]
},
{
name: '苏州市',
children: [
{ name: '姑苏区' },
{ name: '虎丘区' }
]
}
]
}
]
export default { export default {
name: 'EnterprisePersonnelManagement', name: 'EnterprisePersonnelManagement',
data() { data() {
return { return {
personnel: mockEnterprisePersonnel, formData:{
searchQuery: '', personnelCode: '',
searchPhone: '',
selectedRegion: [],
selectedRegionDistrict: '',
isAddDialogOpen: false,
editingPersonnel: null,
currentPage: 1,
pageSize: 20,
personnelForm: {
name: '',
workId: '',
phone: '', phone: '',
region: [], personnelTypes: [3],
status: 'enabled' },
addressStore:{
city: '',
cityArr: [],
county: '',
countyArr: []
},
getData:{
city: '',
county: ''
},
tableData: [],
pageStore:{
currentPage: 1,
pageSize: 20,
total: 0
},
updatePersonStore:{
isShow: false,
row: {},
personnelName: '',
personnelCode: '',
phone: '',
status: '',
city: '',
cityArr: [],
county: '',
countyArr: []
}, },
personnelRules: { personnelRules: {
name: [ personnelName: [
{ required: true, message: '请输入姓名', trigger: 'blur' } { required: true, message: '请输入姓名', trigger: 'blur' }
], ],
workId: [ personnelCode: [
{ required: true, message: '请输入工号', trigger: 'blur' } { required: true, message: '请输入工号', trigger: 'blur' }
], ],
phone: [ phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' } { required: true, message: '请输入手机号', trigger: 'blur' }
], ],
region: [ status: [
{ required: true, message: '请选择所属区域', trigger: 'change' } { required: true, message: '请选择状态', trigger: 'blur' }
] ],
county: [
{ required: true, message: '请选择所属区域', trigger: 'blur' }
],
}, },
regionOptions
isAddDialogOpen: false,
editingPersonnel: null,
currentPage: 1,
pageSize: 20,
personnelForm: {
name: '',
workId: '',
phone: '',
region: [],
status: 'enabled'
}
} }
}, },
computed: { created(){
...mapGetters(['user']), let pa = JSON.parse(localStorage.getItem('accountInfo'))
hasProvincePermission() { this.getData.city = pa.cityCode||''
return this.user?.role === 'province_admin' this.getData.county = pa.countyCode||''
}, this.queryArea()
filteredPersonnel() {
return this.personnel.filter(person => { this.handleFilter()
const matchesQuery = !this.searchQuery || },
person.workId.includes(this.searchQuery) || methods: {
person.phone.includes(this.searchQuery) updatePerson(row){
let ud = this.updatePersonStore
if(row.id){
ud.row =row
ud.personnelName = row.personnelName
ud.personnelCode = row.personnelCode
ud.phone = row.phone
ud.status = row.status
ud.relatedMarketingCode = row.relatedMarketingCode
// 区域筛选逻辑 }else{
let matchesRegion = true ud.row = {}
if (this.hasProvincePermission && this.selectedRegion && this.selectedRegion.length > 0) { ud.personnelName = ''
const selectedPath = this.selectedRegion.join('-') ud.personnelCode = ''
matchesRegion = person.region.startsWith(selectedPath) ud.phone = ''
} else if (!this.hasProvincePermission && this.selectedRegionDistrict) { ud.status = ''
matchesRegion = person.region.includes(this.selectedRegionDistrict) ud.relatedMarketingCode = ''
if(!this.getData.city){
ud.city = ''
ud.county = ''
ud.countyArr = []
ud.grid = ''
ud.gridArr = []
}else if(!this.getData.county){
ud.county = ''
ud.grid = ''
ud.gridArr = []
}
}
this.updatePersonStore.isShow = true
},
cityChange(value,type){
let ad = type=='person'?this.updatePersonStore:this.addressStore
ad.city = value
ad.county = ''
ad.grid = ''
this.apiReq.queryLevelAllArea({
areaLevel: 3,
parentAreaCode: value
}).then(res=>{
if(res.code == 200){
ad.countyArr = res.data
if(type == 'all'){
this.updatePersonStore.countyArr = res.data
}
} }
return matchesQuery && matchesRegion
}) })
}, },
paginatedPersonnel() { timeRender(row,column){
const startIndex = (this.currentPage - 1) * this.pageSize return this.common.detailTime(new Date(row[column.property]).getTime())
const endIndex = startIndex + this.pageSize },
return this.filteredPersonnel.slice(startIndex, endIndex) handleFilter(){
} this.pageStore.currentPage = 1
},
methods: { this.queryPersonList()
},
queryArea(){
this.apiReq.queryLevelAllArea({
areaLevel: 2,
parentAreaCode: '320000'
}).then(res=>{
if(res.code == 200){
this.updatePersonStore.cityArr = res.data
this.addressStore.cityArr = res.data
this.setAddressShow()
}
})
},
setAddressShow(){
let ad = this.addressStore
let pd = this.updatePersonStore
let gd = this.getData
if(gd.city){
ad.city = gd.city
this.cityChange(ad.city,'all')
if(gd.county){
ad.county = gd.county
}
}
pd.city = ad.city
pd.county = ad.county
},
handleSizeChange(size) {
this.pageStore.pageSize = size
this.handleFilter()
},
handleCurrentChange(page) {
this.pageStore.currentPage = page
this.queryPersonList()
},
queryPersonList(){
this.apiReq.queryAllPerson({
pageSize: this.pageStore.pageSize,
pageNum: this.pageStore.currentPage,
areaCode: this.addressStore.county || this.addressStore.city,
...this.formData
}).then(res=>{
if(res.code == 200){
this.tableData = res.data.records
this.pageStore.total = res.data.total
}else{
this.$message.error(res.message)
}
})
},
editPersonnel(person) { editPersonnel(person) {
this.editingPersonnel = person this.editingPersonnel = person
// 分解区域路径为数组 // 分解区域路径为数组
...@@ -296,77 +421,50 @@ export default { ...@@ -296,77 +421,50 @@ export default {
this.isAddDialogOpen = true this.isAddDialogOpen = true
}, },
deletePersonnel(id) { deletePersonnel(id) {
this.$confirm('确定要删除该营销人员吗?删除后该账号将无法登录。', '确认删除', { this.$confirm('确定要删除该人员吗?此操作不可撤销。', '确认删除', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.personnel = this.personnel.filter(p => p.id !== id) this.apiReq.deletePerson({
this.$message.success('政企业务营销人员删除成功') id,
}).catch(() => { }).then(res=>{
// 用户取消删除 if(res.code == '200'){
this.handleFilter()
this.$message.success('删除成功')
}else{
this.$message.error(res.message)
}
})
}) })
}, },
submitPersonnelForm() { submitPersonnelForm() {
this.$refs.personnelForm.validate((valid) => { this.$refs.personnelForm.validate((valid) => {
if (valid) { if (valid) {
if (!this.personnelForm.name || !this.personnelForm.workId || !this.personnelForm.phone) { let ud = this.updatePersonStore
this.$message.error('请填写所有必填项') this.apiReq.addNewPerson({
return personnelName: ud.personnelName,
} personnelCode: ud.personnelCode,
phone: ud.phone,
if (!this.personnelForm.region || this.personnelForm.region.length === 0) { personnelType: '3',
this.$message.error('请选择所属区域') status: ud.status,
return areaCode: ud.county
} }).then(res=>{
if(res.code == '200'){
const region = this.personnelForm.region.join('-') let __this = this
this.$alert('添加成功', '温馨提示', {
if (this.editingPersonnel) { confirmButtonText: '确定',
// 更新人员信息 callback: () => {
const index = this.personnel.findIndex(p => p.id === this.editingPersonnel.id) __this.handleFilter()
if (index !== -1) { __this.updatePersonStore.isShow = false
this.$set(this.personnel, index, { }
...this.editingPersonnel, });
...this.personnelForm, }else{
region this.$message.error(res.message)
})
this.$message.success('政企业务营销人员信息更新成功')
} }
} else { })
// 添加新人员
const newPersonnel = {
...this.personnelForm,
id: `EP${Date.now()}`,
region,
createTime: new Date().toLocaleString('zh-CN')
}
this.personnel.push(newPersonnel)
this.$message.success('政企业务营销人员添加成功')
}
// 重置表单和状态
this.resetPersonnelForm()
this.isAddDialogOpen = false
this.editingPersonnel = null
} }
}) })
},
resetPersonnelForm() {
this.personnelForm = {
name: '',
workId: '',
phone: '',
region: [],
status: 'enabled'
}
},
handleSizeChange(size) {
this.pageSize = size
this.currentPage = 1
},
handleCurrentChange(page) {
this.currentPage = page
} }
} }
} }
......
...@@ -5,78 +5,84 @@ ...@@ -5,78 +5,84 @@
<div class="toolbar-left"> <div class="toolbar-left">
<div class="search-wrapper"> <div class="search-wrapper">
<el-input <el-input
v-model="searchTerm" v-model="formData.personnelCode"
placeholder="请输入工号查询..." placeholder="请输入工号查询..."
class="search-input" class="search-input"
clearable
/> />
</div> </div>
<div class="search-wrapper"> <div class="search-wrapper">
<el-input <el-input
v-model="searchPhone" v-model="formData.phone"
placeholder="请输入手机号查询..." placeholder="请输入手机号查询..."
class="search-input" class="search-input"
clearable
/> />
</div> </div>
<el-select v-model="filterType" placeholder="人员类型" class="filter-select"> <el-select v-model="formData.personnelTypes" placeholder="人员类型" class="filter-select" multiple clearable>
<el-option label="全部类型" value="all"></el-option> <el-option label="全部类型" value=""></el-option>
<el-option label="装维师傅" value="installer"></el-option> <el-option label="装维师傅" value="1"></el-option>
<el-option label="营销人员" value="sales"></el-option> <el-option label="支撑人员" value="2"></el-option>
</el-select> </el-select>
<template v-if="addressStore.cityArr.length>0"> <template v-if="addressStore.cityArr.length>0">
<el-select <el-select
v-model="addressStore.city" v-model="addressStore.city"
class="filter-select" class="filter-select"
placeholder="选择地市" placeholder="选择地市"
@change="cityChange" @change="cityChange"
:disabled="getData.city!=''" :disabled="getData.city!=''"
clearable> clearable>
<el-option <el-option
v-for="item in addressStore.cityArr" v-for="item in addressStore.cityArr"
:key="item.value" :key="item.areaCode"
:label="item.name" :label="item.areaName"
:value="item.value" :value="item.areaCode"
></el-option> ></el-option>
</el-select> </el-select>
</template> </template>
<template v-if="addressStore.countyArr.length>0"> <template v-if="addressStore.countyArr.length>0">
<el-select <el-select
v-model="addressStore.county" v-model="addressStore.county"
class="filter-select" class="filter-select"
placeholder="选择区县" placeholder="选择区县"
@change="countyChange" @change="countyChange"
:disabled="getData.county!=''" :disabled="getData.county!=''"
clearable> clearable>
<el-option <el-option
v-for="item in addressStore.countyArr" v-for="item in addressStore.countyArr"
:key="item.value" :key="item.areaCode"
:label="item.name" :label="item.areaName"
:value="item.value" :value="item.areaCode"
></el-option> ></el-option>
</el-select> </el-select>
</template> </template>
<template v-if="addressStore.gridArr.length>0"> <template v-if="addressStore.gridArr.length>0">
<el-select <el-select
v-model="addressStore.grid" v-model="addressStore.grid"
class="filter-select" class="filter-select"
placeholder="选择网格" placeholder="选择网格"
:disabled="getData.grid!=''" :disabled="getData.grid!=''"
clearable> clearable>
<el-option <el-option
v-for="item in addressStore.gridArr" v-for="item in addressStore.gridArr"
:key="item.value" :key="item.gridCode"
:label="item.name" :label="item.gridName"
:value="item.value" :value="item.gridCode"
></el-option> ></el-option>
</el-select> </el-select>
</template> </template>
</div> </div>
<div class="toolbar-right"> <div class="toolbar-right">
<el-button @click="isImportDialogOpen = true" type="default" size="small"> <el-button size="small" @click="handleFilter">
<i class="el-icon-search"></i>
查询
</el-button>
<el-button @click="isImportDialogOpen = true" type="default" size="small" disabled>
<i class="el-icon-download"></i> <i class="el-icon-download"></i>
批量导入 批量导入
</el-button> </el-button>
<el-button @click="isAddDialogOpen = true" type="primary" size="small"> <el-button @click="updatePerson" type="primary" size="small">
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
添加人员 添加人员
</el-button> </el-button>
...@@ -85,44 +91,29 @@ ...@@ -85,44 +91,29 @@
<!-- 人员列表 --> <!-- 人员列表 -->
<div class="personnel-table"> <div class="personnel-table">
<el-table :data="paginatedPersonnel" style="width: 100%" border> <el-table :data="tableData" style="width: 100%" border>
<el-table-column prop="name" label="姓名" width="100"></el-table-column> <el-table-column prop="personnelName" label="姓名" width="120"></el-table-column>
<el-table-column prop="workId" label="工号" width="100"></el-table-column> <el-table-column prop="personnelCode" label="工号" width="120"></el-table-column>
<el-table-column prop="phone" label="手机号" width="120"></el-table-column> <el-table-column prop="phone" label="手机号" width="150"></el-table-column>
<el-table-column label="人员类型" width="100"> <el-table-column prop="personnelTypeName" label="人员类型" width="120"></el-table-column>
<template slot-scope="scope"> <el-table-column prop="areaName" label="所属区域" width="150"></el-table-column>
<el-tag :type="scope.row.type === 'installer' ? 'info' : 'primary'"> <el-table-column prop="gridName" label="网格" width="220"></el-table-column>
{{ personnelTypeMap[scope.row.type].label }} <el-table-column prop="relatedMarketingCode" label="关联支撑人员" width="120" ></el-table-column>
</el-tag>
</template>
</el-table-column>
<el-table-column prop="region" label="所属区域" width="150"></el-table-column>
<el-table-column prop="gridName" label="网格" width="100"></el-table-column>
<el-table-column label="关联营销人员" width="120">
<template slot-scope="scope">
<span v-if="scope.row.type === 'installer'">
<span v-if="getAssociatedSalesPerson(scope.row.associatedSales)">
{{ getAssociatedSalesPerson(scope.row.associatedSales).workId }}
</span>
<span v-else class="text-muted">未关联</span>
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="状态" width="80"> <el-table-column label="状态" width="80">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag :type="scope.row.status === 'active' ? 'success' : 'info'"> <el-tag :type="scope.row.status == '1' ? 'success' : 'info'">
{{ scope.row.status === 'active' ? '启用' : '禁用' }} {{ scope.row.status == '1' ? '启用' : '关闭' }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="createTime" label="创建时间" width="150"></el-table-column> <el-table-column prop="createTime" label="创建时间" width="200" :formatter="timeRender"></el-table-column>
<el-table-column label="操作" min-width="150"> <el-table-column label="操作" min-width="150">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button <el-button
type="text" type="text"
size="small" size="small"
@click="editPersonnel(scope.row)" disabled
@click="updatePerson(scope.row)"
> >
编辑 编辑
</el-button> </el-button>
...@@ -143,33 +134,34 @@ ...@@ -143,33 +134,34 @@
<el-pagination <el-pagination
@size-change="handleSizeChange" @size-change="handleSizeChange"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
:current-page="currentPage" :current-page="pageStore.currentPage"
:page-sizes="[20, 50, 100]" :page-sizes="[20, 50, 100]"
:page-size="pageSize" :page-size="pageStore.pageSize"
layout="total, sizes, prev, pager, next, jumper" layout="total, sizes, prev, pager, next, jumper"
:total="filteredPersonnel.length" :total="pageStore.total"
> >
</el-pagination> </el-pagination>
</div> </div>
<!-- 添加/编辑人员对话框 --> <!-- 添加/编辑人员对话框 -->
<el-dialog <el-dialog
:title="editingPersonnel ? '编辑人员' : '添加人员'" :title=" updatePersonStore.row.id? '编辑人员' : '添加人员'"
:visible.sync="isAddDialogOpen" :visible.sync="updatePersonStore.isShow"
class="addPersonDialog"
width="800px"> width="800px">
<el-form :model="personnelForm" :rules="personnelRules" ref="personnelForm" label-width="100px"> <el-form :model="updatePersonStore" :rules="personnelRules" ref="personnelForm" label-width="100px">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="姓名" prop="name"> <el-form-item label="姓名" prop="personnelName">
<el-input v-model="personnelForm.name" placeholder="请输入姓名"></el-input> <el-input v-model="updatePersonStore.personnelName" placeholder="请输入姓名"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="工号" prop="workId"> <el-form-item label="工号" prop="personnelCode">
<el-input <el-input
v-model="personnelForm.workId" v-model="updatePersonStore.personnelCode"
placeholder="请输入工号" placeholder="请输入工号"
:disabled="!!editingPersonnel" :disabled="!!updatePersonStore.row.id"
></el-input> ></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
...@@ -178,38 +170,38 @@ ...@@ -178,38 +170,38 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="手机号" prop="phone"> <el-form-item label="手机号" prop="phone">
<el-input v-model="personnelForm.phone" placeholder="请输入手机号"></el-input> <el-input v-model="updatePersonStore.phone" placeholder="请输入手机号"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="人员类型" prop="type"> <el-form-item label="人员类型" prop="personnelType">
<el-select v-model="personnelForm.type" @change="handlePersonnelTypeChange" style="width: 100%;"> <el-select v-model="updatePersonStore.personnelType" @change="personnelTypeChange" style="width: 100%;">
<el-option label="装维师傅" value="installer"></el-option> <el-option label="装维师傅" value="1"></el-option>
<el-option label="营销人员" value="sales"></el-option> <el-option label="支撑人员" value="2"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<!-- 当选择装维师傅时显示关联营销人员字段 --> <!-- 当选择装维师傅时显示关联支撑人员字段 -->
<el-col :span="12"> <el-col :span="12">
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-select v-model="personnelForm.status" style="width: 100%;"> <el-select v-model="updatePersonStore.status" style="width: 100%;">
<el-option label="启用" value="active"></el-option> <el-option label="启用" value="1"></el-option>
<el-option label="禁用" value="inactive"></el-option> <el-option label="关闭" value="0"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" v-if="personnelForm.type === 'installer'"> <el-col :span="12" v-if="updatePersonStore.personnelType == '1'">
<el-form-item label="关联营销人员" prop="associatedSales"> <el-form-item label="关联支撑人" prop="relatedMarketingCode">
<el-select v-model="personnelForm.associatedSales" placeholder="请选择营销人员" style="width: 100%;"> <el-select v-model="updatePersonStore.relatedMarketingCode" placeholder="请选择支撑人员" style="width: 100%;">
<el-option <el-option
v-for="salesPerson in activeSalesPersons" v-for="item in yxPersonList"
:key="salesPerson.id" :key="item.personnelCode"
:label="`${salesPerson.name} (${salesPerson.workId})`" :label="item.personnelName"
:value="salesPerson.id" :value="item.personnelCode"
></el-option> ></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
...@@ -218,50 +210,50 @@ ...@@ -218,50 +210,50 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="30"> <el-col :span="30">
<el-form-item label="所属区域" prop="region"> <el-form-item label="所属区域" prop="grid">
<template v-if="personnelForm.cityArr.length>0"> <template v-if="updatePersonStore.cityArr.length>0">
<el-select <el-select
v-model="personnelForm.city" v-model="updatePersonStore.city"
placeholder="选择地市" placeholder="选择地市"
@change="(value)=>{cityChange(value,'person')}" @change="(value)=>{cityChange(value,'person')}"
:disabled="getData.city!=''" :disabled="getData.city!=''"
style="margin-right: 10px" style="margin-right: 10px"
clearable> clearable>
<el-option <el-option
v-for="item in personnelForm.cityArr" v-for="item in updatePersonStore.cityArr"
:key="item.value" :key="item.areaCode"
:label="item.name" :label="item.areaName"
:value="item.value" :value="item.areaCode"
></el-option> ></el-option>
</el-select> </el-select>
</template> </template>
<template v-if="personnelForm.countyArr.length>0"> <template v-if="updatePersonStore.countyArr.length>0">
<el-select <el-select
v-model="personnelForm.county" v-model="updatePersonStore.county"
placeholder="选择区县" placeholder="选择区县"
@change="(value)=>{countyChange(value,'person')}" @change="(value)=>{countyChange(value,'person')}"
:disabled="getData.county!=''" :disabled="getData.county!=''"
style="margin-right: 10px" style="margin-right: 10px"
clearable> clearable>
<el-option <el-option
v-for="item in personnelForm.countyArr" v-for="item in updatePersonStore.countyArr"
:key="item.value" :key="item.areaCode"
:label="item.name" :label="item.areaName"
:value="item.value" :value="item.areaCode"
></el-option> ></el-option>
</el-select> </el-select>
</template> </template>
<template v-if="personnelForm.gridArr.length>0"> <template v-if="updatePersonStore.gridArr.length>0">
<el-select <el-select
v-model="personnelForm.grid" v-model="updatePersonStore.grid"
placeholder="选择网格" placeholder="选择网格"
:disabled="getData.grid!=''" :disabled="getData.grid!=''"
clearable> clearable>
<el-option <el-option
v-for="item in personnelForm.gridArr" v-for="item in updatePersonStore.gridArr"
:key="item.value" :key="item.gridCode"
:label="item.name" :label="item.gridName"
:value="item.value" :value="item.gridCode"
></el-option> ></el-option>
</el-select> </el-select>
</template> </template>
...@@ -271,7 +263,7 @@ ...@@ -271,7 +263,7 @@
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button @click="isAddDialogOpen = false">取消</el-button> <el-button @click="updatePersonStore.isShow = false">取消</el-button>
<el-button type="primary" @click="submitPersonnelForm">确定</el-button> <el-button type="primary" @click="submitPersonnelForm">确定</el-button>
</div> </div>
</el-dialog> </el-dialog>
...@@ -280,8 +272,7 @@ ...@@ -280,8 +272,7 @@
<el-dialog <el-dialog
title="批量导入人员" title="批量导入人员"
:visible.sync="isImportDialogOpen" :visible.sync="isImportDialogOpen"
width="600px" width="600px">
>
<div class="import-content"> <div class="import-content">
<div class="import-section"> <div class="import-section">
<h4>选择导入文件</h4> <h4>选择导入文件</h4>
...@@ -297,15 +288,19 @@ ...@@ -297,15 +288,19 @@
<i class="el-icon-upload2"></i> <i class="el-icon-upload2"></i>
选择文件 选择文件
</el-button> </el-button>
<el-button @click="downloadTemplate('installer')" type="default"> <el-button @click="downloadTemplate('gongwei')" type="default">
<i class="el-icon-download"></i> <i class="el-icon-download"></i>
装维师傅模板 装维师傅模板
</el-button> </el-button>
<el-button @click="downloadTemplate('sales')" type="default"> <el-button @click="downloadTemplate('yingxiao')" type="default">
<i class="el-icon-download"></i> <i class="el-icon-download"></i>
营销人员模板 支撑人员模板
</el-button> </el-button>
</div> </div>
<div class="file-upload">
<el-radio v-model="importStore.type" label="1">装修师傅</el-radio>
<el-radio v-model="importStore.type" label="2">支撑人员</el-radio>
</div>
<div class="file-info" v-if="selectedFile"> <div class="file-info" v-if="selectedFile">
<span class="file-name">{{ selectedFile.name }}</span> <span class="file-name">{{ selectedFile.name }}</span>
...@@ -330,160 +325,195 @@ ...@@ -330,160 +325,195 @@
</template> </template>
<script> <script>
// 人员类型映射
const personnelTypeMap = {
installer: { label: '装维师傅' },
sales: { label: '营销人员' }
}
// 模拟数据
const mockPersonnel = [
{ id: 'INS001', name: '王师傅', phone: '13987654321', workId: 'W001', type: 'installer', region: '南京市玄武区', gridName: 'A网格', status: 'active', createTime: '2025-09-01 09:00:00', associatedSales: 'SALE001' },
{ id: 'INS002', name: '李师傅', phone: '13876543210', workId: 'W002', type: 'installer', region: '南京市玄武区', gridName: 'B网格', status: 'active', createTime: '2025-09-01 09:00:00', associatedSales: 'SALE002' },
{ id: 'INS003', name: '张师傅', phone: '13765432109', workId: 'W003', type: 'installer', region: '南京市秦淮区', gridName: 'C网格', status: 'active', createTime: '2025-09-02 10:00:00', associatedSales: 'SALE003' },
{ id: 'INS004', name: '刘师傅', phone: '13654321098', workId: 'W004', type: 'installer', region: '南京市建邺区', gridName: 'D网格', status: 'active', createTime: '2025-09-03 11:00:00', associatedSales: 'SALE004' },
{ id: 'INS005', name: '陈师傅', phone: '13543210987', workId: 'W005', type: 'installer', region: '南京市鼓楼区', gridName: 'E网格', status: 'inactive', createTime: '2025-09-04 12:00:00', associatedSales: 'SALE005' },
{ id: 'INS006', name: '赵师傅', phone: '13432109876', workId: 'W006', type: 'installer', region: '南京市浦口区', gridName: 'F网格', status: 'active', createTime: '2025-09-05 13:00:00', associatedSales: 'SALE006' },
{ id: 'INS007', name: '孙师傅', phone: '13321098765', workId: 'W007', type: 'installer', region: '南京市栖霞区', gridName: 'G网格', status: 'active', createTime: '2025-09-06 14:00:00', associatedSales: 'SALE007' },
{ id: 'INS008', name: '周师傅', phone: '13210987654', workId: 'W008', type: 'installer', region: '南京市雨花台区', gridName: 'H网格', status: 'active', createTime: '2025-09-07 15:00:00', associatedSales: 'SALE008' },
{ id: 'INS009', name: '吴师傅', phone: '13109876543', workId: 'W009', type: 'installer', region: '南京市江宁区', gridName: 'I网格', status: 'active', createTime: '2025-09-08 16:00:00', associatedSales: 'SALE009' },
{ id: 'INS010', name: '郑师傅', phone: '13098765432', workId: 'W010', type: 'installer', region: '南京市六合区', gridName: 'J网格', status: 'inactive', createTime: '2025-09-09 17:00:00', associatedSales: 'SALE010' },
{ id: 'SALE001', name: '张营销', phone: '13912345678', workId: 'S001', type: 'sales', region: '南京市玄武区', gridName: 'A网格', status: 'active', createTime: '2025-09-01 09:00:00' },
{ id: 'SALE002', name: '陈营销', phone: '13923456789', workId: 'S002', type: 'sales', region: '南京市玄武区', gridName: 'B网格', status: 'active', createTime: '2025-09-01 09:00:00' },
{ id: 'SALE003', name: '杨营销', phone: '13934567890', workId: 'S003', type: 'sales', region: '南京市秦淮区', gridName: 'C网格', status: 'active', createTime: '2025-09-02 10:00:00' },
{ id: 'SALE004', name: '黄营销', phone: '13945678901', workId: 'S004', type: 'sales', region: '南京市建邺区', gridName: 'D网格', status: 'active', createTime: '2025-09-03 11:00:00' },
{ id: 'SALE005', name: '徐营销', phone: '13956789012', workId: 'S005', type: 'sales', region: '南京市鼓楼区', gridName: 'E网格', status: 'active', createTime: '2025-09-04 12:00:00' },
{ id: 'SALE006', name: '朱营销', phone: '13967890123', workId: 'S006', type: 'sales', region: '南京市浦口区', gridName: 'F网格', status: 'active', createTime: '2025-09-05 13:00:00' },
{ id: 'SALE007', name: '林营销', phone: '13978901234', workId: 'S007', type: 'sales', region: '南京市栖霞区', gridName: 'G网格', status: 'inactive', createTime: '2025-09-06 14:00:00' },
{ id: 'SALE008', name: '何营销', phone: '13989012345', workId: 'S008', type: 'sales', region: '南京市雨花台区', gridName: 'H网格', status: 'active', createTime: '2025-09-07 15:00:00' },
{ id: 'SALE009', name: '罗营销', phone: '13990123456', workId: 'S009', type: 'sales', region: '南京市江宁区', gridName: 'I网格', status: 'active', createTime: '2025-09-08 16:00:00' },
{ id: 'SALE010', name: '郭营销', phone: '13901234567', workId: 'S010', type: 'sales', region: '南京市六合区', gridName: 'J网格', status: 'active', createTime: '2025-09-09 17:00:00' },
{ id: 'INS011', name: '冯师傅', phone: '13812345678', workId: 'W011', type: 'installer', region: '南京市溧水区', gridName: 'K网格', status: 'active', createTime: '2025-09-10 18:00:00' },
{ id: 'INS012', name: '钱师傅', phone: '13823456789', workId: 'W012', type: 'installer', region: '南京市高淳区', gridName: 'L网格', status: 'active', createTime: '2025-09-11 19:00:00' },
{ id: 'SALE011', name: '宋营销', phone: '13834567890', workId: 'S011', type: 'sales', region: '南京市溧水区', gridName: 'K网格', status: 'active', createTime: '2025-09-10 18:00:00' },
{ id: 'SALE012', name: '梁营销', phone: '13845678901', workId: 'S012', type: 'sales', region: '南京市高淳区', gridName: 'L网格', status: 'inactive', createTime: '2025-09-11 19:00:00' }
]
export default { export default {
name: 'PersonnelManagement', name: 'PersonnelManagement',
data() { data() {
return { return {
addressStore:{ importStore:{
isShow: false,
type: '1'
},
updatePersonStore:{
isShow: false,
row: {},
personnelName: '',
personnelCode: '',
relatedMarketingCode: '',
phone: '',
status: '',
city: '', city: '',
cityName: '', cityArr: [],
cityArr: this.addressStoreData,
county: '', county: '',
countyName: '',
countyArr: [], countyArr: [],
grid: '', grid: '',
gridName: '', gridArr: [],
gridArr: []
}, },
getData:{ yxPersonList:[],
city: '320200', formData:{
cityName: '', personnelCode: '',
personnelTypes: '',
personnelTypes: []
},
tableData: [],
pageStore:{
currentPage: 1,
pageSize: 20,
total: 0
},
addressStore:{
city: '',
cityArr: [],
county: '', county: '',
countyName: '', countyArr: [],
grid: '', grid: '',
gridName: '', gridArr: []
}, },
getData:{
personnelTypeMap,
personnel: mockPersonnel,
searchTerm: '',
searchPhone: '',
filterType: 'all',
filterRegion: '',
isAddDialogOpen: false,
isImportDialogOpen: false,
editingPersonnel: null,
selectedFile: null,
currentPage: 1,
pageSize: 20,
personnelForm: {
name: '',
workId: '',
phone: '',
type: 'installer',
city: '', city: '',
cityArr: this.addressStoreData,
county: '', county: '',
countyArr: [],
grid: '', grid: '',
gridArr: [],
status: 'active',
associatedSales: ''
}, },
personnelRules: { personnelRules: {
name: [ personnelName: [
{ required: true, message: '请输入姓名', trigger: 'blur' } { required: true, message: '请输入姓名', trigger: 'blur' }
], ],
workId: [ personnelCode: [
{ required: true, message: '请输入工号', trigger: 'blur' } { required: true, message: '请输入工号', trigger: 'blur' }
], ],
phone: [ phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' } { required: true, message: '请输入手机号', trigger: 'blur' }
], ],
region: [ personnelType: [
{ required: true, message: '请输入所属区域', trigger: 'blur' } { required: true, message: '请选择人员类型', trigger: 'blur' }
] ],
} relatedMarketingCode: [
} { required: true, message: '请选择关联支撑人员', trigger: 'blur' }
}, ],
computed: { status: [
filteredPersonnel() { { required: true, message: '请选择状态', trigger: 'blur' }
return this.personnel.filter(person => { ],
const matchesSearch = !this.searchTerm || grid: [
person.name.toLowerCase().includes(this.searchTerm.toLowerCase()) || { required: true, message: '请选择所属网格', trigger: 'blur' }
person.workId.toLowerCase().includes(this.searchTerm.toLowerCase()) || ],
person.phone.includes(this.searchTerm) },
const matchesType = !this.filterType || this.filterType === 'all' || person.type === this.filterType isImportDialogOpen: false,
selectedFile: null,
// 区域筛选逻辑 - 只按区县筛选
const matchesRegion = !this.filterRegion || person.region.includes(this.filterRegion)
return matchesSearch && matchesType && matchesRegion
})
},
paginatedPersonnel() {
const startIndex = (this.currentPage - 1) * this.pageSize
const endIndex = startIndex + this.pageSize
return this.filteredPersonnel.slice(startIndex, endIndex)
},
activeSalesPersons() {
return this.personnel.filter(person => person.type === 'sales' && person.status === 'active')
} }
}, },
created(){ created(){
this.setAddressShow() let pa = JSON.parse(localStorage.getItem('accountInfo'))
this.getData.city = pa.cityCode||''
this.getData.county = pa.countyCode||''
this.getData.grid = pa.gridCode||''
this.queryArea()
this.handleFilter()
this.queryYxPerson()
}, },
methods: { methods: {
queryArea(){
this.apiReq.queryLevelAllArea({
areaLevel: 2,
parentAreaCode: '320000'
}).then(res=>{
if(res.code == 200){
this.updatePersonStore.cityArr = res.data
this.addressStore.cityArr = res.data
this.setAddressShow()
}
})
},
personnelTypeChange(){
this.updatePersonStore.relatedMarketingCode = ''
},
queryYxPerson(){
this.apiReq.queryAllPerson({
pageSize: 20,
pageNum: 1,
personnelTypes: [2]
}).then(res=>{
if(res.code == 200){
this.yxPersonList = res.data.records
}
})
},
updatePerson(row){
let ud = this.updatePersonStore
if(row.id){
ud.row =row
ud.personnelName = row.personnelName
ud.personnelCode = row.personnelCode
ud.phone = row.phone
ud.status = row.status
ud.relatedMarketingCode = row.relatedMarketingCode
}else{
ud.row = {}
ud.personnelName = ''
ud.personnelCode = ''
ud.phone = ''
ud.status = ''
ud.relatedMarketingCode = ''
if(!this.getData.city){
ud.city = ''
ud.county = ''
ud.countyArr = []
ud.grid = ''
ud.gridArr = []
}else if(!this.getData.county){
ud.county = ''
ud.grid = ''
ud.gridArr = []
}else if(!this.getData.grid){
ud.grid = ''
}
}
this.updatePersonStore.isShow = true
},
timeRender(row,column){
return this.common.detailTime(new Date(row[column.property]).getTime())
},
handleFilter(){
this.pageStore.currentPage = 1
this.queryPersonList()
},
queryPersonList(){
this.apiReq.queryAllPerson({
pageSize: this.pageStore.pageSize,
pageNum: this.pageStore.currentPage,
areaCode: this.addressStore.county || this.addressStore.city,
gridCode: this.addressStore.grid,
...this.formData
}).then(res=>{
if(res.code == 200){
this.tableData = res.data.records
this.pageStore.total = res.data.total
}else{
this.$message.error(res.message)
}
})
},
setAddressShow(){ setAddressShow(){
let ad = this.addressStore let ad = this.addressStore
let pd = this.personnelForm let pd = this.updatePersonStore
let gd = this.getData let gd = this.getData
if(gd.city){ if(gd.city){
ad.city = gd.city ad.city = gd.city
ad.cityArr.forEach(item=>{ this.cityChange(ad.city,'all')
if(item.value == ad.city){
ad.countyArr = item.children
}
})
if(gd.county){ if(gd.county){
ad.county = gd.county ad.county = gd.county
ad.countyArr.forEach(item=>{ this.countyChange(ad.county,'all')
if(item.value == ad.county){
ad.gridArr = item.children
}
})
if(ad.gridArr.length == 1){
gd.grid = 'moren'
}
if(gd.grid){ if(gd.grid){
ad.grid = gd.grid ad.grid = gd.grid
...@@ -492,101 +522,93 @@ export default { ...@@ -492,101 +522,93 @@ export default {
} }
pd.city = ad.city pd.city = ad.city
pd.countyArr = ad.countyArr
pd.county = ad.county pd.county = ad.county
pd.gridArr = ad.gridArr
pd.grid = ad.grid pd.grid = ad.grid
}, },
cityChange(value,type){ cityChange(value,type){
let ad = type=='person'?this.personnelForm:this.addressStore let ad = type=='person'?this.updatePersonStore:this.addressStore
ad.city = value ad.city = value
ad.county = '' ad.county = ''
ad.grid = '' ad.grid = ''
ad.cityArr.forEach(item=>{ this.apiReq.queryLevelAllArea({
if(item.value == ad.city){ areaLevel: 3,
ad.countyArr = item.children parentAreaCode: value
}).then(res=>{
if(res.code == 200){
ad.countyArr = res.data
if(type == 'all'){
this.updatePersonStore.countyArr = res.data
}
} }
}) })
}, },
countyChange(value,type){ countyChange(value,type){
let ad = type=='person'?this.personnelForm:this.addressStore let ad = type=='person'?this.updatePersonStore:this.addressStore
ad.county = value ad.county = value
ad.grid = '' ad.grid = ''
ad.countyArr.forEach(item=>{ this.apiReq.queryGridList({
if(item.value == ad.county){ areaCode:value
ad.gridArr = item.children }).then(res=>{
if(res.code == 200){
ad.gridArr = res.data
if(type == 'all'){
this.updatePersonStore.gridArr = res.data
}
} }
}) })
}, },
handlePersonnelTypeChange(value) {
// 当人员类型改变时,重置关联字段
if (value === 'sales') {
this.personnelForm.associatedSales = ''
} else {
this.personnelForm.gridName = ''
}
},
editPersonnel(person) {
this.editingPersonnel = person
this.personnelForm = { ...person }
this.isAddDialogOpen = true
},
deletePersonnel(id) { deletePersonnel(id) {
this.$confirm('确定要删除该人员吗?此操作不可撤销。', '确认删除', { this.$confirm('确定要删除该人员吗?此操作不可撤销。', '确认删除', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.personnel = this.personnel.filter(p => p.id !== id) this.apiReq.deletePerson({
this.$message.success('人员删除成功') id,
}).catch(() => { }).then(res=>{
// 用户取消删除 if(res.code == '200'){
this.handleFilter()
this.$message.success(res.message)
}else{
this.$message.error(res.message)
}
})
}) })
}, },
submitPersonnelForm() { submitPersonnelForm() {
this.$refs.personnelForm.validate((valid) => { this.$refs.personnelForm.validate((valid) => {
if (valid) { if (valid) {
if (this.editingPersonnel) { let ud = this.updatePersonStore
// 更新人员信息 this.apiReq.addNewPerson({
const index = this.personnel.findIndex(p => p.id === this.editingPersonnel.id) personnelName: ud.personnelName,
if (index !== -1) { personnelCode: ud.personnelCode,
this.$set(this.personnel, index, { ...this.editingPersonnel, ...this.personnelForm }) phone: ud.phone,
this.$message.success('人员信息更新成功') personnelType: ud.personnelType,
status: ud.status,
relatedMarketingCode: ud.relatedMarketingCode,
areaCode: ud.county,
gridCode: ud.grid
}).then(res=>{
if(res.code == '200'){
let __this = this
this.$alert('添加成功', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.handleFilter()
__this.updatePersonStore.isShow = false
}
});
}else{
this.$message.error(res.message)
} }
} else { })
// 添加新人员
const newPersonnel = {
...this.personnelForm,
id: `NEW${Date.now()}`,
createTime: new Date().toLocaleString('zh-CN')
}
this.personnel.push(newPersonnel)
this.$message.success('人员添加成功')
}
// 重置表单和状态
this.resetPersonnelForm()
this.isAddDialogOpen = false
this.editingPersonnel = null
} }
}) })
}, },
resetPersonnelForm() {
this.personnelForm = {
name: '',
workId: '',
phone: '',
type: 'installer',
region: '',
gridName: '',
status: 'active',
associatedSales: ''
}
},
handleFileSelect(event) { handleFileSelect(event) {
const file = event.target.files[0] const file = event.target.files[0]
if (file) { if (file) {
...@@ -594,62 +616,7 @@ export default { ...@@ -594,62 +616,7 @@ export default {
} }
}, },
downloadTemplate(type) { downloadTemplate(type) {
// 创建Excel模板数据 this.common.downloadLocalFile('static/file/'+type+'_tem.xlsx', 'application/xlsx', (type=='gongwei'?'工维人员批量新增模板':'营销人员批量新增模板'), 'xlsx')
const headers = type === 'installer'
? ['工号', '手机号', '姓名', '网格名称', '对应营销人员工号', '对应营销人员手机号', '对应营销人员姓名']
: ['工号', '手机号', '姓名', '网格名称']
const sampleData = type === 'installer'
? [
['IF001', '13812345678', '张师傅', '玄武区A网格', 'SA001', '13898765432', '李营销'],
['IF002', '13887654321', '王师傅', '秦淮区B网格', 'SA002', '13876543210', '赵营销']
]
: [
['SA001', '13898765432', '李营销', '玄武区A网格'],
['SA002', '13876543210', '赵营销', '秦淮区B网格']
]
// 创建Excel格式内容(HTML表格格式,Excel能正确识别)
const excelContent = `
<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">
<head>
<meta charset="utf-8">
<!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>Sheet1</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]-->
</head>
<body>
<table>
<tr>
${headers.map(header => `<th style="background-color: #f0f0f0; font-weight: bold; border: 1px solid #ccc; padding: 8px;">${header}</th>`).join('')}
</tr>
${sampleData.map(row =>
`<tr>
${row.map(cell => `<td style="border: 1px solid #ccc; padding: 8px;">${cell}</td>`).join('')}
</tr>`
).join('')}
</table>
</body>
</html>
`
// 创建Blob并下载
const blob = new Blob([excelContent], {
type: 'application/vnd.ms-excel;charset=utf-8;'
})
// 下载文件
const link = document.createElement('a')
if (link.download !== undefined) {
const url = URL.createObjectURL(blob)
link.setAttribute('href', url)
link.setAttribute('download', `${type === 'installer' ? '装维师傅' : '营销人员'}导入模板.xls`)
link.style.visibility = 'hidden'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
}
this.$message.success(`${type === 'installer' ? '装维师傅' : '营销人员'}模板下载成功`)
}, },
handleBatchImport() { handleBatchImport() {
if (!this.selectedFile) { if (!this.selectedFile) {
...@@ -662,17 +629,16 @@ export default { ...@@ -662,17 +629,16 @@ export default {
this.isImportDialogOpen = false this.isImportDialogOpen = false
this.selectedFile = null this.selectedFile = null
}, },
getAssociatedSalesPerson(associatedSalesId) {
if (!associatedSalesId) return null
return this.personnel.find(p => p.id === associatedSalesId)
},
handleSizeChange(size) { handleSizeChange(size) {
this.pageSize = size this.pageStore.pageSize = size
this.currentPage = 1
this.handleFilter()
}, },
handleCurrentChange(page) { handleCurrentChange(page) {
this.currentPage = page this.pageStore.currentPage = page
}
this.queryPersonList()
},
} }
} }
</script> </script>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div class="tag-management"> <div class="tag-management">
<div class="toolbar"> <div class="toolbar">
<p class="description">管理商机录入时的标签选项</p> <p class="description">管理商机录入时的标签选项</p>
<el-button @click="isAddDialogOpen = true" type="primary" size="small"> <el-button @click="addTag" type="primary" size="small">
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
添加标签 添加标签
</el-button> </el-button>
...@@ -16,34 +16,34 @@ ...@@ -16,34 +16,34 @@
:md="8" :md="8"
:lg="8" :lg="8"
:xl="6" :xl="6"
v-for="tag in tags" v-for="tag in tagsList"
:key="tag.id" :key="tag.id"
> >
<el-card class="tag-card"> <el-card class="tag-card">
<div class="tag-header"> <div class="tag-header">
<div class="tag-title" v-if="tag.isSystem"> <div class="tag-title" v-if="tag.isDefault" style="display: flex;align-items:center;">
<h3>{{ tag.name }}</h3> <h3>{{ tag.tagName }}</h3>
<el-tag size="mini" type="info" style="margin-left: 8px;">系统默认</el-tag> <el-tag size="mini" type="info" style="margin-left: 8px;">系统默认</el-tag>
</div> </div>
<h3 v-else>{{ tag.name }}</h3> <h3 v-else>{{ tag.tagName }}</h3>
</div> </div>
<div class="tag-info" v-if="tag.category"> <div class="tag-info" v-if="tag.tagCategoryName">
<i class="el-icon-folder"></i> <i class="el-icon-folder"></i>
<span>分类:{{ tag.category }}</span> <span>分类:{{ tag.tagCategoryName }}</span>
</div> </div>
<div class="tag-description" v-if="tag.description"> <div class="tag-description">
<i class="el-icon-document"></i> <i class="el-icon-document"></i>
<span>{{ tag.description }}</span> <span>{{ tag.tagDescription || '暂无' }}</span>
</div> </div>
<div class="tag-footer"> <div class="tag-footer">
<el-switch <el-switch
v-model="tag.enabled" v-model="tag.isEnabled"
active-text="启用" active-text="启用"
inactive-text="禁用" inactive-text="禁用"
@change="toggleTag(tag.id)" @change="toggleTag(tag.id,tag.isEnabled)"
:disabled="tag.isSystem" :disabled="tag.isSystem"
></el-switch> ></el-switch>
...@@ -51,14 +51,13 @@ ...@@ -51,14 +51,13 @@
<el-button <el-button
size="mini" size="mini"
@click="editTag(tag)" @click="editTag(tag)"
:disabled="tag.isSystem"
> >
编辑 编辑
</el-button> </el-button>
<el-button <el-button
size="mini" size="mini"
@click="deleteTag(tag.id)" @click="deleteTag(tag.id)"
v-if="!tag.isSystem" v-if="!tag.isDefault"
> >
删除 删除
</el-button> </el-button>
...@@ -69,42 +68,55 @@ ...@@ -69,42 +68,55 @@
</el-row> </el-row>
</div> </div>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageStore.currentPage"
:page-sizes="[20, 50, 100]"
:page-size="pageStore.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="pageStore.total"
>
</el-pagination>
</div>
<!-- 添加/编辑标签对话框 --> <!-- 添加/编辑标签对话框 -->
<el-dialog <el-dialog
:title="editingTag ? '编辑标签' : '添加标签'" :title="editingTag ? '编辑标签' : '添加标签'"
:visible.sync="isAddDialogOpen" :visible.sync="isAddDialogOpen"
width="500px" width="500px">
>
<el-form :model="tagForm" :rules="tagRules" ref="tagForm" label-width="100px"> <el-form :model="tagForm" :rules="tagRules" ref="tagForm" label-width="100px">
<el-form-item label="标签名称" prop="name"> <el-form-item label="标签名称" prop="tagName">
<el-input <el-input
v-model="tagForm.name" v-model="tagForm.tagName"
placeholder="请输入标签名称" placeholder="请输入标签名称"
:disabled="!!editingTag && editingTag.isSystem" :disabled="!!editingTag && editingTag.isDefault"
></el-input> ></el-input>
<p v-if="editingTag && editingTag.isSystem" class="system-tag-info">系统默认标签只能修改描述信息</p> <p v-if="editingTag && editingTag.isDefault" class="system-tag-info">系统默认标签只能修改描述信息</p>
</el-form-item> </el-form-item>
<el-form-item label="标签分类" prop="category"> <el-form-item label="标签分类" prop="tagCategoryName">
<el-input <el-input
v-model="tagForm.category" v-model="tagForm.tagCategoryName"
placeholder="请输����标签分类" placeholder="请输标签分类"
:disabled="!!editingTag && editingTag.isSystem" :disabled="!!editingTag && editingTag.isDefault"
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item label="标签描述" prop="description"> <el-form-item label="标签描述" prop="description">
<el-input <el-input
v-model="tagForm.description" v-model="tagForm.tagDescription"
type="textarea" type="textarea"
:rows="3" :rows="3"
placeholder="可选,描述此标签的用途" placeholder="可选,描述此标签的用途"
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item label="启用标签" v-if="!(editingTag && editingTag.isSystem)"> <el-form-item label="启用标签" v-if="!(editingTag && editingTag.isDefault)">
<el-switch <el-switch
v-model="tagForm.enabled" v-model="tagForm.isEnabled"
></el-switch> ></el-switch>
</el-form-item> </el-form-item>
</el-form> </el-form>
...@@ -118,39 +130,82 @@ ...@@ -118,39 +130,82 @@
</template> </template>
<script> <script>
// 模拟标签数据
const mockTags = [
{ id: 'SYS001', name: '小微工程', color: '#3b82f6', order: 1, description: '选择该标签后商机自动分配给区县负责人', enabled: true, category: '小微工程', isSystem: true },
{ id: 'SYS002', name: '政企业务', color: '#10b981', order: 2, description: '选择该标签后商机自动分配给区县负责人', enabled: true, category: '政企业务', isSystem: true },
{ id: '3', name: '网络升级', color: '#f59e0b', order: 3, description: '用户需要升级网络带宽', enabled: true, category: '产品服务' },
{ id: '4', name: '融合套餐', color: '#ef4444', order: 4, description: '手机+宽带融合套餐', enabled: true, category: '业务类型' },
{ id: '5', name: '智能家居', color: '#8b5cf6', order: 5, description: '智能家居产品需求', enabled: false, category: '产品服务' },
{ id: '6', name: '家庭安防', color: '#ec4899', order: 6, description: '家庭安防监控需求', enabled: true, category: '产品服务' }
]
export default { export default {
name: 'TagManagement', name: 'TagManagement',
data() { data() {
return { return {
tags: mockTags, tagsList: [],
pageStore:{
currentPage: 1,
pageSize: 20,
total: 0
},
isAddDialogOpen: false, isAddDialogOpen: false,
editingTag: null, editingTag: null,
tagForm: { tagForm: {
name: '', tagName: '',
color: '#3b82f6',
order: 1, order: 1,
description: '', tagDescription: '',
enabled: true, isEnabled: true,
category: '' tagCategoryName: '',
isDefault: false
}, },
tagRules: { tagRules: {
name: [ tagName: [
{ required: true, message: '请输入标签名称', trigger: 'blur' } { required: true, message: '请输入标签名称', trigger: 'blur' }
],
tagCategoryName: [
{ required: true, message: '请输入标签分类', trigger: 'blur' }
] ]
} },
} }
}, },
created(){
this.handleFilter()
},
methods: { methods: {
handleSizeChange(size) {
this.pageStore.pageSize = size
this.handleFilter()
},
handleCurrentChange(page) {
this.pageStore.currentPage = page
this.queryBusiLabel()
},
handleFilter(){
this.pageStore.currentPage = 1
this.queryBusiLabel()
},
queryBusiLabel(){
this.apiReq.queryBusiLabelList({
pageNum: this.pageStore.currentPage,
pageSize: this.pageStore.pageSize
}).then(res=>{
if(res.code == 200){
this.tagsList = res.data.records
this.pageStore.total = res.data.total
}
})
},
addTag(){
this.editingTag = null
this.tagForm = {
tagName: '',
tagDescription: '',
isEnabled: true,
tagCategoryName: '',
tagCategory: '3',
isDefault: false
}
this.isAddDialogOpen = true
},
editTag(tag) { editTag(tag) {
this.editingTag = tag this.editingTag = tag
this.tagForm = { ...tag } this.tagForm = { ...tag }
...@@ -162,63 +217,63 @@ export default { ...@@ -162,63 +217,63 @@ export default {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.tags = this.tags.filter(t => t.id !== id) this.apiReq.deleteBusiLabel({
this.$message.success('标签删除成功') id,
}).catch(() => { }).then(res=>{
// 用户取消删除 if(res.code == 200){
let __this = this
this.$alert('删除成功', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.handleFilter()
}
});
}else{
this.$message.error(res.message)
}
})
}) })
}, },
toggleTag(id) { toggleTag(id,enabled) {
const tag = this.tags.find(t => t.id === id) this.apiReq.updateBusiLabelStatus({
if (tag) { id,
const action = tag.enabled ? '开启' : '关闭' enabled
this.$message.success(`标签已${action}`) }).then(res=>{
} if(res.code == 200){
}else{
this.$message.error(res.message)
}
})
}, },
submitTagForm() { submitTagForm() {
this.$refs.tagForm.validate((valid) => { this.$refs.tagForm.validate((valid) => {
if (valid) { if (valid) {
if (this.editingTag) { this.apiReq.createAndUpdateTag({
// 更新标签信息 id: this.editingTag?this.editingTag.id: '',
const index = this.tags.findIndex(t => t.id === this.editingTag.id) tagCategory: this.tagForm.tagCategory,
if (index !== -1) { tagCategoryName: this.tagForm.tagCategoryName,
// 保留系统标签的特殊属性 tagDescription: this.tagForm.tagDescription,
const isSystem = this.tags[index].isSystem tagName: this.tagForm.tagName,
this.$set(this.tags, index, { isDefault: this.tagForm.isDefault?1:0,
...this.tags[index], isEnabled: this.tagForm.isEnabled?1:0,
...this.tagForm, }).then(res=>{
isSystem if(res.code == '200'){
}) let __this = this
this.$message.success('标签更新成功') this.$alert(this.editingTag?'修改成功':'添加成功', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.handleFilter()
__this.isAddDialogOpen = false
}
});
}else{
this.$message.error(res.message)
} }
} else { })
// 添加新标签
const newTag = {
...this.tagForm,
id: `TAG${Date.now()}`,
order: this.tags.length + 1
}
this.tags.push(newTag)
this.$message.success('标签添加成功')
}
// 重置表单和状态
this.resetTagForm()
this.isAddDialogOpen = false
this.editingTag = null
} }
}) })
}, },
resetTagForm() {
this.tagForm = {
name: '',
color: '#3b82f6',
order: 1,
description: '',
enabled: true,
category: ''
}
}
} }
} }
</script> </script>
...@@ -260,6 +315,7 @@ export default { ...@@ -260,6 +315,7 @@ export default {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 10px; margin-bottom: 10px;
height: 18px;
font-size: 14px; font-size: 14px;
color: #606266; color: #606266;
......
...@@ -91,7 +91,7 @@ export default new Vuex.Store({ ...@@ -91,7 +91,7 @@ export default new Vuex.Store({
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 模拟登录验证 // 模拟登录验证
const userData = mockUsers[username] const userData = mockUsers[username]
if (userData && userData.password === password) { if (userData && userData.password == password) {
commit('SET_USER', userData.user) commit('SET_USER', userData.user)
localStorage.setItem('user', JSON.stringify(userData.user)) localStorage.setItem('user', JSON.stringify(userData.user))
resolve(true) resolve(true)
......
...@@ -35,10 +35,9 @@ ...@@ -35,10 +35,9 @@
<template v-for="item in menuItems"> <template v-for="item in menuItems">
<!-- 有子菜单的项 --> <!-- 有子菜单的项 -->
<el-submenu <el-submenu
v-if="item.subItems && hasPermission(item.permissions)" v-if="item.subItems"
:key="item.title" :key="item.title"
:index="item.title" :index="item.title" >
>
<template slot="title"> <template slot="title">
<i :class="item.icon"></i> <i :class="item.icon"></i>
<span slot="title">{{ item.title }}</span> <span slot="title">{{ item.title }}</span>
...@@ -46,8 +45,8 @@ ...@@ -46,8 +45,8 @@
<el-menu-item <el-menu-item
v-for="subItem in item.subItems" v-for="subItem in item.subItems"
:key="subItem.url" :key="subItem.url"
:index="subItem.url" v-if="hasPermissionChild(subItem.url)"
> :index="subItem.url">
<i :class="subItem.icon"></i> <i :class="subItem.icon"></i>
<span slot="title">{{ subItem.title }}</span> <span slot="title">{{ subItem.title }}</span>
</el-menu-item> </el-menu-item>
...@@ -55,7 +54,7 @@ ...@@ -55,7 +54,7 @@
<!-- 无子菜单的项 --> <!-- 无子菜单的项 -->
<el-menu-item <el-menu-item
v-else-if="item.url && hasPermission(item.permissions)" v-else-if="item.url && hasPermission(item.url)"
:key="item.title" :key="item.title"
:index="item.url" :index="item.url"
> >
...@@ -75,17 +74,17 @@ ...@@ -75,17 +74,17 @@
</el-avatar> </el-avatar>
<transition name="fade"> <transition name="fade">
<div v-if="!isCollapse" class="user-details"> <div v-if="!isCollapse" class="user-details">
<span class="user-name">{{ currentUser.name }}</span> <span class="user-name">{{ acc.accountName }}</span>
<span class="user-region">{{ currentUser.region }}</span> <span class="user-region">{{ acc.areaName }}</span>
</div> </div>
</transition> </transition>
</div> </div>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item command="profile"> <!-- <el-dropdown-item command="profile">
<i class="el-icon-user"></i> <i class="el-icon-user"></i>
个人信息 个人信息
</el-dropdown-item> </el-dropdown-item> -->
<el-dropdown-item divided command="logout"> <el-dropdown-item command="logout">
<i class="el-icon-switch-button"></i> <i class="el-icon-switch-button"></i>
退出登录 退出登录
</el-dropdown-item> </el-dropdown-item>
...@@ -120,7 +119,7 @@ ...@@ -120,7 +119,7 @@
<div class="header-right"> <div class="header-right">
<span v-if="isDemoMode" class="demo-badge">演示模式</span> <span v-if="isDemoMode" class="demo-badge">演示模式</span>
<span class="current-region">当前区域: {{ currentUser.region }}</span> <span class="current-region">当前区域: {{ acc.areaName }}</span>
</div> </div>
</el-header> </el-header>
...@@ -201,22 +200,19 @@ export default { ...@@ -201,22 +200,19 @@ export default {
return { return {
isCollapse: false, isCollapse: false,
menuItems: menuItems, menuItems: menuItems,
breadcrumbMap: breadcrumbMap breadcrumbMap: breadcrumbMap,
acc: JSON.parse(localStorage.getItem('accountInfo'))
} }
}, },
computed: { computed: {
...mapGetters(['user', 'isAuthenticated']),
currentUser() {
return this.user || {}
},
userInitial() { userInitial() {
return this.currentUser.name ? this.currentUser.name.charAt(0) : '' return this.acc.accountName ? this.acc.accountName.charAt(0) : ''
}, },
userAvatar() { userAvatar() {
return '' return ''
}, },
isDemoMode() { isDemoMode() {
return sessionStorage.getItem('demoMode') === 'true' return sessionStorage.getItem('demoMode') == 'true'
}, },
activeMenu() { activeMenu() {
const { meta, path } = this.$route const { meta, path } = this.$route
...@@ -245,18 +241,43 @@ export default { ...@@ -245,18 +241,43 @@ export default {
toggleCollapse() { toggleCollapse() {
this.isCollapse = !this.isCollapse this.isCollapse = !this.isCollapse
}, },
hasPermission(permissions) { hasPermission(url) {
if (!this.user || !this.user.permissions) return false if(url=='/grid-query' && this.acc.gridCode){
return permissions.some(permission => this.user.permissions.includes(permission)) return false
}
return true
}, },
async handleUserCommand(command) { hasPermissionChild(url){
if (command === 'logout') { if(url == '/system/opportunity-config'){
await this.logout() if(this.acc.cityCode && !this.acc.countyCode){
this.$message.success('退出登录成功') return true
this.$router.push('/login') }
} else if (command === 'profile') {
this.$message.info('个人信息功能开发中') return false
} }
return true
},
async handleUserCommand(command) {
this.apiReq.logout().then(res=>{
if(res.code == '200'){
localStorage.removeItem('accountInfo')
localStorage.removeItem('tokenInfo')
this.$router.push('/login')
}else{
this.$message.error(res.message)
}
})
// if (command == 'logout') {
// await this.logout()
// this.$message.success('退出登录成功')
// this.$router.push('/login')
// } else if (command == 'profile') {
// this.$message.info('个人信息功能开发中')
// }
} }
}, },
created() { created() {
......
...@@ -5,28 +5,15 @@ ...@@ -5,28 +5,15 @@
</div> </div>
<el-card class="filter-card"> <el-card class="filter-card">
<div class="filter-header">
<h3 class="filter-title">查询条件</h3>
<div class="filter-actions">
<el-button size="small" @click="handleQuery">
<i class="el-icon-search"></i>
查询
</el-button>
<el-button size="small" @click="resetQuery" type="default">
<i class="el-icon-refresh-left"></i>
重置
</el-button>
</div>
</div>
<div class="filter-content"> <div class="filter-content">
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="3"> <el-col :span="3">
<div class="search-wrapper"> <div class="search-wrapper">
<el-input <el-input
v-model="selectedGridName" v-model="formData.gridName"
placeholder="请输入网格名称" placeholder="请输入网格名称"
style="padding-left: 0;" style="padding-left: 0;"
clearable
@keyup.enter.native="handleFilter" @keyup.enter.native="handleFilter"
/> />
</div> </div>
...@@ -35,9 +22,10 @@ ...@@ -35,9 +22,10 @@
<el-col :span="3"> <el-col :span="3">
<div class="search-wrapper"> <div class="search-wrapper">
<el-input <el-input
v-model="selectedGrid" v-model="formData.gridCode"
placeholder="请输入网格ID" placeholder="请输入网格ID"
style="padding-left: 0;" style="padding-left: 0;"
clearable
@keyup.enter.native="handleFilter" @keyup.enter.native="handleFilter"
/> />
</div> </div>
...@@ -48,13 +36,12 @@ ...@@ -48,13 +36,12 @@
v-model="addressStore.city" v-model="addressStore.city"
placeholder="选择地市" placeholder="选择地市"
@change="cityChange" @change="cityChange"
:disabled="getData.city!=''"
clearable> clearable>
<el-option <el-option
v-for="item in addressStore.cityArr" v-for="item in addressStore.cityArr"
:key="item.value" :key="item.areaCode"
:label="item.name" :label="item.areaName"
:value="item.value" :value="item.areaCode"
></el-option> ></el-option>
</el-select> </el-select>
</el-col> </el-col>
...@@ -63,16 +50,28 @@ ...@@ -63,16 +50,28 @@
v-model="addressStore.county" v-model="addressStore.county"
placeholder="选择区县" placeholder="选择区县"
@change="countyChange" @change="countyChange"
:disabled="getData.county!=''"
clearable> clearable>
<el-option <el-option
v-for="item in addressStore.countyArr" v-for="item in addressStore.countyArr"
:key="item.value" :key="item.areaCode"
:label="item.name" :label="item.areaName"
:value="item.value" :value="item.areaCode"
></el-option> ></el-option>
</el-select> </el-select>
</el-col> </el-col>
<el-col :span="3">
<div class="filter-actions">
<el-button size="small" @click="handleQuery">
<i class="el-icon-search"></i>
查询
</el-button>
<el-button size="small" @click="resetQuery" type="default">
<i class="el-icon-refresh-left"></i>
重置
</el-button>
</div>
</el-col>
</el-row> </el-row>
</div> </div>
</el-card> </el-card>
...@@ -88,9 +87,9 @@ ...@@ -88,9 +87,9 @@
:data="tableData" :data="tableData"
border border
> >
<el-table-column prop="name" label="网格名称" width="220"></el-table-column> <el-table-column prop="gridName" label="网格名称" width="220"></el-table-column>
<el-table-column prop="id" label="网格ID" width="220"></el-table-column> <el-table-column prop="gridCode" label="网格ID" width="220"></el-table-column>
<el-table-column prop="region" label="所属区域" min-width="250"></el-table-column> <el-table-column prop="areaName" label="所属区域" min-width="250"></el-table-column>
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
...@@ -118,100 +117,103 @@ export default { ...@@ -118,100 +117,103 @@ export default {
return { return {
addressStore:{ addressStore:{
city: '', city: '',
cityName: '', cityArr: [],
cityArr: this.addressStoreData,
county: '', county: '',
countyName: '',
countyArr: [], countyArr: [],
}, },
getData:{ formData:{
city: '', gridName: '',
cityName: '', gridCode: '',
county: '',
countyName: ''
}, },
pageStore: {
selectedRegion: [],
selectedGrid: '',
selectedGridName: '',
tableData: [],
pageStore:{
currentPage: 1, currentPage: 1,
pageSize: 20, pageSize: 20,
total: 0 total: 0
} },
tableData: []
} }
}, },
created(){ created(){
this.queryArea()
this.handleQuery()
}, },
methods: { methods: {
setAddressShow(){ queryArea(){
let ad = this.addressStore this.apiReq.queryLevelAllArea({
let gd = this.getData areaLevel: 2,
parentAreaCode: '320000'
if(gd.city){ }).then(res=>{
ad.city = gd.city if(res.code == 200){
ad.cityArr.forEach(item=>{ this.addressStore.cityArr = res.data
if(item.value == ad.city){
ad.countyArr = item.children
}
})
if(gd.county){
ad.county = gd.county
ad.countyArr.forEach(item=>{
if(item.value == ad.county){
ad.gridArr = item.children
}
})
if(ad.gridArr.length == 1){
gd.grid = 'moren'
}
if(gd.grid){
ad.grid = gd.grid
}
} }
} })
}, },
cityChange(value){ cityChange(value){
let ad = this.addressStore let ad = this.addressStore
ad.city = value ad.city = value
ad.county = '' ad.county = ''
ad.grid = ''
ad.cityArr.forEach(item=>{ this.apiReq.queryLevelAllArea({
if(item.value == ad.city){ areaLevel: 3,
ad.countyArr = item.children parentAreaCode: value
}).then(res=>{
if(res.code == 200){
ad.countyArr = res.data
} }
}) })
}, },
handleQuery() { handleQuery() {
this.currentPage = 1 this.pageStore.currentPage = 1
this.$message.success('查询成功')
this.queryGridData()
},
queryGridData(){
if(this.addressStore.city && !this.addressStore.county){
this.$message.error('请选择区县')
return
}
this.apiReq.queryAllGridList({
areaCode: this.addressStore.county,
pageSize: this.pageStore.pageSize,
pageNum: this.pageStore.currentPage,
...this.formData
}).then(res=>{
if(res.code == 200){
this.tableData = res.data.records
this.pageStore.total = res.data.total
}
})
}, },
resetQuery() { resetQuery() {
this.selectedRegion = [] this.formData.gridCode = ''
this.selectedGrid = '' this.formData.gridName = ''
this.selectedGridName = '' this.addressStore.city = ''
this.addressStore.county = ''
this.addressStore.countyArr = []
}, },
handleSizeChange(size) { handleSizeChange(size) {
this.pageSize = size this.pageStore.pageSize = size
this.currentPage = 1
this.handleQuery()
}, },
handleCurrentChange(page) { handleCurrentChange(page) {
this.currentPage = page this.pageStore.currentPage = page
}
this.queryGridData()
},
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.grid-query { .grid-query {
.el-button.el-button--small{
padding: 13px 12px;
}
.page-header { .page-header {
margin-bottom: 24px; margin-bottom: 24px;
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
智能商机管理<br>助力业务增长 智能商机管理<br>助力业务增长
</h2> </h2>
<p class="login-desc"> <p class="login-desc">
装维师傅上门服务时发现商机,系统自动分配给营销人员跟进,实现业务闭环管理 装维师傅上门服务时发现商机,系统自动分配给支撑人员跟进,实现业务闭环管理
</p> </p>
</div> </div>
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
</div> </div>
<div class="login-feature-text"> <div class="login-feature-text">
<h3 class="login-feature-title">智能分配</h3> <h3 class="login-feature-title">智能分配</h3>
<p class="login-feature-desc">基于网格自动分配给合适的营销人员</p> <p class="login-feature-desc">基于网格自动分配给合适的支撑人员</p>
</div> </div>
</div> </div>
...@@ -75,23 +75,21 @@ ...@@ -75,23 +75,21 @@
<div class="login-tabs"> <div class="login-tabs">
<div <div
class="login-tab" class="login-tab"
:class="{ active: activeTab === 'account' }" :class="{ active: activeTab == 'account' }"
@click="activeTab = 'account'" @click="activeTab = 'account'">
>
账号密码登录 账号密码登录
</div> </div>
<div <div
class="login-tab" class="login-tab"
:class="{ active: activeTab === 'phone' }" :class="{ active: activeTab == 'phone' }"
@click="activeTab = 'phone'" @click="activeTab = 'phone'">
>
手机验证码登录 手机验证码登录
</div> </div>
</div> </div>
<!-- 账号密码登录表单 --> <!-- 账号密码登录表单 -->
<el-form <el-form
v-if="activeTab === 'account'" v-if="activeTab == 'account'"
:model="loginForm" :model="loginForm"
:rules="loginRules" :rules="loginRules"
ref="loginForm" ref="loginForm"
...@@ -143,7 +141,7 @@ ...@@ -143,7 +141,7 @@
<!-- 手机验证码登录表单 --> <!-- 手机验证码登录表单 -->
<el-form <el-form
v-if="activeTab === 'phone'" v-if="activeTab == 'phone'"
:model="phoneForm" :model="phoneForm"
:rules="phoneRules" :rules="phoneRules"
ref="phoneForm" ref="phoneForm"
...@@ -224,10 +222,6 @@ ...@@ -224,10 +222,6 @@
<p class="forgot-password-message"> <p class="forgot-password-message">
忘记密码请联系商机管理员重置密码 忘记密码请联系商机管理员重置密码
</p> </p>
<!-- <div class="forgot-password-tips">
<p><i class="el-icon-phone"></i> 联系电话:400-8888-8888</p>
<p><i class="el-icon-message"></i> 邮箱:admin@shangmensuixiao.com</p>
</div> -->
</div> </div>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="isForgotPasswordDialogVisible = false">确定</el-button> <el-button @click="isForgotPasswordDialogVisible = false">确定</el-button>
...@@ -247,8 +241,8 @@ export default { ...@@ -247,8 +241,8 @@ export default {
return { return {
activeTab: 'account', // 当前激活的登录方式 activeTab: 'account', // 当前激活的登录方式
loginForm: { loginForm: {
username: '', username: 'ntadmin',
password: '' password: 'ntadmin'
}, },
phoneForm: { phoneForm: {
phone: '', phone: '',
...@@ -289,7 +283,7 @@ export default { ...@@ -289,7 +283,7 @@ export default {
computed: { computed: {
// 是否可以发送验证码 // 是否可以发送验证码
canSendCode() { canSendCode() {
return this.phoneForm.phone && this.phoneForm.captcha && this.phoneForm.captcha === this.captchaText return this.phoneForm.phone && this.phoneForm.captcha
} }
}, },
mounted() { mounted() {
...@@ -310,7 +304,6 @@ export default { ...@@ -310,7 +304,6 @@ export default {
try { try {
await this.$refs.loginForm.validate() await this.$refs.loginForm.validate()
this.apiReq.login({ this.apiReq.login({
username: ld.username, username: ld.username,
password: ld.password password: ld.password
...@@ -332,104 +325,59 @@ export default { ...@@ -332,104 +325,59 @@ export default {
}, },
// 刷新图形验证码 // 刷新图形验证码
refreshCaptcha() { refreshCaptcha() {
// 生成随机验证码(模拟) this.apiReq.getImgCode().then(res=>{
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' if(res.code == 200){
let code = '' this.captchaImage = 'data:image/png;base64,'+res.data.img
for (let i = 0; i < 4; i++) { this.captchaId = res.data.codeId
code += chars.charAt(Math.floor(Math.random() * chars.length))
}
this.captchaText = code
// 创建canvas生成验证码图片
const canvas = document.createElement('canvas')
canvas.width = 100
canvas.height = 40
const ctx = canvas.getContext('2d')
// 背景色
ctx.fillStyle = '#f0f0f0'
ctx.fillRect(0, 0, 100, 40)
// 绘制验证码文字
ctx.font = '20px Arial'
ctx.fillStyle = '#333'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
for (let i = 0; i < code.length; i++) {
ctx.save()
ctx.translate(20 + i * 20, 20)
ctx.rotate((Math.random() - 0.5) * 0.3)
ctx.fillText(code[i], 0, 0)
ctx.restore()
}
// 添加干扰线
for (let i = 0; i < 4; i++) {
ctx.strokeStyle = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.3)`
ctx.beginPath()
ctx.moveTo(Math.random() * 100, Math.random() * 40)
ctx.lineTo(Math.random() * 100, Math.random() * 40)
ctx.stroke()
} }
})
this.captchaImage = canvas.toDataURL()
}, },
// 发送手机验证码 // 发送手机验证码
async sendPhoneCode() { async sendPhoneCode() {
if (this.codeCountdown > 0) return if (this.codeCountdown > 0) return
try { this.apiReq.getTelCode({
await this.$refs.phoneForm.validate(['phone', 'captcha']) phone: this.phoneForm.phone,
captchaCode: this.phoneForm.captcha,
// 模拟发送验证码 captchaId: this.captchaId
this.$message.success('验证码已发送到您的手机') }).then(res=>{
if(res.code == 200){
// 开始倒计时 this.$message.success('验证码发送成功')
this.codeCountdown = 60
this.countdownTimer = setInterval(() => { // 开始倒计时
this.codeCountdown-- this.codeCountdown = 60
if (this.codeCountdown <= 0) { this.countdownTimer = setInterval(() => {
clearInterval(this.countdownTimer) this.codeCountdown--
this.countdownTimer = null if (this.codeCountdown <= 0) {
} clearInterval(this.countdownTimer)
}, 1000) this.countdownTimer = null
}
} catch (error) { }, 1000)
if (this.phoneForm.captcha !== this.captchaText) { }else{
this.$message.error('图形验证码错误') this.$message.error(res.message)
this.refreshCaptcha()
this.phoneForm.captcha = ''
} }
} })
}, },
// 手机验证码登录 // 手机验证码登录
async handlePhoneLogin() { async handlePhoneLogin() {
try { try {
await this.$refs.phoneForm.validate() await this.$refs.phoneForm.validate()
this.phoneLoading = true
this.apiReq.pohoneLogin({
// 模拟手机号登录逻辑 phone: this.phoneForm.phone,
// 这里可以添加手机号到用户名的映射逻辑 personnelCode: this.phoneForm.phone,
const phoneToUserMap = { smsCode: this.phoneForm.phoneCode,
'13812345678': { username: 'grid001', password: '123456' }, rememberMe: true
'13887654321': { username: 'county001', password: '123456' }, }).then(res=>{
'13765432109': { username: 'city001', password: '123456' }, if(res.code == 200){
'13654321098': { username: 'province001', password: '123456' } localStorage.setItem('accountInfo',JSON.stringify(res.data.account))
} localStorage.setItem('tokenInfo',JSON.stringify(res.data.tokenInfo))
const userInfo = phoneToUserMap[this.phoneForm.phone] this.$router.push('/')
if (userInfo && this.phoneForm.phoneCode === '123456') { }else{
await this.login({ this.$message.error(res.message)
username: userInfo.username, }
password: userInfo.password })
})
this.$message.success('登录成功')
this.$router.push('/')
} else {
this.$message.error('手机号或验证码错误')
}
} catch (error) { } catch (error) {
console.error('Phone login error:', error) console.error('Phone login error:', error)
...@@ -659,7 +607,7 @@ export default { ...@@ -659,7 +607,7 @@ export default {
.captcha-image { .captcha-image {
flex-shrink: 0; flex-shrink: 0;
width: 100px; width: 100px;
height: 40px; height: 36px;
border: 1px solid #dcdfe6; border: 1px solid #dcdfe6;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
......
...@@ -35,14 +35,14 @@ ...@@ -35,14 +35,14 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="8"> <el-col :span="8">
<div class="info-item"> <div class="info-item">
<span class="info-label">客户地址</span> <span class="info-label">客户联系方式</span>
<span class="info-value">{{ opportunity.customerAddress }}</span> <span class="info-value">{{ opportunity.customerPhone}}</span>
</div> </div>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="14">
<div class="info-item"> <div class="info-item">
<span class="info-label">客户联系方式</span> <span class="info-label">客户地址</span>
<span class="info-value">{{ opportunity.customerPhone }}</span> <span class="info-value">{{ getAddressShow(opportunity.customerAddress) }}</span>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
...@@ -58,19 +58,19 @@ ...@@ -58,19 +58,19 @@
<el-col :span="8"> <el-col :span="8">
<div class="info-item"> <div class="info-item">
<span class="info-label">姓名:</span> <span class="info-label">姓名:</span>
<span class="info-value">{{ opportunity.installerName }} ({{ opportunity.installerId }})</span> <span class="info-value">{{ opportunity.maintenanceStaffName || -'' }}</span>
</div> </div>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<div class="info-item"> <div class="info-item">
<span class="info-label">联系电话:</span> <span class="info-label">联系电话:</span>
<span class="info-value">{{ opportunity.installerPhone }}</span> <span class="info-value">{{ opportunity.maintenanceStaffPhone || -'' }}</span>
</div> </div>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<div class="info-item"> <div class="info-item">
<span class="info-label">工号:</span> <span class="info-label">工号:</span>
<span class="info-value">{{ opportunity.assignedToName || '-' }} ({{ opportunity.assignedTo || '-' }})</span> <span class="info-value">{{ opportunity.maintenanceStaffNo || '-' }}</span>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
...@@ -79,26 +79,26 @@ ...@@ -79,26 +79,26 @@
<el-card class="detail-card"> <el-card class="detail-card">
<div class="card-header"> <div class="card-header">
<h3>营销人员信息</h3> <h3>支撑人员信息</h3>
</div> </div>
<div class="card-content"> <div class="card-content">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="8"> <el-col :span="8">
<div class="info-item"> <div class="info-item">
<span class="info-label">姓名:</span> <span class="info-label">姓名:</span>
<span class="info-value">{{ opportunity.installerName }} ({{ opportunity.installerId }})</span> <span class="info-value">{{ opportunity.marketingStaffName || '-' }}</span>
</div> </div>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<div class="info-item"> <div class="info-item">
<span class="info-label">电话:</span> <span class="info-label">电话:</span>
<span class="info-value">{{ opportunity.installerPhone }}</span> <span class="info-value">{{ opportunity.marketingStaffPhone || '-' }}</span>
</div> </div>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<div class="info-item"> <div class="info-item">
<span class="info-label">工号:</span> <span class="info-label">工号:</span>
<span class="info-value">{{ opportunity.assignedToName || '-' }} ({{ opportunity.assignedTo || '-' }})</span> <span class="info-value">{{ opportunity.marketingStaffNo || '-' }}</span>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
...@@ -114,7 +114,7 @@ ...@@ -114,7 +114,7 @@
<span class="info-label">商机标签:</span> <span class="info-label">商机标签:</span>
<span class="info-value"> <span class="info-value">
<el-tag <el-tag
v-for="tag in opportunity.tags" v-for="tag in opportunity.tagNames"
:key="tag" :key="tag"
size="small" size="small"
style="margin-right: 8px;" style="margin-right: 8px;"
...@@ -126,7 +126,19 @@ ...@@ -126,7 +126,19 @@
<div class="info-item" style="margin-top: 15px;"> <div class="info-item" style="margin-top: 15px;">
<span class="info-label">商机描述:</span> <span class="info-label">商机描述:</span>
<span class="info-value">{{ opportunity.description }}</span> <span class="info-value">{{ opportunity.textDescription || '--'}}</span>
</div>
<div class="info-item" style="margin-top: 15px;display: block" v-if="opportunity.voiceDescriptionUrl>0">
<div class="info-label">语音描述:</div>
<div class="info-value">
<audio class="single-audio" controls v-for="item in opportunity.audioArr">
<source :src="item" type="audio/wav">
<source :src="item" type="audio/mp3">
<source :src="item" type="audio/ogg">
您的浏览器不支持音频播放
</audio>
</div>
</div> </div>
<div> <div>
...@@ -142,14 +154,21 @@ ...@@ -142,14 +154,21 @@
<div class="card-content"> <div class="card-content">
<el-timeline> <el-timeline>
<el-timeline-item <el-timeline-item
v-for="(activity, index) in activities" v-for="(activity, index) in followList"
:key="index" :key="index"
:timestamp="activity.timestamp" :timestamp="timeRender(activity.createTime)"
:type="activity.type" type="success"
> >
<div class="activity-content"> <div class="activity-content">
<p>{{ activity.content }}</p> <p>{{ activity.followContent }}</p>
<p v-if="activity.operator" class="operator">操作人:{{ activity.operator }}</p> <p v-if="activity.followPersonName" class="operator">操作人:{{ activity.followPersonName }}</p>
<el-image
v-for="(item,index) in activity.imgArr"
:src="item"
:initial-index="index"
:preview-src-list="activity.imgArr"
style="width: 60px; height: 60px;margin-right: 10px;"
/>
</div> </div>
</el-timeline-item> </el-timeline-item>
</el-timeline> </el-timeline>
...@@ -173,6 +192,7 @@ ...@@ -173,6 +192,7 @@
icon="el-icon-edit" icon="el-icon-edit"
class="action-btn" class="action-btn"
@click="assignBusi" @click="assignBusi"
:disabled="opportunity.status!=1 && opportunity.status!=2"
> >
分配商机 分配商机
</el-button> </el-button>
...@@ -180,7 +200,8 @@ ...@@ -180,7 +200,8 @@
type="success" type="success"
icon="el-icon-s-promotion" icon="el-icon-s-promotion"
class="action-btn" class="action-btn"
@click="makeOrder" @click="dealBusi"
:disabled="opportunity.status!=1 && opportunity.status!=2"
> >
标记成单 标记成单
</el-button> </el-button>
...@@ -189,6 +210,7 @@ ...@@ -189,6 +210,7 @@
icon="el-icon-s-check" icon="el-icon-s-check"
class="action-btn" class="action-btn"
@click="audioBusi" @click="audioBusi"
:disabled="opportunity.status!=3"
> >
商机审核 商机审核
</el-button> </el-button>
...@@ -197,6 +219,7 @@ ...@@ -197,6 +219,7 @@
icon="el-icon-circle-close" icon="el-icon-circle-close"
class="action-btn" class="action-btn"
@click="shutBusi" @click="shutBusi"
:disabled="opportunity.status==5 || opportunity.status==4"
> >
关闭商机 关闭商机
</el-button> </el-button>
...@@ -213,19 +236,19 @@ ...@@ -213,19 +236,19 @@
<div class="key-info"> <div class="key-info">
<div class="key-item"> <div class="key-item">
<span class="key-label">创建时间:</span> <span class="key-label">创建时间:</span>
<span class="key-value">{{ opportunity.region }}</span> <span class="key-value">{{ timeRender(opportunity.createTime) }}</span>
</div> </div>
<div class="key-item"> <div class="key-item">
<span class="key-label">分配时间:</span> <span class="key-label">所属区域</span>
<span class="key-value">{{ opportunity.region }}</span> <span class="key-value">{{ opportunity.areaName }}</span>
</div> </div>
<div class="key-item"> <div class="key-item">
<span class="key-label">处理人:</span> <span class="key-label">处理人:</span>
<span class="key-value">{{ opportunity.gridName }}</span> <span class="key-value">{{ opportunity.processorName }}</span>
</div> </div>
<div class="key-item"> <div class="key-item">
<span class="key-label">最新跟进时间:</span> <span class="key-label">最新跟进时间:</span>
<span class="key-value">{{ opportunity.gridName }}</span> <span class="key-value">{{ timeRender(opportunity.latestFollowTime)}}</span>
</div> </div>
</div> </div>
</div> </div>
...@@ -239,6 +262,7 @@ ...@@ -239,6 +262,7 @@
type="text" type="text"
icon="el-icon-edit-outline" icon="el-icon-edit-outline"
class="action-btn" class="action-btn"
:disabled="opportunity.status==5 || opportunity.status==4"
@click="memoStore.isEdit = true" @click="memoStore.isEdit = true"
> >
编辑 编辑
...@@ -261,12 +285,58 @@ ...@@ -261,12 +285,58 @@
</div> </div>
<div v-if="memoStore.isEdit" style="margin-top: 10px;"> <div v-if="memoStore.isEdit" style="margin-top: 10px;">
<el-button @click="memoEditCancel">取消</el-button> <el-button @click="memoEditCancel">取消</el-button>
<el-button type="primary" @click="memoEditSubmit">提交审核</el-button> <el-button type="primary" @click="memoEditSubmit">提交修改</el-button>
</div> </div>
</el-card> </el-card>
</div> </div>
</div> </div>
<!-- 关闭商机弹窗 -->
<el-dialog
title="关闭商机"
:visible.sync="closeBusiStore.isShow"
class="audioBusiness"
width="500px">
<el-form :model="closeBusiStore" label-width="100px">
<el-form-item label="关闭原因">
<el-input
v-model="closeBusiStore.reason"
type="textarea"
:rows="4"
placeholder="请输入关闭商机原因"
></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="closeBusiStore.isShow = false">取消</el-button>
<el-button type="primary" @click="closeBusiSubmit">提交</el-button>
</div>
</el-dialog>
<!-- 商机成单弹窗 -->
<el-dialog
title="标记成单"
:visible.sync="dealBusiStore.isShow"
class="audioBusiness"
width="500px">
<el-form :model="dealBusiStore" label-width="100px">
<el-form-item label="备注">
<el-input
v-model="dealBusiStore.remark"
type="textarea"
:rows="4"
placeholder="请输入备注"
></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dealBusiStore.isShow = false">取消</el-button>
<el-button type="primary" @click="dealBusiSubmit">提交</el-button>
</div>
</el-dialog>
<!-- 审核弹窗 --> <!-- 审核弹窗 -->
<el-dialog <el-dialog
title="商机审核" title="商机审核"
...@@ -279,12 +349,12 @@ ...@@ -279,12 +349,12 @@
<el-form :model="audioBusiStore" label-width="100px"> <el-form :model="audioBusiStore" label-width="100px">
<el-form-item label="审核结果" required> <el-form-item label="审核结果" required>
<el-radio-group v-model="audioBusiStore.result"> <el-radio-group v-model="audioBusiStore.result">
<el-radio label="approved">审核通过</el-radio> <el-radio label="1">审核通过</el-radio>
<el-radio label="rejected">审核不通过</el-radio> <el-radio label="0">审核不通过</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="audioBusiStore.result === 'rejected'" label="不通过理由" required> <el-form-item v-if="audioBusiStore.result == '0'" label="不通过理由" required>
<el-input <el-input
v-model="audioBusiStore.reason" v-model="audioBusiStore.reason"
type="textarea" type="textarea"
...@@ -299,28 +369,27 @@ ...@@ -299,28 +369,27 @@
</div> </div>
</el-dialog> </el-dialog>
<!-- 新增商机弹窗 --> <!-- 分配商机弹窗 -->
<el-dialog <el-dialog
title="分配商机" title="分配商机"
:visible.sync="assignBusiStore.isShow" :visible.sync="assignBusiStore.isShow"
width="500px"> width="500px">
<div style="margin-bottom: 10px;">请输入营销人员工号来分配此商机,系统将自动显示对应的营销人员信息。</div> <div style="margin-bottom: 10px;">请输入支撑人员工号来分配此商机,系统将自动显示对应的支撑人员信息。</div>
<el-form :model="newOpportunity" :rules="opportunityRules" ref="opportunityForm" label-width="120px"> <el-form ref="opportunityForm" label-width="120px">
<el-form-item label="选择营销人员" prop="businessType" required> <el-form-item label="选择支撑人员" prop="marketingStaffId">
<el-select v-model="assignBusiStore.person" placeholder="请选择营销人员" style="width: 100%;"> <el-select v-model="assignBusiStore.marketingStaffId" placeholder="请选择支撑人员" style="width: 100%;">
<el-option label="宽带新装" value="宽带新装"></el-option> <el-option
<el-option label="宽带续费" value="宽带续费"></el-option> v-for="item in yxPersonList"
<el-option label="宽带提速" value="宽带提速"></el-option> :key="item.id"
<el-option label="ITV新装" value="ITV新装"></el-option> :label="item.personnelTypeName"
<el-option label="套餐升级" value="套餐升级"></el-option> :value="item.id"
<el-option label="家庭组网" value="家庭组网"></el-option> ></el-option>
<el-option label="智能家居" value="智能家居"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="文字描述" prop="description"> <el-form-item label="文字描述" prop="description">
<el-input <el-input
v-model="assignBusiStore.des" v-model="assignBusiStore.description"
type="textarea" type="textarea"
:rows="3" :rows="3"
placeholder="请输入商机描述信息" placeholder="请输入商机描述信息"
...@@ -339,104 +408,231 @@ ...@@ -339,104 +408,231 @@
<script> <script>
// 商机状态映射 // 商机状态映射
const statusMap = { const statusMap = {
'assigned': { label: '待跟进', type: 'info' }, '1': { label: '待跟进', type: 'info' },
'following': { label: '跟进中', type: 'warning' }, '2': { label: '跟进中', type: 'warning' },
'pending_review': { label: '成单待审核', type: 'primary' }, '3': { label: '成单待审核', type: 'primary' },
'completed': { label: '已成单', type: 'success' }, '4': { label: '已成单', type: 'success' },
'closed': { label: '已关闭', type: 'info' } '5': { label: '已关闭', type: 'info' }
} }
// 模拟商机数据
const mockOpportunity = {
id: 'OP202500001',
createTime: '2025-09-27 10:30:00',
customerAddress: '南京市玄武区中山路123号',
installerId: 'INS001',
installerName: '王师傅',
installerPhone: '13987654321',
tags: ['网络升级', '家庭安防'],
status: 'following',
assignedTo: 'SALE001',
assignedToName: '张营销',
lastFollowTime: '2025-09-28 14:20:00',
customerPhone: '138****5678',
description: '用户反馈家中网络卡顿,希望升级宽带套餐,同时对家庭监控摄像头很感兴趣',
region: '南京市玄武区',
gridName: 'A网格'
}
// 模拟跟进记录
const mockActivities = [
{
content: '商机创建',
timestamp: '2025-09-27 10:30:00',
type: 'primary',
operator: '王师傅'
},
{
content: '首次跟进,联系客户确认需求',
timestamp: '2025-09-27 15:45:00',
type: 'success',
operator: '张营销'
},
{
content: '预约上门时间',
timestamp: '2025-09-28 09:30:00',
type: 'success',
operator: '张营销'
},
{
content: '上门服务完成,客户有意向办理套餐升级',
timestamp: '2025-09-28 14:20:00',
type: 'success',
operator: '张营销'
}
]
export default { export default {
name: 'OpportunityDetail', name: 'OpportunityDetail',
data() { data() {
return { return {
opportunity: mockOpportunity, picArr: [
activities: mockActivities, 'https://testznzl.lgyzpt.com/activity/zjbPc/static/img/ad61df2f28f6b51d5f386829473ab1b592fd14e0.47afcfd0.png',
// 管理员备注相关数据 'https://testznzl.lgyzpt.com/activity/zjbPc/static/img/1b66793397a66bf54212d266505eb98e3377a354.fb5fc9cc.png'
newNote: '', ],
audioBusiStore:{ opportunity: {},
isShow: false, followList: [],
closeBusiStore:{
result: '', isShow: false,
reason: ''
}, reason: ''
assignBusiStore:{ },
isShow: false, yxPersonList: [],
audioBusiStore:{
person: '', isShow: false,
des: ''
}, result: '',
memoStore:{ reason: ''
isEdit: false, },
memoStore:{
originValue: '', isEdit: false,
value: ''
} value: ''
},
assignBusiStore:{
isShow: false,
marketingStaffId: '',
description: ''
},
dealBusiStore:{
isShow: false,
remark: ''
},
audioEventFlag: false
} }
}, },
created() { created() {
this.queryDetail()
this.queryFollow()
this.queryYxPerson()
}, },
methods: { methods: {
getAddressShow(value){
if(!value){
return '--'
}
return value.split(":")[1]
},
queryYxPerson(){
this.apiReq.queryAllPerson({
pageSize: 20,
pageNum: 1,
personnelTypes: [2]
}).then(res=>{
if(res.code == 200){
this.yxPersonList = res.data.records
}
})
},
closeBusiSubmit(){
if(!this.closeBusiStore.reason){
this.$message.error('请输入关闭原因')
return
}
this.apiReq.closeBusi({
opportunityId: this.opportunity.id,
reason: this.closeBusiStore.reason
}).then(res=>{
if(res.code == '200'){
let __this = this
this.$alert('关闭成功', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.queryDetail()
__this.queryFollow()
__this.closeBusiStore.isShow = false
}
});
}else{
this.$message.error(res.message)
}
})
},
queryFollow(){
this.apiReq.queryBusiFollowList({
opportunityId: this.$route.params.id,
pageNum: 1,
pageSize: 1000
}).then(res=>{
if(res.code == 200){
let __this = this
res.data.records.forEach(item=>{
item.creatTime = __this.timeRender(item.creatTime)
if(item.followImages){
item.imgArr = item.followImages.split(',')
}else{
item.imgArr = []
}
})
this.followList = res.data.records
}
})
},
handleAudio(){
if(this.audioEventFlag){
return
}
this.audioEventFlag = true
// 获取所有需要互斥的音频
const audios = [...document.querySelectorAll('.single-audio')]
let current = null
audios.forEach(audio => {
audio.addEventListener('play', () => {
if (current && current !== audio) {
current.pause()
current.currentTime = 0 // 如需“停完归零”就保留
}
current = audio
})
audio.addEventListener('ended', () => { current = null })
// audio.addEventListener('pause', () => { current = null })
})
},
timeRender(time){
return this.common.detailTime(new Date(time).getTime())
},
queryDetail(){
this.apiReq.queryBusiDetail({
opportunityId: this.$route.params.id
}).then(res=>{
if(res.code == 200){
if(res.data.voiceDescriptionUrl){
res.data.audioArr = res.data.voiceDescriptionUrl.split(',')
}
this.opportunity = res.data
this.memoStore.value = res.data.adminRemark
this.handleAudio()
}else{
this.$alert(res.$message, '温馨提示', {
confirmButtonText: '确定',
callback: () => {
history.go(-1)
}
});
}
})
},
memoEditCancel(){ memoEditCancel(){
let d = this.memoStore let d = this.memoStore
d.value = d.originValue d.value = this.opportunity.adminRemark
d.isEdit = false d.isEdit = false
}, },
memoEditSubmit(){ memoEditSubmit(){
if(!this.memoStore.value){
this.$message.error('请输入备注')
return
}
this.apiReq.updateRemark({
opportunityId: this.opportunity.id,
adminRemark: this.memoStore.value
}).then(res=>{
if(res.code == '200'){
let __this = this
this.$alert('编辑成功', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.queryDetail()
__this.queryFollow()
__this.memoStore.isEdit = false
}
});
}else{
this.$message.error(res.message)
}
})
}, },
audioBusiSubmit(){ audioBusiSubmit(){
let ad = this.audioBusiStore
if(ad.result==0 && !ad.reason){
this.$message.error('请输入不通过理由')
return
}
this.apiReq.audioBusi({
auditStatus: ad.result,
opportunityId: this.opportunity.id,
reason: ad.reason
}).then(res=>{
if(res.code == '200'){
let __this = this
this.$alert('审核完成', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.queryDetail()
__this.queryFollow()
ad.isShow = false
}
});
}else{
this.$message.error(res.message)
}
})
}, },
goBack() { goBack() {
...@@ -449,21 +645,79 @@ export default { ...@@ -449,21 +645,79 @@ export default {
return statusMap[status]?.label || status return statusMap[status]?.label || status
}, },
assignBusi() { assignBusi() {
this.assignBusiStore.des = '' this.assignBusiStore.description = ''
this.assignBusiStore.person = '' this.assignBusiStore.marketingStaffId = ''
this.assignBusiStore.isShow = true this.assignBusiStore.isShow = true
}, },
makeOrder(){ assingBusiSubmit(){
this.$message.success('标记成单') let p = this.assignBusiStore
if(!p.marketingStaffId){
this.$message.success('请选择支撑人员')
return
}
this.apiReq.reassignBusi({
description: p.description,
marketingStaffId: p.marketingStaffId,
opportunityId: this.opportunity.id
}).then(res=>{
if(res.code == 200){
let __this = this
this.$alert('分配成功', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.queryDetail()
__this.queryFollow()
__this.assignBusiStore.isShow = false
}
});
}else{
this.$message.error(res.message)
}
})
},
dealBusi(){
this.dealBusiStore.remark = ''
this.dealBusiStore.isShow = true
},
dealBusiSubmit(){
if(!this.dealBusiStore.remark){
this.$message.error('请输入备注')
return
}
this.apiReq.dealBusi({
opportunityId: this.opportunity.id,
adminRemark: this.dealBusiStore.remark
}).then(res=>{
if(res.code == '200'){
let __this = this
this.$alert('标记成功', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.queryDetail()
__this.queryFollow()
__this.dealBusiStore.isShow = false
}
});
}else{
this.$message.error(res.message)
}
})
}, },
audioBusi() { audioBusi() {
this.audioBusiStore.result = ''
this.audioBusiStore.reason = '' this.audioBusiStore.reason = ''
this.audioBusiStore.isShow = true this.audioBusiStore.isShow = true
}, },
shutBusi() { shutBusi() {
this.$message.success('关闭商机') this.closeBusiStore.reason = ''
this.closeBusiStore.isShow = true
} }
} }
} }
...@@ -518,6 +772,12 @@ export default { ...@@ -518,6 +772,12 @@ export default {
.info-value { .info-value {
flex: 1; flex: 1;
color: #303133; color: #303133;
audio{
display: block;
width: 100%;
height: 40px;
margin: 20px 0;
}
} }
} }
......
...@@ -74,29 +74,6 @@ ...@@ -74,29 +74,6 @@
<!-- 筛选条件 --> <!-- 筛选条件 -->
<el-card class="filter-card"> <el-card class="filter-card">
<div class="filter-header">
<h3 class="filter-title">筛选条件</h3>
<div class="filter-actions">
<el-button size="small" @click="handleFilter">
<i class="el-icon-filter"></i>
查询
</el-button>
<el-button size="small" @click="handleExport" type="default">
<i class="el-icon-download"></i>
导出数据
</el-button>
<el-button
v-if="hasFilters"
size="small"
@click="clearFilters"
type="default"
>
<i class="el-icon-close"></i>
清除筛选
</el-button>
</div>
</div>
<div class="filter-content"> <div class="filter-content">
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="3"> <el-col :span="3">
...@@ -104,6 +81,7 @@ ...@@ -104,6 +81,7 @@
<el-input <el-input
v-model="formData.customerPhone" v-model="formData.customerPhone"
placeholder="用户账号" placeholder="用户账号"
clearable
style="padding-left: 0;" style="padding-left: 0;"
/> />
</div> </div>
...@@ -112,6 +90,7 @@ ...@@ -112,6 +90,7 @@
<div class="search-wrapper"> <div class="search-wrapper">
<el-input <el-input
v-model="formData.maintenanceStaffName" v-model="formData.maintenanceStaffName"
clearable
placeholder="师傅名字" placeholder="师傅名字"
style="padding-left: 0;" style="padding-left: 0;"
/> />
...@@ -121,7 +100,8 @@ ...@@ -121,7 +100,8 @@
<div class="search-wrapper"> <div class="search-wrapper">
<el-input <el-input
v-model="formData.marketingStaffName" v-model="formData.marketingStaffName"
placeholder="营销人员名字" clearable
placeholder="支撑人员名字"
style="padding-left: 0;" style="padding-left: 0;"
/> />
</div> </div>
...@@ -130,6 +110,7 @@ ...@@ -130,6 +110,7 @@
<el-col :span="7"> <el-col :span="7">
<el-date-picker <el-date-picker
v-model="formData.createTime" v-model="formData.createTime"
clearable
type="daterange" type="daterange"
range-separator="至" range-separator="至"
start-placeholder="开始日期" start-placeholder="开始日期"
...@@ -144,8 +125,7 @@ ...@@ -144,8 +125,7 @@
<el-select <el-select
v-model="formData.status" v-model="formData.status"
placeholder="选择状态" placeholder="选择状态"
clearable clearable>
>
<el-option label="全部状态" value="all"></el-option> <el-option label="全部状态" value="all"></el-option>
<el-option <el-option
v-for="item in statusStore" v-for="item in statusStore"
...@@ -169,9 +149,9 @@ ...@@ -169,9 +149,9 @@
clearable> clearable>
<el-option <el-option
v-for="item in addressStore.cityArr" v-for="item in addressStore.cityArr"
:key="item.value" :key="item.areaCode"
:label="item.name" :label="item.areaName"
:value="item.value" :value="item.areaCode"
></el-option> ></el-option>
</el-select> </el-select>
</el-col> </el-col>
...@@ -184,9 +164,9 @@ ...@@ -184,9 +164,9 @@
clearable> clearable>
<el-option <el-option
v-for="item in addressStore.countyArr" v-for="item in addressStore.countyArr"
:key="item.value" :key="item.areaCode"
:label="item.name" :label="item.areaName"
:value="item.value" :value="item.areaCode"
></el-option> ></el-option>
</el-select> </el-select>
</el-col> </el-col>
...@@ -198,14 +178,27 @@ ...@@ -198,14 +178,27 @@
clearable> clearable>
<el-option <el-option
v-for="item in addressStore.gridArr" v-for="item in addressStore.gridArr"
:key="item.value" :key="item.gridCode"
:label="item.name" :label="item.gridName"
:value="item.value" :value="item.gridCode"
></el-option> ></el-option>
</el-select> </el-select>
</el-col> </el-col>
<el-col :span="3">
<el-button size="small" @click="handleFilter">
<i class="el-icon-filter"></i>
查询
</el-button>
<!-- <el-button size="small" @click="handleExport" type="default" disabled>
<i class="el-icon-download"></i>
导出数据
</el-button> -->
</el-col>
</el-row> </el-row>
</div> </div>
</el-card> </el-card>
<!-- 商机列表 --> <!-- 商机列表 -->
...@@ -215,7 +208,7 @@ ...@@ -215,7 +208,7 @@
<div class="list-actions"> <div class="list-actions">
<el-button <el-button
size="small" size="small"
@click="isCreateDialogOpen = true" @click="addNewBusi"
type="primary" type="primary"
> >
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
...@@ -225,15 +218,15 @@ ...@@ -225,15 +218,15 @@
</div> </div>
<div class="list-content"> <div class="list-content">
<el-table <el-table
:data="tableData" border :data="tableData" border
v-loading="loading"> v-loading="loading">
<el-table-column prop="id" label="商机ID" width="150"></el-table-column> <el-table-column prop="id" label="商机ID" width="150"></el-table-column>
<el-table-column prop="createTime" label="创建时间" :formatter="timeRender" width="180"></el-table-column> <el-table-column prop="createTime" label="创建时间" :formatter="timeRender" width="180"></el-table-column>
<el-table-column prop="customerAddress" label="客户地址" width="230" show-overflow-tooltip></el-table-column> <el-table-column prop="customerAddress" label="客户地址" width="230" :formatter="addressRender" show-overflow-tooltip></el-table-column>
<el-table-column prop="customerPhone" label="用户账号" width="120"></el-table-column> <el-table-column prop="customerPhone" label="用户账号" width="120"></el-table-column>
<el-table-column prop="maintenanceStaffName" label="装维师傅姓名" width="120"></el-table-column> <el-table-column prop="maintenanceStaffName" label="装维师傅姓名" width="120"></el-table-column>
<el-table-column prop="marketingStaffName" label="营销人员姓名" width="120"></el-table-column> <el-table-column prop="marketingStaffName" label="支撑人员姓名" width="120"></el-table-column>
<el-table-column label="商机标签" width="250"> <el-table-column label="商机标签" width="250">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag <el-tag
...@@ -252,7 +245,7 @@ ...@@ -252,7 +245,7 @@
<el-table-column label="操作" min-width="200"> <el-table-column label="操作" min-width="200">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="text" size="small" @click="checkDetail(scope.row)">查看详情</el-button> <el-button type="text" size="small" @click="checkDetail(scope.row)">查看详情</el-button>
<el-button type="text" size="small" @click="checkAudio(scope.row)">审核</el-button> <el-button type="text" size="small" @click="checkAudio(scope.row)" :disabled="scope.row.status!=3">审核</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
...@@ -276,32 +269,70 @@ ...@@ -276,32 +269,70 @@
<!-- 新增商机弹窗 --> <!-- 新增商机弹窗 -->
<el-dialog <el-dialog
title="新增商机" title="新增商机"
:visible.sync="isCreateDialogOpen" :visible.sync="newOpportunity.isShow"
width="500px"> width="800px">
<el-form :model="newOpportunity" :rules="opportunityRules" ref="opportunityForm" label-width="120px"> <el-form label-width="120px">
<el-form-item label="用户账号" prop="customerAccount" required> <el-form-item label="用户账号" prop="customerPhone">
<el-input v-model="newOpportunity.customerAccount" placeholder="请输入用户账号"></el-input> <el-input v-model="newOpportunity.customerPhone" placeholder="请输入用户账号"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="客户地址" prop="customerAddress" required> <el-form-item label="客户地址" prop="city">
<el-input v-model="newOpportunity.customerAddress" placeholder="请输入客户地址"></el-input> <template v-if="newOpportunity.cityArr.length>0">
<el-select
v-model="newOpportunity.city"
placeholder="选择地市"
@change="addCityChange"
style="margin-right: 10px"
clearable>
<el-option
v-for="item in newOpportunity.cityArr"
:key="item.code"
:label="item.name"
:value="item.code"
></el-option>
</el-select>
</template>
<template v-if="newOpportunity.countyArr.length>0">
<el-select
v-model="newOpportunity.county"
placeholder="选择区县"
@change="addCountyChange"
style="margin-right: 10px"
clearable>
<el-option
v-for="item in newOpportunity.countyArr"
:key="item.code"
:label="item.name"
:value="item.code"
></el-option>
</el-select>
</template>
<template v-if="newOpportunity.streetsArr.length>0">
<el-select
v-model="newOpportunity.streets"
placeholder="选择街道"
@change="addStreetsChange"
clearable>
<el-option
v-for="item in newOpportunity.streetsArr"
:key="item.code"
:label="item.name"
:value="item.code"
></el-option>
</el-select>
</template>
<el-input v-if="newOpportunity.streets" v-model="newOpportunity.detail" placeholder="请输入详细地址" style="margin-top: 10px;"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="装维人员工号" prop="installerWorkId" required> <el-form-item label="装维人员工号" prop="maintenanceStaffNo">
<el-input v-model="newOpportunity.installerWorkId" placeholder="请输入装维人员工号"></el-input> <el-input v-model="newOpportunity.maintenanceStaffNo" placeholder="请输入装维人员工号"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="业务类型" prop="businessType"> <el-form-item label="业务类型" prop="tagIds">
<el-select v-model="newOpportunity.businessType" placeholder="请选择业务类型" style="width: 100%;"> <el-select v-model="newOpportunity.tagIds" placeholder="请选择业务类型" style="width: 100%;" multiple>
<el-option label="宽带新装" value="宽带新装"></el-option> <el-option v-for="item in busiLabelStore" :label="item.tagName" :value="item.id" :disabled="isTagCanCho(item)"></el-option>
<el-option label="宽带续费" value="宽带续费"></el-option>
<el-option label="宽带提速" value="宽带提速"></el-option>
<el-option label="ITV新装" value="ITV新装"></el-option>
<el-option label="套餐升级" value="套餐升级"></el-option>
<el-option label="家庭组网" value="家庭组网"></el-option>
<el-option label="智能家居" value="智能家居"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="文字描述" prop="description"> <el-form-item label="文字描述" prop="textDescription">
<el-input <el-input
v-model="newOpportunity.description" v-model="newOpportunity.textDescription"
type="textarea" type="textarea"
:rows="3" :rows="3"
placeholder="请输入商机描述信息" placeholder="请输入商机描述信息"
...@@ -309,8 +340,8 @@ ...@@ -309,8 +340,8 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button @click="isCreateDialogOpen = false">取消</el-button> <el-button @click="newOpportunity.isShow = false">取消</el-button>
<el-button type="primary" @click="handleCreateOpportunity">提交</el-button> <el-button type="primary" @click="addBusiSubmit">提交</el-button>
</div> </div>
</el-dialog> </el-dialog>
...@@ -322,12 +353,12 @@ ...@@ -322,12 +353,12 @@
<el-form :model="auditForm" label-width="100px"> <el-form :model="auditForm" label-width="100px">
<el-form-item label="审核结果" required> <el-form-item label="审核结果" required>
<el-radio-group v-model="auditForm.result"> <el-radio-group v-model="auditForm.result">
<el-radio label="approved">审核通过</el-radio> <el-radio label="1">审核通过</el-radio>
<el-radio label="rejected">审核不通过</el-radio> <el-radio label="0">审核不通过</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="auditForm.result === 'rejected'" label="不通过理由" required> <el-form-item v-if="auditForm.result == '0'" label="不通过理由" required>
<el-input <el-input
v-model="auditForm.reason" v-model="auditForm.reason"
type="textarea" type="textarea"
...@@ -360,7 +391,7 @@ const statusMap = { ...@@ -360,7 +391,7 @@ const statusMap = {
const opportunityTags = [ const opportunityTags = [
'网络升级', '家庭安防', '智能家居', '宽带套餐', '移动套餐', '融合套餐', '企业业务' '网络升级', '家庭安防', '智能家居', '宽带套餐', '移动套餐', '融合套餐', '企业业务'
] ]
// 营销人员列表 // 支撑人员列表
const mockSalesPersons = [ const mockSalesPersons = [
{ id: 'SALE001', name: '张营销', gridName: 'A网格', phone: '13812345001' }, { id: 'SALE001', name: '张营销', gridName: 'A网格', phone: '13812345001' },
{ id: 'SALE002', name: '陈营销', gridName: 'B网格', phone: '13812345002' }, { id: 'SALE002', name: '陈营销', gridName: 'B网格', phone: '13812345002' },
...@@ -373,33 +404,25 @@ export default { ...@@ -373,33 +404,25 @@ export default {
name: 'OpportunityManagement', name: 'OpportunityManagement',
data() { data() {
return { return {
busiLabelStore: [],
formData:{ formData:{
customerPhone: '18277777766', customerPhone: '',
maintenanceStaffName: '', maintenanceStaffName: '',
marketingStaffName: '', marketingStaffName: '',
createTime: '', createTime: '',
status: '', status: '',
areaCode: ''
}, },
addressStore:{ addressStore:{
city: '', city: '',
cityName: '', cityArr: '',
cityArr: this.storesData.addressData,
county: '', county: '',
countyName: '',
countyArr: [], countyArr: [],
grid: '', grid: '',
gridName: '',
gridArr: [] gridArr: []
}, },
getData:{ getData:{
city: '', city: '',
cityName: '',
county: '', county: '',
countyName: '',
grid: '', grid: '',
gridName: '',
}, },
statsStore:{}, statsStore:{},
statusStore:[], statusStore:[],
...@@ -410,23 +433,27 @@ export default { ...@@ -410,23 +433,27 @@ export default {
total: 0 total: 0
}, },
newOpportunity: { newOpportunity: {
customerAccount: '', isShow: false,
address: {
city: '', customerPhone: '',
cityName: '',
cityArr: this.storesData.choAddressData, city: '',
county: '', cityName: '',
countyName: '', cityArr: this.storesData.choAddressData,
countyArr: [], county: '',
streets: '', countyName: '',
streetsName: '', countyArr: [],
streetsArr: [], streets: '',
detail: '' streetsName: '',
}, streetsArr: [],
installerWorkId: '', detail: '',
businessType: '',
description: '' maintenanceStaffNo: '',
tagIds: [],
opportunityType: '',
textDescription: ''
}, },
busiLabelStore: [],
loading: false, loading: false,
statusMap, statusMap,
...@@ -438,19 +465,6 @@ export default { ...@@ -438,19 +465,6 @@ export default {
selectedStatus: '', selectedStatus: '',
selectedTag: '', selectedTag: '',
dateRange: [], dateRange: [],
isCreateDialogOpen: false,
opportunityRules: {
customerAccount: [
{ required: true, message: '请输入用户账号', trigger: 'blur' }
],
customerAddress: [
{ required: true, message: '请输入客户地址', trigger: 'blur' }
],
installerWorkId: [
{ required: true, message: '请输入装维人员工号', trigger: 'blur' }
]
},
isAuditDialogOpen: false, isAuditDialogOpen: false,
auditForm: { auditForm: {
result: '', result: '',
...@@ -463,33 +477,79 @@ export default { ...@@ -463,33 +477,79 @@ export default {
} }
}, },
created(){ created(){
this.setAddressShow() let pa = JSON.parse(localStorage.getItem('accountInfo'))
this.getData.city = pa.cityCode||''
this.getData.county = pa.countyCode||''
this.getData.grid = pa.gridCode||''
this.queryArea()
this.queryBusi() this.queryBusi()
this.queryStatistics() this.queryStatistics()
this.queryStatus() this.queryStatus()
this.queryAllBusiLabel() this.queryAllBusiLabel()
this.apiReq.queryAllArea({
areaLevel: 1,
parentAreaCode: ''
})
},
computed: {
...mapGetters(['user']),
// 是否有筛选条件
hasFilters() {
return this.searchTerm ||
this.selectedRegion ||
this.selectedStatus ||
this.selectedTag ||
(this.dateRange && this.dateRange.length > 0)
}
}, },
methods: { methods: {
addressRender(row,column){
let value = row[column.property]
if(!value){
return '--'
}
return value.split(":")[1]
},
queryArea(){
this.apiReq.queryLevelAllArea({
areaLevel: 2,
parentAreaCode: '320000'
}).then(res=>{
if(res.code == 200){
this.addressStore.cityArr = res.data
this.setAddressShow()
}
})
},
isTagCanCho(row){
let arr = this.newOpportunity.tagIds
if(arr.length == 0){
return false
}
if(arr.includes(1) || arr.includes(2)){
this.newOpportunity.opportunityType = 2
if(row.id!=1 && row.id!=2){
return true
}else{
return false
}
}else{
this.newOpportunity.opportunityType = 1
if(row.id==1 || row.id==2){
return true
}else{
return false
}
}
},
addNewBusi(){
let nd = this.newOpportunity
nd.customerPhone = ''
nd.city = ''
nd.cityName = ''
nd.county = ''
nd.countyName = ''
nd.streets = ''
nd.streetsName = ''
nd.maintenanceStaffNo = ''
nd.tagIds = ''
nd.textDescription = ''
nd.isShow = true
},
queryAllBusiLabel(){ queryAllBusiLabel(){
this.apiReq.queryBusiLabel({ this.apiReq.queryBusiLabelList({
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
}).then(res=>{ }).then(res=>{
...@@ -510,22 +570,11 @@ export default { ...@@ -510,22 +570,11 @@ export default {
if(gd.city){ if(gd.city){
ad.city = gd.city ad.city = gd.city
ad.cityArr.forEach(item=>{ this.cityChange(ad.city)
if(item.value == ad.city){
ad.countyArr = item.children
}
})
if(gd.county){ if(gd.county){
ad.county = gd.county ad.county = gd.county
ad.countyArr.forEach(item=>{ this.countyChange(ad.county)
if(item.value == ad.county){
ad.gridArr = item.children
}
})
if(ad.gridArr.length == 1){
gd.grid = 'moren'
}
if(gd.grid){ if(gd.grid){
ad.grid = gd.grid ad.grid = gd.grid
...@@ -540,9 +589,12 @@ export default { ...@@ -540,9 +589,12 @@ export default {
ad.county = '' ad.county = ''
ad.grid = '' ad.grid = ''
ad.cityArr.forEach(item=>{ this.apiReq.queryLevelAllArea({
if(item.value == ad.city){ areaLevel: 3,
ad.countyArr = item.children parentAreaCode: value
}).then(res=>{
if(res.code == 200){
ad.countyArr = res.data
} }
}) })
}, },
...@@ -551,12 +603,41 @@ export default { ...@@ -551,12 +603,41 @@ export default {
ad.county = value ad.county = value
ad.grid = '' ad.grid = ''
this.apiReq.queryGridList({
areaCode:value
}).then(res=>{
if(res.code == 200){
ad.gridArr = res.data
}
})
},
addCityChange(value){
let ad = this.newOpportunity
ad.city = value
ad.cityName = ad.cityArr.find(item => item.code == value).name
ad.cityArr.forEach(item=>{
if(item.code == ad.city){
ad.countyArr = item.districts
}
})
},
addCountyChange(value){
let ad = this.newOpportunity
ad.county = value
ad.countyName = ad.countyArr.find(item => item.code == value).name
ad.countyArr.forEach(item=>{ ad.countyArr.forEach(item=>{
if(item.value == ad.county){ if(item.code == ad.county){
ad.gridArr = item.children ad.streetsArr = item.streets
} }
}) })
}, },
addStreetsChange(value){
let ad = this.newOpportunity
ad.streets = value
ad.streetsName = ad.streetsArr.find(item => item.code == value).name
},
queryBusi(){ queryBusi(){
let d = this.formData let d = this.formData
...@@ -569,27 +650,28 @@ export default { ...@@ -569,27 +650,28 @@ export default {
} }
this.apiReq.queryAllBusi({ this.apiReq.queryAllBusi({
pageNum: 1, pageNum: this.pageStore.currentPage,
pageSize: 20, pageSize: this.pageStore.pageSize,
areaCode: this.addressStore.county || this.addressStore.city,
gridCode: this.addressStore.grid,
...d ...d
}).then(res=>{ }).then(res=>{
if(res.code == 200){ if(res.code == 200){
this.tableData = res.data.records this.tableData = res.data.records
this.pageStore.total = res.total this.pageStore.total = res.data.total
}else{ }else{
this.$message.error(res.message) this.$message.error(res.message)
} }
}) })
}, },
handleSizeChange(size) { handleSizeChange(size) {
this.pageSize = size this.pageStore.pageSize = size
this.currentPage = 1
this.handleFilter()
this.queryBusi()
}, },
handleCurrentChange(page) { handleCurrentChange(page) {
this.currentPage = page this.pageStore.currentPage = page
this.queryBusi() this.queryBusi()
}, },
...@@ -607,8 +689,50 @@ export default { ...@@ -607,8 +689,50 @@ export default {
} }
}) })
}, },
addBusiSubmit(){
let nd = this.newOpportunity
if(!nd.customerPhone){
this.$message.error('请输入用户账号')
return
}
if(!nd.city || !nd.county || !nd.streets || !nd.detail){
this.$message.error('请补全客户地址信息')
return
}
let address = nd.city+'|'+nd.county+'|'+nd.streets+":"+nd.cityName+nd.countyName+nd.streetsName+nd.detail
if(!nd.maintenanceStaffNo){
this.$message.error('请输入装维人员工号')
return
}
if(!nd.tagIds){
this.$message.error('请选择业务类型')
return
}
this.apiReq.createBusi({
customerAddress: address,
...nd
}).then(res=>{
let __this = this
if(res.code == 200){
this.$alert('添加成功', '温馨提示', {
confirmButtonText: '确定',
callback: () => {
__this.handleFilter()
__this.newOpportunity.isShow = false
}
});
}else{
this.$message.error(res.message)
}
})
},
...@@ -625,7 +749,7 @@ export default { ...@@ -625,7 +749,7 @@ export default {
}, },
handleFilter() { handleFilter() {
// 重新计算筛选结果 // 重新计算筛选结果
this.currentPage = 1 this.pageStore.currentPage = 1
this.queryBusi() this.queryBusi()
}, },
...@@ -643,19 +767,6 @@ export default { ...@@ -643,19 +767,6 @@ export default {
checkAudio(row){ checkAudio(row){
this.handleOpenAudit(row.id) this.handleOpenAudit(row.id)
}, },
handleCreateOpportunity() {
this.$refs.opportunityForm.validate((valid) => {
if (valid) {
// 验证必填字段
if (!this.newOpportunity.customerAccount || !this.newOpportunity.customerAddress || !this.newOpportunity.installerWorkId) {
this.$message.error('请填写所有必填字段')
return
}
}
})
},
handleExport() { handleExport() {
// 模拟导出功能 // 模拟导出功能
this.$message.success('商机数据导出成功') this.$message.success('商机数据导出成功')
...@@ -672,22 +783,34 @@ export default { ...@@ -672,22 +783,34 @@ export default {
return return
} }
if (this.auditForm.result === 'rejected' && !this.auditForm.reason.trim()) { if (this.auditForm.result == '0' && !this.auditForm.reason.trim()) {
this.$message.error('审核不通过时必须填写理由') this.$message.error('审核不通过时必须填写理由')
return return
} }
// 模拟审核操作 this.apiReq.audioBusi({
const resultText = this.auditForm.result === 'approved' ? '通过' : '不通过' auditStatus: this.auditForm.result,
this.$message.success(`商机 ${this.auditOpportunityId} 审核${resultText}`) opportunityId: this.auditOpportunityId,
reason: this.auditForm.reason
// 重置状态并关闭对话框 }).then(res=>{
this.isAuditDialogOpen = false if(res.code == 200){
this.auditOpportunityId = null let __this = this
this.auditForm = { this.$alert('审核完成', '温馨提示', {
result: '', confirmButtonText: '确定',
reason: '' callback: () => {
// 重置状态并关闭对话框
__this.isAuditDialogOpen = false
__this.auditOpportunityId = null
__this.auditForm = {
result: '',
reason: ''
}
}
});
}else{
this.$message.error(res.message)
} }
})
} }
} }
} }
...@@ -695,6 +818,10 @@ export default { ...@@ -695,6 +818,10 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.opportunity-management { .opportunity-management {
.el-button.el-button--small{
padding: 13px 12px;
}
.page-header { .page-header {
margin-bottom: 24px; margin-bottom: 24px;
......
...@@ -6,14 +6,14 @@ ...@@ -6,14 +6,14 @@
</div> </div>
<!-- 人员管理页面特殊处理:显示业务类型Tab --> <!-- 人员管理页面特殊处理:显示业务类型Tab -->
<div v-if="activeTab === 'personnel'" class="personnel-management"> <div v-if="activeTab == 'personnel'" class="personnel-management">
<el-tabs v-model="businessTab" @tab-click="handleBusinessTabClick"> <el-tabs v-model="businessTab" @tab-click="handleBusinessTabClick">
<el-tab-pane label="常规业务" name="regular"></el-tab-pane> <el-tab-pane label="常规业务" name="regular"></el-tab-pane>
<el-tab-pane label="政企业务" name="enterprise" v-if="hasEnterprisePermission"></el-tab-pane> <el-tab-pane label="政企业务" name="enterprise" v-if="hasEnterprisePermission"></el-tab-pane>
</el-tabs> </el-tabs>
<!-- 常规业务 --> <!-- 常规业务 -->
<div v-if="businessTab === 'regular'"> <div v-if="businessTab == 'regular'">
<el-card> <el-card>
<div class="card-content"> <div class="card-content">
<PersonnelManagement /> <PersonnelManagement />
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
</div> </div>
<!-- 政企业务 --> <!-- 政企业务 -->
<div v-if="businessTab === 'enterprise' && hasEnterprisePermission"> <div v-if="businessTab == 'enterprise' && hasEnterprisePermission">
<el-card> <el-card>
<div class="card-content"> <div class="card-content">
<EnterprisePersonnelManagement /> <EnterprisePersonnelManagement />
...@@ -32,14 +32,14 @@ ...@@ -32,14 +32,14 @@
</div> </div>
<!-- 商机管理配置 --> <!-- 商机管理配置 -->
<div v-else-if="activeTab === 'tags'" class="opportunity-config"> <div v-else-if="activeTab == 'tags'" class="opportunity-config">
<el-tabs v-model="configTab" @tab-click="handleConfigTabClick"> <el-tabs v-model="configTab" @tab-click="handleConfigTabClick">
<el-tab-pane label="商机标签" name="tags"></el-tab-pane> <el-tab-pane label="商机标签" name="tags"></el-tab-pane>
<el-tab-pane label="商机关闭" name="reasons"></el-tab-pane> <el-tab-pane label="商机关闭" name="reasons"></el-tab-pane>
</el-tabs> </el-tabs>
<!-- 商机标签 --> <!-- 商机标签 -->
<div v-if="configTab === 'tags'"> <div v-if="configTab == 'tags'">
<el-card> <el-card>
<div class="card-content"> <div class="card-content">
<TagManagement /> <TagManagement />
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
</div> </div>
<!-- 商机关闭原因 --> <!-- 商机关闭原因 -->
<div v-if="configTab === 'reasons'"> <div v-if="configTab == 'reasons'">
<el-card> <el-card>
<div class="card-content"> <div class="card-content">
<CloseReasonManagement /> <CloseReasonManagement />
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
</div> </div>
<!-- 账号管理 --> <!-- 账号管理 -->
<div v-else-if="activeTab === 'accounts'"> <div v-else-if="activeTab == 'accounts'">
<el-card> <el-card>
<div class="card-content"> <div class="card-content">
<AccountManagement /> <AccountManagement />
...@@ -98,9 +98,10 @@ export default { ...@@ -98,9 +98,10 @@ export default {
} }
}, },
computed: { computed: {
...mapGetters(['user']),
hasEnterprisePermission() { hasEnterprisePermission() {
return this.user?.role === 'city_admin' || this.user?.role === 'province_admin' let accountInfo = JSON.parse(localStorage.getItem('accountInfo'))
return !accountInfo.countyCode && !accountInfo.gridCode
}, },
activeTabLabel() { activeTabLabel() {
const tabLabels = { const tabLabels = {
......
...@@ -5,12 +5,12 @@ module.exports = { ...@@ -5,12 +5,12 @@ module.exports = {
publicPath: './', publicPath: './',
outputDir: 'dist', outputDir: 'dist',
assetsDir: 'static', assetsDir: 'static',
lintOnSave: process.env.NODE_ENV === 'development', lintOnSave: process.env.NODE_ENV == 'development',
productionSourceMap: false, productionSourceMap: false,
devServer: { devServer: {
proxy: { proxy: {
'/compass': { '/compass': {
target: 'http://39.107.104.220:8899', target: 'https://testznzl.lgyzpt.com/',
//target: 'https://hall.51xinpai.cn', //target: 'https://hall.51xinpai.cn',
changeOrigin: true, changeOrigin: true,
pathRewrite: { pathRewrite: {
...@@ -27,7 +27,8 @@ module.exports = { ...@@ -27,7 +27,8 @@ module.exports = {
alias: { alias: {
'@': path.resolve(__dirname, 'src') '@': path.resolve(__dirname, 'src')
} }
} },
devtool: 'source-map'
}, },
chainWebpack: config => { chainWebpack: config => {
// 配置SVG图标 // 配置SVG图标
......
...@@ -12,6 +12,11 @@ ...@@ -12,6 +12,11 @@
<audio ref="audioObj" playsinline></audio> <audio ref="audioObj" playsinline></audio>
<!-- <div class="topBut" v-if="isAlone">
<div class="left" @click="goZj">质检模拟</div>
<div class="right" @click="loginOut">退出</div>
</div> -->
<!-- 联系方式 --> <!-- 联系方式 -->
<div class="section" data-node-id="79:2566"> <div class="section" data-node-id="79:2566">
<div class="section-card" data-node-id="6:249"> <div class="section-card" data-node-id="6:249">
...@@ -20,7 +25,7 @@ ...@@ -20,7 +25,7 @@
<span class="contact-text" data-node-id="6:259">{{ contactPhone }}</span> <span class="contact-text" data-node-id="6:259">{{ contactPhone }}</span>
<button class="edit-button" data-node-id="6:263" @click="editContact">修改</button> <button class="edit-button" data-node-id="6:263" @click="editContact">修改</button>
</div> </div>
<input class="phoneInput" v-else type="text" placeholder="请输入手机号"> <input class="phoneInput" v-model="contactPhone" v-else type="text" placeholder="请输入手机号">
</div> </div>
</div> </div>
...@@ -37,7 +42,7 @@ ...@@ -37,7 +42,7 @@
</div> </div>
<div class="addressLi"> <div class="addressLi">
<div class="name">详细地址</div> <div class="name">详细地址</div>
<input type="text" placeholder="门牌号、楼栋号"> <input type="text" placeholder="门牌号、楼栋号" v-model="detailAddress">
</div> </div>
</div> </div>
</div> </div>
...@@ -51,10 +56,11 @@ ...@@ -51,10 +56,11 @@
v-for="type in businessTypes" v-for="type in businessTypes"
:key="type.id" :key="type.id"
:class="['tag-button', { active: type.selected }]" :class="['tag-button', { active: type.selected }]"
:style="{color: type.ifCho?'#33':'#CECECE'}"
:data-node-id="type.nodeId" :data-node-id="type.nodeId"
@click="selectBusinessType(type.id)" @click="selectBusinessType(type.id)"
> >
{{ type.name }} {{ type.tagName }}
</div> </div>
</div> </div>
</div> </div>
...@@ -67,8 +73,11 @@ ...@@ -67,8 +73,11 @@
<div class="cordList"> <div class="cordList">
<div class="cordLi" v-for="(item,index) in recordingUrlArr"> <div class="cordLi" v-for="(item,index) in recordingUrlArr">
<div class="left" @click="playAudio(index)"> <div class="left" @click="playAudio(index)">
<img class="pause" src="images/pause.png" alt="">
<img class="voice" src="images/voice.png" alt=""> <img v-if="item.isPlay" class="pause" src="images/play.png" alt="">
<img v-else class="pause" src="images/pause.png" alt="">
<img class="voice" src="images/voice.png" alt="">
<div class="time">{{item.time}}</div> <div class="time">{{item.time}}</div>
</div> </div>
<img class="close" src="images/close.png" @click="removeAudio(index)"> <img class="close" src="images/close.png" @click="removeAudio(index)">
...@@ -134,14 +143,20 @@ ...@@ -134,14 +143,20 @@
<div v-else class="submit-button" data-node-id="6:498" @click="submitBusiness"> <div v-else class="submit-button" data-node-id="6:498" @click="submitBusiness">
提交商机 提交商机
</div> </div>
<div class="loginOut" @click="loginOut">
<img src="images/loginout.png" alt="">
<div>退出</div>
</div>
<!-- 底部导航 --> <!-- 底部导航 -->
<div class="bottom-nav" v-if="isAlone"> <div class="bottom-nav" v-if="isAlone">
<div class="nav-item collect-business"> <div class="nav-item collect-business active">
<img class="nav-icon" src="images/collect-icon.svg" alt="收集商机"> <img class="nav-icon" src="images/collect-icon-active.png" alt="收集商机">
<span class="nav-text" data-node-id="355:528">收集商机</span> <span class="nav-text" data-node-id="355:528">收集商机</span>
</div> </div>
<div class="nav-item all-business active"> <div class="nav-item all-business" @click="navigateToList">
<img class="nav-icon" src="images/business-icon.svg" alt="收集商机"> <img class="nav-icon" src="images/business-icon.png" alt="收集商机">
<span class="nav-text" data-node-id="355:532">全部商机</span> <span class="nav-text" data-node-id="355:532">全部商机</span>
</div> </div>
</div> </div>
...@@ -197,9 +212,11 @@ ...@@ -197,9 +212,11 @@
</div> </div>
<!-- 引入Vue.js --> <!-- 引入Vue.js -->
<script src="https://cdn.jsdelivr.net/npm/lamejs@1.2.1/lame.min.js"></script>
<script src="js/axios.min.js"></script>
<script src="js/vue.min.js"></script> <script src="js/vue.min.js"></script>
<script src="js/util.js"></script> <script src="js/util.js"></script>
<script src="js/addressData.js"></script> <script src="js/addressData.js"></script>
<script src="js/addBusi.js"></script> <script src="js/addBusi.js?123211"></script>
</body> </body>
</html> </html>
\ No newline at end of file \ No newline at end of file
...@@ -11,12 +11,13 @@ ...@@ -11,12 +11,13 @@
<div class="app-container" :class="{'worker-con': isWorker}"> <div class="app-container" :class="{'worker-con': isWorker}">
<img class="topImg" src="images/bg.png" alt=""> <img class="topImg" src="images/bg.png" alt="">
<audio ref="audioObj" playsinline></audio>
<!-- 状态显示 --> <!-- 状态显示 -->
<div class="status-section" data-node-id="294:2278"> <div class="status-section" data-node-id="294:2278">
<div class="status-content"> <div class="status-content">
<h1 class="status-title" data-node-id="294:2279">{{ businessDetail.status }}</h1> <h1 class="status-title" data-node-id="294:2279">{{ followNewInfo.statusName }}</h1>
<p class="status-desc" data-node-id="294:2280">{{ businessDetail.statusDescription }}</p> <p class="status-desc" data-node-id="294:2280">{{ followNewInfo.des }}</p>
</div> </div>
</div> </div>
...@@ -28,12 +29,12 @@ ...@@ -28,12 +29,12 @@
<!-- 客户地址 --> <!-- 客户地址 -->
<div class="info-row" data-node-id="294:2286"> <div class="info-row" data-node-id="294:2286">
<span class="info-label" data-node-id="294:2287">客户地址</span> <span class="info-label" data-node-id="294:2287">客户地址</span>
<span class="info-value" data-node-id="294:2288">{{ businessDetail.customerAddress }}</span> <span class="info-value" data-node-id="294:2288">{{ getAddressShow(businessDetail.customerAddress)}}</span>
</div> </div>
<!-- 客户账号 --> <!-- 客户账号 -->
<div class="info-row" data-node-id="294:2289"> <div class="info-row" data-node-id="294:2289">
<span class="info-label" data-node-id="294:2290">客户账号</span> <span class="info-label" data-node-id="294:2290">客户账号</span>
<span class="info-value" data-node-id="294:2291">{{ businessDetail.customerAccount }}</span> <span class="info-value" data-node-id="294:2291">{{ businessDetail.maintenanceStaffPhone }}</span>
</div> </div>
<!-- 客户电话 --> <!-- 客户电话 -->
<div class="info-row" data-node-id="294:2292"> <div class="info-row" data-node-id="294:2292">
...@@ -43,41 +44,58 @@ ...@@ -43,41 +44,58 @@
<img class="copy-icon" src="images/copy.png" data-node-id="294:2296" @click="copyPhone"></img> <img class="copy-icon" src="images/copy.png" data-node-id="294:2296" @click="copyPhone"></img>
</div> </div>
</div> </div>
<!-- 装维师傅 -->
<div class="info-row" data-node-id="294:2298" v-if="!isWorker">
<span class="info-label" data-node-id="294:2299">装维师傅 </span>
<div class="phone-container" data-node-id="294:2300">
<span class="info-value" data-node-id="294:2301">{{ businessDetail.maintenanceStaffPhone }}</span>
<img class="copy-icon" src="images/copy.png" data-node-id="294:2302" @click="copyMarketerPhone"></img>
</div>
</div>
<!-- 营销人员 --> <!-- 营销人员 -->
<div class="info-row" data-node-id="294:2298"> <div class="info-row" data-node-id="294:2298" v-else>
<span class="info-label" data-node-id="294:2299">营销人员</span> <span class="info-label" data-node-id="294:2299">营销人员</span>
<div class="phone-container" data-node-id="294:2300"> <div class="phone-container" data-node-id="294:2300">
<span class="info-value" data-node-id="294:2301">{{ businessDetail.marketerPhone }}</span> <span class="info-value" data-node-id="294:2301">{{ businessDetail.marketingStaffPhone }}</span>
<img class="copy-icon" src="images/copy.png" data-node-id="294:2302" @click="copyMarketerPhone"></img> <img class="copy-icon" src="images/copy.png" data-node-id="294:2302" @click="copyMarketerPhone"></img>
</div> </div>
</div> </div>
<!-- 营销类型 --> <!-- 营销类型 -->
<div class="info-row" data-node-id="294:2304"> <div class="info-row" data-node-id="294:2304">
<span class="info-label" data-node-id="294:2305">营销类型</span> <span class="info-label" data-node-id="294:2305">营销类型</span>
<div class="type-container" data-node-id="294:2306"> <div class="type-container" data-node-id="294:2306">
<div class="type-tag" data-node-id="294:2307"> <div class="type-tag" v-for="item in businessDetail.tagNames">
<span data-node-id="294:2308">{{ businessDetail.marketingTypes[0] }}</span> <span >{{ item }}</span>
</div>
<div class="type-tag" data-node-id="294:2309">
<span data-node-id="294:2310">{{ businessDetail.marketingTypes[1] }}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- 提交时间 --> <!-- 提交时间 -->
<div class="info-row" data-node-id="294:2311"> <div class="info-row" data-node-id="294:2311">
<span class="info-label" data-node-id="294:2312">提交时间</span> <span class="info-label" data-node-id="294:2312">提交时间</span>
<span class="info-value" data-node-id="294:2313">{{ businessDetail.submitTime }}</span> <span class="info-value" data-node-id="294:2313">{{ switchTime(businessDetail.createTime) }}</span>
</div> </div>
</div> </div>
<!-- 操作按钮 --> <!-- 操作按钮 -->
<div class="action-buttons"> <div class="action-buttons" v-if="isWorker">
<div class="action-button contact-marketer" data-node-id="294:2314" @click="callMarketer"> <div class="action-button contact-marketer" data-node-id="294:2314" @click="takeCall('yx')">
<span data-node-id="294:2316">致电营销人员</span> <span data-node-id="294:2316">致电营销人员</span>
</div> </div>
<div class="action-button contact-customer" data-node-id="294:2317" @click="callCustomer" v-if="!isWorker"> </div>
<div class="action-buttons" v-else>
<div class="action-button contact-marketer" data-node-id="294:2317" @click="takeCall('customer')">
<span data-node-id="294:2319">联系客户</span> <span data-node-id="294:2319">联系客户</span>
</div> </div>
<div class="action-button contact-customer" data-node-id="294:2314" @click="takeCall('zw')" v-if="platform='yx'">
<span data-node-id="294:2316">致电装维师傅</span>
</div>
<div class="action-button contact-customer" data-node-id="294:2314" @click="takeCall('yx')" v-else>
<span data-node-id="294:2316">致电营销人员</span>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -92,19 +110,22 @@ ...@@ -92,19 +110,22 @@
<!-- 多条语音记录 --> <!-- 多条语音记录 -->
<div class="voiceList"> <div class="voiceList">
<div class="voiceLi" v-for="item in '123'"> <div class="voiceLi" v-for="(item,index) in businessDetail.voiceRecords">
<div class="up"> <div class="up">
<div class="left"> <div class="left" @click="playAudio(index)">
<img class="pause" src="images/pause.png" alt="">
<img v-if="item.isPlay" class="pause" src="images/play.png" alt="">
<img v-else class="pause" src="images/pause.png" alt="">
<img class="voice" src="images/voice.png" alt=""> <img class="voice" src="images/voice.png" alt="">
<div class="time">{{item.time}}</div> <div class="time">{{item.time}}</div>
</div> </div>
<div class="right"> <div class="right" v-if="!isWorker" @click="audioToText(index)">
<img src="images/text.png" alt=""> <img src="images/text.png" alt="">
<div>转为文字</div> <div>转为文字</div>
</div> </div>
</div> </div>
<div class="textDiv">asdfasdfadf阿斯顿发到发送到发送到发</div> <div class="textDiv" v-if="!isWorker && item.text">{{item.text}}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -113,7 +134,7 @@ ...@@ -113,7 +134,7 @@
<div class="text-section"> <div class="text-section">
<h3 class="subtitle" data-node-id="294:2459">文字描述</h3> <h3 class="subtitle" data-node-id="294:2459">文字描述</h3>
<div class="text-content" data-node-id="294:2460"> <div class="text-content" data-node-id="294:2460">
<p data-node-id="294:2461">{{ businessDetail.textDescription }}</p> <p data-node-id="294:2461">{{ businessDetail.textDescription||'暂无' }}</p>
</div> </div>
</div> </div>
</div> </div>
...@@ -124,7 +145,7 @@ ...@@ -124,7 +145,7 @@
<div class="notes-card"> <div class="notes-card">
<h2 class="section-title" data-node-id="294:2361">管理员备注</h2> <h2 class="section-title" data-node-id="294:2361">管理员备注</h2>
<div class="notes-content" data-node-id="294:2362"> <div class="notes-content" data-node-id="294:2362">
<p data-node-id="294:2362">{{ businessDetail.adminNotes }}</p> <p data-node-id="294:2362">{{ businessDetail.adminRemark||'暂无' }}</p>
</div> </div>
</div> </div>
</div> </div>
...@@ -135,29 +156,15 @@ ...@@ -135,29 +156,15 @@
<h2 class="section-title" data-node-id="294:2695">处理进度</h2> <h2 class="section-title" data-node-id="294:2695">处理进度</h2>
<div class="progress-content" data-node-id="294:2696"> <div class="progress-content" data-node-id="294:2696">
<!-- 跟进中 --> <!-- 跟进中 -->
<div class="progress-item" v-if="businessDetail.followUpStatus" data-node-id="294:2697"> <div class="progress-item" v-for="item in followList">
<div class="circle"> <div class="circle">
<div></div> <div></div>
</div> </div>
<div class="progress-status" data-node-id="294:2698"> <div class="progress-status" data-node-id="294:2698">
<h3 class="progress-title">{{ businessDetail.followUpStatus }}</h3> <h3 class="progress-title">{{ getStatusName(item.status) }}</h3>
<div class="progress-details" data-node-id="294:2699"> <div class="progress-details" data-node-id="294:2699">
<p class="progress-time">{{ businessDetail.followUpTime }}</p> <p class="progress-time">{{ switchTime(item.createTime) }}</p>
<p class="progress-desc">{{ businessDetail.followUpDescription }}</p> <p class="progress-desc">{{ item.followPersonName }}:{{ item.followContent }}</p>
</div>
</div>
</div>
<!-- 商机创建 -->
<div class="progress-item" data-node-id="294:2702">
<div class="circle cgray">
<div></div>
</div>
<div class="progress-status" data-node-id="294:2703">
<h3 class="progress-title">商机创建</h3>
<div class="progress-details" data-node-id="294:2704">
<p class="progress-time">{{ businessDetail.createTime }}</p>
<p class="progress-desc">{{ businessDetail.createDescription }}</p>
</div> </div>
</div> </div>
</div> </div>
...@@ -166,15 +173,15 @@ ...@@ -166,15 +173,15 @@
</div> </div>
<div class="botButt" v-if="!isWorker"> <div class="botButt" v-if="!isWorker">
<div class="bbli"> <div class="bbli" @click="addBusiProcess" :style="{opacity: ifCanGj?1:0.3}">
<img src="images/genjin.png" alt=""> <img src="images/genjin.png" alt="">
<div>写跟进</div> <div>写跟进</div>
</div> </div>
<div class="bbli"> <div class="bbli" @click="completeBusi" :style="{opacity: ifCanCd?1:0.3}">
<img src="images/chengdan.png" alt=""> <img src="images/chengdan.png" alt="">
<div>转为成单</div> <div>转为成单</div>
</div> </div>
<div class="bbli"> <div class="bbli" @click="closeBusi" :style="{opacity: ifCanGb?1:0.3}">
<img src="images/guanbi.png" alt=""> <img src="images/guanbi.png" alt="">
<div>关闭商机</div> <div>关闭商机</div>
</div> </div>
...@@ -186,29 +193,25 @@ ...@@ -186,29 +193,25 @@
<div class="title">写跟进</div> <div class="title">写跟进</div>
<div class="ali"> <div class="ali">
<div class="til">跟进内容</div> <div class="til">跟进内容</div>
<textarea class="input" placeholder="请输入"></textarea> <textarea v-model="gjStore.des" class="input" placeholder="请输入"></textarea>
</div> </div>
<div class="ali"> <div class="ali">
<div class="til">上传图片(最多三张)</div> <div class="til">上传图片(最多三张)</div>
<div class="imgList"> <div class="imgList">
<div class="addImg"> <div class="addImg" v-if="gjStore.imgArr.length<3">
<img class="add" src="images/add.png" alt=""> <img class="add" src="images/add.png" alt="">
<input type="file" @change="fileChange" accept="image/*" :disabled="gjStore.isLoading"> <input type="file" @change="fileChange" accept="image/*" :disabled="gjStore.isLoading">
</div> </div>
<div class="imgShow"> <div class="imgShow" v-for="(item,index) in gjStore.imgArr">
<img class="show" src="images/bg.png" alt=""> <img class="show" :src="item.url" alt="">
<img class="close" src="images/close.png" alt=""> <img class="close" src="images/close.png" @click="removeImg(index)">
</div>
<div class="imgShow">
<img class="show" src="images/bg.png" alt="">
<img class="close" src="images/close.png" alt="">
</div> </div>
</div> </div>
<div class="imgts">支持上传通话记录截图等凭证</div> <div class="imgts">支持上传通话记录截图等凭证</div>
</div> </div>
<div class="alertButt"> <div class="alertButt">
<div class="cancel">取消</div> <div class="cancel" @click="gjStore.isShow = false">取消</div>
<div class="submit">提交</div> <div class="submit" @click="gjSubmit">提交</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -218,11 +221,11 @@ ...@@ -218,11 +221,11 @@
<div class="title">转为成单</div> <div class="title">转为成单</div>
<div class="ali"> <div class="ali">
<div class="til">成单理由</div> <div class="til">成单理由</div>
<textarea class="input" placeholder="请输入"></textarea> <textarea v-model="cdStore.des" class="input" placeholder="请输入"></textarea>
</div> </div>
<div class="alertButt"> <div class="alertButt">
<div class="cancel">取消</div> <div class="cancel" @click="cdStore.isShow=false">取消</div>
<div class="submit">确认成单</div> <div class="submit" @click="cdSubmit">确认成单</div>
</div> </div>
<div class="botTs">提交后将进入审核流程,审核通过前可以撤回</div> <div class="botTs">提交后将进入审核流程,审核通过前可以撤回</div>
</div> </div>
...@@ -233,7 +236,7 @@ ...@@ -233,7 +236,7 @@
<div class="ali"> <div class="ali">
<div class="til">请选择关闭原因</div> <div class="til">请选择关闭原因</div>
<div class="choList"> <div class="choList">
<div class="choLi" v-for="item in gbStore.choArr" @click="item.isCho=!item.isCho"> <div class="choLi" v-for="(item,index) in gbStore.choArr" @click="gbReasonCho(index)">
<img v-if="!item.isCho" src="images/noCho.png" alt=""> <img v-if="!item.isCho" src="images/noCho.png" alt="">
<img v-else src="images/cho.png" alt=""> <img v-else src="images/cho.png" alt="">
<div class="right">{{item.value}}</div> <div class="right">{{item.value}}</div>
...@@ -241,8 +244,8 @@ ...@@ -241,8 +244,8 @@
</div> </div>
</div> </div>
<div class="alertButt"> <div class="alertButt">
<div class="cancel">取消</div> <div class="cancel" @click="gbStore.isShow = false">取消</div>
<div class="submit">确认关闭</div> <div class="submit" @click="gbSubmit">确认关闭</div>
</div> </div>
<div class="botTs">关闭后将无法再进行任何编辑操作</div> <div class="botTs">关闭后将无法再进行任何编辑操作</div>
</div> </div>
......
...@@ -27,52 +27,38 @@ body { ...@@ -27,52 +27,38 @@ body {
padding-bottom: 1.2rem; /* 为底部按钮预留空间 */ padding-bottom: 1.2rem; /* 为底部按钮预留空间 */
} }
/* 顶部标题栏 */ .loginOut{
.header {
background-color: white;
border-bottom: 1px solid #f1f1f1;
position: sticky;
top: 0;
z-index: 100;
height: 0.88rem;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0 0.32rem; border-radius: .08rem;
background: #EBEDF0;
width: 1.8rem;
height: .6rem;
color: #666;
font-size: .28rem;
margin: .64rem auto;
}
.loginOut img{
width: .32rem;
height: .32rem;
margin-right: .08rem;
} }
.back-button { .topBut{
position: absolute;
left: 0.32rem;
top: 50%;
transform: translateY(-50%);
width: 0.96rem;
height: 0.96rem;
background: none;
border: none;
cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-end;
} margin-bottom: .2rem;
.back-icon {
width: 0.2rem;
height: 0.36rem;
}
.back-icon img {
width: 100%;
height: 100%;
object-fit: contain;
} }
.topBut div{
.header-title { border-radius: 4px;
font-family: 'Source Han Sans CN', sans-serif; background: #0068EE;
font-weight: bold; color: #fff;
font-size: 0.36rem; font-size: .26rem;
line-height: 0.36rem; font-weight: 500;
color: #333333; padding: .2rem .4rem;
margin-right: .1rem;
} }
/* 通用卡片样式 */ /* 通用卡片样式 */
...@@ -190,11 +176,6 @@ body { ...@@ -190,11 +176,6 @@ body {
user-select: none; user-select: none;
} }
.tag-button:hover {
border-color: #0068ee;
transform: translateY(-1px);
}
.tag-button.active { .tag-button.active {
background-color: #0068ee; background-color: #0068ee;
border-color: #0068ee; border-color: #0068ee;
......
...@@ -90,6 +90,7 @@ body { ...@@ -90,6 +90,7 @@ body {
width: 100%; width: 100%;
border: none; border: none;
display: block; display: block;
font-size: .29rem;
} }
.alertCon .ali textarea::placeholder{ .alertCon .ali textarea::placeholder{
color: #999; color: #999;
...@@ -106,6 +107,7 @@ body { ...@@ -106,6 +107,7 @@ body {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: relative;
} }
.alertCon .ali .imgList .addImg img.add{ .alertCon .ali .imgList .addImg img.add{
width: .56rem; width: .56rem;
......
...@@ -53,6 +53,29 @@ div{ ...@@ -53,6 +53,29 @@ div{
font-size: .32rem; font-size: .32rem;
} }
.tabList{
display: flex;
margin: .48rem;
color: #666;
font-size: .32rem;
background: #F1F4F9;
border-radius: .16rem;
height: .96rem;
align-items: center;
padding: .08rem;
}
.tabList div{
width: 33.33%;
text-align: center;
}
.tabList div.cho{
border-radius: .12rem;
background: #FFF;
color: #0068EE;
height: .8rem;
line-height: .8rem;
}
.logo { .logo {
width: .8rem; width: .8rem;
height: .8rem; height: .8rem;
......
...@@ -25,7 +25,7 @@ body { ...@@ -25,7 +25,7 @@ body {
position: relative; position: relative;
} }
.worker-con{ .worker-con{
padding-bottom: 1.31rem; /* 为底部导航预留空间 */ padding-bottom: 1.31rem; /* 为底部导航预留空间 */
} }
/* 搜索区域 */ /* 搜索区域 */
...@@ -117,6 +117,7 @@ body { ...@@ -117,6 +117,7 @@ body {
.filter-tags { .filter-tags {
display: flex; display: flex;
padding: 0.32rem; padding: 0.32rem;
padding-bottom: 0;
} }
.filter-tag { .filter-tag {
...@@ -133,14 +134,33 @@ body { ...@@ -133,14 +134,33 @@ body {
color: #0068EE; color: #0068EE;
} }
.loginOut{
display: flex;
align-items: center;
justify-content: center;
border-radius: .08rem;
background: #EBEDF0;
width: 1.8rem;
height: .6rem;
color: #666;
font-size: .28rem;
margin: .64rem auto;
}
.loginOut img{
width: .32rem;
height: .32rem;
margin-right: .08rem;
}
/* 商机列表 */ /* 商机列表 */
.business-list { .business-list {
padding: 0 0.32rem; padding: 0 0.32rem;
height: calc(100vh - 3.8rem); height: calc(100vh - 2.8rem);
overflow: auto; overflow: auto;
margin-top: .32rem;
} }
.worker-con .business-list{ .worker-con .business-list{
height: calc(100vh - 5.2rem); height: calc(100vh - 3.8rem);
} }
.business-card { .business-card {
......
...@@ -6,9 +6,19 @@ new Vue({ ...@@ -6,9 +6,19 @@ new Vue({
data: { data: {
isAlone: true, isAlone: true,
// 联系方式 // 联系方式
contactPhone: '138****1234', contactPhone: '',
// 商机类型
businessTypes: [],
recordingUrlArr: [], // 录音文件URL
// 选中地址文本
selectedAddressCode: '',
selectedAddressText: '',
detailAddress: '',
// 文字描述
textDescription: '',
// API相关
isSubmitting: false,
// 弹窗相关 // 弹窗相关
modifyPhone:{ modifyPhone:{
...@@ -17,57 +27,12 @@ new Vue({ ...@@ -17,57 +27,12 @@ new Vue({
phone: '', phone: '',
}, },
// 商机类型
businessTypes: [
{
id: 1,
name: '融合套餐',
selected: false,
nodeId: '6:270'
},
{
id: 2,
name: '家庭安防',
selected: false,
nodeId: '6:272'
},
{
id: 3,
name: '智能家居',
selected: false,
nodeId: '6:274'
},
{
id: 4,
name: '企业专线',
selected: false,
nodeId: '6:276'
},
{
id: 5,
name: '云服务',
selected: false,
nodeId: '6:278'
},
{
id: 6,
name: '宽带升级',
selected: false,
nodeId: '6:613'
}
],
// 语音录制状态 // 语音录制状态
isRecording: false, isRecording: false,
recordingTimer: null, recordingTimer: null,
recordingDuration: 0, recordingDuration: 0,
recordingUrlArr: [], // 录音文件URL
// 文字描述
textDescription: '',
// API相关
isSubmitting: false,
// 用户信息 // 用户信息
userInfo: null, userInfo: null,
...@@ -82,24 +47,18 @@ new Vue({ ...@@ -82,24 +47,18 @@ new Vue({
selectedDistrict: null, selectedDistrict: null,
selectedStreet: null selectedStreet: null
}, },
// 选中地址文本
selectedAddressText: '',
// 使用外部地址数据 // 使用外部地址数据
addressData: addressData addressData: addressData,
},
created() { timeWriteStr: '',
// 页面初始化时获取用户信息 nIndex: '--',
}, },
mounted() { mounted() {
this.getUserInfo() this.isAlone = utils.getUrlParam('source')!='zhijian'
// 页面加载完成后的初始化 this.queryLabel()
console.log('商机信息页面已加载'); this.getUserInfo()
// 添加页面可见性变化监听 // 添加页面可见性变化监听
document.addEventListener('visibilitychange', this.handleVisibilityChange); document.addEventListener('visibilitychange', this.handleVisibilityChange);
...@@ -111,17 +70,59 @@ new Vue({ ...@@ -111,17 +70,59 @@ new Vue({
}, },
methods: { methods: {
goZj(){
location.href = 'result.html'
},
loginOut(){
localStorage.removeItem('userInfo')
localStorage.removeItem('tokenInfo')
location.replace('login.html?platform='+localStorage.getItem('platform'))
},
navigateToList(){
location.href = 'myBusi.html'
},
queryLabel(){
utils.httpRequest({
url: '/opportunity/tags',
data: {}
}).then(res=>{
if(res.code == 200){
res.data.forEach(item=>{
item.selected = false,
item.ifCho = true
})
this.businessTypes = res.data.concat([{
id: '3',
tagName: '测试3',
selected: false,
ifCho: true
},{
id: '4',
tagName: '测试4',
selected: false,
ifCho: true
},{
id: '5',
tagName: '测试5',
selected: false,
ifCho: true
}])
}
})
},
/** /**
* 获取用户信息 * 获取用户信息
*/ */
getUserInfo() { getUserInfo() {
try { try {
const loginInfo = localStorage.getItem('appLoginInfo'); const loginInfo = localStorage.getItem('userInfo');
if (loginInfo) { if (loginInfo) {
this.userInfo = JSON.parse(loginInfo); this.userInfo = JSON.parse(loginInfo);
// 如果有用户手机号,可以脱敏显示 // 如果有用户手机号,可以脱敏显示
if (this.userInfo && this.userInfo.phone) { if (this.userInfo && this.userInfo.phone && !this.isAlone) {
this.contactPhone = this.maskPhone(this.userInfo.phone); this.contactPhone = this.maskPhone(this.userInfo.phone);
} }
} }
...@@ -145,44 +146,73 @@ new Vue({ ...@@ -145,44 +146,73 @@ new Vue({
*/ */
editContact() { editContact() {
// 打开弹窗 // 打开弹窗
this.newPhone = ''; this.modifyPhone.phone = '';
this.modifyPhone.isShow = true; this.modifyPhone.isShow = true;
}, },
/** /**
* 关闭编辑弹窗 * 关闭编辑弹窗
*/ */
closeEditModal() { closeEditModal() {
this.modifyPhone.newPhone = '';
this.modifyPhone.isShow = false this.modifyPhone.isShow = false
}, },
submitPhone(){ submitPhone(){
if(!this.modifyPhone.phone){ if(!this.modifyPhone.phone){
util.toast('请输入手机号') utils.toast('请输入手机号')
return return
} }
if(!/^1[3-9]\d{9}$/.test(this.modifyPhone.phone)){ if(!/^1[3-9]\d{9}$/.test(this.modifyPhone.phone)){
util.toast('手机号格式不正确') utils.toast('手机号格式不正确')
return return
} }
this.contactPhone = this.modifyPhone.phone
this.modifyPhone.isShow = false
}, },
/** /**
* 选择商机类型 * 选择商机类型
*/ */
selectBusinessType(typeId) { selectBusinessType(typeId) {
const type = this.businessTypes.find(t => t.id === typeId); const type = this.businessTypes.find(t => t.id === typeId)
if (type) {
type.selected = !type.selected;
if(!type.ifCho){
return
}
if (type) {
// 限制最多选择3个商机类型 // 限制最多选择3个商机类型
const selectedCount = this.businessTypes.filter(t => t.selected).length; const selectedCount = this.businessTypes.filter(t => t.selected).length;
if (selectedCount > 3) { if (selectedCount > 3) {
type.selected = false;
utils.toast('最多只能选择3个商机类型'); utils.toast('最多只能选择3个商机类型');
}else{
type.selected = !type.selected
let arr = this.businessTypes.filter(t => t.selected)
if(arr.length>0){
let idarr = arr.map(item=>{return item.id})
this.businessTypes.forEach(item=>{
if(idarr.includes(1) || idarr.includes(2)){
if(item.id!=1 && item.id!=2){
item.ifCho = false
}else{
item.ifCho = true
}
}else{
if(item.id==1 || item.id==2){
item.ifCho = false
}else{
item.ifCho = true
}
}
})
}else{
this.businessTypes.forEach(item=>{
item.ifCho = true
})
}
} }
// 添加触觉反馈 // 添加触觉反馈
...@@ -213,37 +243,68 @@ new Vue({ ...@@ -213,37 +243,68 @@ new Vue({
} }
}, },
playAudio(index){ async playAudio(index){
let pa = this.recordingUrlArr[index] let pa = this.recordingUrlArr[index]
let __this = this let obj = this.$refs.audioObj
if(!pa.url){
pa.url = URL.createObjectURL(pa.blob)
}
if(obj.src == pa.url){
const isPlaying = !obj.paused && !obj.ended && obj.readyState > 2;
const audioURL = URL.createObjectURL(pa.blob); if(isPlaying){
__this.$refs.audioObj.src = audioURL obj.pause()
pa.isPlay = false
this.writeTime(1)
}else{
obj.play()
pa.isPlay = true
this.writeTime(2,pa,obj)
}
return
}
if(this.nIndex != '--'){
clearInterval(this.timeWriteStr)
this.recordingUrlArr[this.nIndex].time = obj.duration.toFixed(0)+'s'
this.recordingUrlArr[this.nIndex].isPlay = false
}
__this.$refs.audioObj.onloadeddata = async () => { obj.src = pa.url
this.nIndex = index
obj.onloadeddata = async () => {
try { try {
await __this.$refs.audioObj.play() await obj.play()
this.writeTime(2,pa,obj)
pa.isPlay = true
console.log('音频自动播放'); console.log('音频自动播放');
} catch (error) { } catch (error) {
console.log('自动播放被阻止,需要用户交互'); console.log('自动播放被阻止,需要用户交互');
} }
}; }
let __this = this
// const audioURL = URL.createObjectURL(pa.blob); obj.addEventListener('ended', () => {
// const audio = document.createElement('audio'); clearInterval(__this.timeWriteStr)
// audio.controls = true; pa.time = obj.duration.toFixed(0)+'s'
// audio.src = audioURL; pa.isPlay = false
// document.body.appendChild(audio); __this.nIndex = '--'
});
},
writeTime(type,pa,obj){
if(type == 1){
clearInterval(this.timeWriteStr)
}else{
this.timeWriteStr = setInterval(()=>{
pa.time = (obj.duration - obj.currentTime).toFixed(0) + 's'
},1000)
}
}, },
removeAudio(index){ removeAudio(index){
this.$refs.audioObj.pause()
this.recordingUrlArr.splice(index , 1) this.recordingUrlArr.splice(index , 1)
}, },
getTimes(num){
const minutes = Math.floor(this.duration / 60);
const seconds = this.duration % 60;
return
},
/** /**
* 开始录音 * 开始录音
...@@ -262,19 +323,19 @@ new Vue({ ...@@ -262,19 +323,19 @@ new Vue({
sampleRate: 44100 sampleRate: 44100
} }
}); });
const mimeType = this.getSupportedMimeType()
const mediaRecorder = new MediaRecorder(__this.mediaStream,{ const mediaRecorder = new MediaRecorder(__this.mediaStream,{
mimeType: 'audio/webm;codecs=opus' mimeType: mimeType
}); });
const chunks = []; const chunks = [];
mediaRecorder.ondataavailable = e => chunks.push(e.data); mediaRecorder.ondataavailable = e => chunks.push(e.data);
mediaRecorder.onstop = () => { mediaRecorder.onstop = () => {
const blob = new Blob(chunks, { type: 'audio/webm;codecs=opus' }); const blob = new Blob(chunks, { type: mimeType });
__this.recordingUrlArr.push({ __this.recordingUrlArr.push({
blob: blob, blob: blob,
isPlay: false, time: __this.recordingDuration+"s"
time: __this.recordingDuration
}) })
}; };
...@@ -294,6 +355,25 @@ new Vue({ ...@@ -294,6 +355,25 @@ new Vue({
} }
}, },
getSupportedMimeType(){
const types = [
'audio/webm;codecs=opus',
'audio/webm',
'audio/ogg;codecs=opus',
'audio/ogg',
'audio/wav',
'audio/mp4;' // 某些浏览器支持
];
for (let type of types) {
if (MediaRecorder.isTypeSupported(type)) {
console.log('使用格式:', type);
return type;
}
}
return 'audio/webm'; // 默认回退
},
/** /**
* 设置音频处理 * 设置音频处理
*/ */
...@@ -302,7 +382,7 @@ new Vue({ ...@@ -302,7 +382,7 @@ new Vue({
// 暂时使用简单的计时器 // 暂时使用简单的计时器
this.recordingTimer = setInterval(() => { this.recordingTimer = setInterval(() => {
this.recordingDuration++; this.recordingDuration++;
if (this.recordingDuration >= 60*60) { // 限制最长录音一小时 if (this.recordingDuration >=60) { // 限制最长录音一分钟
this.stopRecording(); this.stopRecording();
utils.toast('录音时间已到最长限制'); utils.toast('录音时间已到最长限制');
} }
...@@ -371,71 +451,75 @@ new Vue({ ...@@ -371,71 +451,75 @@ new Vue({
}, },
/** /**
* 获取选中的商机类型名称
*/
getSelectedBusinessTypes() {
return this.businessTypes
.filter(type => type.selected)
.map(type => type.name);
},
/**
* 提交商机 * 提交商机
*/ */
async submitBusiness() { async submitBusiness() {
// 表单验证 // 表单验证
if (!this.validateForm()) { if (this.isAlone && !this.contactPhone) {
utils.toast('请输入手机号');
return;
}
if (!/^1[3-9]\d{9}$/.test(this.contactPhone)) {
utils.toast('请输入正确的手机号');
return;
}
if (!this.selectedAddressText && this.isAlone) {
utils.toast('请选择用户地址');
return; return;
} }
if (!this.detailAddress && this.isAlone) {
utils.toast('请输入详细地址');
return;
}
// 检查是否选择了商机类型
const selectedTypes = this.businessTypes.filter(t => t.selected);
if (selectedTypes.length === 0) {
utils.toast('请选择商机类型');
return false;
}
if (this.isSubmitting) { if (this.isSubmitting) {
utils.toast('正在提交,请稍候...'); utils.toast('正在提交,请稍候...');
return; return;
} }
this.isSubmitting = true
this.isSubmitting = true;
utils.toast('正在提交商机信息...');
try { try {
// 准备提交数据 const fd = new FormData();
const businessData = {
// 商机类型 fd.append('customerPhone',this.contactPhone)
businessTypes: this.getSelectedBusinessTypes(), fd.append('customerAddress',this.selectedAddressCode?(this.selectedAddressCode+":"+this.selectedAddressText+this.detailAddress):'')
// 描述信息 const tagArr = selectedTypes.map(item=>{return item.id})
textDescription: this.textDescription.trim(), fd.append('opportunityType',(tagArr.includes(1)||tagArr.includes(2))?'2':'1')
voiceDuration: this.recordingDuration, fd.append('tagIds',tagArr.join(','))
// 联系方式 fd.append('textDescription',this.textDescription)
contactPhone: this.contactPhone, fd.append('audioType',this.getSupportedMimeType())
if(this.recordingUrlArr.length > 0){
// 用户信息 this.recordingUrlArr.forEach(item=>{
userId: this.userInfo ? this.userInfo.userId : null, fd.append('audioFiles',item.blob)
})
// 提交时间 }
submitTime: new Date().toISOString(),
console.log(fd)
// 设备信息
deviceInfo: {
userAgent: navigator.userAgent,
isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
platform: navigator.platform,
language: navigator.language
}
};
// 调用API提交 // 调用API提交
const result = await this.submitBusinessAPI(businessData); const result = await utils.httpRequest({
url: '/opportunity/create',
data: fd
})
if (result.success) { if (result.code == 200) {
utils.toast('商机提交成功!'); utils.toast('商机提交成功!')
// 添加触觉反馈
this.addHapticFeedback();
// 延迟后重置表单或跳转
setTimeout(() => { setTimeout(() => {
this.handleSubmitSuccess(); location.href = 'myBusi.html'
}, 2000); }, 1000);
} else { } else {
utils.toast(result.message || '提交失败,请重试'); utils.toast(result.message || '提交失败,请重试');
} }
...@@ -685,20 +769,26 @@ new Vue({ ...@@ -685,20 +769,26 @@ new Vue({
*/ */
confirmAddressSelection() { confirmAddressSelection() {
const parts = []; const parts = [];
const codes = [];
if (this.addressSelector.selectedProvince) { if (this.addressSelector.selectedProvince) {
codes.push(this.addressSelector.selectedProvince.code);
parts.push(this.addressSelector.selectedProvince.name); parts.push(this.addressSelector.selectedProvince.name);
} }
if (this.addressSelector.selectedCity) { if (this.addressSelector.selectedCity) {
codes.push(this.addressSelector.selectedCity.code);
parts.push(this.addressSelector.selectedCity.name); parts.push(this.addressSelector.selectedCity.name);
} }
if (this.addressSelector.selectedDistrict) { if (this.addressSelector.selectedDistrict) {
codes.push(this.addressSelector.selectedDistrict.code);
parts.push(this.addressSelector.selectedDistrict.name); parts.push(this.addressSelector.selectedDistrict.name);
} }
if (this.addressSelector.selectedStreet) { if (this.addressSelector.selectedStreet) {
codes.push(this.addressSelector.selectedStreet.code);
parts.push(this.addressSelector.selectedStreet.name); parts.push(this.addressSelector.selectedStreet.name);
} }
this.selectedAddressCode = codes.join('|');
this.selectedAddressText = parts.join(''); this.selectedAddressText = parts.join('');
this.closeAddressSelector(); this.closeAddressSelector();
this.addHapticFeedback(); this.addHapticFeedback();
...@@ -737,29 +827,6 @@ new Vue({ ...@@ -737,29 +827,6 @@ new Vue({
// 计算属性 // 计算属性
computed: { computed: {
/** /**
* 已选择的商机类型数量
*/
selectedTypesCount() {
return this.businessTypes.filter(type => type.selected).length;
},
/**
* 是否可以提交
*/
canSubmit() {
return this.selectedTypesCount > 0 &&
(this.textDescription.trim() || this.recordingDuration > 0) &&
!this.isSubmitting;
},
/**
* 格式化的录音时长显示
*/
formattedRecordingDuration() {
return this.formatRecordingDuration(this.recordingDuration);
},
/**
* 地址选择标签页 * 地址选择标签页
*/ */
addressTabs() { addressTabs() {
......
...@@ -5,39 +5,12 @@ new Vue({ ...@@ -5,39 +5,12 @@ new Vue({
el: '#app', el: '#app',
data: { data: {
isWorker: false, isWorker: true,
platform: '',
// 商机详情数据 - 按照设计图中的真实数据 detailId: '',
businessDetail: { businessDetail: {},
businessId: 'OP20250928002', // 商机ID followList: [],
status: '跟进中', // 状态 followNewInfo:{},
statusDescription: '营销人员 [张三] 将状态变更为跟进中', // 状态描述
customerAddress: '江苏省淮安市淮安区淮海南路456号', // 客户地址
customerAccount: '13477895643', // 客户账号
customerPhone: '155****3344', // 客户电话
marketerPhone: '131****3564', // 营销人员电话
marketingTypes: ['宽带升级', '家庭安防'], // 营销类型
submitTime: '2025-09-26 11:20:00', // 提交时间
voiceRecords: [
{
duration: '00:04',
converted: false,
isPlaying: false
},
{
duration: '00:04',
converted: false,
isPlaying: false
}
],
textDescription: '客户家里目前使用的是50兆的宽带,感觉网速有点慢,想要升级到100兆或者200兆。客户还问了一下家庭套餐的情况,想了解一下手机和宽带一起办理有没有优惠。', // 文字描述
adminNotes: '客户比较倾向于性价比高的方案,建议重点推荐融合套餐', // 管理员备注
followUpStatus: '跟进中', // 跟进状态
followUpTime: '2025-09-29 11:10', // 跟进时间
followUpDescription: '营销人员 [张三] 添加跟进:"已上门与客户沟通,客户确认办理融合套餐,待签单。', // 跟进描述
createTime: '2025-09-29 09:30', // 创建时间
createDescription: '张师傅 提交了此商机' // 创建描述
},
// 页面状态 // 页面状态
isLoading: false, isLoading: false,
...@@ -49,9 +22,13 @@ new Vue({ ...@@ -49,9 +22,13 @@ new Vue({
gjStore: { gjStore: {
isShow: false, isShow: false,
des: '',
imgArr: [],
isLoading: false isLoading: false
}, },
cdStore: { cdStore: {
des: '',
isShow: false isShow: false
}, },
gbStore: { gbStore: {
...@@ -76,12 +53,19 @@ new Vue({ ...@@ -76,12 +53,19 @@ new Vue({
isCho: false, isCho: false,
value: '其他原因' value: '其他原因'
}] }]
} },
timeWriteStr: '',
nIndex: '--'
}, },
created() { created() {
// 初始化页面 this.platform = localStorage.getItem('platform')
this.initializeApp(); this.isWorker = localStorage.getItem('platform')=='gw'
this.detailId = utils.getUrlParam('id')
this.queryDetail()
this.queryFollow()
}, },
mounted() { mounted() {
...@@ -95,152 +79,276 @@ new Vue({ ...@@ -95,152 +79,276 @@ new Vue({
}, },
methods: { methods: {
fileChange(e){ getAddressShow(value){
if(this.gjStore.isLoading){ if(!value){
return '--'
}
return value.split(":")[1]
},
addBusiProcess(){
if(!this.ifCanGj){
return return
} }
let file = e.target.files[0] this.gjStore.des = ''
console.log(file) this.gjStore.imgArr = []
if(file.type.includes('image/') <= 0){
util.toast('请上传图片') this.gjStore.isShow = true
},
async gjSubmit(){
let d = this.gjStore
if(!d.des){
utils.toast('请输入跟进内容')
return return
} }
// if(d.imgArr.length <= 0){
// utils.toast('请上传图片')
// return
// }
const fd = new FormData();
fd.append('followContent',d.des)
fd.append('opportunityId',this.detailId)
d.imgArr.forEach(item=>{
fd.append('images',item.file)
})
this.uploadImg(file) // 调用API提交
}, const result = await utils.httpRequest({
uploadImg(file){ url: '/opportunity/follow',
let fileFormData = new FormData() data: fd
fileFormData.append('file', file)
this.gjStore.isLoading = true
return
util.httpRequest({
url: '/api/comfyui/uploadImage',
data: fileFormData
}).then(res=>{
this.loading = false
if(res.code =='200'){
this['img'+type] = res.data.name
}else{
util.toast(res.msg)
}
}) })
},
/** if (result.code == 200) {
* 初始化应用 utils.toast('跟进提交成功!');
*/ this.gjStore.isShow = false
initializeApp() { this.queryFollow()
// 获取URL参数 } else {
this.getUrlParams(); utils.toast(result.message || '提交失败,请重试');
}
},
completeBusi(){
if(!this.ifCanCd){
return
}
// 获取用户信息 this.cdStore.des = ''
this.getUserInfo();
// 加载商机详情 this.cdStore.isShow = true
this.loadBusinessDetail();
}, },
async cdSubmit(){
let d = this.cdStore
/** if(!d.des){
* 获取URL参数 utils.toast('请输入成单理由')
*/ return
getUrlParams() { }
const urlParams = new URLSearchParams(window.location.search);
this.businessDetail.businessId = urlParams.get('id') || this.businessDetail.businessId;
},
/** // 调用API提交
* 获取用户信息 const result = await utils.httpRequest({
*/ url: '/opportunity/deal',
getUserInfo() { data: {
try { opportunityId: this.detailId,
const loginInfo = localStorage.getItem('appLoginInfo'); reason: d.des
if (loginInfo) {
this.userInfo = JSON.parse(loginInfo);
} }
} catch (error) { })
console.error('获取用户信息失败:', error);
if (result.code == 200) {
utils.toast('成单成功!');
this.cdStore.isShow = false
this.queryFollow()
} else {
utils.toast(result.message || '提交失败,请重试');
} }
}, },
closeBusi(){
if(!this.ifCanGb){
return
}
/** this.gbStore.choArr.forEach(item=>{
* 加载商机详情 item.isCho = false
*/ })
async loadBusinessDetail() {
this.isLoading = true;
try { this.gbStore.isShow = true
// 模拟API调用 - 实际项目中替换为真实API },
await this.simulateAPICall(); gbReasonCho(index){
this.gbStore.choArr.forEach(item=>{
item.isCho = false
})
this.gbStore.choArr[index].isCho = true
},
async gbSubmit(){
let d = this.gbStore
// 实际项目中的API调用 let arr = d.choArr.filter(item=>{return item.isCho})
// const result = await this.getBusinessDetailAPI(this.businessDetail.businessId); if(arr.length <= 0){
utils.toast('请选择原因')
return
}
} catch (error) { // 调用API提交
console.error('加载商机详情失败:', error); const result = await utils.httpRequest({
utils.toast('加载失败,请重试'); url: '/opportunity/close',
} finally { data: {
this.isLoading = false; opportunityId: this.detailId,
reason: arr[0].value
}
})
if (result.code == 200) {
utils.toast('关闭成功!');
this.gbStore.isShow = false
this.queryDetail()
} else {
utils.toast(result.message || '提交失败,请重试');
} }
}, },
getStatusName(value){
if(value == '1'){
return '待跟进'
}else if(value == '2'){
return '跟进中'
}else if(value == '3'){
return '成单待审核'
}else if(value == '4'){
return '已成单'
}else if(value == '5'){
return '已关闭'
}
/** return '--'
* 模拟API调用
*/
simulateAPICall() {
return new Promise((resolve) => {
setTimeout(() => {
// 模拟加载效果,实际使用上面定义的静态数据
resolve();
}, 800);
});
}, },
queryFollow(){
utils.httpRequest({
url: '/opportunity/follow/list',
data: {
opportunityId: this.detailId,
pageNum: 1,
pageSize: 1000
}
}).then(res=>{
if(res.code == 200){
this.followList = res.data.records
/** let pa = res.data.records[0]
* 获取语音记录节点ID this.followNewInfo = {
*/ statusName: this.getStatusName(pa.status),
getVoiceNodeId(index) { des: pa.followPersonName+':'+pa.followContent
const baseId = 2942377; }
return `294:${baseId + index}`; }
})
}, },
queryDetail(){
utils.httpRequest({
url: '/opportunity/detail',
data: {
opportunityId: this.detailId
}
}).then(res=>{
if(res.code == 200){
if(res.data.voiceDescriptionUrl){
let arr = []
res.data.voiceDescriptionUrl.split(',').forEach(item=>{
arr.push({
url: item,
text: '',
isPlay: false,
time: ''
})
})
res.data.voiceRecords = arr
}
/** this.businessDetail = res.data
* 获取波形条高度 }
*/ })
getWaveformHeight(n) {
// 为31个波形条生成不同的高度,模拟真实波形显示
const heights = [
0.02, 0.06, 0.10, 0.14, 0.18, 0.22, 0.26, 0.30, 0.34, 0.38,
0.42, 0.38, 0.34, 0.30, 0.26, 0.22, 0.18, 0.14, 0.10, 0.06,
0.02, 0.06, 0.10, 0.14, 0.18, 0.22, 0.26, 0.30, 0.34, 0.38
];
return `${heights[n - 1] || 0.06}rem`;
}, },
async playAudio(index){
let pa = this.businessDetail.voiceRecords[index]
let obj = this.$refs.audioObj
/** if(obj.src == pa.url){
* 返回上一页 const isPlaying = !obj.paused && !obj.ended && obj.readyState > 2;
*/
goBack() { if(isPlaying){
if (window.history.length > 1) { obj.pause()
window.history.back(); pa.isPlay = false
} else { this.writeTime(1)
// 如果没有历史记录,返回到商机列表 }else{
window.location.href = 'busiList.html'; obj.play()
pa.isPlay = true
this.writeTime(2,pa,obj)
}
return
}
if(this.nIndex != '--'){
clearInterval(this.timeWriteStr)
this.businessDetail.voiceRecords[this.nIndex].time = obj.duration.toFixed(0)+'s'
this.businessDetail.voiceRecords[this.nIndex].isPlay = false
}
obj.src = pa.url
this.nIndex = index
obj.onloadeddata = async () => {
try {
await obj.play()
this.writeTime(2,pa,obj)
pa.isPlay = true
console.log('音频自动播放');
console.log(this.businessDetail.voiceRecords)
} catch (error) {
console.log('自动播放被阻止,需要用户交互');
}
}
let __this = this
obj.addEventListener('ended', () => {
clearInterval(__this.timeWriteStr)
pa.time = obj.duration.toFixed(0)+'s'
pa.isPlay = false
__this.nIndex = '--'
});
},
writeTime(type,pa,obj){
if(type == 1){
clearInterval(this.timeWriteStr)
}else{
clearInterval(this.timeWriteStr)
this.timeWriteStr = setInterval(()=>{
pa.time = (obj.duration - obj.currentTime).toFixed(0) + 's'
},1000)
}
},
fileChange(e){
if(this.gjStore.isLoading){
return
}
let file = e.target.files[0]
console.log(file)
if(file.type.includes('image/') <= 0){
util.toast('请上传图片')
return
} }
this.gjStore.imgArr.push({
file: file,
url: URL.createObjectURL(file)
})
}, },
/** /**
* 复制客户电话 * 复制客户电话
*/ */
copyPhone() { copyPhone() {
const phone = this.businessDetail.customerPhone; const fullPhone = this.businessDetail.customerPhone;
// 从脱敏号码还原(实际项目中应该从API获取完整号码)
const fullPhone = '15555553344';
if (navigator.clipboard) { if (navigator.clipboard) {
navigator.clipboard.writeText(fullPhone).then(() => { navigator.clipboard.writeText(fullPhone).then(() => {
utils.toast('手机号已复制'); utils.toast('客户电话');
this.addHapticFeedback(); this.addHapticFeedback();
}).catch(() => { }).catch(() => {
this.fallbackCopyToClipboard(fullPhone); this.fallbackCopyToClipboard(fullPhone);
...@@ -254,9 +362,7 @@ new Vue({ ...@@ -254,9 +362,7 @@ new Vue({
* 复制营销人员电话 * 复制营销人员电话
*/ */
copyMarketerPhone() { copyMarketerPhone() {
const phone = this.businessDetail.marketerPhone; const fullPhone = this.businessDetail.marketingStaffPhone;
// 从脱敏号码还原
const fullPhone = '13111113564';
if (navigator.clipboard) { if (navigator.clipboard) {
navigator.clipboard.writeText(fullPhone).then(() => { navigator.clipboard.writeText(fullPhone).then(() => {
...@@ -303,26 +409,23 @@ new Vue({ ...@@ -303,26 +409,23 @@ new Vue({
} }
}, },
/** switchTime(value){
* 致电营销人员 return utils.detailTime(new Date(value).getTime())
*/
callMarketer() {
const phone = this.businessDetail.marketerPhone;
const fullPhone = '13111113564'; // 实际号码
if (typeof device !== 'undefined' && device.platform === 'iOS') {
window.location.href = `tel:${fullPhone}`;
} else {
window.open(`tel:${fullPhone}`, '_self');
}
}, },
/** /**
* 联系客户 * 打电话
*/ */
callCustomer() { takeCall(type){
const phone = this.businessDetail.customerPhone; //客户电话
const fullPhone = '15555553344'; // 实际号码 let fullPhone = this.businessDetail.customerPhone
if(type == 'yx'){
//营销人员电话
fullPhone = this.businessDetail.marketingStaffPhone
}else if(type == 'zw'){
//装维师傅电话
fullPhone = this.businessDetail.maintenanceStaffPhone
}
if (typeof device !== 'undefined' && device.platform === 'iOS') { if (typeof device !== 'undefined' && device.platform === 'iOS') {
window.location.href = `tel:${fullPhone}`; window.location.href = `tel:${fullPhone}`;
...@@ -474,6 +577,24 @@ new Vue({ ...@@ -474,6 +577,24 @@ new Vue({
} }
}, },
audioToText(index){
let pa = this.businessDetail.voiceRecords[index]
utils.httpRequest({
url: '/audio-to-text',
middle: 'common',
data: {
audioUrl: pa.url
}
}).then(res=>{
if(res.code == 200){
pa.text = res.data.textContent
}else{
utils.toast(res.message)
}
})
},
/** /**
* 清理资源 * 清理资源
*/ */
...@@ -493,6 +614,15 @@ new Vue({ ...@@ -493,6 +614,15 @@ new Vue({
// 计算属性 // 计算属性
computed: { computed: {
ifCanGj(){
return this.businessDetail.status==1||this.businessDetail.status==2
},
ifCanCd(){
return this.businessDetail.status==1||this.businessDetail.status==2
},
ifCanGb(){
return this.businessDetail.status==1||this.businessDetail.status==2||this.businessDetail.status==3
},
/** /**
* 是否有跟进状态 * 是否有跟进状态
*/ */
...@@ -524,31 +654,6 @@ new Vue({ ...@@ -524,31 +654,6 @@ new Vue({
// 监听器 // 监听器
watch: { watch: {
/**
* 监听语音播放状态变化
*/
'businessDetail.voiceRecords': {
handler(newRecords) {
const isAnyPlaying = newRecords.some(record => record.isPlaying);
if (isAnyPlaying) {
document.title = '播放语音中...';
} else {
document.title = '商机详情';
}
},
deep: true
},
/**
* 监听页面可见性变化
*/
isAnyPlaying(newVal) {
if (document.hidden && newVal) {
// 页面隐藏时暂停所有播放
this.businessDetail.voiceRecords.forEach(record => {
record.isPlaying = false;
});
}
}
} }
}); });
\ No newline at end of file \ No newline at end of file
...@@ -3,16 +3,33 @@ const util = new window.publicMethod() ; ...@@ -3,16 +3,33 @@ const util = new window.publicMethod() ;
new Vue({ new Vue({
el: '#app', el: '#app',
data: { data: {
platform: 'yx',
loginForm: { loginForm: {
employeeId: '', employeeId: '23123456',
phoneNumber: '', phoneNumber: '13718590607',
verifyCode: '' verifyCode: '123456'
}, },
countdown: 0, countdown: 0,
countdownTimer: null, countdownTimer: null,
clickFlag: false clickFlag: false
}, },
methods: { methods: {
tabChang(type){
this.platform = type
localStorage.setItem('platform',type)
if(type == 'gw'){
this.loginForm.employeeId = '1235456'
this.loginForm.phoneNumber = '13718596969'
}else if(type == 'yx'){
this.loginForm.employeeId = '23123456'
this.loginForm.phoneNumber = '13718590607'
}else{
this.loginForm.employeeId = '15862709858'
this.loginForm.phoneNumber = '15862709858'
}
},
getVerifyCode() { getVerifyCode() {
let pa = this.loginForm let pa = this.loginForm
...@@ -37,10 +54,9 @@ new Vue({ ...@@ -37,10 +54,9 @@ new Vue({
this.clickFlag = true this.clickFlag = true
util.httpRequest({ util.httpRequest({
url: '/mobile/sendSms', url: '/send-sms',
middleUrl: '/zhijian-trial/api',
data: { data: {
campaignId: pa.employeeId, personnelCode: pa.employeeId,
phone: pa.phoneNumber, phone: pa.phoneNumber,
}, },
}).then(res=>{ }).then(res=>{
...@@ -59,7 +75,7 @@ new Vue({ ...@@ -59,7 +75,7 @@ new Vue({
}, 1000); }, 1000);
}else{ }else{
this.clickFlag = false this.clickFlag = false
util.toast(res.msg || '获取失败') util.toast(res.message || '获取失败')
} }
}).catch(()=>{ }).catch(()=>{
this.clickFlag = false this.clickFlag = false
...@@ -98,24 +114,30 @@ new Vue({ ...@@ -98,24 +114,30 @@ new Vue({
} }
util.httpRequest({ util.httpRequest({
url: '/mobile/login', url: '/phone-login',
middleUrl: '/zhijian-trial/api',
data: { data: {
campaignId: pa.employeeId, personnelCode: pa.employeeId,
phone: pa.phoneNumber, phone: pa.phoneNumber,
smsCode: pa.verifyCode smsCode: pa.verifyCode
}, },
}).then(res=>{ }).then(res=>{
if(res.code == 200){ if(res.code == 200){
localStorage.setItem('userInfo',JSON.stringify(res.data.personnel))
localStorage.setItem('tokenInfo',JSON.stringify(res.data.tokenInfo))
if(this.platform == 'gw'){
window.location.href = 'addBusi.html'
}else{
window.location.href = 'myBusi.html'
}
}else{ }else{
util.toast(res.msg || '登录失败') util.toast(res.message || '登录失败')
} }
}) })
} }
}, },
created(){ created(){
}, },
beforeDestroy() { beforeDestroy() {
// 清除定时器 // 清除定时器
......
// 商机信息页面 Vue 应用
const utils = new publicMethod()
new Vue({ new Vue({
el: '#app', el: '#app',
data: { data: {
isWorker: true, isWorker: true,
detail: {
records: []
},
searchKeyword: '', searchKeyword: '',
activeTab: 'completed', activeTab: 'all',
tagId: 'all',
filterTags: [ filterTags: [
{ id: 1, name: '全部', selected: true, nodeId: '355:535' }, { id: 'all', name: '全部', selected: true, nodeId: '355:535' },
{ id: 2, name: '成单待审核', selected: false, nodeId: '355:537' }, { id: '3', name: '成单待审核', selected: false, nodeId: '355:537' },
{ id: 3, name: '已成单', selected: false, nodeId: '355:540' }, { id: '4', name: '已成单', selected: false, nodeId: '355:540' },
{ id: 4, name: '已关闭', selected: false, nodeId: '355:543' } { id: '5', name: '已关闭', selected: false, nodeId: '355:543' }
], ],
businessList: [ businessList: []
{
id: 1,
orderNumber: 'OP20250928002',
status: 'follow-up',
statusText: '跟进中',
statusClass: 'follow-up',
nodeId: '355:430',
address: '江苏省淮安市淮安区淮海南路456号',
contactPhone: '13477895643',
tags: [
{ id: 1, name: '宽带升级', nodeId: '355:453' },
{ id: 2, name: '家庭安防', nodeId: '355:455' }
],
processor: '李四',
submitTime: '2025-09-28 16:45',
updateTime: '2025-09-29 09:30'
},
{
id: 2,
orderNumber: 'OP20250928003',
status: 'completed',
statusText: '已完结',
statusClass: 'completed',
nodeId: '355:465',
address: '江苏省淮安市淮安区淮海南路456号',
contactPhone: '13477895643',
tags: [
{ id: 1, name: '宽带升级', nodeId: '355:488' },
{ id: 2, name: '家庭安防', nodeId: '355:490' }
],
processor: '张三',
submitTime: '2025-09-28 16:45',
updateTime: '2025-09-29 09:30'
},
{
id: 3,
orderNumber: 'OP20250927001',
status: 'pending',
statusText: '待跟进',
statusClass: 'pending',
nodeId: '355:491',
address: '江苏省淮安市清江浦区健康东路123号',
contactPhone: '13912345678',
tags: [
{ id: 3, name: '企业宽带', nodeId: '355:492' }
],
processor: '王五',
submitTime: '2025-09-27 10:20',
updateTime: '2025-09-28 14:15'
},
{
id: 4,
orderNumber: 'OP20250926005',
status: 'closed',
statusText: '已关闭',
statusClass: 'closed',
nodeId: '355:493',
address: '江苏省淮安市经济技术开发区枚皋中路789号',
contactPhone: '15898765432',
tags: [
{ id: 4, name: '云服务', nodeId: '355:494' },
{ id: 5, name: 'IDC业务', nodeId: '355:495' }
],
processor: '赵六',
submitTime: '2025-09-26 15:30',
updateTime: '2025-09-27 11:45'
}
]
}, },
computed: { computed: {
// 统计各状态数量
totalCount() { },
return this.businessList.length; methods: {
}, addressShow(value){
pendingCount() { if(!value || value==':'){
return this.businessList.filter(item => item.status === 'pending').length; return '--'
},
followUpCount() {
return this.businessList.filter(item => item.status === 'follow-up').length;
},
completedCount() {
return this.businessList.filter(item => item.status === 'completed').length;
},
// 筛选后的商机列表
filteredBusinessList() {
let list = this.businessList;
// 按标签页筛选
if (this.activeTab !== 'all') {
list = list.filter(item => item.status === this.activeTab);
} }
// 按筛选标签筛选 return value.split(":")[1]
const selectedTag = this.filterTags.find(tag => tag.selected); },
if (selectedTag && selectedTag.id !== 1) { switchTime(value){
// 这里可以根据实际业务逻辑进行筛选 return utils.detailTime(new Date(value).getTime(),true)
switch(selectedTag.id) { },
case 2: // 成单待审核 queryBusiList(){
list = list.filter(item => item.status === 'completed'); let status
break; if(this.activeTab == 'all'){
case 3: // 已成单 status = ''
list = list.filter(item => item.status === 'completed'); }else{
break; if(this.activeTab == '3,4,5'){
case 4: // 已关闭 if(this.tagId == 'all'){
list = list.filter(item => item.status === 'closed'); status = '3,4,5'
break; }else{
status = this.tagId
}
}else{
status = this.activeTab
} }
} }
// 按搜索关键词筛选 utils.httpRequest({
if (this.searchKeyword) { url: '/opportunity/list',
const keyword = this.searchKeyword.toLowerCase(); data: {
list = list.filter(item => customerPhone: this.searchKeyword,
item.orderNumber.toLowerCase().includes(keyword) || status: status,
item.address.toLowerCase().includes(keyword) || pageNum: 1,
item.contactPhone.includes(keyword) || pageSize: 1000
item.processor.toLowerCase().includes(keyword) }
); }).then(res=>{
} if(res.code == 200){
this.detail = res.data
}
})
},
return list;
}
},
methods: {
// 切换标签页 // 切换标签页
switchTab(tab) { switchTab(tab) {
this.activeTab = tab; this.activeTab = tab
this.updateActiveIndicator(tab); this.updateActiveIndicator(tab)
if(tab == '3,4,5'){
this.tagId = 'all'
}
this.queryBusiList()
}, },
// 更新活跃指示器位置 // 更新活跃指示器位置
...@@ -151,9 +84,9 @@ new Vue({ ...@@ -151,9 +84,9 @@ new Vue({
let targetIndex = 0; let targetIndex = 0;
switch(tab) { switch(tab) {
case 'all': targetIndex = 0; break; case 'all': targetIndex = 0; break;
case 'pending': targetIndex = 1; break; case '1': targetIndex = 1; break;
case 'follow-up': targetIndex = 2; break; case '2': targetIndex = 2; break;
case 'completed': targetIndex = 3; break; case '3,4,5': targetIndex = 3; break;
} }
if (indicator && tabTexts[targetIndex]) { if (indicator && tabTexts[targetIndex]) {
...@@ -168,25 +101,21 @@ new Vue({ ...@@ -168,25 +101,21 @@ new Vue({
// 选择筛选标签 // 选择筛选标签
selectFilterTag(tagId) { selectFilterTag(tagId) {
this.filterTags.forEach(tag => { this.tagId = tagId
tag.selected = tag.id === tagId;
}); this.queryBusiList()
}, },
// 查看商机详情 // 查看商机详情
viewBusinessDetail(business) { viewBusinessDetail(business) {
console.log('查看商机详情:', business); console.log('查看商机详情:', business);
// 实际项目中这里应该跳转到详情页面 // 实际项目中这里应该跳转到详情页面
// window.location.href = `busiDetail.html?id=${business.id}`; window.location.href = `busiDetail.html?id=${business.id}`;
// 或者显示详情弹窗
alert(`查看商机详情: ${business.orderNumber}`);
}, },
// 跳转到收集商机页面 // 跳转到收集商机页面
navigateToCollect() { navigateToCollect() {
console.log('跳转到收集商机页面'); window.location.href = 'addBusi.html';
window.location.href = 'addbusi.html';
}, },
// 搜索功能 // 搜索功能
...@@ -205,89 +134,24 @@ new Vue({ ...@@ -205,89 +134,24 @@ new Vue({
// 执行搜索 // 执行搜索
performSearch() { performSearch() {
console.log('执行搜索:', this.searchKeyword); this.queryBusiList()
// 搜索逻辑已在computed中实现 },
}, getStatusCalss(status){
if(status == '1'){
// 格式化时间 return 'pending'
formatTime(timeString) { }else if(status == '2'){
if (!timeString) return ''; return 'follow-up'
return timeString; }else if(status == '5'){
}, return 'closed'
}else{
// 获取商机状态样式类 return 'completed'
getStatusClass(status) {
const statusMap = {
'pending': 'pending',
'follow-up': 'follow-up',
'completed': 'completed',
'closed': 'closed'
};
return statusMap[status] || 'pending';
},
// 获取商机状态文本
getStatusText(status) {
const statusMap = {
'pending': '待跟进',
'follow-up': '跟进中',
'completed': '已完结',
'closed': '已关闭'
};
return statusMap[status] || '待跟进';
},
// 初始化数据
initializeData() {
console.log('初始化数据');
// 延迟更新指示器位置,确保DOM已渲染
this.$nextTick(() => {
this.updateActiveIndicator(this.activeTab);
});
},
// 处理页面可见性变化
handleVisibilityChange() {
if (document.visibilityState === 'visible') {
console.log('页面重新可见,刷新数据');
this.refreshData();
} }
}, },
loginOut(){
localStorage.removeItem('userInfo')
localStorage.removeItem('tokenInfo')
// 处理网络连接恢复 location.replace('login.html?platform='+localStorage.getItem('platform'))
handleOnline() {
console.log('网络连接恢复');
this.refreshData();
},
// 处理网络断开
handleOffline() {
console.log('网络连接断开');
this.showNetworkError();
},
// 刷新数据
refreshData() {
console.log('刷新商机列表数据');
// 实际项目中这里应该重新调用API获取最新数据
},
// 显示网络错误提示
showNetworkError() {
console.error('网络连接断开,请检查网络设置');
},
// 显示错误信息
showError(message) {
console.error(message);
alert(message);
},
// 显示成功信息
showSuccess(message) {
console.log(message);
alert(message);
} }
}, },
...@@ -304,22 +168,14 @@ new Vue({ ...@@ -304,22 +168,14 @@ new Vue({
// 生命周期钩子 // 生命周期钩子
mounted() { mounted() {
console.log('我的商机页面已加载'); this.isWorker = localStorage.getItem('platform')=='gw'
// 初始化数据 // 延迟更新指示器位置,确保DOM已渲染
this.initializeData(); this.$nextTick(() => {
// 添加页面可见性变化监听
document.addEventListener('visibilitychange', this.handleVisibilityChange);
// 添加网络状态监听
window.addEventListener('online', this.handleOnline);
window.addEventListener('offline', this.handleOffline);
// 监听窗口大小变化,更新指示器位置
window.addEventListener('resize', () => {
this.updateActiveIndicator(this.activeTab); this.updateActiveIndicator(this.activeTab);
}); });
this.queryBusiList()
}, },
beforeDestroy() { beforeDestroy() {
...@@ -327,11 +183,5 @@ new Vue({ ...@@ -327,11 +183,5 @@ new Vue({
if (this.searchTimer) { if (this.searchTimer) {
clearTimeout(this.searchTimer); clearTimeout(this.searchTimer);
} }
// 移除事件监听
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
window.removeEventListener('online', this.handleOnline);
window.removeEventListener('offline', this.handleOffline);
window.removeEventListener('resize', this.updateActiveIndicator);
} }
}); });
\ No newline at end of file \ No newline at end of file
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
} }
}, },
methods: { methods: {
goAdd(){
location.href = 'addBusi.html?source=zhijian'
},
goBack(){ goBack(){
history.go(-1) history.go(-1)
} }
......
...@@ -72,8 +72,23 @@ ...@@ -72,8 +72,23 @@
* *
*/ */
httpRequest:function(param){ httpRequest:function(param){
let middle = param.middleUrl || '/zhijian-trial/ha' let middle = '/compass/api/'
let pa = localStorage.getItem('appLoginInfo')
if(param.middle){
middle += param.middle
}else{
if(localStorage.getItem('platform') == 'gw'){
middle += 'personnel'
}
if(localStorage.getItem('platform') == 'yx'){
middle += 'marketing'
}
if(localStorage.getItem('platform') == 'gov'){
middle += 'gov-marketing'
}
}
let pa = localStorage.getItem('tokenInfo')
if(pa){ if(pa){
pa = JSON.parse(pa) pa = JSON.parse(pa)
}else{ }else{
...@@ -88,7 +103,7 @@ ...@@ -88,7 +103,7 @@
data:param.data, data:param.data,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'x-access-token': pa.tokenValue 'Authorization': pa.token||''
} }
}).then((res) => { }).then((res) => {
resolve(res.data) resolve(res.data)
...@@ -97,6 +112,31 @@ ...@@ -97,6 +112,31 @@
}) })
}) })
}, },
detailTime: function(num,flag) {
if (num == null) {
return '';
} else {
num = Number(num);
let d = new Date(num);
let year = d.getFullYear();
let month = d.getMonth() + 1;
let date = d.getDate();
let hour = d.getHours();
let minute = d.getMinutes();
let second = d.getSeconds();
month < 10 ? month = '0' + month : month;
date < 10 ? date = '0' + date : date;
hour < 10 ? hour = '0' + hour : hour;
minute < 10 ? minute = '0' + minute : minute;
second < 10 ? second = '0' + second : second;
if(flag){
return year + "-" + month + "-" + date + " " + hour + ":" + minute
}
return year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second;
}
},
} }
window.publicMethod = utils window.publicMethod = utils
})() })()
\ No newline at end of file \ No newline at end of file
...@@ -20,6 +20,12 @@ ...@@ -20,6 +20,12 @@
</div> </div>
</div> </div>
<div class="tabList">
<div :class="{cho: platform=='yx'}" @click="tabChang('yx')">C端支撑</div>
<div :class="{cho: platform=='gov'}" @click="tabChang('gov')">政企支撑</div>
<div :class="{cho: platform=='gw'}" @click="tabChang('gw')">装维师傅</div>
</div>
<div class="login-form"> <div class="login-form">
<div class="input-group"> <div class="input-group">
<img src="images/1.png" alt="工号图标" class="input-icon"> <img src="images/1.png" alt="工号图标" class="input-icon">
...@@ -58,7 +64,7 @@ ...@@ -58,7 +64,7 @@
<div @click="login" class="login-btn">登录</div> <div @click="login" class="login-btn">登录</div>
<div class="loginTs">登录即表示您同意<span>《用户协议》</span><span>《隐私协议》</span></div> <!-- <div class="loginTs">登录即表示您同意<span>《用户协议》</span>和<span>《隐私协议》</span></div> -->
</div> </div>
</div> </div>
</div> </div>
...@@ -66,6 +72,6 @@ ...@@ -66,6 +72,6 @@
<script src="js/axios.min.js"></script> <script src="js/axios.min.js"></script>
<script src="js/vue.min.js"></script> <script src="js/vue.min.js"></script>
<script src="js/util.js"></script> <script src="js/util.js"></script>
<script src="js/login.js?444"></script> <script src="js/login.js?441234"></script>
</body> </body>
</html> </html>
\ No newline at end of file \ No newline at end of file
...@@ -18,25 +18,25 @@ ...@@ -18,25 +18,25 @@
:class="{ active: activeTab === 'all' }" :class="{ active: activeTab === 'all' }"
@click="switchTab('all')" @click="switchTab('all')"
data-node-id="355:516" data-node-id="355:516"
>全部商机{{ totalCount }}</div> >全部商机{{ detail.totalCount }}</div>
<div <div
class="tab-text" class="tab-text"
:class="{ active: activeTab === 'pending' }" :class="{ active: activeTab === '1' }"
@click="switchTab('pending')" @click="switchTab('1')"
data-node-id="355:517" data-node-id="355:517"
>待跟进{{ pendingCount }}</div> >待跟进{{ detail.pendingCount }}</div>
<div <div
class="tab-text" class="tab-text"
:class="{ active: activeTab === 'follow-up' }" :class="{ active: activeTab === '2' }"
@click="switchTab('follow-up')" @click="switchTab('2')"
data-node-id="355:518" data-node-id="355:518"
>跟进中{{ followUpCount }}</div> >跟进中{{ detail.followingCount }}</div>
<div <div
class="tab-text" class="tab-text"
:class="{ active: activeTab === 'completed' }" :class="{ active: activeTab === '3,4,5' }"
@click="switchTab('completed')" @click="switchTab('3,4,5')"
data-node-id="355:519" data-node-id="355:519"
>已完结{{ completedCount }}</div> >已完结{{ detail.finishedCount }}</div>
</div> </div>
</div> </div>
...@@ -57,11 +57,11 @@ ...@@ -57,11 +57,11 @@
</div> </div>
<!-- 筛选标签 --> <!-- 筛选标签 -->
<div class="filter-tags" data-node-id="355:533"> <div class="filter-tags" data-node-id="355:533" v-if="activeTab == '3,4,5'">
<div <div
v-for="tag in filterTags" v-for="tag in filterTags"
:key="tag.id" :key="tag.id"
:class="['filter-tag', { active: tag.selected }]" :class="['filter-tag', { active: tag.id==tagId }]"
:data-node-id="tag.nodeId" :data-node-id="tag.nodeId"
@click="selectFilterTag(tag.id)" @click="selectFilterTag(tag.id)"
> >
...@@ -72,19 +72,18 @@ ...@@ -72,19 +72,18 @@
<!-- 商机列表 --> <!-- 商机列表 -->
<div class="business-list"> <div class="business-list">
<div <div
v-for="(business, index) in filteredBusinessList" v-for="(business, index) in detail.records"
:key="business.id" :key="business.id"
class="business-card" class="business-card"
:data-node-id="business.nodeId"
@click="viewBusinessDetail(business)"> @click="viewBusinessDetail(business)">
<div class="card-content"> <div class="card-content">
<!-- 顶部编号和状态 --> <!-- 顶部编号和状态 -->
<div class="card-header" data-node-id="355:432"> <div class="card-header" data-node-id="355:432">
<div class="business-number" data-node-id="355:433"> <div class="business-number" data-node-id="355:433">
<div class="number-text" data-node-id="355:434">#{{ business.orderNumber }}</div> <div class="number-text" data-node-id="355:434">{{ business.opportunityCode }}</div>
</div> </div>
<div :class="['status-badge', business.statusClass]" data-node-id="355:435"> <div :class="['status-badge', getStatusCalss(business.status)]" data-node-id="355:435">
<span>{{ business.statusText }}</span> <span>{{ business.statusName }}</span>
</div> </div>
</div> </div>
...@@ -94,7 +93,7 @@ ...@@ -94,7 +93,7 @@
<div class="detail-row" data-node-id="355:437"> <div class="detail-row" data-node-id="355:437">
<img class="detail-icon" src="images/location-icon.svg" alt="地址图标"> <img class="detail-icon" src="images/location-icon.svg" alt="地址图标">
<div class="detail-text" data-node-id="355:441"> <div class="detail-text" data-node-id="355:441">
<span>{{ business.address }}</span> <span>{{ addressShow(business.customerAddress) }}</span>
</div> </div>
</div> </div>
...@@ -102,7 +101,7 @@ ...@@ -102,7 +101,7 @@
<div class="detail-row contact-row" data-node-id="355:443"> <div class="detail-row contact-row" data-node-id="355:443">
<img class="detail-icon" src="images/user-icon.svg" alt="地址图标"> <img class="detail-icon" src="images/user-icon.svg" alt="地址图标">
<div class="detail-text" data-node-id="355:446"> <div class="detail-text" data-node-id="355:446">
<span>{{ business.contactPhone }}</span> <span>{{ business.customerPhone }}</span>
</div> </div>
</div> </div>
...@@ -111,33 +110,37 @@ ...@@ -111,33 +110,37 @@
<img class="detail-icon" src="images/tag-icon.svg" alt="地址图标"> <img class="detail-icon" src="images/tag-icon.svg" alt="地址图标">
<div class="business-tags" data-node-id="355:452"> <div class="business-tags" data-node-id="355:452">
<div <div
v-for="tag in business.tags" v-for="tag in business.tagNames"
:key="tag.id"
class="business-tag" class="business-tag"
:data-node-id="tag.nodeId"
> >
{{ tag.name }} {{ tag }}
</div> </div>
</div> </div>
</div> </div>
<!-- 处理人信息 --> <!-- 处理人信息 -->
<div class="processor-info" data-name="Container" data-node-id="355:457"> <div class="processor-info" data-name="Container" data-node-id="355:457">
<span>处理人:{{ business.processor }}</span> <span>处理人:{{ business.marketingStaffName }}</span>
</div> </div>
<!-- 时间信息 --> <!-- 时间信息 -->
<div class="time-info" data-name="Container" data-node-id="355:459"> <div class="time-info" data-name="Container" data-node-id="355:459">
<div class="submit-time" data-name="Container" data-node-id="355:460"> <div class="submit-time" data-name="Container" data-node-id="355:460">
<span>提交:{{ business.submitTime }}</span> <span>提交:{{ switchTime(business.createTime) }}</span>
</div> </div>
<div class="update-time"> <div class="update-time">
<span>更新:{{ business.updateTime }}</span> <span>更新:{{ switchTime(business.latestFollowTime)}}</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div v-if="detail.records.length <= 0" style="text-align: center;font-size: .4rem;padding-top: 2rem;">暂无订单</div>
<div class="loginOut" @click="loginOut">
<img src="images/loginout.png" alt="">
<div>退出</div>
</div>
</div> </div>
<!-- 底部导航 --> <!-- 底部导航 -->
...@@ -154,7 +157,7 @@ ...@@ -154,7 +157,7 @@
class="nav-item all-business active" class="nav-item all-business active"
data-name="全部商机" data-name="全部商机"
data-node-id="355:529"> data-node-id="355:529">
<img class="nav-icon" src="images/business-icon.svg" alt="收集商机"> <img class="nav-icon" src="images/business-icon-active.svg" alt="收集商机">
<span class="nav-text" data-node-id="355:532">全部商机</span> <span class="nav-text" data-node-id="355:532">全部商机</span>
</div> </div>
</div> </div>
...@@ -162,6 +165,7 @@ ...@@ -162,6 +165,7 @@
</div> </div>
<!-- 引入Vue.js --> <!-- 引入Vue.js -->
<script src="js/axios.min.js"></script>
<script src="js/vue.min.js"></script> <script src="js/vue.min.js"></script>
<script src="js/util.js"></script> <script src="js/util.js"></script>
<script src="js/myBusi.js"></script> <script src="js/myBusi.js"></script>
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
</div> </div>
</div> </div>
<div class="botButt" >发现商机</div> <div class="botButt" @click="goAdd">发现商机</div>
<div class="botButt noBusi" @click="goBack">暂无商机</div> <div class="botButt noBusi" @click="goBack">暂无商机</div>
</div> </div>
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!