feat: 客户管理

master
李小林 9 months ago
parent 06a9bdbb6c
commit 3e8ec67400
  1. 2
      src/App.vue
  2. 23
      src/api/customer/index.ts
  3. 13
      src/api/customer/types.ts
  4. 11
      src/router/index.ts
  5. 588
      src/views/resources/customer/components/AddCustom.vue
  6. 227
      src/views/resources/customer/index.vue

@ -47,7 +47,7 @@ const fontColor = computed(() => {
.el-card__footer {
padding: calc(10px - 2px) 10px !important;
}
.is-horizontal {
.el-scrollbar__thumb {
display: none !important;
}
</style>

@ -1,5 +1,5 @@
import { AxiosPromise } from "axios";
import { CustomerResult } from "@/api/customer/types";
import { CustomerResult, CustomForm } from "@/api/customer/types";
import request from "@/utils/request";
export function customerPage(data: SelectForm): AxiosPromise<CustomerResult> {
@ -9,3 +9,24 @@ export function customerPage(data: SelectForm): AxiosPromise<CustomerResult> {
data,
});
}
export function updateStatus(customId?: number, status?: string) {
return request({
url: `/api/customer/v1/update-status/${customId}/${status}`,
method: "PUT",
});
}
export function deleteCustom(ids: number[]) {
return request({
url: `/api/customer/v1/delete-custom`,
method: "POST",
data: ids,
});
}
export function addCustom(data: CustomForm): AxiosPromise<number> {
return request({
url: `/api/customer/v1/add-custom`,
method: "POST",
data,
});
}

@ -6,3 +6,16 @@ export interface CustomerVO {
regionAreaName?: string;
}
export type CustomerResult = PageResult<CustomerVO[]>;
export interface CustomForm {
custId?: number;
regionAreaId?: number;
customAccount?: string;
customName?: string;
customAddr?: string;
contactType?: string;
customType?: string;
customStatus?: string;
customRemark1?: string;
customRemark2?: string;
customRemark3?: string;
}

@ -171,6 +171,17 @@ export const constantRoutes: RouteRecordRaw[] = [
title: "设备绑定客户",
},
},
{
path: "/resources/custom-add",
name: "AddCustom",
component: () =>
import("@/views/resources/customer/components/AddCustom.vue"),
meta: {
hidden: true,
keepAlive: true,
title: "新增客户",
},
},
],
},
];

@ -0,0 +1,588 @@
<template>
<div class="app-container">
<div v-show="active === 1">
<el-card shadow="never" v-loading="loading">
<template #header>
<div
style="
font-weight: 700;
font-size: 14px;
line-height: 32px;
display: flex;
justify-content: space-between;
"
>
<div>新增客户信息</div>
<div v-if="formData.custId === undefined">
<el-button
type="primary"
:icon="Position"
@click="submitForm(addCustomRef)"
>保存
</el-button>
<el-button
type="danger"
:icon="Refresh"
@click="resetForm(addCustomRef)"
>重置
</el-button>
</div>
</div>
</template>
<el-form :model="formData" ref="addCustomRef" :rules="rules">
<el-descriptions :column="2" border>
<el-descriptions-item
label="客户账号"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item style="margin-bottom: 0" prop="customAccount">
<el-input
v-model="formData.customAccount"
placeholder="请填写客户账号"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="客户名称"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item style="margin-bottom: 0" prop="customName">
<el-input
v-model="formData.customName"
placeholder="请填写客户名称"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="客户状态"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item style="margin-bottom: 0" prop="customStatus">
<dictionary
type-code="custom_status"
v-model="formData.customStatus"
placeholder="请选择客户状态"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="客户类型"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item style="margin-bottom: 0" prop="customType">
<dictionary
type-code="custom_type"
v-model="formData.customType"
placeholder="请选择客户类型"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="客户类型"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item style="margin-bottom: 0" prop="regionAreaId">
<el-tree-select
v-model="formData.regionAreaId"
placeholder="请选择所属管理域"
:data="domainOptions"
filterable
check-strictly
:render-after-expand="false"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="客户端口号"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item style="margin-bottom: 0" prop="customRemark1">
<el-input
v-model="formData.customRemark1"
placeholder="请输入客户端口号"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="接入电话号码"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item style="margin-bottom: 0" prop="customRemark2">
<el-input
v-model="formData.customRemark2"
placeholder="请输入接入电话号码"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="客户身份证号码"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item style="margin-bottom: 0" prop="customRemark3">
<el-input
v-model="formData.customRemark3"
placeholder="请输入客户身份证号码"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="客户地址"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item style="margin-bottom: 0" prop="customAddr">
<el-input
v-model="formData.customAddr"
placeholder="请输入客户地址"
/>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item
label="联系人方式"
label-align="left"
align="center"
label-class-name="my-label"
class-name="my-content"
width="150px"
>
<el-form-item style="margin-bottom: 0" prop="contactType">
<el-input
v-model="formData.contactType"
placeholder="请输入联系人方式"
/>
</el-form-item>
</el-descriptions-item>
</el-descriptions>
</el-form>
</el-card>
<el-card shadow="never" style="margin-top: 10px">
<template #header>
<div
style="
font-weight: 700;
font-size: 14px;
line-height: 32px;
display: flex;
justify-content: space-between;
"
>
<div>客户绑定的设备信息列表</div>
<div>
<el-button
type="primary"
v-if="formData.custId != undefined"
@click="active++"
>
绑定设备</el-button
>
</div>
</div>
</template>
<div class="any-table">
<el-table :data="tableData" v-loading="tableLoading" max-height="120">
<el-table-column
label="设备标识"
align="center"
width="240"
show-overflow-tooltip
>
<template #default="scope">
<el-button type="primary" link @click="skipDev(scope.row.devId)"
>{{ scope.row.devVendorOui }}-{{ scope.row.devSno }}
</el-button>
</template>
</el-table-column>
<el-table-column
label="AD编号"
align="center"
prop="devAdNo"
show-overflow-tooltip
/>
<el-table-column
label="宽带账号"
align="center"
prop="devPppoe"
show-overflow-tooltip
/>
<el-table-column
label="设备类型"
align="center"
width="250"
show-overflow-tooltip
>
<template #default="scope">
<el-button
type="primary"
link
@click="redirect(scope.row.devTypeId)"
>
{{ scope.row.devVendorName }}&nbsp;{{
scope.row.devTypeName
}}&nbsp;{{ scope.row.devHardVer }}
</el-button>
</template>
</el-table-column>
<el-table-column label="软件版本" align="center" prop="softVer" />
<el-table-column label="设备状态" align="center" prop="devStatus">
<template #default="scope">
<el-tag>{{ scope.row.devStatus }}</el-tag>
</template>
</el-table-column>
<el-table-column label="在线状态" align="center" prop="devOnline">
<template #default="scope">
<el-tag>{{ scope.row.devOnline }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-popconfirm
width="240"
confirm-button-text="确认"
cancel-button-text="取消"
:icon="InfoFilled"
icon-color="#626AEF"
@confirm="unbinding(scope.row.devId)"
title="确认解除设备与客户的绑定?"
>
<template #reference>
<el-button type="primary" link>解除绑定</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<pagination
v-if="total > 0"
v-model:total="total"
v-model:page="queryForm.pageNum"
v-model:limit="queryForm.pageSize"
@pagination="getCurrentBindingDev"
/>
</div>
</el-card>
</div>
<div v-show="active === 2">
<div class="search-container">
<el-form :model="bindingQueryForm" :inline="true">
<el-row>
<el-col :span="6">
<el-form-item label="查询条件">
<el-select
v-model="bindingQueryForm.selectName"
placeholder="请选择"
style="width: 240px"
clearable
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6" v-if="bindingQueryForm.selectName != undefined">
<el-input
v-model="bindingQueryForm.selectValue"
placeholder="请输入"
style="width: 300px"
/>
</el-col>
<el-col :span="buttonColSpan">
<el-form-item>
<el-button type="primary" :icon="Search" @click="getData"
>搜索</el-button
>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<el-card shadow="never">
<template #header>
<div style="display: flex; justify-content: flex-end">
<el-button type="primary" @click="bindingDev">绑定</el-button>
<el-button type="primary" @click="active--">返回</el-button>
</div>
</template>
<div class="any-table">
<el-table
:data="tableData1"
v-loading="tableLoading"
highlight-current-row
@current-change="handleDevChange"
>
<el-table-column
label="设备标识"
align="center"
width="240"
show-overflow-tooltip
>
<template #default="scope">
<el-button type="primary" link @click="skipDev(scope.row.devId)"
>{{ scope.row.devVendorOui }}-{{ scope.row.devSno }}
</el-button>
</template>
</el-table-column>
<el-table-column
label="AD编号"
align="center"
show-overflow-tooltip
prop="devAdNo"
/>
<el-table-column
label="宽带账号"
align="center"
prop="devPppoe"
show-overflow-tooltip
/>
<el-table-column
label="设备类型"
align="center"
width="250"
show-overflow-tooltip
>
<template #default="scope">
<el-button
type="primary"
link
@click="redirect(scope.row.devTypeId)"
>
{{ scope.row.devVendorName }}&nbsp;{{
scope.row.devTypeName
}}&nbsp;{{ scope.row.devHardVer }}
</el-button>
</template>
</el-table-column>
<el-table-column label="软件版本" align="center" prop="softVer" />
<el-table-column label="设备状态" align="center" prop="devStatus">
<template #default="scope">
<el-tag>{{ scope.row.devStatus }}</el-tag>
</template>
</el-table-column>
<el-table-column label="在线状态" align="center" prop="devOnline">
<template #default="scope">
<el-tag>{{ scope.row.devOnline }}</el-tag>
</template>
</el-table-column>
</el-table>
<pagination
v-if="total1 > 0"
v-model:total="total1"
v-model:page="bindingQueryForm.pageNum"
v-model:limit="bindingQueryForm.pageSize"
@pagination="getData"
/>
</div>
</el-card>
</div>
</div>
</template>
<script setup lang="ts">
import { InfoFilled, Position, Refresh, Search } from "@element-plus/icons-vue";
import { CustomForm } from "@/api/customer/types";
import { ElTable, FormInstance, FormRules } from "element-plus";
import { getDomainOptions } from "@/api/domain";
import { addCustom } from "@/api/customer";
import { EquipmentTableVO } from "@/api/resources-equipment/types";
import { bindingCustomAPI, getEquipmentPage, unbindingCustomAPI } from "@/api/resources-equipment";
const formData = ref<CustomForm>({});
const active = ref<number>(1);
const addCustomRef = ref<FormInstance>();
const domainOptions = ref<OptionType[]>([]);
const loading = ref<boolean>(false);
const total = ref<number>(0);
const tableData = ref<EquipmentTableVO[]>([]);
const total1 = ref<number>(0);
const tableData1 = ref<EquipmentTableVO[]>([]);
const tableLoading = ref<boolean>(false);
const queryForm = ref<SelectForm>({
pageNum: 1,
pageSize: 10,
selectName: "customId",
});
const bindingQueryForm = ref<SelectForm>({
pageNum: 1,
pageSize: 10,
});
const buttonColSpan = computed(() => {
return queryForm.value.selectName === undefined ? 18 : 12;
});
const options = ref<OptionType[]>([
{ label: "AD编号", value: "devAdNo" },
{ label: "客户名称", value: "customName" },
]);
const rules = reactive<FormRules<CustomForm>>({
customAccount: [
{ required: true, message: "请输入客户账号", trigger: "blur" },
],
customName: [{ required: true, message: "请输入客户名称", trigger: "blur" }],
customType: [{ required: true, message: "请选择客户类型", trigger: "blur" }],
customStatus: [
{ required: true, message: "请选择客户状态", trigger: "blur" },
],
regionAreaId: [
{ required: true, message: "请选择系统管理域", trigger: "blur" },
],
});
const router = useRouter();
const redirect = (devTypeId: number) => {
router.push({
path: `/resources/device_type_to_ver/${devTypeId}`,
});
};
const skipDev = (devId: number) => {
router.push({
path: `/resources/equipment-edit/${devId}`,
});
};
const currentDevId = ref<number>(0);
const handleDevChange = (val: EquipmentTableVO) => {
currentDevId.value = val.devId;
};
async function loadDomainOptions() {
await getDomainOptions().then(({ data }) => {
domainOptions.value = data;
});
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
loading.value = true;
addCustom(formData.value)
.then(({ data }) => {
formData.value.custId = data;
})
.finally(() => {
loading.value = false;
});
}
});
};
const getCurrentBindingDev = () => {
queryForm.value.selectValue = formData.value.custId;
tableLoading.value = true;
getEquipmentPage(queryForm.value)
.then(({ data }) => {
tableData.value = data.list;
total.value = data.total;
})
.finally(() => {
tableLoading.value = false;
});
};
const getData = () => {
if (
bindingQueryForm.value.selectName === undefined ||
bindingQueryForm.value.selectValue === undefined
) {
ElMessage({
message: "查询条件不能为空",
type: "error",
duration: 1000,
});
return;
}
tableLoading.value = true;
getEquipmentPage(bindingQueryForm.value)
.then(({ data }) => {
tableData1.value = data.list;
total1.value = data.total;
})
.finally(() => {
tableLoading.value = false;
});
};
const bindingDev = () => {
if (currentDevId.value === 0) {
ElMessage({
message: "请选择一个设备进行绑定",
duration: 1000,
type: "error",
});
return;
}
tableLoading.value = true;
bindingCustomAPI(currentDevId.value, formData.value.custId)
.then(() => {
ElMessage({
message: "操作成功",
duration: 1000,
type: "success",
onClose: () => {
active.value--;
getCurrentBindingDev();
},
});
})
.finally(() => {
tableLoading.value = false;
});
};
const unbinding = (devId?: number) => {
tableLoading.value = true;
unbindingCustomAPI(devId).then(() => {
ElMessage({
message: "操作成功",
type: "success",
duration: 1000,
onClose: () => {
getCurrentBindingDev();
},
});
});
};
onMounted(() => {
loadDomainOptions();
});
</script>
<style scoped>
:deep(.my-label) {
background: var(--el-color-white) !important;
}
</style>

