Переглянути джерело

添加公司管理、图片管理

王飞 1 рік тому
батько
коміт
fc21d69c0c

+ 1 - 1
.env.base

@@ -8,4 +8,4 @@ VITE_API_BASE_PATH=base
 VITE_BASE_PATH=/
 
 # 标题
-VITE_APP_TITLE=ElementAdmin
+VITE_APP_TITLE=网站后台管理

+ 1 - 1
.env.dev

@@ -20,4 +20,4 @@ VITE_SOURCEMAP=true
 VITE_OUT_DIR=dist-dev
 
 # 标题
-VITE_APP_TITLE=ElementAdmin
+VITE_APP_TITLE=网站后台管理

+ 1 - 1
.env.gitee

@@ -20,4 +20,4 @@ VITE_SOURCEMAP=false
 VITE_OUT_DIR=dist-pro
 
 # 标题
-VITE_APP_TITLE=ElementAdmin
+VITE_APP_TITLE=网站后台管理

+ 1 - 1
.env.pro

@@ -20,4 +20,4 @@ VITE_SOURCEMAP=false
 VITE_OUT_DIR=dist-pro
 
 # 标题
-VITE_APP_TITLE=ElementAdmin
+VITE_APP_TITLE=网站后台管理

+ 1 - 1
.env.test

@@ -20,4 +20,4 @@ VITE_SOURCEMAP=true
 VITE_OUT_DIR=dist-test
 
 # 标题
-VITE_APP_TITLE=ElementAdmin
+VITE_APP_TITLE=网站后台管理

+ 18 - 0
src/api/manage/company.ts

@@ -0,0 +1,18 @@
+import request from '@/config/axios'
+// import type { FileData } from './types'
+
+export const getTableListApi = (data: any) => {
+  return request.post({ url: '/api/company/page', data })
+}
+
+export const delTableListApi = (id: string | number): Promise<IResponse> => {
+  return request.delete({ url: `/api/company/${id}` })
+}
+
+export const saveTableApi = (data: any) => {
+  return request.post({ url: '/api/company', data })
+}
+
+export const updateTableApi = (data: any) => {
+  return request.put({ url: '/api/company', data })
+}

+ 4 - 0
src/api/manage/file.ts

