gemercheung 6 mesi fa
parent
commit
11faa302a3

+ 1 - 1
packages/frontend/package.json

@@ -21,7 +21,7 @@
     "echarts": "^5.5.1",
     "katex": "^0.16.19",
     "lodash-es": "^4.17.21",
-    "naive-ui": "^2.40.3",
+    "naive-ui": "^2.41.0",
     "pinia": "^2.3.0",
     "pinia-plugin-persistedstate": "^4.1.3",
     "remixicon": "^4.6.0",

+ 30 - 20
packages/frontend/src/views/article/edit.vue

@@ -7,34 +7,44 @@
     </template>
 
     <div class="editor-wrap">
-      <n-form ref="modalFormRef" class="form wh-full" label-placement="left" label-align="left" :label-width="80"
-        :model="modalForm">
-        <n-form-item label="文章名称" path="title" :rule="{
-          required: true,
-          message: '请输入文章名称',
-          trigger: ['input', 'blur'],
-        }">
+      <n-form
+        ref="modalFormRef" class="form wh-full" label-placement="left" label-align="left" :label-width="80"
+        :model="modalForm"
+      >
+        <n-form-item
+          label="文章名称" path="title" :rule="{
+            required: true,
+            message: '请输入文章名称',
+            trigger: ['input', 'blur'],
+          }"
+        >
           <n-input v-model:value="modalForm.title" />
         </n-form-item>
 
-        <n-form-item label="文章分类" path="categoryId" :rule="{
-          required: true,
-          type: 'number',
-          trigger: ['change', 'blur'],
-          message: '请输入文章分类',
-        }">
-          <n-tree-select v-model:value="modalForm.categoryId" :options="allCategory" label-field="title" key-field="id"
-            placeholder="根分类" clearable />
+        <n-form-item
+          label="文章分类" path="categoryId" :rule="{
+            required: true,
+            type: 'number',
+            trigger: ['change', 'blur'],
+            message: '请输入文章分类',
+          }"
+        >
+          <n-tree-select
+            v-model:value="modalForm.categoryId" :options="allCategory" label-field="title" key-field="id"
+            placeholder="根分类" clearable
+          />
         </n-form-item>
 
         <n-tabs type="line" animated>
           <template v-for="(lang, index) in langs" :key="lang">
             <n-tab-pane :name="lang" :tab="langLabel[lang]" :index="index">
-              <n-form-item label="文章名称" path="title" :rule="{
-                required: true,
-                message: '请输入文章名称',
-                trigger: ['input', 'blur'],
-              }">
+              <n-form-item
+                label="文章名称" path="title" :rule="{
+                  required: true,
+                  message: '请输入文章名称',
+                  trigger: ['input', 'blur'],
+                }"
+              >
                 <n-input v-model:value="modalForm.translations[index].title" />
               </n-form-item>
               <div class="h-450">

+ 34 - 24
packages/frontend/src/views/category/index.vue

@@ -9,27 +9,33 @@
 
     <MeCrud ref="$table" v-model:query-items="queryItems" :scroll-x="1200" :columns="columns" :get-data="api.read">
       <MeQueryItem label="分类名称" :label-width="80">
-        <n-input v-model:value="queryItems.title" :maxlength="200" show-count type="text" placeholder="请输入分类名称" clearable>
+        <n-input v-model:value="queryItems.title" :maxlength="200" type="text" placeholder="请输入分类名称" clearable show-count>
           <template #password-visible-icon />
         </n-input>
       </MeQueryItem>
       <MeQueryItem label="状态" :label-width="50">
-        <n-select v-model:value="queryItems.enable" clearable :options="[
-          { label: '启用', value: 1 },
-          { label: '停用', value: 0 },
-        ]" />
+        <n-select
+          v-model:value="queryItems.enable" clearable :options="[
+            { label: '启用', value: 1 },
+            { label: '停用', value: 0 },
+          ]"
+        />
       </MeQueryItem>
     </MeCrud>
 
     <MeModal ref="modalRef" width="520px">
       <n-form ref="modalFormRef" label-placement="left" label-align="left" :label-width="80" :model="modalForm">
