xiongzhu 2 سال پیش
والد
کامیت
01881ca9d4

+ 2 - 1
.env.development

@@ -1,3 +1,4 @@
 VITE_BASE_URL=/
 VITE_API_BASE_URL=http://localhost:3333/api
-VITE_WS_URL=ws://localhost:3333
+VITE_WS_URL=ws://localhost:3333
+VITE_IMAGE_PREFIX=https://zm-shorts.oss-cn-hangzhou.aliyuncs.com

+ 2 - 1
.env.production

@@ -1,3 +1,4 @@
 VITE_BASE_URL=/admin/
 VITE_API_BASE_URL=/api
-VITE_WS_URL=/
+VITE_WS_URL=/
+VITE_IMAGE_PREFIX=https://zm-shorts.oss-cn-hangzhou.aliyuncs.com

+ 14 - 10
src/components/SecureImage.vue → src/components/RImage.vue

@@ -1,16 +1,18 @@
 <template>
     <ElImage :src="finalSrc" :preview-src-list="previewList" preview-teleported loading="lazy">
         <template #error>
-            <ElIcon> <IconPicture /></ElIcon>
+            <div class="h-full flex items-center justify-center text-sm text-neutral-400">
+                <PhotoOff style="width: 20px" />
+            </div>
         </template>
     </ElImage>
 </template>
 <script setup>
 import { computed } from 'vue'
-import { Picture as IconPicture } from '@element-plus/icons-vue'
 import uri from 'fast-uri'
 import { ElIcon } from 'element-plus'
