上一条和下一条功能实现

This commit is contained in:
jiangjunhong 2025-04-25 17:01:04 +08:00
parent 903c0d3930
commit 9beb069550
74 changed files with 7895 additions and 2571 deletions

View File

@ -3,7 +3,7 @@ import {
} from "./chunk-FKOWI4VU.js";
import {
ElMessage
} from "./chunk-QM5QNHIO.js";
} from "./chunk-IRFI6G53.js";
import "./chunk-TQUTZUXW.js";
import "./chunk-GVKQVKU2.js";
import "./chunk-PHKUHJQP.js";
@ -11,6 +11,7 @@ import "./chunk-LROEKXT5.js";
import "./chunk-O7KFMITO.js";
import "./chunk-YNRHTVZR.js";
import "./chunk-67TUTJCN.js";
import "./chunk-7BPWZNUD.js";
import "./chunk-ULX5FOVL.js";
import {
Fragment,
@ -51,7 +52,6 @@ import {
withKeys,
withModifiers
} from "./chunk-GTWINWNV.js";
import "./chunk-7BPWZNUD.js";
import "./chunk-GFT2G5UO.js";
// node_modules/.pnpm/@form-create+designer@3.2.8_vue@3.5.12_typescript@5.3.3_/node_modules/@form-create/designer/dist/index.es.js

View File

@ -1,36 +1,35 @@
import "./chunk-BCFMTI3R.js";
import "./chunk-FBJWDERR.js";
import "./chunk-4TBAE7E3.js";
import "./chunk-OX6JP2AA.js";
import "./chunk-5MJQOEES.js";
import "./chunk-WTG273Z3.js";
import "./chunk-46FTKYZS.js";
import "./chunk-4TBAE7E3.js";
import "./chunk-C373DD4S.js";
import "./chunk-HZRC7S76.js";
import "./chunk-DWEZRTLP.js";
import "./chunk-5CTUXGGF.js";
import "./chunk-B4L4KVT2.js";
import "./chunk-MGTDGDA4.js";
import "./chunk-KARBCTKP.js";
import "./chunk-OX6JP2AA.js";
import "./chunk-JWIHIIT3.js";
import "./chunk-MC27QSJL.js";
import "./chunk-UUC56VKA.js";
import "./chunk-HGRMPKRI.js";
import "./chunk-NZR6SVVT.js";
import "./chunk-B4L4KVT2.js";
import "./chunk-WTG273Z3.js";
import "./chunk-MGTDGDA4.js";
import "./chunk-T6DOWK6H.js";
import "./chunk-ZEGP4DVR.js";
import "./chunk-FRMPVTTW.js";
import "./chunk-DWEZRTLP.js";
import "./chunk-5CTUXGGF.js";
import "./chunk-MB7KUOZ6.js";
import "./chunk-5KQYIVGF.js";
import "./chunk-7EBAVFZW.js";
import "./chunk-IIHLKWL5.js";
import "./chunk-JQ2IYF3Y.js";
import "./chunk-THZSTYZP.js";
import "./chunk-DB6OWVVK.js";
import "./chunk-FRMPVTTW.js";
import "./chunk-C47PGQGR.js";
import "./chunk-HODVN2HK.js";
import "./chunk-JQ2IYF3Y.js";
import "./chunk-T6DOWK6H.js";
import "./chunk-C47PGQGR.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-KARBCTKP.js";
import "./chunk-ZEGP4DVR.js";
import "./chunk-IIHLKWL5.js";
import "./chunk-7EBAVFZW.js";
import "./chunk-MB7KUOZ6.js";
import "./chunk-VLWH5T2T.js";
import "./chunk-5KQYIVGF.js";
import {
ElAutocomplete,
ElButton,
@ -60,18 +59,19 @@ import {
ElTooltip,
ElTree,
ElUpload
} from "./chunk-QM5QNHIO.js";
} from "./chunk-IRFI6G53.js";
import "./chunk-TQUTZUXW.js";
import "./chunk-GVKQVKU2.js";
import "./chunk-PHKUHJQP.js";
import "./chunk-LROEKXT5.js";
import "./chunk-O7KFMITO.js";
import "./chunk-VLWH5T2T.js";
import "./chunk-YNRHTVZR.js";
import "./chunk-5TRUIT6X.js";
import "./chunk-67TUTJCN.js";
import "./chunk-7BPWZNUD.js";
import "./chunk-5TRUIT6X.js";
import "./chunk-ULX5FOVL.js";
import "./chunk-GTWINWNV.js";
import "./chunk-7BPWZNUD.js";
import "./chunk-GFT2G5UO.js";
// node_modules/.pnpm/@form-create+element-ui@3.2.14_vue@3.5.12_typescript@5.3.3_/node_modules/@form-create/element-ui/auto-import.js

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,19 @@
import {
PaletteProvider
} from "./chunk-2VL3FEXP.js";
LANE_INDENTATION,
collectLanes,
computeChildrenBBox,
computeLanesResize,
ensureConstraints,
getChildLanes,
getLanesRoot,
getMinResizeBounds,
resizeBounds,
substractTRBL
} from "./chunk-YD4V2JYA.js";
import {
getParent,
isDirectionHorizontal
} from "./chunk-KKQ6WPIB.js";
import {
BaseViewer,
CommandInterceptor,
@ -42,7 +55,31 @@ import {
transform,
translate,
translate_default
} from "./chunk-NTWXIMGM.js";
} from "./chunk-HOG6PRDN.js";
import {
asBounds,
asTRBL,
eachElement,
filterRedundantWaypoints,
findPathIntersections,
getBBox,
getClosure,
getElementLineIntersection,
getEnclosedElements,
getMid,
getMidPoint,
getOrientation,
getParents,
isConnection,
isLabel,
pointDistance,
pointInRect,
pointsAligned,
pointsOnLine,
roundBounds,
roundPoint,
selfAndAllChildren
} from "./chunk-HXHANZ4Q.js";
import {
append,
attr as attr2,
@ -68,46 +105,6 @@ import {
query,
remove
} from "./chunk-PBO22ZEO.js";
import {
LANE_INDENTATION,
collectLanes,
computeChildrenBBox,
computeLanesResize,
ensureConstraints,
getChildLanes,
getLanesRoot,
getMinResizeBounds,
resizeBounds,
substractTRBL
} from "./chunk-UEIE3ZOK.js";
import {
asBounds,
asTRBL,
eachElement,
filterRedundantWaypoints,
findPathIntersections,
getBBox,
getClosure,
getElementLineIntersection,
getEnclosedElements,
getMid,
getMidPoint,
getOrientation,
getParents,
isConnection,
isLabel,
pointDistance,
pointInRect,
pointsAligned,
pointsOnLine,
roundBounds,
roundPoint,
selfAndAllChildren
} from "./chunk-T4R4535C.js";
import {
getParent,
isDirectionHorizontal
} from "./chunk-KKQ6WPIB.js";
import {
hasCompensateEventDefinition,
hasErrorEventDefinition,
@ -124,6 +121,9 @@ import {
is,
isAny
} from "./chunk-FNF472WR.js";
import {
PaletteProvider
} from "./chunk-2VL3FEXP.js";
import {
assign,
bind,

View File

@ -1,9 +1,9 @@
import {
Viewer
} from "./chunk-NTWXIMGM.js";
} from "./chunk-HOG6PRDN.js";
import "./chunk-HXHANZ4Q.js";
import "./chunk-J6RTEKLL.js";
import "./chunk-PBO22ZEO.js";
import "./chunk-T4R4535C.js";
import "./chunk-7C6J56BH.js";
import "./chunk-FNF472WR.js";
import "./chunk-YTJ5ESGD.js";

View File

@ -4,9 +4,9 @@ import {
computeLanesResize,
getChildLanes,
getLanesRoot
} from "./chunk-UEIE3ZOK.js";
import "./chunk-T4R4535C.js";
} from "./chunk-YD4V2JYA.js";
import "./chunk-KKQ6WPIB.js";
import "./chunk-HXHANZ4Q.js";
import "./chunk-7C6J56BH.js";
import "./chunk-FNF472WR.js";
import "./chunk-YTJ5ESGD.js";

View File

@ -1,985 +0,0 @@
import {
assign,
every,
filter,
find,
forEach,
groupBy,
has,
isArray,
isNumber,
isObject,
isUndefined,
sortBy
} from "./chunk-YTJ5ESGD.js";
// node_modules/.pnpm/diagram-js@14.11.3/node_modules/diagram-js/lib/util/Geometry.js
function pointDistance(a, b) {
if (!a || !b) {
return -1;
}
return Math.sqrt(
Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)
);
}
function pointsOnLine(p, q, r, accuracy) {
if (typeof accuracy === "undefined") {
accuracy = 5;
}
if (!p || !q || !r) {
return false;
}
var val = (q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x), dist = pointDistance(p, q);
return Math.abs(val / dist) <= accuracy;
}
var ALIGNED_THRESHOLD = 2;
function pointsAligned(a, b) {
var points = Array.from(arguments).flat();
const axisMap = {
"x": "v",
"y": "h"
};
for (const [axis, orientation] of Object.entries(axisMap)) {
if (pointsAlignedOnAxis(axis, points)) {
return orientation;
}
}
return false;
}
function pointsAlignedOnAxis(axis, points) {
const referencePoint = points[0];
return every(points, function(point) {
return Math.abs(referencePoint[axis] - point[axis]) <= ALIGNED_THRESHOLD;
});
}
function pointInRect(p, rect, tolerance) {
tolerance = tolerance || 0;
return p.x > rect.x - tolerance && p.y > rect.y - tolerance && p.x < rect.x + rect.width + tolerance && p.y < rect.y + rect.height + tolerance;
}
function getMidPoint(p, q) {
return {
x: Math.round(p.x + (q.x - p.x) / 2),
y: Math.round(p.y + (q.y - p.y) / 2)
};
}
// node_modules/.pnpm/path-intersection@3.1.0/node_modules/path-intersection/intersect.js
var p2s = /,?([a-z]),?/gi;
var toFloat = parseFloat;
var math = Math;
var PI = math.PI;
var mmin = math.min;
var mmax = math.max;
var pow = math.pow;
var abs = math.abs;
var pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?[\s]*,?[\s]*)+)/ig;
var pathValues = /(-?\d*\.?\d*(?:e[-+]?\d+)?)[\s]*,?[\s]*/ig;
var isArray2 = Array.isArray || function(o) {
return o instanceof Array;
};
function hasProperty(obj, property) {
return Object.prototype.hasOwnProperty.call(obj, property);
}
function clone(obj) {
if (typeof obj == "function" || Object(obj) !== obj) {
return obj;
}
var res = new obj.constructor();
for (var key in obj) {
if (hasProperty(obj, key)) {
res[key] = clone(obj[key]);
}
}
return res;
}
function repush(array, item) {
for (var i = 0, ii = array.length; i < ii; i++)
if (array[i] === item) {
return array.push(array.splice(i, 1)[0]);
}
}
function cacher(f) {
function newf() {
var arg = Array.prototype.slice.call(arguments, 0), args = arg.join("␀"), cache = newf.cache = newf.cache || {}, count = newf.count = newf.count || [];
if (hasProperty(cache, args)) {
repush(count, args);
return cache[args];
}
count.length >= 1e3 && delete cache[count.shift()];
count.push(args);
cache[args] = f(...arguments);
return cache[args];
}
return newf;
}
function parsePathString(pathString) {
if (!pathString) {
return null;
}
var pth = paths(pathString);
if (pth.arr) {
return clone(pth.arr);
}
var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0 }, data = [];
if (isArray2(pathString) && isArray2(pathString[0])) {
data = clone(pathString);
}
if (!data.length) {
String(pathString).replace(pathCommand, function(a, b, c) {
var params = [], name = b.toLowerCase();
c.replace(pathValues, function(a2, b2) {
b2 && params.push(+b2);
});
if (name == "m" && params.length > 2) {
data.push([b, ...params.splice(0, 2)]);
name = "l";
b = b == "m" ? "l" : "L";
}
while (params.length >= paramCounts[name]) {
data.push([b, ...params.splice(0, paramCounts[name])]);
if (!paramCounts[name]) {
break;
}
}
});
}
data.toString = paths.toString;
pth.arr = clone(data);
return data;
}
function paths(ps) {
var p = paths.ps = paths.ps || {};
if (p[ps]) {
p[ps].sleep = 100;
} else {
p[ps] = {
sleep: 100
};
}
setTimeout(function() {
for (var key in p) {
if (hasProperty(p, key) && key != ps) {
p[key].sleep--;
!p[key].sleep && delete p[key];
}
}
});
return p[ps];
}
function rectBBox(x, y, width, height) {
if (arguments.length === 1) {
y = x.y;
width = x.width;
height = x.height;
x = x.x;
}
return {
x,
y,
width,
height,
x2: x + width,
y2: y + height
};
}
function pathToString() {
return this.join(",").replace(p2s, "$1");
}
function pathClone(pathArray) {
var res = clone(pathArray);
res.toString = pathToString;
return res;
}
function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
var t1 = 1 - t, t13 = pow(t1, 3), t12 = pow(t1, 2), t2 = t * t, t3 = t2 * t, x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x, y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y;
return {
x: fixError(x),
y: fixError(y)
};
}
function bezierBBox(points) {
var bbox = curveBBox(...points);
return rectBBox(
bbox.x0,
bbox.y0,
bbox.x1 - bbox.x0,
bbox.y1 - bbox.y0
);
}
function isPointInsideBBox(bbox, x, y) {
return x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height;
}
function isBBoxIntersect(bbox1, bbox2) {
bbox1 = rectBBox(bbox1);
bbox2 = rectBBox(bbox2);
return isPointInsideBBox(bbox2, bbox1.x, bbox1.y) || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y) || isPointInsideBBox(bbox2, bbox1.x, bbox1.y2) || isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2) || isPointInsideBBox(bbox1, bbox2.x, bbox2.y) || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y) || isPointInsideBBox(bbox1, bbox2.x, bbox2.y2) || isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2) || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x) && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
}
function base3(t, p1, p2, p3, p4) {
var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4, t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
return t * t2 - 3 * p1 + 3 * p2;
}
function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
if (z == null) {
z = 1;
}
z = z > 1 ? 1 : z < 0 ? 0 : z;
var z2 = z / 2, n = 12, Tvalues = [-0.1252, 0.1252, -0.3678, 0.3678, -0.5873, 0.5873, -0.7699, 0.7699, -0.9041, 0.9041, -0.9816, 0.9816], Cvalues = [0.2491, 0.2491, 0.2335, 0.2335, 0.2032, 0.2032, 0.1601, 0.1601, 0.1069, 0.1069, 0.0472, 0.0472], sum = 0;
for (var i = 0; i < n; i++) {
var ct = z2 * Tvalues[i] + z2, xbase = base3(ct, x1, x2, x3, x4), ybase = base3(ct, y1, y2, y3, y4), comb = xbase * xbase + ybase * ybase;
sum += Cvalues[i] * math.sqrt(comb);
}
return z2 * sum;
}
function intersectLines(x1, y1, x2, y2, x3, y3, x4, y4) {
if (mmax(x1, x2) < mmin(x3, x4) || mmin(x1, x2) > mmax(x3, x4) || mmax(y1, y2) < mmin(y3, y4) || mmin(y1, y2) > mmax(y3, y4)) {
return;
}
var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4), ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4), denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (!denominator) {
return;
}
var px = fixError(nx / denominator), py = fixError(ny / denominator), px2 = +px.toFixed(2), py2 = +py.toFixed(2);
if (px2 < +mmin(x1, x2).toFixed(2) || px2 > +mmax(x1, x2).toFixed(2) || px2 < +mmin(x3, x4).toFixed(2) || px2 > +mmax(x3, x4).toFixed(2) || py2 < +mmin(y1, y2).toFixed(2) || py2 > +mmax(y1, y2).toFixed(2) || py2 < +mmin(y3, y4).toFixed(2) || py2 > +mmax(y3, y4).toFixed(2)) {
return;
}
return { x: px, y: py };
}
function fixError(number) {
return Math.round(number * 1e11) / 1e11;
}
function findBezierIntersections(bez1, bez2, justCount) {
var bbox1 = bezierBBox(bez1), bbox2 = bezierBBox(bez2);
if (!isBBoxIntersect(bbox1, bbox2)) {
return justCount ? 0 : [];
}
var l1 = bezlen(...bez1), l2 = bezlen(...bez2), n1 = isLine(bez1) ? 1 : ~~(l1 / 5) || 1, n2 = isLine(bez2) ? 1 : ~~(l2 / 5) || 1, dots1 = [], dots2 = [], xy = {}, res = justCount ? 0 : [];
for (var i = 0; i < n1 + 1; i++) {
var p = findDotsAtSegment(...bez1, i / n1);
dots1.push({ x: p.x, y: p.y, t: i / n1 });
}
for (i = 0; i < n2 + 1; i++) {
p = findDotsAtSegment(...bez2, i / n2);
dots2.push({ x: p.x, y: p.y, t: i / n2 });
}
for (i = 0; i < n1; i++) {
for (var j = 0; j < n2; j++) {
var di = dots1[i], di1 = dots1[i + 1], dj = dots2[j], dj1 = dots2[j + 1], ci = abs(di1.x - di.x) < 0.01 ? "y" : "x", cj = abs(dj1.x - dj.x) < 0.01 ? "y" : "x", is = intersectLines(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y), key;
if (is) {
key = is.x.toFixed(9) + "#" + is.y.toFixed(9);
if (xy[key]) {
continue;
}
xy[key] = true;
var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t), t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
if (justCount) {
res++;
} else {
res.push({
x: is.x,
y: is.y,
t1,
t2
});
}
}
}
}
}
return res;
}
function findPathIntersections(path1, path2, justCount) {
path1 = pathToCurve(path1);
path2 = pathToCurve(path2);
var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2, res = justCount ? 0 : [];
for (var i = 0, ii = path1.length; i < ii; i++) {
var pi = path1[i];
if (pi[0] == "M") {
x1 = x1m = pi[1];
y1 = y1m = pi[2];
} else {
if (pi[0] == "C") {
bez1 = [x1, y1, ...pi.slice(1)];
x1 = bez1[6];
y1 = bez1[7];
} else {
bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
x1 = x1m;
y1 = y1m;
}
for (var j = 0, jj = path2.length; j < jj; j++) {
var pj = path2[j];
if (pj[0] == "M") {
x2 = x2m = pj[1];
y2 = y2m = pj[2];
} else {
if (pj[0] == "C") {
bez2 = [x2, y2, ...pj.slice(1)];
x2 = bez2[6];
y2 = bez2[7];
} else {
bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
x2 = x2m;
y2 = y2m;
}
var intr = findBezierIntersections(bez1, bez2, justCount);
if (justCount) {
res += intr;
} else {
for (var k = 0, kk = intr.length; k < kk; k++) {
intr[k].segment1 = i;
intr[k].segment2 = j;
intr[k].bez1 = bez1;
intr[k].bez2 = bez2;
}
res = res.concat(intr);
}
}
}
}
}
return res;
}
function pathToAbsolute(pathArray) {
var pth = paths(pathArray);
if (pth.abs) {
return pathClone(pth.abs);
}
if (!isArray2(pathArray) || !isArray2(pathArray && pathArray[0])) {
pathArray = parsePathString(pathArray);
}
if (!pathArray || !pathArray.length) {
return [["M", 0, 0]];
}
var res = [], x = 0, y = 0, mx = 0, my = 0, start = 0, pa0;
if (pathArray[0][0] == "M") {
x = +pathArray[0][1];
y = +pathArray[0][2];
mx = x;
my = y;
start++;
res[0] = ["M", x, y];
}
for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
res.push(r = []);
pa = pathArray[i];
pa0 = pa[0];
if (pa0 != pa0.toUpperCase()) {
r[0] = pa0.toUpperCase();
switch (r[0]) {
case "A":
r[1] = pa[1];
r[2] = pa[2];
r[3] = pa[3];
r[4] = pa[4];
r[5] = pa[5];
r[6] = +pa[6] + x;
r[7] = +pa[7] + y;
break;
case "V":
r[1] = +pa[1] + y;
break;
case "H":
r[1] = +pa[1] + x;
break;
case "M":
mx = +pa[1] + x;
my = +pa[2] + y;
default:
for (var j = 1, jj = pa.length; j < jj; j++) {
r[j] = +pa[j] + (j % 2 ? x : y);
}
}
} else {
for (var k = 0, kk = pa.length; k < kk; k++) {
r[k] = pa[k];
}
}
pa0 = pa0.toUpperCase();
switch (r[0]) {
case "Z":
x = +mx;
y = +my;
break;
case "H":
x = r[1];
break;
case "V":
y = r[1];
break;
case "M":
mx = r[r.length - 2];
my = r[r.length - 1];
default:
x = r[r.length - 2];
y = r[r.length - 1];
}
}
res.toString = pathToString;
pth.abs = pathClone(res);
return res;
}
function isLine(bez) {
return bez[0] === bez[2] && bez[1] === bez[3] && bez[4] === bez[6] && bez[5] === bez[7];
}
function lineToCurve(x1, y1, x2, y2) {
return [
x1,
y1,
x2,
y2,
x2,
y2
];
}
function qubicToCurve(x1, y1, ax, ay, x2, y2) {
var _13 = 1 / 3, _23 = 2 / 3;
return [
_13 * x1 + _23 * ax,
_13 * y1 + _23 * ay,
_13 * x2 + _23 * ax,
_13 * y2 + _23 * ay,
x2,
y2
];
}
function arcToCurve(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
var _120 = PI * 120 / 180, rad = PI / 180 * (+angle || 0), res = [], xy, rotate = cacher(function(x3, y3, rad2) {
var X = x3 * math.cos(rad2) - y3 * math.sin(rad2), Y = x3 * math.sin(rad2) + y3 * math.cos(rad2);
return { x: X, y: Y };
});
if (!recursive) {
xy = rotate(x1, y1, -rad);
x1 = xy.x;
y1 = xy.y;
xy = rotate(x2, y2, -rad);
x2 = xy.x;
y2 = xy.y;
var x = (x1 - x2) / 2, y = (y1 - y2) / 2;
var h = x * x / (rx * rx) + y * y / (ry * ry);
if (h > 1) {
h = math.sqrt(h);
rx = h * rx;
ry = h * ry;
}
var rx2 = rx * rx, ry2 = ry * ry, k = (large_arc_flag == sweep_flag ? -1 : 1) * math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))), cx = k * rx * y / ry + (x1 + x2) / 2, cy = k * -ry * x / rx + (y1 + y2) / 2, f1 = math.asin(((y1 - cy) / ry).toFixed(9)), f2 = math.asin(((y2 - cy) / ry).toFixed(9));
f1 = x1 < cx ? PI - f1 : f1;
f2 = x2 < cx ? PI - f2 : f2;
f1 < 0 && (f1 = PI * 2 + f1);
f2 < 0 && (f2 = PI * 2 + f2);
if (sweep_flag && f1 > f2) {
f1 = f1 - PI * 2;
}
if (!sweep_flag && f2 > f1) {
f2 = f2 - PI * 2;
}
} else {
f1 = recursive[0];
f2 = recursive[1];
cx = recursive[2];
cy = recursive[3];
}
var df = f2 - f1;
if (abs(df) > _120) {
var f2old = f2, x2old = x2, y2old = y2;
f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
x2 = cx + rx * math.cos(f2);
y2 = cy + ry * math.sin(f2);
res = arcToCurve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
}
df = f2 - f1;
var c1 = math.cos(f1), s1 = math.sin(f1), c2 = math.cos(f2), s2 = math.sin(f2), t = math.tan(df / 4), hx = 4 / 3 * rx * t, hy = 4 / 3 * ry * t, m1 = [x1, y1], m2 = [x1 + hx * s1, y1 - hy * c1], m3 = [x2 + hx * s2, y2 - hy * c2], m4 = [x2, y2];
m2[0] = 2 * m1[0] - m2[0];
m2[1] = 2 * m1[1] - m2[1];
if (recursive) {
return [m2, m3, m4].concat(res);
} else {
res = [m2, m3, m4].concat(res).join().split(",");
var newres = [];
for (var i = 0, ii = res.length; i < ii; i++) {
newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
}
return newres;
}
}
function curveBBox(x0, y0, x1, y1, x2, y2, x3, y3) {
var tvalues = [], bounds = [[], []], a, b, c, t, t1, t2, b2ac, sqrtb2ac;
for (var i = 0; i < 2; ++i) {
if (i == 0) {
b = 6 * x0 - 12 * x1 + 6 * x2;
a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
c = 3 * x1 - 3 * x0;
} else {
b = 6 * y0 - 12 * y1 + 6 * y2;
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
c = 3 * y1 - 3 * y0;
}
if (abs(a) < 1e-12) {
if (abs(b) < 1e-12) {
continue;
}
t = -c / b;
if (0 < t && t < 1) {
tvalues.push(t);
}
continue;
}
b2ac = b * b - 4 * c * a;
sqrtb2ac = math.sqrt(b2ac);
if (b2ac < 0) {
continue;
}
t1 = (-b + sqrtb2ac) / (2 * a);
if (0 < t1 && t1 < 1) {
tvalues.push(t1);
}
t2 = (-b - sqrtb2ac) / (2 * a);
if (0 < t2 && t2 < 1) {
tvalues.push(t2);
}
}
var j = tvalues.length, jlen = j, mt;
while (j--) {
t = tvalues[j];
mt = 1 - t;
bounds[0][j] = mt * mt * mt * x0 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x3;
bounds[1][j] = mt * mt * mt * y0 + 3 * mt * mt * t * y1 + 3 * mt * t * t * y2 + t * t * t * y3;
}
bounds[0][jlen] = x0;
bounds[1][jlen] = y0;
bounds[0][jlen + 1] = x3;
bounds[1][jlen + 1] = y3;
bounds[0].length = bounds[1].length = jlen + 2;
return {
x0: mmin(...bounds[0]),
y0: mmin(...bounds[1]),
x1: mmax(...bounds[0]),
y1: mmax(...bounds[1])
};
}
function pathToCurve(path) {
var pth = paths(path);
if (pth.curve) {
return pathClone(pth.curve);
}
var curvedPath = pathToAbsolute(path), attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }, processPath = function(path2, d, pathCommand3) {
var nx, ny;
if (!path2) {
return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
}
!(path2[0] in { T: 1, Q: 1 }) && (d.qx = d.qy = null);
switch (path2[0]) {
case "M":
d.X = path2[1];
d.Y = path2[2];
break;
case "A":
path2 = ["C", ...arcToCurve(d.x, d.y, ...path2.slice(1))];
break;
case "S":
if (pathCommand3 == "C" || pathCommand3 == "S") {
nx = d.x * 2 - d.bx;
ny = d.y * 2 - d.by;
} else {
nx = d.x;
ny = d.y;
}
path2 = ["C", nx, ny, ...path2.slice(1)];
break;
case "T":
if (pathCommand3 == "Q" || pathCommand3 == "T") {
d.qx = d.x * 2 - d.qx;
d.qy = d.y * 2 - d.qy;
} else {
d.qx = d.x;
d.qy = d.y;
}
path2 = ["C", ...qubicToCurve(d.x, d.y, d.qx, d.qy, path2[1], path2[2])];
break;
case "Q":
d.qx = path2[1];
d.qy = path2[2];
path2 = ["C", ...qubicToCurve(d.x, d.y, path2[1], path2[2], path2[3], path2[4])];
break;
case "L":
path2 = ["C", ...lineToCurve(d.x, d.y, path2[1], path2[2])];
break;
case "H":
path2 = ["C", ...lineToCurve(d.x, d.y, path2[1], d.y)];
break;
case "V":
path2 = ["C", ...lineToCurve(d.x, d.y, d.x, path2[1])];
break;
case "Z":
path2 = ["C", ...lineToCurve(d.x, d.y, d.X, d.Y)];
break;
}
return path2;
}, fixArc = function(pp, i2) {
if (pp[i2].length > 7) {
pp[i2].shift();
var pi = pp[i2];
while (pi.length) {
pathCommands[i2] = "A";
pp.splice(i2++, 0, ["C", ...pi.splice(0, 6)]);
}
pp.splice(i2, 1);
ii = curvedPath.length;
}
}, pathCommands = [], pfirst = "", pathCommand2 = "";
for (var i = 0, ii = curvedPath.length; i < ii; i++) {
curvedPath[i] && (pfirst = curvedPath[i][0]);
if (pfirst != "C") {
pathCommands[i] = pfirst;
i && (pathCommand2 = pathCommands[i - 1]);
}
curvedPath[i] = processPath(curvedPath[i], attrs, pathCommand2);
if (pathCommands[i] != "A" && pfirst == "C")
pathCommands[i] = "C";
fixArc(curvedPath, i);
var seg = curvedPath[i], seglen = seg.length;
attrs.x = seg[seglen - 2];
attrs.y = seg[seglen - 1];
attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
}
pth.curve = pathClone(curvedPath);
return curvedPath;
}
// node_modules/.pnpm/diagram-js@14.11.3/node_modules/diagram-js/lib/util/ModelUtil.js
function isConnection(value) {
return isObject(value) && has(value, "waypoints");
}
function isLabel(value) {
return isObject(value) && has(value, "labelTarget");
}
// node_modules/.pnpm/diagram-js@14.11.3/node_modules/diagram-js/lib/layout/LayoutUtil.js
function roundBounds(bounds) {
return {
x: Math.round(bounds.x),
y: Math.round(bounds.y),
width: Math.round(bounds.width),
height: Math.round(bounds.height)
};
}
function roundPoint(point) {
return {
x: Math.round(point.x),
y: Math.round(point.y)
};
}
function asTRBL(bounds) {
return {
top: bounds.y,
right: bounds.x + (bounds.width || 0),
bottom: bounds.y + (bounds.height || 0),
left: bounds.x
};
}
function asBounds(trbl) {
return {
x: trbl.left,
y: trbl.top,
width: trbl.right - trbl.left,
height: trbl.bottom - trbl.top
};
}
function getBoundsMid(bounds) {
return roundPoint({
x: bounds.x + (bounds.width || 0) / 2,
y: bounds.y + (bounds.height || 0) / 2
});
}
function getConnectionMid(connection) {
var waypoints = connection.waypoints;
var parts = waypoints.reduce(function(parts2, point, index) {
var lastPoint = waypoints[index - 1];
if (lastPoint) {
var lastPart = parts2[parts2.length - 1];
var startLength = lastPart && lastPart.endLength || 0;
var length = distance(lastPoint, point);
parts2.push({
start: lastPoint,
end: point,
startLength,
endLength: startLength + length,
length
});
}
return parts2;
}, []);
var totalLength = parts.reduce(function(length, part) {
return length + part.length;
}, 0);
var midLength = totalLength / 2;
var i = 0;
var midSegment = parts[i];
while (midSegment.endLength < midLength) {
midSegment = parts[++i];
}
var segmentProgress = (midLength - midSegment.startLength) / midSegment.length;
var midPoint = {
x: midSegment.start.x + (midSegment.end.x - midSegment.start.x) * segmentProgress,
y: midSegment.start.y + (midSegment.end.y - midSegment.start.y) * segmentProgress
};
return midPoint;
}
function getMid(element) {
if (isConnection(element)) {
return getConnectionMid(element);
}
return getBoundsMid(element);
}
function getOrientation(rect, reference, padding) {
padding = padding || 0;
if (!isObject(padding)) {
padding = { x: padding, y: padding };
}
var rectOrientation = asTRBL(rect), referenceOrientation = asTRBL(reference);
var top = rectOrientation.bottom + padding.y <= referenceOrientation.top, right = rectOrientation.left - padding.x >= referenceOrientation.right, bottom = rectOrientation.top - padding.y >= referenceOrientation.bottom, left = rectOrientation.right + padding.x <= referenceOrientation.left;
var vertical = top ? "top" : bottom ? "bottom" : null, horizontal = left ? "left" : right ? "right" : null;
if (horizontal && vertical) {
return vertical + "-" + horizontal;
} else {
return horizontal || vertical || "intersect";
}
}
function getElementLineIntersection(elementPath, linePath, cropStart) {
var intersections = getIntersections(elementPath, linePath);
if (intersections.length === 1) {
return roundPoint(intersections[0]);
} else if (intersections.length === 2 && pointDistance(intersections[0], intersections[1]) < 1) {
return roundPoint(intersections[0]);
} else if (intersections.length > 1) {
intersections = sortBy(intersections, function(i) {
var distance2 = Math.floor(i.t2 * 100) || 1;
distance2 = 100 - distance2;
distance2 = (distance2 < 10 ? "0" : "") + distance2;
return i.segment2 + "#" + distance2;
});
return roundPoint(intersections[cropStart ? 0 : intersections.length - 1]);
}
return null;
}
function getIntersections(a, b) {
return findPathIntersections(a, b);
}
function filterRedundantWaypoints(waypoints) {
waypoints = waypoints.slice();
var idx = 0, point, previousPoint, nextPoint;
while (waypoints[idx]) {
point = waypoints[idx];
previousPoint = waypoints[idx - 1];
nextPoint = waypoints[idx + 1];
if (pointDistance(point, nextPoint) === 0 || pointsOnLine(previousPoint, nextPoint, point)) {
waypoints.splice(idx, 1);
} else {
idx++;
}
}
return waypoints;
}
function distance(a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
// node_modules/.pnpm/diagram-js@14.11.3/node_modules/diagram-js/lib/util/Elements.js
function getParents(elements) {
return filter(elements, function(element) {
return !find(elements, function(e) {
return e !== element && getParent(element, e);
});
});
}
function getParent(element, parent) {
if (!parent) {
return;
}
if (element === parent) {
return parent;
}
if (!element.parent) {
return;
}
return getParent(element.parent, parent);
}
function add(elements, element, unique) {
var canAdd = !unique || elements.indexOf(element) === -1;
if (canAdd) {
elements.push(element);
}
return canAdd;
}
function eachElement(elements, fn, depth) {
depth = depth || 0;
if (!isArray(elements)) {
elements = [elements];
}
forEach(elements, function(s, i) {
var filter2 = fn(s, i, depth);
if (isArray(filter2) && filter2.length) {
eachElement(filter2, fn, depth + 1);
}
});
}
function selfAndChildren(elements, unique, maxDepth) {
var result = [], processedChildren = [];
eachElement(elements, function(element, i, depth) {
add(result, element, unique);
var children = element.children;
if (maxDepth === -1 || depth < maxDepth) {
if (children && add(processedChildren, children, unique)) {
return children;
}
}
});
return result;
}
function selfAndAllChildren(elements, allowDuplicates) {
return selfAndChildren(elements, !allowDuplicates, -1);
}
function getClosure(elements, isTopLevel, closure) {
if (isUndefined(isTopLevel)) {
isTopLevel = true;
}
if (isObject(isTopLevel)) {
closure = isTopLevel;
isTopLevel = true;
}
closure = closure || {};
var allShapes = copyObject(closure.allShapes), allConnections = copyObject(closure.allConnections), enclosedElements = copyObject(closure.enclosedElements), enclosedConnections = copyObject(closure.enclosedConnections);
var topLevel = copyObject(
closure.topLevel,
isTopLevel && groupBy(elements, function(e) {
return e.id;
})
);
function handleConnection(c) {
if (topLevel[c.source.id] && topLevel[c.target.id]) {
topLevel[c.id] = [c];
}
if (allShapes[c.source.id] && allShapes[c.target.id]) {
enclosedConnections[c.id] = enclosedElements[c.id] = c;
}
allConnections[c.id] = c;
}
function handleElement(element) {
enclosedElements[element.id] = element;
if (element.waypoints) {
enclosedConnections[element.id] = allConnections[element.id] = element;
} else {
allShapes[element.id] = element;
forEach(element.incoming, handleConnection);
forEach(element.outgoing, handleConnection);
return element.children;
}
}
eachElement(elements, handleElement);
return {
allShapes,
allConnections,
topLevel,
enclosedConnections,
enclosedElements
};
}
function getBBox(elements, stopRecursion) {
stopRecursion = !!stopRecursion;
if (!isArray(elements)) {
elements = [elements];
}
var minX, minY, maxX, maxY;
forEach(elements, function(element) {
var bbox = element;
if (element.waypoints && !stopRecursion) {
bbox = getBBox(element.waypoints, true);
}
var x = bbox.x, y = bbox.y, height = bbox.height || 0, width = bbox.width || 0;
if (x < minX || minX === void 0) {
minX = x;
}
if (y < minY || minY === void 0) {
minY = y;
}
if (x + width > maxX || maxX === void 0) {
maxX = x + width;
}
if (y + height > maxY || maxY === void 0) {
maxY = y + height;
}
});
return {
x: minX,
y: minY,
height: maxY - minY,
width: maxX - minX
};
}
function getEnclosedElements(elements, bbox) {
var filteredElements = {};
forEach(elements, function(element) {
var e = element;
if (e.waypoints) {
e = getBBox(e);
}
if (!isNumber(bbox.y) && e.x > bbox.x) {
filteredElements[element.id] = element;
}
if (!isNumber(bbox.x) && e.y > bbox.y) {
filteredElements[element.id] = element;
}
if (e.x > bbox.x && e.y > bbox.y) {
if (isNumber(bbox.width) && isNumber(bbox.height) && e.width + e.x < bbox.width + bbox.x && e.height + e.y < bbox.height + bbox.y) {
filteredElements[element.id] = element;
} else if (!isNumber(bbox.width) || !isNumber(bbox.height)) {
filteredElements[element.id] = element;
}
}
});
return filteredElements;
}
function getType(element) {
if ("waypoints" in element) {
return "connection";
}
if ("x" in element) {
return "shape";
}
return "root";
}
function isFrameElement(element) {
return !!(element && element.isFrame);
}
function copyObject(src1, src2) {
return assign({}, src1 || {}, src2 || {});
}
export {
pointDistance,
pointsOnLine,
pointsAligned,
pointInRect,
getMidPoint,
findPathIntersections,
isConnection,
isLabel,
roundBounds,
roundPoint,
asTRBL,
asBounds,
getMid,
getOrientation,
getElementLineIntersection,
filterRedundantWaypoints,
getParents,
eachElement,
selfAndAllChildren,
getClosure,
getBBox,
getEnclosedElements,
getType,
isFrameElement
};
//# sourceMappingURL=chunk-T4R4535C.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,9 @@
import {
install as install2
} from "./chunk-KJFX5ANN.js";
import {
install
} from "./chunk-XNS7XN3L.js";
} from "./chunk-KNIVXUEF.js";
import {
install as install2
} from "./chunk-OBFEZ4QG.js";
import {
extendChartView,
extendSeriesModel,
@ -13,20 +13,20 @@ import {
installLabelLayout,
number_exports,
util_exports
} from "./chunk-2TFNBWBF.js";
import "./chunk-X5CQ556A.js";
import "./chunk-NF6IA4AL.js";
} from "./chunk-M56X7I32.js";
import "./chunk-LMKTAN4E.js";
import "./chunk-V45EMKQW.js";
import {
SeriesData_default,
registerLayout,
registerPreprocessor,
use
} from "./chunk-QJLIGECE.js";
import "./chunk-GVYX3QQL.js";
} from "./chunk-ZCOAJCG3.js";
import "./chunk-IWFMEAQB.js";
import "./chunk-GFT2G5UO.js";
// node_modules/.pnpm/echarts@5.5.1/node_modules/echarts/lib/echarts.js
use([install2, install]);
use([install, install2]);
use(installLabelLayout);
// node_modules/.pnpm/echarts-wordcloud@2.1.0_echarts@5.5.1/node_modules/echarts-wordcloud/src/WordCloudSeries.js