-        <n-form-item label="分类名" path="title" :rule="{
-          required: true,
-          message: '请输入分类名',
-          trigger: ['input', 'blur'],
-        }">
-          <n-input :maxlength="200" show-count
-           v-model:value="modalForm.title" />
+        <n-form-item
+          label="分类名" path="title" :rule="{
+            required: true,
+            message: '请输入分类名',
+            trigger: ['input', 'blur'],
+          }"
+        >
+          <n-input
+            v-model:value="modalForm.title" :maxlength="200"
+            show-count
+          />
         </n-form-item>
         <n-form-item label="上层分类" path="parentId">
           <n-select v-model:value="modalForm.parentId" :options="allCategory" clearable filterable tag />
@@ -41,20 +47,24 @@
         <n-tabs v-if="modalForm.translations.length > 0" type="line" animated>
           <template v-for="(lang, index) in langs" :key="lang">
             <n-tab-pane :name="lang" :tab="langLabel[lang]" :index="index">
-              <n-form-item label="名称" path="title" :rule="{
-                required: true,
-                message: '请输入名称',
-                trigger: ['input', 'blur'],
-              }">
-                <n-input :maxlength="200" show-count v-model:value="modalForm.translations.find(i => i.locale === lang).title" />
+              <n-form-item
+                label="名称" path="title" :rule="{
+                  required: true,
+                  message: '请输入名称',
+                  trigger: ['input', 'blur'],
+                }"
+              >
+                <n-input v-model:value="modalForm.translations.find(i => i.locale === lang).title" :maxlength="200" show-count />
               </n-form-item>
 
-              <n-form-item label="备注" path="remark" :rule="{
-                required: false,
-                message: '请输入备注',
-                trigger: ['input', 'blur'],
-              }">
-                <n-input :maxlength="200" show-count v-model:value="modalForm.translations.find(i => i.locale === lang).remark" type="textarea" />
+              <n-form-item
+                label="备注" path="remark" :rule="{
+                  required: false,
+                  message: '请输入备注',
+                  trigger: ['input', 'blur'],
+                }"
+              >
+                <n-input v-model:value="modalForm.translations.find(i => i.locale === lang).remark" :maxlength="200" show-count type="textarea" />
               </n-form-item>
             </n-tab-pane>
           </template>

+ 51 - 35
packages/frontend/src/views/menu/index.vue

@@ -18,8 +18,10 @@
                   <n-card :bordered="false" size="small">
                     <template #cover>
                       <!-- <div style="width: 100%;height: 50px;overflow: hidden;"> -->
-                      <n-image preview-disabled :src="child.cover" object-fit="scale-down"
-                        style="width: 100%;height: 50px;overflow: hidden;" />
+                      <n-image
+                        preview-disabled :src="child.cover" object-fit="scale-down"
+                        style="width: 100%;height: 50px;overflow: hidden;"
+                      />
                       <!-- </div> -->
                     </template>
                     <div class="text-center text-12">
@@ -36,8 +38,10 @@
             </template>
           </div>
           <template #header-extra>
-            <n-dropdown class="menu_dropdown" trigger="click" :options="options" :show-arrow="true"
-              @select="(key) => handleSelect(key, item)">
+            <n-dropdown
+              class="menu_dropdown" trigger="click" :options="options" :show-arrow="true"
+              @select="(key) => handleSelect(key, item)"
+            >
               <n-button text>
                 <i class="i-material-symbols:more-horiz text-24" />
               </n-button>
@@ -49,47 +53,59 @@
 
     <MeModal ref="modalRef" width="520px">
       <n-form ref="modalFormRef" label-placement="left" label-align="left" :label-width="80" :model="modalForm">
