Forráskód Böngészése

add:上传banner管理页面

zlong 1 éve
szülő
commit
b455930798

+ 1 - 1
.env

@@ -1,3 +1,3 @@
 VITE_ENABLE_MOCK=false
 VITE_API_PREFIX=/api
-VITE_PROXY_ENDPOINT=http://localhost:8000
+VITE_PROXY_ENDPOINT=https://bright.yilibili.com/api/

+ 1 - 1
.env.development

@@ -1 +1 @@
-VITE_ENABLE_MOCK=true
+VITE_ENABLE_MOCK=false

+ 0 - 5
components.d.ts

@@ -8,7 +8,6 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     Copyright: typeof import('./src/components/Copyright.vue')['default']
-    Dialog: typeof import('./src/components/Dialog.vue')['default']
     RegularPage: typeof import('./src/components/RegularPage.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
@@ -17,7 +16,6 @@ declare module 'vue' {
     TButton: typeof import('tdesign-vue-next')['Button']
     TCard: typeof import('tdesign-vue-next')['Card']
     TCheckbox: typeof import('tdesign-vue-next')['Checkbox']
-    TCol: typeof import('tdesign-vue-next')['Col']
     TDialog: typeof import('tdesign-vue-next')['Dialog']
     TDropdown: typeof import('tdesign-vue-next')['Dropdown']
     TForm: typeof import('tdesign-vue-next')['Form']
@@ -29,9 +27,6 @@ declare module 'vue' {
     TLink: typeof import('tdesign-vue-next')['Link']
     TMenu: typeof import('tdesign-vue-next')['Menu']
     TMenuItem: typeof import('tdesign-vue-next')['MenuItem']
-    TPagination: typeof import('tdesign-vue-next')['Pagination']
-    TRow: typeof import('tdesign-vue-next')['Row']
-    TSelect: typeof import('tdesign-vue-next')['Select']
     TSpace: typeof import('tdesign-vue-next')['Space']
     TTable: typeof import('tdesign-vue-next')['Table']
     TTag: typeof import('tdesign-vue-next')['Tag']

+ 30 - 0
src/api/carousal.ts

@@ -0,0 +1,30 @@
+import httpClient from './httpClient'
+import type { CreateCarousalsRequest, GetCarousalsRequest } from '@/model/carousals'
+import type { UnwrapNestedRefs } from 'vue'
+
+export const createCarousal = (createCarousalsRequest: UnwrapNestedRefs<{ size: number; page: number }> & {}) => {
+  return httpClient.post('/carousals', createCarousalsRequest)
+}
+
+export const getCarousalList = (getCarousalsRequest:GetCarousalsRequest) => {
+  return httpClient.get('/carousals',getCarousalsRequest)
+}
+export const getCarousalItem = (id:string) => {
+  return httpClient.get(`/carousals/${id}`)
+}
+
+export const updateCarousalItem = (id:number,updateCarousalsRequest: CreateCarousalsRequest) => {
+  return httpClient.put(`/carousals/${id}`,updateCarousalsRequest)
+}
+
+export const deleteCarousal = (id:number) => {
+  return httpClient.delete(`/carousals/${id}`)
+}
+
+export const activeCarousal = (id:number) => {
+  return httpClient.put(`/carousals/${id}/active`)
+}
+
+export const inactiveCarousal = (id:number) => {
+  return httpClient.put(`/carousals/${id}/inactive`)
+}

+ 12 - 0
src/model/carousals.ts

@@ -0,0 +1,12 @@
+export interface CreateCarousalsRequest {
+  imageUrl:string,
+  targetType:string,
+  targetUrl:string,
+  targetId:string
+}
+export interface GetCarousalsRequest {
+  page:string,
+  size:string,
+  order?:string
+}
+

+ 1 - 1
src/model/token.ts

@@ -1,4 +1,4 @@
 export interface CreateTokenRequest {
-  email: string
+  username: string
   password: string
 }

+ 148 - 0
src/pages/carousal/components/CarousalDialog.vue

@@ -0,0 +1,148 @@
+<template>
+  <t-dialog
+    width="40%"
+    :header="header"
+    :confirmBtn="confirmBtn"
+    :closeOnOverlayClick="false"
+    @close="handleCloseDialog"
+    @confirm="fetchSaveClassifyData"
+  >
+    <t-space direction="vertical" style="width: 100%">
+      <TForm ref="form" :data="classifyData" :rules="rules" resetType="initial">
+        <TFormItem label="分类名称:" name="name" help="名称长度小于5个汉字,大于2个汉字">
+          <t-input v-model.trim="classifyData.name" clearable placeholder="请输入分类名称" />
+        </TFormItem>
+        <TFormItem label="分类编号:" name="numbering" help="编号由大于4个字符的英文字母组成">
+          <t-input v-model.trim="classifyData.numbering" clearable placeholder="请输入分类编号" />
+        </TFormItem>
+        <TFormItem label="排序级别:" name="sort" help="排序级别由正整数组成">
+          <t-input v-model.trim="classifyData.sort" clearable placeholder="请输入排序级别" />
+        </TFormItem>
+      </TForm>
+    </t-space>
+  </t-dialog>
+</template>
+<script lang="ts" setup>
+import type { Classify } from '@/model/classify'
+import { computed, ref, reactive, watch } from 'vue'
+import type { FormInstanceFunctions, FormProps } from 'tdesign-vue-next'
+const props = defineProps<{
+  isEdit?: Boolean | null
+  headerTitle?: String | null
+  classify: Classify | {}
+}>()
+const form = ref<FormInstanceFunctions | null>(null)
+const header = computed(() => `${props.isEdit ? '编辑' : '创建'}${props.headerTitle || ''}`)
+const confirmBtn = computed(() => (props.isEdit ? '保存' : '确定'))
+const classifyData: FormProps['data'] = reactive({ name: '', sort: 0 })
+const rules: FormProps['rules'] = {
+  name: [
+    {
+      required: true,
+      message: '分类名称不能为空',
+      type: 'error',
+      trigger: 'blur'
+    },
+    {
+      required: true,
+      message: '分类名称不能为空',
+      type: 'error',
+      trigger: 'change'
+    },
+    {
+      whitespace: true,
+      message: '分类名称不能为空'
+    },
+    {
+      min: 4,
+      message: '输入汉字长度应在2到5之间',
+      type: 'error',
+      trigger: 'blur'
+    },
+    {
+      max: 10,
+      message: '输入汉字长度应在2到5之间',
+      type: 'error',
+      trigger: 'blur'
+    }
+  ],
+  numbering: [
+    {
+      required: true,
+      message: '分类编号不能为空',
+      type: 'error',
+      trigger: 'blur'
+    },
+    {
+      required: true,
+      message: '分类编号不能为空',
+      type: 'error',
+      trigger: 'change'
+    },
+    {
+      whitespace: true,
+      message: '分类编号不能为空'
+    },
+    {
+      min: 4,
+      message: '输入字符应大于4个字符长度',
+      type: 'error',
+      trigger: 'blur'
+    },
+    {
+      pattern: /^[A-Za-z]+$/,
+      message: '输入字符须为英文字母',
+      type: 'error',
+      trigger: 'blur'
+    }
+  ],
+  sort: [
+    {
+      required: true,
+      message: '排序级别不能为空',
+      type: 'error',
+      trigger: 'blur'
+    },
+    {
+      required: true,
+      message: '排序级别不能为空',
+      type: 'error',
+      trigger: 'change'
+    },
+    {
+      whitespace: true,
+      message: '排序级别不能为空'
+    },
+    {
+      pattern: /^[1-9]\d*$/,
+      message: '排序级别必须为正整数',
+      type: 'error',
+      trigger: 'blur'
+    }
+  ]
+}
+watch(
+  () => props.classify,
+  (newClassify) => {
+    Object.assign(classifyData, newClassify)
+  },
+  { deep: true }
+)
+const fetchSaveClassifyData = async () => {
+  /* TODO: 保存分类数据 */
+  if (form.value) {
+    const valid = await form.value.validate()
+    if (valid && typeof valid === 'boolean') {
+      /* TODO: 校验通过保存分类数据 */
+      return
+    }
+  }
+}
+const handleCloseDialog = () => {
+  // 数据&&规则校验结果重置
+  if (form.value) {
+    form.value.reset()
+    form.value.clearValidate()
+  }
+}
+</script>

+ 105 - 0
src/pages/carousal/index.vue

@@ -0,0 +1,105 @@
+<script setup lang="ts">
+import RegularPage from '@/components/RegularPage.vue'
+import { onMounted, ref, reactive } from 'vue'
+import type { BaseTableColumns } from 'tdesign-vue-next'
+import type { Classify } from '@/model/classify'
+import ClassifyDialog from '@/pages/carousal/components/CarousalDialog.vue'
+import { createCarousal } from '@/api/carousal'
+
+const columns: BaseTableColumns = [
+  {
+    title: 'ID',
+    colKey: 'id',
+    width: 100
+  },
+  {
+    title: '轮播图',
+    colKey: 'imageUrl',
+    width: 100
+  },
+  {
+    title: '显示状态',
+    colKey: 'targetType',
+    width: 100
+  },
+  {
+    title: '操作',
+    colKey: 'operation',
+    width: 100
+  }
+]
+const loading = ref(false)
+const data = ref([])
+const pagination = reactive({
+  page: 1,
+  size: 10
+})
+const classifyDialogVisible = ref(false)
+const isEdit = ref(false)
+const currentTableData = ref<Classify | {}>({})
+const fetchClassifyTableData = () => {
+  /* TODO: 获取轮播图表格数据 */
+  const createCarousalApi = createCarousal(pagination)
+  console.log(createCarousalApi,'api')
+}
+onMounted(fetchClassifyTableData)
+const editClick = (classify: Classify) => {
+  isEdit.value = true
+  currentTableData.value = classify
+  console.log(classify)
+
+  classifyDialogVisible.value = true
+}
+const onPageChange = () => {
+  /* TODO: 分页切换 */
+}
+</script>
+
+<template>
+  <RegularPage title="轮播图管理">
+    <template #right-area>
+      <TButton @click="classifyDialogVisible = true">创建轮播图</TButton>
+    </template>
+    <TTable
+      class="w-full h-full"
+      row-key="id"
+      height="92%"
+      table-layout="auto"
+      :data="data"
+      :loading="loading"
+      :columns="columns"
+      cell-empty-content="-"
+      :paginationAffixedBottom="true"
+      :pagination="{
+        total: pagination.total,
+        current: pagination.page,
+        pageSize: pagination.size,
+        onChange: onPageChange
+      }"
+    >
+      <template #roles="{ row }">
+        <TSpace>
+          <TTag v-for="role in row.roles" :key="role.id" theme="success" variant="light">{{
+            role.label
+          }}</TTag>
+        </TSpace>
+      </template>
+      <template #operation="{ row }">
+        <TButton variant="text" size="small" theme="primary" @click="editClick(row)">编辑</TButton>
+      </template>
+    </TTable>
+    <ClassifyDialog
+      :classify="currentTableData"
+      v-model:visible="classifyDialogVisible"
+      :isEdit="isEdit"
+      headerTitle="分类"
+    ></ClassifyDialog>
+  </RegularPage>
+</template>
+
+<style scoped>
+:deep(.t-card__body) {
+  flex: 1;
+  min-height: 0;
+}
+</style>

+ 4 - 4
src/pages/common/login.vue

@@ -8,8 +8,8 @@ import { MessagePlugin } from 'tdesign-vue-next'
 import { useAppStore } from '@/stores/app'
 
 const loginForm = reactive<CreateTokenRequest>({
-  password: '',
-  email: ''
+  username: 'admin',
+  password: 'admin123'
 })
 
 const canRegister = ref(false)
@@ -18,7 +18,7 @@ const loading = ref(false)
 const appStore = useAppStore()
 
 const handleLogin = async () => {
-  if (!loginForm.email || !loginForm.password) {
+  if (!loginForm.username || !loginForm.password) {
     MessagePlugin.error('用户名或密码不能为空')
     return
   }
@@ -40,7 +40,7 @@ const handleLogin = async () => {
         买有账号?<TLink>注册新账号</TLink>
       </div>
       <div class="flex flex-col gap-6 mb-11">
-        <TInput size="large" placeholder="用户名/邮箱" v-model="loginForm.email" clearable >
+        <TInput size="large" placeholder="用户名/邮箱" v-model="loginForm.username" clearable >
           <template #prefix-icon>
             <UserIcon></UserIcon>
           </template>

+ 17 - 8
src/router/index.ts

@@ -14,6 +14,15 @@ export const asyncRoutes: RouteRecordRaw[] = [
       icon: 'system-3'
     }
   },
+  {
+    name: 'carousal',
+    path: 'carousal',
+    component: () => import('@/pages/carousal/index.vue'),
+    meta: {
+      title: '轮播图管理',
+      icon: 'system-3'
+    }
+  },
 ]
 
 const router = createRouter({
@@ -36,13 +45,13 @@ const router = createRouter({
   ]
 })
 
-router.beforeEach((to, from, next) => {
-  if (to.name !== 'login') {
-    const appStore = useAppStore()
-    appStore.isLogin ? next() : next({ name: 'login' })
-    return
-  }
-  next()
-})
+// router.beforeEach((to, from, next) => {
+//   if (to.name !== 'login') {
+//     const appStore = useAppStore()
+//     appStore.isLogin ? next() : next({ name: 'login' })
+//     return
+//   }
+//   next()
+// })
 
 export default router