更新erp模块

This commit is contained in:
扫地僧 2026-05-13 08:42:09 +08:00
parent 488b50c28c
commit b727bae790
6 changed files with 418 additions and 50 deletions

19
src/env.d.ts vendored
View File

@ -12,6 +12,25 @@ declare module '@/*' {
export default component;
}
declare module '@/api/erp' {
export function getOrganizationList(): Promise<any>;
export function getOrganizationDetail(id: number | string): Promise<any>;
export function createOrganization(data: any): Promise<any>;
export function editOrganization(id: number | string, data: any): Promise<any>;
export function deleteOrganization(id: number | string): Promise<any>;
export function getCompanys(): Promise<any>;
export function getDepartments(parentId?: number | string): Promise<any>;
export function getEmployeeList(tenantId?: number | string): Promise<any>;
export function getEmployeeDetail(id: number | string): Promise<any>;
export function createEmployee(data: any): Promise<any>;
export function editEmployee(id: number | string, data: any): Promise<any>;
export function deleteEmployee(id: number | string): Promise<any>;
}
declare module '@/stores/auth' {
export function useAuthStore(): any;
}
interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string;
// 添加其他环境变量...

View File

@ -1,3 +1,122 @@
<template></template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>
<template>
<el-dialog v-model="visible" title="修改密码" width="420px" :close-on-click-modal="false">
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
<el-form-item label="用户">
<span>{{ currentUser.name || currentUser.account || "-" }}</span>
</el-form-item>
<el-form-item label="新密码" prop="password">
<el-input
v-model="form.password"
type="password"
placeholder="请输入新密码"
show-password
clearable
/>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input
v-model="form.confirmPassword"
type="password"
placeholder="请再次输入新密码"
show-password
clearable
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue";
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
import { editEmployee } from "@/api/erp";
const emit = defineEmits<{
(e: "submit"): void;
}>();
const visible = ref(false);
const submitLoading = ref(false);
const formRef = ref<FormInstance>();
const currentUser = ref<any>({});
const form = reactive({
password: "",
confirmPassword: "",
});
const validateConfirmPassword = (_rule: any, value: string, callback: (error?: Error) => void) => {
if (!value) {
callback(new Error("请再次输入新密码"));
return;
}
if (value !== form.password) {
callback(new Error("两次输入的密码不一致"));
return;
}
callback();
};
const rules: FormRules = {
password: [
{ required: true, message: "请输入新密码", trigger: "blur" },
{ min: 6, message: "密码长度不能少于 6 位", trigger: "blur" },
],
confirmPassword: [
{ required: true, message: "请再次输入新密码", trigger: "blur" },
{ validator: validateConfirmPassword, trigger: "blur" },
],
};
const resetForm = () => {
form.password = "";
form.confirmPassword = "";
formRef.value?.clearValidate();
};
const openChangePass = (row: any) => {
currentUser.value = row || {};
resetForm();
visible.value = true;
};
const handleSubmit = async () => {
if (!currentUser.value?.id) {
ElMessage.error("用户 ID 不存在");
return;
}
await formRef.value?.validate();
submitLoading.value = true;
try {
await editEmployee(currentUser.value.id, {
password: form.password,
});
ElMessage.success("密码修改成功");
visible.value = false;
emit("submit");
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || e?.message || "密码修改失败");
} finally {
submitLoading.value = false;
}
};
defineExpose({
openChangePass,
open: openChangePass,
});
</script>
<style lang="less" scoped></style>

View File