-        <n-form-item label="名称" path="title" :rule="{
-          required: true,
-          message: '请输入名称',
-          trigger: ['input', 'blur'],
-        }">
-          <n-input :maxlength="200" show-count v-model:value="modalForm.title" />
+        <n-form-item
+          label="名称" path="title" :rule="{
+            required: true,
+            message: '请输入名称',
+            trigger: ['input', 'blur'],
+          }"
+        >
+          <n-input v-model:value="modalForm.title" :maxlength="200" show-count />
         </n-form-item>
 
-        <n-form-item label="描述" path="description" :rule="{
-          required: false,
-          message: '请输入描述',
-          trigger: ['input', 'blur'],
-        }">
-          <n-input :maxlength="200" show-count v-model:value="modalForm.description" type="textarea" />
+        <n-form-item
+          label="描述" path="description" :rule="{
+            required: false,
+            message: '请输入描述',
+            trigger: ['input', 'blur'],
+          }"
+        >
+          <n-input v-model:value="modalForm.description" :maxlength="200" show-count type="textarea" />
         </n-form-item>
-        <n-form-item label="样式类型" path="styleType" :rule="{
-          required: true,
-          type: 'number',
-          message: '请输入样式类型',
-          trigger: ['input', 'blur'],
-        }">
+        <n-form-item
+          label="样式类型" path="styleType" :rule="{
+            required: true,
+            type: 'number',
+            message: '请输入样式类型',
+            trigger: ['input', 'blur'],
+          }"
+        >
           <n-select v-model:value="modalForm.styleType" :options="styleEnum" clearable filterable tag />
         </n-form-item>
         <n-tabs v-if="modalForm.translations.length > 0" type="line" animated>
           <template v-for="(lang, index) in langs" :key="lang">
             <n-tab-pane :name="lang" :tab="langLabel[lang]" :index="index">
-              <n-form-item label="名称" path="title" :rule="{
-                required: true,
-                message: '请输入名称',
-                trigger: ['input', 'blur'],
-              }">
-                <n-input :maxlength="200" show-count v-model:value="modalForm.translations.find(i => i.locale === lang).title" />
+              <n-form-item
+                label="名称" path="title" :rule="{
+                  required: true,
+                  message: '请输入名称',
+                  trigger: ['input', 'blur'],
+                }"
+              >
+                <n-input v-model:value="modalForm.translations.find(i => i.locale === lang).title" :maxlength="200" show-count />
               </n-form-item>
 
-              <n-form-item label="描述" path="description" :rule="{
-                required: false,
-                message: '请输入描述',
-                trigger: ['input', 'blur'],
-              }">
-                <n-input :maxlength="200" show-count v-model:value="modalForm.translations.find(i => i.locale === lang).description"
-                  type="textarea" />
+              <n-form-item
+                label="描述" path="description" :rule="{
+                  required: false,
+                  message: '请输入描述',
+                  trigger: ['input', 'blur'],
+                }"
+              >
+                <n-input
+                  v-model:value="modalForm.translations.find(i => i.locale === lang).description" :maxlength="200" show-count
+                  type="textarea"
+                />
               </n-form-item>
             </n-tab-pane>
           </template>

+ 101 - 63
packages/frontend/src/views/menu/list.vue

@@ -19,21 +19,25 @@
         </n-input>
       </MeQueryItem>
       <MeQueryItem label="状态" :label-width="50">
-        <n-select v-model:value="queryItems.enable" clearable :options="[
-          { label: '启用', value: 1 },
-          { label: '停用', value: 0 },
-        ]" />
+        <n-select
+          v-model:value="queryItems.enable" clearable :options="[
+            { label: '启用', value: 1 },
+            { label: '停用', value: 0 },
+          ]"
+        />
       </MeQueryItem>
     </MeCrud>
 
     <MeModal ref="modalRef" width="520px">
       <n-form ref="modalFormRef" label-placement="left" label-align="left" :label-width="95" :model="modalForm">
