This commit is contained in:
磷叶 2025-03-10 15:31:10 +08:00
parent 2110645392
commit c762bab30b
16 changed files with 480 additions and 82 deletions

View File

@ -54,3 +54,4 @@ export function delCustomer(id) {
method: 'delete'
})
}

View File

@ -9,4 +9,12 @@ export function dailyCreateCountCustomer(params) {
})
}
// 客户概览
export function customerBrief(params) {
return request({
url: '/dashboard/customer/brief',
method: 'get',
params
})
}

View File

@ -0,0 +1,9 @@
import request from '@/utils/request';
export function taskBrief(params) {
return request({
url: '/dashboard/task/brief',
method: 'get',
params
});
}

View File

@ -92,7 +92,7 @@
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
th {
word-break: break-word;
background-color: #f8f8f9;
background-color: #fff;
color: #515a6e;
height: 40px;
font-size: 13px;

View File

@ -0,0 +1,77 @@
<template>
<div class="card-tab" :class="{'active': active}" v-on="$listeners">
<div class="card-tab-title">
{{title}}
</div>
<div class="card-tab-content">
<div class="card-tab-item" v-for="(item, index) in data" :key="index">
<div class="card-tab-item-label">{{item.label}}</div>
<div class="card-tab-item-value">{{item.value}}</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "CardTab",
props: {
title: {
type: String,
default: ""
},
data: {
type: Array,
default: () => []
},
active: {
type: Boolean,
default: false
}
},
}
</script>
<style lang="scss" scoped>
.card-tab {
width: 100%;
height: 100%;
background-color: #e3ebfe;
border-radius: 12px 12px 0 0;
padding: 14px;
cursor: pointer;
transition: all 0.25s ease;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1);
.card-tab-title {
font-size: 14px;
margin-bottom: 8px;
}
.card-tab-content {
.card-tab-item {
display: flex;
align-items: center;
justify-content: space-between;
.card-tab-item-label {
font-size: 14px;
color: #212121;
}
.card-tab-item-value {
font-size: 14px;
color: #999;
}
}
}
&:hover {
background-color: #ebf0ff;
.card-tab-title {
color: #007aff;
}
}
&.active {
background-color: #fff;
.card-tab-title {
color: #007aff;
}
}
}
</style>

View File

