增加商机线索管理修复若干bug
This commit is contained in:
@@ -46,9 +46,20 @@
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="letter-bar">
|
||||
<span
|
||||
v-for="item in letterBar"
|
||||
:key="item"
|
||||
class="letter-item"
|
||||
:class="{ active: activeLetter === item, empty: item !== '全部' && !letterCounts[item] }"
|
||||
@click="handleLetterClick(item)"
|
||||
>
|
||||
{{ item }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="table-container" v-loading="loading">
|
||||
<el-table :data="tableData" stripe border>
|
||||
<el-table-column type="index" label="#" width="56" align="center" />
|
||||
<el-table :data="pagedContacts" stripe border>
|
||||
<el-table-column label="姓名" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<div class="name-cell-wrapper">
|
||||
@@ -78,18 +89,9 @@
|
||||
<el-table-column label="更新时间" width="160" align="center">
|
||||
<template #default="{ row }">{{ formatDateTime(row.update_time) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="140" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button
|
||||
v-if="Number(row.is_primary) !== 1"
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleSetPrimary(row)"
|
||||
>
|
||||
设为主联系人
|
||||
</el-button>
|
||||
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -102,11 +104,10 @@
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:total="totalCount"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSearch"
|
||||
@current-change="fetchList"
|
||||
@size-change="handlePageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,10 +166,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import { ref, reactive, computed, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Plus, Search, Refresh } from "@element-plus/icons-vue";
|
||||
import { listContacts, updateContact, deleteContact } from "@/api/contact";
|
||||
import { pinyin } from "pinyin-pro";
|
||||
import { listContacts, deleteContact } from "@/api/contact";
|
||||
import ContactEdit from "./components/edit.vue";
|
||||
import {
|
||||
RELATED_TYPE_OPTIONS,
|
||||
@@ -178,7 +180,6 @@ import {
|
||||
} from "../dict";
|
||||
|
||||
const loading = ref(false);
|
||||
const tableData = ref([]);
|
||||
const editVisible = ref(false);
|
||||
const currentEditData = ref(null);
|
||||
|
||||
@@ -186,6 +187,16 @@ const currentEditData = ref(null);
|
||||
const detailVisible = ref(false);
|
||||
const detailData = ref(null);
|
||||
|
||||
// 全量联系人(用于本地 A-Z 排序 / 首字母筛选)
|
||||
const allContacts = ref([]);
|
||||
|
||||
// A-Z 索引
|
||||
const collator = new Intl.Collator("zh-Hans-CN", { sensitivity: "base" });
|
||||
const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
.split("")
|
||||
.filter((l) => !["I", "U", "V"].includes(l));
|
||||
const activeLetter = ref("全部");
|
||||
|
||||
const filters = reactive({
|
||||
keyword: "",
|
||||
related_type: "",
|
||||
@@ -195,28 +206,93 @@ const filters = reactive({
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
/** 取姓名首字母:中文按拼音、英文按字母,数字/符号/无法识别归 # */
|
||||
function initialOf(name) {
|
||||
const s = String(name || "").trim();
|
||||
if (!s) return "#";
|
||||
const first = s.charAt(0);
|
||||
if (/[a-zA-Z]/.test(first)) return first.toUpperCase();
|
||||
if (!/[\u4e00-\u9fa5]/.test(first)) return "#";
|
||||
try {
|
||||
const py = pinyin(first, { pattern: "first", toneType: "none" });
|
||||
const letter = String(py || "").charAt(0).toUpperCase();
|
||||
return /[A-Z]/.test(letter) ? letter : "#";
|
||||
} catch (e) {
|
||||
return "#";
|
||||
}
|
||||
}
|
||||
|
||||
function rankOf(letter) {
|
||||
return letter === "#" ? 99 : letter.charCodeAt(0) - 65;
|
||||
}
|
||||
|
||||
/** A-Z 排序(「全部」视图同样按 A-Z) */
|
||||
const sortedContacts = computed(() => {
|
||||
const list = [...allContacts.value];
|
||||
list.sort((a, b) => {
|
||||
const la = initialOf(a.contact_name);
|
||||
const lb = initialOf(b.contact_name);
|
||||
if (la !== lb) return rankOf(la) - rankOf(lb);
|
||||
return collator.compare(a.contact_name || "", b.contact_name || "");
|
||||
});
|
||||
return list;
|
||||
});
|
||||
|
||||
/** 各首字母数量 */
|
||||
const letterCounts = computed(() => {
|
||||
const map = {};
|
||||
for (const c of allContacts.value) {
|
||||
const l = initialOf(c.contact_name);
|
||||
map[l] = (map[l] || 0) + 1;
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
/** 顶部索引:全部 + A-Z(存在 # 数据时追加 #) */
|
||||
const letterBar = computed(() => [
|
||||
"全部",
|
||||
...LETTERS,
|
||||
...(letterCounts.value["#"] ? ["#"] : []),
|
||||
]);
|
||||
|
||||
/** 按首字母筛选(全部 → 不筛选) */
|
||||
const filteredContacts = computed(() => {
|
||||
if (activeLetter.value === "全部") return sortedContacts.value;
|
||||
return sortedContacts.value.filter((c) => initialOf(c.contact_name) === activeLetter.value);
|
||||
});
|
||||
|
||||
/** 本地分页 */
|
||||
const pagedContacts = computed(() => {
|
||||
const start = (pagination.page - 1) * pagination.pageSize;
|
||||
return filteredContacts.value.slice(start, start + pagination.pageSize);
|
||||
});
|
||||
|
||||
const totalCount = computed(() => filteredContacts.value.length);
|
||||
|
||||
onMounted(() => {
|
||||
fetchList();
|
||||
});
|
||||
|
||||
/** 拉取全量联系人(分页循环,最多 50 页),本地做 A-Z 排序与筛选 */
|
||||
async function fetchList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await listContacts({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
...filters,
|
||||
});
|
||||
const data = res?.data || {};
|
||||
const list = Array.isArray(data) ? data : data.list || [];
|
||||
tableData.value = list;
|
||||
pagination.total = data.total || list.length;
|
||||
const pageSize = 100;
|
||||
let all = [];
|
||||
let total = 0;
|
||||
for (let page = 1; page <= 50; page++) {
|
||||
const res = await listContacts({ page, pageSize, ...filters });
|
||||
const data = res?.data || {};
|
||||
const list = Array.isArray(data) ? data : data.list || [];
|
||||
all = all.concat(list);
|
||||
total = data.total || all.length;
|
||||
if (list.length === 0 || all.length >= total) break;
|
||||
}
|
||||
allContacts.value = all;
|
||||
} catch (e) {
|
||||
tableData.value = [];
|
||||
pagination.total = 0;
|
||||
allContacts.value = [];
|
||||
ElMessage.error(e.message || "查询失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -232,9 +308,19 @@ function resetFilters() {
|
||||
filters.keyword = "";
|
||||
filters.related_type = "";
|
||||
filters.is_primary = "";
|
||||
activeLetter.value = "全部";
|
||||
handleSearch();
|
||||
}
|
||||
|
||||
function handleLetterClick(item) {
|
||||
activeLetter.value = item;
|
||||
pagination.page = 1;
|
||||
}
|
||||
|
||||
function handlePageSizeChange() {
|
||||
pagination.page = 1;
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
currentEditData.value = null;
|
||||
editVisible.value = true;
|
||||
@@ -250,16 +336,6 @@ function openEdit(row) {
|
||||
editVisible.value = true;
|
||||
}
|
||||
|
||||
async function handleSetPrimary(row) {
|
||||
try {
|
||||
await updateContact({ ...row, is_primary: 1 });
|
||||
ElMessage.success("已设为主联系人");
|
||||
fetchList();
|
||||
} catch (e) {
|
||||
ElMessage.error(e.message || "操作失败");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
@@ -318,6 +394,47 @@ async function handleDelete(row) {
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.letter-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 12px;
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 6px;
|
||||
|
||||
.letter-item {
|
||||
min-width: 28px;
|
||||
height: 26px;
|
||||
padding: 0 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-regular);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&.empty {
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.name-cell-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user