-        <n-form-item label="名称" path="title" :rule="{
-          required: true,
-          message: '请输入名称',
-          trigger: ['input', 'blur'],
-        }">
-          <n-input :maxlength="200" show-count v-model:value="modalForm.title" />
+        <n-form-item
+          label="名称" path="title" :rule="{
+            required: true,
+            message: '请输入名称',
+            trigger: ['input', 'blur'],
+          }"
+        >
+          <n-input v-model:value="modalForm.title" :maxlength="200" show-count />
         </n-form-item>
 
         <!-- <n-form-item label="描述" path="description" :rule="{
@@ -44,46 +48,58 @@
           <n-input v-model:value="modalForm.description" type="textarea" />
         </n-form-item> -->
         <n-form-item v-if="modalForm.level !== 0" label="封面" path="cover">
-          <n-upload accept=".jpg,.jpeg,.png" :multiple="false" :default-upload="true" list-type="image-card"
-            :custom-request="uploadCover" :max="1" :default-file-list="previewFileList" @preview="handlePreview"
-            @remove="handleCoverRemove" />
+          <n-upload
+            accept=".jpg,.jpeg,.png" :multiple="false" :default-upload="true" list-type="image-card"
+            :custom-request="uploadCover" :max="1" ::default-file-list="previewFileList" @preview="handlePreview"
+            @before-upload="beforeUpload" @remove="handleCoverRemove"
+          />
           <n-modal v-model:show="showModal" preset="card" style="width: 600px" title="">
             <img :src="previewImageUrl" style="width: 100%">
           </n-modal>
         </n-form-item>
-        <n-form-item label="分类" path="categoryId" :rule="{
-          required: modalForm.level === 0 ? false : true,
-          type: 'number',
-          trigger: ['change', 'blur'],
-          message: '请输入分类',
-        }">
-          <n-tree-select v-model:value="modalForm.categoryId" :options="allCategory" label-field="title" key-field="id"
-            placeholder="根分类" clearable />
+        <n-form-item
+          label="分类" path="categoryId" :rule="{
+            required: modalForm.level === 0 ? false : true,
+            type: 'number',
+            trigger: ['change', 'blur'],
+            message: '请输入分类',
+          }"
+        >
+          <n-tree-select
+            v-model:value="modalForm.categoryId" :options="allCategory" label-field="title" key-field="id"
+            placeholder="根分类" clearable
+          />
         </n-form-item>
 
-        <n-form-item v-if="modalForm.level === 0" label="样式类型" path="styleType" :rule="{
-          required: true,
-          type: 'number',
-          message: '请输入样式类型',
-          trigger: ['input', 'blur'],
-        }">
+        <n-form-item
+          v-if="modalForm.level === 0" label="样式类型" path="styleType" :rule="{
+            required: true,
+            type: 'number',
+            message: '请输入样式类型',
+            trigger: ['input', 'blur'],
+          }"
+        >
           <n-select v-model:value="modalForm.styleType" :options="styleEnum" clearable filterable tag />
         </n-form-item>
 
-        <n-form-item v-if="isShowOtherCol" label="其他类别" path="otherType" :rule="{
-          required: true,
-          type: 'number',
-          message: '请输入其他类别',
-          trigger: ['input', 'blur'],
-        }">
+        <n-form-item
+          v-if="isShowOtherCol" label="其他类别" path="otherType" :rule="{
+            required: true,
+            type: 'number',
+            message: '请输入其他类别',
+            trigger: ['input', 'blur'],
+          }"
+        >
           <n-select v-model:value="modalForm.otherType" :options="otherstyleEnum" clearable />
         </n-form-item>
-        <n-form-item v-if="modalForm.level !== 0" label="文章链接" path="articleId" :rule="{
-          required: false,
-          type: 'number',
-          trigger: ['change', 'blur'],
-          message: '请输入文章链接',
-        }">
+        <n-form-item
+          v-if="modalForm.level !== 0" label="文章链接" path="articleId" :rule="{
+            required: false,
+            type: 'number',
+            trigger: ['change', 'blur'],
+            message: '请输入文章链接',
+          }"
+        >
           <n-select v-model:value="modalForm.articleId" :options="allArticle" clearable filterable tag />
         </n-form-item>
 
@@ -91,36 +107,46 @@
           <n-input-number v-model:value="modalForm.grid" style="width:100%" />
         </n-form-item>
 
-        <n-form-item label="排序" path="order" :rule="{
-          type: 'number',
-          required: true,
-          message: '此为必填项',
-          trigger: ['blur', 'change'],
-        }">
+        <n-form-item
+          label="排序" path="order" :rule="{
+            type: 'number',
+            required: true,
+            message: '此为必填项',
+            trigger: ['blur', 'change'],
+          }"
+        >
           <n-input-number v-model:value="modalForm.order" />
         </n-form-item>
         <!-- {{ modalForm.translations }} -->
         <n-tabs v-if="modalForm.translations.length > 0" type="line" animated>
           <template v-for="(lang, index) in langs" :key="lang">
             <n-tab-pane :name="lang" :tab="langLabel[lang]" :index="index">
-              <n-form-item label="名称" path="title" :rule="{
-                required: true,
-                message: '请输入名称',
-                trigger: ['input', 'blur'],
-              }">
-                <n-input :maxlength="200" show-count
-                  v-model:value="modalForm.translations.find(i => i.locale === lang).title">
+              <n-form-item
+                label="名称" path="title" :rule="{
+                  required: true,
+                  message: '请输入名称',
+                  trigger: ['input', 'blur'],
+                }"
+              >
+                <n-input
+                  v-model:value="modalForm.translations.find(i => i.locale === lang).title" :maxlength="200"
+                  show-count
+                >
                   <template #password-invisible-icon />
                 </n-input>
               </n-form-item>
 
-              <n-form-item label="描述" path="description" :rule="{
-                required: false,
-                message: '请输入描述',
-                trigger: ['input', 'blur'],
-              }">
-                <n-input :maxlength="200" show-count
-                  v-model:value="modalForm.translations.find(i => i.locale === lang).description" type="textarea" />
+              <n-form-item
+                label="描述" path="description" :rule="{
+                  required: false,
+                  message: '请输入描述',
+                  trigger: ['input', 'blur'],
+                }"
+              >
+                <n-input
+                  v-model:value="modalForm.translations.find(i => i.locale === lang).description"
+                  :maxlength="200" show-count type="textarea"
+                />
               </n-form-item>
             </n-tab-pane>
           </template>
@@ -233,10 +259,10 @@ const columns = [
     key: 'cover',
     render: row => row.cover
       ? h(NImage, {
-        src: row.cover,
-        height: 60,
-        width: 80,
-      })
+          src: row.cover,
+          height: 60,
+          width: 80,
+        })
       : null,
   },
   { title: '创建人', key: 'user.username' },
@@ -334,6 +360,8 @@ onMounted(() => {
   getMenuDetail()
 })
 
+const MAX_FILE_SIZE = 5 * 1024 * 1024
+
 async function uploadCover({ file }) {
   const data = new FormData()
   data.append('file', file.file)
@@ -416,4 +444,14 @@ function getAllType() {
     value: +item.id,
   }))))
 }
+
+function beforeUpload({ file }) {
+  if (file.file.size > MAX_FILE_SIZE) {
+    $message.error('文件大小不能超过 5MB')
+    return false
+  }
+  else {
+    return true
+  }
+}
 </script>

+ 5 - 0
packages/frontend/src/views/profile/api.js

@@ -3,4 +3,9 @@ import { request } from '@/utils'
 export default {
   changePassword: data => request.post('/auth/password', data),
   updateProfile: data => request.patch(`/user/profile/${data.id}`, data),
+  uploadImage: data => request.post('/menu/cover/upload', data, {
+    headers: {
+      'Content-Type': 'multipart/form-data',
+    },
+  }),
 }

+ 65 - 3
packages/frontend/src/views/profile/index.vue

@@ -21,7 +21,7 @@
             </n-button>
           </div>
           <div class="mt-16 flex items-center">
