From bf663be047b717ebdd9896a4fe878a9ce61b087b Mon Sep 17 00:00:00 2001 From: Litrix2 Date: Sun, 12 Jan 2025 09:58:10 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20=E6=96=B0=E5=A2=9E=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E7=A4=BE=E5=9B=A2=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/index.ts | 58 ++++---- src/api/request.ts | 84 ++++++++++++ src/components/PagedWrapper.vue | 30 +++-- src/components/app/LoginRegisterDialog.vue | 12 +- src/components/club/ClubAvatar.vue | 12 ++ src/components/club/ClubDetailDialog.vue | 35 +++++ src/components/club/ClubEditCard.vue | 111 +++++++++++++++ src/components/club/ClubList.vue | 8 +- src/components/club/ClubListItem.vue | 9 +- src/components/club/ClubMemberCard.vue | 0 src/router/index.ts | 26 +++- src/schemas/response.ts | 8 +- src/stores/user.ts | 7 +- src/utils/types.ts | 3 + src/views/club/ClubListPage.vue | 30 +---- src/views/manage/ManageClubEditPage.vue | 11 ++ src/views/manage/ManageClubPage.vue | 149 ++++++++++++++++++--- src/views/manage/ManageMainPage.vue | 2 +- src/views/user/UserEditPage.vue | 74 ++++------ 19 files changed, 517 insertions(+), 152 deletions(-) create mode 100644 src/api/request.ts create mode 100644 src/components/club/ClubAvatar.vue create mode 100644 src/components/club/ClubDetailDialog.vue create mode 100644 src/components/club/ClubEditCard.vue create mode 100644 src/components/club/ClubMemberCard.vue create mode 100644 src/views/manage/ManageClubEditPage.vue diff --git a/src/api/index.ts b/src/api/index.ts index 1069a6e..6aab745 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,14 +1,11 @@ import { REQUEST_BASE_URL } from '@/env'; -import { - userInfoRespSchema, - type AnyRespSchema, - type ErrorResp, - type SucceedRespOf, -} from '@/schemas/response'; +import { type AnyRespSchema, type ErrorResp, type SucceedRespOf } from '@/schemas/response'; import { useUserStore } from '@/stores/user'; +import type { ComparablePrimitive } from '@/utils/types'; import axios, { type AxiosError, type AxiosRequestConfig } from 'axios'; import { ElMessage } from 'element-plus'; import type { z } from 'zod'; +export * from './request'; export const axiosInstance = axios.create({ baseURL: REQUEST_BASE_URL, }); @@ -79,23 +76,36 @@ export async function request( options.validationErrorCB, ); } -export async function getUserInfo({ - userID, - validationErrorCB, -}: { - userID?: number; - validationErrorCB?: ValidationErrorCallback; -}) { - let url = '/api/user/info'; - if (userID !== undefined) { - url += `/${userID}`; +export async function patchRequest>( + obj: T, + old: NoInfer, + mapping: { + [P in keyof T]?: ( + v: T[P], + ) => Promise> | boolean | undefined>; + }, +) { + let changed = false, + succeed = true; + let k: keyof T; + const promises: Promise[] = []; + for (k in obj) { + const v = obj[k]; + if (v !== old[k] && mapping[k]) { + changed = true; + const p = mapping[k]!(v); + if (p) + promises.push( + p.then((resp) => { + if (!resp) succeed = false; + }), + ); + } } - const resp = await request(userInfoRespSchema, { - url, - errorDescription: '获取用户信息失败', - validationErrorCB, - }); - return resp; + await Promise.all(promises); + const res = !changed ? 'unchanged' : succeed ? 'succeed' : 'error'; + if (res === 'unchanged') { + ElMessage.info('没有要修改的信息'); + } + return res; } -export const getAvatarURL = (avatar: string | undefined) => - avatar && `${REQUEST_BASE_URL}/api/avatar/${avatar}`; diff --git a/src/api/request.ts b/src/api/request.ts new file mode 100644 index 0000000..968ebce --- /dev/null +++ b/src/api/request.ts @@ -0,0 +1,84 @@ +import { + clubRespSchema, + ordinarySchema, + uploadAvatarRespSchema, + userInfoRespSchema, +} from '@/schemas/response'; +import { type ValidationErrorCallback, request } from '.'; +import { REQUEST_BASE_URL } from '@/env'; +import { ElMessage, type UploadRawFile } from 'element-plus'; + +export async function getUserInfo({ + userID, + validationErrorCB, +}: { + userID?: number; + validationErrorCB?: ValidationErrorCallback; +}) { + let url = '/api/user/info'; + if (userID !== undefined) { + url += `/${userID}`; + } + const resp = await request(userInfoRespSchema, { + url, + errorDescription: '获取用户信息失败', + validationErrorCB, + }); + return resp; +} +// #region Avatar +export const getAvatarURL = (avatar: string | undefined) => + avatar && `${REQUEST_BASE_URL}/api/avatar/${avatar}`; +export function validateAvatarFile(file: UploadRawFile) { + if (!file.type.startsWith('image')) { + ElMessage.error('只能上传图片'); + return false; + } + if (file.size > 1024 * 1024) { + ElMessage.error('文件大小不能超过1MB'); + return false; + } + return true; +} +export function uploadAvatar(file: File) { + const formData = new FormData(); + formData.append('file', file); + return request(uploadAvatarRespSchema, { + url: '/api/avatar/upload', + method: 'post', + data: formData, + errorDescription: '上传头像失败', + }); +} +// #endregion +// #region 社团 +export const getClubById = (id: number) => + request(clubRespSchema, { + url: `/api/club/${id}`, + errorDescription: '获取社团信息失败', + }).then((r) => r?.data); +export const changeClubName = (id: number, name: string) => + request(ordinarySchema, { + url: '/api/club/name', + method: 'put', + data: { clubId: id, name }, + errorDescription: '修改社团名称失败', + }); +export const changeClubCommit = (id: number, commit: string) => + request(ordinarySchema, { + url: '/api/club/commit', + method: 'put', + data: { clubId: id, commit }, + errorDescription: '修改社团简介失败', + }); +export const changeClubAvatar = (id: number, code: string) => + request(ordinarySchema, { + url: '/api/club/avatar', + method: 'put', + params: { + code, + clubId: id, + }, + errorDescription: '修改社团头像失败', + }); +// #endregion diff --git a/src/components/PagedWrapper.vue b/src/components/PagedWrapper.vue index 5551220..39dd059 100644 --- a/src/components/PagedWrapper.vue +++ b/src/components/PagedWrapper.vue @@ -6,7 +6,7 @@ > diff --git a/src/components/club/ClubMemberCard.vue b/src/components/club/ClubMemberCard.vue new file mode 100644 index 0000000..e69de29 diff --git a/src/router/index.ts b/src/router/index.ts index 42ffa82..eb3cfa2 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -4,6 +4,7 @@ import type { MaybeArray } from '@/utils/types'; import { PageErrorType, type PageErrorReason } from '@/views/ErrorPage.vue'; import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'; import { ClubPermissionId, UserPermissionId } from './permissions'; +import { ElMessage } from 'element-plus'; export type RoutePermissionRelation = 'AND' | 'OR'; export type RoutePermission = | MaybeArray @@ -121,8 +122,29 @@ const routes: RouteRecordRaw[] = [ }, { path: 'club', - name: 'ManageClub', - component: () => import('@/views/manage/ManageClubPage.vue'), + children: [ + { + path: '', + name: 'ManageClub', + component: () => import('@/views/manage/ManageClubPage.vue'), + }, + { + path: ':id/edit', + name: 'ManageClubEdit', + component: () => import('@/views/manage/ManageClubEditPage.vue'), + beforeEnter(to, from) { + const id = Number(to.params.id); + if (isNaN(id) || id < 1) { + ElMessage.error('社团ID无效'); + return from; + } + }, + props: (to) => ({ id: Number(to.params.id) }), + meta: { + breadcrumb: '编辑社团', + }, + }, + ], meta: { userPermission: { id: UserPermissionId.MANAGE_PAGE, diff --git a/src/schemas/response.ts b/src/schemas/response.ts index a910f9e..0af766f 100644 --- a/src/schemas/response.ts +++ b/src/schemas/response.ts @@ -26,6 +26,9 @@ const createRespSchema = (data: T) => }), ), ]); +export type AnyRespSchema = ReturnType; +export type ErrorResp = Extract, { type: 'error' }>; +export type SucceedRespOf> = Extract; export const createPagedRespSchema = (data: T) => createRespSchema( z.object({ @@ -33,9 +36,7 @@ export const createPagedRespSchema = (data: T) => total: z.number(), }), ); -export type AnyRespSchema = ReturnType; -export type ErrorResp = Extract, { type: 'error' }>; -export type SucceedRespOf = Extract; +export type SucceedPagedDataOf> = SucceedRespOf['data']; export type AnyPagedRespSchema = ReturnType; export const ordinarySchema = createRespSchema(z.literal(true)); export const idAndNameSchema = z.object({ @@ -51,6 +52,7 @@ export const clubSchema = idAndNameSchema.extend({ commit: z.string(), }); export type Club = z.infer; +export const clubRespSchema = createRespSchema(clubSchema); export const clubListRespSchema = createPagedRespSchema(clubSchema); const userInfoDataSchema = idAndNameSchema.extend({ avatar: z.optional(z.string()), diff --git a/src/stores/user.ts b/src/stores/user.ts index b18050c..eb714b8 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -99,7 +99,12 @@ export const useUserStore = defineStore('user', () => { } }, }); - if (!resp) return false; + if (!resp) { + if (!token.value) { + userInfo.value = null; + } + return false; + } userInfo.value = resp.data; return true; } finally { diff --git a/src/utils/types.ts b/src/utils/types.ts index 83e095a..8dce26f 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,3 +1,5 @@ +import type { Primitive } from 'zod'; + export type MaybePromise = T | Promise; export type MaybeArray = T | (R extends false ? T[] : readonly T[]); export type Future = PromiseWithResolvers; @@ -9,3 +11,4 @@ export type Override = {}, O extends keyo R; export type Nullable = T | null | undefined; export type StrictOmit = Omit; +export type ComparablePrimitive = Exclude; diff --git a/src/views/club/ClubListPage.vue b/src/views/club/ClubListPage.vue index 9a6f7d7..3cf4a88 100644 --- a/src/views/club/ClubListPage.vue +++ b/src/views/club/ClubListPage.vue @@ -8,45 +8,19 @@ - - -
- -
简介:{{ showingClub.commit }}
- -
-
+ - diff --git a/src/views/manage/ManageClubPage.vue b/src/views/manage/ManageClubPage.vue index 8d9d46b..8e64156 100644 --- a/src/views/manage/ManageClubPage.vue +++ b/src/views/manage/ManageClubPage.vue @@ -2,34 +2,151 @@ - + 新建社团 + 批量删除 + 刷新 + 查询 重置 - + - + + + + + + +
+ 暂无 +
+ 图片 +
+
+
+ + + 查看介绍 + + + +
+ 管理成员 + + 编辑 + + + 删除 + +
+
+
+
- + diff --git a/src/views/manage/ManageMainPage.vue b/src/views/manage/ManageMainPage.vue index 3496a74..a317a6f 100644 --- a/src/views/manage/ManageMainPage.vue +++ b/src/views/manage/ManageMainPage.vue @@ -1,5 +1,5 @@