Commit 9c61bde6 authored by 黄同智's avatar 黄同智

消息中心的列表页面,更新记录的列表页面

parent aa387a20
......@@ -26,6 +26,18 @@ const routes = [
name: 'Home',
component: Home,
meta: { title: '首页', icon: 'House', affix: true }
},
{
path: 'home/update-log',
name: 'HomeUpdateLog',
component: () => import('@/views/home/update-log.vue'),
meta: { title: '更新日志', hidden: true }
},
{
path: 'home/messages',
name: 'HomeMessages',
component: () => import('@/views/home/messages.vue'),
meta: { title: '消息中心', hidden: true }
}
]
},
......
......@@ -8,7 +8,7 @@
<p class="fs-14 color-666">支持成片、素材、脚本、图片与音频分类检索</p>
</div>
</div>
<div class="flex-items-center color-888 pointer">
<div class="flex-items-center color-888 pointer" @click="gotoMessages">
<svg t="1786427654955" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="6217" width="16" height="16">
<path
......@@ -57,6 +57,9 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const tabIndex = ref(1)
const buttonList = [
{ label: '创作', value: 1, num: 6 },
......@@ -67,6 +70,10 @@ const buttonList = [
{ label: '导出', value: 6, num: 0 },
]
const list = ref([1, 2, 3])
const gotoMessages = ()=>{
router.push('/home/messages')
}
</script>
<style lang="scss" scoped>
......
......@@ -5,10 +5,10 @@
<div class="w-40 h-40 bg-main round-6 mr-10 update-icon"></div>
<div class="section2-head">
<div class="fs-16 fw-700 color-000">更新记录</div>
<p class="fs-14 color-666">算法迭代与功能发布日志(点击小卡片查看完整详情)</p>
<p class="fs-14 color-666">算法迭代与功能发布日志</p>
</div>
</div>
<div class="flex-items-center color-888 pointer">
<div class="flex-items-center color-888 pointer" @click="goUpdateLog">
<svg t="1786427654955" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="6217" width="16" height="16">
<path
......@@ -76,68 +76,17 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { updateRecords, type UpdateRecord } from '../updateRecords';
type UpdateRecord = {
version: string;
type: string;
date: string;
title: string;
desc: string;
details: string[];
};
const router = useRouter();
const detailVisible = ref(false);
const currentDetail = ref<UpdateRecord>();
const list = ref<UpdateRecord[]>([
{
version: 'v2.8.0',
type: '新功能',
date: '2026-07-18',
title: 'AI 数字人主播情感音色与多方言能力上线',
desc: '数字人分身支持粤语、四川话、东北话等多方言口播,以及带货高亢、情绪安利等多种音色风格切换。',
details: [
'20 位高清超模数字人分身库全量上线',
'口型匹配误差降低至 < 0.1 秒,唇形与语音高度同步',
'支持自定义智能文案脚本与实时语速微调',
],
},
{
version: 'v2.7.4',
type: '算法升级',
date: '2026-06-30',
title: '商品卖点识别与脚本生成能力升级',
desc: '新增商品标题、详情页与评价内容的联合理解,帮助运营更快生成适合直播和短视频的脚本。',
details: [
'支持自动提取核心卖点、使用场景和人群痛点',
'新增口播、测评、剧情三类脚本生成模板',
'脚本结果可直接进入任务发布流程',
],
},
{
version: 'v2.7.0',
type: '体验优化',
date: '2026-06-12',
title: '素材上传与智能渲染流程优化',
desc: '上传成功后可自动进入创作工作台,减少重复选择素材与配置参数的操作。',
details: [
'支持批量上传后自动分组',
'新增渲染进度实时反馈',
'优化失败任务重试与错误提示',
],
},
{
version: 'v2.6.8',
type: '数据更新',
date: '2026-05-28',
title: '投放数据看板新增多平台汇总',
desc: '看板新增多广告平台核心指标统一汇总,便于团队按账号、素材和时间维度对比投放效果。',
details: [
'新增巨量广告、千川、腾讯 ADQ 数据汇总',
'支持按团队、分组、个人维度筛选',
'新增素材消耗与转化趋势对比',
],
},
]);
const list = ref<UpdateRecord[]>(updateRecords.slice(0, 4));
const goUpdateLog = () => {
router.push('/home/update-log');
};
const lookDetail = (record: UpdateRecord) => {
currentDetail.value = record;
......
export type MessageCategoryCode = 'all' | 'approval' | 'task' | 'content' | 'live' | 'system';
export type MessageIconName = 'Bell' | 'DocumentChecked' | 'List' | 'Upload' | 'VideoCamera' | 'Lock';
export type MessageCategory = {
code: MessageCategoryCode;
name: string;
icon: MessageIconName;
total: number;
unread: number;
color: string;
bgColor: string;
summary: string;
};
export type MessageTag = {
label: string;
tone: 'warning' | 'primary' | 'success' | 'danger' | 'info';
};
export type MessageRecord = {
id: string;
category: Exclude<MessageCategoryCode, 'all'>;
type: string;
title: string;
content: string;
meta: string[];
tags: MessageTag[];
date: string;
time: string;
state: string;
stateTone: 'pending' | 'doing' | 'done' | 'failed' | 'available' | 'expired';
unread: boolean;
};
export const messageCategories: MessageCategory[] = [
{
code: 'all',
name: '全部消息',
icon: 'Bell',
total: 22,
unread: 16,
color: '#8a22ff',
bgColor: '#f6ecff',
summary: '共 22 条',
},
{
code: 'approval',
name: '审批待办',
icon: 'DocumentChecked',
total: 1,
unread: 1,
color: '#e96500',
bgColor: '#fff7df',
summary: '1 条 · 需要你处理的积分...',
},
{
code: 'task',
name: '任务协作',
icon: 'List',
total: 6,
unread: 5,
color: '#236cff',
bgColor: '#edf5ff',
summary: '6 条 · 发布人与执行人的...',
},
{
code: 'content',
name: '内容资源',
icon: 'Upload',
total: 5,
unread: 4,
color: '#00a874',
bgColor: '#eafff6',
summary: '5 条 · 上传、生成、状态...',
},
{
code: 'live',
name: '直播',
icon: 'VideoCamera',
total: 4,
unread: 3,
color: '#f21f67',
bgColor: '#fff0f3',
summary: '4 条 · 直播间场次与排班...',
},
{
code: 'system',
name: '安全与系统',
icon: 'Lock',
total: 6,
unread: 3,
color: '#4b627f',
bgColor: '#f0f4f8',
summary: '6 条 · 账号安全、系统与...',
},
];
export const messageRecords: MessageRecord[] = [
{
id: 'CA-20260819-001',
category: 'approval',
type: '积分申请',
title: '梁靖淇申请 500 积分,等待你的审批',
content: '申请人【梁靖淇】发起【500 积分】算力补给申请,请审批。关联项目:千川服装爆款视频批量生成。',
meta: ['梁靖淇 → 徐振', 'CA-20260819-001'],
tags: [
{ label: '请关注', tone: 'warning' },
{ label: '积分申请', tone: 'info' },
],
date: '2026-08-19',
time: '10:15',
state: '待审批',
stateTone: 'pending',
unread: true,
},
{
id: 'TASK-20260819-018',
category: 'task',
type: '新任务',
title: '王经理向你指派了新任务',
content: '任务《七夕美妆礼盒短视频》需要产出 3 条成片,请在 8 月 22 日前完成。',
meta: ['王经理(发布人) → 徐振(执行人)', 'TASK-20260819-018'],
tags: [
{ label: '通知', tone: 'primary' },
{ label: '新任务', tone: 'info' },
],
date: '2026-08-19',
time: '09:42',
state: '待完成',
stateTone: 'pending',
unread: true,
},
{
id: 'TASK-20260819-018-DATE',
category: 'task',
type: '任务延期',
title: '任务出片日期由 8 月 22 日调整为 8 月 21 日',
content: '王经理修改了《七夕美妆礼盒短视频》的出片日期,请重新安排制作计划。',
meta: ['王经理(发布人) → 徐振(执行人)', 'TASK-20260819-018'],
tags: [
{ label: '请关注', tone: 'warning' },
{ label: '任务延期', tone: 'info' },
],
date: '2026-08-19',
time: '09:18',
state: '制作中',
stateTone: 'doing',
unread: true,
},
{
id: 'TASK-20260819-018-REL',
category: 'task',
type: '关联作品',
title: '徐振为任务关联了 2 条新成片',
content: '任务《七夕美妆礼盒短视频》当前已关联 2/3 条所需作品。',
meta: ['徐振(执行人) → 王经理(发布人)', 'TASK-20260819-018'],
tags: [
{ label: '已完成', tone: 'success' },
{ label: '关联作品', tone: 'info' },
],
date: '2026-08-19',
time: '08:56',
state: '2/3 条已完成',
stateTone: 'doing',
unread: true,
},
{
id: 'TASK-20260819-018-SCRIPT',
category: 'task',
type: '关联脚本',
title: '徐振为任务关联了脚本',
content: '任务《七夕美妆礼盒短视频》已关联脚本《脚本 1 - 口播温和洁面妆感案》。',
meta: ['徐振(执行人) → 王经理(发布人)', 'TASK-20260819-018'],
tags: [
{ label: '通知', tone: 'primary' },
{ label: '关联脚本', tone: 'info' },
],
date: '2026-08-18',
time: '18:24',
state: '制作中',
stateTone: 'doing',
unread: true,
},
{
id: 'TASK-20260816-009',
category: 'task',
type: '任务完成',
title: '任务所需 3 条作品已全部上传',
content: '徐振已完成《夏季防晒运动素材》的全部作品上传,任务自动标记为已达标。',
meta: ['徐振(执行人) → 王经理(发布人)', 'TASK-20260816-009'],
tags: [
{ label: '已完成', tone: 'success' },
{ label: '任务完成', tone: 'info' },
],
date: '2026-08-18',
time: '16:35',
state: '已达标',
stateTone: 'done',
unread: true,
},
{
id: 'TASK-20260815-006',
category: 'task',
type: '任务逾期',
title: '任务已逾期 18 小时',
content: '《家居收纳直播切片》应于 8 月 18 日 18:00 完成,目前仅上传 1/4 条作品。',
meta: ['系统 → 陈主管(发布人)、李剪辑(执行人)', 'TASK-20260815-006'],
tags: [
{ label: '重要', tone: 'danger' },
{ label: '任务逾期', tone: 'info' },
],
date: '2026-08-19',
time: '12:00',
state: '已逾期',
stateTone: 'expired',
unread: true,
},
{
id: '110332278',
category: 'content',
type: '上传成功',
title: '成片《0730-8839-高弹透气提醒内裤走秀实拍.mp4》上传成功',
content: '文件已完成上传与转码,可在成片区查看和继续编辑。',
meta: ['系统 → 徐振(上传人)', '110332278'],
tags: [
{ label: '已完成', tone: 'success' },
{ label: '上传成功', tone: 'info' },
],
date: '2026-08-19',
time: '11:28',
state: '可用',
stateTone: 'available',
unread: true,
},
{
id: 'UPLOAD-20260819-091',
category: 'content',
type: '上传失败',
title: '素材《主播播间全景_4K.mov》上传失败',
content: '网络中断导致文件分片校验失败,原文件未入库,可重新上传。',
meta: ['系统 → 张摄影(上传人)', 'UPLOAD-20260819-091'],
tags: [
{ label: '重要', tone: 'danger' },
{ label: '上传失败', tone: 'info' },
],
date: '2026-08-19',
time: '11:02',
state: '上传失败',
stateTone: 'failed',
unread: true,
},
{
id: 'LIVE-20260818-012',
category: 'live',
type: '直播排班',
title: '8 月 20 日直播间排班已更新',
content: '直播间 A 的脚本讲解时段调整为 19:30 - 21:00,请相关人员确认。',
meta: ['运营中心 → 直播团队', 'LIVE-20260818-012'],
tags: [
{ label: '通知', tone: 'primary' },
{ label: '排班更新', tone: 'info' },
],
date: '2026-08-18',
time: '15:16',
state: '待确认',
stateTone: 'pending',
unread: true,
},
];
<template>
<div class="p-20 vh100 hidden">
<div class="round-12 hidden bg-fff h-full flex-col">
<header class="flex-items-center justify-between minh-70 px-22" style="border-bottom: 1px solid #eee;">
<div class="header-left flex-items-center">
<el-button class="flex-center w-36 h-36 mr-12 round-8 pointer" title="返回" @click="goBack">
<el-icon>
<ArrowLeft />
</el-icon>
</el-button>
<div>
<div class="flex-items-center">
<div class="fs-18 lh-24 fw-700">消息中心</div>
<span class="bg-ff2865 h-22 lh-22 round-12 color-fff fs-12 ml-10 px-10">{{
unreadCount }} 条未读</span>
</div>
<p class="mt-4 fs-12">业务通知、协作消息与管理审批统一入口</p>
</div>
</div>
<el-button class="flex-center gap-6 h-34 px-14 fs-12 pointer" @click="markAllRead">
<el-icon>
<Check />
</el-icon>
<span>全部标为已读</span>
</el-button>
</header>
<div class="flex-1 flex hidden">
<aside class="flex-col w-220 minw-220 py-14 px-8">
<div v-for="item in messageCategories" :key="item.code"
class="relative flex-items-center w-full minh-66 mb-10 py-10 px-14 round-8 gap-10 text-left pointer"
:class="{ active: activeCategory === item.code }" @click="activeCategory = item.code">
<div class="flex-center w-36 h-36 minw-36 round-8"
:style="{ color: item.color, backgroundColor: item.bgColor }">
<el-icon>
<component :is="iconMap[item.icon]" />
</el-icon>
</div>
<div class="flex-1">
<div class="flex-between">
<span class="category-name lh-20 fw-700">{{ item.name }}</span>
<span v-if="item.unread > 0"
class="bg-ff2865 minw-18 h-18 lh-18 px-6 round-10 color-fff fs-12 text-center">{{
item.unread }}</span>
</div>
<span class="mt-4 fs-12 ellipsis color-888">{{ item.summary }}</span>
</div>
</div>
</aside>
<main class="flex-1 h-full flex-col">
<section class="px-20 h-70 py-14">
<div class="flex gap-10">
<el-input v-model="keyword" class="w-full fs-12" placeholder="搜索标题、人员或业务编号">
<template #prefix>
<el-icon>
<Search />
</el-icon>
</template>
</el-input>
<el-select v-model="selectedType" class="minw-160 maxw-160">
<el-option value="all" label="全部类型" />
<el-option v-for="item in typeOptions" :key="item" :value="item" :label="item" />
</el-select>
<el-select v-model="selectedStatus" class="minw-160 maxw-160">
<el-option value="all" label="全部状态" />
<el-option value="unread" label="未读" />
<el-option value="read" label="已读" />
</el-select>
<div class="w-240 minw-240">
<el-date-picker v-model="dateRange" type="daterange" range-separator="至"
start-placeholder="开始时间" end-placeholder="结束时间" style="width: 100%;" />
</div>
<el-button @click="resetFilters">重置</el-button>
</div>
</section>
<section class="px-20 pt-10 pb-20 h-full over-y-auto">
<div class="mb-10">
<div class="lh-20 fw-600">{{ activeCategoryInfo.name }}</div>
<p class="mt-4 fs-12 color-888">当前筛选结果 {{ filteredMessages.length }} 条</p>
</div>
<div class="hidden round-8 border-eee">
<article v-for="item in filteredMessages" :key="item.id"
class="message-item relative flex-items-center gap-16 minh-104 py-16 px-18 pointer"
:class="{ unread: item.unread }" @click="item.unread = false">
<div class="message-icon relative flex-center w-40 h-40 round-8" :class="item.category">
<span v-if="item.unread" class="unread-dot absolute w-10 h-10 round"></span>
<el-icon>
<component :is="messageIconMap[item.category]" />
</el-icon>
</div>
<div class="message-content flex-1 minw-0">
<div class="message-title-row flex-items-center gap-8 minw-0">
<h3 class="fs-14 lh-22 ellipsis">{{ item.title }}</h3>
<span v-for="tag in item.tags" :key="tag.label"
class="tag h-20 lh-20 round-4 fs-12 px-8" :class="tag.tone">
{{ tag.label }}
</span>
</div>
<p class="mt-6 fs-12 lh-20 ellipsis">{{ item.content }}</p>
<div class="message-meta flex-items-center gap-22 mt-6 fs-12">
<span v-for="meta in item.meta" :key="meta" class="flex-items-center gap-4">
<el-icon v-if="meta.includes('→')">
<User />
</el-icon>
{{ meta }}
</span>
</div>
</div>
<div class="message-time flex-col items-end justify-center gap-6 w-100 fs-12">
<span>{{ item.date }}</span>
<strong class="fs-12">{{ item.time }}</strong>
<em class="fs-12" :class="item.stateTone">{{ item.state }}</em>
</div>
</article>
<div v-if="filteredMessages.length === 0" class="empty-state flex-center h-160 fs-14">
暂无匹配消息
</div>
</div>
</section>
<section class="h-60">
分页
</section>
</main>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import {
ArrowLeft,
Bell,
Check,
DocumentChecked,
List,
Lock,
Search,
Upload,
User,
VideoCamera,
} from '@element-plus/icons-vue';
import {
messageCategories,
messageRecords,
type MessageCategoryCode,
type MessageIconName,
} from './messageRecords';
const router = useRouter();
const activeCategory = ref<MessageCategoryCode>('all');
const keyword = ref('');
const selectedType = ref('all');
const selectedStatus = ref('all');
const dateRange = ref<any>('');
const messages = ref(messageRecords.map((item) => ({ ...item })));
const iconMap: Record<MessageIconName, unknown> = {
Bell,
DocumentChecked,
List,
Upload,
VideoCamera,
Lock,
};
const messageIconMap = {
approval: DocumentChecked,
task: List,
content: Upload,
live: VideoCamera,
system: Lock,
};
const activeCategoryInfo = computed(() => {
return messageCategories.find((item) => item.code === activeCategory.value) ?? messageCategories[0];
});
const unreadCount = computed(() => messages.value.filter((item) => item.unread).length);
const typeOptions = computed(() => {
return Array.from(new Set(messages.value.map((item) => item.type)));
});
const filteredMessages = computed(() => {
const searchValue = keyword.value.trim().toLowerCase();
const [startDate, endDate] = ['', ''];
return messages.value.filter((item) => {
const matchCategory = activeCategory.value === 'all' || item.category === activeCategory.value;
const matchType = selectedType.value === 'all' || item.type === selectedType.value;
const matchStatus =
selectedStatus.value === 'all' ||
(selectedStatus.value === 'unread' && item.unread) ||
(selectedStatus.value === 'read' && !item.unread);
const matchKeyword =
!searchValue ||
[item.title, item.content, item.id, ...item.meta].some((text) => text.toLowerCase().includes(searchValue));
const matchStartDate = !startDate || normalizeDate(item.date) >= normalizeDate(startDate);
const matchEndDate = !endDate || normalizeDate(item.date) <= normalizeDate(endDate);
return matchCategory && matchType && matchStatus && matchKeyword && matchStartDate && matchEndDate;
});
});
const normalizeDate = (date: string) => date.replace(/\//g, '-');
const goBack = () => {
router.back();
};
const markAllRead = () => {
messages.value = messages.value.map((item) => ({ ...item, unread: false }));
};
const resetFilters = () => {
keyword.value = '';
selectedType.value = 'all';
selectedStatus.value = 'all';
dateRange.value = [];
};
</script>
<style lang="scss" scoped>
.bg-ff2865 {
background: #ff2865;
}
.active {
border-color: #dbc1ff;
background: #fbf6ff;
.category-name {
color: #7b11ff;
}
}
.message-item {
border-bottom: 1px solid #ecf1f7;
}
.message-item:last-child {
border-bottom: 0;
}
.message-item.unread {
background: linear-gradient(90deg, #fff 0%, #fff 75%, #fbf7ff 100%);
}
.message-icon {
margin-top: 1px;
}
.message-icon.approval {
color: #e96500;
background: #fff7df;
}
.message-icon.task {
color: #236cff;
background: #edf5ff;
}
.message-icon.content {
color: #00a874;
background: #eafff6;
}
.message-icon.live {
color: #f21f67;
background: #fff0f3;
}
.message-icon.system {
color: #4b627f;
background: #f0f4f8;
}
.unread-dot {
top: -2px;
right: -2px;
background: #ff2865;
}
.message-title-row h3 {
color: #061b39;
letter-spacing: 0;
}
.tag {
flex: 0 0 auto;
}
.tag.warning {
color: #d86a00;
background: #fff5dd;
}
.tag.primary {
color: #136bff;
background: #eaf2ff;
}
.tag.success {
color: #00a874;
background: #e7fff5;
}
.tag.danger {
color: #ff2865;
background: #fff0f4;
}
.tag.info {
color: #5c7899;
background: #eef4fb;
}
.message-content p {
color: #526f91;
}
.message-meta {
color: #7890ae;
}
.message-time {
color: #888;
}
.message-time strong {
color: #17345a;
}
.message-time em {
color: #8a22ff;
font-style: normal;
}
.message-time em.done,
.message-time em.available {
color: #00a874;
}
.message-time em.failed,
.message-time em.expired {
color: #ff2865;
}
.empty-state {
color: #7890ae;
background-color: #fdfdfd;
}
</style>
<template>
<div class="update-log-page">
<div class="update-log-inner">
<header class="update-log-header">
<h1>更新日志</h1>
<p>我们在持续进行功能和体验升级</p>
</header>
<section class="timeline-list">
<article v-for="(item, index) in updateRecords" :key="item.version" class="timeline-item">
<aside class="timeline-side">
<div class="version">{{ item.version }}</div>
<div class="date">{{ formatShortDate(item.date) }}</div>
<span v-if="index === 0" class="latest-tag">最新版</span>
</aside>
<div class="timeline-axis">
<span class="timeline-dot" :class="{ active: index === 0 }"></span>
</div>
<div class="update-panel">
<h2>{{ formatTitleDate(item.date) }}产品特性变更内容</h2>
<p v-if="index === 0" class="summary">{{ item.desc }}</p>
<ol class="detail-list">
<li v-for="detail in item.details" :key="detail">{{ detail }}</li>
</ol>
<p v-if="index === 0" class="contact">
如果你的业务场景比较复杂,需要更多支持,请第一时间联系
<a href="mailto:csm@mcaigc.com">csm@mcaigc.com</a>
</p>
<p class="release-time">升级时间:{{ formatTitleDate(item.date) }}22:00</p>
</div>
</article>
</section>
</div>
</div>
</template>
<script setup lang="ts">
import { updateRecords } from './updateRecords';
const formatShortDate = (date: string) => date.replace(/-/g, '.');
const formatTitleDate = (date: string) => {
const [year, month, day] = date.split('-');
return `${year}${month}${day} 日`;
};
</script>
<style lang="scss" scoped>
.update-log-page {
min-height: 100%;
background: #f5f6f8;
color: #202734;
overflow-x: hidden;
}
.update-log-inner {
width: min(1000px, calc(100% - 48px));
margin: 0 auto;
padding: 0 0 72px;
}
.update-log-header {
min-height: 190px;
text-align: center;
padding-top: 26px;
h1 {
margin: 0;
color: #1f2530;
font-size: 44px;
line-height: 1.15;
font-weight: 700;
letter-spacing: 0;
}
p {
margin: 28px 0 0;
color: #5d6a7e;
font-size: 20px;
line-height: 1.4;
}
}
.timeline-list {
padding-top: 14px;
}
.timeline-item {
display: grid;
grid-template-columns: 132px 36px minmax(0, 1fr);
gap: 16px;
align-items: stretch;
}
.timeline-item + .timeline-item {
margin-top: 60px;
}
.timeline-side {
text-align: right;
padding-top: 2px;
}
.version {
color: #202734;
font-size: 26px;
line-height: 1;
font-weight: 700;
}
.date {
margin-top: 7px;
color: #c1c7d1;
font-size: 13px;
}
.latest-tag {
display: inline-flex;
align-items: center;
justify-content: center;
margin-top: 8px;
min-width: 44px;
height: 24px;
padding: 0 8px;
border-radius: 2px;
background: #dbfff0;
color: #2fd18c;
font-size: 12px;
font-weight: 600;
}
.timeline-axis {
position: relative;
display: flex;
justify-content: center;
}
.timeline-axis::before {
content: '';
position: absolute;
top: 8px;
bottom: -60px;
width: 1px;
background: #e1e5ec;
}
.timeline-item:last-child .timeline-axis::before {
bottom: calc(100% - 8px);
}
.timeline-dot {
position: relative;
z-index: 1;
width: 9px;
height: 9px;
margin-top: 4px;
border-radius: 50%;
border: 1px solid #b8c0cc;
background: #f5f6f8;
}
.timeline-dot.active {
width: 17px;
height: 7px;
margin-top: 8px;
border: 0;
border-radius: 2px;
background: #3157e8;
transform: rotate(24deg);
}
.update-panel {
min-height: 302px;
padding: 26px 24px 24px;
border-radius: 8px;
background: #fff;
box-shadow: 0 10px 30px rgba(36, 45, 64, .03);
}
.update-panel h2 {
margin: 0;
color: #202734;
font-size: 16px;
line-height: 1.5;
font-weight: 700;
letter-spacing: 0;
}
.summary {
margin: 18px 0 0;
color: #8895a7;
font-size: 14px;
line-height: 1.8;
font-weight: 600;
}
.detail-list {
margin: 16px 0 0;
padding-left: 0;
list-style: none;
counter-reset: detail;
}
.detail-list li {
counter-increment: detail;
color: #8a96a8;
font-size: 14px;
line-height: 2.75;
}
.detail-list li::before {
content: counter(detail) '. ';
}
.contact {
margin: 12px 0 0;
color: #202734;
font-size: 16px;
line-height: 1.55;
font-weight: 700;
}
.contact a {
color: #1a00ff;
text-decoration: underline;
}
.release-time {
margin: 2px 0 0;
color: #202734;
font-size: 16px;
line-height: 1.55;
font-weight: 700;
}
@media (max-width: 768px) {
.update-log-inner {
width: min(100% - 28px, 640px);
}
.update-log-header {
min-height: 150px;
padding-top: 24px;
h1 {
font-size: 34px;
}
p {
margin-top: 18px;
font-size: 16px;
}
}
.timeline-item {
grid-template-columns: 88px 22px minmax(0, 1fr);
gap: 10px;
}
.timeline-item + .timeline-item {
margin-top: 28px;
}
.version {
font-size: 20px;
}
.update-panel {
min-height: auto;
padding: 20px 18px;
}
.contact,
.release-time {
font-size: 14px;
}
}
</style>
export type UpdateRecord = {
version: string;
type: string;
date: string;
title: string;
desc: string;
details: string[];
};
export const updateRecords: UpdateRecord[] = [
{
version: 'v2.8.0',
type: '新功能',
date: '2026-07-18',
title: 'AI 数字人主播情感音色与多方言能力上线',
desc: '数字人分身支持粤语、四川话、东北话等多方言口播,以及带货高亢、情绪安利等多种音色风格切换。',
details: [
'20 位高清超模数字人分身库全量上线',
'口型匹配误差降低至 < 0.1 秒,唇形与语音高度同步',
'支持自定义智能文案脚本与实时语速微调',
],
},
{
version: 'v2.7.4',
type: '算法升级',
date: '2026-06-30',
title: '商品卖点识别与脚本生成能力升级',
desc: '新增商品标题、详情页与评价内容的联合理解,帮助运营更快生成适合直播和短视频的脚本。',
details: [
'支持自动提取核心卖点、使用场景和人群痛点',
'新增口播、测评、剧情三类脚本生成模板',
'脚本结果可直接进入任务发布流程',
],
},
{
version: 'v2.7.0',
type: '体验优化',
date: '2026-06-12',
title: '素材上传与智能渲染流程优化',
desc: '上传成功后可自动进入创作工作台,减少重复选择素材与配置参数的操作。',
details: [
'支持批量上传后自动分组',
'新增渲染进度实时反馈',
'优化失败任务重试与错误提示',
],
},
{
version: 'v2.6.8',
type: '数据更新',
date: '2026-05-28',
title: '投放数据看板新增多平台汇总',
desc: '看板新增多广告平台核心指标统一汇总,便于团队按账号、素材和时间维度对比投放效果。',
details: [
'新增巨量广告、千川、腾讯 ADQ 数据汇总',
'支持按团队、分组、个人维度筛选',
'新增素材消耗与转化趋势对比',
],
},
];
......@@ -12,6 +12,7 @@ export default defineConfig({
},
publicDir: 'public',
server: {
host: '0.0.0.0',
port: 4173,
open: true,
// ffmpeg.wasm 需要 SharedArrayBuffer,必须配置 COOP/COEP 实现跨域隔离
......
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