更新路由以及静态页面完成

This commit is contained in:
汪圳
2025-12-08 12:44:45 +08:00
parent 5234fb3d8d
commit a66ee9b943
13 changed files with 1979 additions and 56 deletions

BIN
public/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -161,14 +161,6 @@ export const constantRoutes: RouteRecordRaw[] = [
title: "工资/奖金变更", title: "工资/奖金变更",
}, },
}, },
{
path: "role",
name: "FinanceRole",
component: () => import("@/views/system/role/index.vue"),
meta: {
title: "角色管理",
},
},
], ],
}, },
// 业务管理模块 // 业务管理模块
@@ -197,14 +189,6 @@ export const constantRoutes: RouteRecordRaw[] = [
title: "预立案登记", title: "预立案登记",
}, },
}, },
{
path: "projectManagement",
name: "ProjectManagement",
component: () => import("@/views/business/preRegistration/index.vue"),
meta: {
title: "立项管理",
},
},
], ],
}, },
// 案件管理模块 // 案件管理模块
@@ -220,17 +204,9 @@ export const constantRoutes: RouteRecordRaw[] = [
{ {
path: "user", path: "user",
name: "CaseUser", name: "CaseUser",
component: () => import("@/views/system/user/index.vue"), component: () => import("@/views/case/index.vue"),
meta: { meta: {
title: "人事管理", title: "案件管理",
},
},
{
path: "role",
name: "CaseRole",
component: () => import("@/views/system/role/index.vue"),
meta: {
title: "角色管理",
}, },
}, },
], ],
@@ -270,7 +246,7 @@ export const constantRoutes: RouteRecordRaw[] = [
{ {
path: "index", path: "index",
name: "StampPerformanceIndex", name: "StampPerformanceIndex",
component: () => import("@/views/stamp-application/index.vue"), component: () => import("@/views/performance/list/index.vue"),
meta: { meta: {
title: "业绩展示", title: "业绩展示",
}, },
@@ -291,7 +267,7 @@ export const constantRoutes: RouteRecordRaw[] = [
{ {
path: "index", path: "index",
name: "RegistrationIndex", name: "RegistrationIndex",
component: () => import("@/views/stamp-application/index.vue"), component: () => import("@/views/registration/index.vue"),
meta: { meta: {
title: "入库登记", title: "入库登记",
}, },
@@ -312,7 +288,7 @@ export const constantRoutes: RouteRecordRaw[] = [
{ {
path: "index", path: "index",
name: "NoticeIndex", name: "NoticeIndex",
component: () => import("@/views/stamp-application/index.vue"), component: () => import("@/views/notice/index.vue"),
meta: { meta: {
title: "公告管理", title: "公告管理",
}, },
@@ -333,7 +309,7 @@ export const constantRoutes: RouteRecordRaw[] = [
{ {
path: "index", path: "index",
name: "LawyerFileIndex", name: "LawyerFileIndex",
component: () => import("@/views/stamp-application/index.vue"), component: () => import("@/views/lawyer/index.vue"),
meta: { meta: {
title: "律所标准文件", title: "律所标准文件",
}, },

View File

@@ -26,6 +26,22 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<el-button
type="primary"
icon="edit"
style="margin-bottom: 20px"
@click="handleOpenDialog('立项登记')"
>
立项登记
</el-button>
<el-button
type="success"
icon="Plus"
style="margin-bottom: 20px"
@click="handleOpenDialog('投标登记')"
>
投标登记
</el-button>
<el-card shadow="hover" class="data-table"> <el-card shadow="hover" class="data-table">
<el-table <el-table
v-loading="loading" v-loading="loading"
@@ -76,7 +92,13 @@
:size="drawerSize" :size="drawerSize"
@close="handleCloseDialog" @close="handleCloseDialog"
> >
<el-form ref="userFormRef" :model="formData" :rules="rules" label-width="80px"> <el-form
v-show="dialog.title === '立项登记'"
ref="userFormRef"
:model="formData"
:rules="rules"
label-width="80px"
>
<el-form-item label="用户名" prop="username"> <el-form-item label="用户名" prop="username">
<el-input <el-input
v-model="formData.username" v-model="formData.username"
@@ -134,7 +156,21 @@
/> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-form
v-show="dialog.title === '投标登记'"
ref="userFormRef"
:model="formDataTender"
:rules="rulesTender"
label-width="80px"
>
<el-form-item label="案件名" prop="username">
<el-input
v-model="formData.username"
:readonly="!!formData.id"
placeholder="请输入案件名"
/>
</el-form-item>
</el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button type="primary" @click="handleSubmit"> </el-button> <el-button type="primary" @click="handleSubmit"> </el-button>
@@ -212,6 +248,11 @@ const formData = reactive<UserForm>({
status: 1, status: 1,
}); });
// 登记表格数据
const formDataTender = reactive<UserForm>({
status: 1,
});
// 下拉选项数据 // 下拉选项数据
const deptOptions = ref<OptionType[]>(); const deptOptions = ref<OptionType[]>();
const roleOptions = ref<OptionType[]>(); const roleOptions = ref<OptionType[]>();
@@ -273,6 +314,16 @@ const rules = reactive({
], ],
}); });
const rulesTender = reactive({
username: [
{
required: true,
message: "案件名不能为空",
trigger: "blur",
},
],
});
// ==================== 数据加载 ==================== // ==================== 数据加载 ====================
/** /**
@@ -346,8 +397,9 @@ function handleResetPassword(row: UserPageVO): void {
* 打开用户表单弹窗 * 打开用户表单弹窗
* @param id 用户ID编辑时传入 * @param id 用户ID编辑时传入
*/ */
async function handleOpenDialog(id?: string): Promise<void> { async function handleOpenDialog(titleString: string, id?: string): Promise<void> {
dialog.visible = true; dialog.visible = true;
dialog.title = titleString;
// 并行加载下拉选项数据 // 并行加载下拉选项数据
try { try {
@@ -362,7 +414,6 @@ async function handleOpenDialog(id?: string): Promise<void> {
// 编辑:加载用户数据 // 编辑:加载用户数据
if (id) { if (id) {
dialog.title = "修改用户";
try { try {
const data = await UserAPI.getFormData(id); const data = await UserAPI.getFormData(id);
Object.assign(formData, data); Object.assign(formData, data);
@@ -370,9 +421,6 @@ async function handleOpenDialog(id?: string): Promise<void> {
ElMessage.error("加载用户数据失败"); ElMessage.error("加载用户数据失败");
console.error("加载用户数据失败:", error); console.error("加载用户数据失败:", error);
} }
} else {
// 新增:设置默认值
dialog.title = "新增用户";
} }
} }

View File

@@ -353,7 +353,7 @@ const handleSaveDraft = () => {
<style scoped> <style scoped>
.pre-case-registration { .pre-case-registration {
max-width: 1000px; max-width: 1000px;
margin: 0 auto; margin: 20px auto;
padding: 20px; padding: 20px;
background-color: #fff; background-color: #fff;
border-radius: 8px; border-radius: 8px;

View File

@@ -0,0 +1,70 @@
<!-- 部门树 -->
<template>
<el-card shadow="never">
<el-input v-model="deptName" placeholder="部门名称" clearable>
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<el-tree
ref="deptTreeRef"
class="mt-2"
:data="deptList"
:props="{ children: 'children', label: 'label', disabled: '' }"
:expand-on-click-node="false"
:filter-node-method="handleFilter"
default-expand-all
@node-click="handleNodeClick"
/>
</el-card>
</template>
<script setup lang="ts">
import DeptAPI from "@/api/system/dept-api";
const props = defineProps({
modelValue: {
type: [String, Number],
default: undefined,
},
});
const deptList = ref<OptionType[]>(); // 部门列表
const deptTreeRef = ref(); // 部门树
const deptName = ref(); // 部门名称
const emits = defineEmits(["node-click"]);
const deptId = useVModel(props, "modelValue", emits);
watchEffect(
() => {
deptTreeRef.value.filter(deptName.value);
},
{
flush: "post", // watchEffect会在DOM挂载或者更新之前就会触发此属性控制在DOM元素更新后运行
}
);
/**
* 部门筛选
*/
function handleFilter(value: string, data: any) {
if (!value) {
return true;
}
return data.label.indexOf(value) !== -1;
}
/** 部门树节点 Click */
function handleNodeClick(data: { [key: string]: any }) {
deptId.value = data.value;
emits("node-click");
}
onBeforeMount(() => {
DeptAPI.getOptions().then((data) => {
deptList.value = data;
});
});
</script>

View File

@@ -0,0 +1,198 @@
<template>
<div>
<el-dialog
v-model="visible"
:align-center="true"
title="导入数据"
width="600px"
@close="handleClose"
>
<el-scrollbar max-height="60vh">
<el-form
ref="importFormRef"
style="padding-right: var(--el-dialog-padding-primary)"
:model="importFormData"
:rules="importFormRules"
>
<el-form-item label="文件名" prop="files">
<el-upload
ref="uploadRef"
v-model:file-list="importFormData.files"
class="w-full"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
:drag="true"
:limit="1"
:auto-upload="false"
:on-exceed="handleFileExceed"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处
<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
格式为*.xlsx / *.xls文件不超过一个
<el-link
type="primary"
icon="download"
underline="never"
@click="handleDownloadTemplate"
>
下载模板
</el-link>
</div>
</template>
</el-upload>
</el-form-item>
</el-form>
</el-scrollbar>
<template #footer>
<div style="padding-right: var(--el-dialog-padding-primary)">
<el-button v-if="resultData.length > 0" type="primary" @click="handleShowResult">
错误信息
</el-button>
<el-button
type="primary"
:disabled="importFormData.files.length === 0"
@click="handleUpload"
>
</el-button>
<el-button @click="handleClose"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog v-model="resultVisible" title="导入结果" width="600px">
<el-alert
:title="`导入结果:${invalidCount}条无效数据,${validCount}条有效数据`"
type="warning"
:closable="false"
/>
<el-table :data="resultData" style="width: 100%; max-height: 400px">
<el-table-column prop="index" align="center" width="100" type="index" label="序号" />
<el-table-column prop="message" label="错误信息" width="400">
<template #default="scope">
{{ scope.row }}
</template>
</el-table-column>
</el-table>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCloseResult">关闭</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ElMessage, type UploadUserFile } from "element-plus";
import UserAPI from "@/api/system/user-api";
import { ApiCodeEnum } from "@/enums/api/code-enum";
const emit = defineEmits(["import-success"]);
const visible = defineModel("modelValue", {
type: Boolean,
required: true,
default: false,
});
const resultVisible = ref(false);
const resultData = ref<string[]>([]);
const invalidCount = ref(0);
const validCount = ref(0);
const importFormRef = ref(null);
const uploadRef = ref(null);
const importFormData = reactive<{
files: UploadUserFile[];
}>({
files: [],
});
watch(visible, (newValue) => {
if (newValue) {
resultData.value = [];
resultVisible.value = false;
invalidCount.value = 0;
validCount.value = 0;
}
});
const importFormRules = {
files: [{ required: true, message: "文件不能为空", trigger: "blur" }],
};
// 文件超出个数限制
const handleFileExceed = () => {
ElMessage.warning("只能上传一个文件");
};
// 下载导入模板
const handleDownloadTemplate = () => {
UserAPI.downloadTemplate().then((response: any) => {
const fileData = response.data;
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
const fileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
const blob = new Blob([fileData], { type: fileType });
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
});
};
// 上传文件
const handleUpload = async () => {
if (!importFormData.files.length) {
ElMessage.warning("请选择文件");
return;
}
try {
const result = await UserAPI.import("1", importFormData.files[0].raw as File);
if (result.code === ApiCodeEnum.SUCCESS && result.invalidCount === 0) {
ElMessage.success("导入成功,导入数据:" + result.validCount + "条");
emit("import-success");
handleClose();
} else {
ElMessage.error("上传失败");
resultVisible.value = true;
resultData.value = result.messageList;
invalidCount.value = result.invalidCount;
validCount.value = result.validCount;
}
} catch (error: any) {
console.error(error);
ElMessage.error("上传失败:" + error);
}
};
// 显示错误信息
const handleShowResult = () => {
resultVisible.value = true;
};
// 关闭错误信息弹窗
const handleCloseResult = () => {
resultVisible.value = false;
};
// 关闭弹窗
const handleClose = () => {
importFormData.files.length = 0;
visible.value = false;
};
</script>

682
src/views/case/index.vue Normal file
View File

@@ -0,0 +1,682 @@
<!-- 用户管理 -->
<template>
<div class="app-container">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="auto">
<el-form-item label="关键字" prop="keywords">
<el-input
v-model="queryParams.keywords"
placeholder="合同编号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 100px">
<el-option label="正常" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="queryParams.createTime"
:editable="false"
type="daterange"
range-separator="~"
start-placeholder="开始时间"
end-placeholder="截止时间"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item class="search-buttons">
<el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
<el-button icon="refresh" @click="handleResetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<el-card shadow="hover" class="data-table">
<div class="data-table__toolbar">
<div class="data-table__toolbar--actions">
<el-button
v-hasPerm="['sys:user:add']"
type="success"
icon="plus"
@click="handleOpenDialog()"
>
新增
</el-button>
<el-button
v-hasPerm="'sys:user:delete'"
type="danger"
icon="delete"
:disabled="!hasSelection"
@click="handleDelete()"
>
删除
</el-button>
</div>
<div class="data-table__toolbar--tools">
<el-button v-hasPerm="'sys:user:import'" icon="upload" @click="handleOpenImportDialog">
导入
</el-button>
<el-button v-hasPerm="'sys:user:export'" icon="download" @click="handleExport">
导出
</el-button>
</div>
</div>
<el-table
v-loading="loading"
:data="pageData"
border
stripe
highlight-current-row
class="data-table__content"
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table-column label="合同编号" prop="username" />
<el-table-column label="项目类型" width="150" align="center" prop="nickname" />
<el-table-column label="客户名称" width="100" align="center">
<template #default="scope">
<DictLabel v-model="scope.row.gender" code="gender" />
</template>
</el-table-column>
<el-table-column label="相对方名称" width="120" align="center" prop="deptName" />
<el-table-column label="项目简述" align="center" prop="mobile" width="120" />
<el-table-column label="负责人" align="center" prop="email" width="160">
<template #default="scope">
<span @click="handleOpenChangeResponsibleDialog(scope.row)">
{{ scope.row.responsibleName || "未分配" }}
</span>
</template>
</el-table-column>
<el-table-column label="收费情况" align="center" prop="status" width="80">
<template #default="scope">
<el-tag :type="scope.row.status == 1 ? 'success' : 'info'">
{{ scope.row.status == 1 ? "正常" : "禁用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="150" />
<el-table-column label="立案时间" align="center" prop="createTime" width="150" />
<el-table-column label="代理合同" align="center" prop="createTime" width="150" />
<el-table-column label="合同返还" align="center" prop="createTime" width="150" />
<el-table-column label="结案申请" align="center" prop="createTime" width="150" />
<el-table-column label="变更申请" align="center" prop="createTime" width="150" />
<el-table-column label="已开票" align="center" prop="createTime" width="150" />
<el-table-column label="已收款" align="center" prop="createTime" width="150" />
<el-table-column label="案件日志" align="center" prop="createTime" width="150" />
</el-table>
<pagination
v-if="total > 0"
v-model:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="fetchUserList"
/>
</el-card>
<!-- 用户导入 -->
<UserImport v-model="importDialogVisible" @import-success="handleQuery()" />
</div>
</template>
<script setup lang="ts">
// ==================== 1. Vue 核心 API ====================
import { computed, onMounted, reactive, ref } from "vue";
import { useDebounceFn } from "@vueuse/core";
// ==================== 2. Element Plus ====================
import { ElMessage, ElMessageBox } from "element-plus";
// ==================== 3. 类型定义 ====================
import type { UserForm, UserPageQuery, UserPageVO } from "@/api/system/user-api";
// ==================== 4. API 服务 ====================
import UserAPI from "@/api/system/user-api";
import DeptAPI from "@/api/system/dept-api";
import RoleAPI from "@/api/system/role-api";
import fileApi from "@/api/file-api";
// ==================== 5. Store ====================
import { useAppStore } from "@/store/modules/app-store";
import { useUserStore } from "@/store";
// ==================== 6. Enums ====================
import { DeviceEnum } from "@/enums/settings/device-enum";
// ==================== 7. Composables ====================
import { useAiAction, useTableSelection } from "@/composables";
// ==================== 8. 组件 ====================
import DeptTree from "./components/DeptTree.vue";
import UserImport from "./components/UserImport.vue";
// ==================== 组件配置 ====================
defineOptions({
name: "SystemUser",
inheritAttrs: false,
});
// ==================== Store 实例 ====================
const appStore = useAppStore();
const userStore = useUserStore();
// ==================== 响应式状态 ====================
// DOM 引用
const queryFormRef = ref();
const userFormRef = ref();
// 列表查询参数
const queryParams = reactive<UserPageQuery>({
pageNum: 1,
pageSize: 10,
});
// 列表数据
const pageData = ref<UserPageVO[]>();
const total = ref(0);
const loading = ref(false);
// 弹窗状态
const dialog = reactive({
visible: false,
title: "新增用户",
});
// 当前选中的案件
const currentCase = ref(null);
// 负责人
const changeResponsible = ref("");
// 打开负责人更改弹窗
function handleOpenChangeResponsibleDialog(row) {
ElMessageBox.prompt(`请输入案件【${row.username}】的新负责人`, "负责人更改", {
confirmButtonText: "确定",
cancelButtonText: "取消",
inputPattern: /.{1,}/,
inputErrorMessage: "负责人不能为空",
})
.then(({ value }) => {
return UserAPI.resetPassword(row.id, value);
})
.then(() => {
ElMessage.success("负责人更改成功");
})
.catch((error) => {
if (error !== "cancel") {
ElMessage.error("负责人更改失败");
}
});
}
// 更改负责人
function handleChangeResponsible() {
if (!changeResponsible.value.trim()) {
ElMessage.warning("请输入负责人");
return;
}
try {
// 这里应该调用案件管理API更新负责人
// 更新本地数据
if (currentCase.value) {
currentCase.value.responsibleName = changeResponsible.value;
// 更新列表中的数据
if (pageData.value && pageData.value.length > 0) {
const index = pageData.value.findIndex((item) => item.id === currentCase.value.id);
if (index !== -1) {
pageData.value[index].responsibleName = changeResponsible.value;
}
}
}
ElMessage.success("负责人更改成功");
changeResponsibleDialogVisible.value = false;
changeResponsible.value = "";
currentCase.value = null;
} catch (error) {
ElMessage.error("负责人更改失败");
console.error("更改负责人失败:", error);
}
}
// 表单数据
const formData = reactive<UserForm>({
educationResume: [
{
education: [],
// 毕业院校
institute: "",
// 专业
major: "",
// 学历
educationLevel: "",
},
],
});
// 下拉选项数据
const deptOptions = ref<OptionType[]>();
const roleOptions = ref<OptionType[]>();
// 导入弹窗
const importDialogVisible = ref(false);
// ==================== 计算属性 ====================
/**
* 抽屉尺寸(响应式)
*/
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "600px" : "90%"));
// ==================== 表单验证规则 ====================
const rules = reactive({
username: [
{
required: true,
message: "用户名不能为空",
trigger: "blur",
},
],
nickname: [
{
required: true,
message: "用户昵称不能为空",
trigger: "blur",
},
],
deptId: [
{
required: true,
message: "所属部门不能为空",
trigger: "blur",
},
],
roleIds: [
{
required: true,
message: "用户角色不能为空",
trigger: "blur",
},
],
email: [
{
pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/,
message: "请输入正确的邮箱地址",
trigger: "blur",
},
],
mobile: [
{
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
message: "请输入正确的手机号码",
trigger: "blur",
},
],
educationResume: [
{
required: true,
message: "请输入教育经历",
trigger: "blur",
},
],
});
// ==================== 数据加载 ====================
/**
* 获取用户列表数据
*/
async function fetchUserList(): Promise<void> {
loading.value = true;
try {
const data = await UserAPI.getPage(queryParams);
pageData.value = data.list;
total.value = data.total;
} catch (error) {
ElMessage.error("获取用户列表失败");
console.error("获取用户列表失败:", error);
} finally {
loading.value = false;
}
}
// ==================== 表格选择 ====================
const { selectedIds, hasSelection, handleSelectionChange } = useTableSelection<UserPageVO>();
// ==================== 查询操作 ====================
/**
* 查询用户列表
*/
function handleQuery(): Promise<void> {
queryParams.pageNum = 1;
return fetchUserList();
}
/**
* 重置查询条件
*/
function handleResetQuery(): void {
queryFormRef.value.resetFields();
queryParams.deptId = undefined;
queryParams.createTime = undefined;
handleQuery();
}
// ==================== 用户操作 ====================
/**
* 重置用户密码
* @param row 用户数据
*/
function handleResetPassword(row: UserPageVO): void {
ElMessageBox.prompt(`请输入用户【${row.username}】的新密码`, "重置密码", {
confirmButtonText: "确定",
cancelButtonText: "取消",
inputPattern: /.{6,}/,
inputErrorMessage: "密码至少需要6位字符",
})
.then(({ value }) => {
return UserAPI.resetPassword(row.id, value);
})
.then(() => {
ElMessage.success("密码重置成功");
})
.catch((error) => {
if (error !== "cancel") {
ElMessage.error("密码重置失败");
}
});
}
// ==================== 弹窗操作 ====================
/**
* 打开用户表单弹窗
* @param id 用户ID编辑时传入
*/
async function handleOpenDialog(id?: string): Promise<void> {
dialog.visible = true;
// 并行加载下拉选项数据
try {
[roleOptions.value, deptOptions.value] = await Promise.all([
RoleAPI.getOptions(),
DeptAPI.getOptions(),
]);
} catch (error) {
ElMessage.error("加载选项数据失败");
console.error("加载选项数据失败:", error);
}
// 编辑:加载用户数据
if (id) {
dialog.title = "修改用户";
try {
const data = await UserAPI.getFormData(id);
Object.assign(formData, data);
} catch (error) {
ElMessage.error("加载用户数据失败");
console.error("加载用户数据失败:", error);
}
} else {
// 新增:设置默认值
dialog.title = "新增用户";
}
}
/**
* 关闭用户表单弹窗
*/
function handleCloseDialog(): void {
dialog.visible = false;
userFormRef.value.resetFields();
userFormRef.value.clearValidate();
// 重置表单数据
formData.id = undefined;
}
/**
* 提交用户表单(防抖)
*/
const handleSubmit = useDebounceFn(async () => {
const valid = await userFormRef.value.validate().catch(() => false);
if (!valid) return;
const userId = formData.id;
loading.value = true;
try {
if (userId) {
await UserAPI.update(userId, formData);
ElMessage.success("修改用户成功");
} else {
await UserAPI.create(formData);
ElMessage.success("新增用户成功");
}
handleCloseDialog();
handleResetQuery();
} catch (error) {
ElMessage.error(userId ? "修改用户失败" : "新增用户失败");
console.error("提交用户表单失败:", error);
} finally {
loading.value = false;
}
}, 1000);
/**
* 删除用户
* @param id 用户ID单个删除时传入
*/
function handleDelete(id?: string): void {
const userIds = id ? id : selectedIds.value.join(",");
if (!userIds) {
ElMessage.warning("请勾选删除项");
return;
}
// 安全检查:防止删除当前登录用户
const currentUserId = userStore.userInfo?.userId;
if (currentUserId) {
const isCurrentUserInList = id
? id === currentUserId
: selectedIds.value.some((selectedId) => String(selectedId) === currentUserId);
if (isCurrentUserInList) {
ElMessage.error("不能删除当前登录用户");
return;
}
}
ElMessageBox.confirm("确认删除选中的用户吗?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
loading.value = true;
try {
await UserAPI.deleteByIds(userIds);
ElMessage.success("删除成功");
handleResetQuery();
} catch (error) {
ElMessage.error("删除失败");
console.error("删除用户失败:", error);
} finally {
loading.value = false;
}
})
.catch(() => {
// 用户取消操作,无需处理
});
}
// ==================== 上传文件 ====================
/**
* 上传前的校验
* @param file 上传的文件对象
*/
function handleBeforeUpload(file: File): boolean {
// 可以在这里添加文件类型和大小的校验
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
ElMessage.error("上传文件大小不能超过 10MB!");
return false;
}
return true;
}
/**
* 处理文件上传成功后的回调
* @param res 上传响应数据
* @param file 上传的文件对象
* @param field 表单字段名
*/
function handleFileUploadSuccess(res: any, file: File, field: string): void {
formData[field] = res.data.url;
userFormRef.value.clearValidate(field);
ElMessage.success("文件上传成功");
}
/**
* 删除已上传的文件
* @param field 表单字段名
*/
function removeFile(field: string): void {
formData[field] = undefined;
ElMessage.success("文件已删除");
}
// ==================== 导入导出 ====================
/**
* 打开导入弹窗
*/
function handleOpenImportDialog(): void {
importDialogVisible.value = true;
}
/**
* 导出用户列表
*/
async function handleExport(): Promise<void> {
try {
const response = await UserAPI.export(queryParams);
const fileData = response.data;
const contentDisposition = response.headers["content-disposition"];
const fileName = decodeURI(contentDisposition.split(";")[1].split("=")[1]);
const fileType =
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
// 创建下载链接
const blob = new Blob([fileData], { type: fileType });
const downloadUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = downloadUrl;
downloadLink.download = fileName;
// 触发下载
document.body.appendChild(downloadLink);
downloadLink.click();
// 清理
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(downloadUrl);
ElMessage.success("导出成功");
} catch (error) {
ElMessage.error("导出失败");
console.error("导出用户列表失败:", error);
}
}
// ==================== 生命周期 ====================
/**
* 组件挂载时初始化数据
*
* 注意:这里会先加载列表数据,如果 URL 中有 AI 参数(如搜索关键字),
* useAiAction 会在 nextTick 中再次执行搜索,这是预期行为
*/
onMounted(() => {
handleQuery();
});
</script>
<!-- 学业简历表格样式 -->
<style scoped>
.education-resume-table {
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
}
.education-resume-header {
display: flex;
background-color: #f5f7fa;
border-bottom: 1px solid #dcdfe6;
font-weight: bold;
padding: 8px 12px;
}
.education-resume-row {
display: flex;
border-bottom: 1px solid #ebeef5;
padding: 12px;
align-items: center;
}
.education-resume-row:last-child {
border-bottom: none;
}
.table-col {
display: flex;
align-items: center;
}
.date-col {
width: 25%;
}
.school-col {
width: 25%;
}
.major-col {
width: 20%;
}
.degree-col {
width: 20%;
}
.action-col {
width: 10%;
justify-content: center;
}
.education-resume-actions {
margin-top: 12px;
display: flex;
justify-content: flex-start;
}
/* 表单元素样式调整 */
.education-resume-row .el-input,
.education-resume-row .el-select,
.education-resume-row .el-date-editor {
width: 100%;
}
</style>

138
src/views/lawyer/index.vue Normal file
View File

@@ -0,0 +1,138 @@
<template>
<div class="performance-list-container">
<el-button type="primary" icon="edit" style="margin-bottom: 20px">新增律所文件</el-button>
<div
v-for="(item, index) in performanceList"
:key="index"
class="performance-item"
@click="handleItemClick(item)"
>
<div class="item-header">
<img :src="item.imageUrl" :alt="item.title" class="item-image" />
</div>
<div class="item-content">
<h3 class="item-title">{{ item.title }}</h3>
<p class="item-description">{{ item.description }}</p>
</div>
<div class="item-arrow">
<el-icon><ArrowRight /></el-icon>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { ArrowRight } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
// 定义列表数据
const performanceList = ref([
{
id: 1,
title: "案件办理效率提升",
description: "本月案件办理时间平均缩短20%,客户满意度显著提升",
imageUrl: "https://via.placeholder.com/100x100?text=Case+Efficiency",
},
{
id: 2,
title: "团队协作优化",
description: "通过新的协作工具团队沟通效率提升30%",
imageUrl: "https://via.placeholder.com/100x100?text=Team+Collab",
},
{
id: 3,
title: "客户反馈分析",
description: "收集并分析了100份客户反馈制定了改进计划",
imageUrl: "https://via.placeholder.com/100x100?text=Feedback",
},
{
id: 4,
title: "知识库更新",
description: "新增50篇法律知识库文章提升了团队专业能力",
imageUrl: "https://via.placeholder.com/100x100?text=Knowledge",
},
]);
// 点击事件处理
function handleItemClick(item) {
ElMessage.info(`您点击了:${item.title}`);
// 这里可以添加跳转或其他逻辑
}
</script>
<style lang="scss" scoped>
.performance-list-container {
width: 100%;
padding: 20px;
box-sizing: border-box;
}
.performance-item {
display: flex;
align-items: center;
height: 120px;
width: 100%;
padding: 15px;
margin-bottom: 15px;
border: 1px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
box-sizing: border-box;
background-color: #fff;
&:hover {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
border-color: #409eff;
}
.item-header {
flex-shrink: 0;
margin-right: 20px;
}
.item-image {
width: 90px;
height: 90px;
object-fit: cover;
border-radius: 4px;
}
.item-content {
flex: 1;
min-width: 0;
}
.item-title {
margin: 0 0 8px 0;
font-size: 18px;
font-weight: 500;
color: #303133;
}
.item-description {
margin: 0;
font-size: 14px;
color: #909399;
line-height: 1.5;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.item-arrow {
flex-shrink: 0;
margin-left: 20px;
color: #c0c4cc;
font-size: 16px;
transition: color 0.3s ease;
&:hover {
color: #409eff;
}
}
}
</style>

138
src/views/notice/index.vue Normal file
View File

@@ -0,0 +1,138 @@
<template>
<div class="performance-list-container">
<el-button type="primary" icon="edit" style="margin-bottom: 20px">新增公告</el-button>
<div
v-for="(item, index) in performanceList"
:key="index"
class="performance-item"
@click="handleItemClick(item)"
>
<div class="item-header">
<img :src="item.imageUrl" :alt="item.title" class="item-image" />
</div>
<div class="item-content">
<h3 class="item-title">{{ item.title }}</h3>
<p class="item-description">{{ item.description }}</p>
</div>
<div class="item-arrow">
<el-icon><ArrowRight /></el-icon>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { ArrowRight } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
// 定义列表数据
const performanceList = ref([
{
id: 1,
title: "案件办理效率提升",
description: "本月案件办理时间平均缩短20%,客户满意度显著提升",
imageUrl: "https://via.placeholder.com/100x100?text=Case+Efficiency",
},
{
id: 2,
title: "团队协作优化",
description: "通过新的协作工具团队沟通效率提升30%",
imageUrl: "https://via.placeholder.com/100x100?text=Team+Collab",
},
{
id: 3,
title: "客户反馈分析",
description: "收集并分析了100份客户反馈制定了改进计划",
imageUrl: "https://via.placeholder.com/100x100?text=Feedback",
},
{
id: 4,
title: "知识库更新",
description: "新增50篇法律知识库文章提升了团队专业能力",
imageUrl: "https://via.placeholder.com/100x100?text=Knowledge",
},
]);
// 点击事件处理
function handleItemClick(item) {
ElMessage.info(`您点击了:${item.title}`);
// 这里可以添加跳转或其他逻辑
}
</script>
<style lang="scss" scoped>
.performance-list-container {
width: 100%;
padding: 20px;
box-sizing: border-box;
}
.performance-item {
display: flex;
align-items: center;
height: 120px;
width: 100%;
padding: 15px;
margin-bottom: 15px;
border: 1px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
box-sizing: border-box;
background-color: #fff;
&:hover {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
border-color: #409eff;
}
.item-header {
flex-shrink: 0;
margin-right: 20px;
}
.item-image {
width: 90px;
height: 90px;
object-fit: cover;
border-radius: 4px;
}
.item-content {
flex: 1;
min-width: 0;
}
.item-title {
margin: 0 0 8px 0;
font-size: 18px;
font-weight: 500;
color: #303133;
}
.item-description {
margin: 0;
font-size: 14px;
color: #909399;
line-height: 1.5;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.item-arrow {
flex-shrink: 0;
margin-left: 20px;
color: #c0c4cc;
font-size: 16px;
transition: color 0.3s ease;
&:hover {
color: #409eff;
}
}
}
</style>

View File

@@ -0,0 +1,138 @@
<template>
<div class="performance-list-container">
<el-button type="primary" icon="edit" style="margin-bottom: 20px">新增业绩</el-button>
<div
v-for="(item, index) in performanceList"
:key="index"
class="performance-item"
@click="handleItemClick(item)"
>
<div class="item-header">
<img :src="item.imageUrl" :alt="item.title" class="item-image" />
</div>
<div class="item-content">
<h3 class="item-title">{{ item.title }}</h3>
<p class="item-description">{{ item.description }}</p>
</div>
<div class="item-arrow">
<el-icon><ArrowRight /></el-icon>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { ArrowRight } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
// 定义列表数据
const performanceList = ref([
{
id: 1,
title: "案件办理效率提升",
description: "本月案件办理时间平均缩短20%,客户满意度显著提升",
imageUrl: "https://via.placeholder.com/100x100?text=Case+Efficiency",
},
{
id: 2,
title: "团队协作优化",
description: "通过新的协作工具团队沟通效率提升30%",
imageUrl: "https://via.placeholder.com/100x100?text=Team+Collab",
},
{
id: 3,
title: "客户反馈分析",
description: "收集并分析了100份客户反馈制定了改进计划",
imageUrl: "https://via.placeholder.com/100x100?text=Feedback",
},
{
id: 4,
title: "知识库更新",
description: "新增50篇法律知识库文章提升了团队专业能力",
imageUrl: "https://via.placeholder.com/100x100?text=Knowledge",
},
]);
// 点击事件处理
function handleItemClick(item) {
ElMessage.info(`您点击了:${item.title}`);
// 这里可以添加跳转或其他逻辑
}
</script>
<style lang="scss" scoped>
.performance-list-container {
width: 100%;
padding: 20px;
box-sizing: border-box;
}
.performance-item {
display: flex;
align-items: center;
height: 120px;
width: 100%;
padding: 15px;
margin-bottom: 15px;
border: 1px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
box-sizing: border-box;
background-color: #fff;
&:hover {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
border-color: #409eff;
}
.item-header {
flex-shrink: 0;
margin-right: 20px;
}
.item-image {
width: 90px;
height: 90px;
object-fit: cover;
border-radius: 4px;
}
.item-content {
flex: 1;
min-width: 0;
}
.item-title {
margin: 0 0 8px 0;
font-size: 18px;
font-weight: 500;
color: #303133;
}
.item-description {
margin: 0;
font-size: 14px;
color: #909399;
line-height: 1.5;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.item-arrow {
flex-shrink: 0;
margin-left: 20px;
color: #c0c4cc;
font-size: 16px;
transition: color 0.3s ease;
&:hover {
color: #409eff;
}
}
}
</style>

View File

@@ -0,0 +1,138 @@
<template>
<div class="performance-list-container">
<el-button type="primary" icon="edit" style="margin-bottom: 20px">入库登记</el-button>
<div
v-for="(item, index) in performanceList"
:key="index"
class="performance-item"
@click="handleItemClick(item)"
>
<div class="item-header">
<img :src="item.imageUrl" :alt="item.title" class="item-image" />
</div>
<div class="item-content">
<h3 class="item-title">{{ item.title }}</h3>
<p class="item-description">{{ item.description }}</p>
</div>
<div class="item-arrow">
<el-icon><ArrowRight /></el-icon>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { ArrowRight } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
// 定义列表数据
const performanceList = ref([
{
id: 1,
title: "案件办理效率提升",
description: "本月案件办理时间平均缩短20%,客户满意度显著提升",
imageUrl: "https://via.placeholder.com/100x100?text=Case+Efficiency",
},
{
id: 2,
title: "团队协作优化",
description: "通过新的协作工具团队沟通效率提升30%",
imageUrl: "https://via.placeholder.com/100x100?text=Team+Collab",
},
{
id: 3,
title: "客户反馈分析",
description: "收集并分析了100份客户反馈制定了改进计划",
imageUrl: "https://via.placeholder.com/100x100?text=Feedback",
},
{
id: 4,
title: "知识库更新",
description: "新增50篇法律知识库文章提升了团队专业能力",
imageUrl: "https://via.placeholder.com/100x100?text=Knowledge",
},
]);
// 点击事件处理
function handleItemClick(item) {
ElMessage.info(`您点击了:${item.title}`);
// 这里可以添加跳转或其他逻辑
}
</script>
<style lang="scss" scoped>
.performance-list-container {
width: 100%;
padding: 20px;
box-sizing: border-box;
}
.performance-item {
display: flex;
align-items: center;
height: 120px;
width: 100%;
padding: 15px;
margin-bottom: 15px;
border: 1px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
box-sizing: border-box;
background-color: #fff;
&:hover {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
border-color: #409eff;
}
.item-header {
flex-shrink: 0;
margin-right: 20px;
}
.item-image {
width: 90px;
height: 90px;
object-fit: cover;
border-radius: 4px;
}
.item-content {
flex: 1;
min-width: 0;
}
.item-title {
margin: 0 0 8px 0;
font-size: 18px;
font-weight: 500;
color: #303133;
}
.item-description {
margin: 0;
font-size: 14px;
color: #909399;
line-height: 1.5;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.item-arrow {
flex-shrink: 0;
margin-left: 20px;
color: #c0c4cc;
font-size: 16px;
transition: color 0.3s ease;
&:hover {
color: #409eff;
}
}
}
</style>

View File

@@ -1,26 +1,421 @@
<template> <template>
<div class="stamp-application"> <div class="pre-case-registration">
<h1>申请用印页面</h1> <h1>申请用印</h1>
<p>这是一个没有子节点的路由示例页面</p>
<!-- 预立案日期 -->
<div class="form-item">
<label class="form-label" for="preCaseDate">预立案日期*</label>
<el-date-picker
id="preCaseDate"
v-model="formData.preCaseDate"
type="date"
placeholder="请选择预立案日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
required
/>
</div>
<!-- 委托人信息 -->
<div class="section">
<h2>委托人信息</h2>
<el-table
:data="formData.clients"
border
style="width: 100%"
class="client-table"
@selection-change="handleClientSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="index" label="序号" width="80" align="center" />
<el-table-column prop="name" label="自然人姓名/法人名称*" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.name" placeholder="请输入" style="width: 100%" />
</template>
</el-table-column>
<el-table-column prop="idNumber" label="身份证号码/统一代码*" min-width="250">
<template #default="scope">
<el-input v-model="scope.row.idNumber" placeholder="请输入" style="width: 100%" />
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center">
<template #default="scope">
<el-button type="primary" size="small" @click="copyClient(scope.row)">复制</el-button>
<el-button type="danger" size="small" @click="deleteClient(scope.$index)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="table-actions">
<el-button type="primary" @click="addClient">
<el-icon><Plus /></el-icon>
新增一项
</el-button>
<el-button type="danger" @click="batchDeleteClients">
<el-icon><Delete /></el-icon>
批量删除
</el-button>
</div>
</div>
<!-- 相对方信息 -->
<div class="section">
<h2>相对方信息</h2>
<el-table
:data="formData.opponents"
border
style="width: 100%"
class="opponent-table"
@selection-change="handleOpponentSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="index" label="序号" width="80" align="center" />
<el-table-column prop="name" label="自然人姓名/法人名称*" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.name" placeholder="请输入" style="width: 100%" />
</template>
</el-table-column>
<el-table-column prop="idNumber" label="身份证号码/统一代码*" min-width="250">
<template #default="scope">
<el-input v-model="scope.row.idNumber" placeholder="请输入" style="width: 100%" />
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center">
<template #default="scope">
<el-button type="primary" size="small" @click="copyOpponent(scope.row)">复制</el-button>
<el-button type="danger" size="small" @click="deleteOpponent(scope.$index)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="table-actions">
<el-button type="primary" @click="addOpponent">
<el-icon><Plus /></el-icon>
新增一项
</el-button>
<el-button type="danger" @click="batchDeleteOpponents">
<el-icon><Delete /></el-icon>
批量删除
</el-button>
</div>
</div>
<!-- 项目简述/案由 -->
<div class="form-item">
<label class="form-label" for="projectDescription">项目简述/案由*</label>
<el-input
id="projectDescription"
v-model="formData.projectDescription"
type="textarea"
placeholder="简要陈述项目情况"
:rows="4"
style="width: 100%"
required
/>
</div>
<!-- 承办人员 -->
<div class="form-item">
<label class="form-label" for="handler">承办人员*</label>
<el-select
id="handler"
v-model="formData.handler"
placeholder="请选择"
style="width: 100%"
required
>
<!-- 这里可以根据实际情况动态加载承办人员列表 -->
<el-option label="承办人1" value="1" />
<el-option label="承办人2" value="2" />
<el-option label="承办人3" value="3" />
</el-select>
</div>
<!-- 提交按钮 -->
<div class="form-actions">
<el-button type="primary" @click="handleSubmit">提交</el-button>
<el-button @click="handleSaveDraft">暂存</el-button>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// 可以在这里添加组件的逻辑 import { ref, reactive } from "vue";
import { ElMessage } from "element-plus";
import { Plus, Delete } from "@element-plus/icons-vue";
// 定义表单数据类型
interface Client {
index: number;
name: string;
idNumber: string;
}
interface Opponent {
index: number;
name: string;
idNumber: string;
}
interface FormData {
preCaseDate: string;
clients: Client[];
opponents: Opponent[];
projectDescription: string;
handler: string;
}
// 选择的委托人
const selectedClients = ref<Client[]>([]);
// 选择的相对方
const selectedOpponents = ref<Opponent[]>([]);
// 表单数据
const formData = reactive<FormData>({
preCaseDate: "",
clients: [{ index: 1, name: "", idNumber: "" }],
opponents: [{ index: 1, name: "", idNumber: "" }],
projectDescription: "",
handler: "",
});
// 新增委托人
const addClient = () => {
const newIndex = formData.clients.length + 1;
formData.clients.push({ index: newIndex, name: "", idNumber: "" });
};
// 删除委托人
const deleteClient = (index: number) => {
if (formData.clients.length > 1) {
formData.clients.splice(index, 1);
// 更新序号
formData.clients.forEach((client, idx) => {
client.index = idx + 1;
});
} else if (formData.opponents.length === 0) {
ElMessage.warning("委托方和相对方至少需要存在一个");
} else {
formData.clients.splice(index, 1);
}
};
// 复制委托人
const copyClient = (client: Client) => {
const newIndex = formData.clients.length + 1;
formData.clients.push({ ...client, index: newIndex });
};
// 处理委托人选择
const handleClientSelectionChange = (selection: Client[]) => {
selectedClients.value = selection;
};
// 批量删除委托人
const batchDeleteClients = () => {
if (selectedClients.value.length === 0) {
ElMessage.warning("请选择要删除的委托人");
return;
}
// 检查是否删除后还剩至少一个(或相对方存在)
if (
formData.clients.length - selectedClients.value.length === 0 &&
formData.opponents.length === 0
) {
ElMessage.warning("委托方和相对方至少需要存在一个");
return;
}
// 执行批量删除
formData.clients = formData.clients.filter((client) => !selectedClients.value.includes(client));
// 更新序号
formData.clients.forEach((client, idx) => {
client.index = idx + 1;
});
// 清空选择
selectedClients.value = [];
ElMessage.success("批量删除成功");
};
// 新增相对方
const addOpponent = () => {
const newIndex = formData.opponents.length + 1;
formData.opponents.push({ index: newIndex, name: "", idNumber: "" });
};
// 删除相对方
const deleteOpponent = (index: number) => {
if (formData.opponents.length > 1) {
formData.opponents.splice(index, 1);
// 更新序号
formData.opponents.forEach((opponent, idx) => {
opponent.index = idx + 1;
});
} else if (formData.clients.length === 0) {
ElMessage.warning("委托方和相对方至少需要存在一个");
} else {
formData.opponents.splice(index, 1);
}
};
// 复制相对方
const copyOpponent = (opponent: Opponent) => {
const newIndex = formData.opponents.length + 1;
formData.opponents.push({ ...opponent, index: newIndex });
};
// 处理相对方选择
const handleOpponentSelectionChange = (selection: Opponent[]) => {
selectedOpponents.value = selection;
};
// 批量删除相对方
const batchDeleteOpponents = () => {
if (selectedOpponents.value.length === 0) {
ElMessage.warning("请选择要删除的相对方");
return;
}
// 检查是否删除后还剩至少一个(或委托方存在)
if (
formData.opponents.length - selectedOpponents.value.length === 0 &&
formData.clients.length === 0
) {
ElMessage.warning("委托方和相对方至少需要存在一个");
return;
}
// 执行批量删除
formData.opponents = formData.opponents.filter(
(opponent) => !selectedOpponents.value.includes(opponent)
);
// 更新序号
formData.opponents.forEach((opponent, idx) => {
opponent.index = idx + 1;
});
// 清空选择
selectedOpponents.value = [];
ElMessage.success("批量删除成功");
};
// 提交表单
const handleSubmit = () => {
// 表单验证
if (!formData.preCaseDate) {
ElMessage.error("请选择预立案日期");
return;
}
// 验证委托方和相对方至少存在一个
if (formData.clients.length === 0 && formData.opponents.length === 0) {
ElMessage.error("委托方和相对方至少需要存在一个");
return;
}
// 验证委托人信息(如果存在)
const invalidClients = formData.clients.filter((client) => !client.name || !client.idNumber);
if (invalidClients.length > 0) {
ElMessage.error("请填写完整的委托人信息");
return;
}
// 验证相对方信息(如果存在)
const invalidOpponents = formData.opponents.filter(
(opponent) => !opponent.name || !opponent.idNumber
);
if (invalidOpponents.length > 0) {
ElMessage.error("请填写完整的相对方信息");
return;
}
if (!formData.projectDescription) {
ElMessage.error("请填写项目简述/案由");
return;
}
if (!formData.handler) {
ElMessage.error("请选择承办人员");
return;
}
// 提交数据到后端
console.log("提交表单数据:", formData);
ElMessage.success("表单提交成功");
};
// 暂存表单
const handleSaveDraft = () => {
// 保存草稿逻辑
console.log("暂存表单数据:", formData);
ElMessage.success("表单暂存成功");
};
</script> </script>
<style scoped> <style scoped>
.stamp-application { .pre-case-registration {
max-width: 1000px;
margin: 20px auto;
padding: 20px; padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
} }
h1 { h1 {
margin-bottom: 20px; font-size: 24px;
color: #333; margin-bottom: 30px;
text-align: center;
color: #303133;
} }
p { .section {
color: #666; margin-bottom: 30px;
font-size: 16px; }
.section h2 {
font-size: 18px;
margin-bottom: 15px;
color: #606266;
border-bottom: 1px solid #ebeef5;
padding-bottom: 5px;
}
.form-item {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 5px;
color: #606266;
font-weight: 500;
}
.table-actions {
margin-top: 10px;
display: flex;
gap: 10px;
}
.form-actions {
margin-top: 40px;
display: flex;
justify-content: center;
gap: 20px;
}
/* 适配不同屏幕尺寸 */
@media (max-width: 768px) {
.pre-case-registration {
padding: 10px;
}
.table-actions {
flex-direction: column;
}
.form-actions {
flex-direction: column;
align-items: stretch;
}
} }
</style> </style>

View File

@@ -1,5 +1,5 @@
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import { type ConfigEnv, type UserConfig, loadEnv, defineConfig, PluginOption } from "vite"; import { type ConfigEnv, type UserConfig, loadEnv, defineConfig, type PluginOption } from "vite";
import AutoImport from "unplugin-auto-import/vite"; import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite"; import Components from "unplugin-vue-components/vite";
@@ -20,11 +20,12 @@ const __APP_INFO__ = {
const pathSrc = resolve(__dirname, "src"); const pathSrc = resolve(__dirname, "src");
// Vite配置 https://cn.vitejs.dev/config // Vite配置 https://cn.vitejs.dev/config
export default defineConfig(({ mode }: ConfigEnv): UserConfig => { export default defineConfig(({ mode }: ConfigEnv) => {
const env = loadEnv(mode, process.cwd()); const env = loadEnv(mode, process.cwd());
const isProduction = mode === "production"; const isProduction = mode === "production";
return { return {
base: "./", // 设置为相对路径支持直接打开index.html
resolve: { resolve: {
alias: { alias: {
"@": pathSrc, "@": pathSrc,
@@ -40,15 +41,16 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
}, },
server: { server: {
host: "0.0.0.0", host: "0.0.0.0",
port: +env.VITE_APP_PORT, port: +(env.VITE_APP_PORT as string),
open: true, open: true,
proxy: { proxy: {
// 代理 /dev-api 的请求 // 代理 /dev-api 的请求
[env.VITE_APP_BASE_API]: { [env.VITE_APP_BASE_API as string]: {
changeOrigin: true, changeOrigin: true,
// 代理目标地址https://api.youlai.tech // 代理目标地址https://api.youlai.tech
target: env.VITE_APP_API_URL, target: env.VITE_APP_API_URL as string,
rewrite: (path: string) => path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""), rewrite: (path: string) =>
path.replace(new RegExp("^" + (env.VITE_APP_BASE_API as string)), ""),
}, },
}, },
}, },
@@ -86,7 +88,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
dts: false, dts: false,
// dts: "src/types/components.d.ts", // dts: "src/types/components.d.ts",
}), }),
] as PluginOption[], ],
// 预加载项目必需的组件 // 预加载项目必需的组件
optimizeDeps: { optimizeDeps: {
include: [ include: [
@@ -192,7 +194,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
// 构建配置 // 构建配置
build: { build: {
chunkSizeWarningLimit: 2000, // 消除打包大小超过500kb警告 chunkSizeWarningLimit: 2000, // 消除打包大小超过500kb警告
minify: isProduction ? "terser" : false, // 只在生产环境启用压缩 minify: isProduction ? ("terser" as const) : false, // 只在生产环境启用压缩
terserOptions: isProduction terserOptions: isProduction
? { ? {
compress: { compress: {