File diff suppressed because one or more lines are too long

164
node_modules/.vite/deps/echarts.js generated vendored
View File

@ -1,19 +1,20 @@
import {
install as install50
} from "./chunk-6JA6BRXS.js";
import {
install as install27,
install10 as install36,
install11 as install37,
install12 as install38,
install13 as install39,
install14 as install40,
install15 as install41,
install16 as install42,
install17 as install43,
install18 as install44,
install19 as install45,
install14 as install41,
install15 as install42,
install16 as install43,
install17 as install44,
install18 as install45,
install19 as install46,
install2 as install28,
install20 as install46,
install21 as install47,
install22 as install48,
install20 as install47,
install21 as install48,
install22 as install49,
install3 as install29,
install4 as install30,
install5 as install31,
@ -21,55 +22,54 @@ import {
install7 as install33,
install8 as install34,
install9 as install35
} from "./chunk-ND6EB7CF.js";
} from "./chunk-AUPWZZDD.js";
import {
install as install50
} from "./chunk-2I4JXH7Q.js";
install as install40
} from "./chunk-PRCWTGNB.js";
import {
install as install49
} from "./chunk-KJFX5ANN.js";
install as install39
} from "./chunk-X72D7ZPW.js";
import {
install as install38
} from "./chunk-XNXAGDXI.js";
import "./chunk-EM6SBCMK.js";
import {
install,
install10 as install12,
install11 as install13,
install14 as install17,
install15 as install18,
install16 as install19,
install17 as install20,
install18 as install21,
install19 as install22,
install10 as install11,
install11 as install12,
install12 as install14,
install13 as install15,
install14 as install16,
install15 as install17,
install16 as install18,
install17 as install19,
install18 as install20,
install19 as install21,
install2,
install20 as install23,
install21 as install24,
install22 as install25,
install20 as install22,
install21 as install23,
install22 as install24,
install3,
install4 as install5,
install5 as install7,
install6 as install8,
install7 as install9,
install8 as install10,
install9 as install11
} from "./chunk-NMP246CO.js";
install4,
install5,
install6 as install7,
install7 as install8,
install8 as install9,
install9 as install10
} from "./chunk-TWPUBDXC.js";
import {
install as install15
} from "./chunk-P7FEEIEF.js";
import {
install as install16
} from "./chunk-QQGFJP25.js";
import {
install as install14
} from "./chunk-D6WD7HPM.js";
import "./chunk-FGVL4PGJ.js";
import {
install3 as install4,
install4 as install6
} from "./chunk-F5M4HOVH.js";
import "./chunk-VW272IHF.js";
install3 as install6,
install4 as install13
} from "./chunk-WRWLNY4R.js";
import "./chunk-GORRBXQH.js";
import "./chunk-QAR3K42R.js";
import "./chunk-LEHUY6WA.js";
import "./chunk-NWTUODUO.js";
import {
install as install25
} from "./chunk-KNIVXUEF.js";
import {
install as install26
} from "./chunk-XNS7XN3L.js";
} from "./chunk-OBFEZ4QG.js";
import {
extendChartView,
extendComponentModel,
@ -82,12 +82,12 @@ import {
number_exports,
time_exports,
util_exports as util_exports2
} from "./chunk-2TFNBWBF.js";
import "./chunk-X5CQ556A.js";
} from "./chunk-M56X7I32.js";
import "./chunk-LMKTAN4E.js";
import {
Axis_default,
parseGeoJSON
} from "./chunk-NF6IA4AL.js";
} from "./chunk-V45EMKQW.js";
import {
Chart_default,
Component_default,
@ -136,7 +136,7 @@ import {
use,
version,
warn
} from "./chunk-QJLIGECE.js";
} from "./chunk-ZCOAJCG3.js";
import {
BoundingRect_default,
Displayable_default,
@ -163,7 +163,7 @@ import {
util_exports,
vector_exports,
zrender_exports
} from "./chunk-GVYX3QQL.js";
} from "./chunk-IWFMEAQB.js";
import "./chunk-GFT2G5UO.js";
// node_modules/.pnpm/zrender@5.6.0/node_modules/zrender/lib/tool/convertPath.js
@ -1894,42 +1894,42 @@ function installUniversalTransition(registers) {
}
// node_modules/.pnpm/echarts@5.5.1/node_modules/echarts/index.js
use([install49]);
use([install25]);
use([install50]);
use([install27, install28, install29, install30, install31, install32, install33, install34, install35, install36, install37, install38, install39, install40, install41, install42, install43, install44, install45, install46, install47, install48]);
use(install2);
use(install3);
use(install4);
use(install5);
use([install, install2, install3, install4, install5, install7, install8, install9, install10, install11, install12, install14, install15, install16, install17, install18, install19, install20, install21, install22, install23, install24]);
use(install28);
use(install29);
use(install6);
use(install7);
use(install8);
use(install9);
use(install10);
use(install);
use(install11);
use(install12);
use(install30);
use(install13);
use(install14);
use(install15);
use(install16);
use(install17);
use(install20);
use(install18);
use(install19);
use(install23);
use(install21);
use(install22);
use(install24);
use(install25);
use(install31);
use(install32);
use(install33);
use(install34);
use(install27);
use(install35);
use(install36);
use(install37);
use(install38);
use(install39);
use(install40);
use(install41);
use(install44);
use(install42);
use(install43);
use(install47);
use(install45);
use(install46);
use(install48);
use(install49);
use(install26);
use(installUniversalTransition);
use(installLabelLayout);
export {
Axis_default as Axis,
Chart_default as ChartView,
Component_default2 as ComponentModel,
Component_default as ComponentView,
Component_default as ComponentModel,
Component_default2 as ComponentView,
SeriesData_default as List,
Model_default as Model,
PRIORITY,

File diff suppressed because one or more lines are too long

View File

@ -21,15 +21,15 @@ import {
install7,
install8,
install9
} from "./chunk-ND6EB7CF.js";
import "./chunk-F5M4HOVH.js";
import "./chunk-VW272IHF.js";
} from "./chunk-TWPUBDXC.js";
import "./chunk-WRWLNY4R.js";
import "./chunk-GORRBXQH.js";
import "./chunk-QAR3K42R.js";
import "./chunk-LEHUY6WA.js";
import "./chunk-X5CQ556A.js";
import "./chunk-NF6IA4AL.js";
import "./chunk-QJLIGECE.js";
import "./chunk-GVYX3QQL.js";
import "./chunk-NWTUODUO.js";
import "./chunk-LMKTAN4E.js";
import "./chunk-V45EMKQW.js";
import "./chunk-ZCOAJCG3.js";
import "./chunk-IWFMEAQB.js";
import "./chunk-GFT2G5UO.js";
export {
install2 as BarChart,

View File

@ -1,83 +1,83 @@
import {
install as install2,
install10 as install14,
install11 as install15,
install12 as install19,
install13 as install20,
install14 as install21,
install15 as install22,
install16 as install23,
install17 as install24,
install18 as install25,
install19 as install26,
install2 as install3,
install20 as install27,
install21 as install28,
install22 as install29,
install3 as install4,
install4 as install7,
install5 as install9,
install6 as install10,
install7 as install11,
install8 as install12,
install9 as install13
} from "./chunk-NMP246CO.js";
install as install6,
install10 as install15,
install11 as install16,
install12 as install20,
install13 as install21,
install14 as install22,
install15 as install23,
install16 as install24,
install17 as install25,
install18 as install26,
install19 as install27,
install2 as install7,
install20 as install28,
install21 as install29,
install22 as install30,
install3 as install8,
install4 as install9,
install5 as install10,
install6 as install11,
install7 as install12,
install8 as install13,
install9 as install14
} from "./chunk-AUPWZZDD.js";
import {
install as install17
} from "./chunk-P7FEEIEF.js";
install as install19
} from "./chunk-PRCWTGNB.js";
import {
install as install18
} from "./chunk-QQGFJP25.js";
} from "./chunk-X72D7ZPW.js";
import {
install as install16
} from "./chunk-D6WD7HPM.js";
import "./chunk-FGVL4PGJ.js";
install as install17
} from "./chunk-XNXAGDXI.js";
import "./chunk-EM6SBCMK.js";
import {
install,
install2 as install5,
install3 as install6,
install4 as install8
} from "./chunk-F5M4HOVH.js";
import "./chunk-VW272IHF.js";
install2,
install3,
install4
} from "./chunk-WRWLNY4R.js";
import "./chunk-GORRBXQH.js";
import "./chunk-QAR3K42R.js";
import "./chunk-LEHUY6WA.js";
import "./chunk-NWTUODUO.js";
import {
install as install30
} from "./chunk-XNS7XN3L.js";
import "./chunk-NF6IA4AL.js";
import "./chunk-QJLIGECE.js";
import "./chunk-GVYX3QQL.js";
install as install5
} from "./chunk-OBFEZ4QG.js";
import "./chunk-V45EMKQW.js";
import "./chunk-ZCOAJCG3.js";
import "./chunk-IWFMEAQB.js";
import "./chunk-GFT2G5UO.js";
export {
install28 as AriaComponent,
install2 as AxisPointerComponent,
install13 as BrushComponent,
install9 as CalendarComponent,
install24 as DataZoomComponent,
install22 as DataZoomInsideComponent,
install23 as DataZoomSliderComponent,
install30 as DatasetComponent,
install6 as GeoComponent,
install10 as GraphicComponent,
install3 as GridComponent,
install29 as AriaComponent,
install6 as AxisPointerComponent,
install14 as BrushComponent,
install10 as CalendarComponent,
install25 as DataZoomComponent,
install23 as DataZoomInsideComponent,
install24 as DataZoomSliderComponent,
install5 as DatasetComponent,
install3 as GeoComponent,
install11 as GraphicComponent,
install7 as GridComponent,
install as GridSimpleComponent,
install21 as LegendComponent,
install19 as LegendPlainComponent,
install20 as LegendScrollComponent,
install18 as MarkAreaComponent,
install17 as MarkLineComponent,
install16 as MarkPointComponent,
install8 as ParallelComponent,
install4 as PolarComponent,
install5 as RadarComponent,
install7 as SingleAxisComponent,
install15 as TimelineComponent,
install14 as TitleComponent,
install11 as ToolboxComponent,
install12 as TooltipComponent,
install29 as TransformComponent,
install27 as VisualMapComponent,
install25 as VisualMapContinuousComponent,
install26 as VisualMapPiecewiseComponent
install22 as LegendComponent,
install20 as LegendPlainComponent,
install21 as LegendScrollComponent,
install19 as MarkAreaComponent,
install18 as MarkLineComponent,
install17 as MarkPointComponent,
install4 as ParallelComponent,
install8 as PolarComponent,
install2 as RadarComponent,
install9 as SingleAxisComponent,
install16 as TimelineComponent,
install15 as TitleComponent,
install12 as ToolboxComponent,
install13 as TooltipComponent,
install30 as TransformComponent,
install28 as VisualMapComponent,
install26 as VisualMapContinuousComponent,
install27 as VisualMapPiecewiseComponent
};
//# sourceMappingURL=echarts_components.js.map

