瀏覽代碼

feat: save

gemercheung 6 月之前
父節點
當前提交
f77fda5ff1

+ 1 - 1
packages/backend/src/modules/article/article.service.ts

@@ -18,7 +18,7 @@ export class ArticleService {
 
     @InjectRepository(ArticleTranslation)
     private articleTranslationRepo: Repository<ArticleTranslation>,
-  ) { }
+  ) {}
   async create(createArticleDto: CreateArticleDto) {
     const article = this.articleRepo.create(createArticleDto);
     return this.articleRepo.save(article);

+ 1 - 1
packages/backend/src/modules/category/category.module.ts

@@ -9,4 +9,4 @@ import { CategoryTranslation } from './category.entity.translation';
   controllers: [CategoryController],
   providers: [CategoryService],
 })
-export class CategoryModule { }
+export class CategoryModule {}

+ 1 - 1
packages/backend/src/modules/category/category.service.ts

@@ -11,7 +11,7 @@ export class CategoryService {
     @InjectRepository(Category)
     private categoryRepo: Repository<Category>,
     private readonly sharedService: SharedService,
-  ) { }
+  ) {}
 
   async create(createCategoryDto: CreateCategoryDto) {
     const category = this.categoryRepo.create(createCategoryDto);

+ 1 - 3
packages/backend/src/modules/category/dto.ts

@@ -61,8 +61,6 @@ export class GetCategoryDto {
   @ApiProperty({ required: false })
   @Allow()
   enable?: boolean;
-
-
 }
 export class CreateCategoryTranslations {
   @ApiProperty({ required: false })
@@ -89,4 +87,4 @@ export class GetAllCategoryDto {
   enable?: boolean;
 }
 
-export class UpdateCategoryDto extends PartialType(CreateCategoryDto) { }
+export class UpdateCategoryDto extends PartialType(CreateCategoryDto) {}

+ 5 - 0
packages/backend/src/modules/menu/dto.ts

@@ -70,6 +70,11 @@ export class CreateMenuDto {
   @IsOptional()
   styleType?: number;
 
+  @ApiProperty({ nullable: true, required: false, default: 0 })
+  @IsNumber()
+  @IsOptional()
+  otherType?: number;
+
   @ApiProperty({ required: false, default: 0 })
   @IsNumber()
   @IsOptional()

+ 3 - 0
packages/backend/src/modules/menu/menu.entity.ts

@@ -31,6 +31,9 @@ export class Menu extends TranslatableEntity<MenuTranslation> {
   styleType: number;
 
   @Column({ default: 0 })
+  otherType: number;
+
+  @Column({ default: 0 })
   order: number;
 
   @Column({ default: '' })

+ 1 - 1
packages/backend/src/modules/menu/menu.service.ts

@@ -13,7 +13,7 @@ export class MenuService {
 
     @InjectRepository(MenuTranslation)
     private menuTranslation: Repository<MenuTranslation>,
-  ) { }
+  ) {}
 
   async create(createMenuDto: CreateMenuDto) {
     const menu = this.menuRepo.create(createMenuDto);

+ 1 - 0
packages/backend/src/modules/web/web.service.ts

@@ -24,6 +24,7 @@ export class WebService {
     const menus = await this.menuRepo.find({
       where: {
         enable: true,
+        isPublish: true,
       },
       order: {
         order: 'ASC',

+ 11 - 0
packages/frontend/src/utils/enum.js

@@ -20,3 +20,14 @@ export const styleEnum = [
     label: '其他',
   },
 ]
+
+export const otherstyleEnum = [
+  {
+    value: 0,
+    label: '开发者',
+  },
+  {
+    value: 1,
+    label: '推荐阅读',
+  },
+]

+ 1 - 1
packages/frontend/src/views/article/edit.vue

@@ -1,5 +1,5 @@
 <template>
-  <CommonPage back show-footer>
+  <CommonPage show-footer back>
     <template #action>
       <NButton type="primary" @click="handleEdit">
         保存文章

+ 29 - 21
packages/frontend/src/views/category/index.vue

@@ -14,20 +14,24 @@
         </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-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="parentId">
@@ -37,22 +41,26 @@
           <n-input v-model:value="modalForm.remark" />
         </n-form-item>
         <!-- {{ modalForm.translations }} -->
-        <n-tabs type="line" v-if="modalForm.translations.length > 0" animated>
+        <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-form-item
+                label="名称" path="title" :rule="{
+                  required: true,
+                  message: '请输入名称',
+                  trigger: ['input', 'blur'],
+                }"
+              >
                 <n-input v-model:value="modalForm.translations[index].title" />
               </n-form-item>
 
-              <n-form-item label="备注" path="remark" :rule="{
-                required: false,
-                message: '请输入备注',
-                trigger: ['input', 'blur'],
-              }">
+              <n-form-item
+                label="备注" path="remark" :rule="{
+                  required: false,
+                  message: '请输入备注',
+                  trigger: ['input', 'blur'],
+                }"
+              >
                 <n-input v-model:value="modalForm.translations[index].remark" type="textarea" />
               </n-form-item>
             </n-tab-pane>
@@ -79,9 +87,9 @@ import { MeCrud, MeModal, MeQueryItem } from '@/components'
 import { useCrud } from '@/composables'
 import { useUserStore } from '@/store/index.js'
 import { formatDateTime } from '@/utils'
+import { initTranslations, langLabel, langs } from '@/utils/translations'
 import { NButton, NSwitch } from 'naive-ui'
 import { onMounted, watchEffect } from 'vue'
-import { initTranslations, langLabel, langs } from '@/utils/translations'
 import api from './api'
 
 defineOptions({ name: 'Category' })

+ 20 - 17
packages/frontend/src/views/menu/index.vue

@@ -84,23 +84,27 @@
             v-model:value="modalForm.styleType" :options="styleEnum" clearable filterable tag
           />
         </n-form-item>
-        <n-tabs type="line" v-if="modalForm.translations.length > 0" animated>
+        <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 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" />
               </n-form-item>
 
-              <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" 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" type="textarea" />
               </n-form-item>
             </n-tab-pane>
           </template>
@@ -137,8 +141,8 @@
 import { MeModal } from '@/components'
 import { useCrud } from '@/composables'
 import { useUserStore } from '@/store/index.js'
-import { initTranslations, langLabel, langs } from '@/utils/translations'
 import { styleEnum } from '@/utils/enum.js'
+import { initTranslations, langLabel, langs } from '@/utils/translations'
 import { onMounted } from 'vue'
 import { useRouter } from 'vue-router'
 import MenuApi from './api.js'
@@ -158,9 +162,8 @@ const { modalRef, modalFormRef, modalAction, modalForm, handleAdd, handleDelete,
     doCreate: MenuApi.create,
     doDelete: MenuApi.delete,
     doUpdate: MenuApi.update,
-    initForm: { enable: true, isPublish: true,  
-    translations: initTranslations({}, ['title', 'remark']).translations
-   },
+    initForm: { enable: true, isPublish: true, translations: initTranslations({}, ['title', 'remark']).translations,
+    },
     refresh: (_, keepCurrentPage) => $table.value?.handleSearch(keepCurrentPage),
   })
 onMounted(() => {

+ 18 - 7
packages/frontend/src/views/menu/list.vue

@@ -69,6 +69,15 @@
           <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-select v-model:value="modalForm.otherType" :options="otherstyleEnum" clearable  />
+        </n-form-item>
         <n-form-item label="文章链接" path="articleId" :rule="{
           required: false,
           type: 'number',
@@ -91,17 +100,16 @@
           <n-input-number v-model:value="modalForm.order" />
         </n-form-item>
         <!-- {{ modalForm.translations }} -->
-        <n-tabs type="line" v-if="modalForm.translations.length > 0" animated>
+        <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="{
+              <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" >
-                  <template #password-invisible-icon></template>
+                <n-input v-model:value="modalForm.translations.find(i => i.locale === lang).title">
+                  <template #password-invisible-icon />
                 </n-input>
               </n-form-item>
 
@@ -110,7 +118,8 @@
                 message: '请输入描述',
                 trigger: ['input', 'blur'],
               }">
-                <n-input v-model:value="modalForm.translations.find(i=>i.locale===lang).description" type="textarea" />
+                <n-input v-model:value="modalForm.translations.find(i => i.locale === lang).description"
+                  type="textarea" />
               </n-form-item>
             </n-tab-pane>
           </template>
@@ -146,7 +155,7 @@ import { CommonPage } from '@/components/index.js'
 import { useCrud } from '@/composables'
 import { useUserStore } from '@/store/index.js'
 import { formatDateTime } from '@/utils'
-import { styleEnum } from '@/utils/enum.js'
+import { otherstyleEnum, styleEnum } from '@/utils/enum.js'
 import { initTranslations, langLabel, langs } from '@/utils/translations'
 import { NButton, NImage, NSwitch } from 'naive-ui'
 import { ref, watchEffect } from 'vue'
@@ -176,6 +185,7 @@ const detail = ref({
 const queryItems = ref({
   parentId: route.params.id,
 })
+const isShowOtherCol = ref(false)
 
 // const modalForm = ref({})
 
@@ -209,6 +219,7 @@ async function getMenuDetail() {
   if (data) {
     console.log('data', data)
     detail.value = data
+    isShowOtherCol.value = data.styleType === 4
   }
   getAllType()
 }

+ 1 - 0
packages/web/src/api/menu.ts

@@ -8,6 +8,7 @@ export type MenuItem = {
   children: MenuItem[]
   grid: number
   styleType: number
+  otherType: number
   description: string
 }
 export type CategoryItem = {

+ 2 - 1
packages/web/src/locales/json/en.json

@@ -27,5 +27,6 @@
   "distributor": "Be Our Distributor",
   "publish": "Publish At",
   "main_content": "Main Content",
-  "read": "Read"
+  "read": "Read",
+   "more":"More"
 }

+ 2 - 1
packages/web/src/locales/json/zh.json

@@ -28,5 +28,6 @@
   "distributor": "成为经销商",
   "publish":"发布于",
   "main_content":"主要内容",
-  "read":"阅读"
+  "read":"阅读",
+  "more":"更多"
 }

+ 102 - 47
packages/web/src/pages/index.vue

@@ -7,18 +7,13 @@
         <template v-if="item.styleType === 0">
           <n-grid x-gap="100" y-gap="100" :cols="2">
             <n-gi v-for="child of item.children" :key="child.id">
-              <div
-                class="show-item b-rd-3xl relative"
-                :class="{ [`style-${item.styleType}`]: true }"
+              <div class="show-item b-rd-3xl relative" :class="{ [`style-${item.styleType}`]: true }"
                 :style="{ backgroundImage: `url(${child.cover})` }"
-                @click="handleToDoc(child as any as ArticleDetailType)"
-              >
+                @click="handleToDoc(child as any as ArticleDetailType)">
                 <div
-                  class="w-full h-full flex flex-col absolute top-0 left-0 -mx-auto justify-end overflow-hidden shadow-blueGray"
-                >
+                  class="w-full h-full flex flex-col absolute top-0 left-0 -mx-auto justify-end overflow-hidden shadow-blueGray">
                   <div
-                    class="w-full h-[60px] title text-black font-size-[20px] flex justify-center items-center bg-white bg-op-50"
-                  >
+                    class="w-full h-[60px] title text-black font-size-[20px] flex justify-center items-center bg-white bg-op-50">
                     {{ child.title }}
                   </div>
                 </div>
@@ -28,31 +23,19 @@
         </template>
 
         <template v-if="item.styleType === 1">
-          <swiper
-            :slides-per-view="item.grid"
-            :space-between="50"
-            @swiper="onSwiper"
-            @slideChange="onSlideChange"
-          >
-            <swiper-slide
-              v-for="child of item.children"
-              :key="child.id"
-              @click="handleToDoc(child as any as ArticleDetailType)"
-            >
-              <div
-                class="cover w-full h-[400px] overflow-hidden b-rd-xl"
-                :style="{ backgroundImage: `url(${child.cover})` }"
-              ></div>
+          <swiper :slides-per-view="item.grid" :space-between="50" @swiper="onSwiper" @slideChange="onSlideChange">
+            <swiper-slide v-for="child of item.children" :key="child.id"
+              @click="handleToDoc(child as any as ArticleDetailType)">
+              <div class="cover w-full h-[400px] overflow-hidden b-rd-xl"
+                :style="{ backgroundImage: `url(${child.cover})` }"></div>
               <div
-                class="w-full h-full flex flex-col absolute top-0 left-0 -mx-auto justify-end overflow-hidden shadow-blueGray"
-              >
+                class="w-full h-full flex flex-col absolute top-0 left-0 -mx-auto justify-end overflow-hidden shadow-blueGray">
                 <div
-                  class="w-full h-[50px] title text-black font-size-[20px] flex justify-center items-center bg-white bg-op-50"
-                >
+                  class="w-full h-[50px] title text-black font-size-[20px] flex justify-center items-center bg-white bg-op-50">
                   {{ child.title }}
                 </div>
               </div>
-           
+
             </swiper-slide>
             <!-- <div class="swiper-button-prev">
               <button @click="swiperInstance.slidePrev()">Prev</button>
@@ -66,27 +49,18 @@
         <template v-if="item.styleType === 2">
           <n-grid x-gap="100" y-gap="100" :cols="2">
             <n-gi v-for="child of item.children" :key="child.id">
-              <div
-                @click="handleToDoc(child as any as ArticleDetailType)"
-                :class="{ [`style-${item.styleType}`]: true }"
-                class="show-item b-rd-3xl relative w-full h-full"
-              >
-                <img
-                  :src="child.cover"
-                  alt=""
-                  class="absolute w-[128px] h-[128px] left-0 top-[-40px]"
-                />
+              <div @click="handleToDoc(child as any as ArticleDetailType)"
+                :class="{ [`style-${item.styleType}`]: true }" class="show-item b-rd-3xl relative w-full h-full">
+                <img :src="child.cover" alt="" class="absolute w-[128px] h-[128px] left-0 top-[-40px]" />
 
                 <div class="pl-[128px] w-[calc(100%-128px)] overflow-hidden">
                   <div
-                    class="font-size-[20px] whitespace-nowrap text-ellipsis font-bold mt-[25px] w-[calc(100%-20px)] overflow-hidden"
-                  >
+                    class="font-size-[20px] whitespace-nowrap text-ellipsis font-bold mt-[25px] w-[calc(100%-20px)] overflow-hidden">
                     {{ child.title }}
                   </div>
 
                   <div
-                    class="text-size-base whitespace-nowrap text-ellipsis w-[calc(100%-20px)] overflow-hidden color-[#909090]"
-                  >
+                    class="text-size-base whitespace-nowrap text-ellipsis w-[calc(100%-20px)] overflow-hidden color-[#909090]">
                     {{ child.description }}
                   </div>
                 </div>
@@ -98,6 +72,41 @@
         <template v-if="item.styleType === 3">
           <n-grid x-gap="100" y-gap="100" :cols="3">
             <n-gi v-for="child of item.children" :key="child.id">
+              <div @click="handleToDoc(child as any as ArticleDetailType)"
+                :class="{ [`style-${item.styleType}`]: true }"
+                class="show-item b-rd-3xl relative w-full h-full flex flex-col align-center items-center justify-center">
+                <img :src="child.cover" alt="" class="w-[40px] h-[40px]" />
+
+                <div class="font-size-[20px] font-bold my-[16px]">{{ child.title }}</div>
+
+                <div class="color-[#909090] w-[calc(100%-30px)] text-center">
+                  {{ child.description }}
+                </div>
+              </div>
+            </n-gi>
+          </n-grid>
+        </template>
+        <template v-if="item.styleType === 4">
+          <!-- {{ otherstyleEnum }} -->
+          <n-grid x-gap="100" y-gap="20" :cols="2">
+            <n-gi v-for="other of otherstyleEnum" :key="other.value">
+
+              <div :class="{ [`style-${item.styleType}`]: true }" class="show-item">
+                <n-h5 class="font-size-[16px] font-800"> {{ other.label }}</n-h5>
+                <ul class="otherList">
+                  <li class="font-size-[14px]" @click="handleToDoc(child as any as ArticleDetailType)"
+                    v-for="child of item.children.filter(c => c.otherType === other.value).slice(0,3)" :key="child.id">
+                    <span>{{ child.title }}</span>
+                  </li>
+                  <li v-if="item.children.filter(c => c.otherType === other.value).length > 3" class="font-size-[14px] more" @click="handleToMore">
+                    <span class="font-medium">{{ $t('more') }}>></span>
+                  </li>
+
+                </ul>
+              </div>
+
+            </n-gi>
+            <!-- <n-gi v-for="child of item.children" :key="child.id">
               <div
                 @click="handleToDoc(child as any as ArticleDetailType)"
                 :class="{ [`style-${item.styleType}`]: true }"
@@ -111,9 +120,10 @@
                   {{ child.description }}
                 </div>
               </div>
-            </n-gi>
+            </n-gi> -->
           </n-grid>
         </template>
+
       </template>
     </div>
   </div>
@@ -128,7 +138,7 @@ layout: "default"
 }
 </route>
 <script setup lang="ts">
-import { NH1, NGrid, NGi } from 'naive-ui'
+import { NH1, NH5, NGrid, NGi } from 'naive-ui'
 import { getMenuList, type ArticleDetailType } from '@/api'
 import type { MenuItem } from '@/api'
 import { Swiper, SwiperSlide } from 'swiper/vue'
@@ -136,8 +146,19 @@ import 'swiper/css'
 import router from '@/plugins/router.ts'
 // import { useSwiper } from 'swiper/vue';
 
+const otherstyleEnum = [
+  {
+    value: 0,
+    label: '开发者',
+  },
+  {
+    value: 1,
+    label: '推荐阅读',
+  },
+]
+
 const list = ref<MenuItem[]>([])
-const swiperInstance =ref();
+const swiperInstance = ref();
 getMenuList().then((data) => {
   if (data.data) {
     list.value = data.data
@@ -148,7 +169,7 @@ const onSwiper = (swiper: unknown) => {
   console.log(swiper)
   swiperInstance.value = swiper
 }
-const onSlideChange = (index:number) => {
+const onSlideChange = (index: number) => {
   console.log('slide change', index)
 }
 
@@ -161,6 +182,9 @@ const handleToDoc = (child: ArticleDetailType) => {
     // router.push({ path: '/showdoc', query: { cid: categoryId } })
   }
 }
+const handleToMore = () => {
+
+}
 </script>
 
 <style lang="scss" scoped>
@@ -199,5 +223,36 @@ const handleToDoc = (child: ArticleDetailType) => {
     box-shadow: 0px 0px 10px 0px rgba(6, 97, 201, 0.2);
     border-radius: 10px 10px 10px 10px;
   }
+
+  &.style-4 {
+    width: 482px;
+    min-height: 200px;
+    background: #F5F9FF;
+    border-radius: 10px 10px 10px 10px;
+    padding: 30px 64px;
+    cursor: default;
+  }
+
+  .otherList {
+    padding: 0;
+
+    li {
+      padding: 5px 0;
+      text-decoration: none;
+      list-style: none;
+
+      &:hover {
+        cursor: pointer;
+      }
+
+      &.more {
+        color: #0661C9;
+
+        &:hover {
+          color: #107bf5;
+        }
+      }
+    }
+  }
 }
 </style>