From 389c73d834ad710c1398581314cb96787078b954 Mon Sep 17 00:00:00 2001
From: dhb52 <dhb52@126.com>
Date: Sat, 15 Apr 2023 15:59:33 +0800
Subject: [PATCH] =?UTF-8?q?refactor:=20MP=E6=B6=88=E6=81=AF=E7=AE=A1?=
 =?UTF-8?q?=E7=90=86-=E6=8B=86=E5=88=86=E7=BB=84=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/views/mp/material/components/Upload.vue   |  70 +++
 .../mp/material/components/UploadVideo.vue    | 127 ++++++
 .../mp/material/components/VideoTable.vue     |  61 +++
 .../mp/material/components/VoiceTable.vue     |  53 +++
 .../mp/material/components/Waterfall.vue      |  83 ++++
 src/views/mp/material/components/upload.ts    |  73 +++
 src/views/mp/material/index.vue               | 424 ++----------------
 7 files changed, 507 insertions(+), 384 deletions(-)
 create mode 100644 src/views/mp/material/components/Upload.vue
 create mode 100644 src/views/mp/material/components/UploadVideo.vue
 create mode 100644 src/views/mp/material/components/VideoTable.vue
 create mode 100644 src/views/mp/material/components/VoiceTable.vue
 create mode 100644 src/views/mp/material/components/Waterfall.vue
 create mode 100644 src/views/mp/material/components/upload.ts

