Commit ebbd8963 by haojie

创建动作页面

parent 43a0654c
dexnav
\ No newline at end of file
...@@ -61,6 +61,15 @@ const buttonClick = () => { ...@@ -61,6 +61,15 @@ const buttonClick = () => {
background: #1e1e1e; background: #1e1e1e;
} }
} }
.t-button.c-button-dark2 {
background: #343639;
border-color: #343639;
--ripple-color: #343639 !important;
&:hover {
border-color: #343639;
background: #343639;
}
}
.t-button.c-button-green { .t-button.c-button-green {
background: #00cca2; background: #00cca2;
border-color: #00cca2; border-color: #00cca2;
......
...@@ -4,7 +4,7 @@ import Upload from '../upload'; ...@@ -4,7 +4,7 @@ import Upload from '../upload';
import Button from '../Button.vue'; import Button from '../Button.vue';
import { getUploadConfig } from '@/service/Common'; import { getUploadConfig } from '@/service/Common';
import Dialog from './dialog.vue'; import Dialog from './dialog.vue';
import { isDev, show_message } from '@/utils/tool'; import { show_message } from '@/utils/tool';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { RequestMethodResponse } from 'tdesign-vue-next'; import { RequestMethodResponse } from 'tdesign-vue-next';
import request from '@/utils/otherRequest'; import request from '@/utils/otherRequest';
......
...@@ -26,9 +26,13 @@ ...@@ -26,9 +26,13 @@
height: 200px; height: 200px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
.real-upload-component-child {
height: 100%;
}
.t-upload { .t-upload {
width: 100%; width: 100%;
height: 100%; height: 100%;
.dja();
.custom-upload-click-box { .custom-upload-click-box {
border-radius: 8px; border-radius: 8px;
width: 100%; width: 100%;
...@@ -52,7 +56,7 @@ ...@@ -52,7 +56,7 @@
.t-upload__dragger { .t-upload__dragger {
padding: 0; padding: 0;
border: none; border: none;
width: 572px; width: 100%;
height: 200px; height: 200px;
.t-upload__trigger { .t-upload__trigger {
width: 100%; width: 100%;
...@@ -68,14 +72,30 @@ ...@@ -68,14 +72,30 @@
.upload-success-item { .upload-success-item {
position: relative; position: relative;
width: 100%;
.upload-success-icon {
margin-top: 6px;
position: relative;
.dja();
width: 100%;
.upload-success-video-img {
width: 120px;
height: 85px;
.dja();
img {
width: 90px;
height: 65px;
object-fit: contain;
}
}
.close-icon { .close-icon {
position: absolute; position: absolute;
right: 0; right: 8px;
top: 0; top: -4px;
cursor: pointer; cursor: pointer;
} }
.upload-success-icon {
margin-top: 6px;
} }
} }
.file-name { .file-name {
...@@ -83,10 +103,10 @@ ...@@ -83,10 +103,10 @@
color: #e0e0e0; color: #e0e0e0;
font-size: @size-14; font-size: @size-14;
font-weight: 700; font-weight: 700;
max-width: 70px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
.dja();
} }
& > * { & > * {
margin: 12px; margin: 12px;
...@@ -94,15 +114,6 @@ ...@@ -94,15 +114,6 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
.upload-success-box {
.file-name {
margin-top: 4px;
white-space: nowrap;
max-width: 70px;
overflow: hidden;
text-overflow: ellipsis;
}
}
.uploading-title { .uploading-title {
font-weight: 400; font-weight: 400;
font-size: @size-15; font-size: @size-15;
......
...@@ -2,12 +2,11 @@ import { defineComponent, onMounted, reactive, ref, watch } from 'vue'; ...@@ -2,12 +2,11 @@ import { defineComponent, onMounted, reactive, ref, watch } from 'vue';
import './index.less'; import './index.less';
import { Button as TButton, Upload as TUpload, Progress as TProgress, UploadFile } from 'tdesign-vue-next'; import { Button as TButton, Upload as TUpload, Progress as TProgress, UploadFile } from 'tdesign-vue-next';
import { getUserCookie } from '@/utils/api/userApi'; import { getUserCookie } from '@/utils/api/userApi';
import { show_message, getFileSuffix } from '@/utils/tool'; import { show_message, getFileSuffix, getFirstFrameOfVideo, alyOssUpload } from '@/utils/tool';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import Button from '../Button.vue'; import Button from '../Button.vue';
import AudioSvg from '@/assets/svg/upload/audio.svg'; import AudioSvg from '@/assets/svg/upload/audio.svg';
import CloseSvg from '@/assets/svg/home/close.svg'; import CloseSvg from '@/assets/svg/home/close.svg';
import { alyOssUpload } from '@/utils/tool';
export default defineComponent({ export default defineComponent({
props: { props: {
...@@ -16,6 +15,11 @@ export default defineComponent({ ...@@ -16,6 +15,11 @@ export default defineComponent({
type: Array, type: Array,
default: [], default: [],
}, },
// 是否截取视频第一针
firstFrame: {
type: Boolean,
default: false,
},
audioTotolTime: { audioTotolTime: {
type: [Number, null], type: [Number, null],
default: null, default: null,
...@@ -37,9 +41,14 @@ export default defineComponent({ ...@@ -37,9 +41,14 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
// 最大上传个数
maxUploadNum: {
type: Number,
default: 0,
},
}, },
emits: ['update:modelValue', 'change', 'update:totalSize', 'update:audioTotolTime'], emits: ['update:modelValue', 'change', 'update:totalSize', 'update:audioTotolTime'],
setup(props, { emit }) { setup(props, { emit, slots }) {
const uploadRef = ref(); const uploadRef = ref();
// 选择的文件列表 // 选择的文件列表
const fileList = ref([]); const fileList = ref([]);
...@@ -129,6 +138,11 @@ export default defineComponent({ ...@@ -129,6 +138,11 @@ export default defineComponent({
const beforeUpload = async (file: any) => { const beforeUpload = async (file: any) => {
try { try {
// 是否有个数显示
if (props.maxUploadNum && fileList.value.length >= props.maxUploadNum) {
show_message(`本次任务最多上传${props.maxUploadNum}个`);
return false;
}
const { config } = props; const { config } = props;
if (!config) { if (!config) {
show_message('缺少必要的配置'); show_message('缺少必要的配置');
...@@ -162,12 +176,19 @@ export default defineComponent({ ...@@ -162,12 +176,19 @@ export default defineComponent({
}; };
// 上传成功回调 // 上传成功回调
const UploadSuccessCallback = (uuid: string, url: string) => { const UploadSuccessCallback = async (uuid: string, url: string) => {
// 关闭定时器 // 关闭定时器
window.clearInterval(percentageInterval); window.clearInterval(percentageInterval);
show_message('上传成功', 'success'); show_message('上传成功', 'success');
let list = []; let list = [];
let totalSize = 0; let totalSize = 0;
// 获取视频第一帧
let videoCover = '';
let videoCoverBlob = null;
if (props.firstFrame) {
videoCoverBlob = await getFirstFrameOfVideo(url);
videoCover = URL.createObjectURL(videoCoverBlob);
}
// 根据uuid获取更新对应的信息 // 根据uuid获取更新对应的信息
for (let i = 0; i < fileList.value.length; i++) { for (let i = 0; i < fileList.value.length; i++) {
let item = fileList.value[i]; let item = fileList.value[i];
...@@ -175,6 +196,8 @@ export default defineComponent({ ...@@ -175,6 +196,8 @@ export default defineComponent({
item.url = url; item.url = url;
item.status = true; item.status = true;
item.progress = 100; item.progress = 100;
item.videoCover = videoCover;
item.videoCoverBlob = videoCoverBlob;
} }
if (item.url) { if (item.url) {
list.push(item.url); list.push(item.url);
...@@ -215,8 +238,6 @@ export default defineComponent({ ...@@ -215,8 +238,6 @@ export default defineComponent({
ref: null, ref: null,
}); });
openpercentage(); openpercentage();
// 上传中状态
Curfile.status = 1;
return alyOssUpload(props.config, file, UploadSuccessCallback, UploadErrorCallback, uuid); return alyOssUpload(props.config, file, UploadSuccessCallback, UploadErrorCallback, uuid);
}; };
...@@ -229,21 +250,8 @@ export default defineComponent({ ...@@ -229,21 +250,8 @@ export default defineComponent({
Curfile.status = 0; Curfile.status = 0;
fileList.value = []; fileList.value = [];
} else { } else {
// // 要删除的下标 fileList.value = v;
// let deleteIndex = []; Curfile.status = 2;
// // 数组对比,没有的就删除
// for (let i = 0; i < fileList.value.length; i++) {
// let item = fileList.value[i];
// let index = v.findIndex((url: string) => item == url);
// if (index === -1) {
// // 没找到,删除
// deleteIndex.push(i);
// }
// }
// console.log(deleteIndex);
// deleteIndex.forEach((index: any) => {
// fileList.value.splice(index, 1);
// });
} }
} else { } else {
Curfile.status = 0; Curfile.status = 0;
...@@ -319,7 +327,24 @@ export default defineComponent({ ...@@ -319,7 +327,24 @@ export default defineComponent({
<div class="custom-uploading-stauts"> <div class="custom-uploading-stauts">
{fileList.value.map((item: any, index: number) => ( {fileList.value.map((item: any, index: number) => (
<div class={item.status ? 'upload-success-box' : ''} key={item.id}> <div class={item.status ? 'upload-success-box' : ''} key={item.id}>
{!item.status ? ( {item.status ? (
<div class="upload-success-item">
<div class="upload-success-icon">
{props.firstFrame ? (
<div class="upload-success-video-img">
<img src={item.videoCover} alt="" />
</div>
) : (
<AudioSvg></AudioSvg>
)}
<span class="close-icon" onClick={deleteCurrentUpload.bind(this, index)}>
<CloseSvg></CloseSvg>
</span>
</div>
<div class="file-name">{item.name ? item.name : item.file?.name}</div>
</div>
) : (
<div class="uploading-item">
<TProgress <TProgress
class="custom-t-progress" class="custom-t-progress"
theme="circle" theme="circle"
...@@ -327,17 +352,9 @@ export default defineComponent({ ...@@ -327,17 +352,9 @@ export default defineComponent({
size={'60'} size={'60'}
color={'#00f9f9'} color={'#00f9f9'}
/> />
) : ( <div class="file-name">{item.name ? item.name : item.file?.name}</div>
<div class="upload-success-item">
<div class="upload-success-icon">
<AudioSvg></AudioSvg>
</div>
<span class="close-icon" onClick={deleteCurrentUpload.bind(this, index)}>
<CloseSvg></CloseSvg>
</span>
</div> </div>
)} )}
<div class="file-name">{item.name ? item.name : item.file?.name}</div>
{computedTotalTime() ? ( {computedTotalTime() ? (
<audio <audio
src={item.url} src={item.url}
...@@ -357,8 +374,12 @@ export default defineComponent({ ...@@ -357,8 +374,12 @@ export default defineComponent({
<div class="custom-multiple-upload"> <div class="custom-multiple-upload">
<div class="real-upload-content"> <div class="real-upload-content">
<div class="custom-real-upload-component narrow-scrollbar"> <div class="custom-real-upload-component narrow-scrollbar">
<div v-show={Curfile.status == 0 || !fileList.value.length}>{notUploadHtml()}</div> <div class="real-upload-component-child" v-show={Curfile.status == 0 || !fileList.value.length}>
<div v-show={Curfile.status != 0}>{UploadingHtml()}</div> {notUploadHtml()}
</div>
<div class="real-upload-component-child" v-show={Curfile.status != 0}>
{UploadingHtml()}
</div>
</div> </div>
{Curfile.status !== 0 ? ( {Curfile.status !== 0 ? (
<Button class="keep-upload-btn" theme="green" onClick={triggerUpload}> <Button class="keep-upload-btn" theme="green" onClick={triggerUpload}>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div class="custom-card-box" @click="onChange" :class="[clicked ? 'cursor-pointer' : '', className ? className : '']"> <div class="custom-card-box" @click="onChange" :class="[clicked ? 'cursor-pointer' : '', className ? className : '']">
<div class="img-box" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave"> <div class="img-box" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
<img :src="img" alt="" /> <img :src="img" alt="" />
<div v-show="showHover"> <div v-show="showHover" :class="['hover']">
<slot name="hover"></slot> <slot name="hover"></slot>
</div> </div>
</div> </div>
...@@ -71,6 +71,28 @@ watch( ...@@ -71,6 +71,28 @@ watch(
<style lang="less"> <style lang="less">
@import '@/style/variables.less'; @import '@/style/variables.less';
@keyframes show {
0% {
opacity: 0;
transform: translateX(10%);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
@keyframes hide {
0% {
opacity: 1;
transform: translateX(0);
}
100% {
opacity: 0;
transform: translateX(-10%);
}
}
.custom-card-box { .custom-card-box {
width: 170px; width: 170px;
height: 224px; height: 224px;
...@@ -93,6 +115,14 @@ watch( ...@@ -93,6 +115,14 @@ watch(
// object-fit: cover; // object-fit: cover;
} }
} }
.hover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
animation: show 0.4s forwards;
}
} }
.cursor-pointer { .cursor-pointer {
cursor: pointer; cursor: pointer;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="chose-person-dialog-body"> <div class="chose-person-dialog-body">
<div class="header">选择数字人</div> <div class="header">选择数字人</div>
<div class="group-btns"> <div class="group-btns">
<template v-for="item in groupBtns" :key="item.value"> <template v-for="item in constdigitalPeopleTypeList" :key="item.value">
<Button <Button
theme="opacity" theme="opacity"
class="default-chose-person-btn" class="default-chose-person-btn"
...@@ -51,16 +51,20 @@ import Button from '@/components/Button.vue'; ...@@ -51,16 +51,20 @@ import Button from '@/components/Button.vue';
import Dialog from '@/components/Dialog.vue'; import Dialog from '@/components/Dialog.vue';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { jumpToCreateLivePage } from '@/router/jump'; import { jumpToCreateLivePage } from '@/router/jump';
import { constdigitalPeopleTypeList } from '@/service/Common';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
modelValue: boolean; modelValue: boolean;
adminList: any[]; adminList?: any[];
myList: any[]; myList?: any[];
}>(), }>(),
{}, {
adminList: () => [],
myList: () => [],
},
); );
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue', 'confirm']);
const visible = ref(props.modelValue); const visible = ref(props.modelValue);
...@@ -71,18 +75,8 @@ const cardChange = (id: any) => { ...@@ -71,18 +75,8 @@ const cardChange = (id: any) => {
currentCard.value = id; currentCard.value = id;
}; };
// const currentBtn = ref(constdigitalPeopleTypeList[0].value);
const currentBtn = ref('1');
const groupBtns = [
{
label: '数字人库',
value: '1',
},
{
label: '我的数字人',
value: '2',
},
];
const changeBtn = (item: any) => { const changeBtn = (item: any) => {
currentBtn.value = item.value; currentBtn.value = item.value;
}; };
...@@ -90,7 +84,7 @@ const changeBtn = (item: any) => { ...@@ -90,7 +84,7 @@ const changeBtn = (item: any) => {
// 确认 // 确认
const confirm = () => { const confirm = () => {
visible.value = false; visible.value = false;
jumpToCreateLivePage({ id: currentCard.value, title: '', type: 'new' }, true); emit('confirm', currentCard.value);
}; };
watch( watch(
......
...@@ -103,7 +103,7 @@ export const getRoutes = () => { ...@@ -103,7 +103,7 @@ export const getRoutes = () => {
{ {
path: routerConfig.createAction.path, path: routerConfig.createAction.path,
name: routerConfig.createAction.name, name: routerConfig.createAction.name,
component: () => import('@/pages/createAction/index.vue'), component: () => import('@/pages/createAction/index'),
meta: { title: 'snowhome', keepAlive: true }, meta: { title: 'snowhome', keepAlive: true },
}, },
]; ];
...@@ -140,5 +140,8 @@ export const createLiveRouteKey = 'create_live_route_key'; ...@@ -140,5 +140,8 @@ export const createLiveRouteKey = 'create_live_route_key';
// 音频上传格式限制 // 音频上传格式限制
export const audioAccept = 'wav,mpeg,mp3'; export const audioAccept = 'wav,mpeg,mp3';
// 视频上传格式限制
export const videoAccept = 'mp4';
// 音频切割间隔时长 // 音频切割间隔时长
export const audioSplitDuration = 300; export const audioSplitDuration = 300;
@import '@/style/variables';
.action-chose-person-body {
padding: 0 80px;
.header-title {
font-size: @size-16;
color: #fff;
text-align: center;
padding-top: 30px;
}
.group-btns {
margin: 16px 0;
.default-chose-person-btn {
font-size: @size-13;
height: 27px !important;
border-radius: 4px;
}
}
.btn-active {
border: 1px solid #04ae8a;
background: #04ae8a;
&:hover {
border: 1px solid #04ae8a;
background: #04ae8a;
}
}
.person-list {
display: grid;
justify-content: space-between;
grid-template-columns: repeat(auto-fill, 170px);
gap: 35px 18px;
margin-bottom: 16px;
.custom-card-box {
border: 1px solid transparent;
transition: border 0.2s;
}
.card-active {
border: 1px solid #0dd;
transition: border 0.2s;
}
.more-choices {
width: 170px;
height: 224px;
background: #1e1e1e;
border-radius: 8px;
transition: all 0.2s;
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
text-align: center;
font-size: @size-16;
display: flex;
justify-content: center;
align-items: center;
color: #dfdfdf;
cursor: pointer;
}
}
}
import './index.less';
import { computed, defineComponent, onMounted, reactive, ref } from 'vue';
import { constdigitalPeopleTypeList, getDigitalPeopleList } from '@/service/Common';
import Button from '@/components/Button.vue';
import CardOne from '@/components/cardOne.vue';
import DigitalPeopleDiaog from '@/components/digitalPeopleDiaog.vue';
export default defineComponent({
props: {
modelValue: [String, Number],
loading: Boolean,
},
emits: ['update:modelValue', 'update:loading'],
setup(props, { emit }) {
// 数字人最多展示个数
const maxNum = 9;
const currentBtn = ref(constdigitalPeopleTypeList[0].value);
// 数字人列表
const lists = reactive({
// 显示在页面上的list
showAdminList: [],
adminList: [],
showMyList: [],
myList: [],
});
// 弹窗状态
const dialogVisible = ref(false);
// 当前选择的数字人
const currentCard = computed({
get() {
return props.modelValue;
},
set(value) {
emit('update:modelValue', value);
},
});
const loading = computed({
get() {
return props.loading;
},
set(value) {
emit('update:loading', value);
},
});
const changeBtn = (item: any) => {
currentBtn.value = item.value;
};
const cardChange = (id: any) => {
currentCard.value = id;
};
// 打开弹窗
const openDialog = () => {
dialogVisible.value = true;
};
const getList = async () => {
loading.value = true;
let obj = await getDigitalPeopleList();
loading.value = false;
lists.adminList = obj.adminList;
lists.myList = obj.myList;
// 切换数字人
if (!lists.adminList.length) {
currentBtn.value = constdigitalPeopleTypeList[1].value;
}
// 切割出页面展示的数字人个数
if (lists.adminList.length > maxNum) {
lists.showAdminList = lists.adminList.splice(0, maxNum);
} else {
lists.showAdminList = lists.adminList;
}
if (lists.myList.length > maxNum) {
lists.showMyList = lists.myList.splice(0, maxNum);
} else {
lists.showMyList = lists.myList;
}
};
// 弹窗确认
const onConfirm = (id: any) => {
currentCard.value = id;
};
onMounted(() => {
getList();
});
// 更多选择
const moreChoices = (list: any[]) => {
if (list.length) {
return (
<div class="more-choices" onClick={openDialog}>
更多选择 &gt;
</div>
);
}
return '';
};
return () => (
<div class="action-chose-person-body">
<div class="header-title">选择数字人进行动作创建</div>
<div class="group-btns">
{constdigitalPeopleTypeList.map((item: any) => (
<Button
key={item.value}
theme="opacity"
class={['default-chose-person-btn', item.value == currentBtn.value ? 'btn-active' : '']}
onClick={changeBtn.bind(this, item)}
>
{item.label}
</Button>
))}
</div>
<div class="person-list" v-show={currentBtn.value == '2'}>
{lists.showMyList.map((item: any) => (
<CardOne
className={item.id == currentCard.value ? 'card-active' : ''}
img={item.cover_url}
id={item.id}
name={item.name}
onChange={cardChange}
></CardOne>
))}
{moreChoices(lists.showMyList)}
</div>
<div class="person-list" v-show={currentBtn.value == '1'}>
{lists.showAdminList.map((item: any) => (
<CardOne
className={item.id == currentCard.value ? 'card-active' : ''}
img={item.cover_url}
id={item.id}
name={item.name}
onChange={cardChange}
></CardOne>
))}
{moreChoices(lists.showAdminList)}
</div>
<DigitalPeopleDiaog
v-model={dialogVisible.value}
adminList={lists.adminList}
myList={lists.myList}
onConfirm={onConfirm}
></DigitalPeopleDiaog>
</div>
);
},
});
@import '@/style/variables';
.action-create-success-tab {
padding: 30px 40px;
border-radius: 4px 4px 0px 0px;
background: #303030;
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.04);
min-height: 284px;
position: relative;
.my-person-items {
display: flex;
row-gap: 20px;
flex-wrap: wrap;
margin-left: -20px;
& > * {
margin-left: 20px;
}
}
.action-success-hover {
cursor: pointer;
width: 100%;
height: 100%;
.dja(flex-end);
flex-direction: column;
padding-bottom: 20px;
.t-button {
font-size: @size-13;
}
}
}
import './index.less';
import { defineComponent, ref } from 'vue';
import { LIVE_AUDIT_STATUS } from '@/service/Live';
import CardOneVue from '@/components/cardOne.vue';
import Button from '@/components/Button.vue';
export default defineComponent({
props: {
list: {
type: Array,
default: () => [],
},
},
emits: ['editAction'],
setup(props, { emit }) {
const onEdit = (item: any) => {
emit('editAction', item);
};
const getHover = (item: any) => {
return (
<div class="action-success-hover">
<Button theme="dark2" onClick={onEdit.bind(this, item)}>
编辑
</Button>
</div>
);
};
return () => (
<div class="action-create-success-tab">
<div class="my-person-items">
{props.list.map((item: any) => (
<>
{item.audit_status == LIVE_AUDIT_STATUS.LIVE_AUDIT_STATUS_FINISH ? (
<CardOneVue
img={item.cover}
name={item.name}
id={item.id}
clicked={false}
v-slots={{
hover: getHover.bind(this, item),
}}
></CardOneVue>
) : (
''
)}
</>
))}
</div>
</div>
);
},
});
@import '@/style/variables';
.action-create-record-tab {
padding: 30px 40px;
border-radius: 4px 4px 0px 0px;
background: #303030;
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.04);
min-height: 284px;
position: relative;
.record-items {
border-radius: 6px;
background: #1e1e1e;
height: 150px;
display: flex;
align-items: center;
padding: 0 60px;
.left {
width: 250px;
height: 120px;
img {
border-radius: 8px;
width: 200px;
height: 100%;
}
}
.center {
padding-left: 30px;
flex: 1;
color: #fff;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-around;
.name {
font-size: @size-18;
}
.create {
font-size: @size-16;
}
}
}
.record-items + .record-items {
margin-top: 20px;
}
}
import './index.less';
import { defineComponent } from 'vue';
import CustomizationStatus from '@/components/CustomizationStatus';
export default defineComponent({
props: {
list: {
type: Array,
default: () => [],
},
},
setup(props) {
return () => (
<div class="action-create-record-tab">
{props.list.map((item: any) => (
<div class="record-items" key={item.id}>
<div class="left">
<img src={item.cover_url} alt="" />
</div>
<div class="center">
<div class="name">
名称:
<span>{item.name}</span>
</div>
<div class="create">
创建时间:
<span>{item.created_at}</span>
</div>
</div>
<CustomizationStatus status={item.audit_status}></CustomizationStatus>
</div>
))}
</div>
);
},
});
@import '@/style/variables.less';
.custom-action-page {
.header {
margin: 20px 0;
.da();
font-size: @size-24;
font-weight: 700;
color: #fff;
& > :last-child {
margin-left: 8px;
}
}
.izable-page-upload-box {
background: rgb(48, 48, 48);
position: relative;
.person-list-parent {
min-height: 483px;
}
.custom-multiple-upload {
height: 450px;
}
.custom-real-upload-component {
width: 700px !important;
height: 300px !important;
.close-icon {
right: -12px !important;
}
.custom-uploading-stauts {
grid-template-columns: repeat(auto-fill, 120px) !important;
}
.uploading-item {
width: 120px;
height: 119px;
.dja(space-between);
flex-direction: column;
.custom-t-progress {
margin-top: 12px;
}
}
}
.upload-box-footer {
.dja(flex-end);
padding: 0 12px;
border-top: 1px solid #9f9f9f;
height: 60px;
.footer-buttons {
& > :last-child {
margin-left: 20px;
}
}
.upload-total-time-box {
.da();
.label {
font-size: @size-14;
color: #d4d4d4;
font-weight: 700;
}
.value {
margin-left: 20px;
font-size: @size-20;
.da();
font-weight: 600;
.current-total {
color: #04ae8a;
}
.role-total {
color: #fff;
margin-left: 6px;
}
}
}
}
.upload-box-footer-v2 {
.dja(space-between);
}
.custom-real-upload {
background: rgb(48, 48, 48);
}
}
.izable-page-tabs {
margin-top: 50px;
}
}
import './index.less';
import { defineComponent, onActivated, onMounted, ref } from 'vue';
import Button from '@/components/Button.vue';
import { getLiveMovementList, getUploadConfig } from '@/service/Common';
import MultipleUpload from '@/components/MultipleUpload';
import { videoAccept } from '@/constants/token';
import { useStore } from 'vuex';
import routerConfig from '@/router/tool';
import ChoseDigitalPeople from './components/choseDigitalPeople';
import Loading from '@/components/loading.vue';
import { alyOssUpload, show_message } from '@/utils/tool';
import { createLiveMovement, updateLiveMovement } from '@/utils/api/userApi';
import ConfirmDialog from '@/components/ConfirmDialog.vue';
import CustomTabs from '@/components/CustomTabs';
import CustomTabPanel from '@/components/CustomTabPanel';
import CreateSuccess from './components/createSuccess';
import Record from './components/record';
import { v4 } from 'uuid';
import { useRoute, useRouter } from 'vue-router';
export default defineComponent({
name: routerConfig.createAction.name,
props: {
video: {
type: Boolean,
default: false,
},
submit: {
type: [Function, Boolean],
default: false,
},
},
setup(props, { slots }) {
const store = useStore();
const router = useRouter();
const route = useRoute();
// 上传个数限制
const maxUploadNum = ref(0);
const imgs = {
action: new URL('../../assets/svg/home/action.svg', import.meta.url).href,
};
const currentTab = ref('1');
// 确认弹窗
const confirmDialogVisible = ref(false);
// 当前步骤
const currentStep = ref(1);
// 数字人列表的loading
const loading = ref(false);
// 全局loading(提交时)
const globalLoading = ref(false);
// 当前选择的数字人
const currentCard = ref('');
const ossConfig: any = ref({});
const files = ref([]);
// 动作列表
const actionList = ref([]);
const reset = () => {
files.value = [];
currentCard.value = '';
currentStep.value = 1;
};
// 下一步
const nextStep = () => {
if (!currentCard.value) {
show_message('请先选择数字人');
return;
}
currentStep.value = 2;
};
// 弹窗确认
const onConfirm = async () => {
try {
// 成功回调
// 先把所有的封面上传到阿里云
let list = await Promise.all(
files.value.map(async (item: any) => {
let params: any = {
name: item.file.name,
url: item.url,
digital_man_id: currentCard.value,
};
if (!item.videoCover) {
let result = await alyOssUpload(
ossConfig.value,
item.videoCoverBlob,
() => '',
() => '',
v4(),
);
params.cover = result.response.url;
} else {
params.cover = item.videoCover;
}
return params;
}),
);
// 上传
globalLoading.value = true;
if (route.query.type == 'edit') {
// 更新
let res: any = await updateLiveMovement(route.query.id, list[0]);
if (res.code == 0) {
//
show_message('更新成功', 'success');
maxUploadNum.value = 0;
router.replace({
path: routerConfig.createAction.path,
name: routerConfig.createAction.name,
query: {},
});
}
} else {
let res: any = await createLiveMovement(list);
if (res.code == 0) {
show_message('提交成功,等待审核', 'success');
reset();
}
}
// 重新获取生产记录
getMovement();
globalLoading.value = false;
} catch (e) {
globalLoading.value = false;
console.log(e);
}
};
// 获取用户创建的动作
const getMovement = async () => {
actionList.value = await getLiveMovementList();
};
// 提交
const submit = async () => {
if (!files.value.length) {
show_message('请先上传视频');
return;
}
confirmDialogVisible.value = true;
};
// 编辑
const onEditAction = (item: any) => {
router.replace({
path: routerConfig.createAction.path,
name: routerConfig.createAction.name,
query: {
type: 'edit',
id: item.id,
},
});
maxUploadNum.value = 1;
// 修改当前选择的数字人和视频列表
currentCard.value = item.digital_man_id;
files.value = [
{
file: {
name: item.name,
},
videoCover: item.cover,
status: true,
url: item.url,
},
];
};
onMounted(async () => {
// 是否限制
if (route.query.type == 'edit') {
maxUploadNum.value = 1;
}
// 添加导航
store.commit('navbar/setNavbar', {
path: routerConfig.createAction.path,
});
ossConfig.value = await getUploadConfig();
getMovement();
});
onActivated(() => {
// 添加导航
store.commit('navbar/setNavbar', {
path: routerConfig.createAction.path,
});
});
return () => (
<div class="custom-action-page">
<div class="header">
<img src={imgs.action} alt="" />
<span>动作创建</span>
</div>
<div class="izable-page-upload-box">
<div class="person-list-parent">
<div v-show={currentStep.value == 1}>
<ChoseDigitalPeople v-model={currentCard.value} v-model:loading={loading.value}></ChoseDigitalPeople>
</div>
<div v-show={currentStep.value != 1}>
<MultipleUpload
v-model={files.value}
label={'选择视频'}
config={ossConfig.value}
accept={videoAccept}
firstFrame={true}
maxUploadNum={maxUploadNum.value}
></MultipleUpload>
</div>
<Loading v-show={loading.value}></Loading>
</div>
<div class={['upload-box-footer']}>
<div class="footer-buttons">
<Button theme="opacity" onClick={reset}>
重置
</Button>
{currentStep.value == 1 ? (
<Button theme="green" onClick={nextStep}>
下一步
</Button>
) : (
<Button theme="green" onClick={submit}>
生成
</Button>
)}
</div>
</div>
</div>
<div class="izable-page-tabs">
<CustomTabs v-model={currentTab.value} theme="dark2">
<CustomTabPanel
name="1"
label="已创建"
v-slots={{
default: () => <CreateSuccess list={actionList.value} onEditAction={onEditAction}></CreateSuccess>,
}}
></CustomTabPanel>
<CustomTabPanel
name="2"
label="创建记录"
v-slots={{
default: () => <Record list={actionList.value}></Record>,
}}
></CustomTabPanel>
</CustomTabs>
</div>
<Loading v-show={globalLoading.value} position="fixed" mask={true}></Loading>
<ConfirmDialog
v-model={confirmDialogVisible.value}
title="确定要创建该动作吗?"
onConfirm={onConfirm}
></ConfirmDialog>
</div>
);
},
});
<template>
<div class=""></div>
</template>
<script lang="ts" setup></script>
<style lang="less" scoped></style>
...@@ -802,7 +802,7 @@ onMounted(async () => { ...@@ -802,7 +802,7 @@ onMounted(async () => {
height: 100%; height: 100%;
background: transparent; background: transparent;
.custom-uploading-stauts { .custom-uploading-stauts {
margin-top: 12px; padding-top: 12px;
} }
} }
} }
......
...@@ -60,6 +60,7 @@ ...@@ -60,6 +60,7 @@
v-model="digitalPeopleDialogVisible" v-model="digitalPeopleDialogVisible"
:adminList="digitalPeopleList.adminList" :adminList="digitalPeopleList.adminList"
:myList="digitalPeopleList.myList" :myList="digitalPeopleList.myList"
@confirm="confirm"
></DigitalPeopleDiaog> ></DigitalPeopleDiaog>
</div> </div>
</template> </template>
...@@ -67,7 +68,7 @@ ...@@ -67,7 +68,7 @@
<script lang="tsx" setup> <script lang="tsx" setup>
import { computed, onMounted, reactive, ref } from 'vue'; import { computed, onMounted, reactive, ref } from 'vue';
import Loading from '@/components/loading.vue'; import Loading from '@/components/loading.vue';
import DigitalPeopleDiaog from './components/digitalPeopleDiaog.vue'; import DigitalPeopleDiaog from '@/components/digitalPeopleDiaog.vue';
import CardOne from '@/components/cardOne.vue'; import CardOne from '@/components/cardOne.vue';
import CustomTabs from '@/components/CustomTabs'; import CustomTabs from '@/components/CustomTabs';
import CustomTabPanel from '@/components/CustomTabPanel'; import CustomTabPanel from '@/components/CustomTabPanel';
...@@ -82,7 +83,6 @@ import Button from '@/components/Button.vue'; ...@@ -82,7 +83,6 @@ import Button from '@/components/Button.vue';
import { callPyjsInWindow, injectWindow } from '@/utils/pyqt'; import { callPyjsInWindow, injectWindow } from '@/utils/pyqt';
import { jumpToCreateLivePage } from '@/router/jump'; import { jumpToCreateLivePage } from '@/router/jump';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { getFileSuffixInUrl } from '@/utils/tool';
const router = useRouter(); const router = useRouter();
const store = useStore(); const store = useStore();
...@@ -124,14 +124,19 @@ const toolList = [ ...@@ -124,14 +124,19 @@ const toolList = [
path: routerConfig.createInteract.path, path: routerConfig.createInteract.path,
name: routerConfig.createInteract.name, name: routerConfig.createInteract.name,
}, },
// { {
// label: '动作创建', label: '动作创建',
// icon: imgs.action, icon: imgs.action,
// path: routerConfig.createAction.path, path: routerConfig.createAction.path,
// name: routerConfig.createAction.name, name: routerConfig.createAction.name,
// }, },
]; ];
// 跳转到创建直播页面
const confirm = (id: any) => {
jumpToCreateLivePage({ id: id, title: '', type: 'new' }, true);
};
// 刷新我的数字人 // 刷新我的数字人
const myDigtalReload = () => { const myDigtalReload = () => {
reloadNum.value += 1; reloadNum.value += 1;
...@@ -220,16 +225,6 @@ const startTest = async () => { ...@@ -220,16 +225,6 @@ const startTest = async () => {
// duration: audioSplitDuration, // duration: audioSplitDuration,
// id: 1, // id: 1,
// }); // });
console.log(
getFileSuffixInUrl(
'http://yunyi-live.oss-cn-hangzhou.aliyuncs.com/upload/1/2023-08-11dd2372d8-73ff-4526-bf59-b1618a3f218f.mp3',
),
);
console.log(
getFileSuffixInUrl(
'http://nls-cloud-cn-shanghai.oss-cn-shanghai.aliyuncs.com/jupiter-flow/tmp/ad08fa0a70ae4ea88d11ad5e394ce045.wav?Expires=1692149777&OSSAccessKeyId=LTAIUpwNp2H7pBG5&Signature=D93qMT1DovslSOVa9oufV2cGZxE%3D',
),
);
}; };
onMounted(() => { onMounted(() => {
......
...@@ -5,6 +5,7 @@ import { ...@@ -5,6 +5,7 @@ import {
updateLiveTask, updateLiveTask,
getGroupsInteraction, getGroupsInteraction,
updateDrafts, updateDrafts,
getLiveMovement,
} from '@/utils/api/userApi'; } from '@/utils/api/userApi';
import { typeTones, typeSoundColor } from '@/service/CreateLive'; import { typeTones, typeSoundColor } from '@/service/CreateLive';
import { show_message, alyOssUpload } from '@/utils/tool'; import { show_message, alyOssUpload } from '@/utils/tool';
...@@ -14,6 +15,18 @@ import { v4 } from 'uuid'; ...@@ -14,6 +15,18 @@ import { v4 } from 'uuid';
import { writeLog } from '@/utils/pyqt'; import { writeLog } from '@/utils/pyqt';
import { audioSplitDuration } from '@/constants/token'; import { audioSplitDuration } from '@/constants/token';
// 数字人类型
export const constdigitalPeopleTypeList = [
{
label: '数字人库',
value: '1',
},
{
label: '我的数字人',
value: '2',
},
];
// 音频类型校验 // 音频类型校验
export const audioTypeCheck = () => {}; export const audioTypeCheck = () => {};
...@@ -221,3 +234,17 @@ export const uploadToAly = async (fileList: File[]) => { ...@@ -221,3 +234,17 @@ export const uploadToAly = async (fileList: File[]) => {
// 上传完毕 // 上传完毕
return alyList; return alyList;
}; };
// 获取动作列表
export const getLiveMovementList = async () => {
try {
let res: any = await getLiveMovement();
if (res.code == 0) {
return res.data;
}
return [];
} catch (e) {
console.log(e);
return [];
}
};
...@@ -9,6 +9,7 @@ const imgs = { ...@@ -9,6 +9,7 @@ const imgs = {
person: new URL('../../assets/svg/home/person.svg', import.meta.url).href, person: new URL('../../assets/svg/home/person.svg', import.meta.url).href,
speak: new URL('../../assets/svg/home/speaking.svg', import.meta.url).href, speak: new URL('../../assets/svg/home/speaking.svg', import.meta.url).href,
interaction: new URL('../../assets/svg/home/interaction.svg', import.meta.url).href, interaction: new URL('../../assets/svg/home/interaction.svg', import.meta.url).href,
action: new URL('../../assets/svg/home/action.svg', import.meta.url).href,
}; };
const filterKeepAlive = () => { const filterKeepAlive = () => {
...@@ -63,6 +64,9 @@ const mutations = { ...@@ -63,6 +64,9 @@ const mutations = {
} else if (info.path == routerConfig.createInteract.path) { } else if (info.path == routerConfig.createInteract.path) {
info.icon = imgs.interaction; info.icon = imgs.interaction;
info.label = '互动回答'; info.label = '互动回答';
} else if (info.path == routerConfig.createAction.path) {
info.icon = imgs.action;
info.label = '动作创建';
} }
state.navbarList.push(info); state.navbarList.push(info);
} }
......
...@@ -390,3 +390,33 @@ export const liveVideoDownload = (id: any) => { ...@@ -390,3 +390,33 @@ export const liveVideoDownload = (id: any) => {
}, },
}); });
}; };
// 创建动作
export const createLiveMovement = (data: any) => {
const header = getHeader();
return request.post(`/api/live/movement`, data, {
headers: {
...header,
},
});
};
// 更新动作列表
export const updateLiveMovement = (id: any, data: any) => {
const header = getHeader();
return request.post(`/api/live/movement/${id}`, data, {
headers: {
...header,
},
});
};
// 获取动作列表
export const getLiveMovement = () => {
const header = getHeader();
return request.get(`/api/live/movement`, {
params: {},
headers: {
...header,
},
});
};
...@@ -296,6 +296,10 @@ export const getFileSuffixInUrl = (url: string) => { ...@@ -296,6 +296,10 @@ export const getFileSuffixInUrl = (url: string) => {
} }
return suffix; return suffix;
} else { } else {
writeLog({
name: '无法根据url获取文件后缀',
value: url,
});
return ''; return '';
} }
}; };
...@@ -461,3 +465,42 @@ export const mergedArray = (arr: any[], key: string = 'uuid', first: string = 'i ...@@ -461,3 +465,42 @@ export const mergedArray = (arr: any[], key: string = 'uuid', first: string = 'i
// 防止对象引用重复 // 防止对象引用重复
return ecursionDeepCopy(newList); return ecursionDeepCopy(newList);
}; };
// 获取视频第一帧
export const getFirstFrameOfVideo = (url: string) => {
return new Promise<Blob>((resolve, reject) => {
let imgbase64 = null;
let video = document.createElement('video');
video.setAttribute('crossOrigin', 'Anonymous');
video.setAttribute('src', url);
video.setAttribute('preload', 'auto'); // 不设置该项就不会开启预先加载视频,那么拿到的会是黑屏
//如果不设置currentTime,画出来的图片是空的 当前为截取第0.001秒
video.currentTime = 0.001;
// video.onloadeddata = loadedmetadata;
video.addEventListener('loadeddata', function () {
let canvas = document.createElement('canvas');
let vWidth = video.videoWidth;
let vHeight = video.videoHeight;
let widthBg = 512;
let heightBg;
let splitHeight = 0;
if (vWidth < vHeight) {
heightBg = 300;
// heightBg = vHeight * 512 / vWidth;
splitHeight = vHeight / 2 - 150;
} else {
heightBg = vHeight;
}
//设置倍数是rate,倍数越大画图的图片越大,加载速度越慢
let rate = 1;
canvas.width = widthBg * rate;
canvas.height = 300;
canvas.getContext('2d').drawImage(video, 0, splitHeight, vWidth, heightBg, 0, 0, canvas.width, canvas.height);
// imgbase64 = canvas.toDataURL('image/png');
// 转blob
canvas.toBlob(function (blob) {
resolve(blob);
}, 'image/png');
});
});
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment