重构整个项目
This commit is contained in:
@@ -57,24 +57,39 @@ export const ticketApi = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const customerApi = {
|
export const customerApi = {
|
||||||
getCustomers() {
|
|
||||||
return adminRequest.get('/customers')
|
|
||||||
},
|
|
||||||
|
|
||||||
// 添加客户,data 结构应为 { company_name: 'xxx' }
|
// 添加客户,data 结构应为 { company_name: 'xxx' }
|
||||||
addCustomerName(data) {
|
addCompany(data) {
|
||||||
return adminRequest.post('/addcustomername', data)
|
return adminRequest.post('/addCompany', data)
|
||||||
},
|
},
|
||||||
|
|
||||||
addEmployee(data) {
|
addOperator(data) {
|
||||||
return adminRequest.post('/addemployee', data)
|
return adminRequest.post('/addOperator', data)
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteEmployee(empId) {
|
deleteOperator(operatorId) { // 已修改
|
||||||
return adminRequest.delete(`/deleteemployee/${empId}`)
|
return adminRequest.delete(`/deleteoperator/${operatorId}`)
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteCustomer(customerId) {
|
deleteCompany(CompanyId) { // 已修改
|
||||||
return adminRequest.delete(`/deletecustomer/${customerId}`)
|
return adminRequest.delete(`/deletecompany/${CompanyId}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const accountApi = {
|
||||||
|
getAllUsers() {
|
||||||
|
return adminRequest.get('/getAllUsers')
|
||||||
|
},
|
||||||
|
|
||||||
|
addAccount(data) {
|
||||||
|
return adminRequest.post('/addAccount', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
updateAccountStatus(id, status) {
|
||||||
|
return adminRequest.put(`/account/${id}/status`, { status })
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteAccount(id) {
|
||||||
|
return adminRequest.delete(`/account/${id}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog :model-value="modelValue" @update:model-value="$emit('update:modelValue', $event)" title="新增客户员工"
|
|
||||||
width="450px">
|
|
||||||
<el-form :model="form" label-width="100px">
|
|
||||||
<el-form-item label="员工姓名" required>
|
|
||||||
<el-input v-model="form.employee_name" placeholder="请输入姓名" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="所属客户" required>
|
|
||||||
<el-select v-model="form.customer_id" placeholder="请选择归属单位" style="width: 100%">
|
|
||||||
<el-option v-for="item in customerList" :key="item.customer_id" :label="item.company_name"
|
|
||||||
:value="item.customer_id" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="$emit('update:modelValue', false)">取消</el-button>
|
|
||||||
<el-button type="success" @click="handleSubmit">保存员工</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, watch } from "vue";
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import { customerApi } from "@/api/ticket";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: Boolean,
|
|
||||||
customerList: Array,
|
|
||||||
});
|
|
||||||
const emit = defineEmits(["update:modelValue", "success"]);
|
|
||||||
|
|
||||||
const form = ref({ employee_name: "", customer_id: null });
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(val) => {
|
|
||||||
if (val) form.value = { employee_name: "", customer_id: null };
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
if (!form.value.employee_name || !form.value.customer_id) {
|
|
||||||
return ElMessage.error("请填写员工姓名并选择所属单位");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await customerApi.addEmployee({
|
|
||||||
emp_name: form.value.employee_name,
|
|
||||||
customer_id: form.value.customer_id,
|
|
||||||
});
|
|
||||||
if (res.code === 200) {
|
|
||||||
ElMessage.success("员工保存成功");
|
|
||||||
emit("success");
|
|
||||||
emit("update:modelValue", false);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error("保存失败");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog
|
|
||||||
:model-value="modelValue"
|
|
||||||
@update:model-value="$emit('update:modelValue', $event)"
|
|
||||||
:title="`【${customerName}】员工名单`"
|
|
||||||
width="500px"
|
|
||||||
>
|
|
||||||
<el-table :data="employees" border stripe>
|
|
||||||
<el-table-column prop="employee_id" label="员工ID" width="100" />
|
|
||||||
<el-table-column prop="employee_name" label="姓名" />
|
|
||||||
<el-table-column label="操作" width="100" align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button
|
|
||||||
size="small"
|
|
||||||
type="danger"
|
|
||||||
link
|
|
||||||
@click="handleDelete(scope.row.employee_id)"
|
|
||||||
>移除</el-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
|
||||||
import { customerApi } from "@/api/ticket";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: Boolean,
|
|
||||||
customerName: String,
|
|
||||||
employees: Array,
|
|
||||||
});
|
|
||||||
const emit = defineEmits(["update:modelValue", "refresh"]);
|
|
||||||
|
|
||||||
const handleDelete = (empId) => {
|
|
||||||
ElMessageBox.confirm("确定要从该单位移除此员工吗?", "提示", { type: "warning" })
|
|
||||||
.then(async () => {
|
|
||||||
try {
|
|
||||||
const res = await customerApi.deleteEmployee(empId);
|
|
||||||
if (res.code == 200 || res.code == true) {
|
|
||||||
ElMessage.success("操作成功");
|
|
||||||
emit("refresh"); // 通知父组件重新获取数据
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
ElMessage.error("删除失败");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -33,16 +33,18 @@ watch(
|
|||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!form.value.company_name) return ElMessage.error("名称不能为空");
|
if (!form.value.company_name) return ElMessage.error("名称不能为空");
|
||||||
try {
|
try {
|
||||||
const res = await customerApi.addCustomerName({
|
const res = await customerApi.addCompany({
|
||||||
company_name: form.value.company_name,
|
company_name: form.value.company_name,
|
||||||
});
|
});
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
ElMessage.success("客户单位创建成功");
|
ElMessage.success("客户单位创建成功");
|
||||||
emit("success");
|
emit("success");
|
||||||
emit("update:modelValue", false);
|
emit("update:modelValue", false);
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || "创建失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error("创建失败");
|
ElMessage.error(res.msg || "创建失败");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
63
src/components/operators/OperatorEditDialog.vue
Normal file
63
src/components/operators/OperatorEditDialog.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog :model-value="modelValue" @update:model-value="$emit('update:modelValue', $event)" title="新增运营人员"
|
||||||
|
width="450px">
|
||||||
|
<el-form :model="form" label-width="100px">
|
||||||
|
<el-form-item label="客户单位" required>
|
||||||
|
<el-select v-model="form.company_id" placeholder="请选择归属客户" style="width: 100%">
|
||||||
|
<el-option v-for="item in companyList" :key="item.company_id" :label="item.company_name"
|
||||||
|
:value="item.company_id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="运营人员" required>
|
||||||
|
<el-input v-model="form.operator_name" placeholder="请输入运营人员姓名" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="$emit('update:modelValue', false)">取消</el-button>
|
||||||
|
<el-button type="success" @click="handleSubmit">保存员工</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { customerApi } from "@/api/ticket";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: Boolean,
|
||||||
|
companyList: Array,
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["update:modelValue", "success"]);
|
||||||
|
|
||||||
|
const form = ref({ operator_name: "", company_id: null });
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val) => {
|
||||||
|
if (val) form.value = { operator_name: "", company_id: null };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!form.value.operator_name || !form.value.company_id) {
|
||||||
|
return ElMessage.error("请填写运营人员姓名并选择所属单位");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await customerApi.addOperator({
|
||||||
|
operator_name: form.value.operator_name,
|
||||||
|
company_id: form.value.company_id,
|
||||||
|
status: 1
|
||||||
|
});
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success("运营人员保存成功");
|
||||||
|
emit("success");
|
||||||
|
emit("update:modelValue", false);
|
||||||
|
}else{
|
||||||
|
ElMessage.error(res.msg || "保存失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("保存失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
55
src/components/operators/OperatorListDialog.vue
Normal file
55
src/components/operators/OperatorListDialog.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog :model-value="modelValue" @update:model-value="$emit('update:modelValue', $event)"
|
||||||
|
:title="`【${companyName}】运营名单`" width="500px">
|
||||||
|
<el-table :data="localOperators" border stripe>
|
||||||
|
<!-- <el-table-column prop="id" label="员工ID" width="100" /> -->
|
||||||
|
<el-table-column prop="operator_name" label="姓名" />
|
||||||
|
<el-table-column label="操作" width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button size="small" type="danger" link @click="handleDelete(scope.row.id)">移除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
|
import { customerApi } from "@/api/ticket";
|
||||||
|
// 本地数据副本,用于立即更新UI
|
||||||
|
const localOperators = ref([]);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: Boolean,
|
||||||
|
companyName: String,
|
||||||
|
operators: Array,
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["update:modelValue", "refresh"]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 监听 props.operators 的变化,同步到本地
|
||||||
|
watch(() => props.operators, (newVal) => {
|
||||||
|
localOperators.value = [...(newVal || [])];
|
||||||
|
}, { immediate: true, deep: true });
|
||||||
|
|
||||||
|
|
||||||
|
const handleDelete = (operatorId) => {
|
||||||
|
ElMessageBox.confirm("确定要从该单位移除此员工吗?", "提示", { type: "warning" })
|
||||||
|
.then(async () => {
|
||||||
|
try {
|
||||||
|
const res = await customerApi.deleteOperator(operatorId);
|
||||||
|
if (res.code == 200 || res.code == true) {
|
||||||
|
ElMessage.success("操作成功");
|
||||||
|
|
||||||
|
localOperators.value = localOperators.value.filter(item => item.id !== operatorId);
|
||||||
|
emit("refresh"); // 通知父组件重新获取数据
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error("删除失败");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => { });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -4,12 +4,7 @@
|
|||||||
<el-aside width="200px" class="siderbar">
|
<el-aside width="200px" class="siderbar">
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-header">
|
||||||
<!-- 修复:将 logo 区域包装成一个正确的外部链接 -->
|
<!-- 修复:将 logo 区域包装成一个正确的外部链接 -->
|
||||||
<a
|
<a href="https://www.example.com" target="_blank" rel="noopener noreferrer" class="logo-link">
|
||||||
href="https://www.example.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="logo-link"
|
|
||||||
>
|
|
||||||
<div class="logo-container">
|
<div class="logo-container">
|
||||||
<img src="@/assets/logo.jpg" alt="方舟网络" class="logo-image" />
|
<img src="@/assets/logo.jpg" alt="方舟网络" class="logo-image" />
|
||||||
<div class="text-content">
|
<div class="text-content">
|
||||||
@@ -22,7 +17,9 @@
|
|||||||
|
|
||||||
<el-menu default-active="/" class="sidebar-menu" router>
|
<el-menu default-active="/" class="sidebar-menu" router>
|
||||||
<el-menu-item index="/">
|
<el-menu-item index="/">
|
||||||
<el-icon><icon-menu /></el-icon>
|
<el-icon>
|
||||||
|
<icon-menu />
|
||||||
|
</el-icon>
|
||||||
<span>工单管理</span>
|
<span>工单管理</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
|
||||||
@@ -33,12 +30,20 @@
|
|||||||
<span>数据统计</span>
|
<span>数据统计</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
|
||||||
<el-menu-item index="/customers">
|
<el-menu-item index="/companyAoperators">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<User />
|
<User />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span>客户管理</span>
|
<span>客户管理</span>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
|
||||||
|
<el-menu-item index="/accountRegistration">
|
||||||
|
<el-icon>
|
||||||
|
<Plus />
|
||||||
|
</el-icon>
|
||||||
|
<span>账户注册</span>
|
||||||
|
</el-menu-item>
|
||||||
|
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
|
|
||||||
@@ -48,7 +53,7 @@
|
|||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<el-dropdown trigger="click">
|
<el-dropdown trigger="click">
|
||||||
<div class="user-profile">
|
<div class="user-profile">
|
||||||
<el-avatar :size="32" :src="user.avatar" />
|
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" />
|
||||||
<span class="username">{{ user.name }}</span>
|
<span class="username">{{ user.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
|
|||||||
@@ -34,10 +34,16 @@ export const dynamicRoutes = [
|
|||||||
meta: { title: '数据统计' }
|
meta: { title: '数据统计' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'customers',
|
path: 'companyAoperators',
|
||||||
name: 'customers',
|
name: 'company_and_operators',
|
||||||
component: () => import('@/views/CustomersManagement.vue'),
|
component: () => import('@/views/CompanyAndOperatorsManagement.vue'),
|
||||||
meta: { title: '客户管理' }
|
meta: { title: '客户管理' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'accountRegistration',
|
||||||
|
name: 'account_registration',
|
||||||
|
component: () => import('@/views/AccountRegistrationView.vue'),
|
||||||
|
meta: { title: '账户注册' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
269
src/views/AccountRegistrationView.vue
Normal file
269
src/views/AccountRegistrationView.vue
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
<template>
|
||||||
|
<div class="account-container">
|
||||||
|
<h2>账户管理</h2>
|
||||||
|
|
||||||
|
<div class="action-bar">
|
||||||
|
<div class="search-group">
|
||||||
|
<el-input v-model="searchQuery" placeholder="搜索用户名/真实姓名" style="width: 250px" clearable />
|
||||||
|
</div>
|
||||||
|
<el-button type="primary" :icon="Plus" @click="openRegisterDialog">注册用户</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table :data="filteredUsers" border style="width: 100%; margin-top: 20px" v-loading="loading">
|
||||||
|
<template #empty>
|
||||||
|
<el-empty :image-size="100" description="暂无用户数据" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- <el-table-column prop="id" label="ID" width="70" align="center" /> -->
|
||||||
|
<el-table-column prop="username" label="用户名" min-width="120" />
|
||||||
|
<el-table-column prop="real_name" label="真实姓名" min-width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.real_name || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="角色" width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="roleTagType(row.role)" size="small" effect="plain">
|
||||||
|
{{ roleLabel(row.role) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="状态" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small" effect="plain">
|
||||||
|
{{ row.status === 1 ? '正常' : '已禁用' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="创建时间" width="160" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDate(row.create_time) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
:type="scope.row.status === 1 ? 'warning' : 'success'"
|
||||||
|
plain
|
||||||
|
@click="handleToggleStatus(scope.row)"
|
||||||
|
>
|
||||||
|
{{ scope.row.status === 1 ? '禁用' : '启用' }}
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" type="danger" plain @click="handleDeleteAccount(scope.row)">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 注册弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="registerDialogVisible"
|
||||||
|
title="注册新用户"
|
||||||
|
width="450px"
|
||||||
|
destroy-on-close
|
||||||
|
@close="resetRegisterForm"
|
||||||
|
>
|
||||||
|
<el-form ref="registerFormRef" :model="registerForm" :rules="registerRules" label-width="80px">
|
||||||
|
<el-form-item label="用户名" prop="username">
|
||||||
|
<el-input v-model="registerForm.username" placeholder="登录用" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="真实姓名" prop="real_name">
|
||||||
|
<el-input v-model="registerForm.real_name" placeholder="选填" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="密码" prop="password">
|
||||||
|
<el-input v-model="registerForm.password" type="password" placeholder="请输入密码" show-password />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="角色" prop="role">
|
||||||
|
<el-select v-model="registerForm.role" placeholder="请选择角色" style="width: 100%">
|
||||||
|
<el-option label="管理员" value="admin" />
|
||||||
|
<el-option label="员工" value="user" />
|
||||||
|
<el-option label="客户" value="client" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="registerDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="submitting" @click="handleRegister">确定注册</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, onMounted } from "vue";
|
||||||
|
import { Plus } from "@element-plus/icons-vue";
|
||||||
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
|
import { accountApi } from "@/api/ticket";
|
||||||
|
import { formatDate } from "@/utils/format";
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const users = ref([]);
|
||||||
|
const searchQuery = ref("");
|
||||||
|
|
||||||
|
const registerDialogVisible = ref(false);
|
||||||
|
const registerFormRef = ref(null);
|
||||||
|
const submitting = ref(false);
|
||||||
|
|
||||||
|
const registerForm = reactive({
|
||||||
|
username: "",
|
||||||
|
real_name: "",
|
||||||
|
password: "",
|
||||||
|
role: "user",
|
||||||
|
});
|
||||||
|
|
||||||
|
const registerRules = {
|
||||||
|
username: [
|
||||||
|
{ required: true, message: "请输入用户名", trigger: "blur" },
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||||
|
{ min: 6, message: "密码至少6位", trigger: "blur" },
|
||||||
|
],
|
||||||
|
role: [
|
||||||
|
{ required: true, message: "请选择角色", trigger: "change" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const roleLabel = (role) => {
|
||||||
|
const map = { admin: "管理员", user: "员工", client: "客户" };
|
||||||
|
return map[role] || role;
|
||||||
|
};
|
||||||
|
|
||||||
|
const roleTagType = (role) => {
|
||||||
|
const map = { admin: "danger", user: "warning", client: "info" };
|
||||||
|
return map[role] || "info";
|
||||||
|
};
|
||||||
|
|
||||||
|
const rolePriority = { admin: 0, user: 1, client: 2 };
|
||||||
|
|
||||||
|
const filteredUsers = computed(() => {
|
||||||
|
let list = users.value;
|
||||||
|
if (searchQuery.value) {
|
||||||
|
const q = searchQuery.value.toLowerCase();
|
||||||
|
list = list.filter(
|
||||||
|
(u) =>
|
||||||
|
u.username.toLowerCase().includes(q) ||
|
||||||
|
(u.real_name && u.real_name.toLowerCase().includes(q))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [...list].sort((a, b) => (rolePriority[a.role] ?? 99) - (rolePriority[b.role] ?? 99));
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchUsers = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await accountApi.getAllUsers();
|
||||||
|
if (res.code === 200) {
|
||||||
|
users.value = res.data || [];
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || "获取用户列表失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("获取用户数据失败");
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openRegisterDialog = () => {
|
||||||
|
registerDialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetRegisterForm = () => {
|
||||||
|
if (registerFormRef.value) registerFormRef.value.resetFields();
|
||||||
|
registerForm.username = "";
|
||||||
|
registerForm.real_name = "";
|
||||||
|
registerForm.password = "";
|
||||||
|
registerForm.role = "user";
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRegister = async () => {
|
||||||
|
if (!registerFormRef.value) return;
|
||||||
|
try {
|
||||||
|
await registerFormRef.value.validate();
|
||||||
|
submitting.value = true;
|
||||||
|
const res = await accountApi.addAccount({ ...registerForm });
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success("用户注册成功");
|
||||||
|
registerDialogVisible.value = false;
|
||||||
|
fetchUsers();
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || "注册失败");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e?.name !== "ValidationError") {
|
||||||
|
ElMessage.error("注册请求异常");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleStatus = (row) => {
|
||||||
|
const newStatus = row.status === 1 ? 0 : 1;
|
||||||
|
const action = newStatus === 0 ? "禁用" : "启用";
|
||||||
|
ElMessageBox.confirm(`确定${action}用户【${row.username}】吗?`, "提示", {
|
||||||
|
type: "warning",
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
const res = await accountApi.updateAccountStatus(row.id, newStatus);
|
||||||
|
if (res.code === 200) {
|
||||||
|
row.status = newStatus;
|
||||||
|
ElMessage.success(`${action}成功`);
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || `${action}失败`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteAccount = (row) => {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
`确定删除用户【${row.username}】吗?此操作不可逆。`,
|
||||||
|
"警告",
|
||||||
|
{ type: "error", confirmButtonClass: "el-button--danger" }
|
||||||
|
)
|
||||||
|
.then(async () => {
|
||||||
|
const res = await accountApi.deleteAccount(row.id);
|
||||||
|
if (res.code === 200) {
|
||||||
|
const idx = users.value.findIndex((u) => u.id === row.id);
|
||||||
|
if (idx > -1) users.value.splice(idx, 1);
|
||||||
|
ElMessage.success("删除成功");
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || "删除失败");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(fetchUsers);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.account-container {
|
||||||
|
padding: 30px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
color: #303133;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: #fff;
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
159
src/views/CompanyAndOperatorsManagement.vue
Normal file
159
src/views/CompanyAndOperatorsManagement.vue
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<template>
|
||||||
|
<div class="customers-container">
|
||||||
|
<h2>运营人员管理</h2>
|
||||||
|
|
||||||
|
<div class="action-bar">
|
||||||
|
<div class="search-group">
|
||||||
|
<el-input v-model="searchQuery" placeholder="搜索客户单位" style="width: 250px" clearable />
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<el-button type="primary" :icon="Plus" @click="customerDialogVisible = true">
|
||||||
|
新增客户单位
|
||||||
|
</el-button>
|
||||||
|
<el-button type="success" :icon="User" @click="handleAddOperator">
|
||||||
|
新增运营人员
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table :data="companyAndOperators" border style="width: 100%; margin-top: 20px" v-loading="loading">
|
||||||
|
<template #empty>
|
||||||
|
<el-empty :image-size="100" description="暂无客户数据" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-table-column prop="id" label="客户ID" width="100" />
|
||||||
|
<el-table-column prop="company_name" label="客户单位名称" />
|
||||||
|
|
||||||
|
<el-table-column label="员工管理" width="250" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<span style="margin-right: 15px">
|
||||||
|
共 <b style="color: #409eff">{{ scope.row.companyoperators.length }}</b> 人
|
||||||
|
</span>
|
||||||
|
<el-button size="small" type="primary" link :icon="View" @click="showCompanyAndOperatorsDetails(scope.row)">
|
||||||
|
查看详情
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" width="150" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button size="small" type="danger" plain @click="handleDeleteCompany(scope.row)">
|
||||||
|
删除单位
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<CompanyEditDialog v-model="customerDialogVisible" @success="fetchCompanyAndOperators" />
|
||||||
|
|
||||||
|
<OperatorEditDialog v-model="operatorDialogVisible" :company-list="companyList"
|
||||||
|
@success="fetchCompanyAndOperators" />
|
||||||
|
|
||||||
|
<OperatorListDialog v-model="employeeDetailVisible" :company-name="activeCompanyName"
|
||||||
|
:operators="currentCompanyOperators" @refresh="fetchCompanyAndOperators" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from "vue";
|
||||||
|
import { Plus, User, View } from "@element-plus/icons-vue";
|
||||||
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
|
import { customerApi, ticketApi } from "@/api/ticket";
|
||||||
|
|
||||||
|
import CompanyEditDialog from "@/components/operators/CompanyEditDialog.vue";
|
||||||
|
import OperatorEditDialog from "@/components/operators/OperatorEditDialog.vue";
|
||||||
|
import OperatorListDialog from "@/components/operators/OperatorListDialog.vue";
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const companyList = ref([]);
|
||||||
|
const employeeList = ref([]);
|
||||||
|
const searchQuery = ref("");
|
||||||
|
const companyAndOperators = ref([]);
|
||||||
|
|
||||||
|
const customerDialogVisible = ref(false);
|
||||||
|
const operatorDialogVisible = ref(false);
|
||||||
|
const employeeDetailVisible = ref(false);
|
||||||
|
|
||||||
|
|
||||||
|
const activeCompanyName = ref(""); // 当前选中客户单位的名称
|
||||||
|
const currentCompanyOperators = ref([]); // 当前选中客户单位的员工列表
|
||||||
|
|
||||||
|
|
||||||
|
const fetchCompanyAndOperators = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await ticketApi.getOperatorsNew();
|
||||||
|
if (response.code === 200) {
|
||||||
|
companyAndOperators.value = response.data
|
||||||
|
companyList.value = response.data.map(item => ({ company_id: item.id, company_name: item.company_name }))
|
||||||
|
}
|
||||||
|
// console.log('获取数据', companyAndOperators.value);
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("获取客户数据失败,请检查网络");
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleAddOperator = () => {
|
||||||
|
if (companyList.value.length === 0) {
|
||||||
|
return ElMessage.warning("请先创建客户单位");
|
||||||
|
}
|
||||||
|
operatorDialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const showCompanyAndOperatorsDetails = (row) => {
|
||||||
|
activeCompanyName.value = row.company_name;
|
||||||
|
currentCompanyOperators.value = row.companyoperators || [];
|
||||||
|
employeeDetailVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除客户单位
|
||||||
|
const handleDeleteCompany = (row) => {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
`确定删除单位【${row.company_name}】吗?操作不可恢复!`,
|
||||||
|
"警告",
|
||||||
|
{ type: "error", confirmButtonClass: "el-button--danger" }
|
||||||
|
)
|
||||||
|
.then(async () => {
|
||||||
|
const res = await customerApi.deleteCompany(row.id);
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success("清理成功");
|
||||||
|
fetchCompanyAndOperators();
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || "删除失败");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => { });
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(fetchCompanyAndOperators);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.customers-container {
|
||||||
|
padding: 30px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
color: #303133;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: #fff;
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group .el-button {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="customers-container">
|
|
||||||
<h2>客户及员工管理系统</h2>
|
|
||||||
|
|
||||||
<div class="action-bar">
|
|
||||||
<div class="search-group">
|
|
||||||
<el-input
|
|
||||||
v-model="searchQuery"
|
|
||||||
placeholder="搜索客户单位"
|
|
||||||
style="width: 250px"
|
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="button-group">
|
|
||||||
<el-button type="primary" :icon="Plus" @click="customerDialogVisible = true"
|
|
||||||
>新增客户单位</el-button
|
|
||||||
>
|
|
||||||
<el-button type="success" :icon="User" @click="handleAddEmployee"
|
|
||||||
>新增员工</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-table
|
|
||||||
:data="filteredData"
|
|
||||||
border
|
|
||||||
style="width: 100%; margin-top: 20px"
|
|
||||||
v-loading="loading"
|
|
||||||
>
|
|
||||||
<el-table-column prop="customer_id" label="客户ID" width="100" />
|
|
||||||
<el-table-column prop="company_name" label="客户单位名称" />
|
|
||||||
|
|
||||||
<el-table-column label="员工管理" width="250" align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<span style="margin-right: 15px">
|
|
||||||
共
|
|
||||||
<b style="color: #409eff">{{ getEmployeeCount(scope.row.customer_id) }}</b> 人
|
|
||||||
</span>
|
|
||||||
<el-button
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
link
|
|
||||||
:icon="View"
|
|
||||||
@click="showEmployeeDetails(scope.row)"
|
|
||||||
>查看详情</el-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column label="操作" width="150" fixed="right">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button
|
|
||||||
size="small"
|
|
||||||
type="danger"
|
|
||||||
plain
|
|
||||||
@click="handleDeleteCustomer(scope.row)"
|
|
||||||
>删除单位</el-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<CustomerEditDialog v-model="customerDialogVisible" @success="fetchCustomers" />
|
|
||||||
|
|
||||||
<EmployeeEditDialog
|
|
||||||
v-model="employeeDialogVisible"
|
|
||||||
:customer-list="customerList"
|
|
||||||
@success="fetchCustomers"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EmployeeListDialog
|
|
||||||
v-model="employeeDetailVisible"
|
|
||||||
:customer-name="activeCustomerName"
|
|
||||||
:employees="currentCustomerEmployees"
|
|
||||||
@refresh="fetchCustomers"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed, onMounted } from "vue";
|
|
||||||
import { Plus, User, View } from "@element-plus/icons-vue";
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
|
||||||
import { customerApi } from "@/api/ticket";
|
|
||||||
|
|
||||||
// 导入新组件
|
|
||||||
import CustomerEditDialog from "@/components/customer/CustomerEditDialog.vue";
|
|
||||||
import EmployeeEditDialog from "@/components/customer/EmployeeEditDialog.vue";
|
|
||||||
import EmployeeListDialog from "@/components/customer/EmployeeListDialog.vue";
|
|
||||||
|
|
||||||
// --- 状态数据 ---
|
|
||||||
const loading = ref(false);
|
|
||||||
const customerList = ref([]);
|
|
||||||
const employeeList = ref([]);
|
|
||||||
const searchQuery = ref("");
|
|
||||||
|
|
||||||
const customerDialogVisible = ref(false);
|
|
||||||
const employeeDialogVisible = ref(false);
|
|
||||||
const employeeDetailVisible = ref(false);
|
|
||||||
|
|
||||||
const activeCustomerName = ref("");
|
|
||||||
const activeCustomerId = ref(null);
|
|
||||||
|
|
||||||
// --- 核心逻辑 ---
|
|
||||||
const fetchCustomers = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const res = await customerApi.getCustomers();
|
|
||||||
if (res.code === 200) {
|
|
||||||
customerList.value = res.customers || [];
|
|
||||||
employeeList.value = res.employees || [];
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getEmployeeCount = (id) =>
|
|
||||||
employeeList.value.filter((emp) => emp.customer_id === id).length;
|
|
||||||
|
|
||||||
const currentCustomerEmployees = computed(() =>
|
|
||||||
employeeList.value.filter((emp) => emp.customer_id === activeCustomerId.value)
|
|
||||||
);
|
|
||||||
|
|
||||||
const filteredData = computed(() =>
|
|
||||||
customerList.value.filter((item) =>
|
|
||||||
item.company_name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleAddEmployee = () => {
|
|
||||||
if (customerList.value.length === 0) return ElMessage.warning("请先创建客户单位");
|
|
||||||
employeeDialogVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const showEmployeeDetails = (row) => {
|
|
||||||
activeCustomerId.value = row.customer_id;
|
|
||||||
activeCustomerName.value = row.company_name;
|
|
||||||
employeeDetailVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteCustomer = (row) => {
|
|
||||||
ElMessageBox.confirm(`确定删除单位【${row.company_name}】吗?操作不可恢复!`, "警告", {
|
|
||||||
type: "error",
|
|
||||||
confirmButtonClass: "el-button--danger",
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
const res = await customerApi.deleteCustomer(row.customer_id);
|
|
||||||
if (res.code === 200) {
|
|
||||||
ElMessage.success("清理成功");
|
|
||||||
fetchCustomers();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(fetchCustomers);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* 保持原有样式不变 */
|
|
||||||
.customers-container {
|
|
||||||
padding: 30px;
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
color: #303133;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.action-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
background: #fff;
|
|
||||||
padding: 15px 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
.button-group .el-button {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -109,7 +109,6 @@ const handleLogin = async () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.name !== "ValidationError") {
|
if (error.name !== "ValidationError") {
|
||||||
ElMessage.error("登录请求异常,请稍后重试");
|
ElMessage.error("登录请求异常,请稍后重试");
|
||||||
// console.error("登录错误:", error);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user