@@ -12,3 +12,7 @@ export const delTableListApi = (id: string | number): Promise<IResponse> => {
 export const saveTableApi = (data: any) => {
   return request.post({ url: '/api/sysApk/addSysApk', data })
 }
+
+export const updateTableApi = (data: any) => {
+  return request.post({ url: '/api/sysApk/updateSysApk', data })
+}

+ 18 - 0
src/api/manage/img.ts

@@ -0,0 +1,18 @@
+import request from '@/config/axios'
+// import type { FileData } from './types'
+
+export const getTableListApi = (data: any) => {
+  return request.post({ url: '/api/banner/page', data })
+}
+
+export const delTableListApi = (id: string | number): Promise<IResponse> => {
+  return request.delete({ url: `/api/banner/${id}` })
+}
+
+export const saveTableApi = (data: any) => {
+  return request.post({ url: '/api/banner', data })
+}
+
+export const updateTableApi = (data: any) => {
+  return request.put({ url: '/api/banner', data })
+}

+ 22 - 0
src/api/manage/news.ts

@@ -0,0 +1,22 @@
+import request from '@/config/axios'
+import type { NewsTableData } from './types'
+
+export const getTableListApi = (data: any) => {
+  return request.post({ url: '/api/article/page', data })
+}
+
+export const saveTableApi = (data: Partial<NewsTableData>): Promise<IResponse> => {
+  return request.post({ url: '/api/article', data })
+}
+
+export const updateTableApi = (data: Partial<NewsTableData>): Promise<IResponse> => {
+  return request.put({ url: '/api/article', data })
+}
+
+export const getTableDetApi = (id: string): Promise<IResponse<NewsTableData>> => {
+  return request.get({ url: `/api/article/${id}` })
+}
+
+export const delTableListApi = (id: string | number): Promise<IResponse> => {
+  return request.delete({ url: `/api/article/${id}` })
+}

+ 40 - 4
src/api/manage/types.ts

@@ -1,9 +1,9 @@
 export type FileData = {
-  id: string
+  id?: string
   virtualPath: string
-  updateTime: string
-  fileName: string
-  filePath: string
+  updateTime?: string
+  fileName?: string
+  filePath?: string
 }
 
 export type ProductTableData = {
@@ -19,3 +19,39 @@ export type ProductTableData = {
   remark: string
 }
 
+export type NewsTableData = {
+  id?: string
+  articleContent: string
+  articlePic: string
+  articleTitle: string
+  categoryId?: string
+  articleViews: number
+  introduce: string
+  remark: string
+  updateTime: string
+  videoUrl: string
+  weight: number
+}
+
+
+export type CompanyData = {
+  id?: string
+  companyAddress?: string
+  companyBrief?: string
+  companyEmail?: string
+  companyName: string
+  companyPhone?: string
+  isActive?: string
+  name?: string
+  phone?: string
+  wechat?: string
+  wechatImg: string
+}
+
+export type ImgData = {
+  id?: string
+  bannerUrl: string
+  updateTime?: string
+  bannerType?: string
+  bannerTarget?: string
+}

+ 0 - 1
src/config/axios/config.ts

@@ -91,7 +91,6 @@ const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
 }
 
 const defaultResponseInterceptors = (response: AxiosResponse<any>) => {
-  console.log(response)
   if (response?.config?.responseType === 'blob') {
     // 如果是文件流,直接过
     return response

+ 0 - 1
src/hooks/web/useTable.ts

@@ -80,7 +80,6 @@ export const useTable = (config: UseTableConfig) => {
       loading.value = true
       try {
         const res = await config?.fetchDataApi()
-        console.log('fetchDataApi res', res)
         if (res) {
           dataList.value = res.list
           total.value = res.total || 0

+ 1 - 3
src/main.ts

@@ -14,7 +14,7 @@ import { setupStore } from '@/store'
 import { setupGlobCom } from '@/components'
 
 // 引入element-plus
-import { setupElementPlus, setupElementPlusIcon } from '@/plugins/elementPlus'
+import { setupElementPlus } from '@/plugins/elementPlus'
 
 // 引入全局样式
 import '@/styles/index.less'
@@ -50,8 +50,6 @@ const setupAll = async () => {
 
   setupPermission(app)
 
-  setupElementPlusIcon(app)
-
   app.mount('#app')
 }
 

+ 0 - 7
src/plugins/elementPlus/index.ts

@@ -2,7 +2,6 @@ import type { App } from 'vue'
 
 // 需要全局引入一些组件,如ElScrollbar,不然一些下拉项样式有问题
 import { ElLoading, ElScrollbar } from 'element-plus'
-import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 
 const plugins = [ElLoading]
 
@@ -17,9 +16,3 @@ export const setupElementPlus = (app: App<Element>) => {
     app.component(component.name, component)
   })
 }
-
-export const setupElementPlusIcon = (app: App<Element>) => {
-  for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
-    app.component(key, component)
-  }
-}

+ 20 - 4
src/router/index.ts

@@ -95,7 +95,7 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         component: () => import('@/views/Manage/News/NewsPage.vue'),
         name: 'NewsPage',
         meta: {
-          title: '新闻管理'
+          title: '文案管理'
         }
       },
       {
@@ -103,7 +103,7 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         component: () => import('@/views/Manage/News/NewsAdd.vue'),
         name: 'NewsAdd',
         meta: {
-          title: '新增新闻',
+          title: '新增文案',
           noTagsView: true,
           noCache: true,
           hidden: true,
@@ -116,7 +116,7 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         component: () => import('@/views/Manage/News/NewsEdit.vue'),
         name: 'NewsEdit',
         meta: {
-          title: '编辑新闻',
+          title: '编辑文案',
           noTagsView: true,
           noCache: true,
           hidden: true,
@@ -129,7 +129,7 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         component: () => import('@/views/Manage/News/NewsDetail.vue'),
         name: 'NewsDetail',
         meta: {
-          title: '新闻详情',
+          title: '文案详情',
           noTagsView: true,
           noCache: true,
           hidden: true,
@@ -191,6 +191,22 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         meta: {
           title: '文件管理'
         }
+      },
+      {
+        path: 'img-page',
+        component: () => import('@/views/Manage/Img/ImgPage.vue'),
+        name: 'ImgPage',
+        meta: {
+          title: '图片管理'
+        }
+      },
+      {
+        path: 'company-page',
+        component: () => import('@/views/Manage/Company/CompanyPage.vue'),
+        name: 'CompanyPage',
+        meta: {
+          title: '公司信息管理'
+        }
       }
     ]
   },

+ 10 - 233
src/views/Dashboard/Workplace.vue

@@ -1,111 +1,9 @@
 <script setup lang="ts">
-import { useTimeAgo } from '@/hooks/web/useTimeAgo'
-import { ElRow, ElCol, ElSkeleton, ElCard, ElDivider, ElLink } from 'element-plus'
+import { ElRow, ElCol, ElSkeleton, ElCard } from 'element-plus'
 import { useI18n } from '@/hooks/web/useI18n'
-import { ref, reactive } from 'vue'
-import { CountTo } from '@/components/CountTo'
-import { formatTime } from '@/utils'
-import { Echart } from '@/components/Echart'
-import { EChartsOption } from 'echarts'
-import { radarOption } from './echarts-data'
-import { Highlight } from '@/components/Highlight'
-import {
-  getCountApi,
-  getProjectApi,
-  getDynamicApi,
-  getTeamApi,
-  getRadarApi
-} from '@/api/dashboard/workplace'
-import type { WorkplaceTotal, Project, Dynamic, Team } from '@/api/dashboard/workplace/types'
-import { set } from 'lodash-es'
+import { ref } from 'vue'
 
-const loading = ref(true)
-
-// 获取统计数
-let totalSate = reactive<WorkplaceTotal>({
-  project: 0,
-  access: 0,
-  todo: 0
-})
-
-const getCount = async () => {
-  const res = await getCountApi().catch(() => {})
-  if (res) {
-    totalSate = Object.assign(totalSate, res.data)
-  }
-}
-
-let projects = reactive<Project[]>([])
-
-// 获取项目数
-const getProject = async () => {
-  const res = await getProjectApi().catch(() => {})
-  if (res) {
-    projects = Object.assign(projects, res.data)
-  }
-}
-
-// 获取动态
-let dynamics = reactive<Dynamic[]>([])
-
-const getDynamic = async () => {
-  const res = await getDynamicApi().catch(() => {})
-  if (res) {
-    dynamics = Object.assign(dynamics, res.data)
-  }
-}
-
-// 获取团队
-let team = reactive<Team[]>([])
-
-const getTeam = async () => {
-  const res = await getTeamApi().catch(() => {})
-  if (res) {
-    team = Object.assign(team, res.data)
-  }
-}
-
-// 获取指数
-let radarOptionData = reactive<EChartsOption>(radarOption) as EChartsOption
-
-const getRadar = async () => {
-  const res = await getRadarApi().catch(() => {})
-  if (res) {
-    set(
-      radarOptionData,
-      'radar.indicator',
-      res.data.map((v) => {
-        return {
-          name: t(v.name),
-          max: v.max
-        }
-      })
-    )
-    set(radarOptionData, 'series', [
-      {
-        name: `xxx${t('workplace.index')}`,
-        type: 'radar',
-        data: [
-          {
-            value: res.data.map((v) => v.personal),
-            name: t('workplace.personal')
-          },
-          {
-            value: res.data.map((v) => v.team),
-            name: t('workplace.team')
-          }
-        ]
-      }
-    ])
-  }
-}
-
-const getAllApi = async () => {
-  await Promise.all([getCount(), getProject(), getDynamic(), getTeam(), getRadar()])
-  loading.value = false
-}
-
-getAllApi()
+const loading = ref(false)
 
 const { t } = useI18n()
 </script>
@@ -117,23 +15,23 @@ const { t } = useI18n()
         <ElRow :gutter="20" justify="space-between">
           <ElCol :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
             <div class="flex items-center">
-              <img
+              <!-- <img
                 src="@/assets/imgs/avatar.jpg"
                 alt=""
                 class="w-70px h-70px rounded-[50%] mr-20px"
-              />
+              /> -->
               <div>
                 <div class="text-20px">
-                  {{ t('workplace.goodMorning') }},Archer,{{ t('workplace.happyDay') }}
+                  {{ t('workplace.happyDay') }}
                 </div>
-                <div class="mt-10px text-14px text-gray-500">
+                <!-- <div class="mt-10px text-14px text-gray-500">
                   {{ t('workplace.toady') }},20℃ - 32℃!
-                </div>
+                </div> -->
               </div>
             </div>
           </ElCol>
           <ElCol :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
-            <div class="flex h-70px items-center justify-end lt-sm:mt-20px">
+            <!-- <div class="flex h-70px items-center justify-end lt-sm:mt-20px">
               <div class="px-8px text-right">
                 <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.project') }}</div>
                 <CountTo
@@ -163,131 +61,10 @@ const { t } = useI18n()
                   :duration="2600"
                 />
               </div>
-            </div>
+            </div> -->
           </ElCol>
         </ElRow>
       </ElSkeleton>
     </ElCard>
   </div>
-
-  <ElRow class="mt-20px" :gutter="20" justify="space-between">
-    <ElCol :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-20px">
-      <ElCard shadow="never">
-        <template #header>
-          <div class="flex justify-between">
-            <span>{{ t('workplace.project') }}</span>
-            <ElLink type="primary" :underline="false">{{ t('workplace.more') }}</ElLink>
-          </div>
-        </template>
-        <ElSkeleton :loading="loading" animated>
-          <ElRow>
-            <ElCol
-              v-for="(item, index) in projects"
-              :key="`card-${index}`"
-              :xl="8"
-              :lg="8"
-              :md="12"
-              :sm="24"
-              :xs="24"
-            >
-              <ElCard shadow="hover">
-                <div class="flex items-center">
-                  <Icon :icon="item.icon" :size="25" class="mr-10px" />
-                  <span class="text-16px">{{ item.name }}</span>
-                </div>
-                <div class="mt-15px text-14px text-gray-400">{{ t(item.message) }}</div>
-                <div class="mt-20px text-12px text-gray-400 flex justify-between">
-                  <span>{{ item.personal }}</span>
-                  <span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
-                </div>
-              </ElCard>
-            </ElCol>
-          </ElRow>
-        </ElSkeleton>
-      </ElCard>
-
-      <ElCard shadow="never" class="mt-20px">
-        <template #header>
-          <div class="flex justify-between">
-            <span>{{ t('workplace.dynamic') }}</span>
-            <ElLink type="primary" :underline="false">{{ t('workplace.more') }}</ElLink>
-          </div>
-        </template>
-        <ElSkeleton :loading="loading" animated>
-          <div v-for="(item, index) in dynamics" :key="`dynamics-${index}`">
-            <div class="flex items-center">
-              <img
-                src="@/assets/imgs/avatar.jpg"
-                alt=""
-                class="w-35px h-35px rounded-[50%] mr-20px"
-              />
-              <div>
-                <div class="text-14px">
-                  <Highlight :keys="item.keys.map((v) => t(v))">
-                    {{ t('workplace.pushCode') }}
-                  </Highlight>
-                </div>
-                <div class="mt-15px text-12px text-gray-400">
-                  {{ useTimeAgo(item.time) }}
-                </div>
-              </div>
-            </div>
-            <ElDivider />
-          </div>
-        </ElSkeleton>
-      </ElCard>
-    </ElCol>
-    <ElCol :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-20px">
-      <ElCard shadow="never">
-        <template #header>
-          <span>{{ t('workplace.shortcutOperation') }}</span>
-        </template>
-        <ElSkeleton :loading="loading" animated>
-          <ElRow>
-            <ElCol
-              v-for="item in 9"
-              :key="`card-${item}`"
-              :xl="12"
-              :lg="12"
-              :md="12"
-              :sm="24"
-              :xs="24"
-              class="mb-10px"
-            >
-              <ElLink type="default" :underline="false">
-                {{ t('workplace.operation') }}{{ item }}
-              </ElLink>
-            </ElCol>
-          </ElRow>
-        </ElSkeleton>
-      </ElCard>
-
-      <ElCard shadow="never" class="mt-20px">
-        <template #header>
-          <span>xx{{ t('workplace.index') }}</span>
-        </template>
-        <ElSkeleton :loading="loading" animated>
-          <Echart :options="radarOptionData" :height="400" />
-        </ElSkeleton>
-      </ElCard>
-
-      <ElCard shadow="never" class="mt-20px">
-        <template #header>
-          <span>{{ t('workplace.team') }}</span>
-        </template>
-        <ElSkeleton :loading="loading" animated>
-          <ElRow>
-            <ElCol v-for="item in team" :key="`team-${item.name}`" :span="12" class="mb-20px">
-              <div class="flex items-center">
-                <Icon :icon="item.icon" class="mr-10px" />
-                <ElLink type="default" :underline="false">
-                  {{ item.name }}
-                </ElLink>
-              </div>
-            </ElCol>
-          </ElRow>
-        </ElSkeleton>
-      </ElCard>
-    </ElCol>
-  </ElRow>
 </template>

+ 4 - 6
src/views/Login/Login.vue

@@ -50,9 +50,7 @@ const toLogin = () => {
             >
               <img src="@/assets/svgs/login-box-bg.svg" key="1" alt="" class="w-350px" />
               <div class="text-3xl text-white" key="2">{{ t('login.welcome') }}</div>
-              <div class="mt-5 font-normal text-white text-14px" key="3">
-                {{ t('login.message') }}
-              </div>
+              <div class="mt-5 font-normal text-white text-14px" key="3"> 管理网站信息 </div>
             </TransitionGroup>
           </div>
         </div>
@@ -76,14 +74,14 @@ const toLogin = () => {
             >
               <LoginForm
                 v-if="isLogin"
-                class="p-20px h-auto m-auto lt-xl:rounded-3xl lt-xl:light:bg-white"
+                class="p-20px h-auto m-auto lt-xl:rounded-3xl bg-white"
                 @to-register="toRegister"
               />
-              <RegisterForm
+              <!-- <RegisterForm
                 v-else
                 class="p-20px h-auto m-auto lt-xl:rounded-3xl lt-xl:light:bg-white"
                 @to-login="toLogin"
-              />
+              /> -->
             </div>
           </Transition>
         </div>

+ 54 - 59
src/views/Login/components/LoginForm.vue

@@ -111,71 +111,66 @@ const schema = reactive<FormSchema[]>([
                   {t('login.login')}
                 </ElButton>
               </div>
-              <div class="w-[100%] mt-15px">
-                <ElButton class="w-[100%]" onClick={toRegister}>
-                  {t('login.register')}
-                </ElButton>
-              </div>
-            </>
-          )
-        }
-      }
-    }
-  },
-  {
-    field: 'other',
-    component: 'Divider',
-    label: t('login.otherLogin'),
-    componentProps: {
-      contentPosition: 'center'
-    }
-  },
-  {
-    field: 'otherIcon',
-    colProps: {
-      span: 24
-    },
-    formItemProps: {
-      slots: {
-        default: () => {
-          return (
-            <>
-              <div class="flex justify-between w-[100%]">
-                <Icon
-                  icon="ant-design:github-filled"
-                  size={iconSize}
-                  class="cursor-pointer ant-icon"
-                  color={iconColor}
-                  hoverColor={hoverColor}
-                />
-                <Icon
-                  icon="ant-design:wechat-filled"
-                  size={iconSize}
-                  class="cursor-pointer ant-icon"
-                  color={iconColor}
-                  hoverColor={hoverColor}
-                />
-                <Icon
-                  icon="ant-design:alipay-circle-filled"
-                  size={iconSize}
-                  color={iconColor}
-                  hoverColor={hoverColor}
-                  class="cursor-pointer ant-icon"
-                />
-                <Icon
-                  icon="ant-design:weibo-circle-filled"
-                  size={iconSize}
-                  color={iconColor}
-                  hoverColor={hoverColor}
-                  class="cursor-pointer ant-icon"
-                />
-              </div>
             </>
           )
         }
       }
     }
   }
+  // {
+  //   field: 'other',
+  //   component: 'Divider',
+  //   label: t('login.otherLogin'),
+  //   componentProps: {
+  //     contentPosition: 'center'
+  //   }
+  // },
+  // {
+  //   field: 'otherIcon',
+  //   colProps: {
+  //     span: 24
+  //   },
+  //   formItemProps: {
+  //     slots: {
+  //       default: () => {
+  //         return (
+  //           <>
+  //             <div class="flex justify-between w-[100%]">
+  //               <Icon
+  //                 icon="ant-design:github-filled"
+  //                 size={iconSize}
+  //                 class="cursor-pointer ant-icon"
+  //                 color={iconColor}
+  //                 hoverColor={hoverColor}
+  //               />
+  //               <Icon
+  //                 icon="ant-design:wechat-filled"
+  //                 size={iconSize}
+  //                 class="cursor-pointer ant-icon"
+  //                 color={iconColor}
+  //                 hoverColor={hoverColor}
+  //               />
+  //               <Icon
+  //                 icon="ant-design:alipay-circle-filled"
+  //                 size={iconSize}
+  //                 color={iconColor}
+  //                 hoverColor={hoverColor}
+  //                 class="cursor-pointer ant-icon"
+  //               />
+  //               <Icon
+  //                 icon="ant-design:weibo-circle-filled"
+  //                 size={iconSize}
+  //                 color={iconColor}
+  //                 hoverColor={hoverColor}
+  //                 class="cursor-pointer ant-icon"
+  //               />
+  //             </div>
+  //           </>
+  //         )
+  //       }
+  //     }
+  //   }
+  // }
 ])
 
 const iconSize = 30

