Browse Source

//第一版提交

wyj0522 1 ngày trước cách đây
mục cha
commit
fa51935ab9

+ 1 - 0
App.vue

@@ -39,5 +39,6 @@
 </script>
 
 <style lang="scss">
+  @import "uview-ui/index.scss";
   @import '@/static/scss/index.scss'
 </style>

+ 15 - 5
api/system/user.js

@@ -27,7 +27,15 @@ export function getUserProfile() {
     method: 'get'
   })
 }
-
+export function getTaskList() {
+  return request({
+    url: '/video/VideoTask/list',
+		headers: {
+			isClientId:true,
+		},
+    method: 'get',
+  })
+}
 // 修改用户个人信息
 export function updateUserProfile(data) {
   return request({
@@ -60,21 +68,23 @@ export function uploadPerson(data) {
 // 音频生成接口
 export function audioCreateVideo(data) {
   return request({
-    url: '/system/user/profile',
+    url: '/video/Videofile/createVideo',
   	headers: {
   		isClientId:true,
   	},
-    method: 'post'
+    method: 'post',
+    data:data
   })
 }
 
 // 文本生成接口
 export function textCreateVideo(data) {
   return request({
-    url: '/system/user/profile',
+    url: '/video/Videofile/createVideo',
   	headers: {
   		isClientId:true,
   	},
-    method: 'post'
+    method: 'post',
+    data:data
   })
 }

+ 1 - 3
config.js

@@ -1,8 +1,6 @@
 // 应用全局配置
 module.exports = {
-  // baseUrl: 'https://vue.ruoyi.vip/prod-api',
-  // baseUrl: 'http://localhost:8080',
-	baseUrl:"http://192.168.114.19:8848/api",
+	baseUrl:"http://192.168.0.2:9090",
   // 应用信息
   appInfo: {
     // 应用名称

+ 3 - 3
main.js

@@ -5,9 +5,9 @@ import plugins from './plugins' // plugins
 import './permission' // permission
 import { getDicts } from "@/api/system/dict/data"
 import { connectWebSocket, eventBus } from '@/utils/websocket'
-
-Vue.use(plugins)
-
+import uView from "uview-ui";
+Vue.use(plugins);
+Vue.use(uView);
 Vue.config.productionTip = false
 Vue.prototype.$store = store
 Vue.prototype.getDicts = getDicts

+ 2 - 2
manifest.json

@@ -5,9 +5,9 @@
     "versionName" : "1.2.0",
     "versionCode" : "100",
     "transformPx" : false,
+	"sassImplementationName": "node-sass",
     "app-plus" : {
         "usingComponents" : true,
-        "nvueCompiler" : "uni-app",
         "splashscreen" : {
             "alwaysShowBeforeRender" : true,
             "waiting" : true,
@@ -57,7 +57,7 @@
     "h5" : {
         "template" : "static/index.html",
         "devServer" : {
-            "port" : 9090,
+            "port" : 9099,
             "https" : false
         },
         "title" : "Digital-Human-App",

+ 11 - 2
package.json

@@ -1,6 +1,15 @@
 {
+  "scripts": {
+    "build": "uni build"
+  },
   "dependencies": {
     "crypto-js": "^4.2.0",
-    "jsencrypt": "^3.3.2"
+    "jsencrypt": "^3.3.2",
+    "uview-ui": "^2.0.38"
+  },
+  "devDependencies": {
+    "@types/vue": "^2.0.0",
+    "sass": "^1.89.2",
+    "sass-loader": "^10.5.2"
   }
-}
+}

+ 3 - 0
pages.json

@@ -120,5 +120,8 @@
 		"navigationBarTextStyle": "black",
 		"navigationBarTitleText": "DigitalHuman",
 		"navigationBarBackgroundColor": "#FFFFFF"
+	},
+	"easycom": {
+		"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
 	}
 }

+ 306 - 0
pages/common/fileupload/index.vue

@@ -0,0 +1,306 @@
+<template>
+  <view class="upload-container">
+    <u-upload
+      :fileList="fileList"
+      @afterRead="handleAfterRead"
+      @delete="handleDelete"
+      :name="name"
+      :multiple="multiple"
+      :maxCount="maxCount"
+      :auto-upload="false"
+      :show-progress="true"
+      :sizeType="sizeType"
+      :sourceType="sourceType"
+      :accept="accept"
+      previewFullImage
+    >
+      <view v-if="accept !== 'image'">
+        <text class="file-icon">📎</text>
+        <text>点击上传文件</text>
+      </view>
+    </u-upload>
+    
+    <!-- 文件列表区域,针对文件类型优化显示 -->
+    <view  class="file-list" v-if="fileList && fileList.length > 0 && accept !== 'image'">
+      <view class="file-item" v-for="(file, index) in fileList" :key="index">
+        <view class="file-info">
+          <view class="file-icon">
+            <text>{{ getFileIcon(file.fileName) }}</text>
+          </view>
+          <view class="file-details">
+            <text class="file-name">{{ file.fileName || '未命名文件' }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import config from '@/config.js'; // 根据实际路径调整
+export default {
+  name: 'UploadComponent',
+  data() {
+    return {
+      // 上传URL
+      uploadUrl: `${config.baseUrl}/common/upload`,
+      fileList: []
+    }
+  },
+  props: {
+    // 组件标识名称
+    name: {
+      type: String,
+      default: 'upload'
+    },
+    // 是否允许多文件
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    // 最大文件数量
+    maxCount: {
+      type: Number,
+      default: 10
+    },
+
+    // 文件类型限制
+    accept: {
+      type: String,
+      default: 'image'
+    },
+    // 图片尺寸类型
+    sizeType: {
+      type: Array,
+      default: () => ['original', 'compressed']
+    },
+    // 图片来源
+    sourceType: {
+      type: Array,
+      default: () => ['album', 'camera']
+    },
+    // 额外的表单数据
+    formData: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  methods: {
+    // 删除图片/文件
+    handleDelete(event) {
+      // 通知父组件删除文件
+      this.$emit('delete', {
+        name: this.name,
+        index: event.index,
+        file: this.fileList[event.index]
+      });
+      
+      // 从本地列表中移除
+      this.fileList.splice(event.index, 1);
+    },
+    
+    // 新增图片/文件
+    async handleAfterRead(event) {
+      // 触发开始上传事件
+      this.$emit('upload-start', {
+        name: this.name,
+        files: event.file
+      });
+      
+      // 处理文件列表
+      let lists = [].concat(event.file);
+      
+      // 更新UI显示上传中状态
+      lists.forEach((item) => {
+        // 添加上传中的文件到列表
+        this.fileList.push({
+          ...item,
+          status: "uploading",
+          message: "上传中"
+        });
+        
+        this.$emit('file-added', {
+          name: this.name,
+          file: this.fileList[this.fileList.length - 1]
+        });
+      });
+      
+      try {
+        // 逐个上传文件
+        for (let i = 0; i < lists.length; i++) {
+          const fileIndex = this.fileList.length - lists.length + i;
+          const result = await this.uploadFilePromise(lists[i].url);
+           this.fileList= [result]
+          // 更新文件状态为成功
+          this.fileList[fileIndex] = {
+            ...this.fileList[fileIndex],
+            ...result,
+            status: "success",
+            message: "上传成功"
+          };
+          
+          this.$emit('file-updated', {
+            name: this.name,
+            index: fileIndex,
+            file: this.fileList[fileIndex]
+          });
+        }
+        
+        // 触发上传完成事件
+        this.$emit('upload-success', {
+          name: this.name,
+          files: lists
+        });
+      } catch (error) {
+        // 标记上传失败
+        lists.forEach((_, i) => {
+          const fileIndex = this.fileList.length - lists.length + i;
+          this.fileList[fileIndex].status = "error";
+          this.fileList[fileIndex].message = "上传失败";
+        });
+        
+        // 触发上传失败事件
+        this.$emit('upload-error', {
+          name: this.name,
+          error: error
+        });
+      }
+    },
+    
+    // 上传文件的Promise封装
+    uploadFilePromise(url) {
+      return new Promise((resolve, reject) => {
+        uni.uploadFile({
+          url: this.uploadUrl,
+          filePath: url,
+          name: "file",
+          formData: {
+            ...this.formData,
+          },
+          success: (res) => {
+            try {
+              // 尝试解析JSON响应
+              const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
+              resolve(data.data || data);
+            } catch (e) {
+              // 如果解析失败,直接返回原始数据
+              resolve(res.data);
+            }
+          },
+          fail: (err) => {
+            reject(err);
+          }
+        });
+      });
+    },
+    
+    // 根据文件扩展名获取对应的图标
+    getFileIcon(filename) {
+      if (!filename) return '📄';
+      
+      const ext = filename.split('.').pop().toLowerCase();
+      
+      // 根据文件类型返回不同的图标emoji
+      if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext)) return '🖼️';
+      if (['pdf'].includes(ext)) return '📑';
+      if (['doc', 'docx'].includes(ext)) return '📝';
+      if (['xls', 'xlsx'].includes(ext)) return '📊';
+      if (['ppt', 'pptx'].includes(ext)) return '📈';
+      if (['mp3', 'wav', 'flac'].includes(ext)) return '🎵';
+      if (['mp4', 'mov', 'avi'].includes(ext)) return '🎬';
+      if (['zip', 'rar', '7z'].includes(ext)) return '🗜️';
+      
+      return '📄';
+    },
+    
+    // 格式化文件大小
+    formatFileSize(bytes) {
+      if (bytes === 0) return '0 B';
+      
+      const k = 1024;
+      const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+      const i = Math.floor(Math.log(bytes) / Math.log(k));
+      
+      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.upload-container {
+  .upload-hint {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    width: 80px;
+    height: 80px;
+    border: 1px dashed #d9d9d9;
+    border-radius: 4px;
+    background-color: #fafafa;
+    color: #999;
+    font-size: 12px;
+    
+    .file-icon {
+      font-size: 24px;
+      margin-bottom: 4px;
+    }
+  }
+  
+  .file-list {
+    margin-top: 10px;
+    
+    .file-item {
+      display: flex;
+      align-items: center;
+      padding: 8px 0;
+      border-bottom: 1px solid #eee;
+      
+      .file-info {
+        display: flex;
+        align-items: center;
+        flex: 1;
+        
+        .file-thumb {
+          width: 32px;
+          height: 32px;
+          margin-right: 10px;
+          border-radius: 4px;
+          background-color: #f5f5f5;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          
+          image {
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+            border-radius: 4px;
+          }
+          
+          .file-icon {
+            font-size: 20px;
+          }
+        }
+        
+        .file-details {
+          .file-name {
+            font-size: 14px;
+            color: #333;
+            max-width: 200px;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+          }
+          
+          .file-size {
+            font-size: 12px;
+            color: #999;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 553 - 316
pages/index.vue

@@ -1,373 +1,610 @@
 <template>
 	<view class="content">
+		<!-- 步骤引导轮播 -->
 		<view class="uni-margin-wrap">
-			<swiper class="swiper" circular :indicator-dots="true" indicator-color="#DE868F" indicator-active-color="#fff"
-				:autoplay="true" :interval="5000" :duration="1000">
+			<swiper class="swiper" circular :indicator-dots="true" indicator-color="rgba(255,255,255,0.3)"
+				indicator-active-color="#fff" :autoplay="true" :interval="5000" :duration="800">
 				<swiper-item>
-					<view class="swiper-item"><text>创建步骤1<br>填写视频名称</text></view>
+					<view class="swiper-item">
+						<view class="step-number">1</view>
+						<text class="step-text">填写视频名称</text>
+					</view>
 				</swiper-item>
 				<swiper-item>
-					<view class="swiper-item">创建步骤2<br>选择个人形象照片,最好是正面照</view>
+					<view class="swiper-item">
+						<view class="step-number">2</view>
+						<text class="step-text">选择个人形象照片,最好是正面照</text>
+					</view>
 				</swiper-item>
 				<swiper-item>
-					<view class="swiper-item">创建步骤3<br>选择生成视频内容的方式:<br>若选择文字输入,则输入口播的文字内容及使用音色;<br>若选择语音录入则点击开始录音,录音结束后再点击停止录音,可在下方播放容器试听录音内容;<br>若选择音频选择,则选择本机的音频文件</view>
+					<view class="swiper-item">
+						<view class="step-number">3</view>
+						<text
+							class="step-text">选择生成视频内容的方式:\n若选择文字输入,则输入口播的文字内容及使用音色;\n若选择语音录入则点击开始录音;\n若选择音频选择则选择本机音频文件</text>
+					</view>
 				</swiper-item>
 			</swiper>
 		</view>
-		<form class="form-area">
-			<view class="form-area-item">
-				<text class="form-area-item-text">视频名称</text>
-				<input type="text" v-model="videoName" placeholder="请输入视频名称">
-			</view>
-			<view class="form-area-item">
-				<text class="form-area-item-text">选择您的形象</text>
-				<button @click="chooseImage" class="cu-btn bg-red">选择图片</button>
-				<text class="none-file" v-if="!imagePath">未选择图片</text>
-				<image :src="imagePath" v-else mode="aspectFit" style="width: 30px; height: 30px;margin-left:10px" />
-			</view>
-			<view class="form-area-item" style="align-items: baseline;">
-				<text class="form-area-item-text">视频内容</text>
-				<uni-data-checkbox selectedColor="#BD3124" selectedTextColor="#BD3124" v-model="current"
-					:localdata="videoContent" />
-			</view>
-			<view class="form-area-item" v-if="current=='text'">
-				<textarea v-model="videoText" placeholder="请输入视频中的文字内容" />
-			</view>
-			<view class="form-area-item" v-if="current=='text'">
-				<text class="form-area-item-text">音色选择</text>
-				<uni-data-checkbox selectedColor="#BD3124" selectedTextColor="#BD3124" v-model="timbre"
-					:localdata="timbreContent" />
-			</view>
-			<view class="form-area-item" v-if="current=='record'">
-				<button @click="toggleRecording">{{ isRecording ? '停止录音' : '开始录音' }}</button>
-			</view>
-			<view class="form-area-item" v-if="current=='audio'" :key="timeStamp">
-				<text class="form-area-item-text">选择音频文件</text>
-				<button @click="chooseFile" class="cu-btn bg-red">选择文件</button>
-				<text class="file-name" v-if="fileInfo.name">{{fileInfo.name}}</text>
-				<text class="none-file" v-if="!showPerson">未选择文件</text>
+
+		<!-- 表单区域 -->
+		<u--form class="form-area" labelPosition="left" :model="formData" ref="uForm" labelWidth="70">
+			<view class="form-card">
+				<u-form-item lableWidth="100rpx" label="视频名称" prop="formData.videoName" borderBottom ref="videoName">
+					<u--input type="text" v-model="formData.videoName" placeholder="请输入视频名称" border="none"></u--input>
+				</u-form-item>
+				<u-form-item lableWidth="100rpx" label="上传形象" prop="formData.videoName" borderBottom ref="videoName">
+					<upload-component accept="image" :max-count="1" @file-updated="handleFileUpdated" />
+				</u-form-item>
+				<u-form-item @click="openPicker" :label="currentType" labelPosition="top" borderBottom>
+					<u-picker :show="show" :columns="columns" keyName="label" closeOnClickOverlay="true"
+						immediateChange="true" @confirm="confirmPicker"></u-picker>
+				</u-form-item>
+				<u-form-item >
+						<u--textarea v-if="formData.current == 'text'" v-model="formData.txtCount" placeholder="请输入视频中的文字内容" class="text-area" />
+					<view class="form-area-item input-panel" v-if="formData.current == 'record'">
+						<button @click="toggleRecording" class="record-btn" :class="{ recording: isRecording }">
+							{{ isRecording ? '停止录音' : '开始录音' }}
+						</button>
+						<view class="record-status" v-if="isRecording">
+							<view class="audio-wave">
+								<view class="wave-bar" :style="{ height: '30%' }"></view>
+								<view class="wave-bar" :style="{ height: '70%' }"></view>
+								<view class="wave-bar" :style="{ height: '50%' }"></view>
+							</view>
+							<text class="status-text">录音中...</text>
+						</view>
+					</view>
+						
+					<!-- 音频选择区域 -->
+					<view class="form-area-item input-panel" v-if="formData.current == 'audio'" :key="timeStamp">
+						<upload-component accept="file" :max-count="1" @file-updated="handleFileUpdatedWav" />
+					</view>
+					<!-- 音频预览 -->
+					<audio v-if="audioPath" class="audio-bar" :src="audioPath" name="语音试听"
+						poster="../static/images/xijiao/avator.png" :action="audioAction" controls>
+					</audio>
+				</u-form-item>
+				<u-form-item @click="openTimbrePicker" :label="timbreType" labelPosition="top" borderBottom>
+					<u-picker :show="timbreShow" :columns="timbreContent" keyName="text" closeOnClickOverlay="true"
+						immediateChange="true" @confirm="confirmTimbrePicker"></u-picker>
+				</u-form-item>
 			</view>
-			<audio v-if="audioPath" class="audio-bar" :src="audioPath" name="语音试听" poster="../static/images/xijiao/avator.png"
-				:action="audioAction" controls></audio>
-		</form>
+		</u--form>
+
+		<!-- 生成按钮 -->
 		<view class="btn-area">
-			<button @click="jumpToList" class="bg-red lg round create-project">生成数字人视频</button>
-			<uni-popup ref="alertDialog" type="dialog">
-				<uni-popup-dialog type="warning" cancelText="确定" title="提示" :content="missingContent"
-					@close="dialogClose"></uni-popup-dialog>
-			</uni-popup>
+			<u-button @click="jumpToList" class="create-project" :hover-class="button - hover">
+				生成数字人视频
+			</u-button>
 		</view>
+
+		<!-- 提示弹窗 -->
+		<uni-popup ref="alertDialog" type="dialog">
+			<uni-popup-dialog type="warning" cancelText="确定" title="提示" :content="missingContent" @close="dialogClose"
+				:style="{
+					'--uni-dialog-border-radius': '12rpx',
+					'--uni-dialog-title-color': '#BD3124'
+				}"></uni-popup-dialog>
+		</uni-popup>
+
 	</view>
 </template>
+
 <script>
-	import {
-		uploadPerson,
-		audioCreateVideo,
-		textCreateVideo
-	} from "@/api/system/user"
-	import store from '@/store'
-	const recorderManager = uni.getRecorderManager();
-	const innerAudioContext = uni.createInnerAudioContext();
-	innerAudioContext.autoplay = true;
-	export default {
-		data() {
-			return {
-				videoName: '',
-				fileInfo: {},
-				videoContent: [{
-						value: 'text',
-						text: '文字输入'
-					},
-					{
-						value: 'record',
-						text: '语音录入'
-					},
-					{
-						value: 'audio',
-						text: '音频选择'
-					},
-				],
-				current: 'text',
-				imagePath: '',
-				isRecording: false,
-				audioPath: null,
-				recorderManager: null,
-				innerAudioContext: null,
-				showPerson: false,
-				timeStamp: null,
-				audioAction: {
-					method: 'pause'
-				},
-				videoText: '',
-				missingContent: [],
-				timbre: 'a',
-				timbreContent: [{
-						value: 'a',
-						text: 'timbre1'
-					},
-					{
-						value: 'b',
-						text: 'timbre2'
-					}
-				],
-				personPath: '',
-				createVideoPath: '',
+import UploadComponent from './common/fileupload/index.vue';
+import { uploadPerson, audioCreateVideo, textCreateVideo } from "@/api/system/user"
+import store from '@/store'
+const recorderManager = uni.getRecorderManager();
+const innerAudioContext = uni.createInnerAudioContext();
+innerAudioContext.autoplay = true;
+
+export default {
+	components: {
+		UploadComponent
+	},
+	data() {
+		return {
+			timbreType: '选择音色',
+			currentType: '选择内容',
+			timbreShow: false,
+			show: false,
+			columns: [[
+				{ id: 'text', label: '文字输入' },
+				{ id: 'record', label: '语音录入' },
+				{ id: 'audio', label: '音频选择' }]
+			],
+			// 表单数据
+			formData: {
+				current: '',         // 生成任务类型(text/record/audio)
+				videoName: '',       // 视频名称
+				imagefile: '',       // 形象文件路径
+				audioFile: '',       // 音频文件路径
+				txtCount: '',        // 文字内容
+				timbre: ''           // 音色选择
+			},
+			// 音色选项(修复原数据value重复问题)
+			timbreContent: [[
+				{ value: '1', text: '青年男' },
+				{ value: '2', text: '青年女' },
+				{ value: '3', text: '中年男' },
+				{ value: '4', text: '中年女' }
+			]],
+			imagePath: '',        // 形象图片本地路径
+			isRecording: false,   // 录音状态
+			audioPath: null,      // 录音/音频本地路径
+			recorderManager: null,
+			innerAudioContext: null,
+			showPerson: false,    // 是否显示音频文件
+			timeStamp: null,      // 音频文件时间戳(用于刷新)
+			audioAction: { method: 'pause' },
+			fileInfo: {},         // 音频文件信息
+			missingContent: '',   // 缺失项提示文本
+			personPath: '',       // 人物形象路径
+			createVideoPath: ''   // 生成的视频路径
+		}
+	},
+	onLoad() {
+		// 初始化录音管理器
+		this.recorderManager = uni.getRecorderManager();
+		this.innerAudioContext = uni.createInnerAudioContext();
+
+		// 监听录音停止
+		this.recorderManager.onStop((res) => {
+			console.log('录音结束,临时路径:', res.tempFilePath);
+			this.audioPath = res.tempFilePath;
+			this.isRecording = false;
+		});
+
+		// 监听录音错误
+		this.recorderManager.onError((err) => {
+			uni.showToast({ title: '录音出错', icon: 'none' });
+		});
+	},
+	onShow() {
+		// 页面显示时加载本地存储的音频文件
+		this.timeStamp = new Date().getTime();
+		this.fileInfo.path = uni.getStorageSync('filePath');
+		this.fileInfo.name = uni.getStorageSync('fileName');
+		if (this.fileInfo.name) {
+			this.showPerson = true;
+			this.uploadAudioPath(this.fileInfo.path);
+		}
+	},
+	methods: {
+		handleFileUpdatedWav(data){
+			this.formData.audioFile = data.url
+		},
+		openTimbrePicker() {
+			this.timbreShow = true;
+		},
+		// 音色选择
+		confirmTimbrePicker(data) {
+			console.log('选择结果:', data.value[0]);
+			this.formData.timbre = data.value[0].value
+			this.timbreType = data.value[0].text
+			this.timbreShow = false;
+		},
+		openPicker() {
+			this.show = true;
+		},
+		confirmPicker(data) {
+			console.log('选择结果:', data.value[0]);
+			this.formData.current = data.value[0].id
+			this.currentType = data.value[0].label
+			this.show = false;
+		},
+		handleFileUpdated(data) {
+			this.formData.imagefile = data.url
+		},
+		// 选择音频文件(跳转文件列表)
+		chooseFile() {
+			if (!this.imagePath) {
+				uni.showToast({ title: '请先选择形象照片', icon: 'none' });
+				return;
 			}
+			uni.navigateTo({ url: "/pages/root-filelist/root-filelist" });
 		},
-		onLoad() {
-			this.recorderManager = uni.getRecorderManager();
-			this.innerAudioContext = uni.createInnerAudioContext();
-
-			// 监听录音停止
-			this.recorderManager.onStop((res) => {
-				console.log('录音结束,临时路径:', res.tempFilePath);
-				this.audioPath = res.tempFilePath; // 替换为新录音路径
-				this.isRecording = false;
-			});
 
-			// 监听录音错误
-			this.recorderManager.onError((err) => {
-				uni.showToast({
-					title: '录音出错',
-					icon: 'none'
+		// 录音控制(开始/停止)
+		toggleRecording() {
+			if (this.isRecording) {
+				// 停止录音并上传
+				this.recorderManager.stop();
+				this.uploadAudioPath(this.audioPath);
+			} else {
+				// 开始录音(清空已有录音)
+				if (this.audioPath) this.audioPath = null;
+				this.recorderManager.start({
+					duration: 600000,    // 最大录音时间(10分钟)
+					sampleRate: 44100,
+					numberOfChannels: 1,
+					encodeBitRate: 192000,
+					format: 'wav'
 				});
-			});
-		},
-		onShow() {
-			this.timeStamp = new Date().getTime()
-			this.fileInfo.path = uni.getStorageSync('filePath');
-			this.fileInfo.name = uni.getStorageSync('fileName');
-			if (this.fileInfo.name) {
-				this.showPerson = true
-				this.uploadAudioPath(this.fileInfo.path)
+				this.isRecording = true;
 			}
 		},
-		methods: {
-			// 选择音频文件
-			chooseFile() {
-				if(!this.imagePath) {
-					uni.showToast({
-						title: '请先选择形象照片',
-						icon: 'none'
-					});
-					return
-				}
-				uni.navigateTo({
-					url: "/pages/root-filelist/root-filelist",
-				});
-			},
-			// 选择图片文件
-			async chooseImage() {
-				const that = this;
-				uni.chooseImage({
-					count: 1,
-					sizeType: ['original', 'compressed'],
-					sourceType: ['album', 'camera'],
-					success: function(res) {
-						that.imagePath = res.tempFilePaths[0];
-						that.uploadImagePath()
-					},
-					fail: function(err) {
-						uni.showToast({
-							title: '图片选择失败',
-							icon: 'none'
-						});
-					}
+
+
+		// 验证表单并提交生成视频
+		jumpToList() {
+			// 表单验证
+			const missingFields = [];
+			// if (!this.formData.videoName.trim()) missingFields.push('视频名称');
+			// if (!this.formData.imagefile) missingFields.push('人物形象');
+
+			// // 内容验证(文字/语音至少一项)
+			// const hasTextContent = this.formData.txtCount.trim() && this.formData.timbre;
+			// const hasVoiceContent = this.audioPath;
+			// if (!hasTextContent && !hasVoiceContent) missingFields.push('文字内容或语音输入');
+
+			// // 显示缺失提示
+			// if (missingFields.length > 0) {
+			// 	this.missingContent = `请完善以下信息:${missingFields.join('、')}`;
+			// 	this.$refs.alertDialog.open();
+			// 	return;
+			// }
+
+			// 跳转生成页面
+			uni.navigateTo({ url: 'list/creatingVideo/creatingVideo' });
+
+			// 调用生成接口
+			if (this.formData.current === 'record' || this.formData.current === 'audio') {
+				// 音频生成视频
+				audioCreateVideo(this.formData).then(response => {
+					if (response.status === 200) this.createVideoPath = response.data;
+					this.openWebsocket();
+				}).catch(() => {
+					uni.showToast({ title: '视频生成请求已提交', icon: 'none' });
 				});
-			},
-			// 开始/停止录音
-			toggleRecording() {
-				if (this.isRecording) {
-					this.recorderManager.stop();
-					this.uploadAudioPath(this.audioPath)
-				} else {
-					// 开始录音前,如果已有录音则清空
-					if (this.audioPath) {
-						this.audioPath = null;
-					}
-					// 开始录音
-					this.recorderManager.start({
-						duration: 600000, // 最大录音时间(毫秒)
-						sampleRate: 44100,
-						numberOfChannels: 1,
-						encodeBitRate: 192000,
-						format: 'wav',
-					});
-					this.isRecording = true;
-				}
-			},
-			// 上传形象照片
-			uploadImagePath() {
-				let data = {
-					file: this.imagePath
-				}
-				uploadPerson(data).then(response => {
-					this.personPath = response.data.url
-				})
-			},
-			// 上传音频文件
-			uploadAudioPath(url) {
-				let data = {
-					file: url
-				}
-				uploadPerson(data).then(response => {
-					this.personAudio = response.data.url
-				})
-			},
-			// 点击生成视频,跳转生成页面
-			jumpToList() {
-				let missingFields = [];
-				if (!this.videoName.trim()) {
-					missingFields.push('视频名称');
-				}
-				if (!this.imagePath) {
-					missingFields.push('人物形象');
-				}
-				const hasContent = this.videoText.trim() !== '' && this.timbre;
-				const hasVoice = this.audioPath;
-				if (!hasContent && !hasVoice) {
-					missingFields.push('文字内容或语音输入');
-				}
-				if (missingFields.length > 0) {
-					this.missingContent = `请确定:${missingFields.join('、')}是否填写完整!`
-					this.$refs.alertDialog.open()
-					return;
-				}
-				uni.navigateTo({
-					url: 'list/creatingVideo/creatingVideo',
+			} else if (this.formData.current === 'text') {
+				// 文字生成视频
+				textCreateVideo(this.formData).then(response => {
+					if (response.status === 200) this.createVideoPath = response.data;
+						this.openWebsocket();
 				});
-				if (this.current == 'record' || this.current == 'audio') {
-					let data = {
-						'persona_template': this.personPath,
-						'persona_audio': this.personAudio,
-					}
-					audioCreateVideo(data).then(response => {
-						if (response.status == 200) {
-							this.createVideoPath = response.data
-						}
-					}).catch(e => {
-						uni.showToast({
-							title: '视频返回成功'
-						})
-					})
-				} else if (this.current == 'text') {
-					let data = {
-						'persona_template': this.personPath,
-						'voice_type': this.timbre,
-						'audio_text': this.videoText,
-					}
-					textCreateVideo(data).then(response => {
-						if (response.status == 200) {
-							this.createVideoPath = response.data
-						}
-					})
+			}
+		},
+		openWebsocket() {
+			uni.connectSocket({
+				url: 'ws://192.168.0.2:8080/ws',
+				header: {
+					'Content-Type': 'application/json'
+				},
+				method: 'POST',
+				data: {
+					'videoId': this.createVideoPath
+				},
+				success: (res) => {
+					console.log('连接成功', res);
+				},
+				fail: (err) => {
+					console.error('连接失败', err);
 				}
-			},
-			dialogClose() {
-				console.log('点击关闭')
-			},
+			});
+		},
+		// 关闭提示弹窗
+		dialogClose() {
+			console.log('弹窗关闭');
 		}
 	}
+}
 </script>
+
 <style scoped lang="scss">
-	.content {
-		display: flex;
-		flex-direction: column;
+/* 基础变量定义 */
+$primary: #BD3124; // 主色调(红色)
+$primary-light: #F8E1E3; // 主色浅色
+$text-main: #333; // 主要文本色
+$text-secondary: #666; // 次要文本色
+$border-radius: 12rpx; // 统一圆角
+$shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); // 基础阴影
+
+/* 页面容器 */
+.content {
+	display: flex;
+	flex-direction: column;
+	padding: 20rpx;
+	background-color: #f9f9f9;
+	min-height: 100vh;
+}
+
+/* 轮播步骤区 */
+.uni-margin-wrap {
+	width: 100%;
+	margin-bottom: 30rpx;
+	border-radius: $border-radius;
+	overflow: hidden;
+	box-shadow: $shadow;
+}
+
+.swiper {
+	height: 320rpx;
+}
+
+.swiper-item {
+	position: relative;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	height: 100%;
+	background: linear-gradient(135deg, $primary, #E65C68); // 渐变背景
+	color: #fff;
+	padding: 40rpx;
+	text-align: center;
+}
+
+.step-number {
+	width: 60rpx;
+	height: 60rpx;
+	background-color: rgba(255, 255, 255, 0.2);
+	border-radius: 50%;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	font-size: 30rpx;
+	font-weight: bold;
+	margin-bottom: 20rpx;
+}
+
+.step-text {
+	font-size: 28rpx;
+	line-height: 1.6;
+	white-space: pre-line; // 支持换行
+}
+
+/* 表单区域 */
+.form-area {
+	width: 100%;
+}
+
+.form-card {
+	background-color: #fff;
+	border-radius: $border-radius;
+	padding: 30rpx 20rpx;
+	box-shadow: $shadow;
+}
+
+.form-area-item {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	margin-bottom: 35rpx;
+	padding: 0 10rpx;
+}
+
+.form-area-item-text {
+	width: 160rpx;
+	font-size: 28rpx;
+	color: $text-main;
+	font-weight: 500;
+}
+
+/* 输入控件样式 */
+.form-input {
+	flex: 1;
+	height: 80rpx;
+	line-height: 80rpx;
+	padding: 0 20rpx;
+	border: 1px solid #eee;
+	border-radius: 8rpx;
+	font-size: 26rpx;
+	transition: all 0.3s;
+
+	&:focus {
+		border-color: $primary;
+		box-shadow: 0 0 0 2rpx rgba(189, 49, 36, 0.2);
 	}
+}
+
+.text-area {
+	flex: 1;
+	min-height: 160rpx;
+	padding: 20rpx;
+	border: 1px solid #eee;
+	border-radius: 8rpx;
+	font-size: 26rpx;
+	line-height: 1.6;
+	resize: none;
+	transition: all 0.3s;
 
-	.uni-margin-wrap {
-		width: 690rpx;
-		width: 100%;
+	&:focus {
+		border-color: $primary;
+		box-shadow: 0 0 0 2rpx rgba(189, 49, 36, 0.2);
 	}
+}
+
+/* 按钮样式 */
+.btn-primary {
+	height: 70rpx;
+	line-height: 70rpx;
+	padding: 0 25rpx;
+	border-radius: 8rpx;
+	font-size: 26rpx;
+	background-color: $primary;
+	color: #fff;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	transition: all 0.2s;
 
-	.swiper {
-		height: 350rpx;
+	&:hover {
+		background-color: #C94030;
+		transform: scale(0.98);
 	}
+}
 
-	.swiper-item {
-		display: block;
-		height: 350rpx;
-		text-align: center;
-		background-color: #BD3124;
+.record-btn {
+	height: 70rpx;
+	line-height: 70rpx;
+	padding: 0 30rpx;
+	border-radius: 8rpx;
+	font-size: 26rpx;
+	background-color: #f5f5f5;
+	color: $text-main;
+	transition: all 0.2s;
+
+	&.recording {
+		background-color: $primary;
 		color: #fff;
-		padding-top:30px;
 	}
 
-	.form-area {
-		padding: 20px;
+	&:hover {
+		transform: scale(0.98);
+	}
+}
 
-		.form-area-item {
-			display: flex;
-			flex-direction: row;
-			margin-top: 30px;
-			align-items: center;
+/* 图片预览 */
+.image-preview {
+	width: 80rpx;
+	height: 80rpx;
+	margin-left: 20rpx;
+	border-radius: 8rpx;
+	overflow: hidden;
+	border: 1px solid #eee;
+}
 
-			.form-area-item-text {
-				width: 120px;
-			}
+.preview-img {
+	width: 100%;
+	height: 100%;
+	object-fit: cover;
+}
 
-			/deep/ uni-input {
-				border: 1px solid #e5e5e5;
-				border-radius: 4px;
-				height: 30px;
-				width: 200px;
-			}
+/* 录音动画 */
+.record-status {
+	display: flex;
+	align-items: center;
+	margin-left: 20rpx;
+}
 
-			/deep/ uni-picker {
-				border-bottom: 1px solid #aaa;
-				height: 30px;
-				width: 200px;
-				display: flex;
-				align-items: center;
-			}
+.audio-wave {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	gap: 6rpx;
+	height: 40rpx;
+}
 
-			/deep/ uni-radio-group,
-			/deep/ uni-label {
-				display: flex;
-				align-items: center;
-			}
+.wave-bar {
+	width: 6rpx;
+	background-color: $primary;
+	border-radius: 3rpx;
+	animation: wave 1s infinite ease-in-out;
+}
 
-			/deep/ uni-label {
-				margin-right: 10px;
-			}
+.wave-bar:nth-child(2) {
+	animation-delay: 0.2s;
+}
 
-			/deep/ textarea {
-				border: 1px solid #aaa;
-				border-radius: 4px;
-				width: 320px;
-				padding: 5px;
-				margin: 0 auto;
-			}
+.wave-bar:nth-child(3) {
+	animation-delay: 0.4s;
+}
 
-			.none-file {
-				margin-left: 10px;
-				color: #aaa;
-			}
+@keyframes wave {
 
-			.file-name {
-				margin-left: 10px;
-				width: 50px;
-				overflow: hidden;
-				white-space: nowrap;
-				text-overflow: ellipsis;
-			}
-		}
+	0%,
+	100% {
+		transform: scaleY(0.5);
+	}
+
+	50% {
+		transform: scaleY(1);
 	}
+}
+
+.status-text {
+	font-size: 24rpx;
+	color: $text-secondary;
+	margin-left: 10rpx;
+}
+
+/* 音频文件信息 */
+.file-info {
+	display: flex;
+	flex-direction: column;
+	margin-left: 20rpx;
+}
+
+.file-name {
+	font-size: 24rpx;
+	color: $text-main;
+	width: 200rpx;
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+}
+
+.file-type {
+	font-size: 22rpx;
+	color: #999;
+}
+
+/* 音频播放器 */
+.audio-bar {
+	width: 100%;
+	margin: 20rpx auto;
+}
 
-	.btn-area {
+/* 生成按钮 */
+.btn-area {
+	display: flex;
+	justify-content: center;
+	margin: 40rpx 0;
+	padding: 0 20rpx;
+}
+
+.create-project {
+	width: 100%;
+	height: 90rpx;
+	line-height: 90rpx;
+	background-color: $primary;
+	color: #fff;
+	border-radius: 45rpx;
+	font-size: 30rpx;
+	font-weight: 500;
+	box-shadow: 0 6rpx 12rpx rgba(189, 49, 36, 0.2);
+	transition: all 0.3s;
+}
+
+.button-hover {
+	transform: translateY(2rpx);
+	box-shadow: 0 3rpx 6rpx rgba(189, 49, 36, 0.2);
+}
+
+/* 提示文本 */
+.none-file {
+	margin-left: 20rpx;
+	font-size: 24rpx;
+	color: #999;
+}
+
+/* 选项容器 */
+.content-type-wrap,
+.timbre-wrap {
+	flex: 1;
+
+	/deep/ .uni-data-checkbox {
 		display: flex;
-		justify-content: center;
-		margin: 20px auto;
-		width: 100%;
+		flex-wrap: wrap;
+		gap: 15rpx 20rpx;
 	}
 
-	.audio-bar {
-		text-align: center;
-		display: block;
-		margin-top: 10px;
+	/deep/ .uni-checkbox {
+		margin-right: 5rpx;
 	}
 
-	/deep/ .uni-border-left {
-		display: none;
+	/deep/ .uni-checkbox-input.checked {
+		background-color: $primary !important;
+		border-color: $primary !important;
 	}
+}
+
+/* 功能区块面板 */
+.input-panel {
+	padding: 20rpx;
+	background-color: #fcfcfc;
+	border-radius: 8rpx;
+	margin-top: 10rpx;
+}
 </style>

+ 13 - 7
pages/list/index.vue

@@ -3,10 +3,10 @@
 		<view class="list-item" v-for="item in videoList" :key="item.id">
 			<view class="item-video">
 				<video class="video-player" :controls="false" @play.prevent id="myVideo"
-					src="@/static/videos/86e914d805fe8578f8258d21eeac7ddc.mp4"></video>
+					:src="item.videoUrl"></video>
 			</view>
 			<view class="item-content" @click="jumpToPlayVideo(item)">
-				<view>{{item.name}}</view>
+				<view>{{item.videoName}}</view>
 				<view>{{item.date}}</view>
 				<view :style="{color:item.status==0?'#DE868F':'#333'}">{{item.status==0?'视频生成中':'已生成'}}</view>
 			</view>
@@ -15,6 +15,7 @@
 </template>
 
 <script>
+import { getTaskList } from "@/api/system/user"
 	export default {
 		data() {
 			return {
@@ -22,7 +23,7 @@
 						id: 1,
 						name: 'ceshi1',
 						date: '2025-03-23',
-						status: 0
+						status: 1
 					},
 					{
 						id: 2,
@@ -40,11 +41,17 @@
 						id: 4,
 						name: 'ceshi4',
 						date: '2025-03-23',
-						status: 0
+						status: 1
 					},
 				]
 			}
 		},
+		onLoad() {
+			getTaskList().then(res=>{
+				console.log('res',res);
+				this.videoList=res.data;
+			});
+		},
 		methods: {
 			jumpToPlayVideo(item) {
 				if(item.status==0){
@@ -53,7 +60,7 @@
 					});
 				}else{
 					uni.navigateTo({
-					  url: 'playVideo/playVideo'
+					  url: 'playVideo/playVideo' 
 					});
 				}
 			}
@@ -63,8 +70,7 @@
 
 <style scoped lang="scss">
 	.list-content {
-		padding: 20px;
-
+		padding: 20px; 
 		.list-item {
 			height: 100px;
 			width: 100%;

+ 5 - 5
pages/list/playVideo/playVideo.vue

@@ -2,7 +2,7 @@
 	<view class="video-player-container">
 		<view>
 			<video class="video-player" controls @play.prevent id="myVideo"
-				src="@/static/videos/86e914d805fe8578f8258d21eeac7ddc.mp4"></video>
+				src="http://192.168.0.2:9090/profile/upload/2025/07/16/a_20250716113016A002.mp4"></video>
 		</view>
 		<view class="icon-area">
 			<uni-icons type="download-filled" size="30" color="#BD3124" @click="downloadVideo"></uni-icons>
@@ -51,7 +51,7 @@
 					duration:100000
 				})
 				uni.downloadFile({
-					url: 'https://samplelib.com/lib/preview/mp4/sample-5s.mp4', 
+					url: 'http://192.168.0.2:9090/profile/upload/2025/07/16/a_20250716113016A002.mp4', 
 					success: (res) => {
 						if (res.statusCode === 200) {
 							console.log('下载成功');
@@ -79,7 +79,7 @@
 				uni.share({
 					provider: "sinaweibo",
 					type: 4,
-					mediaUrl: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4",
+					mediaUrl: "http://192.168.0.2:9090/profile/upload/2025/07/16/a_20250716113016A002.mp4",
 					success: function(res) {
 						console.log("success:" + JSON.stringify(res));
 					},
@@ -93,7 +93,7 @@
 					provider: "weixin",
 					scene: "WXSceneSession",
 					type: 4,
-					mediaUrl: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4",
+					mediaUrl: "http://192.168.0.2:9090/profile/upload/2025/07/16/a_20250716113016A002.mp4",
 					success: function(res) {
 						console.log("success:" + JSON.stringify(res));
 					},
@@ -107,7 +107,7 @@
 					provider: "weixin",
 					scene: "WXSceneTimeline",
 					type: 4,
-					mediaUrl: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4",
+					mediaUrl: "http://192.168.0.2:9090/profile/upload/2025/07/16/a_20250716113016A002.mp4",
 					success: function(res) {
 						console.log("success:" + JSON.stringify(res));
 					},

+ 1 - 1
permission.js

@@ -21,7 +21,7 @@ list.forEach(item => {
     invoke(to) {
       if (getToken()) {
         if (to.url === loginPage) {
-          uni.reLaunch({ url: "/" })
+          uni.reLaunch({ url: "index" })
         }
         return true
       } else {

+ 0 - 0
static/scss/@/static/font/iconfont.css


+ 1 - 1
uni.scss

@@ -1,7 +1,7 @@
 /**
  * uni-app内置的常用样式变量
  */
-
+@import 'uview-ui/theme.scss';
 /* 行为相关颜色 */
 $uni-color-primary: #007aff;
 $uni-color-success: #4cd964;