@ -33,6 +33,7 @@ export default {
width: 100%;
position: relative;
overflow: hidden;
background-color: #fafafa;
}
.fixed-header + .app-main {

View File

@ -52,7 +52,11 @@ export const CustomerStatus = {
POTENTIAL: "1", // 潜在
INTENTION: "2", // 意向
TRANSACTION: "3", // 成交
INVALID: "4" // 失效
INVALID: "4", // 失效
// 跟进中的客户状态
following() {
return [this.POTENTIAL, this.INTENTION, this.TRANSACTION]
}
}
// 客户意向级别

View File

@ -0,0 +1,90 @@
<template>
<el-row v-loading="loading" :gutter="4">
<el-col :xs="4" :sm="3">
<card-tab title="全部客户" :data="[{label: brief.total}]" :active="value == null" @click="handleClick(null)"/>
</el-col>
<el-col :xs="4" :sm="3" v-for="item of dict.type.customer_status" :key="item.value" >
<card-tab
:title="item.label"
:data="getData(item.value)"
:active="value === item.value"
@click="handleClick(item.value)"
/>
</el-col>
</el-row>
</template>
<script>
import CardTab from "@/components/CardTab";
import { customerBrief } from '@/api/dashboard/customer';
import { BriefKeys, CustomerStatus } from '@/utils/enums';
export default {
name: "CustomerCardTabs",
dicts: ['customer_status'],
components: {
CardTab
},
props: {
value: {
type: String,
default: null,
},
query: {
type: Object,
default: () => ({})
}
},
data() {
return {
brief: {},
loading: false
}
},
computed: {
getData() {
return (key) => {
switch(key) {
case CustomerStatus.POTENTIAL:
return [{label: this.brief.potential}];
case CustomerStatus.INTENTION:
return [{label: this.brief.intention}];
case CustomerStatus.TRANSACTION:
return [{label: this.brief.transaction}];
case CustomerStatus.INVALID:
return [{label: this.brief.invalid}];
default:
return [{label: 0}];
}
}
},
},
created() {
this.getBrief();
},
methods: {
getBrief() {
this.loading = true;
customerBrief({
...this.query,
keys: [
BriefKeys.CUSTOMER_STATUS
]
}).then(res => {
this.brief = res.data;
}).finally(() => {
this.loading = false;
});
},
handleClick(value) {
if (this.value != value) {
this.$emit('input', value);
this.$emit('change', value);
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -7,35 +7,31 @@
<div class="app-container">
<el-form ref="form" :model="form" :rules="rules" label-width="80px" size="small" v-loading="loading">
<el-row>
<form-col :span="24" label="客户名称" prop="name">
<form-col :span="span" 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>
<template v-if="form.id != null">
<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>
</template>
<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
@ -59,6 +55,12 @@
<form-col :span="span" label="跟进人" prop="followId">
<user-select v-model="form.followId" :disabled="disabledFollowUser" />
</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="24" label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" maxlength="200" show-word-limit />
</form-col>
@ -121,12 +123,6 @@ export default {
status: [
{ required: true, message: "状态不能为空", trigger: "change" }
],
intentLevel: [
{ required: true, message: "意向强度不能为空", trigger: "change" }
],
source: [
{ required: true, message: "来源不能为空", trigger: "change" }
],
followId: [
{ required: true, message: "跟进人不能为空", trigger: "change" }
],
@ -139,6 +135,15 @@ export default {
],
followTime: [
{ required: true, message: "跟进时间不能为空", trigger: "change" }
],
customerStatus: [
{ required: true, message: "客户状态不能为空", trigger: "change" }
],
customerIntentLevel: [
{ required: true, message: "意向强度不能为空", trigger: "change" }
],
nextFollowTime: [
{ required: true, message: "下次跟进时间不能为空", trigger: "change" }
]
}
},
@ -187,8 +192,8 @@ export default {
id: null,
code: null,
name: null,
status: CustomerStatus.POTENTIAL,
intentLevel: CustomerIntentLevel.MEDIUM,
status: null,
intentLevel: null,
mobile: null,
wechat: null,
source: null,
@ -197,6 +202,9 @@ export default {
remark: null,
follow: {
followTime: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'),
nextFollowTime: null,
customerStatus: CustomerStatus.POTENTIAL,
customerIntentLevel: CustomerIntentLevel.MEDIUM,
}
};
this.resetForm("form");

View File

@ -1,15 +1,15 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="客户编号" prop="code">
<el-form-item label="客户编号" prop="id" v-if="isShow('id')">
<el-input
v-model="queryParams.code"
v-model="queryParams.id"
placeholder="请输入客户编号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="客户名称" prop="name">
<el-form-item label="客户名称" prop="name" v-if="isShow('name')">
<el-input
v-model="queryParams.name"
placeholder="请输入客户名称"
@ -17,7 +17,7 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<!-- <el-form-item label="状态" prop="status" v-if="isShow('name')">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable @change="handleQuery">
<el-option
v-for="dict in dict.type.customer_status"
@ -26,8 +26,8 @@
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="意向强度" prop="intentLevel">
</el-form-item> -->
<el-form-item label="意向强度" prop="intentLevel" v-if="isShow('name')">
<el-select v-model="queryParams.intentLevel" placeholder="请选择意向强度" clearable @change="handleQuery">
<el-option
v-for="dict in dict.type.customer_intent_level"
@ -37,7 +37,7 @@
/>
</el-select>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-form-item label="手机号" prop="mobile" v-if="isShow('mobile')">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号"
@ -45,7 +45,7 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="微信号" prop="wechat">
<el-form-item label="微信号" prop="wechat" v-if="isShow('wechat')">
<el-input
v-model="queryParams.wechat"
placeholder="请输入微信号"
@ -53,7 +53,7 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="来源" prop="source">
<el-form-item label="来源" prop="source" v-if="isShow('source')">
<el-input
v-model="queryParams.source"
placeholder="请输入来源"
@ -61,7 +61,7 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="意向" prop="intent">
<el-form-item label="意向" prop="intent" v-if="isShow('intent')">
<el-input
v-model="queryParams.intent"
placeholder="请输入意向"
@ -69,7 +69,7 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="跟进人" prop="followName">
<el-form-item label="跟进人" prop="followName" v-if="isShow('followName')">
<el-input
v-model="queryParams.followName"
placeholder="请输入跟进人"
@ -118,6 +118,8 @@
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<customer-card-tabs v-model="queryParams.status" :query="{...queryParams, status: null}" @change="handleChangeTab" ref="customerCardTabs" />
<el-table v-loading="loading" :data="customerList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="onSortChange">
<el-table-column type="selection" width="55" align="center" />
<template v-for="column of showColumns">
@ -136,17 +138,13 @@
<template v-if="column.key === 'id'">
{{d.row[column.key]}}
</template>
<template v-else-if="column.key === 'status'">
<dict-tag :options="dict.type.customer_status" :value="d.row[column.key]"/>
</template>
<template v-else-if="column.key === 'intentLevel'">
<dict-tag :options="dict.type.customer_intent_level" :value="d.row[column.key]"/>
</template>
<template v-else-if="column.key === 'intents'">
{{d.row[column.key].join(',')}}
</template>
<template v-else-if="column.key === 'name'">
<customer-link :id="d.row.id" :text="d.row[column.key]"/>
<dict-tag :options="dict.type.customer_status" :value="d.row.status" size="mini" style="margin-left: 4px;"/>
<dict-tag :options="dict.type.customer_intent_level" :value="d.row.intentLevel" size="mini" style="margin-left: 4px;"/>
</template>
<template v-else>
{{d.row[column.key]}}
@ -200,7 +198,12 @@
<customer-follow-edit-dialog
:show.sync="showFollowDialog"
@success="getList"
:init-data="{customerId: row.id, customerName: row.name}"
:init-data="{
customerId: row.id,
customerName: row.name,
customerStatus: row.status,
customerIntentLevel: row.intentLevel
}"
/>
</div>
</template>
@ -211,6 +214,8 @@ import { $showColumns } from '@/utils/mixins';
import FormCol from "@/components/FormCol/index.vue";
import CustomerLink from '@/components/Business/Customer/CustomerLink.vue';
import CustomerFollowEditDialog from '@/views/bst/customerFollow/components/CustomerFollowEditDialog.vue';
import CustomerCardTabs from './components/CustomerCardTabs.vue';
//
const defaultSort = {
prop: "createTime",
@ -221,7 +226,7 @@ export default {
name: "Customer",
mixins: [$showColumns],
dicts: ['customer_intent_level', 'customer_status'],
components: {FormCol, CustomerLink, CustomerFollowEditDialog},
components: {FormCol, CustomerLink, CustomerFollowEditDialog, CustomerCardTabs},
props: {
query: {
type: Object,
@ -231,6 +236,10 @@ export default {
initShowSearch: {
type: Boolean,
default: true
},
listApi: {
type: Function,
default: listCustomer
}
},
data() {
@ -241,17 +250,15 @@ export default {
//
columns: [
{key: 'id', visible: false, label: '编号', minWidth: null, sortable: true, overflow: false, align: 'center', width: "80"},
{key: 'name', visible: true, label: '名称', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'status', visible: true, label: '状态', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'intentLevel', visible: true, label: '意向强度', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'name', visible: true, label: '名称', minWidth: "200", sortable: true, overflow: false, align: 'left', width: null},
{key: 'remark', visible: true, label: '备注', minWidth: null, sortable: true, overflow: true, align: 'center', width: null},
{key: 'intents', visible: true, label: '意向', minWidth: null, sortable: true, overflow: true, align: 'center', width: null},
{key: 'mobile', visible: true, label: '手机号', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'wechat', visible: true, label: '微信号', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'source', visible: true, label: '来源', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'intents', visible: true, label: '意向', minWidth: null, sortable: true, overflow: true, align: 'center', width: null},
{key: 'followName', visible: true, label: '跟进人', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'lastFollowTime', visible: true, label: '最近跟进', minWidth: null, sortable: false, overflow: false, align: 'center', width: "100"},
{key: 'nextFollowTime', visible: true, label: '下次跟进', minWidth: null, sortable: false, overflow: false, align: 'center', width: "100"},
{key: 'remark', visible: true, label: '备注', minWidth: null, sortable: true, overflow: true, align: 'center', width: null},
{key: 'lastFollowTime', visible: true, label: '最近跟进', minWidth: null, sortable: true, overflow: false, align: 'center', width: "100"},
{key: 'nextFollowTime', visible: true, label: '下次跟进', minWidth: null, sortable: true, overflow: false, align: 'center', width: "100"},
{key: 'createName', visible: true, label: '创建人', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'createTime', visible: true, label: '创建时间', minWidth: null, sortable: true, overflow: false, align: 'center', width: "100"},
],
@ -338,16 +345,24 @@ export default {
/** 查询客户列表 */
getList() {
this.loading = true;
listCustomer(this.queryParams).then(response => {
this.listApi(this.queryParams).then(response => {
this.customerList = response.rows;
this.total = response.total;
this.loading = false;
});
},
//
handleChangeTab() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
if (this.$refs.customerCardTabs) {
this.$refs.customerCardTabs.getBrief();
}
},
/** 重置按钮操作 */
resetQuery() {

View File

@ -24,6 +24,7 @@ import { getCustomerFollow, addCustomerFollow, updateCustomerFollow } from "@/ap
import { mapGetters } from 'vuex';
import { parseTime } from '@/utils/ruoyi.js';
import CustomerFollowForm from '@/views/bst/customerFollow/components/CustomerFollowForm.vue';
export default {
name: "CustomerFollowEditDialog",
components: { CustomerFollowForm },
@ -61,6 +62,15 @@ export default {
],
followTime: [
{ required: true, message: "跟进时间不能为空", trigger: "change" }
],
customerStatus: [
{ required: true, message: "客户状态不能为空", trigger: "change" }
],
customerIntentLevel: [
{ required: true, message: "意向强度不能为空", trigger: "change" }
],
nextFollowTime: [
{ required: true, message: "下次跟进时间不能为空", trigger: "change" }
]
},
disabledKeys: [],
@ -101,6 +111,8 @@ export default {
userId: null,
nextFollowTime: null,
followTime: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'),
customerStatus: null,
customerIntentLevel: null,
// vo
customerName: null,
...this.initData

View File

@ -4,7 +4,32 @@
<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')"/>
<customer-input
v-model="form.customerId"
:text.sync="form.customerName"
:disabled="isDisabled('customerId')"
@confirm="handleConfirmCustomer"
/>
</form-col>
<form-col :span="span" label="客户状态" prop="customerStatus" :prop="propPrefix + 'customerStatus'">
<el-select v-model="form.customerStatus" placeholder="请选择客户状态" style="width: 100%;" @change="handleChangeCustomerStatus">
<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="customerIntentLevel" :prop="propPrefix + 'customerIntentLevel'">
<el-select v-model="form.customerIntentLevel" 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="propPrefix + 'type'">
<el-select v-model="form.type" placeholder="请选择跟进方式" style="width: 100%;">
@ -30,7 +55,7 @@
placeholder="请选择跟进时间">
</el-date-picker>
</form-col>
<form-col :span="span" label="下次跟进" :prop="propPrefix + 'nextFollowTime'">
<form-col :span="span" label="下次跟进" :prop="propPrefix + 'nextFollowTime'" v-if="form.customerStatus !== CustomerStatus.INVALID">
<el-date-picker clearable
style="width: 100%;"
v-model="form.nextFollowTime"
@ -48,10 +73,12 @@
import { DatePickerOptions } from '@/utils/constants.js';
import FormCol from '@/components/FormCol/index.vue';
import CustomerInput from '@/components/Business/Customer/CustomerInput.vue';
import { CustomerStatus } from '@/utils/enums.js';
export default {
name: 'CustomerFollowForm',
components: { FormCol, CustomerInput },
dicts: ['customer_follow_type'],
dicts: ['customer_follow_type', 'customer_status', 'customer_intent_level'],
props: {
form: {
type: Object,
@ -76,6 +103,7 @@ export default {
},
data() {
return {
CustomerStatus,
DatePickerOptions,
}
},
@ -90,6 +118,20 @@ export default {
return this.disabled.includes(key);
}
}
},
methods: {
//
handleConfirmCustomer(customer) {
this.form.customerStatus = customer.status;
this.form.customerIntentLevel = customer.intentLevel;
},
//
handleChangeCustomerStatus(value) {
//
if (value === CustomerStatus.INVALID) {
this.form.nextFollowTime = null;
}
}
}
}
</script>

View File

@ -11,22 +11,37 @@
</el-col>
</el-row>
<el-card>
<el-tabs >
<el-tab-pane label="项目列表" lazy v-if="checkPermi(['bst:project:list'])">
<project :hide-columns="projectHideColumns" :init-show-search="false"/>
<el-tabs>
<el-tab-pane label="我的项目" lazy v-if="checkPermi(['bst:project:list'])">
<project :hide-columns="projectHideColumns" :query="{joinUserId: userId}" :init-show-search="false"/>
</el-tab-pane>
<el-tab-pane label="任务列表" lazy v-if="checkPermi(['bst:task:list'])">
<task :hide-columns="taskHideColumns" :init-show-search="false"/>
<el-tab-pane label="我的任务" lazy v-if="checkPermi(['bst:task:list'])">
<task :hide-columns="taskHideColumns" :query="{ownerId: userId}" :init-show-search="false"/>
</el-tab-pane>
<el-tab-pane label="今日完成任务" lazy v-if="checkPermi(['bst:task:list'])">
<task :hide-columns="taskHideColumns" :query="{passDateRange: [today, today], statusList: TaskStatus.complete()}" :init-show-search="false"/>
<task
:hide-columns="taskHideColumns"
:query="{passDateRange: [today, today], statusList: TaskStatus.complete(), ownerId: userId}"
:init-show-search="false"
/>
</el-tab-pane>
<el-tab-pane label="今日新增客户" lazy v-if="checkPermi(['bst:customer:list'])">
<customer :hide-columns="customerHideColumns" :query="{createDate: today}" :init-show-search="false"/>
<customer :hide-columns="customerHideColumns" :query="{createDate: today, createId: userId}" :init-show-search="false"/>
</el-tab-pane>
<el-tab-pane label="待跟进客户" lazy v-if="checkPermi(['bst:customer:list'])">
<customer
:hide-columns="customerHideColumns"
:query="{
followId: userId,
statusList: CustomerStatus.following(),
nextFollowDateEnd: today,
orderByColumn: 'nextFollowTime',
isAsc: 'ascending'
}"
:init-show-search="false" />
</el-tab-pane>
</el-tabs>
</el-card>
<!-- <project-list-panel/> -->
</el-col>
<el-col :xs="24" :lg="5">
<el-card class="card-box">
@ -51,7 +66,8 @@ import Task from '@/views/bst/task/index.vue';
import Customer from '@/views/bst/customer/index.vue';
import { checkPermi } from '@/utils/permission';
import { parseTime } from '@/utils/ruoyi';
import { TaskStatus } from '@/utils/enums';
import { TaskStatus, CustomerStatus } from '@/utils/enums';
import { mapGetters } from 'vuex';
export default {
name: 'Index',
@ -59,7 +75,9 @@ export default {
data() {
return {
TaskStatus,
CustomerStatus,
today: parseTime(new Date(), '{y}-{m}-{d}'),
now: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'),
projectHideColumns: [
'id',
'no',
@ -100,8 +118,13 @@ export default {
],
}
},
computed: {
...mapGetters([
'userId'
])
},
methods: {
checkPermi,
checkPermi
}
}
</script>

View File

@ -0,0 +1,94 @@
<template>
<el-row v-loading="loading" :gutter="4">
<el-col :xs="4" :sm="3">
<card-tab title="全部任务" :data="[{label: brief.total}]" :active="value == null" @click="handleClick(null)"/>
</el-col>
<el-col :xs="4" :sm="3" v-for="item of dict.type.task_status" :key="item.value" >
<card-tab
:title="item.label"
:data="getData(item.value)"
:active="value === item.value"
@click="handleClick(item.value)"
/>
</el-col>
</el-row>
</template>
<script>
import CardTab from "@/components/CardTab";
import { taskBrief } from '@/api/dashboard/task';
import { BriefKeys, TaskStatus } from '@/utils/enums';
export default {
name: "TaskCardTabs",
dicts: ['task_status'],
components: {
CardTab
},
props: {
value: {
type: String,
default: null,
},
query: {
type: Object,
default: () => ({})
}
},
data() {
return {
brief: {},
loading: false
}
},
computed: {
getData() {
return (key) => {
switch(key) {
case TaskStatus.WAIT_RECEIVE:
return [{label: this.brief.waitReceive}];
case TaskStatus.PROCESSING:
return [{label: this.brief.inProgress}];
case TaskStatus.WAIT_CONFIRM:
return [{label: this.brief.waitConfirm}];
case TaskStatus.PASS:
return [{label: this.brief.completed}];
case TaskStatus.REJECT:
return [{label: this.brief.reject}];
case TaskStatus.CANCEL:
return [{label: 0}];
default:
return [{label: 0}];
}
}
},
},
created() {
this.getBrief();
},
methods: {
getBrief() {
this.loading = true;
taskBrief({
...this.query,
keys: [
BriefKeys.TASK_STATUS
]
}).then(res => {
this.brief = res.data;
}).finally(() => {
this.loading = false;
});
},
handleClick(value) {
if (this.value != value) {
this.$emit('input', value);
this.$emit('change', value);
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -14,7 +14,7 @@
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="statusList" v-if="isShow('description')">
<!-- <el-form-item label="状态" prop="statusList" v-if="isShow('description')">
<el-checkbox-group v-model="queryParams.statusList" placeholder="请选择状态" multiple clearable @change="handleQuery">
<el-checkbox-button
v-for="dict in dict.type.task_status"
@ -22,7 +22,7 @@
:label="dict.value"
>{{ dict.label }}</el-checkbox-button>
</el-checkbox-group>
</el-form-item>
</el-form-item> -->
<el-form-item label="优先级" prop="level" v-if="isShow('description')">
<el-select v-model="queryParams.level" placeholder="请选择优先级" clearable @change="handleQuery">
<el-option
@ -98,9 +98,11 @@
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<el-row class="mb8">
<!-- <el-row class="mb8">
<task-statistics />
</el-row>
</el-row> -->
<task-card-tabs v-model="queryParams.status" :query="{...queryParams, status: null}" @change="handleChangeTab" ref="taskCardTabs" />
<el-table v-loading="loading" :data="taskList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="onSortChange">
<el-table-column type="selection" width="55" align="center" />
@ -222,6 +224,8 @@ import UserSelect from '@/components/Business/User/UserSelect.vue';
import {TaskStatus} from '@/utils/enums'
import BooleanTag from '@/components/BooleanTag'
import TaskStatistics from '@/views/bst/task/components/TaskStatistics.vue'
import TaskCardTabs from '@/views/bst/task/components/TaskCardTabs.vue'
//
const defaultSort = {
prop: "createTime",
@ -232,7 +236,7 @@ export default {
name: "Task",
mixins: [$showColumns, $task],
dicts: ['task_status', 'task_level', 'task_type'],
components: {FormCol, TaskEditDialog, ProjectSelect, TaskViewDialog, ProjectLink, AvatarList, UserSelect, BooleanTag, TaskStatistics},
components: {FormCol, TaskEditDialog, ProjectSelect, TaskViewDialog, ProjectLink, AvatarList, UserSelect, BooleanTag, TaskStatistics, TaskCardTabs},
props: {
initData: {
type: Object,
@ -352,10 +356,18 @@ export default {
this.loading = false;
});
},
//
handleChangeTab() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
if (this.$refs.taskCardTabs) {
this.$refs.taskCardTabs.getBrief();
}
},
/** 重置按钮操作 */
resetQuery() {

View File

@ -293,6 +293,8 @@ export default {
{key: 'status', visible: true, label: '状态', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'phonenumber', visible: true, label: '手机号', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'email', visible: true, label: '邮箱', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'projectCount', visible: true, label: '项目数', minWidth: null, sortable: false, overflow: false, align: 'center', width: null},
{key: 'taskCount', visible: true, label: '任务数', minWidth: null, sortable: false, overflow: false, align: 'center', width: null},
{key: 'loginIp', visible: true, label: '登录IP', minWidth: null, sortable: true, overflow: false, align: 'center', width: null},
{key: 'loginDate', visible: true, label: '登录时间', minWidth: null, sortable: true, overflow: false, align: 'center', width: "100"},
{key: 'createTime', visible: true, label: '创建时间', minWidth: null, sortable: true, overflow: false, align: 'center', width: "100"},