@ -0,0 +1,227 @@
<template>
<div class="app-container">
<div class="search-container">
<el-form :model="queryForm" :inline="true">
<el-row>
<el-col :span="6">
<el-form-item label="查询条件">
<el-select
v-model="queryForm.selectName"
placeholder="请选择"
style="width: 240px"
clearable
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6" v-if="queryForm.selectName != undefined">
<el-input
v-model="queryForm.selectValue"
placeholder="请输入"
style="width: 300px"
/>
</el-col>
<el-col :span="buttonColSpan">
<el-form-item>
<el-button type="primary" :icon="Search" @click="queryCustomer"
>搜索
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<el-card shadow="never">
<template #header>
<div style="display: flex; justify-content: flex-end">
<el-button type="primary" :icon="Plus" @click="skipAddCustom"
>新增</el-button
>
<el-popconfirm
width="200"
confirm-button-text="确认"
cancel-button-text="取消"
:icon="InfoFilled"
icon-color="#626AEF"
@confirm="handleDelete"
title="确认删除所选客户吗?"
>
<template #reference>
<el-button
type="danger"
:disabled="multiples.length === 0"
:icon="Delete"
>删除</el-button
>
</template>
</el-popconfirm>
</div>
</template>
<div class="any-table">
<el-table
:data="tableData"
@selection-change="handleSelectionChange"
v-loading="loading"
max-height="350"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="customType" label="客户类型" align="center" />
<el-table-column
prop="regionAreaName"
label="客户系统管理域"
align="center"
/>
<el-table-column
prop="customStatus"
label="客户状态"
align="center"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<div v-if="scope.row.customStatus === '正常'">
<el-popconfirm
width="240"
confirm-button-text="确认"
cancel-button-text="取消"
:icon="InfoFilled"
icon-color="#626AEF"
@confirm="updateCustomStatus(scope.row)"
title="确认注销该客户吗?"
>
<template #reference>
<el-button type="danger" link>注销客户 </el-button>
</template>
</el-popconfirm>
</div>
<div v-else>
<el-popconfirm
width="240"
confirm-button-text="确认"
cancel-button-text="取消"
:icon="InfoFilled"
icon-color="#626AEF"
@confirm="updateCustomStatus(scope.row)"
title="确认恢复该客户吗?"
>
<template #reference>
<el-button type="primary" link>恢复用户</el-button>
</template>
</el-popconfirm>
</div>
</template>
</el-table-column>
</el-table>
<pagination
v-if="total > 0"
v-model:total="total"
v-model:page="queryForm.pageNum"
v-model:limit="queryForm.pageSize"
@pagination="queryCustomer"
/>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import {
Delete,
InfoFilled,
Plus,
Position,
Search,
} from "@element-plus/icons-vue";
import { ElTable } from "element-plus";
import { CustomerVO } from "@/api/customer/types";
import { customerPage, deleteCustom, updateStatus } from "@/api/customer";
defineOptions({
name: "Customer",
inheritAttrs: false,
});
const queryForm = ref<SelectForm>({
pageNum: 1,
pageSize: 10,
});
const multiples = ref<CustomerVO[]>([]);
const loading = ref<boolean>(false);
const tableData = ref<CustomerVO[]>([]);
const total = ref<number>(0);
const router = useRouter();
const options = ref<OptionType[]>([{ label: "客户名称", value: "customName" }]);
const buttonColSpan = computed(() => {
return queryForm.value.selectName === undefined ? 18 : 12;
});
const queryCustomer = () => {
if (
queryForm.value.selectName === undefined ||
queryForm.value.selectValue === undefined
) {
ElMessage({
message: "查询条件不能为空",
type: "error",
duration: 1000,
});
return;
}
loading.value = true;
customerPage(queryForm.value)
.then(({ data }) => {
tableData.value = data.list;
total.value = data.total;
})
.finally(() => {
loading.value = false;
});
};
const updateCustomStatus = (val: CustomerVO) => {
loading.value = true;
let status = val.customStatus === "正常" ? "1" : "0";
updateStatus(val.customId, status).then(() => {
ElMessage({
message: "操作成功",
duration: 1000,
type: "success",
onClose: () => {
queryCustomer();
},
});
});
};
const handleSelectionChange = (val: CustomerVO[]) => {
multiples.value = val;
};
const handleDelete = () => {
let ids: number[] = [];
multiples.value.forEach((obj) => {
ids.push(<number>obj.customId);
});
loading.value = true;
deleteCustom(ids)
.then(() => {
ElMessage({
message: "操作成功",
duration: 1000,
type: "success",
onClose: () => {
queryCustomer();
},
});
})
.finally(() => {
loading.value = false;
});
};
const skipAddCustom = () => {
router.push({
path: `/resources/custom-add`,
});
};
</script>
<style scoped></style>
Loading…
Cancel
Save