临时提交
This commit is contained in:
parent
44fff551dc
commit
916efb8df5
12
src/api/dashboard/customer.js
Normal file
12
src/api/dashboard/customer.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 每日新增客户统计
|
||||
export function dailyCreateCountCustomer(params) {
|
||||
return request({
|
||||
url: '/dashboard/customer/dailyCreateCount',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1,10 +1,19 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 获取概览
|
||||
export function getBrief() {
|
||||
// 获取本人概览
|
||||
export function getMineBrief() {
|
||||
return request({
|
||||
url: '/dashboard/brief',
|
||||
url: '/dashboard/mineBrief',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取概览
|
||||
export function getBrief(params) {
|
||||
return request({
|
||||
url: '/dashboard/brief',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -5,16 +5,8 @@
|
|||
backgroundColor: backgroundColor
|
||||
}"
|
||||
>
|
||||
<el-avatar
|
||||
:size="size"
|
||||
:src="src"
|
||||
v-if="src"
|
||||
>
|
||||
</el-avatar>
|
||||
<el-avatar
|
||||
:size="size"
|
||||
v-else
|
||||
>
|
||||
<el-avatar :size="size" :src="src" v-if="src"/>
|
||||
<el-avatar :size="size" v-else :style="{fontSize: fontSize}">
|
||||
{{ displayChar }}
|
||||
</el-avatar>
|
||||
</div>
|
||||
|
@ -40,12 +32,11 @@ export default {
|
|||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
id: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fontSize() {
|
||||
return `${this.size / 1.8}px`
|
||||
},
|
||||
displayChar() {
|
||||
if (!this.name) {
|
||||
return '?';
|
||||
|
@ -66,7 +57,12 @@ export default {
|
|||
'#F8BBD0', // 浅粉色
|
||||
'#D7CCC8' // 浅棕色
|
||||
]
|
||||
const index = parseInt(this.id) % colors.length
|
||||
// 将中文字符串转换为数字
|
||||
const nameStr = this.name || ''
|
||||
const sum = nameStr.split('').reduce((acc, char) => {
|
||||
return acc + char.charCodeAt(0)
|
||||
}, 0)
|
||||
const index = Math.abs(sum) % colors.length
|
||||
return colors[index]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<el-tag v-if="value != null" :type="value ? trueType : falseType" :size="size">{{value ? trueText : falseText}}</el-tag>
|
||||
<el-tag style="display: inline-block;" v-if="value != null" :type="value ? trueType : falseType" :size="size">{{value ? trueText : falseText}}</el-tag>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
|
|
76
src/components/LineField/index.vue
Normal file
76
src/components/LineField/index.vue
Normal file
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<div class="line-field">
|
||||
<div class="label">
|
||||
<slot name="label">
|
||||
{{label | dv}}
|
||||
</slot>
|
||||
</div>
|
||||
<div class="right-box" >
|
||||
<slot>
|
||||
{{value | dv}}
|
||||
</slot>
|
||||
</div>
|
||||
<el-button type="text" v-if="editable" icon="el-icon-edit" class="editable" @click="$emit('edit')"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "LineField",
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: null,
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.line-field {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 0.5em 1.5em 0.5em 1em;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
height: fit-content;
|
||||
line-height: 1.5em;
|
||||
justify-content: space-between;
|
||||
.label {
|
||||
width: fit-content;
|
||||
margin-right: 2em;
|
||||
}
|
||||
.right-box {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
vertical-align: center;
|
||||
}
|
||||
i {
|
||||
line-height: 1.5em;
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
.editable {
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
top: 10px;
|
||||
right: 4px;
|
||||
}
|
||||
}
|
||||
.line-field:nth-child(n + 2) {
|
||||
border-top: 1px solid #ededed;
|
||||
}
|
||||
.line-field:hover {
|
||||
background-color: rgba(131, 180, 240, 0.08);
|
||||
}
|
||||
</style>
|
113
src/components/StatisticsCard/index.vue
Normal file
113
src/components/StatisticsCard/index.vue
Normal file
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<div class="statistics-card">
|
||||
<div class="card-content">
|
||||
<div class="info">
|
||||
<div class="value">{{ value }}</div>
|
||||
<div class="label">{{ label }}</div>
|
||||
</div>
|
||||
<div class="icon-wrapper">
|
||||
<i :class="icon" :style="iconStyle"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'StatisticsCard',
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: null,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
startColor: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
endColor: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconStyle() {
|
||||
return {
|
||||
background: `linear-gradient(135deg, ${this.startColor}, ${this.endColor})`,
|
||||
'-webkit-background-clip': 'text',
|
||||
'-webkit-text-fill-color': 'transparent',
|
||||
'background-clip': 'text'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.statistics-card {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||||
padding: 12px;
|
||||
height: 70px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px 0 rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
|
||||
.info {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
.value {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
bottom: -12px;
|
||||
z-index: 1;
|
||||
|
||||
i {
|
||||
font-size: 48px;
|
||||
opacity: 0.15;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.icon-wrapper i {
|
||||
opacity: 0.25;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -86,7 +86,13 @@ export const constantRoutes = [
|
|||
component: () => import('@/views/bst/project/edit/index.vue'),
|
||||
name: 'ProjectEdit',
|
||||
meta: { title: '编辑项目', noCache: false }
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'customer/:id?',
|
||||
component: () => import('@/views/bst/customer/edit/index.vue'),
|
||||
name: 'CustomerEdit',
|
||||
meta: { title: '编辑客户', noCache: false }
|
||||
},
|
||||
]
|
||||
},
|
||||
/**
|
||||
|
@ -108,6 +114,12 @@ export const constantRoutes = [
|
|||
component: () => import('@/views/bst/customer/view/index.vue'),
|
||||
name: 'CustomerView',
|
||||
meta: { title: '客户详情', noCache: false }
|
||||
},
|
||||
{
|
||||
path: 'user/:id?',
|
||||
component: () => import('@/views/system/user/view/index.vue'),
|
||||
name: 'UserView',
|
||||
meta: { title: '用户详情', noCache: false }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -22,7 +22,7 @@ module.exports = {
|
|||
/**
|
||||
* 是否固定头部
|
||||
*/
|
||||
fixedHeader: false,
|
||||
fixedHeader: true,
|
||||
|
||||
/**
|
||||
* 是否显示logo
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { parseTime } from '@/utils/ruoyi';
|
||||
|
||||
/**
|
||||
* 计算周年
|
||||
*/
|
||||
|
@ -46,3 +48,62 @@ export function calcSecond(start, end) {
|
|||
let endDate = toDate(end);
|
||||
return Math.floor((endDate.getTime() - startDate.getTime()) / 1000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 获取前n天的日期
|
||||
export function getLastDate(n) {
|
||||
let now = new Date();
|
||||
return new Date(now.getTime() - n * 24 * 3600 * 1000)
|
||||
}
|
||||
|
||||
// 获取前n月的日期
|
||||
export function getLastMonth(n) {
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth();
|
||||
const day = date.getDate();
|
||||
|
||||
// 减去 n 个月
|
||||
date.setMonth(month - n);
|
||||
|
||||
// 确保日期不超过当月的最大天数
|
||||
const maxDay = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
|
||||
date.setDate(Math.min(day, maxDay));
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
// 获取前n月的日期字符串
|
||||
export function getLastMonthDateStr(n) {
|
||||
let date = getLastMonth(n);
|
||||
return parseTime(date, "{y}-{m}-{d}")
|
||||
}
|
||||
|
||||
// 获取前n天的日期字符串
|
||||
export function getLastDateStr(n) {
|
||||
let date = getLastDate(n);
|
||||
return parseTime(date, "{y}-{m}-{d}");
|
||||
}
|
||||
|
||||
// 获取前n天的日期时间字符串00:00:00
|
||||
export function getLastDateTimeStartStr(n) {
|
||||
let date = getLastDate(n);
|
||||
return parseTime(date, "{y}-{m}-{d} 00:00:00");
|
||||
}
|
||||
|
||||
// 获取前n天的日期时间字符串23:59:59
|
||||
export function getLastDateTimeEndStr(n) {
|
||||
let date = getLastDate(n);
|
||||
return parseTime(date, "{y}-{m}-{d} 23:59:59");
|
||||
}
|
||||
|
||||
// 获取前n天的日期时间00:00:00
|
||||
export function getLastDateTimeStart(n) {
|
||||
return new Date(getLastDateTimeStartStr(n));
|
||||
}
|
||||
|
||||
// 获取前n天的日期时间23:59:59
|
||||
export function getLastDateTimeEnd(n) {
|
||||
return new Date(getLastDateTimeEndStr(n));
|
||||
}
|
||||
|
|
|
@ -1,213 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="title"
|
||||
:visible.sync="dialogVisible"
|
||||
width="800px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
@open="handleOpen"
|
||||
>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px" v-loading="loading">
|
||||
<el-row>
|
||||
<form-col :span="24" label="客户名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入客户名称" />
|
||||
</form-col>
|
||||
<form-col :span="span" label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="请选择状态" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="dict in dict.type.customer_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</form-col>
|
||||
<form-col :span="span" label="意向强度" prop="intentLevel">
|
||||
<el-select v-model="form.intentLevel" placeholder="请选择意向强度" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="dict in dict.type.customer_intent_level"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</form-col>
|
||||
<form-col :span="span" label="手机号" prop="mobile">
|
||||
<el-input v-model="form.mobile" placeholder="请输入手机号" maxlength="11" show-word-limit />
|
||||
</form-col>
|
||||
<form-col :span="span" label="微信号" prop="wechat">
|
||||
<el-input v-model="form.wechat" placeholder="请输入微信号" maxlength="50" show-word-limit />
|
||||
</form-col>
|
||||
<form-col :span="span" label="来源" prop="source">
|
||||
<el-select v-model="form.source" placeholder="请选择来源,可自定义" style="width: 100%;" allow-create filterable default-first-option>
|
||||
<el-option
|
||||
v-for="dict in dict.type.customer_source"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</form-col>
|
||||
<form-col :span="span" label="意向" prop="intents">
|
||||
<el-select v-model="form.intents" placeholder="请选择意向,可自定义" style="width: 100%;" allow-create filterable default-first-option multiple>
|
||||
<el-option
|
||||
v-for="dict in dict.type.customer_intent"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</form-col>
|
||||
<form-col :span="span" label="跟进人" prop="followId">
|
||||
<user-select v-model="form.followId" />
|
||||
</form-col>
|
||||
<form-col :span="24" label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</form-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
|
||||
<el-button @click="handleClose">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCustomer, addCustomer, updateCustomer } from "@/api/bst/customer";
|
||||
import FormCol from "@/components/FormCol/index.vue";
|
||||
import { CustomerStatus, CustomerIntentLevel } from '@/utils/enums';
|
||||
import { mapGetters } from 'vuex';
|
||||
import UserSelect from '@/components/Business/User/UserSelect.vue';
|
||||
|
||||
export default {
|
||||
name: "CustomerEditDialog",
|
||||
components: { FormCol, UserSelect },
|
||||
dicts: ['customer_intent_level', 'customer_status', 'customer_source', 'customer_intent'],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
id: {
|
||||
type: [String, Number],
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
submitLoading: false,
|
||||
span: 12,
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
code: [
|
||||
{ required: true, message: "客户编号不能为空", trigger: "change" }
|
||||
],
|
||||
name: [
|
||||
{ required: true, message: "客户姓名不能为空", trigger: "change" }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: "状态不能为空", trigger: "change" }
|
||||
],
|
||||
intentLevel: [
|
||||
{ required: true, message: "意向强度不能为空", trigger: "change" }
|
||||
],
|
||||
source: [
|
||||
{ required: true, message: "来源不能为空", trigger: "change" }
|
||||
],
|
||||
followId: [
|
||||
{ required: true, message: "跟进人不能为空", trigger: "change" }
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userId']),
|
||||
title() {
|
||||
return this.id ? '修改客户' : '新增客户';
|
||||
},
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.show;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:show', value);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 获取详细信息 */
|
||||
getInfo(id) {
|
||||
this.loading = true;
|
||||
getCustomer(id).then(response => {
|
||||
this.form = response.data;
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
id: null,
|
||||
code: null,
|
||||
name: null,
|
||||
status: CustomerStatus.POTENTIAL,
|
||||
intentLevel: CustomerIntentLevel.MEDIUM,
|
||||
mobile: null,
|
||||
wechat: null,
|
||||
source: null,
|
||||
intents: [],
|
||||
followId: this.userId,
|
||||
remark: null
|
||||
};
|
||||
this.resetForm("form");
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.id != null) {
|
||||
this.submitLoading = true;
|
||||
updateCustomer(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
this.$emit('success');
|
||||
this.handleClose();
|
||||
}).finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
} else {
|
||||
this.submitLoading = true;
|
||||
addCustomer(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.$emit('success');
|
||||
this.handleClose();
|
||||
}).finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
// 取消按钮
|
||||
handleClose() {
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
// 打开事件
|
||||
handleOpen() {
|
||||
if (this.id) {
|
||||
this.getInfo(this.id);
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
221
src/views/bst/customer/edit/index.vue
Normal file
221
src/views/bst/customer/edit/index.vue
Normal file
|
@ -0,0 +1,221 @@
|
|||
<template>
|
||||
<div>
|
||||
<edit-header :title="title">
|
||||
<el-button @click="handleClose" plain icon="el-icon-close">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm" :loading="submitLoading" plain icon="el-icon-check">确 定</el-button>
|
||||
</edit-header>
|
||||
<div class="app-container">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px" v-loading="loading">
|
||||
<el-row>
|
||||
<form-col :span="24" label="客户名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入客户名称" />
|
||||
</form-col>
|
||||
<form-col :span="span" label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="请选择状态" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="dict in dict.type.customer_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</form-col>
|
||||
<form-col :span="span" label="意向强度" prop="intentLevel">
|
||||
<el-select v-model="form.intentLevel" placeholder="请选择意向强度" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="dict in dict.type.customer_intent_level"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</form-col>
|
||||
<form-col :span="span" label="手机号" prop="mobile">
|
||||
<el-input v-model="form.mobile" placeholder="请输入手机号" maxlength="11" show-word-limit />
|
||||
</form-col>
|
||||
<form-col :span="span" label="微信号" prop="wechat">
|
||||
<el-input v-model="form.wechat" placeholder="请输入微信号" maxlength="50" show-word-limit />
|
||||
</form-col>
|
||||
<form-col :span="span" label="来源" prop="source">
|
||||
<el-select v-model="form.source" placeholder="请选择来源,可自定义" style="width: 100%;" allow-create filterable default-first-option>
|
||||
<el-option
|
||||
v-for="dict in dict.type.customer_source"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</form-col>
|
||||
<form-col :span="span" label="意向" prop="intents">
|
||||
<el-select v-model="form.intents" placeholder="请选择意向,可自定义" style="width: 100%;" allow-create filterable default-first-option multiple>
|
||||
<el-option
|
||||
v-for="dict in dict.type.customer_intent"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</form-col>
|
||||
<form-col :span="span" label="跟进人" prop="followId">
|
||||
<user-select v-model="form.followId" />
|
||||
</form-col>
|
||||
<form-col :span="24" label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</form-col>
|
||||
</el-row>
|
||||
|
||||
<el-tabs v-if="form.id == null">
|
||||
<el-tab-pane label="跟进记录">
|
||||
<customer-follow-form :form="form.follow" :hide="['customerId']" :prop-prefix="'follow.'" :span="span"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCustomer, addCustomer, updateCustomer } from "@/api/bst/customer";
|
||||
import FormCol from "@/components/FormCol/index.vue";
|
||||
import { CustomerStatus, CustomerIntentLevel } from '@/utils/enums';
|
||||
import { mapGetters } from 'vuex';
|
||||
import UserSelect from '@/components/Business/User/UserSelect.vue';
|
||||
import CustomerFollowForm from '@/views/bst/customerFollow/components/CustomerFollowForm.vue';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import EditHeader from '@/components/EditHeader/index.vue';
|
||||
export default {
|
||||
name: "CustomerEdit",
|
||||
components: { FormCol, UserSelect, CustomerFollowForm, EditHeader },
|
||||
dicts: ['customer_intent_level', 'customer_status', 'customer_source', 'customer_intent'],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
submitLoading: false,
|
||||
span: 6,
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
code: [
|
||||
{ required: true, message: "客户编号不能为空", trigger: "change" }
|
||||
],
|
||||
name: [
|
||||
{ required: true, message: "客户姓名不能为空", trigger: "change" }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: "状态不能为空", trigger: "change" }
|
||||
],
|
||||
intentLevel: [
|
||||
{ required: true, message: "意向强度不能为空", trigger: "change" }
|
||||
],
|
||||
source: [
|
||||
{ required: true, message: "来源不能为空", trigger: "change" }
|
||||
],
|
||||
followId: [
|
||||
{ required: true, message: "跟进人不能为空", trigger: "change" }
|
||||
],
|
||||
follow: {
|
||||
type: [
|
||||
{ required: true, message: "跟进方式不能为空", trigger: "change" }
|
||||
],
|
||||
content: [
|
||||
{ required: true, message: "跟进内容不能为空", trigger: "change" }
|
||||
],
|
||||
followTime: [
|
||||
{ required: true, message: "跟进时间不能为空", trigger: "change" }
|
||||
]
|
||||
}
|
||||
},
|
||||
id: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userId']),
|
||||
title() {
|
||||
return this.id ? '修改客户' : '新增客户';
|
||||
},
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.show;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('update:show', value);
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.id = this.$route.params.id;
|
||||
if (this.id) {
|
||||
this.getInfo(this.id);
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 获取详细信息 */
|
||||
getInfo(id) {
|
||||
this.loading = true;
|
||||
getCustomer(id).then(response => {
|
||||
this.form = response.data;
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
id: null,
|
||||
code: null,
|
||||
name: null,
|
||||
status: CustomerStatus.POTENTIAL,
|
||||
intentLevel: CustomerIntentLevel.MEDIUM,
|
||||
mobile: null,
|
||||
wechat: null,
|
||||
source: null,
|
||||
intents: [],
|
||||
followId: this.userId,
|
||||
remark: null,
|
||||
follow: {
|
||||
followTime: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'),
|
||||
}
|
||||
};
|
||||
this.resetForm("form");
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.id != null) {
|
||||
this.submitLoading = true;
|
||||
updateCustomer(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
this.$emit('success');
|
||||
this.handleClose();
|
||||
}).finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
} else {
|
||||
this.submitLoading = true;
|
||||
addCustomer(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.$emit('success');
|
||||
this.handleClose();
|
||||
}).finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
// 取消按钮
|
||||
handleClose() {
|
||||
this.$tab.closeBack();
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
|
@ -154,7 +154,7 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
|
@ -196,13 +196,6 @@
|
|||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改客户对话框 -->
|
||||
<customer-edit-dialog
|
||||
:show.sync="open"
|
||||
:id="row.id"
|
||||
@success="getList"
|
||||
/>
|
||||
|
||||
<!-- 跟进对话框 -->
|
||||
<customer-follow-edit-dialog
|
||||
:show.sync="showFollowDialog"
|
||||
|
@ -216,7 +209,6 @@
|
|||
import { listCustomer, delCustomer} from "@/api/bst/customer";
|
||||
import { $showColumns } from '@/utils/mixins';
|
||||
import FormCol from "@/components/FormCol/index.vue";
|
||||
import CustomerEditDialog from './components/CustomerEditDialog.vue';
|
||||
import CustomerLink from '@/components/Business/Customer/CustomerLink.vue';
|
||||
import CustomerFollowEditDialog from '@/views/bst/customerFollow/components/CustomerFollowEditDialog.vue';
|
||||
// 默认排序字段
|
||||
|
@ -229,7 +221,7 @@ export default {
|
|||
name: "Customer",
|
||||
mixins: [$showColumns],
|
||||
dicts: ['customer_intent_level', 'customer_status'],
|
||||
components: {FormCol, CustomerEditDialog, CustomerLink, CustomerFollowEditDialog},
|
||||
components: {FormCol, CustomerLink, CustomerFollowEditDialog},
|
||||
data() {
|
||||
return {
|
||||
showFollowDialog: false,
|
||||
|
@ -351,13 +343,11 @@ export default {
|
|||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.row = {};
|
||||
this.open = true;
|
||||
this.$router.push(`/edit/customer`);
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.row = row;
|
||||
this.open = true;
|
||||
this.$router.push(`/edit/customer/${row.id}`);
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
|
|
|
@ -9,49 +9,7 @@
|
|||
@open="handleOpen"
|
||||
>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px" v-loading="loading">
|
||||
<el-row>
|
||||
<form-col :span="24" label="图片" prop="picture">
|
||||
<image-upload v-model="form.picture" />
|
||||
</form-col>
|
||||
<form-col :span="span" label="客户" prop="customerId">
|
||||
<customer-input v-model="form.customerId" :text.sync="form.customerName" :disabled="initData.customerId != null"/>
|
||||
</form-col>
|
||||
<form-col :span="span" label="跟进方式" prop="type">
|
||||
<el-select v-model="form.type" placeholder="请选择跟进方式" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="dict in dict.type.customer_follow_type"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</form-col>
|
||||
<form-col :span="24" label="跟进内容" prop="content">
|
||||
<el-input v-model="form.content" type="textarea" placeholder="请输入跟进内容" maxlength="1000" show-word-limit :autosize="{ minRows: 4 }"/>
|
||||
</form-col>
|
||||
<form-col :span="span" label="跟进时间" prop="followTime">
|
||||
<el-date-picker clearable
|
||||
style="width: 100%;"
|
||||
v-model="form.followTime"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
default-time="09:00:00"
|
||||
:picker-options="DatePickerOptions.DISABLE_FUTURE"
|
||||
placeholder="请选择跟进时间">
|
||||
</el-date-picker>
|
||||
</form-col>
|
||||
<form-col :span="span" label="下次跟进" prop="nextFollowTime">
|
||||
<el-date-picker clearable
|
||||
style="width: 100%;"
|
||||
v-model="form.nextFollowTime"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
default-time="09:00:00"
|
||||
:picker-options="DatePickerOptions.DISABLE_PAST"
|
||||
placeholder="请选择下次跟进时间">
|
||||
</el-date-picker>
|
||||
</form-col>
|
||||
</el-row>
|
||||
<customer-follow-form :form="form" :disabled="disabledKeys" :span="span"/>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
|
||||
|
@ -62,17 +20,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { getCustomerFollow, addCustomerFollow, updateCustomerFollow } from "@/api/bst/customerFollow";
|
||||
import FormCol from "@/components/FormCol/index.vue";
|
||||
import UserSelect from '@/components/Business/User/UserSelect.vue';
|
||||
import CustomerInput from '@/components/Business/Customer/CustomerInput.vue';
|
||||
import { getCustomerFollow, addCustomerFollow, updateCustomerFollow } from "@/api/bst/customerFollow"
|
||||
import { mapGetters } from 'vuex';
|
||||
import { parseTime } from '@/utils/ruoyi.js';
|
||||
import { DatePickerOptions } from '@/utils/constants.js';
|
||||
import CustomerFollowForm from '@/views/bst/customerFollow/components/CustomerFollowForm.vue';
|
||||
export default {
|
||||
name: "CustomerFollowEditDialog",
|
||||
components: { FormCol, UserSelect, CustomerInput },
|
||||
dicts: ['customer_follow_type'],
|
||||
components: { CustomerFollowForm },
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
|
@ -85,11 +39,10 @@ export default {
|
|||
initData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
DatePickerOptions,
|
||||
loading: false,
|
||||
submitLoading: false,
|
||||
span: 12,
|
||||
|
@ -105,8 +58,12 @@ export default {
|
|||
],
|
||||
content: [
|
||||
{ required: true, message: "跟进内容不能为空", trigger: "change" }
|
||||
],
|
||||
followTime: [
|
||||
{ required: true, message: "跟进时间不能为空", trigger: "change" }
|
||||
]
|
||||
}
|
||||
},
|
||||
disabledKeys: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -148,6 +105,12 @@ export default {
|
|||
customerName: null,
|
||||
...this.initData
|
||||
};
|
||||
this.disabledKeys = [];
|
||||
Object.keys(this.initData).forEach(key => {
|
||||
if (this.initData[key] != null) {
|
||||
this.disabledKeys.push(key);
|
||||
}
|
||||
});
|
||||
this.resetForm("form");
|
||||
},
|
||||
/** 提交按钮 */
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<el-row>
|
||||
<form-col :span="24" label="图片" :prop="propPrefix + 'picture'">
|
||||
<image-upload v-model="form.picture" />
|
||||
</form-col>
|
||||
<form-col :span="span" label="客户" prop="customerId" v-if="!isHide('customerId')">
|
||||
<customer-input v-model="form.customerId" :text.sync="form.customerName" :disabled="isDisabled('customerId')"/>
|
||||
</form-col>
|
||||
<form-col :span="span" label="跟进方式" :prop="propPrefix + 'type'">
|
||||
<el-select v-model="form.type" placeholder="请选择跟进方式" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="dict in dict.type.customer_follow_type"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</form-col>
|
||||
<form-col :span="24" label="跟进内容" :prop="propPrefix + 'content'">
|
||||
<el-input v-model="form.content" type="textarea" placeholder="请输入跟进内容" maxlength="1000" show-word-limit :autosize="{ minRows: 4 }"/>
|
||||
</form-col>
|
||||
<form-col :span="span" label="跟进时间" :prop="propPrefix + 'followTime'">
|
||||
<el-date-picker clearable
|
||||
style="width: 100%;"
|
||||
v-model="form.followTime"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
default-time="09:00:00"
|
||||
:picker-options="DatePickerOptions.DISABLE_FUTURE"
|
||||
placeholder="请选择跟进时间">
|
||||
</el-date-picker>
|
||||
</form-col>
|
||||
<form-col :span="span" label="下次跟进" :prop="propPrefix + 'nextFollowTime'">
|
||||
<el-date-picker clearable
|
||||
style="width: 100%;"
|
||||
v-model="form.nextFollowTime"
|
||||
type="datetime"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
default-time="09:00:00"
|
||||
:picker-options="DatePickerOptions.DISABLE_PAST"
|
||||
placeholder="请选择下次跟进时间">
|
||||
</el-date-picker>
|
||||
</form-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DatePickerOptions } from '@/utils/constants.js';
|
||||
import FormCol from '@/components/FormCol/index.vue';
|
||||
import CustomerInput from '@/components/Business/Customer/CustomerInput.vue';
|
||||
export default {
|
||||
name: 'CustomerFollowForm',
|
||||
components: { FormCol, CustomerInput },
|
||||
dicts: ['customer_follow_type'],
|
||||
props: {
|
||||
form: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
hide: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
disabled: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
span: {
|
||||
type: Number,
|
||||
default: 12
|
||||
},
|
||||
propPrefix: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
DatePickerOptions,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isHide() {
|
||||
return (key) => {
|
||||
return this.hide.includes(key);
|
||||
}
|
||||
},
|
||||
isDisabled() {
|
||||
return (key) => {
|
||||
return this.disabled.includes(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
|
@ -4,7 +4,7 @@
|
|||
<el-form-item label="项目" prop="projectId" v-if="isShow('projectName')">
|
||||
<project-select v-model="queryParams.projectId" @change="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type" v-if="isShow('type')">
|
||||
<el-form-item label="类型" prop="type" v-if="isShow('description')">
|
||||
<el-select v-model="queryParams.type" placeholder="请选择类型" clearable @change="handleQuery">
|
||||
<el-option
|
||||
v-for="dict in dict.type.task_type"
|
||||
|
@ -14,7 +14,7 @@
|
|||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status" v-if="isShow('status')">
|
||||
<el-form-item label="状态" prop="status" v-if="isShow('description')">
|
||||
<el-radio-group v-model="queryParams.status" placeholder="请选择状态" clearable @change="handleQuery">
|
||||
<el-radio-button :label="null">全部</el-radio-button>
|
||||
<el-radio-button
|
||||
|
@ -24,7 +24,7 @@
|
|||
>{{ dict.label }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="优先级" prop="level" v-if="isShow('level')">
|
||||
<el-form-item label="优先级" prop="level" v-if="isShow('description')">
|
||||
<el-select v-model="queryParams.level" placeholder="请选择优先级" clearable @change="handleQuery">
|
||||
<el-option
|
||||
v-for="dict in dict.type.task_level"
|
||||
|
@ -34,6 +34,13 @@
|
|||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否逾期" prop="overdue" v-if="isShow('description')">
|
||||
<el-radio-group v-model="queryParams.overdue" placeholder="请选择是否逾期" clearable @change="handleQuery">
|
||||
<el-radio-button :label="null">全部</el-radio-button>
|
||||
<el-radio-button :label="true">逾期</el-radio-button>
|
||||
<el-radio-button :label="false">正常</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建人" prop="createId" v-if="isShow('createName')">
|
||||
<user-select v-model="queryParams.createId" @change="handleQuery"/>
|
||||
</el-form-item>
|
||||
|
@ -101,15 +108,10 @@
|
|||
</template>
|
||||
<template v-else-if="column.key === 'description'">
|
||||
<el-link :underline="false" @click="handleView(d.row)">{{d.row.description}}</el-link>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'type'">
|
||||
<dict-tag :options="dict.type.task_type" :value="d.row.type"/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<dict-tag :options="dict.type.task_status" :value="d.row.status"/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'level'">
|
||||
<dict-tag :options="dict.type.task_level" :value="d.row.level" />
|
||||
<dict-tag style="margin-left: 4px" :options="dict.type.task_status" :value="d.row.status" size="mini"/>
|
||||
<dict-tag style="margin-left: 4px" :options="dict.type.task_type" :value="d.row.type" size="mini"/>
|
||||
<dict-tag style="margin-left: 4px" :options="dict.type.task_level" :value="d.row.level" size="mini"/>
|
||||
<boolean-tag style="margin-left: 4px" v-if="d.row.overdue " :value="!d.row.overdue" true-text="正常" false-text="逾期" size="mini"/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'picture'">
|
||||
<image-preview :src="d.row[column.key]" :width="50" :height="50"/>
|
||||
|
@ -195,7 +197,7 @@ import ProjectLink from '@/components/Business/Project/ProjectLink.vue';
|
|||
import AvatarList from '@/components/AvatarList/index.vue';
|
||||
import UserSelect from '@/components/Business/User/UserSelect.vue';
|
||||
import {TaskStatus} from '@/utils/enums'
|
||||
|
||||
import BooleanTag from '@/components/BooleanTag'
|
||||
// 默认排序字段
|
||||
const defaultSort = {
|
||||
prop: "createTime",
|
||||
|
@ -206,7 +208,7 @@ export default {
|
|||
name: "Task",
|
||||
mixins: [$showColumns, $task],
|
||||
dicts: ['task_status', 'task_level', 'task_type'],
|
||||
components: {FormCol, TaskEditDialog, ProjectSelect, TaskViewDialog, ProjectLink, AvatarList, UserSelect},
|
||||
components: {FormCol, TaskEditDialog, ProjectSelect, TaskViewDialog, ProjectLink, AvatarList, UserSelect, BooleanTag},
|
||||
props: {
|
||||
initData: {
|
||||
type: Object,
|
||||
|
@ -269,7 +271,8 @@ export default {
|
|||
level: null,
|
||||
createId: null,
|
||||
ownerId: null,
|
||||
deleted: null
|
||||
deleted: null,
|
||||
overdue: null,
|
||||
},
|
||||
// 编辑的行
|
||||
row: {},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="dashboard-panel" v-loading="loading">
|
||||
<!-- 项目统计 -->
|
||||
<el-card class="card-box" header="项目统计">
|
||||
<el-card class="card-box" header="参与项目">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="8">
|
||||
<div class="stat-card project" :class="{'hover': hoveredCard === 'project-total'}">
|
||||
|
@ -86,7 +86,7 @@
|
|||
|
||||
|
||||
<!-- 任务统计 -->
|
||||
<el-card class="card-box" header="任务统计">
|
||||
<el-card class="card-box" header="负责任务">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="8">
|
||||
<div class="stat-card task" :class="{'hover': hoveredCard === 'task-total'}">
|
||||
|
@ -170,7 +170,7 @@
|
|||
</el-card>
|
||||
|
||||
<!-- 客户统计 -->
|
||||
<el-card class="card-box" header="客户统计">
|
||||
<el-card class="card-box" header="我的客户">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="8">
|
||||
<div class="stat-card customer" :class="{'hover': hoveredCard === 'customer-total'}">
|
||||
|
@ -257,7 +257,7 @@
|
|||
|
||||
<script>
|
||||
import CountTo from 'vue-count-to'
|
||||
import {getBrief} from "@/api/dashboard/dashboard";
|
||||
import {getMineBrief} from "@/api/dashboard/dashboard";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
@ -280,7 +280,7 @@ export default {
|
|||
methods: {
|
||||
getData() {
|
||||
this.loading = true;
|
||||
getBrief().then(res => {
|
||||
getMineBrief().then(res => {
|
||||
this.data = res.data;
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
|
|
|
@ -172,6 +172,13 @@
|
|||
class-name="small-padding fixed-width"
|
||||
>
|
||||
<template slot-scope="scope" v-if="scope.row.userId !== '1'">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-view"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['system:user:query']"
|
||||
>详情</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
|
@ -430,6 +437,9 @@ export default {
|
|||
});
|
||||
},
|
||||
methods: {
|
||||
handleView(row) {
|
||||
this.$router.push(`/view/user/${row.userId}`);
|
||||
},
|
||||
isEmpty,
|
||||
calcBirthDay,
|
||||
calcFullYear,
|
||||
|
|
142
src/views/system/user/view/components/PerformanceChart.vue
Normal file
142
src/views/system/user/view/components/PerformanceChart.vue
Normal file
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<div v-loading="loading">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createDateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="yyyy-MM-dd"
|
||||
size="mini"
|
||||
@change="getList"
|
||||
/>
|
||||
<div ref="chart" class="chart-container"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import { dailyCreateCountCustomer } from '@/api/dashboard/customer'
|
||||
import { getLastDateStr } from '@/utils/date'
|
||||
import resize from '@/views/dashboard/mixins/resize'
|
||||
export default {
|
||||
name: 'PerformanceChart',
|
||||
mixins: [resize],
|
||||
props: {
|
||||
query: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
loading: false,
|
||||
chart: null,
|
||||
queryParams: {
|
||||
createDateRange: [
|
||||
getLastDateStr(7),
|
||||
getLastDateStr(0)
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.queryParams = {
|
||||
...this.queryParams,
|
||||
...this.query
|
||||
}
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true
|
||||
dailyCreateCountCustomer(this.queryParams).then(res => {
|
||||
this.list = res.data;
|
||||
this.initChart()
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
initChart() {
|
||||
if (this.chart) {
|
||||
this.chart.dispose()
|
||||
}
|
||||
|
||||
this.chart = echarts.init(this.$refs.chart)
|
||||
|
||||
const xAxisData = this.list.map(item => item.key)
|
||||
const seriesData = this.list.map(item => item.value)
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '2%',
|
||||
right: '2%',
|
||||
bottom: '2%',
|
||||
top: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xAxisData,
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
show: false
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
minInterval: 1,
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#eee'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '新增客户数',
|
||||
type: 'line',
|
||||
data: seriesData,
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
itemStyle: {
|
||||
color: '#409EFF'
|
||||
},
|
||||
lineStyle: {
|
||||
width: 2
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
this.chart.setOption(option)
|
||||
|
||||
// 监听窗口大小变化,调整图表大小
|
||||
window.addEventListener('resize', this.chart.resize)
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.chart) {
|
||||
window.removeEventListener('resize', this.chart.resize)
|
||||
this.chart.dispose()
|
||||
this.chart = null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chart-container {
|
||||
margin-top: 16px;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
143
src/views/system/user/view/components/UserStatistics.vue
Normal file
143
src/views/system/user/view/components/UserStatistics.vue
Normal file
|
@ -0,0 +1,143 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="statistics-title">
|
||||
<div style="flex:5">任务统计</div>
|
||||
<div style="flex:4">项目统计</div>
|
||||
</div>
|
||||
<div class="statistics-wrapper" v-loading="loading">
|
||||
<!-- 任务统计 -->
|
||||
<statistics-card
|
||||
style="flex: 1"
|
||||
:value="task.total"
|
||||
label="任务总数"
|
||||
icon="el-icon-s-order"
|
||||
start-color="#409EFF"
|
||||
end-color="#53a8ff"
|
||||
/>
|
||||
<statistics-card
|
||||
style="flex: 1"
|
||||
:value="task.completed"
|
||||
label="已完成任务"
|
||||
icon="el-icon-check"
|
||||
start-color="#67C23A"
|
||||
end-color="#85ce61"
|
||||
/>
|
||||
<statistics-card
|
||||
style="flex: 1"
|
||||
:value="task.overdueUncompleted"
|
||||
label="逾期未完成"
|
||||
icon="el-icon-warning"
|
||||
start-color="#E6A23C"
|
||||
end-color="#ebb563"
|
||||
/>
|
||||
<statistics-card
|
||||
style="flex: 1"
|
||||
:value="task.overdueCompleted"
|
||||
label="逾期完成"
|
||||
icon="el-icon-time"
|
||||
start-color="#F56C6C"
|
||||
end-color="#f78989"
|
||||
/>
|
||||
<statistics-card
|
||||
style="flex: 1"
|
||||
:value="task.bug"
|
||||
label="BUG数"
|
||||
icon="el-icon-error"
|
||||
start-color="#F56C6C"
|
||||
end-color="#f78989"
|
||||
/>
|
||||
<!-- 项目统计 -->
|
||||
<statistics-card
|
||||
style="flex: 1"
|
||||
:value="project.total"
|
||||
label="项目总数"
|
||||
icon="el-icon-folder"
|
||||
start-color="#409EFF"
|
||||
end-color="#53a8ff"
|
||||
/>
|
||||
<statistics-card
|
||||
style="flex: 1"
|
||||
:value="project.devCompleted"
|
||||
label="正常开发完成"
|
||||
icon="el-icon-circle-check"
|
||||
start-color="#67C23A"
|
||||
end-color="#85ce61"
|
||||
/>
|
||||
<statistics-card
|
||||
style="flex: 1"
|
||||
:value="project.devOverdueUncompleted"
|
||||
label="逾期未完成"
|
||||
icon="el-icon-warning"
|
||||
start-color="#E6A23C"
|
||||
end-color="#ebb563"
|
||||
/>
|
||||
<statistics-card
|
||||
style="flex: 1"
|
||||
:value="project.devOverdueCompleted"
|
||||
label="逾期完成"
|
||||
icon="el-icon-warning-outline"
|
||||
start-color="#F56C6C"
|
||||
end-color="#f78989"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StatisticsCard from '@/components/StatisticsCard'
|
||||
import { getBrief } from '@/api/dashboard/dashboard'
|
||||
|
||||
export default {
|
||||
name: 'UserStatistics',
|
||||
components: {
|
||||
StatisticsCard
|
||||
},
|
||||
props: {
|
||||
userId: {
|
||||
type: String,
|
||||
default: null,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
task: {},
|
||||
project: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getBrief()
|
||||
},
|
||||
methods: {
|
||||
getBrief() {
|
||||
this.loading = true
|
||||
getBrief({ joinUserId: this.userId }).then(res => {
|
||||
if (res.code === 200 && res.data) {
|
||||
this.task = res.data.task
|
||||
this.project = res.data.project
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.statistics-wrapper,
|
||||
.statistics-title {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
.statistics-title {
|
||||
margin-bottom: 8px;
|
||||
div {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
</style>
|
175
src/views/system/user/view/index.vue
Normal file
175
src/views/system/user/view/index.vue
Normal file
|
@ -0,0 +1,175 @@
|
|||
<template>
|
||||
<div class="app-container" v-loading="loading">
|
||||
<el-row :gutter="12">
|
||||
<!-- 左侧个人信息 -->
|
||||
<el-col :xs="24" :sm="24" :md="8" :lg="6" class="mb-12">
|
||||
<el-card class="user-info-card card-box" shadow="hover">
|
||||
<div slot="header" class="card-header">
|
||||
<span>个人信息</span>
|
||||
</div>
|
||||
<div class="avatar-box">
|
||||
<avatar :src="detail.avatar" :size="64" :name="detail.nickName" :char-index="-1"/>
|
||||
<div class="name">{{ detail.nickName | dv}}</div>
|
||||
</div>
|
||||
<div>
|
||||
<line-field label="手机号" :value="detail.phonenumber" />
|
||||
<line-field label="所属部门" :value="detail.deptName" />
|
||||
<line-field label="账号" :value="detail.userName" />
|
||||
<line-field label="入职时间" :value="detail.employDate" />
|
||||
<line-field label="最后登录IP" :value="detail.loginIp" />
|
||||
<line-field label="最后登录时间" :value="detail.loginDate" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 右侧统计信息 -->
|
||||
<el-col :xs="24" :sm="24" :md="16" :lg="18">
|
||||
<!-- 统计数据 -->
|
||||
<user-statistics class="mb-12" v-if="detail.userId" :user-id="detail.userId" />
|
||||
|
||||
<!-- 数据曲线图 -->
|
||||
<el-card class="card-box" shadow="hover">
|
||||
<el-tabs>
|
||||
<el-tab-pane label="客户统计">
|
||||
<performance-chart v-if="detail.userId" :query="{followId: detail.userId}" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-card class="card-box" style="margin-top: 12px;" shadow="hover" v-if="detail.userId">
|
||||
<el-tabs>
|
||||
<el-tab-pane label="参与项目" v-if="checkPermi(['bst:project:list'])">
|
||||
<project :query="{joinUserId: detail.userId}" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="任务列表" v-if="checkPermi(['bst:task:list'])">
|
||||
<task :query="{ownerId: detail.userId}" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getUser } from '@/api/system/user'
|
||||
import PerformanceChart from './components/PerformanceChart'
|
||||
import LineField from '@/components/LineField'
|
||||
import Avatar from '@/components/Avatar'
|
||||
import Project from '@/views/bst/project/index.vue'
|
||||
import Task from '@/views/bst/task/index.vue'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import UserStatistics from '@/views/system/user/view/components/UserStatistics'
|
||||
export default {
|
||||
name: 'UserView',
|
||||
components: {
|
||||
PerformanceChart,
|
||||
LineField,
|
||||
Avatar,
|
||||
Project,
|
||||
Task,
|
||||
UserStatistics
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
detail: {},
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getDetail()
|
||||
},
|
||||
methods: {
|
||||
checkPermi,
|
||||
/**
|
||||
* 获取用户详细信息
|
||||
*/
|
||||
getDetail() {
|
||||
this.loading = true
|
||||
getUser(this.$route.params.id).then(res => {
|
||||
this.detail = res.data
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
padding: 12px;
|
||||
|
||||
.user-info-card {
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.mb-12 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.login-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 13px;
|
||||
|
||||
span {
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-descriptions-item__label) {
|
||||
color: #606266;
|
||||
width: auto;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions-item__content) {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions-item) {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-tag--small) {
|
||||
height: 22px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
.avatar-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
.name {
|
||||
font-size: 16px;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端适配
|
||||
@media screen and (max-width: 767px) {
|
||||
.app-container {
|
||||
padding: 8px;
|
||||
|
||||
.mb-12 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user