Merge remote-tracking branch 'refs/remotes/origin/main' into ccd
This commit is contained in:
commit
ca1c3947c1
|
@ -24,7 +24,7 @@ export function batchAddAttachment(businessType: string, formData: FormData) {
|
||||||
* @param businessType 业务类型
|
* @param businessType 业务类型
|
||||||
* @param formData 表单数据
|
* @param formData 表单数据
|
||||||
*/
|
*/
|
||||||
export function addAttachment(formData: FormData) {
|
export function addAttachment(formData: FormData) {
|
||||||
return request<AttachInfoData>({
|
return request<AttachInfoData>({
|
||||||
url: `/attach-info/model`,
|
url: `/attach-info/model`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
|
@ -39,7 +39,7 @@ export function addAttachment(formData: FormData) {
|
||||||
* @param businessType 业务类型
|
* @param businessType 业务类型
|
||||||
* @param formData 表单数据
|
* @param formData 表单数据
|
||||||
*/
|
*/
|
||||||
export function addAttachmentByDefectMarkPic(formData: FormData) {
|
export function addAttachmentByDefectMarkPic(formData: FormData) {
|
||||||
return request<AttachInfoData>({
|
return request<AttachInfoData>({
|
||||||
url: `/attach-info/defect_mark_pic`,
|
url: `/attach-info/defect_mark_pic`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
|
@ -54,7 +54,7 @@ export function addAttachmentByDefectMarkPic(formData: FormData) {
|
||||||
* @param businessType 业务类型
|
* @param businessType 业务类型
|
||||||
* @param formData 表单数据
|
* @param formData 表单数据
|
||||||
*/
|
*/
|
||||||
export function addAttachInsurance(formData: FormData) {
|
export function addAttachInsurance(formData: FormData) {
|
||||||
return request<AttachInfoData>({
|
return request<AttachInfoData>({
|
||||||
url: `/attach-info/insurance_file`,
|
url: `/attach-info/insurance_file`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
|
|
|
@ -1,64 +1,64 @@
|
||||||
/** 用户信息响应类型 */
|
/** 用户信息响应类型 */
|
||||||
export interface UserInfoResponse {
|
export interface UserInfoResponse {
|
||||||
code: number;
|
code: number
|
||||||
status: number;
|
status: number
|
||||||
success: boolean;
|
success: boolean
|
||||||
msg: string;
|
msg: string
|
||||||
data: {
|
data: {
|
||||||
user: UserDetail;
|
user: UserDetail
|
||||||
dept: DeptDetail;
|
dept: DeptDetail
|
||||||
roles: RoleDetail[];
|
roles: RoleDetail[]
|
||||||
posts: any[];
|
posts: any[]
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 用户详细信息 */
|
/** 用户详细信息 */
|
||||||
export interface UserDetail {
|
export interface UserDetail {
|
||||||
userId: string;
|
userId: string
|
||||||
account: string;
|
account: string
|
||||||
name: string;
|
name: string
|
||||||
status: number;
|
status: number
|
||||||
userCode: string;
|
userCode: string
|
||||||
userStatus: string;
|
userStatus: string
|
||||||
userType: string;
|
userType: string
|
||||||
mobile: string;
|
mobile: string
|
||||||
createTime: string;
|
createTime: string
|
||||||
avatar?: string;
|
avatar?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 部门详细信息 */
|
/** 部门详细信息 */
|
||||||
export interface DeptDetail {
|
export interface DeptDetail {
|
||||||
deptId: string;
|
deptId: string
|
||||||
deptName: string;
|
deptName: string
|
||||||
parentId: string;
|
parentId: string
|
||||||
orderNum: number;
|
orderNum: number
|
||||||
leaderId: string;
|
leaderId: string
|
||||||
status: string;
|
status: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 角色详细信息 */
|
/** 角色详细信息 */
|
||||||
export interface RoleDetail {
|
export interface RoleDetail {
|
||||||
roleId: string;
|
roleId: string
|
||||||
roleName: string;
|
roleName: string
|
||||||
roleCode: string | null;
|
roleCode: string | null
|
||||||
roleKey: string;
|
roleKey: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 用户类型 - 兼容旧版本 */
|
/** 用户类型 - 兼容旧版本 */
|
||||||
export interface UserInfo {
|
export interface UserInfo {
|
||||||
id: string;
|
id: string
|
||||||
username: string;
|
username: string
|
||||||
nickname: string;
|
nickname: string
|
||||||
gender: 0 | 1 | 2;
|
gender: 0 | 1 | 2
|
||||||
email: string;
|
email: string
|
||||||
phone: string;
|
phone: string
|
||||||
avatar: string;
|
avatar: string
|
||||||
pwdResetTime: string;
|
pwdResetTime: string
|
||||||
pwdExpired: boolean;
|
pwdExpired: boolean
|
||||||
registrationDate: string;
|
registrationDate: string
|
||||||
deptName: string;
|
deptName: string
|
||||||
roles: string[];
|
roles: string[]
|
||||||
permissions: string[];
|
permissions: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 路由类型 */
|
/** 路由类型 */
|
||||||
|
|
|
@ -64,201 +64,234 @@ export const systemRoutes: RouteRecordRaw[] = [
|
||||||
path: '/organization',
|
path: '/organization',
|
||||||
name: 'Organization',
|
name: 'Organization',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/organization/hr/member',
|
redirect: '/organization/dept',
|
||||||
meta: { title: '组织架构', icon: 'user-group', hidden: false, sort: 2 },
|
meta: { title: '组织架构', icon: 'user-group', hidden: false, sort: 2 },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/organization/hr',
|
path: '/organization/user',
|
||||||
name: 'HRManagement',
|
name: 'OrganizationUser',
|
||||||
component: () => import('@/components/ParentView/index.vue'),
|
component: () => import('@/views/system/user/index.vue'),
|
||||||
redirect: '/organization/hr/member',
|
meta: { title: '用户管理', icon: 'user', hidden: false, sort: 2.25 },
|
||||||
meta: { title: '人员管理', icon: 'user', hidden: false },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/organization/hr/member',
|
|
||||||
name: 'HRMember',
|
|
||||||
component: () => import('@/views/system/user/index.vue'),
|
|
||||||
meta: { title: '成员', icon: 'user', hidden: false },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/dept',
|
|
||||||
name: 'HRDept',
|
|
||||||
component: () => import('@/views/system/dept/index.vue'),
|
|
||||||
meta: { title: '部门', icon: 'dept', hidden: false },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/workload',
|
|
||||||
name: 'HRWorkload',
|
|
||||||
component: () => import('@/views/hr/workload/index.vue'),
|
|
||||||
meta: { title: '任务管理', icon: 'workload', hidden: false },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/attendance',
|
|
||||||
name: 'HRAttendance',
|
|
||||||
component: () => import('@/views/hr/attendance/index.vue'),
|
|
||||||
meta: { title: '考勤', icon: 'attendance', hidden: false },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/performance',
|
|
||||||
name: 'HRPerformance',
|
|
||||||
component: () => import('@/components/ParentView/index.vue'),
|
|
||||||
meta: { title: '绩效', icon: 'performance', hidden: false },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/organization/hr/performance/dimention',
|
|
||||||
name: 'Dimention',
|
|
||||||
component: () => import('@/views/performance/setting/index.vue'),
|
|
||||||
meta: { title: '绩效维度', icon: 'performance', hidden: false },
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/performance/rule',
|
|
||||||
name: 'Rule',
|
|
||||||
component: () => import('@/views/performance/rule.vue'),
|
|
||||||
meta: { title: '绩效细则', icon: 'performance', hidden: false },
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/performance/my',
|
|
||||||
name: 'MyPerformance',
|
|
||||||
component: () => import('@/views/performance/my.vue'),
|
|
||||||
meta: { title: '我的绩效', icon: 'performance', hidden: false },
|
|
||||||
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/salary',
|
|
||||||
name: 'HRSalary',
|
|
||||||
component: () => import('@/components/ParentView/index.vue'),
|
|
||||||
redirect: '/organization/hr/salary/overview',
|
|
||||||
meta: { title: '工资', icon: 'salary', hidden: false },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/organization/hr/salary/overview',
|
|
||||||
name: 'HRSalaryOverview',
|
|
||||||
component: () => import('@/components/ParentView/index.vue'),
|
|
||||||
meta: { title: '工资概览', icon: 'salary', hidden: false },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/organization/hr/salary/payroll',
|
|
||||||
name: 'Payroll',
|
|
||||||
component: () => import('@/views/salary-management/index.vue'),
|
|
||||||
meta: { title: '工资单', icon: 'salary', hidden: false },
|
|
||||||
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// path: '/organization/hr/salary/insurance',
|
|
||||||
// name: 'HRInsurance',
|
|
||||||
// component: () => import('@/components/ParentView/index.vue'),
|
|
||||||
// redirect: '/organization/hr/salary/insurance/overview',
|
|
||||||
// meta: { title: '保险', icon: 'safety', hidden: false },
|
|
||||||
// children: [
|
|
||||||
// {
|
|
||||||
// path: '/organization/hr/salary/insurance/overview',
|
|
||||||
// name: 'HRInsuranceOverview',
|
|
||||||
// component: () => import('@/views/hr/salary/insurance/overview/index.vue'),
|
|
||||||
// meta: { title: '工作台概览', icon: 'dashboard', hidden: false },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// path: '/organization/hr/salary/insurance/my-insurance',
|
|
||||||
// name: 'HRMyInsurance',
|
|
||||||
// component: () => import('@/views/hr/salary/insurance/my-insurance/index.vue'),
|
|
||||||
// meta: { title: '我的保险', icon: 'shield', hidden: false },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// path: '/organization/hr/salary/insurance/health-records',
|
|
||||||
// name: 'HRHealthRecords',
|
|
||||||
// component: () => import('@/views/hr/salary/insurance/health-records/index.vue'),
|
|
||||||
// meta: { title: '健康档案', icon: 'heart', hidden: false },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// path: '/organization/hr/salary/insurance/policy-files',
|
|
||||||
// name: 'HRPolicyFiles',
|
|
||||||
// component: () => import('@/views/hr/salary/insurance/policy-files/index.vue'),
|
|
||||||
// meta: { title: '保单文件', icon: 'file', hidden: false },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// path: '/organization/hr/salary/insurance/personal-info',
|
|
||||||
// name: 'HRPersonalInfo',
|
|
||||||
// component: () => import('@/views/hr/salary/insurance/personal-info/index.vue'),
|
|
||||||
// meta: { title: '个人信息', icon: 'user', hidden: false },
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
{
|
|
||||||
path: '/organization/hr/salary/system-insurance/health-management',
|
|
||||||
name: 'HRSystemHealthManagement',
|
|
||||||
component: () => import('@/views/hr/salary/system-insurance/health-management/index.vue'),
|
|
||||||
meta: { title: '健康档案管理', icon: 'heart', hidden: false },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/salary/system-insurance',
|
|
||||||
name: 'HRSystemInsurance',
|
|
||||||
component: () => import('@/components/ParentView/index.vue'),
|
|
||||||
redirect: '/organization/hr/salary/system-insurance/overview',
|
|
||||||
meta: { title: '人员保险', icon: 'settings', hidden: false },
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/organization/hr/salary/system-insurance/overview',
|
|
||||||
name: 'HRSystemInsuranceOverview',
|
|
||||||
component: () => import('@/views/hr/salary/system-insurance/overview/index.vue'),
|
|
||||||
meta: { title: '工作台概览', icon: 'dashboard', hidden: false },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/salary/system-insurance/management',
|
|
||||||
name: 'HRSystemInsuranceManagement',
|
|
||||||
component: () => import('@/views/hr/salary/system-insurance/management/index.vue'),
|
|
||||||
meta: { title: '保险管理', icon: 'shield', hidden: false },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/salary/system-insurance/file-management',
|
|
||||||
name: 'HRSystemFileManagement',
|
|
||||||
component: () => import('@/views/hr/salary/system-insurance/file-management/index.vue'),
|
|
||||||
meta: { title: '保单文件管理', icon: 'file', hidden: false },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/salary/system-insurance/company-management',
|
|
||||||
name: 'HRSystemCompanyManagement',
|
|
||||||
component: () => import('@/views/hr/salary/system-insurance/company-management/index.vue'),
|
|
||||||
meta: { title: '保险公司管理', icon: 'building', hidden: false },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/salary/system-insurance/type-management',
|
|
||||||
name: 'HRSystemTypeManagement',
|
|
||||||
component: () => import('@/views/hr/salary/system-insurance/type-management/index.vue'),
|
|
||||||
meta: { title: '保险类型管理', icon: 'category', hidden: false },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/salary/certification',
|
|
||||||
name: 'HRCertification',
|
|
||||||
component: () => import('@/views/hr/salary/certification/index.vue'),
|
|
||||||
meta: { title: '人员资质管理', icon: 'idcard', hidden: false },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/organization/hr/contribution',
|
|
||||||
name: 'HRContribution',
|
|
||||||
component: () => import('@/views/hr/contribution/index.vue'),
|
|
||||||
meta: { title: '责献积分制度', icon: 'contribution', hidden: false },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/organization/role',
|
path: '/organization/dept',
|
||||||
name: 'OrganizationRole',
|
name: 'OrganizationDept',
|
||||||
component: () => import('@/views/system/role/index.vue'),
|
component: () => import('@/views/system/dept/index.vue'),
|
||||||
meta: { title: '角色管理', icon: 'role', hidden: false },
|
meta: { title: '部门管理', icon: 'mind-mapping', hidden: false, sort: 2.5 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/organization/post',
|
||||||
|
name: 'OrganizationPost',
|
||||||
|
component: () => import('@/views/system/post/index.vue'),
|
||||||
|
meta: { title: '岗位管理', icon: 'nav', hidden: false, sort: 2.75 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/organization/hr/workload',
|
||||||
|
name: 'HRWorkload',
|
||||||
|
component: () => import('@/views/hr/workload/index.vue'),
|
||||||
|
meta: { title: '任务管理', icon: 'bookmark', hidden: false },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// path: '/organization',
|
||||||
|
// name: 'Organization',
|
||||||
|
// component: Layout,
|
||||||
|
// redirect: '/organization/hr/member',
|
||||||
|
// meta: { title: '组织架构', icon: 'user-group', hidden: false, sort: 2 },
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr',
|
||||||
|
// name: 'HRManagement',
|
||||||
|
// component: () => import('@/components/ParentView/index.vue'),
|
||||||
|
// redirect: '/organization/hr/member',
|
||||||
|
// meta: { title: '人员管理', icon: 'user', hidden: false },
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/member',
|
||||||
|
// name: 'HRMember',
|
||||||
|
// component: () => import('@/views/system/user/index.vue'),
|
||||||
|
// meta: { title: '成员', icon: 'user', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/dept',
|
||||||
|
// name: 'HRDept',
|
||||||
|
// component: () => import('@/views/system/dept/index.vue'),
|
||||||
|
// meta: { title: '部门', icon: 'dept', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/workload',
|
||||||
|
// name: 'HRWorkload',
|
||||||
|
// component: () => import('@/views/hr/workload/index.vue'),
|
||||||
|
// meta: { title: '任务管理', icon: 'workload', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/attendance',
|
||||||
|
// name: 'HRAttendance',
|
||||||
|
// component: () => import('@/views/hr/attendance/index.vue'),
|
||||||
|
// meta: { title: '考勤', icon: 'attendance', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/performance',
|
||||||
|
// name: 'HRPerformance',
|
||||||
|
// component: () => import('@/components/ParentView/index.vue'),
|
||||||
|
// meta: { title: '绩效', icon: 'performance', hidden: false },
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/performance/dimention',
|
||||||
|
// name: 'Dimention',
|
||||||
|
// component: () => import('@/views/performance/setting/index.vue'),
|
||||||
|
// meta: { title: '绩效维度', icon: 'performance', hidden: false },
|
||||||
|
//
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/performance/rule',
|
||||||
|
// name: 'Rule',
|
||||||
|
// component: () => import('@/views/performance/rule.vue'),
|
||||||
|
// meta: { title: '绩效细则', icon: 'performance', hidden: false },
|
||||||
|
//
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/performance/my',
|
||||||
|
// name: 'MyPerformance',
|
||||||
|
// component: () => import('@/views/performance/my.vue'),
|
||||||
|
// meta: { title: '我的绩效', icon: 'performance', hidden: false },
|
||||||
|
//
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary',
|
||||||
|
// name: 'HRSalary',
|
||||||
|
// component: () => import('@/components/ParentView/index.vue'),
|
||||||
|
// redirect: '/organization/hr/salary/overview',
|
||||||
|
// meta: { title: '工资', icon: 'salary', hidden: false },
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/overview',
|
||||||
|
// name: 'HRSalaryOverview',
|
||||||
|
// component: () => import('@/components/ParentView/index.vue'),
|
||||||
|
// meta: { title: '工资概览', icon: 'salary', hidden: false },
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/payroll',
|
||||||
|
// name: 'Payroll',
|
||||||
|
// component: () => import('@/views/salary-management/index.vue'),
|
||||||
|
// meta: { title: '工资单', icon: 'salary', hidden: false },
|
||||||
|
//
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/insurance',
|
||||||
|
// name: 'HRInsurance',
|
||||||
|
// component: () => import('@/components/ParentView/index.vue'),
|
||||||
|
// redirect: '/organization/hr/salary/insurance/overview',
|
||||||
|
// meta: { title: '保险', icon: 'safety', hidden: false },
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/insurance/overview',
|
||||||
|
// name: 'HRInsuranceOverview',
|
||||||
|
// component: () => import('@/views/hr/salary/insurance/overview/index.vue'),
|
||||||
|
// meta: { title: '工作台概览', icon: 'dashboard', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/insurance/my-insurance',
|
||||||
|
// name: 'HRMyInsurance',
|
||||||
|
// component: () => import('@/views/hr/salary/insurance/my-insurance/index.vue'),
|
||||||
|
// meta: { title: '我的保险', icon: 'shield', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/insurance/health-records',
|
||||||
|
// name: 'HRHealthRecords',
|
||||||
|
// component: () => import('@/views/hr/salary/insurance/health-records/index.vue'),
|
||||||
|
// meta: { title: '健康档案', icon: 'heart', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/insurance/policy-files',
|
||||||
|
// name: 'HRPolicyFiles',
|
||||||
|
// component: () => import('@/views/hr/salary/insurance/policy-files/index.vue'),
|
||||||
|
// meta: { title: '保单文件', icon: 'file', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/insurance/personal-info',
|
||||||
|
// name: 'HRPersonalInfo',
|
||||||
|
// component: () => import('@/views/hr/salary/insurance/personal-info/index.vue'),
|
||||||
|
// meta: { title: '个人信息', icon: 'user', hidden: false },
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/system-insurance/health-management',
|
||||||
|
// name: 'HRSystemHealthManagement',
|
||||||
|
// component: () => import('@/views/hr/salary/system-insurance/health-management/index.vue'),
|
||||||
|
// meta: { title: '健康档案管理', icon: 'heart', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/system-insurance',
|
||||||
|
// name: 'HRSystemInsurance',
|
||||||
|
// component: () => import('@/components/ParentView/index.vue'),
|
||||||
|
// redirect: '/organization/hr/salary/system-insurance/overview',
|
||||||
|
// meta: { title: '人员保险', icon: 'settings', hidden: false },
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/system-insurance/overview',
|
||||||
|
// name: 'HRSystemInsuranceOverview',
|
||||||
|
// component: () => import('@/views/hr/salary/system-insurance/overview/index.vue'),
|
||||||
|
// meta: { title: '工作台概览', icon: 'dashboard', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/system-insurance/management',
|
||||||
|
// name: 'HRSystemInsuranceManagement',
|
||||||
|
// component: () => import('@/views/hr/salary/system-insurance/management/index.vue'),
|
||||||
|
// meta: { title: '保险管理', icon: 'shield', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/system-insurance/file-management',
|
||||||
|
// name: 'HRSystemFileManagement',
|
||||||
|
// component: () => import('@/views/hr/salary/system-insurance/file-management/index.vue'),
|
||||||
|
// meta: { title: '保单文件管理', icon: 'file', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/system-insurance/company-management',
|
||||||
|
// name: 'HRSystemCompanyManagement',
|
||||||
|
// component: () => import('@/views/hr/salary/system-insurance/company-management/index.vue'),
|
||||||
|
// meta: { title: '保险公司管理', icon: 'building', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/system-insurance/type-management',
|
||||||
|
// name: 'HRSystemTypeManagement',
|
||||||
|
// component: () => import('@/views/hr/salary/system-insurance/type-management/index.vue'),
|
||||||
|
// meta: { title: '保险类型管理', icon: 'category', hidden: false },
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/salary/certification',
|
||||||
|
// name: 'HRCertification',
|
||||||
|
// component: () => import('@/views/hr/salary/certification/index.vue'),
|
||||||
|
// meta: { title: '人员资质管理', icon: 'idcard', hidden: false },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/hr/contribution',
|
||||||
|
// name: 'HRContribution',
|
||||||
|
// component: () => import('@/views/hr/contribution/index.vue'),
|
||||||
|
// meta: { title: '责献积分制度', icon: 'contribution', hidden: false },
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/organization/role',
|
||||||
|
// name: 'OrganizationRole',
|
||||||
|
// component: () => import('@/views/system/role/index.vue'),
|
||||||
|
// meta: { title: '角色管理', icon: 'role', hidden: false },
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
path: '/asset-management',
|
path: '/asset-management',
|
||||||
name: 'AssetManagement',
|
name: 'AssetManagement',
|
||||||
|
@ -525,9 +558,9 @@ export const systemRoutes: RouteRecordRaw[] = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'project-management/project-template/information-retrieval',
|
path: '/project-management/project-template/information-retrieval',
|
||||||
name: 'InformationRetrieval',
|
name: 'InformationRetrieval',
|
||||||
component: () => import ('@/views/default/error/404.vue'),
|
component: () => import ('@/views/project-management/bidding/information-retrieval/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '信息检索(N)',
|
title: '信息检索(N)',
|
||||||
icon: 'trophy',
|
icon: 'trophy',
|
||||||
|
@ -776,11 +809,11 @@ export const systemRoutes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/attachment',
|
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/attachment',
|
||||||
name: 'AttachmentManagement',
|
name: 'AttachmentManagement',
|
||||||
component: () => import('@/views/operation-platform/data-processing/data-storage/index.vue'),
|
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/data-storage/index.vue'),
|
||||||
meta: { title: '附件管理', icon: 'attachment', hidden: false },
|
meta: { title: '附件管理', icon: 'attachment', hidden: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/model-config',
|
path: '/construction-operation-platform/implementation-workflow/data-processing/model-config',
|
||||||
name: 'ModelConfig',
|
name: 'ModelConfig',
|
||||||
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/model-config/index.vue'),
|
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/model-config/index.vue'),
|
||||||
meta: { title: '模型配置', icon: 'robot', hidden: false },
|
meta: { title: '模型配置', icon: 'robot', hidden: false },
|
||||||
|
@ -791,8 +824,23 @@ export const systemRoutes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/preprocessed-data',
|
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/preprocessed-data',
|
||||||
name: 'PreprocessedData',
|
name: 'PreprocessedData',
|
||||||
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/data-preprocessing/index.vue'),
|
component: () => import('@/components/ParentView/index.vue'),
|
||||||
|
redirect: '/construction-operation-platform/implementation-workflow/data-processing/data-storage',
|
||||||
meta: { title: '数据预处理', icon: 'filter', hidden: false },
|
meta: { title: '数据预处理', icon: 'filter', hidden: false },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/preprocessed-data/ImageBatchUpload',
|
||||||
|
name: 'ImageBatchUpload',
|
||||||
|
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/data-preprocessing/index.vue'),
|
||||||
|
meta: { title: '批量上传', icon: 'file', hidden: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/construction-operation-platform/implementation-workflow/data-processing/data-storage/preprocessed-data/ImageSorting',
|
||||||
|
name: 'ImageSorting',
|
||||||
|
component: () => import('@/views/construction-operation-platform/implementation-workflow/data-processing/image-sorting/index.vue'),
|
||||||
|
meta: { title: '图像分拣', icon: 'attachment', hidden: false },
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/construction-operation-platform/implementation-workflow/data-processing/intelligent-inspection',
|
path: '/construction-operation-platform/implementation-workflow/data-processing/intelligent-inspection',
|
||||||
|
@ -971,14 +1019,26 @@ export const systemRoutes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/user/profile',
|
path: '/user/profile',
|
||||||
name: 'UserProfile',
|
name: 'UserProfile',
|
||||||
component: () => import('@/views/user/profile/index.vue'),
|
component: Layout,
|
||||||
|
redirect: '/user/profile',
|
||||||
meta: {
|
meta: {
|
||||||
title: '个人中心',
|
title: '个人中心',
|
||||||
icon: 'user',
|
icon: 'user',
|
||||||
hidden: false,
|
hidden: false,
|
||||||
sort: 100,
|
sort: 100,
|
||||||
},
|
},
|
||||||
children: [],
|
children: [
|
||||||
|
{
|
||||||
|
path: '/user/profile',
|
||||||
|
name: 'UserProfile',
|
||||||
|
component: () => import('@/views/user/profile/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '个人中心',
|
||||||
|
icon: 'user',
|
||||||
|
hidden: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/enterprise-settings',
|
path: '/enterprise-settings',
|
||||||
|
|
|
@ -4,10 +4,9 @@ import type { RouteRecordRaw } from 'vue-router'
|
||||||
import { mapTree, toTreeArray } from 'xe-utils'
|
import { mapTree, toTreeArray } from 'xe-utils'
|
||||||
import { cloneDeep, omit } from 'lodash-es'
|
import { cloneDeep, omit } from 'lodash-es'
|
||||||
import { constantRoutes, systemRoutes } from '@/router/route'
|
import { constantRoutes, systemRoutes } from '@/router/route'
|
||||||
import { type RouteItem, getUserRouteWithAdapter } from '@/apis'
|
import type { RouteItem } from '@/apis'
|
||||||
import { transformPathToName } from '@/utils'
|
import { transformPathToName } from '@/utils'
|
||||||
import { asyncRouteModules } from '@/router/asyncModules'
|
import { asyncRouteModules } from '@/router/asyncModules'
|
||||||
import { convertMenuData, type ApiMenuItem } from '@/utils/menuConverter'
|
|
||||||
|
|
||||||
const layoutComponentMap = {
|
const layoutComponentMap = {
|
||||||
Layout: () => import('@/layout/index.vue'),
|
Layout: () => import('@/layout/index.vue'),
|
||||||
|
@ -94,91 +93,63 @@ const storeSetup = () => {
|
||||||
// 获取路由数据并已通过适配器转换
|
// 获取路由数据并已通过适配器转换
|
||||||
// const { data } = await getUserRouteWithAdapter()
|
// const { data } = await getUserRouteWithAdapter()
|
||||||
const data = [{
|
const data = [{
|
||||||
"id": 1000,
|
id: 1000,
|
||||||
"parentId": 0,
|
parentId: 0,
|
||||||
"title": "系统管理",
|
title: '系统管理',
|
||||||
"type": 1,
|
type: 1,
|
||||||
"path": "/system",
|
path: '/system',
|
||||||
"name": "System",
|
name: 'System',
|
||||||
"component": "Layout",
|
component: 'Layout',
|
||||||
"redirect": "/system/user",
|
redirect: '/system/user',
|
||||||
"icon": "settings",
|
icon: 'settings',
|
||||||
"isExternal": false,
|
isExternal: false,
|
||||||
"isCache": false,
|
isCache: false,
|
||||||
"isHidden": false,
|
isHidden: false,
|
||||||
"sort": 1,
|
sort: 1,
|
||||||
"children": [
|
children: [
|
||||||
{
|
{
|
||||||
"id": 1010,
|
id: 1010,
|
||||||
"parentId": 1000,
|
parentId: 1000,
|
||||||
"title": "用户管理",
|
title: '用户管理',
|
||||||
"type": 2,
|
type: 2,
|
||||||
"path": "/system/user",
|
path: '/system/user',
|
||||||
"name": "SystemUser",
|
name: 'SystemUser',
|
||||||
"component": "system/user/index",
|
component: 'system/user/index',
|
||||||
"icon": "user",
|
icon: 'user',
|
||||||
"isExternal": false,
|
isExternal: false,
|
||||||
"isCache": false,
|
isCache: false,
|
||||||
"isHidden": false,
|
isHidden: false,
|
||||||
"sort": 1
|
sort: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 1030,
|
id: 1030,
|
||||||
"parentId": 1000,
|
parentId: 1000,
|
||||||
"title": "角色管理",
|
title: '角色管理',
|
||||||
"type": 2,
|
type: 2,
|
||||||
"path": "/system/role",
|
path: '/system/role',
|
||||||
"name": "SystemRole",
|
name: 'SystemRole',
|
||||||
"component": "system/role/index",
|
component: 'system/role/index',
|
||||||
"icon": "user-group",
|
icon: 'user-group',
|
||||||
"isExternal": false,
|
isExternal: false,
|
||||||
"isCache": false,
|
isCache: false,
|
||||||
"isHidden": false,
|
isHidden: false,
|
||||||
"sort": 2
|
sort: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 1050,
|
id: 1050,
|
||||||
"parentId": 1000,
|
parentId: 1000,
|
||||||
"title": "菜单管理",
|
title: '菜单管理',
|
||||||
"type": 2,
|
type: 2,
|
||||||
"path": "/system/menu",
|
path: '/system/menu',
|
||||||
"name": "SystemMenu",
|
name: 'SystemMenu',
|
||||||
"component": "system/menu/index",
|
component: 'system/menu/index',
|
||||||
"icon": "menu",
|
icon: 'menu',
|
||||||
"isExternal": false,
|
isExternal: false,
|
||||||
"isCache": false,
|
isCache: false,
|
||||||
"isHidden": false,
|
isHidden: false,
|
||||||
"sort": 3
|
sort: 3,
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
"id": 1070,
|
|
||||||
"parentId": 1000,
|
|
||||||
"title": "部门管理",
|
|
||||||
"type": 2,
|
|
||||||
"path": "/system/dept",
|
|
||||||
"name": "SystemDept",
|
|
||||||
"component": "system/dept/index",
|
|
||||||
"icon": "mind-mapping",
|
|
||||||
"isExternal": false,
|
|
||||||
"isCache": false,
|
|
||||||
"isHidden": false,
|
|
||||||
"sort": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 1090,
|
|
||||||
"parentId": 1000,
|
|
||||||
"title": "岗位管理",
|
|
||||||
"type": 2,
|
|
||||||
"path": "/system/post",
|
|
||||||
"name": "SystemPost",
|
|
||||||
"component": "system/post/index",
|
|
||||||
"icon": "settings",
|
|
||||||
"isExternal": false,
|
|
||||||
"isCache": false,
|
|
||||||
"isHidden": false,
|
|
||||||
"sort": 5
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}]
|
}]
|
||||||
// 使用已转换的数据生成路由
|
// 使用已转换的数据生成路由
|
||||||
const asyncRoutes = formatAsyncRoutes(data as unknown as RouteItem[])
|
const asyncRoutes = formatAsyncRoutes(data as unknown as RouteItem[])
|
||||||
|
|
|
@ -70,6 +70,6 @@ declare global {
|
||||||
// for type re-export
|
// for type re-export
|
||||||
declare global {
|
declare global {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||||
import('vue')
|
import('vue')
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -105,8 +105,16 @@ const formData = reactive({
|
||||||
const fetchBusinessTypes = async () => {
|
const fetchBusinessTypes = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getAttachBusinessTypes()
|
const res = await getAttachBusinessTypes()
|
||||||
|
console.log("res:",res);
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
businessTypes.value = res.data
|
res.data.forEach(item => {
|
||||||
|
const key = Object.keys(item)[0];
|
||||||
|
const value = item[key];
|
||||||
|
businessTypes.value.push({
|
||||||
|
name: value,
|
||||||
|
code:key
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取业务类型失败:', error)
|
console.error('获取业务类型失败:', error)
|
||||||
|
|
|
@ -0,0 +1,603 @@
|
||||||
|
<template>
|
||||||
|
<GiPageLayout>
|
||||||
|
<div class="data-preprocessing-container">
|
||||||
|
<!-- 上传进度框 -->
|
||||||
|
<div v-if="uploadState.visible" class="upload-progress-fixed">
|
||||||
|
<a-card :title="`上传进度 (${uploadState.percent}%)`" :bordered="false">
|
||||||
|
<a-progress
|
||||||
|
:percent="uploadState.percent"
|
||||||
|
:status="uploadState.status"
|
||||||
|
:stroke-width="16"
|
||||||
|
/>
|
||||||
|
<div class="progress-details">
|
||||||
|
<p><icon-file /> {{ uploadState.currentFile || '准备中...' }}</p>
|
||||||
|
<p><icon-check-circle /> 已完成 {{ uploadState.uploadedCount }}/{{ uploadState.totalCount }}</p>
|
||||||
|
<p><icon-clock-circle /> 状态: {{ getStatusText(uploadState.status) }}</p>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step-panel">
|
||||||
|
<div class="step-header">
|
||||||
|
<h3>批量上传图片</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="data-selection">
|
||||||
|
<a-form :model="form" layout="vertical">
|
||||||
|
<!-- 项目选择 -->
|
||||||
|
<a-form-item label="所属项目" required>
|
||||||
|
<a-select
|
||||||
|
v-model="form.projectId"
|
||||||
|
placeholder="请选择项目"
|
||||||
|
allow-search
|
||||||
|
:filter-option="filterProjectOption"
|
||||||
|
>
|
||||||
|
<a-option
|
||||||
|
v-for="project in projectList"
|
||||||
|
:key="project.id"
|
||||||
|
:value="project.id"
|
||||||
|
:label="project.name"
|
||||||
|
/>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 图片来源选择 -->
|
||||||
|
<a-form-item label="图片来源" required>
|
||||||
|
<a-select
|
||||||
|
v-model="form.imageSource"
|
||||||
|
placeholder="请选择图片来源"
|
||||||
|
allow-search
|
||||||
|
:filter-option="filterSourceOption"
|
||||||
|
>
|
||||||
|
<a-option
|
||||||
|
v-for="source in imageSources"
|
||||||
|
:key="source.value"
|
||||||
|
:value="source.value"
|
||||||
|
:label="source.label"
|
||||||
|
/>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 文件夹操作 -->
|
||||||
|
<a-form-item label="文件操作">
|
||||||
|
<div class="folder-actions">
|
||||||
|
<a-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
directory
|
||||||
|
:multiple="true"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept="image/*"
|
||||||
|
:key="uploadKey"
|
||||||
|
@change="handleFolderSelect"
|
||||||
|
>
|
||||||
|
<template #upload-button>
|
||||||
|
<a-button type="outline">
|
||||||
|
<template #icon>
|
||||||
|
<icon-folder />
|
||||||
|
</template>
|
||||||
|
选择文件夹
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</a-upload>
|
||||||
|
<a-button
|
||||||
|
type="outline"
|
||||||
|
status="warning"
|
||||||
|
@click="clearFileList"
|
||||||
|
:disabled="selectedFiles.length === 0"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<icon-delete />
|
||||||
|
</template>
|
||||||
|
清空列表
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:loading="uploading"
|
||||||
|
:disabled="!canUpload"
|
||||||
|
@click="handleUpload"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<icon-upload />
|
||||||
|
</template>
|
||||||
|
开始上传
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 文件列表 -->
|
||||||
|
<a-form-item v-if="selectedFiles.length > 0">
|
||||||
|
<div class="file-list-container">
|
||||||
|
<div class="file-list-header">
|
||||||
|
<span>已选择 {{ selectedFiles.length }} 个文件(选中 {{ checkedFiles.length }} 个)</span>
|
||||||
|
<a-checkbox
|
||||||
|
v-model="selectAll"
|
||||||
|
:indeterminate="indeterminate"
|
||||||
|
@change="handleSelectAllChange"
|
||||||
|
>
|
||||||
|
全选
|
||||||
|
</a-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="file-table-wrapper">
|
||||||
|
<a-table
|
||||||
|
:data="selectedFiles"
|
||||||
|
:columns="fileColumns"
|
||||||
|
:row-selection="rowSelection"
|
||||||
|
:pagination="false"
|
||||||
|
row-key="uid"
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<!-- 表格列模板 -->
|
||||||
|
<template #thumbnail="{ record }">
|
||||||
|
<div class="thumbnail-cell">
|
||||||
|
<img
|
||||||
|
v-if="isImage(record.type)"
|
||||||
|
:src="record.preview"
|
||||||
|
class="thumbnail-image"
|
||||||
|
alt="预览"
|
||||||
|
/>
|
||||||
|
<div v-else class="file-icon">
|
||||||
|
<icon-file />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #fileType="{ record }">
|
||||||
|
<a-tag :color="getFileTypeColor(record.type)" size="small">
|
||||||
|
{{ record.type }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<template #fileSize="{ record }">
|
||||||
|
{{ formatFileSize(record.size) }}
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</GiPageLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, nextTick } from 'vue'
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
import type { UploadItem } from '@arco-design/web-vue/es/upload'
|
||||||
|
import type { SelectOptionData, TableColumnData, TableRowSelection } from '@arco-design/web-vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
import {
|
||||||
|
getProjectList,
|
||||||
|
getImageSources
|
||||||
|
} from '@/apis/industrial-image'
|
||||||
|
|
||||||
|
// 类型定义
|
||||||
|
interface FileItem {
|
||||||
|
uid: string
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
size: number
|
||||||
|
file: File
|
||||||
|
preview?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadStatus = 'waiting' | 'uploading' | 'success' | 'error'
|
||||||
|
|
||||||
|
// 数据状态
|
||||||
|
const projectList = ref([])
|
||||||
|
|
||||||
|
const imageSources = ref([])
|
||||||
|
|
||||||
|
// 获取项目列表
|
||||||
|
const fetchProjectList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getProjectList({ page: 1, pageSize: 1000 });
|
||||||
|
projectList.value = res.data.map(item => ({
|
||||||
|
name: item.projectName,
|
||||||
|
id: item.projectId
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
Message.error('获取项目列表失败')
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取图片来源
|
||||||
|
const fetchImageSourceList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getImageSources();
|
||||||
|
res.data.forEach(item => {
|
||||||
|
const key = Object.keys(item)[0];
|
||||||
|
const value = item[key];
|
||||||
|
imageSources.value.push({
|
||||||
|
label: value,
|
||||||
|
value:key
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
Message.error('获取项目列表失败')
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
projectId: undefined as number | undefined,
|
||||||
|
imageSource: undefined as string | undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedFiles = ref<FileItem[]>([])
|
||||||
|
const checkedFiles = ref<string[]>([])
|
||||||
|
const uploading = ref(false)
|
||||||
|
const uploadRef = ref()
|
||||||
|
const uploadKey = ref(0)
|
||||||
|
|
||||||
|
const uploadState = reactive({
|
||||||
|
visible: false,
|
||||||
|
percent: 0,
|
||||||
|
status: 'waiting' as UploadStatus,
|
||||||
|
currentFile: '',
|
||||||
|
uploadedCount: 0,
|
||||||
|
totalCount: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const canUpload = computed(() => {
|
||||||
|
return checkedFiles.value.length > 0
|
||||||
|
&& !!form.projectId
|
||||||
|
&& !!form.imageSource
|
||||||
|
&& !uploading.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectAll = ref(false)
|
||||||
|
const indeterminate = computed(() => {
|
||||||
|
return checkedFiles.value.length > 0 &&
|
||||||
|
checkedFiles.value.length < selectedFiles.value.length
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
const fileColumns: TableColumnData[] = [
|
||||||
|
{
|
||||||
|
title: '选择',
|
||||||
|
dataIndex: 'selection',
|
||||||
|
type: 'selection',
|
||||||
|
width: 60,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '预览',
|
||||||
|
dataIndex: 'thumbnail',
|
||||||
|
slotName: 'thumbnail',
|
||||||
|
width: 100,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '文件名',
|
||||||
|
dataIndex: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
tooltip: true,
|
||||||
|
width: 300
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
slotName: 'fileType',
|
||||||
|
width: 100,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '大小',
|
||||||
|
dataIndex: 'size',
|
||||||
|
slotName: 'fileSize',
|
||||||
|
width: 100,
|
||||||
|
align: 'center'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 行选择配置
|
||||||
|
const rowSelection = reactive<TableRowSelection>({
|
||||||
|
type: 'checkbox',
|
||||||
|
showCheckedAll: false,
|
||||||
|
selectedRowKeys: checkedFiles,
|
||||||
|
onChange: (rowKeys: string[]) => {
|
||||||
|
checkedFiles.value = rowKeys
|
||||||
|
selectAll.value = rowKeys.length === selectedFiles.value.length
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 搜索过滤函数
|
||||||
|
const filterProjectOption = (inputValue: string, option: SelectOptionData) => {
|
||||||
|
return option.label.toLowerCase().includes(inputValue.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterSourceOption = (inputValue: string, option: SelectOptionData) => {
|
||||||
|
return option.label.toLowerCase().includes(inputValue.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件选择处理
|
||||||
|
const handleFolderSelect = async (fileList: UploadItem[]) => {
|
||||||
|
// 1. 清空现有状态
|
||||||
|
clearFileList()
|
||||||
|
|
||||||
|
// 2. 处理新文件
|
||||||
|
const newFiles: FileItem[] = []
|
||||||
|
for (const item of fileList) {
|
||||||
|
const file = item.file
|
||||||
|
if (!file) continue
|
||||||
|
|
||||||
|
const fileType = file.type.split('/')[0] || 'unknown'
|
||||||
|
const preview = fileType === 'image' ? URL.createObjectURL(file) : undefined
|
||||||
|
|
||||||
|
newFiles.push({
|
||||||
|
uid: item.uid,
|
||||||
|
name: file.name,
|
||||||
|
type: fileType,
|
||||||
|
size: file.size,
|
||||||
|
file: file,
|
||||||
|
preview: preview
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 更新状态
|
||||||
|
selectedFiles.value = newFiles
|
||||||
|
checkedFiles.value = newFiles.map(f => f.uid)
|
||||||
|
selectAll.value = true
|
||||||
|
|
||||||
|
// 4. 重置上传组件(通过改变key强制重新创建组件)
|
||||||
|
uploadKey.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空文件列表
|
||||||
|
const clearFileList = () => {
|
||||||
|
// 释放预览URL
|
||||||
|
selectedFiles.value.forEach(file => {
|
||||||
|
if (file.preview) URL.revokeObjectURL(file.preview)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
selectedFiles.value = []
|
||||||
|
checkedFiles.value = []
|
||||||
|
selectAll.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理全选变化
|
||||||
|
const handleSelectAllChange = (checked: boolean) => {
|
||||||
|
checkedFiles.value = checked ? selectedFiles.value.map(f => f.uid) : []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传处理
|
||||||
|
const handleUpload = async () => {
|
||||||
|
if (!canUpload.value) {
|
||||||
|
Message.error('请完成所有必填项并选择文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置上传状态
|
||||||
|
Object.assign(uploadState, {
|
||||||
|
visible: true,
|
||||||
|
percent: 0,
|
||||||
|
status: 'uploading',
|
||||||
|
currentFile: '',
|
||||||
|
uploadedCount: 0,
|
||||||
|
totalCount: checkedFiles.value.length
|
||||||
|
})
|
||||||
|
|
||||||
|
uploading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filesToUpload = selectedFiles.value.filter(f => checkedFiles.value.includes(f.uid))
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
// 添加所有图片文件到FormData
|
||||||
|
filesToUpload.forEach(file => {
|
||||||
|
formData.append('files', file);
|
||||||
|
});
|
||||||
|
|
||||||
|
let url =`http://pms.dtyx.net:9158/image/${form.projectId}/${form.imageSource}/upload-batch`;
|
||||||
|
let res = await axios.post(
|
||||||
|
url,
|
||||||
|
formData,
|
||||||
|
{
|
||||||
|
onUploadProgress: (progressEvent) => {
|
||||||
|
if (progressEvent.total) {
|
||||||
|
uploadedBytes = progressEvent.loaded
|
||||||
|
const elapsedTime = (Date.now() - startTime) / 1000
|
||||||
|
const speed = uploadedBytes / elapsedTime
|
||||||
|
uploadState.speed = `${formatFileSize(speed)}/s`
|
||||||
|
|
||||||
|
const remainingBytes = totalBytes - uploadedBytes
|
||||||
|
uploadState.remainingTime = formatTime(remainingBytes / speed)
|
||||||
|
|
||||||
|
uploadState.percent = Math.round((uploadedBytes / totalBytes) * 100)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
console.log("res:",res);
|
||||||
|
uploadState.status = 'success'
|
||||||
|
Message.success(`成功上传 ${filesToUpload.length} 个文件`)
|
||||||
|
} catch (error) {
|
||||||
|
uploadState.status = 'error'
|
||||||
|
Message.error('上传失败: ' + (error as Error).message)
|
||||||
|
} finally {
|
||||||
|
uploading.value = false
|
||||||
|
// 5秒后自动隐藏进度框
|
||||||
|
setTimeout(() => uploadState.visible = false, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数
|
||||||
|
const getStatusText = (status: UploadStatus) => {
|
||||||
|
const statusMap = {
|
||||||
|
waiting: '等待上传',
|
||||||
|
uploading: '上传中',
|
||||||
|
success: '上传成功',
|
||||||
|
error: '上传失败'
|
||||||
|
}
|
||||||
|
return statusMap[status] || status
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFileTypeColor = (type: string) => {
|
||||||
|
const colors: Record<string, string> = {
|
||||||
|
image: 'arcoblue',
|
||||||
|
video: 'green',
|
||||||
|
audio: 'orange',
|
||||||
|
document: 'purple'
|
||||||
|
}
|
||||||
|
return colors[type.toLowerCase()] || 'gray'
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatFileSize = (bytes: number) => {
|
||||||
|
if (bytes === 0) return '0 B'
|
||||||
|
const k = 1024
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
const isImage = (type: string) => type === 'image'
|
||||||
|
|
||||||
|
// 在组件挂载时获取项目列表
|
||||||
|
onMounted(() => {
|
||||||
|
fetchProjectList()
|
||||||
|
fetchImageSourceList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.data-preprocessing-container {
|
||||||
|
position: relative;
|
||||||
|
padding: 20px;
|
||||||
|
min-height: calc(100vh - 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-panel {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
padding-bottom: 16px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-container {
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-table-wrapper {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
:deep(.arco-table-th) {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
background-color: var(--color-fill-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-cell {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 60px;
|
||||||
|
|
||||||
|
.thumbnail-image {
|
||||||
|
max-height: 60px;
|
||||||
|
max-width: 80px;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 上传进度框样式 */
|
||||||
|
.upload-progress-fixed {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
width: 360px;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 8px;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
|
||||||
|
:deep(.arco-card-header) {
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-progress-text) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-details {
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
|
||||||
|
p {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 6px 0;
|
||||||
|
|
||||||
|
.arco-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -106,12 +106,14 @@ const getAudioUrl = (filePath: string): string => {
|
||||||
const openPreview = (item: PreviewItem) => {
|
const openPreview = (item: PreviewItem) => {
|
||||||
currentPreviewItem.value = item
|
currentPreviewItem.value = item
|
||||||
audioList.value = []
|
audioList.value = []
|
||||||
for (const audio of item.audios) {
|
if(item.audios){
|
||||||
let temp={
|
for (const audio of item.audios) {
|
||||||
audioId:audio.audioId,
|
let temp={
|
||||||
url:getAudioUrl(audio.filePath)
|
audioId:audio.audioId,
|
||||||
|
url:getAudioUrl(audio.filePath)
|
||||||
|
}
|
||||||
|
audioList.value.push(temp)
|
||||||
}
|
}
|
||||||
audioList.value.push(temp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
previewModalVisible.value = true
|
previewModalVisible.value = true
|
||||||
|
|
|
@ -251,15 +251,22 @@ const fetchPartList = async (projectId: string, turbineId: string) => {
|
||||||
|
|
||||||
// 处理筛选变化,获取图像列表
|
// 处理筛选变化,获取图像列表
|
||||||
const handleFilterChange = async () => {
|
const handleFilterChange = async () => {
|
||||||
if (!filterParams.unit) return
|
// if (!filterParams.project) return
|
||||||
|
|
||||||
loading.image = true
|
loading.image = true
|
||||||
try {
|
try {
|
||||||
let params = {
|
let params = {
|
||||||
turbineId: filterParams.unit
|
projectId: filterParams.project
|
||||||
|
}
|
||||||
|
if(filterParams.unit){
|
||||||
|
params = {
|
||||||
|
projectId: filterParams.project,
|
||||||
|
turbineId: filterParams.unit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(filterParams.component){
|
if(filterParams.component){
|
||||||
params = {
|
params = {
|
||||||
|
projectId: filterParams.project,
|
||||||
turbineId: filterParams.unit,
|
turbineId: filterParams.unit,
|
||||||
partId: filterParams.component
|
partId: filterParams.component
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:visible="visible"
|
||||||
|
title="招标详情"
|
||||||
|
width="800px"
|
||||||
|
:footer="false"
|
||||||
|
:mask-closable="false"
|
||||||
|
@update:visible="(val) => $emit('update:visible', val)"
|
||||||
|
>
|
||||||
|
<a-descriptions
|
||||||
|
:column="2"
|
||||||
|
bordered
|
||||||
|
:label-style="{ width: '120px', fontWeight: 'bold' }"
|
||||||
|
>
|
||||||
|
<a-descriptions-item label="项目名称">{{ detail.projectName }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="招标单位">{{ detail.biddingUnit }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="预算金额">{{ detail.budgetAmount }}万元</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="截止时间">{{ detail.deadline }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="爬取时间">{{ detail.crawlingTime }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="项目地点">{{ detail.projectLocation }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="项目周期">{{ detail.projectDuration }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="招标范围">{{ detail.biddingScope }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="资质要求">{{ detail.qualificationRequirements }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="来源平台">
|
||||||
|
<a
|
||||||
|
:href="getPlatformUrl(detail.sourcePlatform)"
|
||||||
|
target="_blank"
|
||||||
|
class="platform-link"
|
||||||
|
>
|
||||||
|
{{ detail.sourcePlatform }}
|
||||||
|
</a>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="招标文件">
|
||||||
|
<div class="file-display">
|
||||||
|
<a-link
|
||||||
|
v-if="detail.biddingDocuments"
|
||||||
|
:href="detail.biddingDocuments"
|
||||||
|
target="_blank"
|
||||||
|
class="file-link"
|
||||||
|
>
|
||||||
|
{{ displayedFileName }}
|
||||||
|
</a-link>
|
||||||
|
<span v-else class="no-file">暂无文件</span>
|
||||||
|
|
||||||
|
<a-upload
|
||||||
|
:show-file-list="false"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
accept=".pdf,.doc,.docx"
|
||||||
|
style="margin-left: 10px"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
type="outline"
|
||||||
|
size="mini"
|
||||||
|
:loading="uploading"
|
||||||
|
>
|
||||||
|
<template #icon><icon-upload /></template>
|
||||||
|
{{ detail.biddingDocuments ? '重新上传' : '上传文件' }}
|
||||||
|
</a-button>
|
||||||
|
</a-upload>
|
||||||
|
</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
|
||||||
|
<div class="content-section">
|
||||||
|
<h3>招标内容</h3>
|
||||||
|
<div class="content-text">{{ detail.biddingContent }}</div>
|
||||||
|
<ul v-if="detail.contentItems && detail.contentItems.length">
|
||||||
|
<li v-for="(item, index) in detail.contentItems" :key="index">{{ item }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import type { BiddingDetail } from './types'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean
|
||||||
|
detail: BiddingDetail
|
||||||
|
uploading?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:visible', 'upload'])
|
||||||
|
|
||||||
|
const displayedFileName = computed(() => {
|
||||||
|
if (!props.detail?.biddingDocuments) return ''
|
||||||
|
const url = props.detail.biddingDocuments
|
||||||
|
return url.split('/').pop() || '招标文件'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 平台URL映射
|
||||||
|
const platformUrls: Record<string, string> = {
|
||||||
|
'中国招标投标网': 'https://www.cebpubservice.com/',
|
||||||
|
'国能e招': 'https://www.negc.cn/',
|
||||||
|
'中国节能': 'https://www.cecec.cn/',
|
||||||
|
'三峡招标': 'https://epp.ctg.com.cn/'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPlatformUrl = (platformName: string): string => {
|
||||||
|
return platformUrls[platformName] || '#'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpload = () => {
|
||||||
|
emit('upload')
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeUpload = (file: File) => {
|
||||||
|
// 检查文件类型
|
||||||
|
const isValidType = [
|
||||||
|
'application/pdf',
|
||||||
|
'application/msword',
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||||
|
].includes(file.type)
|
||||||
|
|
||||||
|
// 检查文件大小 (限制10MB)
|
||||||
|
const isLt10M = file.size / 1024 / 1024 < 10
|
||||||
|
|
||||||
|
if (!isValidType) {
|
||||||
|
Message.error('只能上传PDF或Word文件!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLt10M) {
|
||||||
|
Message.error('文件大小不能超过10MB!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发上传事件
|
||||||
|
emit('upload', file)
|
||||||
|
|
||||||
|
// 返回false阻止默认上传行为,由父组件处理
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.file-display {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-link {
|
||||||
|
color: #1890ff;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
max-width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #40a9ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-file {
|
||||||
|
color: var(--color-text-3);
|
||||||
|
}
|
||||||
|
.platform-link {
|
||||||
|
color: #1890ff; /* 蓝色 */
|
||||||
|
text-decoration: underline; /* 下划线 */
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #40a9ff; /* 悬停时变浅蓝色 */
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content-section {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: var(--color-fill-2);
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-text {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,164 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:visible="visible"
|
||||||
|
title="爬虫设置"
|
||||||
|
width="800px"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
@ok="handleOk"
|
||||||
|
:mask-closable="false"
|
||||||
|
>
|
||||||
|
<a-form :model="form" layout="vertical">
|
||||||
|
<a-form-item label="爬取频率">
|
||||||
|
<a-select v-model="form.frequency">
|
||||||
|
<a-option value="hourly">每小时</a-option>
|
||||||
|
<a-option value="daily">每天</a-option>
|
||||||
|
<a-option value="weekly">每周</a-option>
|
||||||
|
<a-option value="monthly">每月</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="关键词过滤">
|
||||||
|
<a-input-tag
|
||||||
|
v-model="form.keywords"
|
||||||
|
placeholder="输入关键词后回车"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="来源平台">
|
||||||
|
<a-checkbox-group v-model="form.platforms">
|
||||||
|
<a-row :gutter="[16, 16]">
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-checkbox value="国能e招">国能e招</a-checkbox>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-checkbox value="中国节能">中国节能</a-checkbox>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-checkbox value="科幻集团">科幻集团</a-checkbox>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-checkbox value="三峡招标">三峡招标</a-checkbox>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-checkbox value="三峡采购">三峡采购</a-checkbox>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-checkbox value="北京京能">北京京能</a-checkbox>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<a-checkbox value="华润守正">华润守正</a-checkbox>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item label="自动通知">
|
||||||
|
<a-switch v-model="form.autoNotify" />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<template v-if="form.autoNotify">
|
||||||
|
<a-form-item label="通知方式">
|
||||||
|
<a-checkbox-group v-model="form.notifyMethods">
|
||||||
|
<a-checkbox value="inApp">站内消息</a-checkbox>
|
||||||
|
<a-checkbox value="email">电子邮件</a-checkbox>
|
||||||
|
</a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item v-if="form.notifyMethods.includes('email')" label="通知邮箱">
|
||||||
|
<a-input v-model="form.notifyEmail" placeholder="请输入接收通知的邮箱" />
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-divider />
|
||||||
|
|
||||||
|
<a-form-item label="爬虫日志">
|
||||||
|
<div class="log-container">
|
||||||
|
<div v-for="(log, index) in logs" :key="index" class="log-item">
|
||||||
|
[{{ log.time }}] {{ log.message }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<a-button @click="handleCancel">取消</a-button>
|
||||||
|
<a-button type="primary" @click="handleOk">保存设置</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, watch } from 'vue'
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
|
||||||
|
interface LogEntry {
|
||||||
|
time: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:visible', 'save'])
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
frequency: 'daily',
|
||||||
|
keywords: ['风电', '叶片', '检查', '运维'],
|
||||||
|
platforms: ['国能e招', '中国节能', '三峡招标'],
|
||||||
|
autoNotify: true,
|
||||||
|
notifyMethods: ['inApp', 'email'],
|
||||||
|
notifyEmail: 'user@example.com'
|
||||||
|
})
|
||||||
|
|
||||||
|
const logs = ref<LogEntry[]>([
|
||||||
|
{ time: '2025-07-30 22:00', message: '爬虫任务已启动' },
|
||||||
|
{ time: '2025-07-30 21:00', message: '爬虫任务已启动' },
|
||||||
|
{ time: '2023-11-01 09:30', message: '成功爬取中国招标投标网数据' },
|
||||||
|
{ time: '2023-11-01 09:32', message: '成功爬取某省公共资源交易中心数据' },
|
||||||
|
{ time: '2023-11-02 10:15', message: '爬取企业自有招标平台失败:连接超时' }
|
||||||
|
])
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
Message.success('设置保存成功')
|
||||||
|
emit('save', form)
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for email notification toggle
|
||||||
|
watch(() => form.notifyMethods, (newVal) => {
|
||||||
|
if (!newVal.includes('email')) {
|
||||||
|
form.notifyEmail = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.log-container {
|
||||||
|
height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid var(--color-border-2);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--color-fill-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-item {
|
||||||
|
padding: 4px 0;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-item:not(:last-child) {
|
||||||
|
border-bottom: 1px dashed var(--color-border-2);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -46,11 +46,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { TableColumnData, TableInstance } from '@arco-design/web-vue'
|
import type { TableColumnData } from '@arco-design/web-vue'
|
||||||
import PostAddModal from './PostAddModal.vue'
|
import PostAddModal from './PostAddModal.vue'
|
||||||
import PostDetailDrawer from './PostDetailDrawer.vue'
|
import PostDetailDrawer from './PostDetailDrawer.vue'
|
||||||
import { deletePost, pagePost,listPost } from '@/apis/system/post'
|
import { deletePost, listPost } from '@/apis/system/post'
|
||||||
import type { PostVO, PostPageQuery } from '@/apis/system/type'
|
import type { PostVO } from '@/apis/system/type'
|
||||||
import { useResetReactive, useTable } from '@/hooks'
|
import { useResetReactive, useTable } from '@/hooks'
|
||||||
import { isMobile } from '@/utils'
|
import { isMobile } from '@/utils'
|
||||||
import has from '@/utils/has'
|
import has from '@/utils/has'
|
||||||
|
@ -81,7 +81,7 @@ const queryFormColumns: ColumnItem[] = reactive([
|
||||||
props: {
|
props: {
|
||||||
options: [
|
options: [
|
||||||
{ label: '正常', value: 1 },
|
{ label: '正常', value: 1 },
|
||||||
{ label: '停用', value: 0 }
|
{ label: '停用', value: 0 },
|
||||||
],
|
],
|
||||||
placeholder: '请选择状态',
|
placeholder: '请选择状态',
|
||||||
},
|
},
|
||||||
|
@ -94,8 +94,8 @@ const {
|
||||||
pagination,
|
pagination,
|
||||||
search,
|
search,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
} = useTable((params) => listPost({
|
} = useTable((params) => listPost({
|
||||||
...queryForm,
|
...queryForm,
|
||||||
}), { immediate: true })
|
}), { immediate: true })
|
||||||
|
|
||||||
const tableColumns = ref<TableColumnData[]>([
|
const tableColumns = ref<TableColumnData[]>([
|
||||||
|
@ -106,44 +106,44 @@ const tableColumns = ref<TableColumnData[]>([
|
||||||
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
|
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
|
||||||
fixed: !isMobile() ? 'left' : undefined,
|
fixed: !isMobile() ? 'left' : undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '岗位名称',
|
title: '岗位名称',
|
||||||
dataIndex: 'postName',
|
dataIndex: 'postName',
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true,
|
tooltip: true,
|
||||||
fixed: !isMobile() ? 'left' : undefined,
|
fixed: !isMobile() ? 'left' : undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '岗位排序',
|
title: '岗位排序',
|
||||||
dataIndex: 'postSort',
|
dataIndex: 'postSort',
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
slotName: 'status',
|
slotName: 'status',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 100
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '说明',
|
title: '说明',
|
||||||
dataIndex: 'remark',
|
dataIndex: 'remark',
|
||||||
minWidth: 180,
|
minWidth: 180,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true
|
tooltip: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
dataIndex: 'createTime',
|
dataIndex: 'createTime',
|
||||||
minWidth: 180,
|
minWidth: 180,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
tooltip: true,
|
tooltip: true,
|
||||||
sortable: {
|
sortable: {
|
||||||
sortDirections: ['ascend', 'descend'],
|
sortDirections: ['ascend', 'descend'],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
|
@ -188,4 +188,4 @@ const onDetail = (record: PostVO) => {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<h1>这个暂时写不了,角色管理采用菜单权限的设置</h1>
|
|
||||||
<GiPageLayout :header-style="{ padding: 0, borderBottom: 'none' }">
|
<GiPageLayout :header-style="{ padding: 0, borderBottom: 'none' }">
|
||||||
<template #left>
|
<template #left>
|
||||||
<RoleTree @node-click="handleSelectRole" />
|
<RoleTree @node-click="handleSelectRole" />
|
||||||
|
|
|
@ -17,25 +17,25 @@
|
||||||
</template>
|
</template>
|
||||||
</a-upload>
|
</a-upload>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<span style="margin-right: 10px">{{ userInfo.nickname }}</span>
|
<span style="margin-right: 10px">{{ userInfo.name }}</span>
|
||||||
<icon-edit :size="16" class="btn" @click="onUpdate" />
|
<icon-edit :size="16" class="btn" @click="onUpdate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="id">
|
<div class="id">
|
||||||
<GiSvgIcon name="id" :size="16" />
|
<GiSvgIcon name="id" :size="16" />
|
||||||
<span>{{ userInfo.id }}</span>
|
<span>{{ userInfo.userId }}</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<footer>
|
<footer>
|
||||||
<a-descriptions :column="4" size="large">
|
<a-descriptions :column="4" size="large">
|
||||||
<a-descriptions-item :span="4">
|
<a-descriptions-item :span="4">
|
||||||
<template #label> <icon-user /><span style="margin-left: 5px">用户名</span></template>
|
<template #label> <icon-user /><span style="margin-left: 5px">用户名</span></template>
|
||||||
{{ userInfo.username }}
|
{{ userInfo.account }}
|
||||||
<icon-man v-if="userInfo.gender === 1" style="color: #19bbf1" />
|
<icon-man v-if="userInfo.gender === 1" style="color: #19bbf1" />
|
||||||
<icon-woman v-else-if="userInfo.gender === 2" style="color: #fa7fa9" />
|
<icon-woman v-else-if="userInfo.gender === 2" style="color: #fa7fa9" />
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item :span="4">
|
<a-descriptions-item :span="4">
|
||||||
<template #label> <icon-phone /><span style="margin-left: 5px">手机</span></template>
|
<template #label> <icon-phone /><span style="margin-left: 5px">手机</span></template>
|
||||||
{{ userInfo.phone || '暂无' }}
|
{{ userInfo.mobile || '暂无' }}
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item :span="4">
|
<a-descriptions-item :span="4">
|
||||||
<template #label> <icon-email /><span style="margin-left: 5px">邮箱</span></template>
|
<template #label> <icon-email /><span style="margin-left: 5px">邮箱</span></template>
|
||||||
|
@ -47,12 +47,12 @@
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item :span="4">
|
<a-descriptions-item :span="4">
|
||||||
<template #label> <icon-user-group /><span style="margin-left: 5px">角色</span></template>
|
<template #label> <icon-user-group /><span style="margin-left: 5px">角色</span></template>
|
||||||
{{ userInfo.roles.join(',') }}
|
{{ userInfo.roles?.map(role => role.roleName).join(', ') || '暂无' }}
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
</a-descriptions>
|
</a-descriptions>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">注册于 {{ userInfo.registrationDate }}</div>
|
<div class="footer">注册于 {{ userInfo.createTime }}</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
<a-modal v-model:visible="visible" title="上传头像" :width="width >= 400 ? 400 : '100%'" :footer="false" draggable @close="reset">
|
<a-modal v-model:visible="visible" title="上传头像" :width="width >= 400 ? 400 : '100%'" :footer="false" draggable @close="reset">
|
||||||
|
@ -102,19 +102,22 @@ import { uploadAvatar } from '@/apis/system'
|
||||||
import 'vue-cropper/dist/index.css'
|
import 'vue-cropper/dist/index.css'
|
||||||
import { useUserStore } from '@/stores'
|
import { useUserStore } from '@/stores'
|
||||||
import getAvatar from '@/utils/avatar'
|
import getAvatar from '@/utils/avatar'
|
||||||
|
import XG_DEBUG from 'xgplayer/es/utils/debug'
|
||||||
|
import config = XG_DEBUG.config
|
||||||
|
|
||||||
const { width } = useWindowSize()
|
const { width } = useWindowSize()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const userInfo = computed(() => userStore.userInfo)
|
const userInfo = computed(() => userStore.userInfo)
|
||||||
|
|
||||||
const avatar = {
|
const avatar = computed(() => ({
|
||||||
uid: '-2',
|
uid: '-2',
|
||||||
name: 'avatar.png',
|
name: 'avatar.png',
|
||||||
url: userInfo.value.avatar,
|
url: userInfo.value.avatar || getAvatar(userInfo.value.avatar, undefined),
|
||||||
}
|
}))
|
||||||
const avatarList = ref<FileItem[]>([avatar])
|
|
||||||
const fileRef = ref(reactive({ name: 'avatar.png' }))
|
const avatarList = computed<FileItem[]>(() => [avatar.value])
|
||||||
const options: cropperOptions = reactive({
|
const fileRef = ref<File | null>(null)
|
||||||
|
const options = reactive<cropperOptions>({
|
||||||
img: '',
|
img: '',
|
||||||
autoCrop: true,
|
autoCrop: true,
|
||||||
autoCropWidth: 160,
|
autoCropWidth: 160,
|
||||||
|
@ -128,13 +131,14 @@ const options: cropperOptions = reactive({
|
||||||
outputType: 'png',
|
outputType: 'png',
|
||||||
})
|
})
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
|
|
||||||
// 打开裁剪框
|
// 打开裁剪框
|
||||||
const onBeforeUpload = (file: File): boolean => {
|
const onBeforeUpload = (file: File): boolean => {
|
||||||
fileRef.value = file
|
fileRef.value = file
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
options.img = reader.result
|
options.img = reader.result as string
|
||||||
}
|
}
|
||||||
visible.value = true
|
visible.value = true
|
||||||
return false
|
return false
|
||||||
|
|
Loading…
Reference in New Issue