@ -40,6 +40,7 @@
<!-- 性别 -->
<el-form-item label="性别" required>
<el-radio-group v-model="form.sex" placeholder="请选择性别">
<el-radio-button label="未知" :value="0" />
<el-radio-button label="男" :value="1" />
<el-radio-button label="女" :value="2" />
</el-radio-group>
@ -50,6 +51,7 @@
<el-date-picker
v-model="form.birthday"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择生日"
/>
</el-form-item>
@ -57,14 +59,14 @@
<!-- 隶属单位 -->
<el-form-item label="隶属单位" required>
<el-select v-model="form.affiliate_unit" placeholder="请选择隶属单位" clearable style="width: 100%">
<el-option v-for="item in companyList" :key="item.id" :label="item.org_name" :value="item.id" />
<el-option v-for="item in companyList" :key="item.id" :label="item.org_name" :value="String(item.id)" />
</el-select>
</el-form-item>
<!-- 部门 -->
<el-form-item label="部门" required>
<el-select v-model="form.department" placeholder="请先选择隶属单位" clearable style="width: 100%" :disabled="!form.affiliate_unit">
<el-option v-for="item in departmentList" :key="item.id" :label="item.org_name" :value="item.id" />
<el-option v-for="item in departmentList" :key="item.id" :label="item.org_name" :value="String(item.id)" />
</el-select>
</el-form-item>
@ -80,7 +82,7 @@
<!-- 民族 -->
<el-form-item label="民族" required>
<el-input v-model="form.nationality" placeholder="请输入民族" />
<el-input v-model="form.nation" placeholder="请输入民族" />
</el-form-item>
<!-- 手机 -->
@ -126,7 +128,7 @@
</template>
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { ref, computed, watch, nextTick } from "vue";
import { ElMessage } from "element-plus";
import { createEmployee, editEmployee, getEmployeeDetail, getCompanys, getDepartments } from "@/api/erp";
@ -152,6 +154,7 @@ import { ref, computed, watch } from "vue";
const isAdd = ref(false);
const companyList = ref<any[]>([]);
const departmentList = ref<any[]>([]);
const isHydratingUserData = ref(false);
//
const loadCompanyList = async () => {
@ -176,25 +179,37 @@ import { ref, computed, watch } from "vue";
const form = ref<any>({
id: null,
tid: 1,
tenant_id: 1,
account: "",
name: "",
phone: "",
qq: "",
sex: 1,
sex: 0,
birthday: "",
affiliate_unit: "",
department: "",
position: "",
education: "",
nation: "",
wechat: "",
home_address: "",
password: "",
confirmPassword: "",
email: "",
account_status: 1,
status: 1,
affiliate_unit: "",
department: "",
});
//
watch(() => form.value.affiliate_unit, (newVal) => {
if (isHydratingUserData.value) {
return;
}
form.value.department = "";
departmentList.value = [];
if (newVal) {
loadDepartments(newVal);
loadDepartments(Number(newVal));
}
});
@ -301,25 +316,59 @@ import { ref, computed, watch } from "vue";
const sexValue =
data.sex !== undefined && data.sex !== null
? Number(data.sex)
: 1;
: data.gender !== undefined && data.gender !== null
? Number(data.gender)
: 0;
const statusValue =
data.status !== undefined && data.status !== null
? Number(data.status)
: 1;
data.account_status !== undefined && data.account_status !== null
? Number(data.account_status)
: data.status !== undefined && data.status !== null
? Number(data.status)
: 1;
const affiliateUnitValue =
data.affiliate_unit !== undefined && data.affiliate_unit !== null
? String(data.affiliate_unit)
: "";
const departmentValue =
data.department !== undefined && data.department !== null
? String(data.department)
: "";
isHydratingUserData.value = true;
form.value = {
id: data.id,
account: data.account,
name: data.name,
phone: data.phone,
qq: data.qq,
tid: data.tid || data.tenant_id || 1,
tenant_id: data.tenant_id || data.tid || 1,
account: data.account || "",
name: data.name || "",
phone: data.phone || "",
qq: data.qq || "",
sex: sexValue,
birthday: data.birthday || "",
affiliate_unit: affiliateUnitValue,
department: departmentValue,
position: data.position || "",
education: data.education || "",
nation: data.nation || data.nationality || "",
wechat: data.wechat || "",
home_address: data.home_address || "",
password: "",
confirmPassword: "",
email: data.email,
email: data.email || "",
account_status: statusValue,
status: statusValue,
};
await nextTick();
departmentList.value = [];
if (form.value.affiliate_unit) {
await loadDepartments(Number(form.value.affiliate_unit));
}
form.value.department = departmentValue;
isHydratingUserData.value = false;
} catch (e: any) {
console.error("Failed to load user data:", e);
const errorMsg = e?.response?.data?.message || e?.message || "加载用户失败";
@ -370,13 +419,24 @@ import { ref, computed, watch } from "vue";
if (isAdd.value) {
//
const submitData: any = {
tid: form.value.tid,
tenant_id: form.value.tenant_id,
account: form.value.account,
name: form.value.name,
phone: form.value.phone,
qq: form.value.qq,
sex: form.value.sex,
birthday: form.value.birthday,
affiliate_unit: form.value.affiliate_unit,
department: form.value.department,
position: form.value.position,
education: form.value.education,
nation: form.value.nation,
wechat: form.value.wechat,
email: form.value.email,
status: form.value.status,
home_address: form.value.home_address,
account_status: form.value.account_status,
status: form.value.account_status,
password: form.value.password,
};
@ -391,13 +451,24 @@ import { ref, computed, watch } from "vue";
const submitData: any = {
id: form.value.id,
tid: form.value.tid,
tenant_id: form.value.tenant_id,
account: form.value.account,
name: form.value.name,
phone: form.value.phone,
qq: form.value.qq,
sex: form.value.sex,
birthday: form.value.birthday,
affiliate_unit: form.value.affiliate_unit,
department: form.value.department,
position: form.value.position,
education: form.value.education,
nation: form.value.nation,
wechat: form.value.wechat,
email: form.value.email,
status: form.value.status,
home_address: form.value.home_address,
account_status: form.value.account_status,
status: form.value.account_status,
};
//
@ -424,14 +495,25 @@ import { ref, computed, watch } from "vue";
isAdd.value = true;
form.value = {
id: 0,
tid: 1,
tenant_id: 1,
account: "",
name: "",
phone: "",
qq: "",
sex: 1,
sex: 0,
birthday: "",
affiliate_unit: "",
department: "",
position: "",
education: "",
nation: "",
wechat: "",
home_address: "",
password: "",
confirmPassword: "",
email: "",
account_status: 1,
status: 1,
};
loadCompanyList();
@ -461,14 +543,25 @@ import { ref, computed, watch } from "vue";
isAdd.value = true;
form.value = {
id: 0,
tid: 1,
tenant_id: 1,
account: "",
name: "",
phone: "",
qq: "",
sex: 1,
sex: 0,
birthday: "",
affiliate_unit: "",
department: "",
position: "",
education: "",
nation: "",
wechat: "",
home_address: "",
password: "",
confirmPassword: "",
email: "",
account_status: 1,
status: 1,
};
}
@ -488,4 +581,3 @@ import { ref, computed, watch } from "vue";
margin-bottom: 20px;
}
</style>