View File

@ -9,12 +9,12 @@ import {
number_exports,
time_exports,
util_exports as util_exports2
} from "./chunk-2TFNBWBF.js";
import "./chunk-X5CQ556A.js";
} from "./chunk-M56X7I32.js";
import "./chunk-LMKTAN4E.js";
import {
Axis_default,
parseGeoJSON
} from "./chunk-NF6IA4AL.js";
} from "./chunk-V45EMKQW.js";
import {
Chart_default,
Component_default,
@ -52,7 +52,7 @@ import {
throttle,
use,
version
} from "./chunk-QJLIGECE.js";
} from "./chunk-ZCOAJCG3.js";
import {
brushSingle,
color_exports,
@ -62,13 +62,13 @@ import {
util_exports,
vector_exports,
zrender_exports
} from "./chunk-GVYX3QQL.js";
} from "./chunk-IWFMEAQB.js";
import "./chunk-GFT2G5UO.js";
export {
Axis_default as Axis,
Chart_default as ChartView,
Component_default2 as ComponentModel,
Component_default as ComponentView,
Component_default as ComponentModel,
Component_default2 as ComponentView,
SeriesData_default as List,
Model_default as Model,
PRIORITY,

View File

@ -1,12 +1,12 @@
import {
install
} from "./chunk-QQGFJP25.js";
import "./chunk-FGVL4PGJ.js";
} from "./chunk-PRCWTGNB.js";
import "./chunk-EM6SBCMK.js";
import "./chunk-QAR3K42R.js";
import {
use
} from "./chunk-QJLIGECE.js";
import "./chunk-GVYX3QQL.js";
} from "./chunk-ZCOAJCG3.js";
import "./chunk-IWFMEAQB.js";
import "./chunk-GFT2G5UO.js";
// node_modules/.pnpm/echarts@5.5.1/node_modules/echarts/lib/component/markArea.js

View File

@ -1,13 +1,13 @@
import {
install
} from "./chunk-P7FEEIEF.js";
import "./chunk-FGVL4PGJ.js";
import "./chunk-VW272IHF.js";
} from "./chunk-X72D7ZPW.js";
import "./chunk-EM6SBCMK.js";
import "./chunk-GORRBXQH.js";
import "./chunk-QAR3K42R.js";
import {
use
} from "./chunk-QJLIGECE.js";
import "./chunk-GVYX3QQL.js";
} from "./chunk-ZCOAJCG3.js";
import "./chunk-IWFMEAQB.js";
import "./chunk-GFT2G5UO.js";
// node_modules/.pnpm/echarts@5.5.1/node_modules/echarts/lib/component/markLine.js

View File

@ -1,12 +1,12 @@
import {
install
} from "./chunk-D6WD7HPM.js";
import "./chunk-FGVL4PGJ.js";
import "./chunk-LEHUY6WA.js";
} from "./chunk-XNXAGDXI.js";
import "./chunk-EM6SBCMK.js";
import "./chunk-NWTUODUO.js";
import {
use
} from "./chunk-QJLIGECE.js";
import "./chunk-GVYX3QQL.js";
} from "./chunk-ZCOAJCG3.js";
import "./chunk-IWFMEAQB.js";
import "./chunk-GFT2G5UO.js";
// node_modules/.pnpm/echarts@5.5.1/node_modules/echarts/lib/component/markPoint.js

View File

@ -1,10 +1,10 @@
import {
install as install2
} from "./chunk-2I4JXH7Q.js";
} from "./chunk-6JA6BRXS.js";
import {
install
} from "./chunk-KJFX5ANN.js";
import "./chunk-GVYX3QQL.js";
} from "./chunk-KNIVXUEF.js";
import "./chunk-IWFMEAQB.js";
import "./chunk-GFT2G5UO.js";
export {
install as CanvasRenderer,

View File

@ -453,7 +453,7 @@ import {
virtualizedScrollbarProps,
watermarkProps,
zIndexContextKey
} from "./chunk-QM5QNHIO.js";
} from "./chunk-IRFI6G53.js";
import {
genFileId,
uploadBaseProps,
@ -481,9 +481,9 @@ import "./chunk-LROEKXT5.js";
import "./chunk-O7KFMITO.js";
import "./chunk-YNRHTVZR.js";
import "./chunk-67TUTJCN.js";
import "./chunk-7BPWZNUD.js";
import "./chunk-ULX5FOVL.js";
import "./chunk-GTWINWNV.js";
import "./chunk-7BPWZNUD.js";
import "./chunk-GFT2G5UO.js";
var export_dayjs = import_dayjs.default;
export {

View File

@ -453,7 +453,7 @@ import {
virtualizedScrollbarProps,
watermarkProps,
zIndexContextKey
} from "./chunk-QM5QNHIO.js";
} from "./chunk-IRFI6G53.js";
import {
genFileId,
uploadBaseProps,
@ -481,9 +481,9 @@ import "./chunk-LROEKXT5.js";
import "./chunk-O7KFMITO.js";
import "./chunk-YNRHTVZR.js";
import "./chunk-67TUTJCN.js";
import "./chunk-7BPWZNUD.js";
import "./chunk-ULX5FOVL.js";
import "./chunk-GTWINWNV.js";
import "./chunk-7BPWZNUD.js";
import "./chunk-GFT2G5UO.js";
var export_dayjs = import_dayjs.default;
export {

View File

@ -1,6 +1,6 @@
import "./chunk-5MJQOEES.js";
import "./chunk-C47PGQGR.js";
import "./chunk-HODVN2HK.js";
import "./chunk-C47PGQGR.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-5TRUIT6X.js";
//# sourceMappingURL=element-plus_es_components_autocomplete_style_css.js.map

View File

@ -1,9 +1,9 @@
import "./chunk-HZRC7S76.js";
import "./chunk-NZR6SVVT.js";
import "./chunk-C47PGQGR.js";
import "./chunk-HODVN2HK.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-7EBAVFZW.js";
import "./chunk-HODVN2HK.js";
import "./chunk-C47PGQGR.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-VLWH5T2T.js";
import "./chunk-5TRUIT6X.js";
//# sourceMappingURL=element-plus_es_components_cascader_style_css.js.map

View File

@ -1,7 +1,7 @@
import "./chunk-THZSTYZP.js";
import "./chunk-DB6OWVVK.js";
import "./chunk-C47PGQGR.js";
import "./chunk-HODVN2HK.js";
import "./chunk-C47PGQGR.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-5TRUIT6X.js";
//# sourceMappingURL=element-plus_es_components_date-picker_style_css.js.map

View File

@ -2,8 +2,8 @@ import "./chunk-MC27QSJL.js";
import "./chunk-UUC56VKA.js";
import "./chunk-HGRMPKRI.js";
import "./chunk-NZR6SVVT.js";
import "./chunk-C47PGQGR.js";
import "./chunk-HODVN2HK.js";
import "./chunk-C47PGQGR.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-5TRUIT6X.js";

View File

@ -1,7 +1,7 @@
import "./chunk-OKMHUHKP.js";
import "./chunk-NZR6SVVT.js";
import "./chunk-C47PGQGR.js";
import "./chunk-HODVN2HK.js";
import "./chunk-C47PGQGR.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-5TRUIT6X.js";

View File

@ -1,7 +1,7 @@
import "./chunk-WTG273Z3.js";
import "./chunk-MGTDGDA4.js";
import "./chunk-C47PGQGR.js";
import "./chunk-T6DOWK6H.js";
import "./chunk-C47PGQGR.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-5TRUIT6X.js";
//# sourceMappingURL=element-plus_es_components_slider_style_css.js.map

View File

@ -1,7 +1,7 @@
import "./chunk-HODVN2HK.js";
import "./chunk-T6DOWK6H.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-7EBAVFZW.js";
import "./chunk-HODVN2HK.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-5TRUIT6X.js";
// node_modules/.pnpm/element-plus@2.9.1_vue@3.5.12_typescript@5.3.3_/node_modules/element-plus/es/components/table/style/css.mjs

View File

@ -1,6 +1,6 @@
import "./chunk-FRMPVTTW.js";
import "./chunk-C47PGQGR.js";
import "./chunk-HODVN2HK.js";
import "./chunk-C47PGQGR.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-5TRUIT6X.js";
//# sourceMappingURL=element-plus_es_components_time-picker_style_css.js.map

View File

@ -2,8 +2,8 @@ import "./chunk-MC27QSJL.js";
import "./chunk-UUC56VKA.js";
import "./chunk-HGRMPKRI.js";
import "./chunk-NZR6SVVT.js";
import "./chunk-C47PGQGR.js";
import "./chunk-HODVN2HK.js";
import "./chunk-C47PGQGR.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-5TRUIT6X.js";

View File

@ -1,7 +1,7 @@
import "./chunk-7EBAVFZW.js";
import "./chunk-JQ2IYF3Y.js";
import "./chunk-DB6OWVVK.js";
import "./chunk-C47PGQGR.js";
import "./chunk-JQ2IYF3Y.js";
import "./chunk-7EBAVFZW.js";
import "./chunk-5TRUIT6X.js";
// node_modules/.pnpm/element-plus@2.9.1_vue@3.5.12_typescript@5.3.3_/node_modules/element-plus/es/components/transfer/style/css.mjs

View File

@ -3,9 +3,9 @@ import "./chunk-MC27QSJL.js";
import "./chunk-UUC56VKA.js";
import "./chunk-HGRMPKRI.js";
import "./chunk-NZR6SVVT.js";
import "./chunk-7EBAVFZW.js";
import "./chunk-HODVN2HK.js";
import "./chunk-3N2VGZJ2.js";
import "./chunk-7EBAVFZW.js";
import "./chunk-5TRUIT6X.js";
// node_modules/.pnpm/element-plus@2.9.1_vue@3.5.12_typescript@5.3.3_/node_modules/element-plus/es/components/tree-select/style/css.mjs

View File

@ -1,3 +1,13 @@
import {
Hook,
UrlBuilder,
buildCSSItem,
buildJSItem,
loadJS,
noop,
walkTree,
wrapFunction
} from "./chunk-UGLGEV52.js";
import {
BinTrieFlags,
DecodingMode,
@ -11,16 +21,6 @@ import {
fromCodePoint,
replaceCodePoint
} from "./chunk-QK75OLN6.js";
import {
Hook,
UrlBuilder,
buildCSSItem,
buildJSItem,
loadJS,
noop,
walkTree,
wrapFunction
} from "./chunk-UGLGEV52.js";
import {
__commonJS,
__export,

View File

@ -6,6 +6,8 @@ export interface CirculateRecordVO {
customerId: number // 客户ID
type: number // 1:客户池2:公海池 3:线索池
typeId: number // 根据type来判断type=1则typeid是客户ID
creator: string // 创建人
createTime: string // 创建时间
}
// 流转记录 API

View File

@ -15,6 +15,10 @@ export const FollowLabelApi = {
return await request.get({ url: `/crm/follow-label/page`, params })
},
getFollowLabelList: async (params: any) => {
return await request.get({ url: `/crm/follow-label/list`, params })
},
// 查询跟进标签详情
getFollowLabel: async (id: number) => {
return await request.get({ url: `/crm/follow-label/get?id=` + id })

View File

@ -14,6 +14,10 @@ export const CustomerLabelApi = {
return await request.get({ url: `/crm/customer-label/page`, params })
},
getCustomerLabelList: async (params: any) => {
return await request.get({ url: `/crm/customer-label/list`, params })
},
// 查询客户标签详情
getCustomerLabel: async (id: number) => {
return await request.get({ url: `/crm/customer-label/get?id=` + id })

View File

@ -0,0 +1 @@

View File

@ -7,6 +7,7 @@ export interface FollwRecordVO {
content: string // 跟进内容
createTime: string // 创建时间
creator: string // 创建人
userId: string // 客户经理
}
// 跟进记录 API
@ -16,32 +17,8 @@ export const FollwRecordApi = {
return await request.get({ url: `/crm/follw-record/page`, params })
},
getFollwRecordList: async (params: any) => {
return await request.get({ url: `/crm/follw-record/list`, params })
},
// 查询跟进记录详情
getFollwRecord: async (id: number) => {
return await request.get({ url: `/crm/follw-record/get?id=` + id })
},
// 新增跟进记录
createFollwRecord: async (data: FollwRecordVO) => {
return await request.post({ url: `/crm/follw-record/create`, data })
},
// 修改跟进记录
updateFollwRecord: async (data: FollwRecordVO) => {
return await request.put({ url: `/crm/follw-record/update`, data })
},
// 删除跟进记录
deleteFollwRecord: async (id: number) => {
return await request.delete({ url: `/crm/follw-record/delete?id=` + id })
},
// 导出跟进记录 Excel
exportFollwRecord: async (params) => {
return await request.download({ url: `/crm/follw-record/export-excel`, params })
}
}

112
src/store/modules/crm.ts Normal file
View File

@ -0,0 +1,112 @@
import { defineStore } from 'pinia'
import { ImportLevelApi, type ImportLevelVO } from '@/api/crm/config/Importlevel'
import { ChannelApi, type ChannelVO } from '@/api/crm/config/channel'
import { FollowStatusApi, type FollowStatusVO } from '@/api/crm/config/followstatus'
import { CustomerSourceApi } from '@/api/crm/config/customersource'
import { CustomerTypeApi } from '@/api/crm/config/customertype'
import { OpenSeaApi, type OpenSeaVO } from '@/api/crm/opensea/index'
import { CustomerLabelApi } from '@/api/crm/config/customerlabel'
import { FollowLabelApi } from '@/api/crm/config/Followlabel'
import * as UserApi from '@/api/system/user'
import * as DepApi from '@/api/system/dept'
import { UserVO } from '@/api/system/user'
import { DeptVO } from '@/api/system/dept'
export const useCrmStore = defineStore('crm', {
state: () => ({
importLevelList: [] as ImportLevelVO[],
channelList: [] as ChannelVO[],
followStatusList: [] as FollowStatusVO[],
customerSourceList: [] as any[],
customerTypeList: [] as any[],
userList: [] as UserVO[],
depList: [] as DeptVO[],
openSeaList: [] as OpenSeaVO[],
customerLabelList: [] as any[],
followLabelList: [] as any[]
}),
actions: {
async getImportLevelList() {
if (this.importLevelList.length > 0) {
return this.importLevelList
}
const data = await ImportLevelApi.getImportLevelList(null)
this.importLevelList = data
return this.importLevelList
},
async getChannelList() {
if (this.channelList.length > 0) {
return this.channelList
}
const data = await ChannelApi.getChannelList(null)
this.channelList = data
return this.channelList
},
async getFollowStatusList() {
if (this.followStatusList.length > 0) {
return this.followStatusList
}
const data = await FollowStatusApi.getFollowStatusList(null)
this.followStatusList = data
return this.followStatusList
},
async getCustomerSourceList() {
if (this.customerSourceList.length > 0) {
return this.customerSourceList
}
const data = await CustomerSourceApi.getCustomerSourceList(null)
this.customerSourceList = data
return this.customerSourceList
},
async getCustomerTypeList() {
if (this.customerTypeList.length > 0) {
return this.customerTypeList
}
const data = await CustomerTypeApi.getCustomerTypeList(null)
this.customerTypeList = data
return this.customerTypeList
},
async getUserList() {
const data = await UserApi.getSimpleUsertList()
this.userList = data
return this.userList
},
async getDepList() {
const data = await DepApi.getSimpleDeptList()
this.depList = data
return this.depList
},
async getDepListByCurUser() {
const data = await DepApi.getDeptListByCurUser()
this.depList = data
return this.depList
},
async getOpenSeaList() {
const data = await OpenSeaApi.getOpenSeaList(null)
this.openSeaList = data
return this.openSeaList
},
async getOpenSeaListByCurUser() {
const data = await OpenSeaApi.getOpenSeaListByCurUser()
this.openSeaList = data
return this.openSeaList
},
async getCustomerLabelList() {
if (this.customerLabelList.length > 0) {
return this.customerLabelList
}
const data = await CustomerLabelApi.getCustomerLabelList(null)
this.customerLabelList = data
return this.customerLabelList
},
async getFollowLabelList() {
if (this.followLabelList.length > 0) {
return this.followLabelList
}
const data = await FollowLabelApi.getFollowLabelList(null)
this.followLabelList = data
return this.followLabelList
}
}
})

View File

@ -7,6 +7,11 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
2: typeof import('./../views/crm/customer/dep/tables/all copy 2.vue')['default']
3: typeof import('./../views/crm/customer/dep/tables/all copy 3.vue')['default']
4: typeof import('./../views/crm/customer/dep/tables/all copy 4.vue')['default']
5: typeof import('./../views/crm/customer/my/tables/all copy 5.vue')['default']
All: typeof import('./../views/crm/customer/my/tables/all.vue')['default']
Allcustomer: typeof import('./../views/crm/customer/customerlist/allcustomer/index.vue')['default']
Allocate: typeof import('./../api/crm/allocate/index.ts')['default']
AllocateForm: typeof import('./../views/crm/components/Allocate/AllocateForm.vue')['default']
@ -29,20 +34,23 @@ declare module 'vue' {
ContentDetailWrap: typeof import('./../components/ContentDetailWrap/src/ContentDetailWrap.vue')['default']
ContentWrap: typeof import('./../components/ContentWrap/src/ContentWrap.vue')['default']
CopperModal: typeof import('./../components/Cropper/src/CopperModal.vue')['default']
copy: typeof import('./../views/crm/opensea/customer/myopensea copy.vue')['default']
copy: typeof import('./../views/crm/components/Customer/Infor copy.vue')['default']
CopyTaskNode: typeof import('./../components/SimpleProcessDesignerV2/src/nodes/CopyTaskNode.vue')['default']
CopyTaskNodeConfig: typeof import('./../components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue')['default']
CountTo: typeof import('./../components/CountTo/src/CountTo.vue')['default']
Crm: typeof import('./../router/modules/crm.ts')['default']
Crm: typeof import('./../store/modules/crm.ts')['default']
Crontab: typeof import('./../components/Crontab/src/Crontab.vue')['default']
Cropper: typeof import('./../components/Cropper/src/Cropper.vue')['default']
CropperAvatar: typeof import('./../components/Cropper/src/CropperAvatar.vue')['default']
Cule: typeof import('./../views/crm/customer/cule/index.vue')['default']
Culecustomer: typeof import('./../api/crm/customer/culecustomer/index.ts')['default']
Customer: typeof import('./../api/crm/customer/customer/index.ts')['default']
CustomerBaseInfo: typeof import('./../views/crm/components/CustomerDetail/customerBaseInfor/CustomerBaseInfo.vue')['default']
CustomerDetail: typeof import('./../views/crm/components/CustomerDetail/CustomerDetail.vue')['default']
Customer: typeof import('./../api/crm/customer/customer.ts')['default']
CustomerBaseInfo: typeof import('./../views/crm/components/CustomerDetail/CustomerInfor/CustomerBaseInfo.vue')['default']
CustomerBaseInfo_1: typeof import('./../views/crm/components/Customer/CustomerInfor/CustomerBaseInfo_1.vue')['default']
CustomerDetail: typeof import('./../views/crm/components/Customer/CustomerDetail.vue')['default']
CustomerInfor: typeof import('./../views/crm/components/Customer/CustomerInfor/CustomerInfor.vue')['default']
CustomerInforForm: typeof import('./../views/crm/customer/opensea/CustomerInforForm.vue')['default']
Customerlabels: typeof import('./../api/crm/config/customerlabels.ts')['default']
Data: typeof import('./../views/crm/data/upload/data/index.vue')['default']
DelayTimerNode: typeof import('./../components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue')['default']
DelayTimerNodeConfig: typeof import('./../components/SimpleProcessDesignerV2/src/nodes-config/DelayTimerNodeConfig.vue')['default']
@ -50,7 +58,7 @@ declare module 'vue' {
Depcustomer: typeof import('./../views/crm/customer/customerlist/depcustomer/index.vue')['default']
Descriptions: typeof import('./../components/Descriptions/src/Descriptions.vue')['default']
DescriptionsItemLabel: typeof import('./../components/Descriptions/src/DescriptionsItemLabel.vue')['default']
Detail: typeof import('./../views/crm/data/upload/detail/index.vue')['default']
Detail: typeof import('./../views/crm/components/Customer/Detail.vue')['default']
Dialog: typeof import('./../components/Dialog/src/Dialog.vue')['default']
DictSelect: typeof import('./../components/FormCreate/src/components/DictSelect.vue')['default']
DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default']
@ -64,10 +72,12 @@ declare module 'vue' {
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton']
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
ElCard: typeof import('element-plus/es')['ElCard']
ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCheckTag: typeof import('element-plus/es')['ElCheckTag']
ElCol: typeof import('element-plus/es')['ElCol']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
@ -84,6 +94,7 @@ declare module 'vue' {
ElementOtherConfig: typeof import('./../components/bpmnProcessDesigner/package/penal/other/ElementOtherConfig.vue')['default']
ElementProperties: typeof import('./../components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue')['default']
ElementTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/ElementTask.vue')['default']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
@ -112,6 +123,7 @@ declare module 'vue' {
ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
@ -121,6 +133,7 @@ declare module 'vue' {
ExclusiveNode: typeof import('./../components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue')['default']
FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default']
Follow: typeof import('./../views/crm/follow/index.vue')['default']
FollowRecord: typeof import('./../views/crm/components/Customer/FollowRecord.vue')['default']
Follw: typeof import('./../api/crm/follw/index.ts')['default']
Follwrecord: typeof import('./../api/crm/follw/follwrecord/index.ts')['default']
Form: typeof import('./../components/Form/src/Form.vue')['default']
@ -129,7 +142,9 @@ declare module 'vue' {
IconSelect: typeof import('./../components/Icon/src/IconSelect.vue')['default']
IFrame: typeof import('./../components/IFrame/src/IFrame.vue')['default']
ImageViewer: typeof import('./../components/ImageViewer/src/ImageViewer.vue')['default']
Import: typeof import('./../views/crm/customer/my/tables/import.vue')['default']
InclusiveNode: typeof import('./../components/SimpleProcessDesignerV2/src/nodes/InclusiveNode.vue')['default']
Infor: typeof import('./../views/crm/components/Customer/Infor.vue')['default']
Infotip: typeof import('./../components/Infotip/src/Infotip.vue')['default']
InputPassword: typeof import('./../components/InputPassword/src/InputPassword.vue')['default']
InputWithColor: typeof import('./../components/InputWithColor/index.vue')['default']
@ -142,7 +157,7 @@ declare module 'vue' {
NodeHandler: typeof import('./../components/SimpleProcessDesignerV2/src/NodeHandler.vue')['default']
Opeansea: typeof import('../api/crm/opensea')['default']
OpenDepForm: typeof import('./../views/crm/opensea/opensea/OpenDepForm.vue')['default']
Opensea: typeof import('./../api/crm/opensea/index.ts')['default']
Opensea: typeof import('./../views/crm/customer/opensea/index.vue')['default']
OpenseaCustomerForm: typeof import('./../views/crm/customer/customerlist/openseacustomer/OpenseaCustomerForm.vue')['default']
OpenSeaForm: typeof import('./../views/crm/opensea/opensea/OpenSeaForm.vue')['default']
OperateLogV2: typeof import('./../components/OperateLogV2/src/OperateLogV2.vue')['default']
@ -156,6 +171,7 @@ declare module 'vue' {
ProcessViewer: typeof import('./../components/bpmnProcessDesigner/package/designer/ProcessViewer.vue')['default']
PropertiesPanel: typeof import('./../components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue')['default']
Qrcode: typeof import('./../components/Qrcode/src/Qrcode.vue')['default']
QuickFollow: typeof import('./../views/crm/components/Customer/QuickFollow.vue')['default']
ReceiveTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/task-components/ReceiveTask.vue')['default']
Repeat: typeof import('./../api/crm/repeat/index.ts')['default']
RouterLink: typeof import('vue-router')['RouterLink']
@ -164,6 +180,7 @@ declare module 'vue' {
ScriptTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/task-components/ScriptTask.vue')['default']
Search: typeof import('./../components/Search/src/Search.vue')['default']
ServiceTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/task-components/ServiceTask.vue')['default']
Seven: typeof import('./../views/crm/customer/my/tables/seven.vue')['default']
ShortcutDateRangePicker: typeof import('./../components/ShortcutDateRangePicker/index.vue')['default']
SignalAndMessage: typeof import('./../components/bpmnProcessDesigner/package/penal/signal-message/SignalAndMessage.vue')['default']
SimpleProcessDesigner: typeof import('./../components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue')['default']
@ -174,11 +191,18 @@ declare module 'vue' {
Sticky: typeof import('./../components/Sticky/src/Sticky.vue')['default']
SummaryCard: typeof import('./../components/SummaryCard/index.vue')['default']
Table: typeof import('./../components/Table/src/Table.vue')['default']
Tables: typeof import('./../views/crm/customer/dep/tables/index.vue')['default']
TableSelectForm: typeof import('./../components/Table/src/TableSelectForm.vue')['default']
Tabs: typeof import('./../views/crm/customer/my/tabs.vue')['default']
Team: typeof import('./../api/crm/team/index.ts')['default']
Teamcustomer: typeof import('../api/crm/customer/team')['default']
Today: typeof import('./../views/crm/customer/my/tables/today.vue')['default']
Tody: typeof import('./../views/crm/customer/team/tables/tody.vue')['default']
Tooltip: typeof import('./../components/Tooltip/src/Tooltip.vue')['default']
TransferForm: typeof import('./../views/crm/components/Transfer/TransferForm.vue')['default']
TransferRecord: typeof import('./../views/crm/components/Customer/TransferRecord.vue')['default']
Types: typeof import('./../api/system/dept/types.ts')['default']
Unfollow: typeof import('./../views/crm/customer/my/tables/unfollow.vue')['default']
UploadFile: typeof import('./../components/UploadFile/src/UploadFile.vue')['default']
UploadImg: typeof import('./../components/UploadFile/src/UploadImg.vue')['default']
UploadImgs: typeof import('./../components/UploadFile/src/UploadImgs.vue')['default']
@ -196,6 +220,7 @@ declare module 'vue' {
XTextButton: typeof import('./../components/XButton/src/XTextButton.vue')['default']
}
export interface ComponentCustomProperties {
vInfiniteScroll: typeof import('element-plus/es')['ElInfiniteScroll']
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}

View File

@ -128,7 +128,7 @@ const submitForm = async () => {
customerIds: formData.value.customerIds
}
await CustomerInforApi.allocate(data)
message.success(t('common.updateSuccess'))
message.success('分配成功')
dialogVisible.value = false
emit('success')//
} catch (error) {

View File

@ -0,0 +1,196 @@
<template>
<ContentWrap>
<!-- 顶部标题和操作按钮区域 -->
<div class="flex justify-between mb-2">
<div class="flex items-center">
<h2 class="text-xl font-bold mr-10px min-w-[100px]" >{{ customerForm.customerName }}({{ customerForm.mobile }})</h2>
<span class="ml-2px text-sm">撞库次数: {{ customerForm.repeatCount }}</span>
<span class="text-sm ml-2">跟进次数: {{ customerForm.followCount }}</span>
</div>
<div class="flex">
<el-button size="small" class="mr-1" :disabled="!props.prevId" @click="emit('prev')">上一条</el-button>
<el-button size="small" :disabled="!props.nextId" @click="emit('next')">下一条</el-button>
</div>
</div>
<!-- 操作按钮栏 -->
<div class="mb-4">
<el-button size="small" class="mr-1" type="primary" plain @click="openAllocateForm"><Icon icon="ep:plus" class="mr-5px" />分配</el-button>
<el-button size="small" class="mr-1" type="primary" plain @click="handleReceive"><Icon icon="ep:plus" class="mr-5px" />领取</el-button>
<el-button size="small" class="mr-1" type="warning" plain @click="openTransferForm"><Icon icon="ep:share" class="mr-5px" />转公海</el-button>
</div>
<!-- 进度导航 -->
<div class="mb-4">
<el-steps :space="200" :active="3" simple>
<el-step title="线索" />
<el-step title="跟进中" />
<el-step title="已邀约" />
<el-step title="已上门" />
<el-step title="已签约" />
<el-step title="办理中" />
<el-step title="已完成" />
</el-steps>
</div>
<!-- 标签页和时间轴布局 -->
<div class="flex gap-4">
<!-- 左侧标签页 -->
<div class="w-[300px]">
<el-tabs>
<el-tab-pane label="跟进记录">
<FollowRecord :customer-id="props.customerId" />
</el-tab-pane>
<el-tab-pane label="流转记录">
<TransferRecord :customer-id="props.customerId" />
</el-tab-pane>
</el-tabs>
</div>
<!-- 右侧跟进记录 -->
<div class="rightContent w-[calc(100%-300px)]">
<el-tabs>
<el-tab-pane label="客户资料">
<div class="mt-4">
<QuickFollow />
<Infor />
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</ContentWrap>
<!-- 分配弹窗 -->
<AllocateForm ref="formRef" />
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" />
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import Infor from './Infor.vue'
import QuickFollow from './QuickFollow.vue'
import FollowRecord from './FollowRecord.vue'
import TransferRecord from './TransferRecord.vue'
import { CustomerInforApi } from '@/api/crm/customer/customer'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import { useMessage } from '@/hooks/web/useMessage'
const message = useMessage() //
const props = defineProps<{
customerId?: number
prevId?: number
nextId?: number
}>()
const emit = defineEmits(['prev', 'next'])
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
//
const customerForm = ref({
id: undefined as number | undefined,
sex: 1,
age: undefined as string | undefined,
customerName: '',
expectAmount: undefined as string | undefined,
city: '',
customerLevel: 0,
customerType: '潜在',
followContent: '',
mobile: '',
remark: '',
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
ownerUserId: undefined,
depId: undefined,
contactLastTime: undefined,
contactLastContent: undefined,
createTime: undefined,
repeatCount: 0,
followCount: 0
})
//
const loadCustomerData = async (id?: number) => {
if (id) {
try {
//
const data = await CustomerInforApi.getCustomer(id)
//
Object.assign(customerForm.value, data)
} catch (error) {
message.error('获取失败')
}
}
}
// customerId
watch(() => props.customerId, (newId) => {
loadCustomerData(newId)
}, { immediate: true })
//
const provide = {
customerForm
}
/** 打开分配弹窗 */
const openAllocateForm = () => {
const customerIds = [props.customerId]
const depId =0 // depId
formRef.value.open(customerIds, depId)
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
const customerIds = [props.customerId]
transferFormRef.value.open(customerIds)
}
/** 领取客户 */
const handleReceive = async () => {
try {
const customerIds = [props.customerId]
await CustomerInforApi.receive({ customerIds: customerIds.map(String) })
message.success('领取成功')
} catch (error) {
message.error('领取失败')
}
}
</script>
<style scoped>
.el-timeline {
padding-right: 10px;
max-height: calc(100vh - 240px);
overflow-y: auto;
}
:deep(.el-step__icon) {
display: none !important;
}
:deep(.el-step.is-simple .el-step__title) {
font-size: 14px;
line-height: 20px;
}
:deep(.el-steps--simple) {
padding: 5px 25px;
}
:deep(.el-step.is-simple .el-step__arrow) {
transform: scale(0.6);
}
.rightContent {
overflow: auto;
}
</style>

View File

@ -0,0 +1,102 @@
<template>
<div class="mt-4">
<el-timeline v-if="followRecordList.length > 0" v-infinite-scroll="loadMore" :infinite-scroll-disabled="disabled">
<el-timeline-item v-for="item in followRecordList" :key="item.id">
<div>{{ item.content }}</div>
<div class="flex flex-col gap-1 text-gray-400 text-sm mt-1">
<span>客户经理{{ userList.find((user) => user.id === Number(item.userId))?.nickname || '-' }}</span>
<span>跟进时间{{ formatDate(new Date(Number(item.createTime))) }}</span>
</div>
</el-timeline-item>
<div v-if="loading" class="text-center py-4">加载中...</div>
<div v-if="finished" class="text-center text-gray-400 py-4">没有更多了</div>
</el-timeline>
<div v-else class="text-center text-gray-400 py-4">暂无跟进记录</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import { formatDate } from '@/utils/formatTime'
import { FollwRecordApi, FollwRecordVO } from '@/api/crm/follw'
import { useMessage } from '@/hooks/web/useMessage'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
defineOptions({ name: 'FollowRecord' })
const message = useMessage() //
// 使 CRM store
const crmStore = useCrmStore()
const { userList } = storeToRefs(crmStore)
const props = defineProps<{
customerId?: number
}>()
const followRecordList = ref<FollwRecordVO[]>([])
const pageNo = ref(1)
const pageSize = ref(10)
const total = ref(0)
const loading = ref(false)
const finished = ref(false)
const disabled = computed(() => loading.value || finished.value)
//
const loadData = async () => {
if (!props.customerId || loading.value) return
loading.value = true
try {
const res = await FollwRecordApi.getFollwRecordPage({
customerId: props.customerId,
pageNo: pageNo.value,
pageSize: pageSize.value
})
if (pageNo.value === 1) {
followRecordList.value = res.list || []
} else {
followRecordList.value.push(...(res.list || []))
}
total.value = res.total || 0
//
if (followRecordList.value.length >= total.value) {
finished.value = true
}
} catch (error: any) {
message.error('获取跟进记录失败')
} finally {
loading.value = false
}
}
//
const loadMore = async () => {
if (finished.value) return
pageNo.value++
await loadData()
}
// customerId
watch(() => props.customerId, async (id) => {
if (id) {
pageNo.value = 1
finished.value = false
//
await crmStore.getUserList()
await loadData()
}
}, { immediate: true })
</script>
<style scoped>
.el-timeline {
padding-right: 10px;
max-height: calc(100vh - 240px);
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,148 @@
<template>
<div class="flex justify-between items-center mb-2 bg-gray-50 px-4 py-2 rounded-md border border-gray-100">
<div class="text-xs font-medium text-gray-700 flex items-center">
<div class="w-1 h-3.5 bg-blue-500 rounded-full mr-2"></div>
基本信息
</div>
<el-button type="primary" size="small" class="text-xs !px-3 !py-1.5">保存</el-button>
</div>
<el-form :model="form" label-width="80px" class="bg-white p-4 rounded-lg text-xs">
<!-- 选择器组 -->
<div class="flex items-center gap-6 mb-3 pb-2 border-b border-gray-100">
<el-form-item label-class="text-gray-600 text-xs" label="客户姓名" prop="customerName" class="!mb-0 !text-xs" >
<el-input
v-model="form.customerName"
placeholder="请输入"
class="!w-[120px]"
size="small"
/>
</el-form-item>
<el-form-item label-class="text-gray-600 text-xs" label="年龄" prop="age" class="!mb-0 !text-xs" >
<el-input
v-model="form.age"
placeholder="请输入"
class="!w-[120px]"
size="small"
/>
</el-form-item>
<el-form-item label-class="text-gray-600 text-xs" label="性别" prop="sex" class="!mb-0 !text-xs" >
<el-select
v-model="form.sex"
placeholder="请选择"
class="!w-[120px]"
size="small"
>
<el-option label="未知" value="0" class="!text-xs" />
<el-option label="男" value="1" class="!text-xs" />
<el-option label="女" value="2" class="!text-xs" />
</el-select>
</el-form-item>
</div>
<div class="flex items-center gap-6 mb-3 pb-2 border-b border-gray-100">
<el-form-item label-class="text-gray-600 text-xs" label="期望金额" prop="expectAmount" class="!mb-0 !text-xs" >
<el-input
v-model="form.expectAmount"
placeholder="请输入"
class="!w-[120px]"
size="small"
/>
</el-form-item>
<el-form-item label-class="text-gray-600 text-xs" label="居住地址" prop="adress" class="!mb-0 !text-xs" >
<el-input
v-model="form.adress"
placeholder="请输入"
class="!w-[350px]"
size="small"
/>
</el-form-item>
</div>
<!-- 备注 -->
<div class="flex items-start">
<el-form-item label-class="text-gray-600 text-xs" label="客户信息" class="flex-1 !text-xs">
<el-input
v-model="form.remark"
type="textarea"
:rows="3"
placeholder=""
resize="none"
size="small"
class="[&_.el-textarea__inner]:!text-xs"
/>
</el-form-item>
</div>
</el-form>
</template>
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
import { buildTree } from '@/utils/tree'
const props = defineProps<{
customerId?: number
}>()
// CRM store
const crmStore = useCrmStore()
const { importLevelList, customerTypeList, customerLabelList, followLabelList } = storeToRefs(crmStore)
const customerTypeOptions = ref<any>()
// cascader
const cascaderProps = {
emitPath: false
}
const form = ref({
sex: 1,
age: undefined as string | undefined,
customerName: '',
expectAmount: undefined as string | undefined,
adress: '',
remark: '',
})
// customerId
watch(() => props.customerId, async (id) => {
if (id) {
try {
// API
// const data = await CustomerApi.getCustomerFollowInfo(id)
// Object.assign(form.value, data)
} catch (error) {
console.error('Failed to fetch customer follow info:', error)
}
}
}, { immediate: true })
//
onMounted(async () => {
try {
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getCustomerTypeList(),
crmStore.getCustomerLabelList(),
crmStore.getFollowLabelList()
])
//
customerTypeOptions.value = buildTree(customerTypeList.value)
} catch (error) {
console.error('Failed to initialize data:', error)
}
})
//
const handleSave = () => {
// TODO:
}
</script>

View File

@ -0,0 +1,256 @@
<template>
<div class="flex justify-between items-center mb-2 bg-gray-50 px-4 py-2 rounded-md border border-gray-100">
<div class="text-xs font-medium text-gray-700 flex items-center">
<div class="w-1 h-3.5 bg-blue-500 rounded-full mr-2"></div>
快捷输入
</div>
<el-button type="primary" size="small" class="text-xs !px-3 !py-1.5">保存</el-button>
</div>
<el-form :model="form" label-width="80px" class="bg-white p-4 rounded-lg text-xs">
<!-- 开关选项组 -->
<div class="flex items-center gap-4 mb-3 pb-2 border-b border-gray-100">
<el-form-item label="车" class="!mb-0 !text-xs">
<el-switch v-model="form.haveCar" size="small" />
</el-form-item>
<el-form-item label="房" class="!mb-0 !text-xs">
<el-switch v-model="form.haveHouse" size="small" />
</el-form-item>
<el-form-item label="社保" class="!mb-0 !text-xs">
<el-switch v-model="form.haveSocialSecurity" size="small" />
</el-form-item>
<el-form-item label="公积" class="!mb-0 !text-xs">
<el-switch v-model="form.haveProvidentFund" size="small" />
</el-form-item>
<el-form-item label="保单" class="!mb-0 !text-xs">
<el-switch v-model="form.haveGuaranteeSlip" size="small" />
</el-form-item>
</div>
<!-- 选择器组 -->
<div class="flex items-center gap-6 mb-3 pb-2 border-b border-gray-100">
<el-form-item label-class="text-gray-600 text-xs" label="客户类型" prop="customerTypeId" class="!mb-0 !text-xs">
<el-cascader
v-model="form.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
placeholder="请选择"
class="!w-[150px]"
size="small"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId" class="!mb-0 !text-xs">
<el-select
v-model="form.importLevelId"
placeholder="请选择"
clearable
class="!w-[150px]"
size="small"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
class="!text-xs"
/>
</el-select>
</el-form-item>
<el-form-item label="重点客户" class="!mb-0 !text-xs">
<el-switch v-model="form.haveGuaranteeSlip" size="small" />
</el-form-item>
</div>
<div class="flex items-center gap-6 mb-3 pb-2 border-b border-gray-100">
<el-form-item label-class="text-gray-600 text-xs" label="下次跟进" prop="nextFollowTime" class="!mb-0 !text-xs">
<div class="flex items-center gap-2">
<el-date-picker
v-model="form.nextFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetime"
placeholder="请选择时间"
class="!w-[150px]"
size="small"
/>
<el-button
type="danger"
size="small"
class="!text-xs"
plain
@click="setNextFollowTime(15)"
>15分钟</el-button>
<el-button
type="danger"
size="small"
class="!text-xs"
plain
@click="setNextFollowTime(30)"
>30分钟</el-button>
<el-button
type="danger"
size="small"
class="!text-xs"
plain
@click="setNextFollowTime(60)"
>1小时</el-button>
<el-button
type="danger"
size="small"
class="!text-xs"
plain
@click="setNextFollowTime(1440)"
>1天后</el-button>
<el-button
type="danger"
size="small"
class="!text-xs"
plain
@click="setNextFollowTime(4320)"
>3天后</el-button>
</div>
</el-form-item>
</div>
<!-- 标签组 -->
<div class="mb-3 pb-2 border-b border-gray-100">
<el-form-item label-class="text-gray-600 text-xs" label="客户标签" class="!mb-0 !text-xs">
<div class="flex flex-wrap gap-1.5">
<el-check-tag
v-for="label in customerLabelList"
:key="label.id"
:modelValue="form.labelIds?.includes(label.id)"
@change="(checked) => handleLabelChange(label.id, checked)"
class="!cursor-pointer hover:!border-primary !text-xs !py-0 !px-1.5"
>
{{ label.name }}
</el-check-tag>
</div>
</el-form-item>
</div>
<!-- 备注 -->
<div class="flex items-start">
<el-form-item label-class="text-gray-600 text-xs" label="跟进备注" class="flex-1 !text-xs">
<el-input
v-model="form.content"
type="textarea"
:rows="3"
placeholder="请输入跟进备注"
resize="none"
size="small"
class="[&_.el-textarea__inner]:!text-xs"
/>
</el-form-item>
</div>
<div class="flex items-center">
<label class="text-gray-600 w-[80px] text-xs"></label>
<div class="flex flex-wrap gap-1.5">
<el-check-tag
v-for="label in followLabelList"
:key="label.id"
:modelValue="form.labelIds?.includes(label.id)"
@change="(checked) => handleLabelChange(label.id, checked)"
class="!cursor-pointer hover:!border-primary !text-xs !py-0 !px-1.5"
>
{{ label.name }}
</el-check-tag>
</div>
</div>
</el-form>
</template>
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
import { buildTree } from '@/utils/tree'
import dayjs from 'dayjs'
const props = defineProps<{
customerId?: number
}>()
// CRM store
const crmStore = useCrmStore()
const { importLevelList, customerTypeList, customerLabelList, followLabelList } = storeToRefs(crmStore)
const customerTypeOptions = ref<any>()
// cascader
const cascaderProps = {
emitPath: false
}
const form = ref({
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerTypeId: undefined,
importLevelId: undefined,
labelIds: [] as number[],
content: undefined,
nextFollowTime: undefined as string | undefined
})
// customerId
watch(() => props.customerId, async (id) => {
if (id) {
try {
// API
// const data = await CustomerApi.getCustomerFollowInfo(id)
// Object.assign(form.value, data)
} catch (error) {
console.error('Failed to fetch customer follow info:', error)
}
}
}, { immediate: true })
//
onMounted(async () => {
try {
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getCustomerTypeList(),
crmStore.getCustomerLabelList(),
crmStore.getFollowLabelList()
])
//
customerTypeOptions.value = buildTree(customerTypeList.value)
} catch (error) {
console.error('Failed to initialize data:', error)
}
})
//
const handleLabelChange = (labelId: number, checked: boolean) => {
if (checked) {
form.value.labelIds.push(labelId)
} else {
const index = form.value.labelIds.indexOf(labelId)
if (index !== -1) {
form.value.labelIds.splice(index, 1)
}
}
}
//
const setNextFollowTime = (minutes: number) => {
form.value.nextFollowTime = dayjs().add(minutes, 'minute').format('YYYY-MM-DD HH:mm:ss')
}
//
const handleSave = () => {
// TODO:
}
</script>

View File

@ -0,0 +1,157 @@
<template>
<div class="transfer-record-container">
<el-timeline
v-if="transferRecordList.length > 0"
v-infinite-scroll="loadMore"
:infinite-scroll-disabled="disabled"
:infinite-scroll-immediate="false"
:infinite-scroll-distance="50"
class="timeline-wrapper"
>
<el-timeline-item v-for="item in transferRecordList" :key="item.id">
<div class="flex flex-col gap-2">
<div class="flex items-center">
<span class="text-gray-500 w-[80px]">数据位置</span>
<span>{{ getDictLabel(DICT_TYPE.CRM_CUSTOMER_DATA_BELONG, item.type) }}</span>
</div>
<div class="flex items-center">
<span class="text-gray-500 w-[80px]">数据归属</span>
<template v-if="item.type === 1">
<span>{{ userList.find(user => user.id === Number(item.typeId))?.nickname || '-' }}</span>
</template>
<template v-if="item.type === 2">
<span>{{ openSeaList.find(openSea => openSea.id === Number(item.typeId))?.name || '-' }}</span>
</template>
</div>
<div class="flex items-center">
<span class="text-gray-500 w-[80px]">操作时间</span>
<span>{{ formatDate(new Date(Number(item.createTime))) }}</span>
</div>
</div>
</el-timeline-item>
<div v-if="loading" class="text-center py-4">加载中...</div>
<div v-if="finished" class="text-center text-gray-400 py-4">没有更多了</div>
</el-timeline>
<div v-else class="text-center text-gray-400 py-4">暂无流转记录</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import { formatDate } from '@/utils/formatTime'
import { CirculateRecordApi, CirculateRecordVO } from '@/api/crm/circulate'
import { useMessage } from '@/hooks/web/useMessage'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
defineOptions({ name: 'TransferRecord' })
const message = useMessage() //
// 使 CRM store
const crmStore = useCrmStore()
const { userList, openSeaList } = storeToRefs(crmStore)
const props = defineProps<{
customerId?: number
}>()
const transferRecordList = ref<CirculateRecordVO[]>([])
const pageNo = ref(1)
const pageSize = ref(10)
const total = ref(0)
const loading = ref(false)
const finished = ref(false)
const disabled = computed(() => {
return loading.value || finished.value || !props.customerId
})
//
const loadData = async () => {
if (!props.customerId || loading.value) return
loading.value = true
try {
//
if (pageNo.value === 1) {
await Promise.all([
crmStore.getUserList(),
crmStore.getOpenSeaList()
])
}
const res = await CirculateRecordApi.getCirculateRecordPage({
customerId: props.customerId,
pageNo: pageNo.value,
pageSize: pageSize.value
})
const list = res.list || []
total.value = res.total || 0
if (pageNo.value === 1) {
transferRecordList.value = list
} else {
transferRecordList.value.push(...list)
}
//
finished.value = transferRecordList.value.length >= total.value || list.length < pageSize.value
} catch (error: any) {
message.error('获取流转记录失败')
finished.value = true
} finally {
loading.value = false
}
}
//
const loadMore = async () => {
if (loading.value || finished.value) return
pageNo.value++
await loadData()
}
// customerId
watch(() => props.customerId, async (id) => {
if (id) {
pageNo.value = 1
total.value = 0
finished.value = false
transferRecordList.value = []
await loadData()
} else {
transferRecordList.value = []
finished.value = true
}
}, { immediate: true })
</script>
<style scoped>
.transfer-record-container {
height: calc(100vh - 240px);
overflow: hidden;
}
.timeline-wrapper {
height: 100%;
padding-right: 10px;
overflow-y: auto;
scrollbar-width: thin;
}
.timeline-wrapper::-webkit-scrollbar {
width: 6px;
}
.timeline-wrapper::-webkit-scrollbar-thumb {
background-color: #dcdfe6;
border-radius: 3px;
}
.timeline-wrapper::-webkit-scrollbar-track {
background-color: transparent;
}
</style>

View File

@ -1,172 +0,0 @@
<template>
<ContentWrap>
<!-- 顶部标题和操作按钮区域 -->
<div class="flex justify-between mb-2">
<div class="flex items-center">
<h2 class="text-xl font-bold mr-2">{{ customerForm.customerName }}</h2>
<el-tag size="small">撞库次数: 0</el-tag>
<el-tag size="small" class="ml-2">跟进次数: 0</el-tag>
</div>
<div class="flex">
<el-button size="small" class="mr-1">上一条</el-button>
<el-button size="small">下一条</el-button>
</div>
</div>
<!-- 操作按钮栏 -->
<div class="mb-4">
<el-button size="small" class="mr-1">分配客户</el-button>
<el-button size="small" class="mr-1">领取客户</el-button>
<el-button size="small" class="mr-1">退回公海</el-button>
</div>
<!-- 进度导航 -->
<div class="progress-nav mb-4">
<el-steps :space="200" :active="1" simple>
<el-step title="线索" />
<el-step title="跟进中" />
<el-step title="已邀约" />
<el-step title="已上门" />
<el-step title="已签约" />
<el-step title="办理中" />
<el-step title="已完成" />
</el-steps>
</div>
<!-- 标签页和时间轴布局 -->
<div class="flex gap-4">
<!-- 左侧标签页 -->
<div class="flex-1">
<el-tabs>
<el-tab-pane label="客户资料">
<div class="mt-4">
<CustomerBaseInfo />
</div>
</el-tab-pane>
<el-tab-pane label="客户资质">
<div class="mt-4">
<h3 class="text-lg font-bold mb-4">客户资质</h3>
</div>
</el-tab-pane>
<el-tab-pane label="邀约记录0">
<div class="mt-4">
<h3 class="text-lg font-bold mb-4">邀约记录</h3>
</div>
</el-tab-pane>
<el-tab-pane label="上门记录0">
<div class="mt-4">
<h3 class="text-lg font-bold mb-4">上门记录</h3>
</div>
</el-tab-pane>
<el-tab-pane label="签约记录0">
<div class="mt-4">
<h3 class="text-lg font-bold mb-4">签约记录</h3>
</div>
</el-tab-pane>
<el-tab-pane label="进件记录0">
<div class="mt-4">
<h3 class="text-lg font-bold mb-4">进件记录</h3>
</div>
</el-tab-pane>
<el-tab-pane label="撞库记录0">
<div class="mt-4">
<h3 class="text-lg font-bold mb-4">撞库记录</h3>
</div>
</el-tab-pane>
</el-tabs>
</div>
<!-- 右侧跟进记录 -->
<div class="w-[300px]">
<el-tabs>
<el-tab-pane label="跟进历史">
<div class="mt-4">
<el-timeline>
<el-timeline-item v-for="item in followRecordList" :key="item.id">
<div>{{ item.content }}</div>
<div class="flex justify-between text-gray-400 text-sm mt-1">
<span>跟进人{{ item.creator }}</span>
<span>{{ formatDate(new Date(Number(item.createTime))) }}</span>
</div>
</el-timeline-item>
</el-timeline>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</ContentWrap>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import CustomerBaseInfo from './customerBaseInfor/CustomerBaseInfo.vue'
import { CustomerInforApi } from '@/api/crm/customer/customer'
import { FollwRecordApi,FollwRecordVO } from '@/api/crm/follw'
import { formatDate } from '@/utils/formatTime'
defineOptions({ name: 'CustomerDetail' })
const props = defineProps<{
customerId?: number
}>()
const followRecordList = ref<FollwRecordVO[]>([])
//
const customerForm = ref({
id: undefined as number | undefined,
sex: 1,
age: undefined as string | undefined,
customerName: '',
expectAmount: undefined as string | undefined,
city: '',
customerLevel: 0,
customerType: '潜在',
followContent: '',
mobile: '',
remark: '',
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
ownerUserId: undefined,
depId: undefined,
contactLastTime: undefined,
contactLastContent: undefined,
createTime: undefined
})
// customerId
watch(() => props.customerId, async (id) => {
if (id) {
try {
//
const data = await CustomerInforApi.getCustomerInfor(id)
//
Object.assign(customerForm.value, data)
//
followRecordList.value = await FollwRecordApi.getFollwRecordList(id)
} catch (error) {
console.error('Failed to fetch customer details:', error)
}
}
}, { immediate: true })
//
const provide = {
customerForm
}
</script>
<style scoped>
.el-timeline {
padding-right: 10px;
max-height: calc(100vh - 240px);
overflow-y: auto;
}
</style>

View File

@ -1,143 +0,0 @@
<template>
<div class="customer-base-info">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-bold">基本信息</h3>
<el-button type="primary" size="small">保存</el-button>
</div>
<el-form label-width="100px">
<div class="grid grid-cols-3 gap-4">
<!-- 性别 -->
<el-form-item label="性别">
<el-radio-group v-model="form.sex">
<el-radio :label="1">先生</el-radio>
<el-radio :label="2">女士</el-radio>
</el-radio-group>
</el-form-item>
<!-- 年龄 -->
<el-form-item label="年龄">
<el-select v-model="form.age" class="w-full" placeholder="18-24岁">
<el-option label="18-24岁" value="18-24" />
<el-option label="25-35岁" value="25-35" />
<el-option label="36-45岁" value="36-45" />
</el-select>
</el-form-item>
<el-form-item label="期望金额">
<el-select v-model="form.expectAmount" class="w-full" placeholder="暂不确定">
<el-option label="暂不确定" value="" />
</el-select>
</el-form-item>
</div>
<div class="grid grid-cols-3 gap-4">
<!-- 企业名称 -->
<el-form-item label="客户星级">
<el-select v-model="form.expectAmount" class="w-full" placeholder="暂不确定">
<el-option label="暂不确定" value="" />
</el-select>
</el-form-item>
<el-form-item label="家庭住址">
<el-input v-model="form.companyName" placeholder="请输入企业名称" />
</el-form-item>
<el-form-item label="客户备注">
<el-input v-model="form.city" placeholder="" type="textarea" :rows="2" />
</el-form-item>
</div>
<div class="flex gap-4 items-center">
<el-form-item label="房">
<el-switch />
</el-form-item>
<el-form-item label="车">
<el-switch />
</el-form-item>
<el-form-item label="社保">
<el-switch />
</el-form-item>
<el-form-item label="公积金">
<el-switch />
</el-form-item>
<el-form-item label="保单">
<el-switch />
</el-form-item>
<el-form-item label="营业执照">
<el-switch />
</el-form-item>
</div>
<!-- 客户类型 -->
<el-form-item label="客户类型" required>
<div class="mb-2">
<el-radio-group v-model="form.customerType" class="flex gap-2">
<el-radio-button label="潜在">潜在</el-radio-button>
<el-radio-button label="意向">意向</el-radio-button>
<el-radio-button label="无效">无效</el-radio-button>
<el-radio-button label="空号">空号</el-radio-button>
<el-radio-button label="需求不明">需求不明</el-radio-button>
</el-radio-group>
</div>
</el-form-item>
<!-- 客户标签 -->
<el-form-item label="客户标签">
<div class="grid grid-cols-10 gap-2">
<el-tag v-for="tag in customerTags" :key="tag" class="cursor-pointer" @click="toggleTag(tag)">
{{ tag }}
</el-tag>
</div>
</el-form-item>
<!-- 客户标签 -->
<el-form-item label="跟进标签">
<div class="grid grid-cols-10 gap-2">
<el-tag v-for="tag in customerTags" :key="tag" class="cursor-pointer" @click="toggleTag(tag)">
{{ tag }}
</el-tag>
</div>
</el-form-item>
<!-- 跟进内容 -->
<el-form-item label="跟进内容">
<el-input type="textarea" v-model="form.followContent" :rows="4" placeholder="请输入跟进内容" maxlength="500" show-word-limit />
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const form = ref({
sex: 1,
age: '18-24',
companyName: '',
expectAmount: '',
city: '',
customerLevel: 0,
customerType: '潜在',
followContent: ''
})
const customerTags = [
'生意', '上班', '自业', '信用卡', '微粒贷',
'抵押房', '全款房', '有保单', '有社保', '公积金'
]
const toggleTag = (tag: string) => {
//
}
</script>
<style scoped>
.el-radio-group {
display: flex;
gap: 1rem;
}
</style>

View File

@ -93,7 +93,7 @@ const submitForm = async () => {
customerIds: formData.value.customerIds
}
await CustomerInforApi.transfer(data)
message.success(t('common.updateSuccess'))
message.success('转移成功')
dialogVisible.value = false
emit('success')
} catch (error) {

View File

@ -52,6 +52,56 @@
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="录入时间" prop="createTime">
<el-date-picker
@ -108,13 +158,38 @@
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="录入时间" align="center" prop="createTime" :formatter="dateFormatter" min-width="180px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.customerId)"
@click="openInforForm(scope.row.id)"
>
详情
</el-button>
@ -133,7 +208,13 @@
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
<CustomerDetail
:customer-id="customerId"
:prev-id="prevId"
:next-id="nextId"
@prev="handlePrevNext('prev')"
@next="handlePrevNext('next')"
/>
</el-drawer>
<!-- 分配弹窗 -->
@ -148,9 +229,10 @@ import { buildTree } from '@/utils/tree'
import {CustomerSourceApi} from '@/api/crm/config/customersource'
import {ChannelApi,ChannelVO} from '@/api/crm/config/channel'
import {CustomerInforApi, CustomerInforVO} from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/CustomerDetail/CustomerDetail.vue'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
/** 客户信息 列表 */
@ -170,6 +252,11 @@ const queryParams = reactive({
mobile: undefined,
customerSourceId: undefined,
channelId: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
createTime: []
})
@ -186,6 +273,8 @@ const multipleSelection = ref<CustomerInforVO[]>([])
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const prevId = ref<number>(0) // ID
const nextId = ref<number>(0) // ID
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
@ -217,6 +306,13 @@ const resetQuery = () => {
const openInforForm = (id?: number) => {
customerId.value = id
//
const currentIndex = list.value.findIndex(item => item.id === id)
// ID
prevId.value = currentIndex > 0 ? list.value[currentIndex - 1].id : 0
nextId.value = currentIndex < list.value.length - 1 ? list.value[currentIndex + 1].id : 0
drawerVisible.value = true
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
@ -253,4 +349,23 @@ onMounted(async () => {
customerSourceList.value=await CustomerSourceApi.getCustomerSourceList(null);
customerSourceOptions.value=buildTree(customerSourceList.value);
})
/** 处理上一条/下一条切换 */
const handlePrevNext = async (type: 'prev' | 'next') => {
//
const currentIndex = list.value.findIndex(item => item.id === customerId.value)
//
const targetIndex = type === 'prev' ? currentIndex - 1 : currentIndex + 1
//
if (targetIndex >= 0 && targetIndex < list.value.length) {
const targetId = list.value[targetIndex].id
// ID
customerId.value = targetId
// /ID
prevId.value = targetIndex > 0 ? list.value[targetIndex - 1].id : 0
nextId.value = targetIndex < list.value.length - 1 ? list.value[targetIndex + 1].id : 0
}
}
</script>

View File

@ -1,255 +0,0 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户姓名"
clearable
class="!w-120px "
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="数据渠道" prop="channelId">
<el-select
v-model="queryParams.channelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in channelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="录入时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openAllocateForm"
>
<Icon icon="ep:plus" class="mr-5px" /> 分配
</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="120px" fixed/>
<el-table-column label="来源标签" align="center" prop="customerSourceId" width="100px">
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="数据渠道" align="center" prop="channelId" width="100px">
<template #default="scope">
<div>
{{ (channelList.find((cs: any) => cs.id === scope.row.channelId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="录入时间" align="center" prop="createTime" :formatter="dateFormatter" min-width="180px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.customerId)"
>
详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 分配弹窗 -->
<AllocateForm ref="formRef" @success="getList" />
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import {CustomerSourceApi} from '@/api/crm/config/customersource'
import {CuleApi, CuleVO} from '@/api/crm/cule/index'
import {ChannelApi,ChannelVO} from '@/api/crm/config/channel'
import CustomerDetail from '@/views/crm/customer/components/detail/CustomerDetail.vue'
import AllocateForm from '../components/Allocate/AllocateForm.vue'
import TransferForm from '../components/Opensea/TransferForm.vue'
/** 客户信息 列表 */
defineOptions({ name: 'AllCustomer' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<CuleVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
customerName: undefined,
mobile: undefined,
customerSourceId: undefined,
channelId: undefined,
createTime: []
})
const cascaderProps= {
emitPath: false,
}
const queryFormRef = ref() //
const channelList = ref<ChannelVO[]>([])
const customerSourceList = ref<[]>([])
let customerSourceOptions = ref<any>();
const multipleSelection = ref<CuleVO[]>([])
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CuleApi.getCuleCustomerPage(queryParams);
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
const handleSelectionChange = (val: CuleVO[]) => {
multipleSelection.value = val
console.log(multipleSelection.value)
}
/** 打开分配弹窗 */
const openAllocateForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要分配的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.customerId)
formRef.value.open(customerIds)
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.customerId)
transferFormRef.value.open(customerIds, 1)
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
channelList.value = await ChannelApi.getChannelList(null);
customerSourceList.value=await CustomerSourceApi.getCustomerSourceList(null);
customerSourceOptions.value=buildTree(customerSourceList.value);
})
</script>

View File

@ -0,0 +1,493 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="负责部门" prop="depId">
<el-select
v-model="queryParams.depId"
placeholder="请选择"
filterable
class="!w-120px"
>
<el-option
v-for="option in depList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="客户经理" prop="userId">
<el-select
v-model="queryParams.userId"
placeholder="请选择"
clearable
filterable
class="!w-120px"
>
<el-option
v-for="option in userList"
:key="option.id"
:value="option.id"
:label="option.nickname"
/>
</el-select>
</el-form-item>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户姓名"
clearable
class="!w-120px "
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
v-model="queryParams.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId">
<el-select
v-model="queryParams.importLevelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进状态" prop="followStatusId">
<el-select
v-model="queryParams.followStatusId"
placeholder="请选择"
clearable
class="!w-120px"
>
<el-option
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重点客户" prop="isImport">
<el-select v-model="queryParams.isImport" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_IMPORT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否跟进" prop="isFollow">
<el-select v-model="queryParams.isFollow" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="分配时间" prop="latestAllocateTime">
<el-date-picker
v-model="queryParams.latestAllocateTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openAllocateForm"
>
<Icon icon="ep:plus" class="mr-5px" /> 分配
</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatusId">
<template #default="scope">
<div>
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column>
<el-table-column label="重要程度" align="center" prop="importLevelId" >
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重点客户" align="center" prop="isImport" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_IMPORT_STATUS" :value="scope.row.isImport" />
</template>
</el-table-column >
<el-table-column label="是否跟进" align="center" prop="isFollow" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_STATUS" :value="scope.row.isFollow" />
</template>
</el-table-column >
<el-table-column label="客户经理" align="center" prop="userId" >
<template #default="scope">
<div>
{{ userList.find((cs) => cs.id === scope.row.userId)?.nickname||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="来源标签" align="center" prop="customerSourceId" >
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.id)"
>
跟进
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 分配弹窗 -->
<AllocateForm ref="formRef" @success="getList" />
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
/** 客户信息 列表 */
defineOptions({ name: 'DepAllCustomer' })
const emit = defineEmits(['update-count'])
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(false) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList, userList, depList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
customerName: undefined,
channelId: undefined,
mobile: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
depId: 0,
lastFollowTime: [],
lastFollowContent: undefined,
createTime: [],
isImport: undefined,
isFollow: undefined,
latestAllocateTime: []
})
const queryFormRef = ref() //
const multipleSelection = ref<CustomerInforVO[]>([])
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const cascaderProps = {
emitPath: false
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CustomerInforApi.getDepCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
// ID
if (depList.value.length > 0) {
queryParams.depId = depList.value[0].id
}
handleQuery()
}
/** 打开跟进抽屉 */
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开分配弹窗 */
const openAllocateForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要分配的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
formRef.value.open(customerIds, queryParams.depId)
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds, 1)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
multipleSelection.value = val
}
/** 初始化 **/
onMounted(async () => {
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList(),
crmStore.getUserList(),
crmStore.getDepListByCurUser()
])
//
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
//
if (depList.value.length > 0) {
queryParams.depId = depList.value[0].id
} else {
message.error('无部门管理权限')
return
}
await getList()
})
</script>

View File

@ -0,0 +1,489 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="负责部门" prop="depId">
<el-select
v-model="queryParams.depId"
placeholder="请选择"
filterable
class="!w-120px"
>
<el-option
v-for="option in depList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="客户经理" prop="userId">
<el-select
v-model="queryParams.userId"
placeholder="请选择"
clearable
filterable
class="!w-120px"
>
<el-option
v-for="option in userList"
:key="option.id"
:value="option.id"
:label="option.nickname"
/>
</el-select>
</el-form-item>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户姓名"
clearable
class="!w-120px "
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
v-model="queryParams.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId">
<el-select
v-model="queryParams.importLevelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进状态" prop="followStatusId">
<el-select
v-model="queryParams.followStatusId"
placeholder="请选择"
clearable
class="!w-120px "
>
<el-option
v-for="option in followstatusList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="是否跟进" prop="isFollow">
<el-select v-model="queryParams.isFollow" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="分配时间" prop="latestAllocateTime">
<el-date-picker
v-model="queryParams.latestAllocateTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openAllocateForm"
>
<Icon icon="ep:plus" class="mr-5px" /> 分配
</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatusId" >
<template #default="scope">
<div>
{{ followstatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重要程度" align="center" prop="importLevelId" >
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否跟进" align="center" prop="isFollow" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_STATUS" :value="scope.row.isFollow" />
</template>
</el-table-column >
<el-table-column label="客户经理" align="center" prop="userId" >
<template #default="scope">
<div>
{{ userList.find((cs) => cs.id === scope.row.userId)?.nickname||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="来源标签" align="center" prop="customerSourceId" >
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.id)"
>
跟进
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 分配弹窗 -->
<AllocateForm ref="formRef" @success="getList" />
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
interface FollowStatus {
id: number
name: string
}
/** 客户信息 列表 */
defineOptions({ name: 'DepImportCustomer' })
const emit = defineEmits(['update-count'])
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(false)
const list = ref<CustomerInforVO[]>([])
const total = ref(0)
const queryFormRef = ref() //
const multipleSelection = ref<CustomerInforVO[]>([]) //
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const crmStore = useCrmStore()
const { importLevelList, channelList , customerSourceList, customerTypeList, userList, depList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
const cascaderProps= {
emitPath: false,
}
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
customerName: undefined,
channelId: undefined,
mobile: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
depId: depList.value[0]?.id,
lastFollowTime: [],
lastFollowContent: undefined,
createTime: [],
isImport: 1,
isFollow: undefined,
latestAllocateTime: []
})
const followstatusList = ref<FollowStatus[]>([])
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CustomerInforApi.getDepCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
if (depList.value.length > 0) {
queryParams.depId = depList.value[0].id
}
queryParams.isImport = 1
handleQuery()
}
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开分配弹窗 */
const openAllocateForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要分配的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
formRef.value.open(customerIds, queryParams.depId)
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds, 1)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
multipleSelection.value = val
console.log(multipleSelection.value)
}
/** 初始化 **/
onMounted(async () => {
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList(),
crmStore.getUserList(),
crmStore.getDepListByCurUser()
])
//
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
//
if (depList.value.length > 0) {
queryParams.depId = depList.value[0].id
} else {
message.error('无部门管理权限')
return
}
//
await getList()
})
</script>

View File

@ -0,0 +1,492 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="负责部门" prop="depId">
<el-select
v-model="queryParams.depId"
placeholder="请选择"
filterable
class="!w-120px"
>
<el-option
v-for="option in depList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="客户经理" prop="userId">
<el-select
v-model="queryParams.userId"
placeholder="请选择"
clearable
filterable
class="!w-120px"
>
<el-option
v-for="option in userList"
:key="option.id"
:value="option.id"
:label="option.nickname"
/>
</el-select>
</el-form-item>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户姓名"
clearable
class="!w-120px "
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
v-model="queryParams.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId">
<el-select
v-model="queryParams.importLevelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进状态" prop="followStatusId">
<el-select
v-model="queryParams.followStatusId"
placeholder="请选择"
clearable
class="!w-120px "
>
<el-option
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重点客户" prop="isImport">
<el-select v-model="queryParams.isImport" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_IMPORT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否跟进" prop="isFollow">
<el-select v-model="queryParams.isFollow" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openAllocateForm"
>
<Icon icon="ep:plus" class="mr-5px" /> 分配
</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatusId" >
<template #default="scope">
<div>
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重要程度" align="center" prop="importLevelId" >
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重点客户" align="center" prop="isImport" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_IMPORT_STATUS" :value="scope.row.isImport" />
</template>
</el-table-column >
<el-table-column label="是否跟进" align="center" prop="isFollow" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_STATUS" :value="scope.row.isFollow" />
</template>
</el-table-column >
<el-table-column label="客户经理" align="center" prop="userId" >
<template #default="scope">
<div>
{{ userList.find((cs) => cs.id === scope.row.userId)?.nickname||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="来源标签" align="center" prop="customerSourceId" >
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.id)"
>
跟进
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 分配弹窗 -->
<AllocateForm ref="formRef" @success="getList" />
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
import dayjs from 'dayjs'
/** 客户信息 列表 */
defineOptions({ name: 'DepSevenCustomer' })
const emit = defineEmits(['update-count'])
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(false) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList, userList, depList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
const queryFormRef = ref() //
const multipleSelection = ref<CustomerInforVO[]>([])
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const cascaderProps = {
emitPath: false
}
// 7
const sevenDaysAgo = dayjs().subtract(7, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss')
const today = dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
customerName: undefined,
channelId: undefined,
mobile: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
depId: depList.value[0]?.id,
lastFollowTime: [],
lastFollowContent: undefined,
createTime: [],
isImport: undefined,
isFollow: undefined,
latestAllocateTime: [sevenDaysAgo, today]// 7
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CustomerInforApi.getDepCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
// ID7
if (depList.value.length > 0) {
queryParams.depId = depList.value[0].id
}
queryParams.createTime = [sevenDaysAgo, today]
handleQuery()
}
/** 打开跟进抽屉 */
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开分配弹窗 */
const openAllocateForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要分配的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
formRef.value.open(customerIds, queryParams.depId)
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds, 1)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
multipleSelection.value = val
}
/** 初始化 **/
onMounted(async () => {
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList(),
crmStore.getUserList(),
crmStore.getDepListByCurUser()
])
//
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
//
if (depList.value.length > 0) {
queryParams.depId = depList.value[0].id
} else {
message.error('无部门管理权限')
return
}
//
await getList()
})
</script>

View File

@ -0,0 +1,493 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="负责部门" prop="depId">
<el-select
v-model="queryParams.depId"
placeholder="请选择"
filterable
class="!w-120px"
>
<el-option
v-for="option in depList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="客户经理" prop="userId">
<el-select
v-model="queryParams.userId"
placeholder="请选择"
clearable
filterable
class="!w-120px"
>
<el-option
v-for="option in userList"
:key="option.id"
:value="option.id"
:label="option.nickname"
/>
</el-select>
</el-form-item>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户姓名"
clearable
class="!w-120px "
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
v-model="queryParams.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId">
<el-select
v-model="queryParams.importLevelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进状态" prop="followStatusId">
<el-select
v-model="queryParams.followStatusId"
placeholder="请选择"
clearable
class="!w-120px "
>
<el-option
v-for="option in followstatusList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重点客户" prop="isImport">
<el-select v-model="queryParams.isImport" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_IMPORT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否跟进" prop="isFollow">
<el-select v-model="queryParams.isFollow" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openAllocateForm"
>
<Icon icon="ep:plus" class="mr-5px" /> 分配
</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatusId" >
<template #default="scope">
<div>
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重要程度" align="center" prop="importLevelId" >
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重点客户" align="center" prop="isImport" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_IMPORT_STATUS" :value="scope.row.isImport" />
</template>
</el-table-column >
<el-table-column label="是否跟进" align="center" prop="isFollow" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_STATUS" :value="scope.row.isFollow" />
</template>
</el-table-column >
<el-table-column label="客户经理" align="center" prop="userId" >
<template #default="scope">
<div>
{{ userList.find((cs) => cs.id === scope.row.userId)?.nickname||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="来源标签" align="center" prop="customerSourceId" >
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.id)"
>
跟进
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 分配弹窗 -->
<AllocateForm ref="formRef" @success="getList" />
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
import dayjs from 'dayjs'
/** 客户信息 列表 */
defineOptions({ name: 'DepTodayCustomer' })
const emit = defineEmits(['update-count'])
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(false) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const crmStore = useCrmStore()
const { importLevelList, followStatusList, customerSourceList, customerTypeList, userList, depList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
customerName: undefined,
channelId: undefined,
mobile: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
depId: depList.value[0]?.id,
lastFollowTime: [],
lastFollowContent: undefined,
createTime: [],
isImport: undefined,
isFollow: undefined,
latestAllocateTime: [
dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
]
})
const queryFormRef = ref() //
const multipleSelection = ref<CustomerInforVO[]>([])
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const cascaderProps = {
emitPath: false
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CustomerInforApi.getDepCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
// ID
if (depList.value.length > 0) {
queryParams.depId = depList.value[0].id
}
queryParams.latestAllocateTime = [
dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
]
handleQuery()
}
/** 打开跟进抽屉 */
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开分配弹窗 */
const openAllocateForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要分配的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
formRef.value.open(customerIds, queryParams.depId)
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds, 1)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
multipleSelection.value = val
}
/** 初始化 **/
onMounted(async () => {
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList(),
crmStore.getUserList(),
crmStore.getDepListByCurUser()
])
//
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
//
if (depList.value.length > 0) {
queryParams.depId = depList.value[0].id
} else {
message.error('无部门管理权限')
return
}
//
await getList()
})
</script>

View File

@ -0,0 +1,482 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="负责部门" prop="depId">
<el-select
v-model="queryParams.depId"
placeholder="请选择"
filterable
class="!w-120px"
>
<el-option
v-for="option in depList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="客户经理" prop="userId">
<el-select
v-model="queryParams.userId"
placeholder="请选择"
clearable
filterable
class="!w-120px"
>
<el-option
v-for="option in userList"
:key="option.id"
:value="option.id"
:label="option.nickname"
/>
</el-select>
</el-form-item>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户姓名"
clearable
class="!w-120px "
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
v-model="queryParams.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId">
<el-select
v-model="queryParams.importLevelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进状态" prop="followStatusId">
<el-select
v-model="queryParams.followStatusId"
placeholder="请选择"
clearable
class="!w-120px "
>
<el-option
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重点客户" prop="isImport">
<el-select v-model="queryParams.isImport" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_IMPORT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="分配时间" prop="latestAllocateTime">
<el-date-picker
v-model="queryParams.latestAllocateTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openAllocateForm"
>
<Icon icon="ep:plus" class="mr-5px" /> 分配
</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatusId">
<template #default="scope">
<div>
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name || "未标记" }}
</div>
</template>
</el-table-column>
<el-table-column label="重要程度" align="center" prop="importLevelId" >
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重点客户" align="center" prop="isImport" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_IMPORT_STATUS" :value="scope.row.isImport" />
</template>
</el-table-column >
<el-table-column label="客户经理" align="center" prop="userId" >
<template #default="scope">
<div>
{{ userList.find((cs) => cs.id === scope.row.userId)?.nickname||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="来源标签" align="center" prop="customerSourceId" >
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.id)"
>
跟进
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 分配弹窗 -->
<AllocateForm ref="formRef" @success="getList" />
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
/** 客户信息 列表 */
defineOptions({ name: 'DepUnfollowCustomer' })
const emit = defineEmits(['update-count'])
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(false) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList, userList, depList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
customerName: undefined,
channelId: undefined,
mobile: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
depId: depList.value[0]?.id,
lastFollowTime: [],
lastFollowContent: undefined,
createTime: [],
isImport: undefined,
isFollow: 0, //
latestAllocateTime: []
})
const queryFormRef = ref() //
const multipleSelection = ref<CustomerInforVO[]>([])
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const cascaderProps = {
emitPath: false
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CustomerInforApi.getDepCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
// ID
if (depList.value.length > 0) {
queryParams.depId = depList.value[0].id
}
queryParams.isFollow = 0
handleQuery()
}
/** 打开跟进抽屉 */
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开分配弹窗 */
const openAllocateForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要分配的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
formRef.value.open(customerIds, queryParams.depId)
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds, 1)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
multipleSelection.value = val
}
/** 初始化 **/
onMounted(async () => {
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList(),
crmStore.getUserList(),
crmStore.getDepListByCurUser()
])
//
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
//
if (depList.value.length > 0) {
queryParams.depId = depList.value[0].id
} else {
message.error('无部门管理权限')
return
}
//
await getList()
})
</script>

View File

@ -0,0 +1,55 @@
<template>
<ContentWrap>
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane :label="'所有客户 (' + counts.all + ')'" name="all">
<AllCustomer v-if="activeTab === 'all'" @update-count="updateCount('all', $event)" />
</el-tab-pane>
<el-tab-pane :label="'今日分配 (' + counts.today + ')'" name="today">
<TodayCustomer v-if="activeTab === 'today'" @update-count="updateCount('today', $event)" />
</el-tab-pane>
<el-tab-pane :label="'未跟进客户 (' + counts.unfollow + ')'" name="unfollow">
<UnfollowCustomer v-if="activeTab === 'unfollow'" @update-count="updateCount('unfollow', $event)" />
</el-tab-pane>
<el-tab-pane :label="'重点客户 (' + counts.import + ')'" name="import">
<ImportCustomer v-if="activeTab === 'import'" @update-count="updateCount('import', $event)" />
</el-tab-pane>
<el-tab-pane :label="'近七天新数据 (' + counts.seven + ')'" name="seven">
<SevenCustomer v-if="activeTab === 'seven'" @update-count="updateCount('seven', $event)" />
</el-tab-pane>
</el-tabs>
</ContentWrap>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import AllCustomer from '@/views/crm/customer/dep/tables/all.vue'
import TodayCustomer from '@/views/crm/customer/dep/tables/today.vue'
import UnfollowCustomer from '@/views/crm/customer/dep/tables/unfollow.vue'
import ImportCustomer from '@/views/crm/customer/dep/tables/import.vue'
import SevenCustomer from '@/views/crm/customer/dep/tables/seven.vue'
defineOptions({ name: 'CustomerTabs' })
const activeTab = ref('all')
const counts = reactive({
all: 0,
today: 0,
unfollow: 0,
import: 0,
seven: 0
})
//
const updateCount = (type: string, count: number) => {
counts[type] = count
}
const handleTabClick = () => {
//
}
</script>
<style lang="scss" scoped>
:deep(.el-tabs__content) {
padding: 20px 0;
}
</style>

View File

@ -61,7 +61,7 @@
class="!w-120px "
>
<el-option
v-for="option in followstatusList"
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
@ -133,21 +133,8 @@
class="!w-120px"
/>
</el-form-item>
<el-form-item label="数据渠道" prop="channelId">
<el-select
v-model="queryParams.channelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in channelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="重点客户" prop="isImport">
<el-select v-model="queryParams.isImport" placeholder="请选择" clearable class="!w-120px">
<el-option
@ -180,9 +167,9 @@
/>
</el-form-item>
<el-form-item label="最后跟进" prop="lastContactTime">
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastContactTime"
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
@ -227,7 +214,7 @@
<el-table-column label="跟进状态" align="center" prop="followStatusId" >
<template #default="scope">
<div>
{{ followstatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
@ -248,8 +235,8 @@
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_STATUS" :value="scope.row.isFollow" />
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastContactTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastContactContent" width="180px"/>
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
<template #default="scope">
<div>
@ -288,17 +275,8 @@
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="数据渠道" align="center" prop="channelId" >
<template #default="scope">
<div>
{{ (channelList.find((cs: any) => cs.id === scope.row.channelId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="录入时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
@ -337,14 +315,10 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import {ImportLevelApi,ImportLevelVO} from '@/api/crm/config/Importlevel'
import {FollowStatusApi,FollowStatusVO} from '@/api/crm/config/followstatus'
import {CustomerSourceApi} from '@/api/crm/config/customersource'
import {CustomerTypeApi} from '@/api/crm/config/customertype'
import {ChannelApi,ChannelVO} from '@/api/crm/config/channel'
import CustomerDetail from '@/views/crm/components/CustomerDetail/CustomerDetail.vue'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
/** 客户信息 列表 */
defineOptions({ name: 'AllCustomer' })
@ -358,6 +332,7 @@ const total = ref(0) // 列表的总页数
const multipleSelection = ref<CustomerInforVO[]>([])
const transferFormRef = ref() // Ref
const emit = defineEmits(['update-count'])
const queryParams = reactive({
pageNo: 1,
@ -375,18 +350,15 @@ const queryParams = reactive({
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
lastContactTime: [],
lastFollowTime: [],
createTime: [],
isImport: undefined,
isFollow: undefined,
latestAllocateTime: []
})
const queryFormRef = ref() //
const importLevelList = ref<ImportLevelVO[]>([])
const channelList = ref<ChannelVO[]>([])
const followstatusList = ref<FollowStatusVO[]>([])
const customerSourceList = ref<[]>([])
const customerTypeList = ref<[]>([])
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>();
let customerTypeOptions = ref<any>();
const drawerVisible = ref(false) //
@ -394,14 +366,15 @@ const customerId = ref<number>() // 当前查看的客户ID
const cascaderProps= {
emitPath: false,
}
/** 查询列表 */
const getList = async () => {
const getList = async () => {
loading.value = true
try {
const data = await CustomerInforApi.getMyCustomerPage(queryParams)
list.value = data.list
total.value = data.total
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
@ -444,14 +417,16 @@ const handleSelectionChange = (val: CustomerInforVO[]) => {
/** 初始化 **/
onMounted(async () => {
await getList()
//
importLevelList.value = await ImportLevelApi.getImportLevelList(null);
channelList.value = await ChannelApi.getChannelList(null);
followstatusList.value = await FollowStatusApi.getFollowStatusList(null);
customerSourceList.value=await CustomerSourceApi.getCustomerSourceList(null);
customerTypeList.value=await CustomerTypeApi.getCustomerTypeList(null);
customerSourceOptions.value=buildTree(customerSourceList.value);
customerTypeOptions.value=buildTree(customerTypeList.value);
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList()
])
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
})
</script>

View File

@ -0,0 +1,412 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户姓名"
clearable
class="!w-120px "
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
v-model="queryParams.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId">
<el-select
v-model="queryParams.importLevelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进状态" prop="followStatusId">
<el-select
v-model="queryParams.followStatusId"
placeholder="请选择"
clearable
class="!w-120px "
>
<el-option
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="是否跟进" prop="isFollow">
<el-select v-model="queryParams.isFollow" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="分配时间" prop="latestAllocateTime">
<el-date-picker
v-model="queryParams.latestAllocateTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatusId" >
<template #default="scope">
<div>
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重要程度" align="center" prop="importLevelId" >
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重点客户" align="center" prop="isImport" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_IMPORT_STATUS" :value="scope.row.isImport" />
</template>
</el-table-column >
<el-table-column label="是否跟进" align="center" prop="isFollow" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_STATUS" :value="scope.row.isFollow" />
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="来源标签" align="center" prop="customerSourceId" >
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="录入时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.id)"
>
跟进
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
/** 客户信息 列表 */
defineOptions({ name: 'AllCustomer' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const multipleSelection = ref<CustomerInforVO[]>([])
const transferFormRef = ref() // Ref
const emit = defineEmits(['update-count'])
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
customerName: undefined,
channelId: undefined,
mobile: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
lastFollowTime: [] as string[],
createTime: [] as string[],
isImport: 1 as number | undefined,
isFollow: undefined,
latestAllocateTime: [] as string[]
})
const queryFormRef = ref() //
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const cascaderProps= {
emitPath: false,
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CustomerInforApi.getMyCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 打开跟进抽屉 */
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds,1)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
multipleSelection.value = val
console.log(multipleSelection.value)
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList()
])
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
})
</script>

View File

@ -0,0 +1,417 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户姓名"
clearable
class="!w-120px "
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
v-model="queryParams.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId">
<el-select
v-model="queryParams.importLevelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进状态" prop="followStatusId">
<el-select
v-model="queryParams.followStatusId"
placeholder="请选择"
clearable
class="!w-120px "
>
<el-option
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重点客户" prop="isImport">
<el-select v-model="queryParams.isImport" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_IMPORT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否跟进" prop="isFollow">
<el-select v-model="queryParams.isFollow" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatusId" >
<template #default="scope">
<div>
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重要程度" align="center" prop="importLevelId" >
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重点客户" align="center" prop="isImport" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_IMPORT_STATUS" :value="scope.row.isImport" />
</template>
</el-table-column >
<el-table-column label="是否跟进" align="center" prop="isFollow" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_STATUS" :value="scope.row.isFollow" />
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="来源标签" align="center" prop="customerSourceId" >
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="录入时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.id)"
>
跟进
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
import dayjs from 'dayjs'
/** 客户信息 列表 */
defineOptions({ name: 'AllCustomer' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const multipleSelection = ref<CustomerInforVO[]>([])
const transferFormRef = ref() // Ref
const emit = defineEmits(['update-count'])
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
customerName: undefined,
channelId: undefined,
mobile: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
lastFollowTime: [] as string[],
createTime: [] as string[],
isImport: undefined,
isFollow: undefined,
latestAllocateTime: [] as string[]
})
const queryFormRef = ref() //
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const cascaderProps= {
emitPath: false,
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
// 7
queryParams.latestAllocateTime = [
dayjs().subtract(7, 'day').format('YYYY-MM-DD 00:00:00'),
dayjs().format('YYYY-MM-DD 23:59:59')
]
const data = await CustomerInforApi.getMyCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 打开跟进抽屉 */
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds,1)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
multipleSelection.value = val
console.log(multipleSelection.value)
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList()
])
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
})
</script>

View File

@ -0,0 +1,417 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户姓名"
clearable
class="!w-120px "
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
v-model="queryParams.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId">
<el-select
v-model="queryParams.importLevelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进状态" prop="followStatusId">
<el-select
v-model="queryParams.followStatusId"
placeholder="请选择"
clearable
class="!w-120px "
>
<el-option
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重点客户" prop="isImport">
<el-select v-model="queryParams.isImport" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_IMPORT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否跟进" prop="isFollow">
<el-select v-model="queryParams.isFollow" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatusId" >
<template #default="scope">
<div>
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重要程度" align="center" prop="importLevelId" >
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重点客户" align="center" prop="isImport" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_IMPORT_STATUS" :value="scope.row.isImport" />
</template>
</el-table-column >
<el-table-column label="是否跟进" align="center" prop="isFollow" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_STATUS" :value="scope.row.isFollow" />
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="来源标签" align="center" prop="customerSourceId" >
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.id)"
>
跟进
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
import dayjs from 'dayjs'
/** 客户信息 列表 */
defineOptions({ name: 'AllCustomer' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const multipleSelection = ref<CustomerInforVO[]>([])
const transferFormRef = ref() // Ref
const emit = defineEmits(['update-count'])
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
customerName: undefined,
channelId: undefined,
mobile: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
lastFollowTime: [] as string[],
createTime: [] as string[],
isImport: undefined,
isFollow: undefined,
latestAllocateTime: [] as string[]
})
const queryFormRef = ref() //
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const cascaderProps= {
emitPath: false,
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
//
queryParams.latestAllocateTime = [
dayjs().format('YYYY-MM-DD 00:00:00'),
dayjs().format('YYYY-MM-DD 23:59:59')
]
const data = await CustomerInforApi.getMyCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 打开跟进抽屉 */
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds,1)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
multipleSelection.value = val
console.log(multipleSelection.value)
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList()
])
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
})
</script>