-const prefix = 'https://zm-shorts.oss-cn-hangzhou.aliyuncs.com'
+import { PhotoOff } from '@vicons/tabler'
+const prefix = import.meta.env.VITE_IMAGE_PREFIX
 const props = defineProps({
     src: {
         type: [String, Array],
@@ -21,22 +23,24 @@ const props = defineProps({
         default: 'cover'
     }
 })
+function resolve(url) {
+    if (!url) return ''
+    if (/^http.*/.test(url)) return url
+    else return uri.resolve(prefix, url)
+}
 const finalSrc = computed(() => {
-    if (!props.src) {
-        return ''
-    }
     if (Array.isArray(props.src)) {
-        return uri.resolve(prefix, props.src[0])
+        return resolve(props.src[0])
     }
-    return uri.resolve(prefix, props.src)
+    return resolve(props.src)
 })
 const previewList = computed(() => {
     if (!props.src) {
         return []
     }
     if (Array.isArray(props.src)) {
-        return props.src.map((src) => uri.resolve(prefix, src))
+        return props.src.map((src) => resolve(src))
     }
-    return [uri.resolve(prefix, props.src)]
+    return [resolve(props.src)]
 })
 </script>

+ 8 - 2
src/components/SingleUpload.vue

@@ -15,7 +15,7 @@
                 class="w-40 h-40 bg-neutral-100 dark:bg-neutral-900 rounded-md cursor-pointer border border-dashed border-gray-500 hover:border-blue-500 flex justify-center items-center overflow-hidden relative"
                 v-loading="loading"
             >
-                <img v-if="imageUrl" :src="imageUrl" class="w-full h-full object-cover" />
+                <img v-if="imageUrl" :src="finalUrl" class="w-full h-full object-cover" />
                 <Plus v-if="!imageUrl && !loading" class="w-10 h-10 text-gray-400"> </Plus>
                 <div v-if="imageUrl" class="absolute bottom-4 left-0 right-0 flex items-center justify-center">
                     <Eye
@@ -32,7 +32,7 @@
             </div>
         </template>
     </el-upload>
-    <ElImageViewer v-if="showPreview" :url-list="[imageUrl]" teleported @close="showPreview = false" />
+    <ElImageViewer v-if="showPreview" :url-list="[finalUrl]" teleported @close="showPreview = false" />
 </template>
 <script setup>
 import { computed, ref, unref, watch } from 'vue'
@@ -72,6 +72,12 @@ const fileList = ref([])
 const imageUrl = ref(null)
 const showPreview = ref(false)
 
+const finalUrl = computed(() => {
+    if (!imageUrl.value) return null
+    if (/^http.*/.test(imageUrl.value)) return imageUrl.value
+    else return resolveUrl(import.meta.env.VITE_IMAGE_PREFIX, imageUrl.value)
+})
+
 if (props.modelValue) {
     imageUrl.value = unref(props.modelValue)
     fileList.value = [

+ 2 - 2
src/main.js

@@ -4,7 +4,7 @@ import { createPinia } from 'pinia'
 import App from './App.vue'
 import router from './router'
 import ElementPlus from 'element-plus'
-import SecureImage from '@/components/SecureImage.vue'
+import RImage from '@/components/RImage.vue'
 
 import './styles/main.less'
 import 'element-plus/dist/index.css'
@@ -15,6 +15,6 @@ const app = createApp(App)
 app.use(createPinia())
 app.use(router)
 app.use(ElementPlus)
-app.component('SecureImage', SecureImage)
+app.component('r-image', RImage)
 
 app.mount('#app')

+ 9 - 1
src/router/index.js

@@ -51,7 +51,15 @@ const router = createRouter({
                     name: 'series',
                     component: () => import('../views/SeriesView.vue'),
                     meta: {
-                        title: '短剧列表'
+                        title: '剧集管理'
+                    }
+                },
+                {
+                    path: 'categories',
+                    name: 'categories',
+                    component: () => import('../views/CategoriesView.vue'),
+                    meta: {
+                        title: '分类列表'
                     }
                 }
             ]

+ 48 - 0
src/views/CategoriesView.vue

@@ -0,0 +1,48 @@
+<template>
+    <PagingTable url="/categories" :query="query" ref="table">
+        <template #filter>
+            <ElButton :icon="Plus" @click="onEdit()">添加</ElButton>
+        </template>
+        <ElTableColumn prop="id" label="#" width="80" />
+        <ElTableColumn prop="name" label="名称" min-width="120" />
+        <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
+        <ElTableColumn label="操作" align="center" width="120">
+            <template #default="{ row }">
+                <ElButton @click="onEdit(row)">编辑</ElButton>
+            </template>
+        </ElTableColumn>
+    </PagingTable>
+    <EditDialog v-model="showEditDialog" :model="model" :rules="rules" :on-submit="submit" @success="table.refresh()">
+        <ElFormItem prop="name" label="名称">
+            <ElInput v-model="model.name" placeholder="请输入名称" />
+        </ElFormItem>
+    </EditDialog>
+</template>
+<script setup>
+import { ref } from 'vue'
+import PagingTable from '@/components/PagingTable.vue'
+import { useTimeFormatter } from '@/utils/formatter'
+import { Plus } from '@vicons/tabler'
+import EditDialog from '@/components/EditDialog.vue'
+import { setupEditDialog } from '@/utils/editDialog'
+import { http } from '@/plugins/http'
+import { ElMessage } from 'element-plus'
+const query = ref({})
+const timeFormatter = useTimeFormatter()
+const table = ref(null)
+const model = ref({})
+const rules = {
+    name: [{ required: true, message: '请输入名称', trigger: 'blur' }]
+}
+const { showEditDialog } = setupEditDialog(model)
+async function onEdit(row) {
+    model.value = row ? await http.get(`/categories/${row.id}`) : {}
+    showEditDialog.value = true
+}
+async function submit() {
+    model.value.id
+        ? await http.put(`/categories/${model.value.id}`, model.value)
+        : await http.post('/categories', model.value)
+    ElMessage.success('保存成功')
+}
+</script>

+ 7 - 2
src/views/MainView.vue

@@ -54,7 +54,7 @@ import DarkSwitch from '@/components/DarkSwitch.vue'
 import SideMenu from '@/components/SideMenu.vue'
 import { useRoute } from 'vue-router'
 import { ref, watch, shallowRef, inject } from 'vue'
-import { User, Home, Menu2, Settings, Video } from '@vicons/tabler'
+import { User, Home, Menu2, Settings, Video, LayoutGrid } from '@vicons/tabler'
 import UserAvatar from '@/components/UserAvatar.vue'
 import ChangePwd from '@/components/ChangePwd.vue'
 import { http } from '@/plugins/http'
@@ -71,9 +71,14 @@ const menus = [
     },
     {
         name: 'series',
-        title: '短剧列表',
+        title: '剧集管理',
         icon: Video
     },
+    {
+        name: 'categories',
+        title: '分类管理',
+        icon: LayoutGrid
+    },
     {
         name: 'user-parent',
         title: '用户管理',

+ 3 - 4
src/views/SeriesView.vue

@@ -7,7 +7,7 @@
         <ElTableColumn prop="title" label="剧名" min-width="120" />
         <ElTableColumn prop="cover" label="封面" width="120" align="center">
             <template #default="{ row }">
-                <SecureImage style="width: 30px; height: 35px; vertical-align: middle" :src="row.cover" fit="cover" />
+                <RImage style="width: 30px; height: 35px; vertical-align: middle" :src="row.cover" fit="cover" />
             </template>
         </ElTableColumn>
         <ElTableColumn prop="price" label="价格" width="150" />
@@ -33,7 +33,7 @@
         </ElFormItem>
         <ElFormItem prop="categories" label="分类">
             <ElSelect v-model="model.categories" value-key="id" multiple>
-                <ElOption v-for="item in categories" :key="item.id" :label="item.name" :value="item"/>
+                <ElOption v-for="item in categories" :key="item.id" :label="item.name" :value="item" />
             </ElSelect>
         </ElFormItem>
     </EditDialog>
@@ -51,8 +51,7 @@ import { ElMessage } from 'element-plus'
 import { useClipboard } from '@vueuse/core'
 import SingleUpload from '@/components/SingleUpload.vue'
 const query = ref({
-    preload: 'categories',
-    categories: 1
+    preload: 'categories'
 })
 const timeFormatter = useTimeFormatter()
 const table = ref(null)

+ 1 - 3
src/views/UserView.vue

@@ -6,10 +6,9 @@
         </template>
         <ElTableColumn prop="id" label="#" width="80" />
         <ElTableColumn prop="username" label="用户名" min-width="120" />
-        <ElTableColumn prop="name" label="昵称" min-width="120" />
+        <ElTableColumn prop="email" label="邮箱" min-width="120" />
         <ElTableColumn prop="phone" label="手机" min-width="120" />
         <ElTableColumn prop="createdAt" label="注册时间" :formatter="timeFormatter" width="150" />
-        <ElTableColumn prop="invitor" label="上级" />
         <ElTableColumn label="操作" align="center" width="120">
             <template #default="{ row }">
                 <ElButton @click="getToken(row)">Token</ElButton>
@@ -53,7 +52,6 @@ const table = ref(null)
 const model = ref({})
 const rules = {
     username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
-    name: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
     phone: [{ required: true, message: '请输入手机', trigger: 'blur' }],
     password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
     roles: [{ required: true, message: '请选择角色', trigger: 'blur' }]