+ 458 - 0
src/views/Manage/Company/CompanyPage.vue

@@ -0,0 +1,458 @@
+<script setup lang="tsx">
+import { ContentWrap } from '@/components/ContentWrap'
+import { Search } from '@/components/Search'
+import { useI18n } from '@/hooks/web/useI18n'
+import { ElButton, ElTooltip, ElButtonGroup } from 'element-plus'
+import { Table } from '@/components/Table'
+import {
+  getTableListApi,
+  delTableListApi,
+  saveTableApi,
+  updateTableApi
+} from '@/api/manage/company'
+import { useTable } from '@/hooks/web/useTable'
+import { ref, unref } from 'vue'
+import { useEmitt } from '@/hooks/event/useEmitt'
+import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
+import { TableSetting } from '@/components/TableSetting'
+import { usePageStore } from '@/store/modules/page'
+import { set } from 'lodash-es'
+import { useStorage } from '@/hooks/web/useStorage'
+import { Dialog } from '@/components/Dialog'
+import { CompanyData } from '@/api/manage/types'
+import Write from './components/Write.vue'
+import { useIcon } from '@/hooks/web/useIcon'
+defineOptions({
+  name: 'CompanyPage'
+})
+const DeleteIcon = useIcon({ icon: 'ep:delete' })
+const EditIcon = useIcon({ icon: 'ep:edit' })
+const id = ref<string>('')
+const { getStorage } = useStorage()
+const searchParams = ref({})
+const setSearchParams = (params: any) => {
+  searchParams.value = params
+  getList()
+}
+const currentRow = ref<CompanyData>({
+  wechatImg: '',
+  companyName: ''
+})
+const wechatImg = ref('')
+const formState = ref<string>('add')
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const { currentPage, pageSize } = tableState
+    const res = await getTableListApi({
+      pageNum: unref(currentPage),
+      pageSize: unref(pageSize),
+      ...unref(searchParams)
+    })
+    return {
+      list: res.data.list,
+      total: Number(res.data.totalCount)
+    }
+  },
+  fetchDelApi: async () => {
+    const res = await delTableListApi(unref(id))
+    return !!res
+  }
+})
+const { loading, dataList, total, currentPage, pageSize } = tableState
+const { getList, delList, setColumn } = tableMethods
+
+getList()
+
+useEmitt({
+  name: 'getList',
+  callback: (type: string) => {
+    if (type === 'add') {
+      currentPage.value = 1
+    }
+    getList()
+  }
+})
+
+const { t } = useI18n()
+const appStore = usePageStore()
+const dialogVisible = ref(false)
+// const fileList = ref([])
+const crudSchemas: CrudSchema[] = [
+  {
+    field: 'companyName',
+    label: '公司名称',
+    minWidth: 100,
+    table: {
+      hidden: false
+    }
+  },
+
+  {
+    field: 'companyPhone',
+    label: '公司电话',
+    minWidth: 120,
+    table: {
+      hidden: false
+    },
+    search: {
+      hidden: true
+    },
+    form: {}
+  },
+  {
+    field: 'companyEmail',
+    label: '公司邮箱',
+    minWidth: 120,
+    table: {
+      hidden: false
+    },
+    search: {
+      hidden: true
+    },
+    form: {}
+  },
+  {
+    field: 'companyAddress',
+    label: '公司地址',
+    minWidth: 120,
+    table: {
+      hidden: false
+    },
+    search: {
+      hidden: true
+    },
+    form: {
+      hidden: false
+    }
+  },
+  {
+    field: 'companyBrief',
+    label: '公司简介',
+    minWidth: 100,
+    table: {
+      hidden: false
+    },
+    search: {
+      hidden: true
+    },
+    form: {
+      componentProps: {
+        type: 'textarea'
+      },
+      colProps: {
+        span: 24
+      }
+    }
+  },
+  {
+    field: 'name',
+    label: '负责人',
+    minWidth: 100,
+    table: {
+      hidden: false
+    },
+    form: {}
+  },
+  {
+    field: 'phone',
+    label: '负责人电话',
+    minWidth: 100,
+    table: {
+      hidden: false
+    },
+    search: {
+      hidden: true
+    },
+    form: {}
+  },
+  {
+    field: 'wechat',
+    label: '微信账号',
+    minWidth: 100,
+    table: {
+      hidden: false
+    },
+    search: {
+      hidden: true
+    },
+    form: {}
+  },
+  {
+    field: 'isActive',
+    label: '是否启用',
+    minWidth: 100,
+    table: {
+      hidden: false
+    },
+    search: {
+      hidden: true
+    },
+    form: {
+      component: 'Switch',
+      value: '1',
+      componentProps: {
+        activeText: '启用',
+        inactiveText: '禁用',
+        activeValue: '1',
+        inactiveValue: '0'
+      }
+    }
+  },
+  {
+    field: 'wechatImg',
+    label: '微信二维码',
+    minWidth: 120,
+    search: {
+      hidden: true
+    },
+    table: {
+      hidden: false
+    },
+    form: {
+      colProps: {
+        span: 24
+      },
+      component: 'Upload',
+      componentProps: {
+        action: '/api/sysApk/uploadFile',
+        limit: 1,
+        showFileList: false,
+        class: 'companyUploader',
+        fileList: wechatImg.value
+          ? [
+              {
+                url: wechatImg.value
+              }
+            ]
+          : [],
+        headers: {
+          token: getStorage('token')
+        },
+        onSuccess: (response) => {
+          wechatImg.value = response.data.virtualPath
+          currentRow.value.wechatImg = response.data.virtualPath
+        },
+        onExceed: (_files, responses) => {
+          wechatImg.value = responses[0].response.data.virtualPath
+          currentRow.value.wechatImg = responses[0].response.data.virtualPath
+        },
+        slots: {
+          default: () => (
+            <>
+              {wechatImg.value ? <img src={wechatImg.value} class="avatar" /> : null}
+              {!wechatImg.value ? (
+                <Icon class="avatar-uploader-icon" icon="ep:plus" size={28}></Icon>
+              ) : null}
+            </>
+          )
+        }
+      }
+    }
+  },
+  {
+    field: 'createTime',
+    label: '创建时间',
+    minWidth: 160,
+    table: {
+      hidden: false
+    },
+    search: {
+      hidden: true
+    },
+    form: {
+      hidden: true
+    }
+  },
+  {
+    field: 'action',
+    width: '120px',
+    label: '操作',
+    search: {
+      hidden: true
+    },
+    form: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    },
+    table: {
+      hidden: false,
+      fixed: 'right',
+      slots: {
+        default: (data: any) => {
+          return (
+            <ElButtonGroup>
+              <ElTooltip content="编辑">
+                <ElButton text icon={EditIcon} onClick={() => handleEdit(data.row)} />
+              </ElTooltip>
+              <ElTooltip content="删除">
+                <ElButton text icon={DeleteIcon} type="danger" onClick={() => delData(data.row)} />
+              </ElTooltip>
+            </ElButtonGroup>
+          )
+        }
+      }
+    }
+  }
+]
+
+// @ts-ignore
+const getSchemas = () => {
+  let localSchemas = appStore.getPageData['CompanyPage']
+  if (localSchemas && localSchemas.schemas) {
+    let localSchemasArr = localSchemas.schemas
+    for (let i = 0; i < localSchemasArr.length; i++) {
+      let item = localSchemasArr[i]
+      let index = crudSchemas.findIndex((e) => {
+        return e.field == item.field
+      })
+      if (index > 0) {
+        set(crudSchemas[index], 'table.hidden', item.table.hidden)
+      }
+    }
+  }
+}
+getSchemas()
+let allSchemas = useCrudSchemas(crudSchemas).allSchemas
+// 修改列设置后调用
+const setSchemas = (schemas: CrudSchema[]) => {
+  let arr = schemas.map((item) => {
+    return {
+      field: item.field,
+      path: 'hidden',
+      value: item.table ? item.table.hidden : false
+    }
+  })
+  setColumn(arr)
+}
+const writeRef = ref<ComponentRef<typeof Write>>()
+
+const handleEdit = (row: CompanyData) => {
+  currentRow.value = row
+  wechatImg.value = row.wechatImg
+  formState.value = 'edit'
+  dialogVisible.value = true
+}
+
+const save = async () => {
+  const write = unref(writeRef)
+  const formData = await write?.submit()
+  console.log(formData)
+  if (formData) {
+    delLoading.value = true
+    try {
+      if (formData.id) {
+        let res = await updateTableApi(formData)
+        if (res) {
+          currentPage.value = 1
+          getList()
+        }
+      } else {
+        let res = await saveTableApi(formData)
+        if (res) {
+          currentPage.value = 1
+          getList()
+        }
+      }
+    } catch (error) {
+    } finally {
+      delLoading.value = false
+      dialogVisible.value = false
+    }
+  }
+}
+
+const delLoading = ref(false)
+
+const delData = async (row: CompanyData) => {
+  if (!row.id) return
+  id.value = row?.id
+  delLoading.value = true
+  await delList(unref(id).length).finally(() => {
+    delLoading.value = false
+  })
+}
+
+const handleAdd = () => {
+  formState.value = 'add'
+  currentRow.value = {
+    wechatImg: '',
+    companyName: ''
+  }
+  wechatImg.value = ''
+  dialogVisible.value = true
+}
+</script>
+
+<template>
+  <ContentWrap>
+    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
+
+    <div class="mb-10px">
+      <el-button type="primary" @click="handleAdd">新增</el-button>
+      <TableSetting page="CompanyPage" :data="crudSchemas" @set-schemas="setSchemas" />
+    </div>
+
+    <Table
+      v-model:pageSize="pageSize"
+      v-model:currentPage="currentPage"
+      :columns="allSchemas.tableColumns"
+      :data="dataList"
+      :loading="loading"
+      :pagination="{
+        total: total
+      }"
+      @register="tableRegister"
+    />
+  </ContentWrap>
+  <Dialog v-model="dialogVisible" :title="formState == 'add' ? '新增' : '编辑'">
+    <Write ref="writeRef" :form-schema="allSchemas.formSchema" :current-row="currentRow" />
+    <template #footer>
+      <ElButton type="primary" :loading="delLoading" @click="save">
+        {{ t('exampleDemo.save') }}
+      </ElButton>
+      <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
+    </template>
+  </Dialog>
+</template>
+@/hooks/event/useEmitt
+
+<style lang="less">
+.uploadBtn {
+  display: inline-block;
+  margin-right: 12px;
+}
+.filePageUploader {
+  width: 100%;
+}
+</style>
+<style scoped>
+.companyUploader .avatar {
+  width: 178px;
+  height: 178px;
+  display: block;
+}
+</style>
+
+<style lang="less">
+.companyUploader .el-upload {
+  border: 1px dashed var(--el-border-color);
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  transition: var(--el-transition-duration-fast);
+  img {
+    width: 178px;
+    height: 178px;
+  }
+  .avatar-uploader-icon {
+    font-size: 28px;
+    color: #8c939d;
+    width: 178px;
+    height: 178px;
+    text-align: center;
+  }
+}
+</style>