View File

@ -0,0 +1,408 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户姓名"
clearable
class="!w-120px "
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
v-model="queryParams.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId">
<el-select
v-model="queryParams.importLevelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进状态" prop="followStatusId">
<el-select
v-model="queryParams.followStatusId"
placeholder="请选择"
clearable
class="!w-120px "
>
<el-option
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重点客户" prop="isImport">
<el-select v-model="queryParams.isImport" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_IMPORT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="分配时间" prop="latestAllocateTime">
<el-date-picker
v-model="queryParams.latestAllocateTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatusId" >
<template #default="scope">
<div>
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重要程度" align="center" prop="importLevelId" >
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重点客户" align="center" prop="isImport" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_IMPORT_STATUS" :value="scope.row.isImport" />
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="来源标签" align="center" prop="customerSourceId" >
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.id)"
>
跟进
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
/** 客户信息 列表 */
defineOptions({ name: 'AllCustomer' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const multipleSelection = ref<CustomerInforVO[]>([])
const transferFormRef = ref() // Ref
const emit = defineEmits(['update-count'])
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
customerName: undefined,
channelId: undefined,
mobile: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
lastFollowTime: [] as string[],
createTime: [] as string[],
isImport: undefined,
isFollow: 0 as number | undefined,
latestAllocateTime: [] as string[]
})
const queryFormRef = ref() //
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const cascaderProps= {
emitPath: false,
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
queryParams.isFollow = 0 //
const data = await CustomerInforApi.getMyCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 打开跟进抽屉 */
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds,1)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
multipleSelection.value = val
console.log(multipleSelection.value)
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList()
])
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
})
</script>

View File

@ -0,0 +1,55 @@
<template>
<ContentWrap>
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane :label="'所有客户 (' + counts.all + ')'" name="all">
<AllCustomer v-if="activeTab === 'all'" @update-count="updateCount('all', $event)" />
</el-tab-pane>
<el-tab-pane :label="'今日分配 (' + counts.today + ')'" name="today">
<TodayCustomer v-if="activeTab === 'today'" @update-count="updateCount('today', $event)" />
</el-tab-pane>
<el-tab-pane :label="'未跟进客户 (' + counts.unfollow + ')'" name="unfollow">
<UnfollowCustomer v-if="activeTab === 'unfollow'" @update-count="updateCount('unfollow', $event)" />
</el-tab-pane>
<el-tab-pane :label="'重点客户 (' + counts.import + ')'" name="import">
<ImportCustomer v-if="activeTab === 'import'" @update-count="updateCount('import', $event)" />
</el-tab-pane>
<el-tab-pane :label="'近七天新数据 (' + counts.seven + ')'" name="seven">
<SevenCustomer v-if="activeTab === 'seven'" @update-count="updateCount('seven', $event)" />
</el-tab-pane>
</el-tabs>
</ContentWrap>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import AllCustomer from '@/views/crm/customer/my/tables/all.vue'
import TodayCustomer from '@/views/crm/customer/my/tables/today.vue'
import UnfollowCustomer from '@/views/crm/customer/my/tables/unfollow.vue'
import ImportCustomer from '@/views/crm/customer/my/tables/import.vue'
import SevenCustomer from '@/views/crm/customer/my/tables/seven.vue'
defineOptions({ name: 'CustomerTabs' })
const activeTab = ref('all')
const counts = reactive({
all: 0,
today: 0,
unfollow: 0,
import: 0,
seven: 0
})
//
const updateCount = (type: string, count: number) => {
counts[type] = count
}
const handleTabClick = () => {
//
}
</script>
<style lang="scss" scoped>
:deep(.el-tabs__content) {
padding: 20px 0;
}
</style>

View File