-            <n-button type="primary" ghost @click="avatarModalRef.open()">
+            <n-button type="primary" ghost @click="handleAvatarOpen">
               更改头像
             </n-button>
             <span class="ml-12 opacity-60">
@@ -62,7 +62,15 @@
     </n-card>
 
     <MeModal ref="avatarModalRef" width="420px" title="更改头像" @ok="handleAvatarSave()">
-      <n-input v-model:value="newAvatar" />
+      <!-- <n-input v-model:value="newAvatar" /> -->
+      <n-upload
+        accept=".jpg,.jpeg,.png" :multiple="false" :default-upload="true" list-type="image-card"
+        :custom-request="uploadCover" :max="1" :default-file-list="previewFileList" @preview="handlePreview"
+        @before-upload="beforeUpload" @remove="handleCoverRemove"
+      />
+      <n-modal v-model:show="showModal" preset="card" style="width: 600px" title="">
+        <img :src="previewImageUrl" style="width: 100%">
+      </n-modal>
     </MeModal>
 
     <MeModal ref="pwdModalRef" title="修改密码" width="420px" @ok="handlePwdSave()">
@@ -84,7 +92,7 @@
     <MeModal ref="profileModalRef" title="修改资料" width="420px" @ok="handleProfileSave()">
       <n-form ref="profileFormRef" :model="profileForm" label-placement="left">
         <n-form-item label="昵称" path="nickName">
-          <n-input :maxlength="10" show-count v-model:value="profileForm.nickName" placeholder="请输入昵称" />
+          <n-input v-model:value="profileForm.nickName" :maxlength="10" show-count placeholder="请输入昵称" />
         </n-form-item>
         <n-form-item label="性别" path="gender">
           <n-select
@@ -121,6 +129,10 @@ const required = {
 const [pwdModalRef] = useModal()
 const [pwdFormRef, pwdForm, pwdValidation] = useForm()
 
+const previewFileList = ref([])
+const previewImageUrl = ref('')
+const showModal = ref(false)
+
 async function handlePwdSave() {
   await pwdValidation()
   await api.changePassword(pwdForm.value)
@@ -164,4 +176,54 @@ async function refreshUserInfo() {
   const user = await getUserInfo()
   userStore.setUser(user)
 }
+
+const MAX_FILE_SIZE = 5 * 1024 * 1024
+
+async function uploadCover({ file }) {
+  const data = new FormData()
+  data.append('file', file.file)
+  const res = await api.uploadImage(data)
+  newAvatar.value = res.data
+  file.url = res.data
+  file.thumbnailUrl = res.data
+  previewFileList.value = [{
+    id: '0',
+    status: 'finished',
+    url: res.data,
+  }]
+}
+function handleCoverRemove() {
+  newAvatar.value = ''
+  previewFileList.value = []
+  previewImageUrl.value = ''
+}
+function handlePreview(file) {
+  const { url } = file
+  previewImageUrl.value = url
+  showModal.value = true
+}
+
+function beforeUpload({ file }) {
+  if (file.file.size > MAX_FILE_SIZE) {
+    $message.error('文件大小不能超过 5MB')
+    return false
+  }
+  else {
+    return true
+  }
+}
+
+function handleAvatarOpen() {
+  if (avatarModalRef.value) {
+    avatarModalRef.value.open()
+  }
+  if (newAvatar.value) {
+    previewFileList.value = [{
+      id: '0',
+      status: 'finished',
+      url: newAvatar.value,
+    }]
+    console.log('previewFileList', previewFileList)
+  }
+}
 </script>

+ 1 - 1
pnpm-lock.yaml

@@ -219,7 +219,7 @@ importers:
         specifier: ^4.17.21
         version: 4.17.21
       naive-ui:
-        specifier: ^2.40.3
+        specifier: ^2.41.0
         version: 2.41.0(vue@3.5.13(typescript@5.7.3))
       pinia:
         specifier: ^2.3.0