+ 66 - 0
src/views/Manage/Company/components/Write.vue

@@ -0,0 +1,66 @@
+<script setup lang="ts">
+import { Form, FormSchema } from '@/components/Form'
+import { useForm } from '@/hooks/web/useForm'
+import { PropType, reactive, watch } from 'vue'
+import { CompanyData } from '@/api/manage/types'
+import { useValidator } from '@/hooks/web/useValidator'
+
+const { required } = useValidator()
+
+const props = defineProps({
+  currentRow: {
+    type: Object as PropType<CompanyData>,
+    default: () => undefined
+  },
+  formSchema: {
+    type: Array as PropType<FormSchema[]>,
+    default: () => []
+  }
+})
+
+const rules = reactive({
+  companyName: [required()]
+})
+
+const { formRegister, formMethods } = useForm()
+const { setValues, getFormData, getElFormExpose, setSchema } = formMethods
+
+const submit = async () => {
+  const elForm = await getElFormExpose()
+  const valid = await elForm?.validate().catch((err) => {
+    console.log(err)
+  })
+  if (valid) {
+    const formData = await getFormData()
+    return formData
+  }
+}
+
+const resetForm = async () => {
+  const elForm = await getElFormExpose()
+  elForm?.resetFields()
+}
+
+watch(
+  () => props.currentRow,
+  (currentRow) => {
+    if (!currentRow) return
+    setValues(currentRow)
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+
+defineExpose({
+  submit,
+  setSchema,
+  setValues,
+  resetForm
+})
+</script>
+
+<template>
+  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+</template>

+ 47 - 10
src/views/Manage/File/FilePage.vue

@@ -4,7 +4,7 @@ import { Search } from '@/components/Search'
 import { useI18n } from '@/hooks/web/useI18n'
 import { ElButton, ElTooltip, ElMessage, ElButtonGroup } from 'element-plus'
 import { Table } from '@/components/Table'
-import { getTableListApi, delTableListApi, saveTableApi } from '@/api/manage/file'
+import { getTableListApi, delTableListApi, saveTableApi, updateTableApi } from '@/api/manage/file'
 import { useTable } from '@/hooks/web/useTable'
 import { ref, unref } from 'vue'
 import { useEmitt } from '@/hooks/event/useEmitt'
@@ -25,6 +25,7 @@ const QRIcon = useIcon({ icon: 'ic:round-qr-code' })
 const DeleteIcon = useIcon({ icon: 'ep:delete' })
 const DownLoadIcon = useIcon({ icon: 'ep:download' })
 const copyIcon = useIcon({ icon: 'ep:document-copy' })
+const EditIcon = useIcon({ icon: 'ep:edit' })
 const id = ref<string>('')
 const QrVisible = ref<boolean>(false)
 const QrSrc = ref<string>('')
@@ -34,8 +35,10 @@ const setSearchParams = (params: any) => {
   searchParams.value = params
   getList()
 }
-const currentRow = ref<FileData>()
-
+const currentRow = ref<FileData>({
+  virtualPath: ''
+})
+const formState = ref<string>('add')
 const { tableRegister, tableState, tableMethods } = useTable({
   fetchDataApi: async () => {
     const { currentPage, pageSize } = tableState
@@ -72,6 +75,7 @@ useEmitt({
 const { t } = useI18n()
 const appStore = usePageStore()
 const dialogVisible = ref(false)
+// const fileList = ref([])
 const crudSchemas: CrudSchema[] = [
   {
     field: 'name',
@@ -141,6 +145,13 @@ const crudSchemas: CrudSchema[] = [
         action: '/api/sysApk/uploadFile',
         limit: 1,
         class: 'filePageUploader',
+        fileList: currentRow.value.virtualPath
+          ? [
+              {
+                url: currentRow.value.virtualPath
+              }
+            ]
+          : [],
         headers: {
           token: getStorage('token')
         },
@@ -230,7 +241,7 @@ const crudSchemas: CrudSchema[] = [
   },
   {
     field: 'action',
-    width: '200px',
+    width: '250px',
     label: '操作',
     search: {
       hidden: true
@@ -248,6 +259,9 @@ const crudSchemas: CrudSchema[] = [
         default: (data: any) => {
           return (
             <ElButtonGroup>
+              <ElTooltip content="编辑">
+                <ElButton text icon={EditIcon} onClick={() => handleEdit(data.row)} />
+              </ElTooltip>
               <ElTooltip content="二维码">
                 <ElButton text icon={QRIcon} onClick={() => showQrCode(data.row)} />
               </ElTooltip>
@@ -298,16 +312,31 @@ const setSchemas = (schemas: CrudSchema[]) => {
   setColumn(arr)
 }
 const writeRef = ref<ComponentRef<typeof Write>>()
+
+const handleEdit = (row: FileData) => {
+  currentRow.value = row
+  formState.value = 'edit'
+  dialogVisible.value = true
+}
+
 const save = async () => {
   const write = unref(writeRef)
   const formData = await write?.submit()
   if (formData) {
     delLoading.value = true
     try {
-      let res = await saveTableApi(formData)
-      if (res) {
-        currentPage.value = 1
-        getList()
+      if (formData.id) {
+        let res = await updateTableApi(formData)
+        if (res) {
+          currentPage.value = 1
+          getList()
+        }
+      } else {
+        let res = await saveTableApi(formData)
+        if (res) {
+          currentPage.value = 1
+          getList()
+        }
       }
     } catch (error) {
     } finally {
@@ -345,6 +374,14 @@ const onCopy = (row: FileData) => {
     })
   })
 }
+
+const handleAdd = () => {
+  formState.value = 'add'
+  currentRow.value = {
+    virtualPath: ''
+  }
+  dialogVisible.value = true
+}
 </script>
 
 <template>
@@ -352,7 +389,7 @@ const onCopy = (row: FileData) => {
     <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
 
     <div class="mb-10px">
-      <el-button type="primary" @click="dialogVisible = true">上传文件</el-button>
+      <el-button type="primary" @click="handleAdd">上传文件</el-button>
       <TableSetting page="FilePage" :data="crudSchemas" @set-schemas="setSchemas" />
     </div>
 
@@ -368,7 +405,7 @@ const onCopy = (row: FileData) => {
       @register="tableRegister"
     />
   </ContentWrap>
-  <Dialog v-model="dialogVisible" title="新增文件">
+  <Dialog v-model="dialogVisible" :title="formState == 'add' ? '新增文件' : '编辑文件'">
     <Write ref="writeRef" :form-schema="allSchemas.formSchema" :current-row="currentRow" />
     <template #footer>
       <ElButton type="primary" :loading="delLoading" @click="save">

+ 7 - 1
src/views/Manage/File/components/Write.vue

@@ -36,6 +36,11 @@ const submit = async () => {
   }
 }
 
+const resetForm = async () => {
+  const elForm = await getElFormExpose()
+  elForm?.resetFields()
+}
+
 watch(
   () => props.currentRow,
   (currentRow) => {
@@ -51,7 +56,8 @@ watch(
 defineExpose({
   submit,
   setSchema,
-  setValues
+  setValues,
+  resetForm
 })
 </script>
 

+ 409 - 0
src/views/Manage/Img/ImgPage.vue

@@ -0,0 +1,409 @@
+<script setup lang="tsx">
+import { ContentWrap } from '@/components/ContentWrap'
+import { Search } from '@/components/Search'
+import { useI18n } from '@/hooks/web/useI18n'
+import { ElButton, ElTooltip, ElMessage, ElButtonGroup } from 'element-plus'
+import { Table } from '@/components/Table'
+import { getTableListApi, delTableListApi, saveTableApi, updateTableApi } from '@/api/manage/img'
+import { useTable } from '@/hooks/web/useTable'
+import { ref, unref } from 'vue'
+import { useEmitt } from '@/hooks/event/useEmitt'
+import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
+import { TableSetting } from '@/components/TableSetting'
+import { usePageStore } from '@/store/modules/page'
+import { set } from 'lodash-es'
+import { useStorage } from '@/hooks/web/useStorage'
+import { Dialog } from '@/components/Dialog'
+import { ImgData } from '@/api/manage/types'
+import Write from './components/Write.vue'
+import { useIcon } from '@/hooks/web/useIcon'
+import { Qrcode } from '@/components/Qrcode'
+defineOptions({
+  name: 'ImgPage'
+})
+const QRIcon = useIcon({ icon: 'ic:round-qr-code' })
+const DeleteIcon = useIcon({ icon: 'ep:delete' })
+const DownLoadIcon = useIcon({ icon: 'ep:download' })
+const copyIcon = useIcon({ icon: 'ep:document-copy' })
+const EditIcon = useIcon({ icon: 'ep:edit' })
+const id = ref<string>('')
+const QrVisible = ref<boolean>(false)
+const QrSrc = ref<string>('')
+const { getStorage } = useStorage()
+const searchParams = ref({})
+const setSearchParams = (params: any) => {
+  searchParams.value = params
+  getList()
+}
+const currentRow = ref<ImgData>({
+  bannerUrl: ''
+})
+const formState = ref<string>('add')
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const { currentPage, pageSize } = tableState
+    const res = await getTableListApi({
+      pageNum: unref(currentPage),
+      pageSize: unref(pageSize),
+      ...unref(searchParams)
+    })
+    return {
+      list: res.data.list,
+      total: Number(res.data.totalCount)
+    }
+  },
+  fetchDelApi: async () => {
+    const res = await delTableListApi(unref(id))
+    return !!res
+  }
+})
+const { loading, dataList, total, currentPage, pageSize } = tableState
+const { getList, delList, setColumn } = tableMethods
+
+getList()
+
+useEmitt({
+  name: 'getList',
+  callback: (type: string) => {
+    if (type === 'add') {
+      currentPage.value = 1
+    }
+    getList()
+  }
+})
+
+const { t } = useI18n()
+const appStore = usePageStore()
+const dialogVisible = ref(false)
+// const fileList = ref([])
+const crudSchemas: CrudSchema[] = [
+  {
+    field: 'bannerType',
+    label: '类型',
+    minWidth: 120,
+    table: {
+      hidden: false,
+      formatter: (_: Recordable, _colomun, cellValue: string) => {
+        return cellValue
+          ? cellValue === '0'
+            ? '首页'
+            : cellValue === '1'
+            ? '中间'
+            : cellValue === '2'
+            ? '底部'
+            : ''
+          : ''
+      }
+    },
+    form: {
+      component: 'Select',
+      componentProps: {
+        options: [
+          {
+            label: '首页',
+            value: '0'
+          },
+          {
+            label: '中间',
+            value: '1'
+          },
+          {
+            label: '底部',
+            value: '2'
+          }
+        ]
+      }
+    }
+  },
+  {
+    field: 'bannerUrl',
+    label: '图片地址',
+    minWidth: 120,
+    search: {
+      hidden: true
+    },
+    table: {
+      hidden: false
+    },
+    form: {
+      colProps: {
+        span: 24
+      },
+      component: 'Upload',
+      componentProps: {
+        action: '/api/sysApk/uploadFile',
+        limit: 1,
+        class: 'filePageUploader',
+        fileList: currentRow.value.bannerUrl
+          ? [
+              {
+                url: currentRow.value.bannerUrl
+              }
+            ]
+          : [],
+        headers: {
+          token: getStorage('token')
+        },
+        onSuccess: (response) => {
+          currentRow.value.bannerUrl = response.data.virtualPath
+        },
+        onExceed: (_files, responses) => {
+          currentRow.value.bannerUrl = responses[0].response.data.virtualPath
+        },
+        slots: {
+          default: () => <ElButton type="primary">上传文件</ElButton>
+        }
+      }
+    }
+  },
+  {
+    field: 'bannerTarget',
+    label: '跳转地址',
+    minWidth: 120,
+    search: {
+      hidden: true
+    },
+    table: {
+      hidden: false
+    },
+    form: {
+      colProps: {
+        span: 24
+      }
+    }
+  },
+  {
+    field: 'remark',
+    label: '备注',
+    minWidth: 120,
+    search: {
+      hidden: true
+    },
+    table: {
+      hidden: false
+    },
+    form: {
+      colProps: {
+        span: 24
+      },
+      componentProps: {
+        type: 'textarea'
+      }
+    }
+  },
+  {
+    field: 'createTime',
+    label: '创建时间',
+    minWidth: 160,
+    table: {
+      hidden: false
+    },
+    search: {
+      hidden: true
+    },
+    form: {
+      hidden: true
+    }
+  },
+  {
+    field: 'action',
+    width: '250px',
+    label: '操作',
+    search: {
+      hidden: true
+    },
+    form: {
+      hidden: true
+    },
+    detail: {
+      hidden: true
+    },
+    table: {
+      hidden: false,
+      fixed: 'right',
+      slots: {
+        default: (data: any) => {
+          return (
+            <ElButtonGroup>
+              <ElTooltip content="编辑">
+                <ElButton text icon={EditIcon} onClick={() => handleEdit(data.row)} />
+              </ElTooltip>
+              <ElTooltip content="二维码">
+                <ElButton text icon={QRIcon} onClick={() => showQrCode(data.row)} />
+              </ElTooltip>
+              <ElTooltip content="下载">
+                <ElButton text icon={DownLoadIcon} onClick={() => downloadFile(data.row)} />
+              </ElTooltip>
+              <ElTooltip content="复制链接">
+                <ElButton text icon={copyIcon} onClick={() => onCopy(data.row)} />
+              </ElTooltip>
+              <ElTooltip content="删除">
+                <ElButton text icon={DeleteIcon} type="danger" onClick={() => delData(data.row)} />
+              </ElTooltip>
+            </ElButtonGroup>
+          )
+        }
+      }
+    }
+  }
+]
+
+// @ts-ignore
+const getSchemas = () => {
+  let localSchemas = appStore.getPageData['ImgPage']
+  if (localSchemas && localSchemas.schemas) {
+    let localSchemasArr = localSchemas.schemas
+    for (let i = 0; i < localSchemasArr.length; i++) {
+      let item = localSchemasArr[i]
+      let index = crudSchemas.findIndex((e) => {
+        return e.field == item.field
+      })
+      if (index > 0) {
+        set(crudSchemas[index], 'table.hidden', item.table.hidden)
+      }
+    }
+  }
+}
+getSchemas()
+let allSchemas = useCrudSchemas(crudSchemas).allSchemas
+// 修改列设置后调用
+const setSchemas = (schemas: CrudSchema[]) => {
+  let arr = schemas.map((item) => {
+    return {
+      field: item.field,
+      path: 'hidden',
+      value: item.table ? item.table.hidden : false
+    }
+  })
+  setColumn(arr)
+}
+const writeRef = ref<ComponentRef<typeof Write>>()
+
+const handleEdit = (row: ImgData) => {
+  currentRow.value = row
+  formState.value = 'edit'
+  dialogVisible.value = true
+}
+
+const save = async () => {
+  const write = unref(writeRef)
+  const formData = await write?.submit()
+  if (formData) {
+    delLoading.value = true
+    try {
+      if (formData.id) {
+        let res = await updateTableApi(formData)
+        if (res) {
+          currentPage.value = 1
+          getList()
+        }
+      } else {
+        let res = await saveTableApi(formData)
+        if (res) {
+          currentPage.value = 1
+          getList()
+        }
+      }
+    } catch (error) {
+    } finally {
+      delLoading.value = false
+      dialogVisible.value = false
+    }
+  }
+}
+
+const delLoading = ref(false)
+
+const delData = async (row: ImgData) => {
+  if (!row.id) return
+  id.value = row?.id
+  delLoading.value = true
+  await delList(unref(id).length).finally(() => {
+    delLoading.value = false
+  })
+}
+
+const showQrCode = (row: ImgData) => {
+  QrSrc.value = row.bannerUrl
+  QrVisible.value = true
+}
+
+const downloadFile = (row: ImgData) => {
+  window.open(row.bannerUrl)
+}
+
+const onCopy = (row: ImgData) => {
+  navigator.clipboard.writeText(row.bannerUrl).then(() => {
+    ElMessage({
+      message: '复制成功!',
+      type: 'success'
+    })
+  })
+}
+
+const handleAdd = () => {
+  formState.value = 'add'
+  currentRow.value = {
+    bannerUrl: ''
+  }
+  dialogVisible.value = true
+}
+</script>
+
+<template>
+  <ContentWrap>
+    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
+
+    <div class="mb-10px">
+      <el-button type="primary" @click="handleAdd">上传文件</el-button>
+      <TableSetting page="ImgPage" :data="crudSchemas" @set-schemas="setSchemas" />
+    </div>
+
+    <Table
+      v-model:pageSize="pageSize"
+      v-model:currentPage="currentPage"
+      :columns="allSchemas.tableColumns"
+      :data="dataList"
+      :loading="loading"
+      :pagination="{
+        total: total
+      }"
+      @register="tableRegister"
+    />
+  </ContentWrap>
+  <Dialog v-model="dialogVisible" :title="formState == 'add' ? '新增文件' : '编辑文件'">
+    <Write ref="writeRef" :form-schema="allSchemas.formSchema" :current-row="currentRow" />
+    <template #footer>
+      <ElButton type="primary" :loading="delLoading" @click="save">
+        {{ t('exampleDemo.save') }}
+      </ElButton>
+      <ElButton @click="dialogVisible = false">{{ t('dialogDemo.close') }}</ElButton>
+    </template>
+  </Dialog>
+  <Dialog v-model="QrVisible" width="430px" title="二维码">
+    <Qrcode
+      :width="395"
+      :options="{
+        color: {
+          dark: '#55D187',
+          light: '#ffffff'
+        }
+      }"
+      :text="QrSrc"
+    />
+    <template #footer>
+      <ElButton @click="QrVisible = false">{{ t('dialogDemo.close') }}</ElButton>
+    </template>
+  </Dialog>
+</template>
+@/hooks/event/useEmitt
+
+<style lang="less">
+.uploadBtn {
+  display: inline-block;
+  margin-right: 12px;
+}
+.filePageUploader {
+  width: 100%;
+}
+</style>

+ 65 - 0
src/views/Manage/Img/components/Write.vue

@@ -0,0 +1,65 @@
+<script setup lang="ts">
+import { Form, FormSchema } from '@/components/Form'
+import { useForm } from '@/hooks/web/useForm'
+import { PropType, reactive, watch } from 'vue'
+import { ImgData } from '@/api/manage/types'
+import { useValidator } from '@/hooks/web/useValidator'
+
+const { required } = useValidator()
+
+const props = defineProps({
+  currentRow: {
+    type: Object as PropType<ImgData>,
+    default: () => undefined
+  },
+  formSchema: {
+    type: Array as PropType<FormSchema[]>,
+    default: () => []
+  }
+})
+
+const rules = reactive({
+  bannerUrl: [required()]
+})
+
+const { formRegister, formMethods } = useForm()
+const { setValues, getFormData, getElFormExpose, setSchema } = formMethods
+
+const submit = async () => {
+  const elForm = await getElFormExpose()
+  const valid = await elForm?.validate().catch((err) => {
+    console.log(err)
+  })
+  if (valid) {
+    const formData = await getFormData()
+    return formData
+  }
+}
+
+const resetForm = async () => {
+  const elForm = await getElFormExpose()
+  elForm?.resetFields()
+}
+
+watch(
+  () => props.currentRow,
+  (currentRow) => {
+    if (!currentRow) return
+    setValues(currentRow)
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+
+defineExpose({
+  submit,
+  setSchema,
+  setValues
+})
+</script>
+
+<template>
+  <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+</template>

+ 1 - 1
src/views/Manage/News/NewsAdd.vue

@@ -5,7 +5,7 @@ import { ref, unref } from 'vue'
 import { ElButton } from 'element-plus'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useRouter } from 'vue-router'
-import { saveTableApi } from '@/api/table'
+import { saveTableApi } from '@/api/manage/news'
 import { useEmitt } from '@/hooks/event/useEmitt'
 
 const { emitter } = useEmitt()

+ 3 - 3
src/views/Manage/News/NewsDetail.vue

@@ -4,8 +4,8 @@ import { ContentDetailWrap } from '@/components/ContentDetailWrap'
 import { ref } from 'vue'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useRouter, useRoute } from 'vue-router'
-import { getTableDetApi } from '@/api/table'
-import { TableData } from '@/api/table/types'
+import { getTableDetApi } from '@/api/manage/news'
+import { NewsTableData } from '@/api/manage/types'
 import { ElButton } from 'element-plus'
 
 const { push, go } = useRouter()
@@ -14,7 +14,7 @@ const { query } = useRoute()
 
 const { t } = useI18n()
 
-const currentRow = ref<Nullable<TableData>>(null)
+const currentRow = ref<Nullable<NewsTableData>>(null)
 
 const getTableDet = async () => {
   const res = await getTableDetApi(query.id as string)

+ 4 - 4
src/views/Manage/News/NewsEdit.vue

@@ -5,8 +5,8 @@ import { ref, unref } from 'vue'
 import { ElButton } from 'element-plus'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useRouter, useRoute } from 'vue-router'
-import { saveTableApi, getTableDetApi } from '@/api/table'
-import { TableData } from '@/api/table/types'
+import { updateTableApi, getTableDetApi } from '@/api/manage/news'
+import { NewsTableData } from '@/api/manage/types'
 import { useEmitt } from '@/hooks/event/useEmitt'
 
 const { emitter } = useEmitt()
@@ -17,7 +17,7 @@ const { query } = useRoute()
 
 const { t } = useI18n()
 
-const currentRow = ref<Nullable<TableData>>(null)
+const currentRow = ref<Nullable<NewsTableData>>(null)
 
 const getTableDet = async () => {
   const res = await getTableDetApi(query.id as string)
@@ -37,7 +37,7 @@ const save = async () => {
   const formData = await write?.submit()
   if (formData) {
     loading.value = true
-    const res = await saveTableApi(formData)
+    const res = await updateTableApi(formData)
       .catch(() => {})
       .finally(() => {
         loading.value = false

+ 69 - 127
src/views/Manage/News/NewsPage.vue

@@ -2,11 +2,11 @@
 import { ContentWrap } from '@/components/ContentWrap'
 import { Search } from '@/components/Search'
 import { useI18n } from '@/hooks/web/useI18n'
-import { ElButton, ElTag } from 'element-plus'
+import { ElButton, ElButtonGroup, ElTooltip } from 'element-plus'
 import { Table } from '@/components/Table'
-import { getTableListApi, delTableListApi } from '@/api/table'
+import { getTableListApi, delTableListApi } from '@/api/manage/news'
 import { useTable } from '@/hooks/web/useTable'
-import { TableData } from '@/api/table/types'
+import { NewsTableData } from '@/api/manage/types'
 import { ref, unref } from 'vue'
 import { useRouter } from 'vue-router'
 import { useEmitt } from '@/hooks/event/useEmitt'
@@ -14,14 +14,17 @@ import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
 import { TableSetting } from '@/components/TableSetting'
 import { usePageStore } from '@/store/modules/page'
 import { set } from 'lodash-es'
-
+import { useIcon } from '@/hooks/web/useIcon'
+const EditIcon = useIcon({ icon: 'ep:edit' })
+const DetailIcon = useIcon({ icon: 'ep:document' })
+const DeleteIcon = useIcon({ icon: 'ep:delete' })
 defineOptions({
   name: 'NewsPage'
 })
 
 const { push } = useRouter()
 
-const ids = ref<string[]>([])
+const id = ref<string>('')
 
 const searchParams = ref({})
 const setSearchParams = (params: any) => {
@@ -33,22 +36,22 @@ const { tableRegister, tableState, tableMethods } = useTable({
   fetchDataApi: async () => {
     const { currentPage, pageSize } = tableState
     const res = await getTableListApi({
-      pageIndex: unref(currentPage),
+      pageNum: unref(currentPage),
       pageSize: unref(pageSize),
       ...unref(searchParams)
     })
     return {
       list: res.data.list,
-      total: res.data.total
+      total: Number(res.data.totalCount)
     }
   },
   fetchDelApi: async () => {
-    const res = await delTableListApi(unref(ids))
+    const res = await delTableListApi(unref(id))
     return !!res
   }
 })
 const { loading, dataList, total, currentPage, pageSize } = tableState
-const { getList, getElTableExpose, delList, setColumn } = tableMethods
+const { getList, delList, setColumn } = tableMethods
 
 getList()
 
@@ -66,41 +69,9 @@ const { t } = useI18n()
 const appStore = usePageStore()
 const crudSchemas: CrudSchema[] = [
   {
-    field: 'selection',
-    search: {
-      hidden: true
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    },
-    table: {
-      type: 'selection',
-      hidden: false
-    }
-  },
-  {
-    field: 'index',
-    label: t('tableDemo.index'),
-    type: 'index',
-    search: {
-      hidden: true
-    },
-    table: {
-      hidden: false
-    },
-    form: {
-      hidden: true
-    },
-    detail: {
-      hidden: true
-    }
-  },
-  {
-    field: 'title',
-    label: t('tableDemo.title'),
+    field: 'articleTitle',
+    label: '文章标题',
+    minWidth: 150,
     search: {
       component: 'Input'
     },
@@ -118,35 +89,31 @@ const crudSchemas: CrudSchema[] = [
     }
   },
   {
-    field: 'author',
-    label: t('tableDemo.author'),
-    search: {
-      hidden: true
+    field: 'introduce',
+    label: '文章简介',
+    minWidth: 180,
+    form: {
+      component: 'Input',
+      componentProps: {
+        type: 'textarea'
+      },
+      colProps: {
+        span: 24
+      }
     },
-    table: {
-      hidden: false
-    }
-  },
-  {
-    field: 'display_time',
-    label: t('tableDemo.displayTime'),
     search: {
       hidden: true
     },
     table: {
       hidden: false
-    },
-    form: {
-      component: 'DatePicker',
-      componentProps: {
-        type: 'datetime',
-        valueFormat: 'YYYY-MM-DD HH:mm:ss'
-      }
     }
   },
   {
-    field: 'importance',
-    label: t('tableDemo.importance'),
+    field: 'weight',
+    label: '权重',
+    width: 100,
+    headerAlign: 'center',
+    align: 'right',
     search: {
       hidden: true
     },
@@ -154,50 +121,16 @@ const crudSchemas: CrudSchema[] = [
       hidden: false
     },
     form: {
-      component: 'Select',
-      componentProps: {
-        style: {
-          width: '100%'
-        },
-        options: [
-          {
-            label: '重要',
-            value: 3
-          },
-          {
-            label: '良好',
-            value: 2
-          },
-          {
-            label: '一般',
-            value: 1
-          }
-        ]
-      }
-    },
-    detail: {
-      slots: {
-        default: (data: any) => {
-          return (
-            <ElTag
-              type={
-                data.importance === 1 ? 'success' : data.importance === 2 ? 'warning' : 'danger'
-              }
-            >
-              {data.importance === 1
-                ? t('tableDemo.important')
-                : data.importance === 2
-                ? t('tableDemo.good')
-                : t('tableDemo.commonly')}
-            </ElTag>
-          )
-        }
-      }
+      component: 'InputNumber',
+      value: 0
     }
   },
   {
-    field: 'pageviews',
-    label: t('tableDemo.pageviews'),
+    field: 'articleViews',
+    label: '当前浏览量',
+    width: 100,
+    headerAlign: 'center',
+    align: 'right',
     search: {
       hidden: true
     },
@@ -210,8 +143,9 @@ const crudSchemas: CrudSchema[] = [
     }
   },
   {
-    field: 'content',
-    label: t('exampleDemo.content'),
+    field: 'articleContent',
+    label: '文章内容',
+    minWidth: 120,
     search: {
       hidden: true
     },
@@ -233,9 +167,21 @@ const crudSchemas: CrudSchema[] = [
       }
     }
   },
+  {
+    field: 'remark',
+    label: '备注',
+    minWidth: 120,
+    search: {
+      hidden: true
+    },
+    table: {
+      hidden: false
+    }
+  },
   {
     field: 'action',
-    width: '260px',
+    width: '160px',
+    fixed: 'right',
     label: t('tableDemo.action'),
     search: {
       hidden: true
@@ -251,17 +197,17 @@ const crudSchemas: CrudSchema[] = [
       slots: {
         default: (data: any) => {
           return (
-            <>
-              <ElButton type="primary" onClick={() => action(data.row, 'edit')}>
-                {t('exampleDemo.edit')}
-              </ElButton>
-              <ElButton type="success" onClick={() => action(data.row, 'detail')}>
-                {t('exampleDemo.detail')}
-              </ElButton>
-              <ElButton type="danger" onClick={() => delData(data.row)}>
-                {t('exampleDemo.del')}
-              </ElButton>
-            </>
+            <ElButtonGroup>
+              <ElTooltip content="编辑">
+                <ElButton text icon={EditIcon} onClick={() => action(data.row, 'edit')} />
+              </ElTooltip>
+              <ElTooltip content="详情">
+                <ElButton text icon={DetailIcon} onClick={() => action(data.row, 'detail')} />
+              </ElTooltip>
+              <ElTooltip content="删除">
+                <ElButton text icon={DeleteIcon} type="danger" onClick={() => delData(data.row)} />
+              </ElTooltip>
+            </ElButtonGroup>
           )
         }
       }
@@ -305,16 +251,15 @@ const AddAction = () => {
 
 const delLoading = ref(false)
 
-const delData = async (row: TableData | null) => {
-  const elTableExpose = await getElTableExpose()
-  ids.value = row ? [row.id] : elTableExpose?.getSelectionRows().map((v: TableData) => v.id) || []
+const delData = async (row: NewsTableData) => {
+  id.value = row?.id || ''
   delLoading.value = true
-  await delList(unref(ids).length).finally(() => {
+  await delList(unref(id).length).finally(() => {
     delLoading.value = false
   })
 }
 
-const action = (row: TableData, type: string) => {
+const action = (row: NewsTableData, type: string) => {
   push(`/manage/news-${type}?id=${row.id}`)
 }
 </script>
@@ -325,9 +270,6 @@ const action = (row: TableData, type: string) => {
 
     <div class="mb-10px">
       <ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
-      <ElButton :loading="delLoading" type="danger" @click="delData(null)">
-        {{ t('exampleDemo.del') }}
-      </ElButton>
       <TableSetting page="NewsPage" :data="crudSchemas" @set-schemas="setSchemas" />
     </div>
 

+ 22 - 26
src/views/Manage/News/components/Detail.vue

@@ -1,6 +1,6 @@
 <script setup lang="tsx">
 import { PropType, reactive } from 'vue'
-import type { TableData } from '@/api/table/types'
+import type { NewsTableData } from '@/api/manage/types'
 import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
 import { useI18n } from '@/hooks/web/useI18n'
 import { ElTag } from 'element-plus'
@@ -9,55 +9,51 @@ const { t } = useI18n()
 
 defineProps({
   currentRow: {
-    type: Object as PropType<Nullable<TableData>>,
+    type: Object as PropType<Nullable<NewsTableData>>,
     default: () => null
   }
 })
 
 const schema = reactive<DescriptionsSchema[]>([
   {
-    field: 'title',
-    label: '新闻标题',
+    field: 'articleTitle',
+    label: '文章标题',
     span: 24
   },
   {
-    field: 'author',
-    label: t('exampleDemo.author')
+    field: 'introduce',
+    label: '文章简介',
+    span: 24
+  },
+  {
+    field: 'weight',
+    label: '权重'
   },
   {
-    field: 'display_time',
-    label: t('exampleDemo.displayTime')
+    field: 'articleViews',
+    label: '当前浏览量'
   },
   {
-    field: 'importance',
-    label: t('exampleDemo.importance'),
+    field: 'articlePic',
+    label: '文章展图',
     slots: {
       default: (data: any) => {
-        return (
-          <ElTag
-            type={data.importance === 1 ? 'success' : data.importance === 2 ? 'warning' : 'danger'}
-          >
-            {data.importance === 1
-              ? t('tableDemo.important')
-              : data.importance === 2
-              ? t('tableDemo.good')
-              : t('tableDemo.commonly')}
-          </ElTag>
-        )
+        return <img style="width:100%" src={data.articlePic}></img>
       }
     }
   },
   {
-    field: 'pageviews',
-    label: t('exampleDemo.pageviews')
+    field: 'remark',
+    label: '备注',
+    span: 24
   },
   {
-    field: 'content',
-    label: t('exampleDemo.content'),
+    field: 'articleContent',
+    label: '文章内容',
     span: 24,
     slots: {
       default: (data: any) => {
-        return <div innerHTML={data.content}></div>
+        return <div innerHTML={data.articleContent}></div>
       }
     }
   }

+ 121 - 67
src/views/Manage/News/components/Write.vue

@@ -1,31 +1,30 @@
 <script setup lang="tsx">
 import { Form, FormSchema } from '@/components/Form'
 import { useForm } from '@/hooks/web/useForm'
-import { PropType, reactive, watch } from 'vue'
-import { TableData } from '@/api/table/types'
-import { useI18n } from '@/hooks/web/useI18n'
+import { PropType, reactive, watch, ref } from 'vue'
+import { NewsTableData } from '@/api/manage/types'
 import { useValidator } from '@/hooks/web/useValidator'
 import { IDomEditor } from '@wangeditor/editor'
-import { ElButton } from 'element-plus'
+import { useStorage } from '@/hooks/web/useStorage'
 
 const { required } = useValidator()
-
+const { getStorage } = useStorage()
 const props = defineProps({
   currentRow: {
-    type: Object as PropType<Nullable<TableData>>,
+    type: Object as PropType<Nullable<NewsTableData>>,
     default: () => null
   }
 })
 
-const { t } = useI18n()
+const articlePic = ref(props.currentRow?.articlePic)
 
 const { formRegister, formMethods } = useForm()
 const { setValues, getFormData, getElFormExpose, setSchema } = formMethods
-
+type InsertFnType = (url: string, alt: string, href: string) => void
 const schema = reactive<FormSchema[]>([
   {
-    field: 'title',
-    label: t('exampleDemo.title'),
+    field: 'articleTitle',
+    label: '文章标题',
     component: 'Input',
     formItemProps: {
       rules: [required()]
@@ -35,55 +34,32 @@ const schema = reactive<FormSchema[]>([
     }
   },
   {
-    field: 'author',
-    label: t('exampleDemo.author'),
+    field: 'introduce',
+    label: '文章简介',
     component: 'Input',
     formItemProps: {
       rules: [required()]
     },
-    componentProps: {
-      placeholder: '请输入作者'
-    }
-  },
-  {
-    field: 'display_time',
-    label: t('exampleDemo.displayTime'),
-    component: 'DatePicker',
-    componentProps: {
-      type: 'datetime',
-      valueFormat: 'YYYY-MM-DD HH:mm:ss'
+    colProps: {
+      span: 24
     },
-    formItemProps: {
-      rules: [required()]
+    componentProps: {
+      type: 'textarea',
+      placeholder: '请输入'
     }
   },
   {
-    field: 'importance',
-    label: t('exampleDemo.importance'),
-    component: 'Select',
+    field: 'weight',
+    label: '权重',
+    component: 'InputNumber',
+    value: 0,
     formItemProps: {
       rules: [required()]
-    },
-    componentProps: {
-      options: [
-        {
-          label: '重要',
-          value: 3
-        },
-        {
-          label: '良好',
-          value: 2
-        },
-        {
-          label: '一般',
-          value: 1
-        }
-      ]
     }
   },
   {
-    field: 'pageviews',
-    label: t('exampleDemo.pageviews'),
+    field: 'articleViews',
+    label: '当前浏览量',
     component: 'InputNumber',
     value: 0,
     formItemProps: {
@@ -91,48 +67,95 @@ const schema = reactive<FormSchema[]>([
     }
   },
   {
-    field: 'imageUrl',
+    field: 'articlePic',
     component: 'Upload',
-    label: '图',
+    label: '文章展图',
     componentProps: {
-      action: 'https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15',
+      action: '/api/sysApk/uploadFile',
       showFileList: false,
-      onSuccess: (_response, uploadFile) => {
-        console.log(uploadFile)
+      class: 'NewsUploader',
+      headers: {
+        token: getStorage('token')
+      },
+      onSuccess: (response) => {
+        setValues({
+          articlePic: response.data.virtualPath
+        })
+        articlePic.value = response.data.virtualPath
       },
       slots: {
-        default: () => <ElButton type="primary">Click to upload</ElButton>,
-        tip: () => <div class="el-upload__tip">jpg/png files with a size less than 500KB.</div>
+        default: () => (
+          <>
+            {articlePic.value ? <img src={articlePic.value} class="avatar" /> : null}
+            {!articlePic.value ? (
+              <Icon class="avatar-uploader-icon" icon="ep:plus" size={28}></Icon>
+            ) : null}
+          </>
+        )
       }
     }
   },
   {
-    field: 'content',
+    field: 'remark',
+    label: '备注',
+    component: 'Input',
+    formItemProps: {
+      rules: [required()]
+    },
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      type: 'textarea',
+      placeholder: '请输入'
+    }
+  },
+  {
+    field: 'articleContent',
+    label: '文章内容',
     component: 'Editor',
     colProps: {
       span: 24
     },
     componentProps: {
       defaultHtml: '',
+      editorConfig: {
+        MENU_CONF: {
+          uploadImage: {
+            server: '/api/sysApk/uploadFile',
+            fieldName: 'file',
+            headers: {
+              token: getStorage('token')
+            },
+            customInsert(res: any, insertFn: InsertFnType) {
+              // 从 res 中找到 url alt href ,然后插入图片
+              insertFn(res.data.virtualPath, res.fileName, '')
+            }
+          },
+          uploadVideo: {
+            server: '/api/sysApk/uploadFile',
+            fieldName: 'file',
+            headers: {
+              token: getStorage('token')
+            },
+            customInsert(res: any, insertFn: InsertFnType) {
+              // 从 res 中找到 url alt href ,然后插入视频
+              insertFn(res.data.virtualPath, res.fileName, '')
+            }
+          }
+        }
+      },
       // @ts-ignore
       onChange: (edit: IDomEditor) => {
         setValues({
-          content: edit.getHtml()
+          articleContent: edit.getHtml()
         })
       }
-    },
-    label: t('exampleDemo.content')
+    }
   }
 ])
 
-const rules = reactive({
-  title: [required()],
-  author: [required()],
-  importance: [required()],
-  pageviews: [required()],
-  display_time: [required()],
-  content: [required()]
-})
+const rules = reactive({})
 
 const submit = async () => {
   const elForm = await getElFormExpose()
@@ -152,11 +175,12 @@ watch(
     setValues(currentRow)
     setSchema([
       {
-        field: 'content',
+        field: 'articleContent',
         path: 'componentProps.defaultHtml',
-        value: currentRow.content
+        value: currentRow.articleContent
       }
     ])
+    articlePic.value = currentRow.articlePic
   },
   {
     deep: true,
@@ -172,3 +196,33 @@ defineExpose({
 <template>
   <Form :rules="rules" @register="formRegister" :schema="schema" />
 </template>
+
+<style scoped>
+.NewsUploader .avatar {
+  width: 178px;
+  height: 178px;
+  display: block;
+}
+</style>
+
+<style lang="less">
+.NewsUploader .el-upload {
+  border: 1px dashed var(--el-border-color);
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  transition: var(--el-transition-duration-fast);
+  img {
+    width: 178px;
+    height: 178px;
+  }
+  .avatar-uploader-icon {
+    font-size: 28px;
+    color: #8c939d;
+    width: 178px;
+    height: 178px;
+    text-align: center;
+  }
+}
+</style>

+ 0 - 1
src/views/Manage/Product/components/Detail.vue

@@ -50,7 +50,6 @@ const schema = reactive<DescriptionsSchema[]>([
   {
     field: 'remark',
     label: '备注',
-
     span: 24
   },