@ -60,7 +60,7 @@
class="!w-120px "
>
<el-option
v-for="option in followstatusList"
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
@ -204,9 +204,9 @@
/>
</el-form-item>
<el-form-item label="最后跟进" prop="lastContactTime">
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastContactTime"
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
@ -260,7 +260,7 @@
<el-table-column label="跟进状态" align="center" prop="followStatusId" width="100px">
<template #default="scope">
<div>
{{ followstatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
@ -295,8 +295,8 @@
</div>
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastContactTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastContactContent" width="180px"/>
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" width="140px">
<template #default="scope">
<div>
@ -384,28 +384,30 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import {ImportLevelApi,ImportLevelVO} from '@/api/crm/config/Importlevel'
import {FollowStatusApi,FollowStatusVO} from '@/api/crm/config/followstatus'
import {CustomerSourceApi} from '@/api/crm/config/customersource'
import {CustomerTypeApi} from '@/api/crm/config/customertype'
import {ChannelApi,ChannelVO} from '@/api/crm/config/channel'
import CustomerDetail from '@/views/crm/components/CustomerDetail/CustomerDetail.vue'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import * as UserApi from '@/api/system/user'
import * as DepApi from '@/api/system/dept'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
/** 客户信息 列表 */
defineOptions({ name: 'AllCustomer' })
defineOptions({ name: 'TeamCustomer' })
const emit = defineEmits(['update-count'])
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<CustomerInforVO[]>([]) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList, userList, depList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
let depOptions = ref<any>()
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
@ -423,24 +425,14 @@ const queryParams = reactive({
followStatusId: undefined,
userId: undefined,
depId: undefined,
lastContactTime: [],
lastContactContent: undefined,
lastFollowTime: [],
lastFollowContent: undefined,
createTime: [],
isImport: undefined,
isFollow: undefined,
latestAllocateTime: []
})
const queryFormRef = ref() //
const importLevelList = ref<ImportLevelVO[]>([])
const channelList = ref<ChannelVO[]>([])
const followstatusList = ref<FollowStatusVO[]>([])
const userList = ref<UserApi.UserVO[]>([])
const depList = ref<DepApi.DeptVO[]>([])
const customerSourceList = ref<[]>([])
const customerTypeList = ref<[]>([])
let depOptions = ref<any>();
let customerSourceOptions = ref<any>();
let customerTypeOptions = ref<any>();
const multipleSelection = ref<CustomerInforVO[]>([])
const cascaderProps= {
@ -453,12 +445,14 @@ const formRef = ref() // 分配表单的 Ref
const transferFormRef = ref() // Ref
/** 查询列表 */
const getList = async () => {
const getList = async () => {
loading.value = true
try {
const data = await CustomerInforApi.getTeamCustomerPage(queryParams)
list.value = data.list
total.value = data.total
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
@ -506,27 +500,22 @@ const handleSelectionChange = (val: CustomerInforVO[]) => {
console.log(multipleSelection.value)
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
importLevelList.value = await ImportLevelApi.getImportLevelList(null);
channelList.value = await ChannelApi.getChannelList(null);
followstatusList.value = await FollowStatusApi.getFollowStatusList(null);
userList.value=await UserApi.getSimpleUsertList();
depList.value=await DepApi.getSimpleDeptList();
customerSourceList.value=await CustomerSourceApi.getCustomerSourceList(null);
customerTypeList.value=await CustomerTypeApi.getCustomerTypeList(null);
depOptions.value=buildTree(depList.value);
customerSourceOptions.value=buildTree(customerSourceList.value);
customerTypeOptions.value=buildTree(customerTypeList.value);
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList(),
crmStore.getUserList(),
crmStore.getDepList()
])
depOptions.value = buildTree(depList.value)
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
})
</script>

View File

@ -8,38 +8,7 @@
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="负责部门" prop="depId">
<el-select
v-model="queryParams.depId"
placeholder="请选择"
filterable
class="!w-120px"
>
<el-option
v-for="option in depList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="客户经理" prop="userId">
<el-select
v-model="queryParams.userId"
placeholder="请选择"
clearable
filterable
class="!w-120px"
>
<el-option
v-for="option in userList"
:key="option.id"
:value="option.id"
:label="option.nickname"
/>
</el-select>
</el-form-item>
>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
@ -50,12 +19,12 @@
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
clearable
class="!w-120px"
/>
</el-form-item>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
@ -91,7 +60,7 @@
class="!w-120px "
>
<el-option
v-for="option in followstatusList"
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
@ -108,7 +77,7 @@
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
@ -149,7 +118,34 @@
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-form-item>
<el-form-item label="所属部门" prop="depId">
<el-cascader
v-model="queryParams.depId"
:show-all-levels="false"
:options="depOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户经理" prop="userId">
<el-select
v-model="queryParams.userId"
placeholder="请选择"
clearable
filterable
class="!w-120px"
>
<el-option
v-for="option in userList"
:key="option.id"
:value="option.id"
:label="option.nickname"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
@ -175,17 +171,7 @@
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="重点客户" prop="isImport">
<el-select v-model="queryParams.isImport" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_IMPORT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-form-item>
<el-form-item label="是否跟进" prop="isFollow">
<el-select v-model="queryParams.isFollow" placeholder="请选择" clearable class="!w-120px">
<el-option
@ -208,9 +194,9 @@
/>
</el-form-item>
<el-form-item label="最后跟进" prop="lastContactTime">
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastContactTime"
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
@ -246,33 +232,35 @@
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatusId" >
<el-table-column label="跟进状态" align="center" prop="followStatusId" width="100px">
<template #default="scope">
<div>
{{ followstatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重要程度" align="center" prop="importLevelId" >
<el-table-column label="重要程度" align="center" prop="importLevelId" width="100px">
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
</template>
</el-table-column >
<el-table-column label="重点客户" align="center" prop="isImport" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_IMPORT_STATUS" :value="scope.row.isImport" />
@ -283,22 +271,29 @@
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_STATUS" :value="scope.row.isFollow" />
</template>
</el-table-column >
<el-table-column label="客户经理" align="center" prop="userId" >
<el-table-column label="所属部门" align="center" prop="depId" width="100px">
<template #default="scope">
<div>
{{ depList.find((cs) => cs.id === scope.row.depId)?.name||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="客户经理" align="center" prop="userId" width="100px">
<template #default="scope">
<div>
{{ userList.find((cs) => cs.id === scope.row.userId)?.nickname||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastContactTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastContactContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" width="140px">
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
@ -331,17 +326,15 @@
</div>
</template>
</el-table-column >
<el-table-column label="数据渠道" align="center" prop="channelId" >
<el-table-column label="数据渠道" align="center" prop="channelId" width="140px">
<template #default="scope">
<div>
{{ (channelList.find((cs: any) => cs.id === scope.row.channelId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="录入时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
@ -352,18 +345,19 @@
>
跟进
</el-button>
</template>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
@pagination="getList"
/>
</ContentWrap>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
@ -373,7 +367,6 @@
<AllocateForm ref="formRef" @success="getList" />
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
@ -381,48 +374,29 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import {ImportLevelApi,ImportLevelVO} from '@/api/crm/config/Importlevel'
import {FollowStatusApi,FollowStatusVO} from '@/api/crm/config/followstatus'
import {CustomerSourceApi} from '@/api/crm/config/customersource'
import {CustomerTypeApi} from '@/api/crm/config/customertype'
import {ChannelApi,ChannelVO} from '@/api/crm/config/channel'
import CustomerDetail from '@/views/crm/components/CustomerDetail/CustomerDetail.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import * as UserApi from '@/api/system/user'
import * as DepApi from '@/api/system/dept'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
/** 客户信息 列表 */
defineOptions({ name: 'AllCustomer' })
defineOptions({ name: 'ImportCustomer' })
const emit = defineEmits(['update-count'])
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const depList = ref<DepApi.DeptVO[]>([])
const queryFormRef = ref() //
const importLevelList = ref<ImportLevelVO[]>([])
const channelList = ref<ChannelVO[]>([])
const followstatusList = ref<FollowStatusVO[]>([])
const userList = ref<UserApi.UserVO[]>([])
const customerSourceList = ref<[]>([])
const customerTypeList = ref<[]>([])
let depOptions = ref<any>();
let customerSourceOptions = ref<any>();
let customerTypeOptions = ref<any>();
const multipleSelection = ref<CustomerInforVO[]>([])
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const cascaderProps= {
emitPath: false,
}
const total = ref(0) //
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList, userList, depList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
let depOptions = ref<any>()
const queryParams = reactive({
pageNo: 1,
@ -434,38 +408,48 @@ const queryParams = reactive({
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
depId: depList.value[0]?.id,
lastContactTime: [],
lastContactContent: undefined,
userId: undefined,
depId: undefined,
lastFollowTime: [],
lastFollowContent: undefined,
createTime: [],
isImport: undefined,
isImport: 1,
isFollow: undefined,
latestAllocateTime: []
})
const queryFormRef = ref() //
const multipleSelection = ref<CustomerInforVO[]>([])
const cascaderProps= {
emitPath: false,
}
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
/** 查询列表 */
const getList = async () => {
const getList = async () => {
loading.value = true
try {
const data = await CustomerInforApi.getDepCustomerPage(queryParams)
const data = await CustomerInforApi.getTeamCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
@ -473,15 +457,14 @@ const handleQuery = () => {
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
queryParams.isImport = 1
handleQuery()
}
/** 打开跟进抽屉 */
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开分配弹窗 */
const openAllocateForm = () => {
if (multipleSelection.value.length === 0) {
@ -489,8 +472,9 @@ const openAllocateForm = () => {
return
}
const customerIds = multipleSelection.value.map(item => item.id)
formRef.value.open(customerIds, queryParams.depId)
}
const depId =0
formRef.value.open(customerIds, depId)
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
@ -499,7 +483,7 @@ const openTransferForm = () => {
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds, 1)
transferFormRef.value.open(customerIds)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
@ -507,27 +491,22 @@ const handleSelectionChange = (val: CustomerInforVO[]) => {
console.log(multipleSelection.value)
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList(),
crmStore.getUserList(),
crmStore.getDepList()
])
//
importLevelList.value = await ImportLevelApi.getImportLevelList(null);
channelList.value = await ChannelApi.getChannelList(null);
followstatusList.value = await FollowStatusApi.getFollowStatusList(null);
userList.value=await UserApi.getSimpleUsertList();
depList.value=await DepApi.getDeptListByCurUser();
if(depList.value.length==0){
return message.error('无部门管理权限')
}
queryParams.depId = depList.value[0].id
customerSourceList.value=await CustomerSourceApi.getCustomerSourceList(null);
customerTypeList.value=await CustomerTypeApi.getCustomerTypeList(null);
depOptions.value=buildTree(depList.value);
customerSourceOptions.value=buildTree(customerSourceList.value);
customerTypeOptions.value=buildTree(customerTypeList.value);
await getList()
depOptions.value = buildTree(depList.value)
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
})
</script>

View File

@ -0,0 +1,518 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户姓名"
clearable
class="!w-120px "
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
v-model="queryParams.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId">
<el-select
v-model="queryParams.importLevelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进状态" prop="followStatusId">
<el-select
v-model="queryParams.followStatusId"
placeholder="请选择"
clearable
class="!w-120px"
>
<el-option
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="所属部门" prop="depId">
<el-cascader
v-model="queryParams.depId"
:show-all-levels="false"
:options="depOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户经理" prop="userId">
<el-select
v-model="queryParams.userId"
placeholder="请选择"
clearable
filterable
class="!w-120px"
>
<el-option
v-for="option in userList"
:key="option.id"
:value="option.id"
:label="option.nickname"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="数据渠道" prop="channelId">
<el-select
v-model="queryParams.channelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in channelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="重点客户" prop="isImport">
<el-select v-model="queryParams.isImport" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_IMPORT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否跟进" prop="isFollow">
<el-select v-model="queryParams.isFollow" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item label="录入时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openAllocateForm"
>
<Icon icon="ep:plus" class="mr-5px" /> 分配
</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatusId" width="100px">
<template #default="scope">
<div>
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重要程度" align="center" prop="importLevelId" width="100px">
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重点客户" align="center" prop="isImport" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_IMPORT_STATUS" :value="scope.row.isImport" />
</template>
</el-table-column >
<el-table-column label="是否跟进" align="center" prop="isFollow" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_STATUS" :value="scope.row.isFollow" />
</template>
</el-table-column >
<el-table-column label="所属部门" align="center" prop="depId" width="100px">
<template #default="scope">
<div>
{{ depList.find((cs) => cs.id === scope.row.depId)?.name||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="客户经理" align="center" prop="userId" width="100px">
<template #default="scope">
<div>
{{ userList.find((cs) => cs.id === scope.row.userId)?.nickname||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" width="140px">
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="来源标签" align="center" prop="customerSourceId" >
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="数据渠道" align="center" prop="channelId" width="140px">
<template #default="scope">
<div>
{{ (channelList.find((cs: any) => cs.id === scope.row.channelId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="录入时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.id)"
>
跟进
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 分配弹窗 -->
<AllocateForm ref="formRef" @success="getList" />
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
import dayjs from 'dayjs'
/** 客户信息 列表 */
defineOptions({ name: 'SevenCustomer' })
const emit = defineEmits(['update-count'])
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList, userList, depList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
let depOptions = ref<any>()
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
customerName: undefined,
channelId: undefined,
mobile: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
depId: undefined,
lastFollowTime: [],
lastFollowContent: undefined,
createTime: [],
isImport: undefined,
isFollow: undefined,
latestAllocateTime: [
dayjs().subtract(7, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
],
})
const queryFormRef = ref() //
const multipleSelection = ref<CustomerInforVO[]>([])
const cascaderProps= {
emitPath: false,
}
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CustomerInforApi.getTeamCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
queryParams.latestAllocateTime = [
dayjs().subtract(7, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
]
handleQuery()
}
/** 打开跟进抽屉 */
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开分配弹窗 */
const openAllocateForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要分配的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
const depId =0
formRef.value.open(customerIds, depId)
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
multipleSelection.value = val
console.log(multipleSelection.value)
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList(),
crmStore.getUserList(),
crmStore.getDepList()
])
depOptions.value = buildTree(depList.value)
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
})
</script>

View File

@ -0,0 +1,513 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="客户姓名" prop="customerName" >
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户姓名"
clearable
class="!w-120px "
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
v-model="queryParams.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId">
<el-select
v-model="queryParams.importLevelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进状态" prop="followStatusId">
<el-select
v-model="queryParams.followStatusId"
placeholder="请选择"
clearable
class="!w-120px"
>
<el-option
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="所属部门" prop="depId">
<el-cascader
v-model="queryParams.depId"
:show-all-levels="false"
:options="depOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户经理" prop="userId">
<el-select
v-model="queryParams.userId"
placeholder="请选择"
clearable
filterable
class="!w-120px"
>
<el-option
v-for="option in userList"
:key="option.id"
:value="option.id"
:label="option.nickname"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="数据渠道" prop="channelId">
<el-select
v-model="queryParams.channelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in channelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="重点客户" prop="isImport">
<el-select v-model="queryParams.isImport" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_IMPORT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否跟进" prop="isFollow">
<el-select v-model="queryParams.isFollow" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item label="录入时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openAllocateForm"
>
<Icon icon="ep:plus" class="mr-5px" /> 分配
</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户姓名" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatusId" width="100px">
<template #default="scope">
<div>
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重要程度" align="center" prop="importLevelId" width="100px">
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重点客户" align="center" prop="isImport" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_IMPORT_STATUS" :value="scope.row.isImport" />
</template>
</el-table-column >
<el-table-column label="是否跟进" align="center" prop="isFollow" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_STATUS" :value="scope.row.isFollow" />
</template>
</el-table-column >
<el-table-column label="所属部门" align="center" prop="depId" width="100px">
<template #default="scope">
<div>
{{ depList.find((cs) => cs.id === scope.row.depId)?.name||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="客户经理" align="center" prop="userId" width="100px">
<template #default="scope">
<div>
{{ userList.find((cs) => cs.id === scope.row.userId)?.nickname||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" width="140px">
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="来源标签" align="center" prop="customerSourceId" >
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="数据渠道" align="center" prop="channelId" width="140px">
<template #default="scope">
<div>
{{ (channelList.find((cs: any) => cs.id === scope.row.channelId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="录入时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.id)"
>
跟进
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 分配弹窗 -->
<AllocateForm ref="formRef" @success="getList" />
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
import dayjs from 'dayjs'
/** 客户信息 列表 */
defineOptions({ name: 'TodayCustomer' })
const emit = defineEmits(['update-count'])
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList, userList, depList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
let depOptions = ref<any>()
const queryParams = reactive({
pageNo: 1,
pageSize: 15,
customerName: undefined,
channelId: undefined,
mobile: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
customerSourceId: undefined,
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
userId: undefined,
depId: undefined,
lastFollowTime: [],
lastFollowContent: undefined,
createTime: [],
isImport: undefined,
isFollow: undefined,
latestAllocateTime: [
dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
]
})
const queryFormRef = ref() //
const multipleSelection = ref<CustomerInforVO[]>([])
const cascaderProps= {
emitPath: false,
}
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CustomerInforApi.getTeamCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 打开跟进抽屉 */
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开分配弹窗 */
const openAllocateForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要分配的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
const depId =0
formRef.value.open(customerIds, depId)
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
multipleSelection.value = val
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList(),
crmStore.getUserList(),
crmStore.getDepList()
])
depOptions.value = buildTree(depList.value)
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
})
</script>

View File

@ -0,0 +1,525 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="客户名称" prop="customerName">
<el-input
v-model="queryParams.customerName"
placeholder="请输入客户名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="座机号码" prop="telephone">
<el-input
v-model="queryParams.telephone"
placeholder="请输入座机号码"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="客户类型" prop="customerTypeId">
<el-cascader
v-model="queryParams.customerTypeId"
:show-all-levels="false"
:options="customerTypeOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="重要程度" prop="importLevelId">
<el-select
v-model="queryParams.importLevelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in importLevelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="跟进状态" prop="followStatus">
<el-select
v-model="queryParams.followStatus"
placeholder="请选择跟进状态"
clearable
>
<el-option
v-for="dict in followStatusList"
:key="dict.id"
:label="dict.name"
:value="dict.id"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有车" prop="haveCar">
<el-select v-model="queryParams.haveCar" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有房" prop="haveHouse">
<el-select v-model="queryParams.haveHouse" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="公积金" prop="haveProvidentFund">
<el-select v-model="queryParams.haveProvidentFund" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="社保" prop="haveSocialSecurity">
<el-select v-model="queryParams.haveSocialSecurity" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="保单" prop="haveGuaranteeSlip">
<el-select v-model="queryParams.haveGuaranteeSlip" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_HAVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="所属部门" prop="depId">
<el-cascader
v-model="queryParams.depId"
:show-all-levels="false"
:options="depOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="客户经理" prop="userId">
<el-select
v-model="queryParams.userId"
placeholder="请选择"
clearable
filterable
class="!w-120px"
>
<el-option
v-for="option in userList"
:key="option.id"
:value="option.id"
:label="option.nickname"
/>
</el-select>
</el-form-item>
<el-form-item label="来源标签" prop="customerSourceId">
<el-cascader
v-model="queryParams.customerSourceId"
:show-all-levels="false"
:options="customerSourceOptions"
:props="cascaderProps"
clearable
class="!w-120px"
/>
</el-form-item>
<el-form-item label="数据渠道" prop="channelId">
<el-select
v-model="queryParams.channelId"
placeholder="请选择"
clearable
class="!w-120px margin-left:15px"
>
<el-option
v-for="iml in channelList"
:key="iml.id"
:value="iml.id"
:label="iml.name"
/>
</el-select>
</el-form-item>
<el-form-item label="重点客户" prop="isImport">
<el-select v-model="queryParams.isImport" placeholder="请选择" clearable class="!w-120px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_IMPORT_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="分配时间" prop="latestAllocateTime">
<el-date-picker
v-model="queryParams.latestAllocateTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item label="录入时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openAllocateForm"
>
<Icon icon="ep:plus" class="mr-5px" /> 分配
</el-button>
<el-button
type="warning"
plain
@click="openTransferForm"
>
<Icon icon="ep:share" class="mr-5px" /> 转公海
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="30" />
<el-table-column label="序号" align="center" width="60" type="index" fixed/>
<el-table-column label="客户名称" align="center" prop="customerName" width="100px" fixed/>
<el-table-column label="手机号码" align="center" prop="mobile" width="140px" fixed/>
<el-table-column label="跟进状态" align="center" prop="followStatus" width="100px">
<template #default="scope">
<div>
{{ followStatusList.find((item) => item.id === scope.row.followStatus)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重要程度" align="center" prop="importLevelId" width="100px">
<template #default="scope">
<div>
{{ importLevelList.find((cs) => cs.id === scope.row.importLevelId)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="重点客户" align="center" prop="isImport" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_IMPORT_STATUS" :value="scope.row.isImport" />
</template>
</el-table-column >
<el-table-column label="是否跟进" align="center" prop="isFollow" width="80px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_FOLLOW_STATUS" :value="scope.row.isFollow" />
</template>
</el-table-column >
<el-table-column label="所属部门" align="center" prop="depId" width="100px">
<template #default="scope">
<div>
{{ depList.find((cs) => cs.id === scope.row.depId)?.name||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="客户经理" align="center" prop="userId" width="100px">
<template #default="scope">
<div>
{{ userList.find((cs) => cs.id === scope.row.userId)?.nickname||"未分配" }}
</div>
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" width="140px">
<template #default="scope">
<div>
{{ (customerTypeList.find((cs: any) => cs.id === scope.row.customerTypeId) as any)?.name||"未标记" }}
</div>
</template>
</el-table-column >
<el-table-column label="是否有车" align="center" prop="haveCar" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveCar" />
</template>
</el-table-column >
<el-table-column label="是否有房" align="center" prop="haveHouse" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveHouse" />
</template>
</el-table-column >
<el-table-column label="公积金" align="center" prop="haveProvidentFund" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveProvidentFund" />
</template>
</el-table-column >
<el-table-column label="社保" align="center" prop="haveSocialSecurity" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveSocialSecurity" />
</template>
</el-table-column >
<el-table-column label="保单" align="center" prop="haveGuaranteeSlip" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_HAVE_STATUS" :value="scope.row.haveGuaranteeSlip" />
</template>
</el-table-column >
<el-table-column label="来源标签" align="center" prop="customerSourceId" >
<template #default="scope">
<div >
{{ (customerSourceList.find((cs: any) => cs.id === scope.row.customerSourceId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="数据渠道" align="center" prop="channelId" width="140px">
<template #default="scope">
<div>
{{ (channelList.find((cs: any) => cs.id === scope.row.channelId) as any)?.name||"无" }}
</div>
</template>
</el-table-column >
<el-table-column label="分配时间" align="center" prop="latestAllocateTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="录入时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="客户资料" align="center" prop="remark" min-width="400px" />
<el-table-column label="操作" align="center" width="120px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openInforForm( scope.row.id)"
>
跟进
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 客户详情抽屉 -->
<el-drawer v-model="drawerVisible" size="80%" :destroy-on-close="true" :with-header="false" :show-close="true">
<CustomerDetail :customer-id="customerId" />
</el-drawer>
<!-- 分配弹窗 -->
<AllocateForm ref="formRef" @success="getList" />
<!-- 转公海弹窗 -->
<TransferForm ref="transferFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import TransferForm from '@/views/crm/components/Transfer/TransferForm.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
/** 客户信息 列表 */
defineOptions({ name: 'UnfollowCustomer' })
const emit = defineEmits(['update-count'])
const { t } = useI18n() //
const message = useMessage() //
const loading = ref(false) //
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const crmStore = useCrmStore()
const { importLevelList, channelList, followStatusList, customerSourceList, customerTypeList, userList, depList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
let depOptions = ref<any>()
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
customerName: '',
mobile: '',
telephone: '',
followStatus: undefined,
customerTypeId: undefined,
importLevelId: undefined,
haveCar: undefined,
haveHouse: undefined,
haveProvidentFund: undefined,
haveSocialSecurity: undefined,
haveGuaranteeSlip: undefined,
depId: undefined,
userId: undefined,
ownerUserId: undefined,
customerSourceId: undefined,
channelId: undefined,
isImport: undefined,
isFollow: 0,
latestAllocateTime: undefined,
lastFollowTime: undefined,
createTime: undefined
})
const queryFormRef = ref() //
const multipleSelection = ref<CustomerInforVO[]>([])
const cascaderProps= {
emitPath: false,
}
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const formRef = ref() // Ref
const transferFormRef = ref() // Ref
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CustomerInforApi.getTeamCustomerPage(queryParams)
list.value = data.list
total.value = data.total
//
emit('update-count', data.total)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
queryParams.isFollow = 0
queryParams.followStatus = undefined
queryParams.customerName = ''
queryParams.mobile = ''
queryParams.telephone = ''
queryParams.ownerUserId = undefined
queryParams.pageNo = 1
getList()
}
/** 打开跟进抽屉 */
const openInforForm = (id?: number) => {
customerId.value = id
drawerVisible.value = true
}
/** 打开分配弹窗 */
const openAllocateForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要分配的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
const depId =0
formRef.value.open(customerIds, depId)
}
/** 打开转公海弹窗 */
const openTransferForm = () => {
if (multipleSelection.value.length === 0) {
message.error('请选择要转移的客户')
return
}
const customerIds = multipleSelection.value.map(item => item.id)
transferFormRef.value.open(customerIds)
}
const handleSelectionChange = (val: CustomerInforVO[]) => {
multipleSelection.value = val
console.log(multipleSelection.value)
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getChannelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList(),
crmStore.getUserList(),
crmStore.getDepList()
])
depOptions.value = buildTree(depList.value)
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
})
</script>

View File

@ -0,0 +1,54 @@
<template>
<ContentWrap>
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane :label="'所有客户 (' + counts.all + ')'" name="all">
<AllCustomer v-if="activeTab === 'all'" @update-count="updateCount('all', $event)" />
</el-tab-pane>
<el-tab-pane :label="'今日分配 (' + counts.today + ')'" name="today">
<TodayCustomer v-if="activeTab === 'today'" @update-count="updateCount('today', $event)" />
</el-tab-pane>
<el-tab-pane :label="'未跟进客户 (' + counts.unfollow + ')'" name="unfollow">
<UnfollowCustomer v-if="activeTab === 'unfollow'" @update-count="updateCount('unfollow', $event)" />
</el-tab-pane>
<el-tab-pane :label="'重点客户 (' + counts.import + ')'" name="import">
<ImportCustomer v-if="activeTab === 'import'" @update-count="updateCount('import', $event)" />
</el-tab-pane>
<el-tab-pane :label="'近七天新数据 (' + counts.seven + ')'" name="seven">
<SevenCustomer v-if="activeTab === 'seven'" @update-count="updateCount('seven', $event)" />
</el-tab-pane>
</el-tabs>
</ContentWrap>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import AllCustomer from '@/views/crm/customer/team/tables/all.vue'
import TodayCustomer from '@/views/crm/customer/team/tables/today.vue'
import UnfollowCustomer from '@/views/crm/customer/team/tables/unfollow.vue'
import ImportCustomer from '@/views/crm/customer/team/tables/import.vue'
import SevenCustomer from '@/views/crm/customer/team/tables/seven.vue'
defineOptions({ name: 'CustomerTabs' })
const activeTab = ref('all')
const counts = reactive({
all: 0,
today: 0,
unfollow: 0,
import: 0,
seven: 0
})
//
const updateCount = (type: string, count: number) => {
counts[type] = count
}
const handleTabClick = () => {
//
}
</script>
<style lang="scss" scoped>
:deep(.el-tabs__content) {
padding: 20px 0;
}
</style>

View File

@ -131,15 +131,14 @@
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { buildTree } from '@/utils/tree'
import { buildTree } from '@/utils/tree'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
import { getAccessToken, getTenantId } from '@/utils/auth'
import download from '@/utils/download'
import { ChannelApi, ChannelVO } from '@/api/crm/config/channel'
import {CustomerSourceApi} from '@/api/crm/config/customersource'
import { ChannelVO } from '@/api/crm/config/channel'
import {UploadRecordApi} from '@/api/crm/data/upload/record'
import { CrmCustomerDataBelongEnum } from '@/utils/constants'
import * as UserApi from '@/api/system/user'
import { OpenSeaApi, OpenSeaVO } from '@/api/crm/opensea'
/** 导入记录 表单 */
defineOptions({ name: 'UploadRecordForm' })
@ -175,11 +174,9 @@ const formRules = reactive({
customerSourceId: [{ required: true, message: '请选择来源标签', trigger: 'blur' }]
})
const formRef = ref() // Ref
const openSeaList = ref<OpenSeaVO[]>([])
const customerSourceList = ref<[]>([])
const channelList = ref<ChannelVO[]>([])
let customerSourceOptions = ref<any>();
const userList = ref<UserApi.UserVO[]>([])
const crmStore = useCrmStore()
const { openSeaList, customerSourceList, channelList, userList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
const cascaderProps= {
emitPath: false,
}
@ -284,12 +281,20 @@ const importTemplate = async () => {
}
/** 初始化 **/
onMounted(async () => {
openSeaList.value=await OpenSeaApi.getOpenSeaList(null);
customerSourceList.value=await CustomerSourceApi.getCustomerSourceList(null);
customerSourceOptions.value=buildTree(customerSourceList.value);
channelList.value=await ChannelApi.getChannelList(null);
const newChannel: ChannelVO = { id: 0, name: '未知', type:'' , remark:""};
channelList.value.unshift(newChannel);
userList.value=await UserApi.getSimpleUsertList();
//
await Promise.all([
crmStore.getOpenSeaList(),
crmStore.getCustomerSourceList(),
crmStore.getChannelList(),
crmStore.getUserList()
])
//
customerSourceOptions.value = buildTree(customerSourceList.value)
//
const newChannel: ChannelVO = { id: 0, name: '未知', type: '', remark: "" }
// 使
channelList.value = [newChannel, ...channelList.value]
})
</script>

View File

@ -178,7 +178,8 @@ import { OpenSeaApi, OpenSeaVO } from '@/api/crm/opensea'
import { ChannelApi, ChannelVO } from '@/api/crm/config/channel'
import {CustomerSourceApi} from '@/api/crm/config/customersource'
import * as UserApi from '@/api/system/user'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
/** 导入记录 列表 */
defineOptions({ name: 'UploadRecord' })
@ -205,12 +206,10 @@ const queryParams = reactive({
channelId:undefined
})
const queryFormRef = ref() //
const openSeaList = ref<OpenSeaVO[]>([])
const customerSourceList = ref<[]>([])
const channelList = ref<ChannelVO[]>([])
const userList = ref<UserApi.UserVO[]>([])
let customerSourceOptions = ref<any>();
const crmStore = useCrmStore()
const { openSeaList, customerSourceList, channelList, userList } = storeToRefs(crmStore)
let customerSourceOptions = ref<any>()
const cascaderProps= {
emitPath: false,
}
@ -258,15 +257,20 @@ const handleDelete = async (id: number) => {
} catch {}
}
/** 初始化 **/
onMounted(async () => {
//
await Promise.all([
crmStore.getOpenSeaList(),
crmStore.getCustomerSourceList(),
crmStore.getChannelList(),
crmStore.getUserList()
])
//
customerSourceOptions.value = buildTree(customerSourceList.value)
//
await getList()
openSeaList.value = await OpenSeaApi.getOpenSeaList(null);
customerSourceList.value = await CustomerSourceApi.getCustomerSourceList(null);
customerSourceOptions.value = buildTree(customerSourceList.value);
channelList.value = await ChannelApi.getChannelList(null);
userList.value = await UserApi.getSimpleUsertList();
})
</script>

View File

@ -108,8 +108,9 @@
import { dateFormatter } from '@/utils/formatTime'
import { FollwRecordApi, FollwRecordVO } from '@/api/crm/follw'
import { buildTree } from '@/utils/tree'
import * as UserApi from '@/api/system/user'
import * as DepApi from '@/api/system/dept'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
/** 跟进记录 列表 */
defineOptions({ name: 'FollwRecord' })
@ -129,14 +130,15 @@ const queryParams = reactive({
createTime: []
})
const queryFormRef = ref() //
const userList = ref<UserApi.UserVO[]>([])
const depList = ref<DepApi.DeptVO[]>([])
let depOptions = ref<any>();
const cascaderProps= {
emitPath: false,
}
// 使 CRM store
const crmStore = useCrmStore()
const { userList, depList } = storeToRefs(crmStore)
let depOptions = ref<any>()
const cascaderProps = {
emitPath: false
}
/** 查询列表 */
const getList = async () => {
@ -162,12 +164,18 @@ const resetQuery = () => {
handleQuery()
}
/** 初始化 **/
onMounted(async () => {
//
await Promise.all([
crmStore.getUserList(),
crmStore.getDepList()
])
//
depOptions.value = buildTree(depList.value)
//
await getList()
userList.value=await UserApi.getSimpleUsertList();
depList.value=await DepApi.getSimpleDeptList();
depOptions.value=buildTree(depList.value);
})
</script>

View File

@ -84,7 +84,7 @@
class="!w-120px "
>
<el-option
v-for="option in followstatusList"
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
@ -134,9 +134,9 @@
</el-select>
</el-form-item>
<el-form-item label="最后跟进" prop="lastContactTime">
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastContactTime"
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
@ -188,7 +188,7 @@
<el-table-column label="跟进状态" align="center" prop="followStatusId" >
<template #default="scope">
<div>
{{ followstatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未知" }}
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未知" }}
</div>
</template>
</el-table-column >
@ -199,8 +199,8 @@
</div>
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastContactTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastContactContent" width="180px"/>
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
<template #default="scope">
<div>
@ -276,17 +276,11 @@
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { OpenSeaApi, OpenSeaVO } from '@/api/crm/opensea/index'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import { ImportLevelApi, ImportLevelVO } from '@/api/crm/config/Importlevel'
import { FollowStatusApi, FollowStatusVO } from '@/api/crm/config/followstatus'
import { CustomerSourceApi } from '@/api/crm/config/customersource'
import {CustomerTypeApi} from '@/api/crm/config/customertype'
import CustomerDetail from '@/views/crm/components/CustomerDetail/CustomerDetail.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import * as UserApi from '@/api/system/user'
import * as DepApi from '@/api/system/dept'
/** 客户信息 列表 */
defineOptions({ name: 'AllCustomer' })
@ -298,16 +292,14 @@ const loading = ref(true) // 列表的加载中
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const queryFormRef = ref() //
const importLevelList = ref<ImportLevelVO[]>([])
const followstatusList = ref<FollowStatusVO[]>([])
const userList = ref<UserApi.UserVO[]>([])
const depList = ref<DepApi.DeptVO[]>([])
const openSeaList = ref<OpenSeaVO[]>([])
const customerSourceList = ref<[]>([])
const customerTypeList = ref<[]>([])
let depOptions = ref<any>();
let customerSourceOptions = ref<any>();
let customerTypeOptions = ref<any>();
// 使 CRM store
const crmStore = useCrmStore()
const { importLevelList, followStatusList, customerSourceList, customerTypeList, depList, openSeaList } = storeToRefs(crmStore)
let depOptions = ref<any>()
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const multipleSelection = ref<CustomerInforVO[]>([])
@ -333,7 +325,7 @@ const queryParams = reactive({
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
lastContactTime: [],
lastFollowTime: [],
openSeaId: undefined as number | undefined,
createTime: []
})
@ -386,22 +378,27 @@ const handleSelectionChange = (val: CustomerInforVO[]) => {
/** 初始化 **/
onMounted(async () => {
//
importLevelList.value = await ImportLevelApi.getImportLevelList(null);
followstatusList.value = await FollowStatusApi.getFollowStatusList(null);
userList.value=await UserApi.getSimpleUsertList();
depList.value=await DepApi.getSimpleDeptList();
customerSourceList.value=await CustomerSourceApi.getCustomerSourceList(null);
customerTypeList.value=await CustomerTypeApi.getCustomerTypeList(null);
depOptions.value=buildTree(depList.value);
customerSourceOptions.value=buildTree(customerSourceList.value);
customerTypeOptions.value=buildTree(customerTypeList.value);
openSeaList.value=await OpenSeaApi.getOpenSeaList(null);
if (openSeaList.value?.length <=0) {
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList(),
crmStore.getUserList(),
crmStore.getDepList(),
crmStore.getOpenSeaList()
])
//
depOptions.value = buildTree(depList.value)
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
if (openSeaList.value?.length <= 0) {
message.error('你没有适配的公海')
}else{
queryParams.openSeaId = openSeaList.value[0].id;
await getList();
} else {
queryParams.openSeaId = openSeaList.value[0].id
await getList()
}
})

View File

@ -84,7 +84,7 @@
class="!w-120px "
>
<el-option
v-for="option in followstatusList"
v-for="option in followStatusList"
:key="option.id"
:value="option.id"
:label="option.name"
@ -134,9 +134,9 @@
</el-select>
</el-form-item>
<el-form-item label="最后跟进" prop="lastContactTime">
<el-form-item label="最后跟进" prop="lastFollowTime">
<el-date-picker
v-model="queryParams.lastContactTime"
v-model="queryParams.lastFollowTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
@ -188,7 +188,7 @@
<el-table-column label="跟进状态" align="center" prop="followStatusId" >
<template #default="scope">
<div>
{{ followstatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未知" }}
{{ followStatusList.find((cs) => cs.id === scope.row.followStatusId)?.name||"未知" }}
</div>
</template>
</el-table-column >
@ -199,8 +199,8 @@
</div>
</template>
</el-table-column >
<el-table-column label="最后跟进时间" align="center" prop="lastContactTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastContactContent" width="180px"/>
<el-table-column label="最后跟进时间" align="center" prop="lastFollowTime" :formatter="dateFormatter" width="180px" />
<el-table-column label="最后跟进内容" align="center" prop="lastFollowContent" width="180px"/>
<el-table-column label="客户类型" align="center" prop="customerTypeId" >
<template #default="scope">
<div>
@ -276,20 +276,15 @@
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { buildTree } from '@/utils/tree'
import { OpenSeaApi, OpenSeaVO } from '@/api/crm/opensea/index'
import { CustomerInforApi, CustomerInforVO } from '@/api/crm/customer/customer'
import { ImportLevelApi, ImportLevelVO } from '@/api/crm/config/Importlevel'
import { FollowStatusApi, FollowStatusVO } from '@/api/crm/config/followstatus'
import { CustomerSourceApi } from '@/api/crm/config/customersource'
import {CustomerTypeApi} from '@/api/crm/config/customertype'
import CustomerDetail from '@/views/crm/components/CustomerDetail/CustomerDetail.vue'
import { useCrmStore } from '@/store/modules/crm'
import { storeToRefs } from 'pinia'
import CustomerDetail from '@/views/crm/components/Customer/Detail.vue'
import AllocateForm from '@/views/crm/components/Allocate/AllocateForm.vue'
import * as UserApi from '@/api/system/user'
import * as DepApi from '@/api/system/dept'
/** 客户信息 列表 */
defineOptions({ name: 'AllCustomer' })
defineOptions({ name: 'MyOpenSea' })
const message = useMessage() //
const { t } = useI18n() //
@ -298,16 +293,14 @@ const loading = ref(true) // 列表的加载中
const list = ref<CustomerInforVO[]>([]) //
const total = ref(0) //
const queryFormRef = ref() //
const importLevelList = ref<ImportLevelVO[]>([])
const followstatusList = ref<FollowStatusVO[]>([])
const userList = ref<UserApi.UserVO[]>([])
const depList = ref<DepApi.DeptVO[]>([])
const openSeaList = ref<OpenSeaVO[]>([])
const customerSourceList = ref<[]>([])
const customerTypeList = ref<[]>([])
let depOptions = ref<any>();
let customerSourceOptions = ref<any>();
let customerTypeOptions = ref<any>();
// 使 CRM store
const crmStore = useCrmStore()
const { importLevelList, followStatusList, customerSourceList, customerTypeList, depList, openSeaList } = storeToRefs(crmStore)
let depOptions = ref<any>()
let customerSourceOptions = ref<any>()
let customerTypeOptions = ref<any>()
const drawerVisible = ref(false) //
const customerId = ref<number>() // ID
const multipleSelection = ref<CustomerInforVO[]>([])
@ -333,7 +326,7 @@ const queryParams = reactive({
customerTypeId: undefined,
importLevelId: undefined,
followStatusId: undefined,
lastContactTime: [],
lastFollowTime: [],
openSeaId: undefined as number | undefined,
createTime: []
})
@ -386,26 +379,28 @@ const handleSelectionChange = (val: CustomerInforVO[]) => {
/** 初始化 **/
onMounted(async () => {
//
importLevelList.value = await ImportLevelApi.getImportLevelList(null);
followstatusList.value = await FollowStatusApi.getFollowStatusList(null);
userList.value=await UserApi.getSimpleUsertList();
depList.value=await DepApi.getSimpleDeptList();
customerSourceList.value=await CustomerSourceApi.getCustomerSourceList(null);
customerTypeList.value=await CustomerTypeApi.getCustomerTypeList(null);
depOptions.value=buildTree(depList.value);
customerSourceOptions.value=buildTree(customerSourceList.value);
customerTypeOptions.value=buildTree(customerTypeList.value);
openSeaList.value=await OpenSeaApi.getOpenSeaListByCurUser();
if (openSeaList.value?.length <=0) {
//
await Promise.all([
crmStore.getImportLevelList(),
crmStore.getFollowStatusList(),
crmStore.getCustomerSourceList(),
crmStore.getCustomerTypeList(),
crmStore.getUserList(),
crmStore.getDepList(),
crmStore.getOpenSeaListByCurUser()
])
//
depOptions.value = buildTree(depList.value)
customerSourceOptions.value = buildTree(customerSourceList.value)
customerTypeOptions.value = buildTree(customerTypeList.value)
if (openSeaList.value?.length <= 0) {
message.error('你没有适配的公海')
}else{
queryParams.openSeaId = openSeaList.value[0].id;
await getList();
} else {
queryParams.openSeaId = openSeaList.value[0].id
await getList()
}
})
/** 领取客户 */