View File

@ -1,3 +1,97 @@
<template></template>
<script setup lang="ts"></script>
<style lang="less" scoped></style>
<template>
<el-drawer v-model="visible" title="查看用户" width="500px">
<el-descriptions :column="1" border>
<el-descriptions-item label="ID">{{ detail.id || "-" }}</el-descriptions-item>
<el-descriptions-item label="账号">{{ detail.account || "-" }}</el-descriptions-item>
<el-descriptions-item label="姓名">{{ detail.name || "-" }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ genderText }}</el-descriptions-item>
<el-descriptions-item label="生日">{{ detail.birthday || "-" }}</el-descriptions-item>
<el-descriptions-item label="隶属单位">{{ detail.affiliate_unit_name || detail.affiliate_unit || "-" }}</el-descriptions-item>
<el-descriptions-item label="部门">{{ detail.department_name || detail.department || "-" }}</el-descriptions-item>
<el-descriptions-item label="职位">{{ detail.position || "-" }}</el-descriptions-item>
<el-descriptions-item label="学历">{{ detail.education || "-" }}</el-descriptions-item>
<el-descriptions-item label="民族">{{ detail.nation || "-" }}</el-descriptions-item>
<el-descriptions-item label="手机">{{ detail.phone || "-" }}</el-descriptions-item>
<el-descriptions-item label="微信">{{ detail.wechat || "-" }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ detail.email || "-" }}</el-descriptions-item>
<el-descriptions-item label="家庭住址">{{ detail.home_address || "-" }}</el-descriptions-item>
<el-descriptions-item label="账号状态">
<el-tag :type="statusValue === 1 ? 'success' : 'danger'">
{{ statusText }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button @click="visible = false">关闭</el-button>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import { ElMessage } from "element-plus";
import { getEmployeeDetail } from "@/api/erp";
const visible = ref(false);
const detail = ref<any>({});
const genderValue = computed(() => {
const value = detail.value.sex ?? detail.value.gender ?? 0;
return Number(value);
});
const genderText = computed(() => {
if (genderValue.value === 1) {
return "男";
}
if (genderValue.value === 2) {
return "女";
}
return "未知";
});
const statusValue = computed(() => {
const value = detail.value.account_status ?? detail.value.status ?? 0;
return Number(value);
});
const statusText = computed(() => {
if (statusValue.value === 1) {
return "启用";
}
if (statusValue.value === 2) {
return "离职";
}
return "禁用";
});
const loadUserData = async (user: any) => {
try {
const userId = typeof user === "number" ? user : user?.id || user?.userId;
if (!userId) {
throw new Error("未提供有效的用户 ID");
}
const res = await getEmployeeDetail(userId);
detail.value = res.data || res || {};
} catch (e: any) {
const errorMsg = e?.response?.data?.message || e?.message || "加载用户失败";
ElMessage.error(errorMsg);
throw e;
}
};
const openView = async (user: any) => {
visible.value = true;
await loadUserData(user);
};
defineExpose({
loadUserData,
openView,
open: openView,
});
</script>
<style lang="less" scoped></style>

View File

@ -22,25 +22,33 @@
<!-- 用户列表表格 -->
<el-table :data="users" style="width: 100%" v-loading="loading">
<el-table-column prop="id" label="ID" align="center" fixed="left" />
<el-table-column prop="account" label="账号" align="center" />
<el-table-column prop="name" label="姓名" align="center">
<el-table-column prop="id" label="ID" align="center" fixed="left" width="80" />
<el-table-column prop="account" label="账号" align="center" width="120" />
<el-table-column prop="name" label="姓名" align="center" width="120">
<template #default="scope">
<span class="name-link" @click="handlePreview(scope.row)">{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column prop="qq" label="隶属单位" align="center" />
<el-table-column prop="phone" label="部门" align="center" />
<el-table-column prop="phone" label="职位" align="center" />
<el-table-column prop="status" label="账号状态" width="80" align="center">
<el-table-column label="隶属单位" align="center">
<template #default="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">{{
scope.row.status === 1 ? "启用" : "禁用"
{{ scope.row.affiliate_unit_name || scope.row.affiliate_unit || "-" }}
</template>
</el-table-column>
<el-table-column label="部门" align="center">
<template #default="scope">
{{ scope.row.department_name || scope.row.department || "-" }}
</template>
</el-table-column>
<el-table-column prop="position" label="职位" align="center" />
<el-table-column prop="account_status" label="账号状态" width="80" align="center">
<template #default="scope">
<el-tag :type="getStatusValue(scope.row) === 1 ? 'success' : 'danger'">{{
getStatusValue(scope.row) === 1 ? "启用" : getStatusValue(scope.row) === 2 ? "离职" : "禁用"
}}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="240" align="center" fixed="right">
<template #default="scope">
<template #default="scope"><!-- -->
<el-button size="small" @click="handlePreview(scope.row)">查看</el-button>
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="small" type="warning" @click="handleChangePassword(scope.row)">
@ -58,6 +66,12 @@
layout="total, prev, pager, next" />
</div>
<!-- 查看弹窗 -->
<View ref="viewRef" />
<!-- 修改密码弹窗 -->
<ChangePass ref="changePassRef" @submit="fetchData" />
<!-- 编辑弹窗 -->
<Edit ref="editRef" @submit="fetchData" />
</div>
@ -69,6 +83,8 @@ import { ElMessage, ElMessageBox } from "element-plus";
import { Plus, Refresh } from "@element-plus/icons-vue";
import { getEmployeeList, deleteEmployee } from "@/api/erp";
import Edit from "./components/edit.vue";
import View from "./components/view.vue";
import ChangePass from "./components/changepass.vue";
import { useAuthStore } from "@/stores/auth";
const users = ref<any[]>([]);
@ -77,6 +93,13 @@ const page = ref(1);
const pageSize = ref(10);
const total = ref(0);
const editRef = ref<any>(null);
const viewRef = ref<any>(null);
const changePassRef = ref<any>(null);
const getStatusValue = (row: any) => {
const status = row.account_status ?? row.status ?? 0;
return Number(status);
};
const authStore = useAuthStore();
const tenantId = (authStore.user as any)?.tid;
@ -84,10 +107,10 @@ const tenantId = (authStore.user as any)?.tid;
// console.log(tenantId);
//
const fetchData = async (tenantId: number) => {
const fetchData = async (currentTenantId = tenantId) => {
loading.value = true;
try {
const res = await getEmployeeList(tenantId);
const res = await getEmployeeList(currentTenantId);
users.value = res.data || res || [];
total.value = users.value.length;
} catch (e: any) {
@ -112,9 +135,13 @@ const handleEdit = (row: any) => {
editRef.value?.openEdit(row);
};
const handleChangePassword = (row: any) => {
changePassRef.value?.openChangePass(row);
};
//
const handlePreview = (row: any) => {
editRef.value?.openEdit(row);
viewRef.value?.openView(row);
};
//
@ -171,4 +198,4 @@ onMounted(() => {
text-decoration: underline;
}
}
</style>
</style>

View File

@ -15,8 +15,13 @@
<el-input v-model="formData.org_code" placeholder="请输入组织编码,例如LYG-YZ-GROUP" />
</el-form-item>
<el-form-item label="负责人" prop="leader_id">
<el-select v-model="formData.leader_id" placeholder="请选择负责人">
<el-option v-for="item in leaderList" :key="item.id" :label="item.name" :value="item.id" />
<el-select v-model="formData.leader_id" placeholder="请选择负责人" filterable clearable style="width: 100%">
<el-option
v-for="item in leaderList"
:key="item.id"
:label="item.name || item.account || String(item.id)"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="排序" prop="sort">
@ -91,7 +96,7 @@ const formRef = ref<FormInstance>()
const isEdit = ref(false)
const authStore = useAuthStore()
const tenantId = (authStore.user as any)?.tid
const leaderList = ref([])
const leaderList = ref<Array<{ id: number; name?: string; account?: string }>>([])
const formData = reactive({
id: 0,
parent_id: undefined as number | undefined,
@ -126,14 +131,26 @@ const extendedTreeData = computed(() => {
})
const getLeaderList = async () => {
const res = await getTenantUsers(tenantId)
if (res.code === 200) {
leaderList.value = res.data.list
if (!tenantId) {
leaderList.value = []
return
}
const res: any = await getTenantUsers(tenantId)
const responseData = res?.data?.code !== undefined ? res.data : res
if (responseData?.code === 200) {
const list = responseData.data?.list || responseData.data || []
leaderList.value = list.map((item: any) => ({
...item,
id: Number(item.id)
}))
}
}
//
const open = (data?: TreeNode, parentId?: number) => {
getLeaderList()
if (data) {
// children
isEdit.value = true
@ -141,7 +158,7 @@ const open = (data?: TreeNode, parentId?: number) => {
formData.parent_id = data.parent_id ?? 0
formData.org_name = data.org_name
formData.org_code = data.org_code || ''
formData.leader_id = data.leader_id
formData.leader_id = data.leader_id === undefined || data.leader_id === null ? undefined : Number(data.leader_id)
formData.is_company = data.is_company ?? 0
formData.sort = data.sort ?? 0
formData.status = data.status ?? 1