diff --git a/src/views/mp/material/components/Upload.vue b/src/views/mp/material/components/Upload.vue
new file mode 100644
index 00000000..541dbc20
--- /dev/null
+++ b/src/views/mp/material/components/Upload.vue
@@ -0,0 +1,70 @@
+<template>
+  <el-upload
+    :action="UPLOAD_URL"
+    :headers="HEADERS"
+    multiple
+    :limit="1"
+    :file-list="fileList"
+    :data="uploadData"
+    :on-progress="() => (uploading = true)"
+    :before-upload="beforeUpload"
+    :on-success="handleUploadSuccess"
+  >
+    <el-button type="primary" plain :loading="uploading" :disabled="uploading">
+      {{ uploading ? '正在上传' : '点击上传' }}
+    </el-button>
+    <template #tip>
+      <span class="el-upload__tip" style="margin-left: 5px">
+        <slot></slot>
+      </span>
+    </template>
+  </el-upload>
+</template>
+
+<script setup lang="ts">
+import type { UploadProps, UploadUserFile } from 'element-plus'
+import {
+  HEADERS,
+  UPLOAD_URL,
+  UploadData,
+  MaterialType,
+  beforeImageUpload,
+  beforeVoiceUpload
+} from './upload'
+
+const message = useMessage()
+
+const props = defineProps<{ type: boolean }>()
+
+const fileList = ref<UploadUserFile[]>([])
+const emit = defineEmits<{
+  (e: 'uploaded', v: void)
+}>()
+
+const uploadData: UploadData = reactive({
+  type: MaterialType.Image,
+  title: '',
+  introduction: ''
+})
+const uploading = ref(false)
+
+const beforeUpload = props.type == MaterialType.Image ? beforeImageUpload : beforeVoiceUpload
+
+const handleUploadSuccess: UploadProps['onSuccess'] = (res: any) => {
+  if (res.code !== 0) {
+    message.alertError('上传出错:' + res.msg)
+    return false
+  }
+
+  // 清空上传时的各种数据
+  fileList.value = []
+  uploadData.title = ''
+  uploadData.introduction = ''
+
+  message.notifySuccess('上传成功')
+  uploading.value = false
+  emit('uploaded')
+}
+</script>
+
+<style scoped></style>
diff --git a/src/views/mp/material/components/UploadVideo.vue b/src/views/mp/material/components/UploadVideo.vue
new file mode 100644
index 00000000..5da7d980
--- /dev/null
+++ b/src/views/mp/material/components/UploadVideo.vue
@@ -0,0 +1,127 @@
+<template>
+  <el-dialog title="新建视频" v-model="showDialog" width="600px" destroy-on-close>
+    <el-upload
+      :action="UPLOAD_URL"
+      :headers="HEADERS"
+      multiple
+      :limit="1"
+      :file-list="fileList"
+      :data="uploadData"
+      :before-upload="beforeVideoUpload"
+      :on-progress="() => (uploading = true)"
+      :on-success="handleUploadSuccess"
+      ref="uploadVideoRef"
+      :auto-upload="false"
+      class="mb-5"
+    >
+      <template #trigger>
+        <el-button type="primary" plain>选择视频</el-button>
+      </template>
+      <span class="el-upload__tip" style="margin-left: 10px"
+        >格式支持 MP4,文件大小不超过 10MB</span
+      >
+    </el-upload>
+    <el-divider />
+    <el-form :model="uploadData" :rules="uploadRules" ref="uploadFormRef" v-loading="uploading">
+      <el-form-item label="标题" prop="title">
+        <el-input
+          v-model="uploadData.title"
+          placeholder="标题将展示在相关播放页面,建议填写清晰、准确、生动的标题"
+        />
+      </el-form-item>
+      <el-form-item label="描述" prop="introduction">
+        <el-input
+          :rows="3"
+          type="textarea"
+          v-model="uploadData.introduction"
+          placeholder="介绍语将展示在相关播放页面,建议填写简洁明确、有信息量的内容"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="showDialog = false">取 消</el-button>
+      <el-button type="primary" @click="submitVideo" :loading="uploading" :disabled="uploading"
+        >提 交</el-button
+      >
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import type {
+  FormInstance,
+  FormRules,
+  UploadInstance,
+  UploadProps,
+  UploadUserFile
+} from 'element-plus'
+import { HEADERS, UploadData, UPLOAD_URL, beforeVideoUpload, MaterialType } from './upload'
+
+const message = useMessage()
+
+const uploadRules: FormRules = {
+  title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
+  introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }]
+}
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    default: false
+  }
+})
+const emit = defineEmits<{
+  (e: 'update:modelValue', v: boolean)
+  (e: 'uploaded', v: void)
+}>()
+
+const showDialog = computed({
+  get() {
+    return props.modelValue
+  },
+  set(val) {
+    emit('update:modelValue', val)
+  }
+})
+
+const uploading = ref(false)
+
+const fileList = ref<UploadUserFile[]>([])
+
+const uploadData: UploadData = reactive({
+  type: MaterialType.Video,
+  title: '',
+  introduction: ''
+})
+
+const uploadFormRef = ref<FormInstance>()
+const uploadVideoRef = ref<UploadInstance>()
+
+const submitVideo = () => {
+  uploadFormRef.value?.validate((valid) => {
+    if (!valid) {
+      return false
+    }
+    uploadVideoRef.value?.submit()
+  })
+}
+
+const handleUploadSuccess: UploadProps['onSuccess'] = (res: any) => {
+  uploading.value = false
+  if (res.code !== 0) {
+    message.error('上传出错:' + res.msg)
+    return false
+  }
+
+  // 清空上传时的各种数据
+  fileList.value = []
+  uploadData.title = ''
+  uploadData.introduction = ''
+
+  showDialog.value = false
+  message.notifySuccess('上传成功')
+  emit('uploaded')
+}
+</script>
+
+<style scoped></style>
diff --git a/src/views/mp/material/components/VideoTable.vue b/src/views/mp/material/components/VideoTable.vue
new file mode 100644
index 00000000..3bd2fe00
--- /dev/null
+++ b/src/views/mp/material/components/VideoTable.vue
@@ -0,0 +1,61 @@
+<template>
+  <el-table :data="props.list" stripe border v-loading="props.loading" style="margin-top: 10px">
+    <el-table-column label="编号" align="center" prop="mediaId" />
+    <el-table-column label="文件名" align="center" prop="name" />
+    <el-table-column label="标题" align="center" prop="title" />
+    <el-table-column label="介绍" align="center" prop="introduction" />
+    <el-table-column label="视频" align="center">
+      <template #default="scope">
+        <WxVideoPlayer v-if="scope.row.url" :url="scope.row.url" />
+      </template>
+    </el-table-column>
+    <el-table-column
+      label="上传时间"
+      align="center"
+      :formatter="dateFormatter"
+      prop="createTime"
+      width="180"
+    >
+      <template #default="scope">
+        <span>{{ scope.row.createTime }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column label="操作" align="center" fixed="right">
+      <template #default="scope">
+        <el-button type="primary" link @click="handleDownload(scope.row.url)">
+          <Icon icon="ep:download" />下载
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          @click="emit('delete', scope.row.id)"
+          v-hasPermi="['mp:material:delete']"
+        >
+          <Icon icon="ep:delete" />删除
+        </el-button>
+      </template>
+    </el-table-column>
+  </el-table>
+</template>
+
+<script setup lang="ts">
+import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
+import { dateFormatter } from '@/utils/formatTime'
+
+const props = defineProps<{
+  list: any[]
+  loading: boolean
+}>()
+
+const emit = defineEmits<{
+  (e: 'delete', v: number)
+  (e: 'download', v: string)
+}>()
+
+// 下载文件
+const handleDownload = (url: string) => {
+  window.open(url, '_blank')
+}
+</script>
+
+<style scoped></style>
diff --git a/src/views/mp/material/components/VoiceTable.vue b/src/views/mp/material/components/VoiceTable.vue
new file mode 100644
index 00000000..44bd01da
--- /dev/null
+++ b/src/views/mp/material/components/VoiceTable.vue
@@ -0,0 +1,53 @@
+<template>
+  <el-table :data="props.list" stripe border v-loading="props.loading" style="margin-top: 10px">
+    <el-table-column label="编号" align="center" prop="mediaId" />
+    <el-table-column label="文件名" align="center" prop="name" />
+    <el-table-column label="语音" align="center">
+      <template #default="scope">
+        <WxVoicePlayer v-if="scope.row.url" :url="scope.row.url" />
+      </template>
+    </el-table-column>
+    <el-table-column
+      label="上传时间"
+      align="center"
+      prop="createTime"
+      :formatter="dateFormatter"
+      width="180"
+    >
+      <template #default="scope">
+        <span>{{ scope.row.createTime }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+      <template #default="scope">
+        <el-button type="primary" link @click="emit('delete', scope.row.id)">
+          <Icon icon="ep:download" />下载
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          @click="emit('delete', scope.row.id)"
+          v-hasPermi="['mp:material:delete']"
+        >
+          <Icon icon="ep:delete" />删除
+        </el-button>
+      </template>
+    </el-table-column>
+  </el-table>
+</template>
+
+<script setup lang="ts">
+import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
+import { dateFormatter } from '@/utils/formatTime'
+
+const props = defineProps<{
+  list: any[]
+  loading: boolean
+}>()
+
+const emit = defineEmits<{
+  (e: 'delete', v: number)
+}>()
+</script>
+
+<style scoped></style>
diff --git a/src/views/mp/material/components/Waterfall.vue b/src/views/mp/material/components/Waterfall.vue
new file mode 100644
index 00000000..118fc7bd
--- /dev/null
+++ b/src/views/mp/material/components/Waterfall.vue
@@ -0,0 +1,83 @@
+<template>
+  <div class="waterfall" v-loading="props.loading">
+    <div class="waterfall-item" v-for="item in props.list" :key="item.id">
+      <a target="_blank" :href="item.url">
+        <img class="material-img" :src="item.url" />
+        <div class="item-name">{{ item.name }}</div>
+      </a>
+      <el-row justify="center">
+        <el-button
+          type="danger"
+          circle
+          @click="emit('delete', item.id)"
+          v-hasPermi="['mp:material:delete']"
+        >
+          <Icon icon="ep:delete" />
+        </el-button>
+      </el-row>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+const props = defineProps<{
+  list: any[]
+  loading: boolean
+}>()
+
+const emit = defineEmits<{
+  (e: 'delete', v: number)
+}>()
+</script>
+
+<style lang="scss" scoped>
+/*瀑布流样式*/
+.waterfall {
+  width: 100%;
+  column-gap: 10px;
+  column-count: 5;
+  margin-top: 10px;
+  /* 芋道源码:增加 10px,避免顶着上面 */
+}
+
+.waterfall-item {
+  padding: 10px;
+  margin-bottom: 10px;
+  break-inside: avoid;
+  border: 1px solid #eaeaea;
+}
+
+.material-img {
+  width: 100%;
+}
+
+p {
+  line-height: 30px;
+}
+
+@media (min-width: 992px) and (max-width: 1300px) {
+  .waterfall {
+    column-count: 3;
+  }
+
+  p {
+    color: red;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+  .waterfall {
+    column-count: 2;
+  }
+
+  p {
+    color: orange;
+  }
+}
+
+@media (max-width: 767px) {
+  .waterfall {
+    column-count: 1;
+  }
+}
+</style>
diff --git a/src/views/mp/material/components/upload.ts b/src/views/mp/material/components/upload.ts
new file mode 100644
index 00000000..37441f5b
--- /dev/null
+++ b/src/views/mp/material/components/upload.ts
@@ -0,0 +1,73 @@
+import type { UploadProps, UploadRawFile } from 'element-plus'
+import { getAccessToken } from '@/utils/auth'
+
+const message = useMessage()
+
+const HEADERS = { Authorization: 'Bearer ' + getAccessToken() }
+const UPLOAD_URL = 'http://127.0.0.1:8000/upload/' //import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent'
+
+enum MaterialType {
+  Image = 'image',
+  Voice = 'voice',
+  Video = 'video'
+}
+
+interface UploadData {
+  type: MaterialType
+  title: string
+  introduction: string
+}
+
+const beforeUpload = (rawFile: UploadRawFile, materialType: MaterialType): boolean => {
+  let allowTypes: string[] = []
+  let maxSizeMB = 0
+  let name = ''
+
+  switch (materialType) {
+    case MaterialType.Image:
+      allowTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg']
+      maxSizeMB = 2
+      name = '图片'
+      break
+    case MaterialType.Voice:
+      allowTypes = ['audio/mp3', 'audio/mpeg', 'audio/wma', 'audio/wav', 'audio/amr']
+      maxSizeMB = 2
+      name = '图片'
+      break
+    case MaterialType.Video:
+      allowTypes = ['video/mp4']
+      maxSizeMB = 10
+      name = '视频'
+  }
+
+  if (!allowTypes.includes(rawFile.type)) {
+    message.error(`上传${name}格式不对!`)
+    return false
+  }
+
+  if (rawFile.size / 1024 / 1024 > maxSizeMB) {
+    message.error(`上传${name}大小不能超过${maxSizeMB}M!`)
+    return false
+  }
+
+  return true
+}
+
+const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
+  beforeUpload(rawFile, MaterialType.Image)
+
+const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
+  beforeUpload(rawFile, MaterialType.Voice)
+
+const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
+  beforeUpload(rawFile, MaterialType.Video)
+
+export {
+  HEADERS,
+  UPLOAD_URL,
+  MaterialType,
+  UploadData,
+  beforeImageUpload,
+  beforeVoiceUpload,
+  beforeVideoUpload
+}
diff --git a/src/views/mp/material/index.vue b/src/views/mp/material/index.vue
index c6d8055d..dd54cf19 100644
--- a/src/views/mp/material/index.vue
+++ b/src/views/mp/material/index.vue
@@ -12,47 +12,18 @@
   <ContentWrap>
     <el-tabs v-model="type" @tab-change="onTabChange">
       <!-- tab 1:图片  -->
-      <el-tab-pane name="image">
+      <el-tab-pane :name="MaterialType.Image">
         <template #label>
           <span> <Icon icon="ep:picture" />图片 </span>
         </template>
-        <div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
-          <el-upload
-            :action="uploadUrl"
-            :headers="headers"
-            multiple
-            :limit="1"
-            :file-list="fileList"
-            :data="uploadData"
-            :before-upload="beforeImageUpload"
-            :on-success="handleUploadSuccess"
-          >
-            <el-button type="primary" plain>点击上传</el-button>
-            <template #tip>
-              <span class="el-upload__tip" style="margin-left: 5px">
-                支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M
-              </span>
-            </template>
-          </el-upload>
-        </div>
-        <div class="waterfall" v-loading="loading">
-          <div class="waterfall-item" v-for="item in list" :key="item.id">
-            <a target="_blank" :href="item.url">
-              <img class="material-img" :src="item.url" />
-              <div class="item-name">{{ item.name }}</div>
-            </a>
-            <el-row justify="center">
-              <el-button
-                type="danger"
-                circle
-                @click="handleDelete(item)"
-                v-hasPermi="['mp:material:delete']"
-              >
-                <Icon icon="ep:delete" />
-              </el-button>
-            </el-row>
-          </div>
-        </div>
+        <Upload
+          v-hasPermi="['mp:material:upload-permanent']"
+          :type="MaterialType.Image"
+          @uploaded="getList"
+        >
+          支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M
+        </Upload>
+        <Waterfall :loading="loading" :list="list" @delete="handleDelete" />
         <!-- 分页组件 -->
         <Pagination
           :total="total"
@@ -63,67 +34,20 @@
       </el-tab-pane>
 
       <!-- tab 2:语音  -->
-      <el-tab-pane name="voice">
+      <el-tab-pane :name="MaterialType.Voice">
         <template #label>
           <span> <Icon icon="ep:microphone" />语音 </span>
         </template>
-        <div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
-          <el-upload
-            :action="uploadUrl"
-            :headers="headers"
-            multiple
-            :limit="1"
-            :file-list="fileList"
-            :data="uploadData"
-            :on-success="handleUploadSuccess"
-            :before-upload="beforeVoiceUpload"
-          >
-            <el-button type="primary" plain>点击上传</el-button>
-            <template #tip>
-              <span class="el-upload__tip" style="margin-left: 5px">
-                格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s
-              </span>
-            </template>
-          </el-upload>
-        </div>
+        <Upload
+          v-hasPermi="['mp:material:upload-permanent']"
+          :type="MaterialType.Voice"
+          @uploaded="getList"
+        >
+          格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s
+        </Upload>
 
         <!-- 列表 -->
-        <el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px">
-          <el-table-column label="编号" align="center" prop="mediaId" />
-          <el-table-column label="文件名" align="center" prop="name" />
-          <el-table-column label="语音" align="center">
-            <template #default="scope">
-              <WxVoicePlayer v-if="scope.row.url" :url="scope.row.url" />
-            </template>
-          </el-table-column>
-          <el-table-column
-            label="上传时间"
-            align="center"
-            prop="createTime"
-            :formatter="dateFormatter"
-            width="180"
-          >
-            <template #default="scope">
-              <span>{{ scope.row.createTime }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-            <template #default="scope">
-              <el-button type="primary" link plain @click="handleDownload(scope.row)">
-                <Icon icon="ep:download" />下载
-              </el-button>
-              <el-button
-                type="primary"
-                link
-                plain
-                @click="handleDelete(scope.row)"
-                v-hasPermi="['mp:material:delete']"
-              >
-                <Icon icon="ep:delete" />删除
-              </el-button>
-            </template>
-          </el-table-column>
-        </el-table>
+        <VoiceTable :list="list" :loading="loading" @delete="handleDelete" />
         <!-- 分页组件 -->
         <Pagination
           :total="total"
@@ -134,105 +58,22 @@
       </el-tab-pane>
 
       <!-- tab 3:视频 -->
-      <el-tab-pane name="video">
+      <el-tab-pane :name="MaterialType.Video">
         <template #label>
           <span> <Icon icon="ep:video-play" /> 视频 </span>
         </template>
-        <div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
-          <el-button type="primary" plain @click="handleAddVideo">新建视频</el-button>
-        </div>
-        <!-- 新建视频的弹窗 -->
-        <el-dialog
-          title="新建视频"
-          v-model="dialogVideoVisible"
-          width="600px"
-          v-loading="addMaterialLoading"
+        <el-button
+          v-hasPermi="['mp:material:upload-permanent']"
+          type="primary"
+          plain
+          @click="showCreateVideo = true"
+          >新建视频</el-button
         >
-          <el-upload
-            :action="uploadUrl"
-            :headers="headers"
-            multiple
-            :limit="1"
-            :file-list="fileList"
-            :data="uploadData"
-            :before-upload="beforeVideoUpload"
-            :on-success="handleUploadSuccess"
-            ref="uploadVideoRef"
-            :auto-upload="false"
-          >
-            <template #trigger>
-              <el-button size="small" type="primary">选择视频</el-button>
-            </template>
-            <span class="el-upload__tip" style="margin-left: 10px"
-              >格式支持 MP4,文件大小不超过 10MB</span
-            >
-          </el-upload>
-          <el-form :model="uploadData" :rules="uploadRules" ref="uploadFormRef" label-width="80px">
-            <el-row>
-              <el-form-item label="标题" prop="title">
-                <el-input
-                  v-model="uploadData.title"
-                  placeholder="标题将展示在相关播放页面,建议填写清晰、准确、生动的标题"
-                />
-              </el-form-item>
-            </el-row>
-            <el-row>
-              <el-form-item label="描述" prop="introduction">
-                <el-input
-                  :rows="3"
-                  type="textarea"
-                  v-model="uploadData.introduction"
-                  placeholder="介绍语将展示在相关播放页面,建议填写简洁明确、有信息量的内容"
-                />
-              </el-form-item>
-            </el-row>
-          </el-form>
-          <template #footer>
-            <el-button @click="cancelVideo">取 消</el-button>
-            <el-button type="primary" @click="submitVideo">提 交</el-button>
-          </template>
-        </el-dialog>
+        <!-- 新建视频的弹窗 -->
+        <UploadVideo v-model="showCreateVideo" />
 
         <!-- 列表 -->
-        <el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px">
-          <el-table-column label="编号" align="center" prop="mediaId" />
-          <el-table-column label="文件名" align="center" prop="name" />
-          <el-table-column label="标题" align="center" prop="title" />
-          <el-table-column label="介绍" align="center" prop="introduction" />
-          <el-table-column label="视频" align="center">
-            <template #default="scope">
-              <WxVideoPlayer v-if="scope.row.url" :url="scope.row.url" />
-            </template>
-          </el-table-column>
-          <el-table-column
-            label="上传时间"
-            align="center"
-            :formatter="dateFormatter"
-            prop="createTime"
-            width="180"
-          >
-            <template #default="scope">
-              <span>{{ scope.row.createTime }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="操作" align="center" fixed="right">
-            <template #default="scope">
-              <el-button type="primary" link plain @click="handleDownload(scope.row)">
-                <Icon icon="ep:download" />下载
-              </el-button>
-              <el-button
-                type="primary"
-                link
-                size="small"
-                plain
-                @click="handleDelete(scope.row)"
-                v-hasPermi="['mp:material:delete']"
-              >
-                <Icon icon="ep:delete" />删除
-              </el-button>
-            </template>
-          </el-table-column>
-        </el-table>
+        <VideoTable :list="list" :loading="loading" @delete="handleDelete" />
         <!-- 分页组件 -->
         <Pagination
           :total="total"
@@ -246,39 +87,19 @@
 </template>
 
 <script lang="ts" setup name="MpMaterial">
-import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
-import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
 import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
+import Waterfall from './components/Waterfall.vue'
+import VoiceTable from './components/VoiceTable.vue'
+import VideoTable from './components/VideoTable.vue'
+import Upload from './components/Upload.vue'
+import UploadVideo from './components/UploadVideo.vue'
+import { MaterialType } from './components/upload'
 import * as MpMaterialApi from '@/api/mp/material'
-import * as authUtil from '@/utils/auth'
-import { dateFormatter } from '@/utils/formatTime'
-import type {
-  FormInstance,
-  FormRules,
-  TabPaneName,
-  UploadInstance,
-  UploadProps,
-  UploadRawFile,
-  UploadUserFile
-} from 'element-plus'
-
-const BASE_URL = import.meta.env.VITE_BASE_URL
-const uploadUrl = BASE_URL + '/admin-api/mp/material/upload-permanent'
-const headers = { Authorization: 'Bearer ' + authUtil.getAccessToken() }
 
 const message = useMessage()
 
-const uploadFormRef = ref<FormInstance>()
-const uploadVideoRef = ref<UploadInstance>()
-
-const uploadRules: FormRules = {
-  title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
-  introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }]
-}
-
 // 素材类型
-type MaterialType = 'image' | 'voice' | 'video'
-const type = ref<MaterialType>('image')
+const type = ref<MaterialType>(MaterialType.Image)
 const loading = ref(false) // 遮罩层
 const list = ref<any[]>([]) // 总条数
 const total = ref(0) // 数据列表
@@ -296,22 +117,8 @@ const queryParams: QueryParams = reactive({
   permanent: true
 })
 
-const fileList = ref<UploadUserFile[]>([])
-
-interface UploadData {
-  type: MaterialType
-  title: string
-  introduction: string
-}
-const uploadData: UploadData = reactive({
-  type: 'image',
-  title: '',
-  introduction: ''
-})
-
 // === 视频上传,独有变量 ===
-const dialogVideoVisible = ref(false)
-const addMaterialLoading = ref(false)
+const showCreateVideo = ref(false)
 
 /** 侦听公众号变化 **/
 const onAccountChanged = (id?: number) => {
@@ -341,10 +148,7 @@ const handleQuery = () => {
   getList()
 }
 
-const onTabChange = (tabName: TabPaneName) => {
-  // 设置 type
-  uploadData.type = tabName as MaterialType
-
+const onTabChange = () => {
   // 提前情况数据,避免tab切换后显示垃圾数据
   list.value = []
   total.value = 0
@@ -353,160 +157,12 @@ const onTabChange = (tabName: TabPaneName) => {
   handleQuery()
 }
 
-// ======================== 文件上传 ========================
-const beforeUpload = (rawFile: UploadRawFile, category: 'image' | 'audio' | 'video'): boolean => {
-  let allowTypes: string[] = []
-  let maxSizeMB = 0
-  let name = ''
-
-  switch (category) {
-    case 'image':
-      allowTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg']
-      maxSizeMB = 2
-      name = '图片'
-      break
-    case 'audio':
-      allowTypes = ['audio/mp3', 'audio/mpeg', 'audio/wma', 'audio/wav', 'audio/amr']
-      maxSizeMB = 2
-      name = '图片'
-      break
-    case 'video':
-      allowTypes = ['video/mp4']
-      maxSizeMB = 10
-      name = '视频'
-  }
-
-  if (!allowTypes.includes(rawFile.type)) {
-    message.error(`上传${name}格式不对!`)
-    return false
-  }
-  // 校验大小
-  if (rawFile.size / 1024 / 1024 > maxSizeMB) {
-    message.error(`上传${name}大小不能超过${maxSizeMB}M!`)
-    return false
-  }
-  loading.value = true
-  return true
-}
-
-const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
-  beforeUpload(rawFile, 'image')
-
-const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
-  beforeUpload(rawFile, 'audio')
-
-const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
-  beforeUpload(rawFile, 'video')
-
-const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => {
-  loading.value = false
-  addMaterialLoading.value = false
-  if (response.code !== 0) {
-    message.error('上传出错:' + response.msg)
-    return false
-  }
-
-  // 清空上传时的各种数据
-  dialogVideoVisible.value = false
-  fileList.value = []
-  uploadData.title = ''
-  uploadData.introduction = ''
-
-  // 加载数据
-  getList()
-}
-
-// 下载文件
-const handleDownload = (row: any) => {
-  window.open(row.url, '_blank')
-}
-
-// 提交 video 新建的表单
-const submitVideo = () => {
-  uploadFormRef.value?.validate((valid) => {
-    if (!valid) {
-      return false
-    }
-    uploadVideoRef.value?.submit()
-  })
-}
-
-// 弹出 video 新建的表单
-const handleAddVideo = () => {
-  resetVideo()
-  dialogVideoVisible.value = true
-}
-
-/** 取消按钮 */
-const cancelVideo = () => {
-  dialogVideoVisible.value = false
-  resetVideo()
-}
-
-/** 表单重置 */
-const resetVideo = () => {
-  fileList.value = []
-  uploadData.title = ''
-  uploadData.introduction = ''
-  uploadFormRef.value?.resetFields()
-}
-
 // ======================== 其它操作 ========================
-const handleDelete = async (item: any) => {
+const handleDelete = async (id: number) => {
   await message.confirm('此操作将永久删除该文件, 是否继续?')
-  await MpMaterialApi.deletePermanentMaterial(item.id)
+  await MpMaterialApi.deletePermanentMaterial(id)
   message.alertSuccess('删除成功')
 }
 </script>
 
-<style lang="scss" scoped>
-/*瀑布流样式*/
-.waterfall {
-  width: 100%;
-  column-gap: 10px;
-  column-count: 5;
-  margin-top: 10px;
-  /* 芋道源码:增加 10px,避免顶着上面 */
-}
-
-.waterfall-item {
-  padding: 10px;
-  margin-bottom: 10px;
-  break-inside: avoid;
-  border: 1px solid #eaeaea;
-}
-
-.material-img {
-  width: 100%;
-}
-
-p {
-  line-height: 30px;
-}
-
-@media (min-width: 992px) and (max-width: 1300px) {
-  .waterfall {
-    column-count: 3;
-  }
-
-  p {
-    color: red;
-  }
-}
-
-@media (min-width: 768px) and (max-width: 991px) {
-  .waterfall {
-    column-count: 2;
-  }
-
-  p {
-    color: orange;
-  }
-}
-
-@media (max-width: 767px) {
-  .waterfall {
-    column-count: 1;
-  }
-}
-</style>
+<style lang="scss" scoped></style>