Files
msh-system/msh_single_uniapp/pages/ai-generate/history.vue

1063 lines
29 KiB
Vue
Raw Normal View History

<template>
<view class="history-container" :style="{ paddingTop: statusBarHeight + 'px' }">
<!-- 顶部导航 -->
<view class="nav-bar">
<view class="nav-left" @click="goBack">
<text class="iconfont icon-fanhui"></text>
</view>
<view class="nav-title">我的创作</view>
<view class="nav-right" @click="showManageMenu">
<text class="iconfont icon-gengduo"></text>
</view>
</view>
<!-- AI生成内容标识 -->
<!-- <view class="ai-notice">
<text class="ai-notice-icon">🤖</text>
<text class="ai-notice-text">作品由AI生成结果仅供参考</text>
</view> -->
<!-- Tab切换 -->
<view class="tab-bar">
<view
v-for="(tab, index) in tabs"
:key="index"
:class="['tab-item', selectedTab === tab.value ? 'active' : '']"
@click="selectedTab = tab.value"
>
<text class="tab-text">{{ tab.label }}</text>
</view>
</view>
<!-- 内容区 -->
<scroll-view scroll-y class="content-scroll" :style="{ height: scrollViewHeight }">
<!-- 空状态 -->
<view class="empty-state" v-if="historyList.length === 0">
<image src="/static/images/empty-history.png" class="empty-image" mode="aspectFit"></image>
<text class="empty-text">暂无历史记录</text>
<button class="empty-btn" @click="goToGenerate">开始创作</button>
</view>
<!-- 历史列表 -->
<view class="history-list" v-else>
<view
v-for="(item, index) in filteredList"
:key="item.id"
class="history-item"
@click="viewHistory(item)"
>
<!-- 左侧图片预览 -->
<view class="item-images">
<image
v-if="item.imageInput"
:src="item.imageInput"
mode="aspectFill"
class="preview-image"
></image>
<image
v-else-if="item.images && item.images.length > 0"
:src="item.images[0]"
mode="aspectFill"
class="preview-image"
></image>
<view class="image-count" v-if="item.images && item.images.length > 1">
<text class="count-text">+{{ item.images.length - 1 }}</text>
</view>
<!-- 状态标识 -->
<view class="status-badge" :class="{
'status-processing': item.statusTask === 0,
'status-completed': item.statusTask === 1,
'status-failed': item.statusTask === 2
}">
<text class="status-text">{{ getStatusTextByTask(item.statusTask) }}</text>
</view>
</view>
<!-- 右侧信息 -->
<view class="item-info">
<view class="info-header">
<text class="item-prompt">{{ item.prompt }}</text>
<view class="item-actions" @click.stop="showItemMenu(item)">
<text class="iconfont icon-gengduo"></text>
</view>
</view>
<view class="info-meta">
<view class="meta-tags">
<!-- <text class="meta-tag">{{ item.tags }}</text> -->
<!-- 类型标识 -->
<text class="meta-tag">{{ getTypeText(item.type) }}</text>
<text class="meta-tag" v-if="item.quality">{{ item.quality }}</text>
</view>
<text class="meta-time">{{ formatTime(item.createdAt) }}</text>
</view>
<!-- 进度条生成中 -->
<view class="progress-bar" v-if="item.statusTask === 0">
<view class="progress-fill" :style="{ width: item.progress + '%' }"></view>
</view>
<!-- 备注失败状态 -->
<view class="item-remark" v-if="item.statusTask === 2 && item.remark">
<text class="remark-text">{{ item.remark }}</text>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<view class="load-more" v-if="hasMore && historyList.length > 0">
<text class="load-text" @click="loadMore">加载更多</text>
</view>
</scroll-view>
<!-- 操作菜单 -->
<view class="action-sheet" v-if="showActionSheet" @click="showActionSheet = false">
<view class="action-content" @click.stop>
<view class="action-title">操作</view>
<view
v-for="(action, index) in currentActions"
:key="index"
class="action-item"
@click="handleAction(action.value)"
>
<text class="action-icon iconfont" :class="action.icon"></text>
<text class="action-text">{{ action.label }}</text>
</view>
<view class="action-cancel" @click="showActionSheet = false">
<text>取消</text>
</view>
</view>
</view>
</view>
</template>
<script>
import api from '@/api/models-api.js'
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters(['userInfo','uid']),
filteredList() {
if (this.selectedTab === 'all') {
return this.historyList;
} else if (this.selectedTab === 'completed') {
// 已完成statusTask === 1
return this.historyList.filter(item => item.statusTask === 1);
} else if (this.selectedTab === 'draft') {
// 草稿/未完成statusTask === 0
return this.historyList.filter(item => item.statusTask === 0);
}
return this.historyList;
}
},
data() {
return {
statusBarHeight: 0,
scrollViewHeight: '100vh',
// Tab
selectedTab: 'all',
tabs: [
{ label: '全部', value: 'all' },
{ label: '已完成', value: 'completed' },
{ label: '草稿', value: 'draft' }
],
// 历史记录
historyList: [],
// 分页
currentPage: 1,
pageSize: 10,
hasMore: true,
isLoading: false,
// 操作菜单
showActionSheet: false,
currentItem: null,
currentActions: []
};
},
onLoad() {
this.initPage();
this.loadHistoryList();
},
methods: {
initPage() {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
// 计算滚动区域高度
const navHeight = 44 + 44; // 导航+Tab
this.scrollViewHeight = `calc(100vh - ${this.statusBarHeight + navHeight}px)`;
},
// 返回
goBack() {
uni.navigateBack();
},
// 查看历史记录
viewHistory(item) {
// 根据 statusTask 判断状态
if (item.statusTask === 0) {
// 未完成:继续编辑或提示生成中
uni.showToast({
title: '生成中,请稍候...',
icon: 'none'
});
return;
} else if (item.statusTask === 2) {
// 失败
uni.showToast({
title: '生成失败,请重试',
icon: 'none'
});
return;
} else if (item.statusTask === 1) {
// 已完成:根据 type 字段判断跳转页面
// type == 1 表示图片,跳转到 image.vue
// 其他情况(包括 undefined、null、0、2等跳转到 video.vue
console.log("====viewHistory=====", item);
if (item.type === 1) {
uni.navigateTo({
url: `/pages/ai-generate/image?id=${item.id}`
});
} else {
uni.navigateTo({
url: `/pages/ai-generate/video?id=${item.id}`
});
}
}
},
// 显示管理菜单
showManageMenu() {
this.currentActions = [
{ label: '清空已完成', value: 'clear_completed', icon: 'icon-shanchu' },
{ label: '清空全部', value: 'clear_all', icon: 'icon-qingkong' }
];
this.showActionSheet = true;
},
// 显示单项菜单
showItemMenu(item) {
this.currentItem = item;
// 根据 statusTask 判断
if (item.statusTask === 1) {
// 已完成
this.currentActions = [
{ label: '查看详情', value: 'view', icon: 'icon-chakan' },
{ label: '再次生成', value: 'regenerate', icon: 'icon-shuaxin' },
{ label: '下载保存', value: 'download', icon: 'icon-xiazai' },
{ label: '分享', value: 'share', icon: 'icon-fenxiang' },
{ label: '删除', value: 'delete', icon: 'icon-shanchu' }
];
} else if (item.statusTask === 0) {
// 未完成
this.currentActions = [
{ label: '查看进度', value: 'view', icon: 'icon-chakan' },
{ label: '取消生成', value: 'cancel', icon: 'icon-guanbi' },
{ label: '删除', value: 'delete', icon: 'icon-shanchu' }
];
} else if (item.statusTask === 2) {
// 失败
this.currentActions = [
{ label: '重新生成', value: 'regenerate', icon: 'icon-shuaxin' },
{ label: '删除', value: 'delete', icon: 'icon-shanchu' }
];
} else {
// 其他状态
this.currentActions = [
{ label: '取消生成', value: 'cancel', icon: 'icon-guanbi' }
];
}
this.showActionSheet = true;
},
// 处理操作
handleAction(action) {
this.showActionSheet = false;
switch (action) {
case 'view':
this.viewHistory(this.currentItem);
break;
case 'regenerate':
// TODO: 再次生成
uni.showToast({ title: '开始生成', icon: 'none' });
break;
case 'download':
// TODO: 下载
uni.showToast({ title: '已保存', icon: 'success' });
break;
case 'share':
// TODO: 分享
uni.showToast({ title: '分享功能开发中', icon: 'none' });
break;
case 'delete':
this.deleteItem(this.currentItem);
break;
case 'edit':
uni.navigateTo({
url: `/pages/ai-generate/index?draftId=${this.currentItem.id}`
});
break;
case 'cancel':
// TODO: 取消生成
uni.showToast({ title: '已取消', icon: 'none' });
break;
case 'clear_completed':
this.clearHistory('completed');
break;
case 'clear_all':
this.clearHistory('all');
break;
}
},
// 删除单项
deleteItem(item) {
uni.showModal({
title: '确认删除',
content: '删除后无法恢复',
success: async (res) => {
if (res.confirm) {
try {
// TODO: 调用删除API
// await api.deleteArticle(item.id);
const index = this.historyList.findIndex(i => i.id === item.id);
if (index > -1) {
this.historyList.splice(index, 1);
uni.showToast({ title: '已删除', icon: 'success' });
}
} catch (error) {
console.error('删除失败:', error);
uni.showToast({ title: '删除失败,请重试', icon: 'none' });
}
}
}
});
},
// 清空历史
clearHistory(type) {
uni.showModal({
title: '确认清空',
content: type === 'all' ? '清空所有历史记录?' : '清空已完成的记录?',
success: async (res) => {
if (res.confirm) {
try {
// TODO: 调用批量删除API
// if (type === 'all') {
// await api.deleteAllArticles();
// } else {
// await api.deleteCompletedArticles();
// }
if (type === 'all') {
this.historyList = [];
} else {
// 清空已完成statusTask === 1
this.historyList = this.historyList.filter(item =>
item.statusTask !== 1
);
}
uni.showToast({ title: '已清空', icon: 'success' });
// 重新加载数据
this.loadHistoryList(true);
} catch (error) {
console.error('清空失败:', error);
uni.showToast({ title: '清空失败,请重试', icon: 'none' });
}
}
}
});
},
// 加载历史记录列表
async loadHistoryList(refresh = false) {
if (this.isLoading) return;
if (refresh) {
this.currentPage = 1;
this.hasMore = true;
}
if (!this.hasMore) return;
// 检查是否有用户ID
if (!this.uid) {
console.warn('用户ID不存在无法加载作品列表');
uni.showToast({
title: '请先登录',
icon: 'none',
duration: 2000
});
this.isLoading = false;
return;
}
this.isLoading = true;
try {
// 使用 searchArticles 接口,支持 uid 和 statusTask 参数
const res = await api.searchArticles({
uid: this.uid,
page: this.currentPage,
size: this.pageSize
});
if (res && res.code === 200 && res.data) {
// 兼容不同的数据结构
const list = res.data.list || res.data.records || res.data || [];
// 转换数据格式
const formattedList = list.map(item => {
// 解析图片列表(可能是字符串或数组)
let images = [];
if (item.imageOutput) {
if (typeof item.imageOutput === 'string') {
try {
images = JSON.parse(item.imageOutput);
} catch (e) {
images = [item.imageOutput];
}
} else if (Array.isArray(item.imageOutput)) {
images = item.imageOutput;
}
}
// 获取 statusTask默认为 0未完成
const statusTask = item.statusTask !== undefined && item.statusTask !== null
? item.statusTask
: (images && images.length > 0 ? 1 : 0);
// 根据 statusTask 判断状态(用于显示)
let status = 'draft';
if (statusTask === 1) {
status = 'completed';
} else if (statusTask === 2) {
status = 'failed';
} else if (statusTask === 0) {
status = 'draft';
}
return {
id: item.id,
prompt: item.title || item.prompt || '未命名作品',
images: images,
imageInput: item.imageInput || '', // 保存 imageInput 用于左侧图片展示
model: item.model || 'design-v1',
uid: item.uid,
taskId: item.taskId,
tags: item.tags || item.model || '',
aspectRatio: item.image_size || item.aspect_ratio || '9:16',
quality: item.quality || '2K',
status: status,
statusTask: statusTask, // 保存原始 statusTask 用于过滤
remark: item.remark || '',
type: item.type, // 保存文章类型,用于判断跳转页面
progress: statusTask === 1 ? 100 : 0,
createdAt: item.created_at || item.createTime ? new Date(item.created_at || item.createTime).getTime() : Date.now(),
articleData: item // 保存原始数据
};
});
if (refresh) {
this.historyList = formattedList;
} else {
this.historyList = [...this.historyList, ...formattedList];
}
// 判断是否还有更多
if (list.length < this.pageSize) {
this.hasMore = false;
} else {
this.currentPage++;
}
} else {
throw new Error(res?.message || '获取作品列表失败');
}
} catch (error) {
console.error('加载历史记录失败:', error);
uni.showToast({
title: error.message || '加载失败,请重试',
icon: 'none',
duration: 2000
});
} finally {
this.isLoading = false;
}
},
// 加载更多
loadMore() {
if (this.hasMore && !this.isLoading) {
this.loadHistoryList(false);
}
},
// 去生成页面
goToGenerate() {
uni.navigateTo({
url: '/pages/ai-generate/index'
});
},
// 获取状态文本(兼容旧方法)
getStatusText(status) {
const map = {
processing: '生成中',
failed: '失败',
draft: '草稿'
};
return map[status] || '';
},
// 根据 statusTask 获取状态文本
getStatusTextByTask(statusTask) {
switch (statusTask) {
case 0:
return '进行中';
case 1:
return '已完成';
case 2:
return '失败';
default:
return '未知';
}
},
// 根据 type 获取类型文本
getTypeText(type) {
if (type === 1) {
return '图片';
} else if (type === 2) {
return '视频';
}
return '';
},
// 格式化时间
formatTime(timestamp) {
const now = Date.now();
const diff = now - timestamp;
if (diff < 60000) {
return '刚刚';
} else if (diff < 3600000) {
return Math.floor(diff / 60000) + '分钟前';
} else if (diff < 86400000) {
return Math.floor(diff / 3600000) + '小时前';
} else if (diff < 2592000000) {
return Math.floor(diff / 86400000) + '天前';
} else {
const date = new Date(timestamp);
return `${date.getMonth() + 1}-${date.getDate()}`;
}
}
}
};
</script>
<style lang="scss" scoped>
.history-container {
width: 100%;
min-height: 100vh;
background: linear-gradient(180deg, #000000 0%, #1a1a1a 100%);
overflow-x: hidden;
}
// 顶部导航
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 16px;
.nav-left,
.nav-right {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
.iconfont {
font-size: 20px;
color: #ffffff;
}
}
.nav-title {
font-size: 17px;
font-weight: 500;
color: #ffffff;
}
}
// AI生成内容标识
.ai-notice {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 8px 16px;
margin: 8px 16px;
background: rgba(66, 202, 77, 0.1);
border: 1px solid rgba(66, 202, 77, 0.3);
border-radius: 8px;
backdrop-filter: blur(10px);
.ai-notice-icon {
font-size: 16px;
}
.ai-notice-text {
font-size: 12px;
color: #42ca4d;
line-height: 1.4;
}
}
// Tab栏
.tab-bar {
display: flex;
height: 44px;
padding: 0 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
.tab-item {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
.tab-text {
font-size: 15px;
color: #8f9bb3;
}
&.active {
.tab-text {
color: #42ca4d;
font-weight: 500;
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 24px;
height: 3px;
background: #42ca4d;
border-radius: 2px;
}
}
}
}
// 内容滚动区
.content-scroll {
padding: 16px; width: 92%;
}
// 空状态
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 0;
.empty-image {
width: 200px;
height: 200px;
margin-bottom: 24px;
}
.empty-text {
font-size: 15px;
color: #8f9bb3;
margin-bottom: 24px;
}
.empty-btn {
width: 140px;
height: 44px;
background: linear-gradient(135deg, #42ca4d 0%, #38b045 100%);
border-radius: 22px;
font-size: 15px;
color: #ffffff;
border: none;
line-height: 44px;
}
}
// 历史列表
.history-list {
.history-item {
display: flex;
gap: 16px;
padding: 16px;
background: rgba(26, 31, 46, 0.6);
border-radius: 16px;
margin-bottom: 16px;
min-height: 140px;
border: 1px solid rgba(255, 255, 255, 0.06);
backdrop-filter: blur(20px);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2), 0 0 1px rgba(255, 255, 255, 0.05);
&:active {
transform: scale(0.98);
background: rgba(26, 31, 46, 0.75);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3), 0 0 1px rgba(255, 255, 255, 0.08);
}
&:hover {
border-color: rgba(66, 202, 77, 0.2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), 0 0 2px rgba(66, 202, 77, 0.1);
}
.item-images {
width: 120px;
height: 120px;
flex-shrink: 0;
position: relative;
border-radius: 12px;
overflow: hidden;
background: rgba(0, 0, 0, 0.3);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.08);
.preview-image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
&:hover .preview-image {
transform: scale(1.05);
}
.image-count {
position: absolute;
bottom: 6px;
right: 6px;
padding: 3px 8px;
background: rgba(0, 0, 0, 0.75);
border-radius: 8px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
.count-text {
font-size: 11px;
color: #ffffff;
font-weight: 500;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
}
.type-badge {
position: absolute;
top: 6px;
right: 6px;
padding: 3px 8px;
border-radius: 10px;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.2);
z-index: 9;
.type-text {
font-size: 10px;
font-weight: 500;
color: #ffffff;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
letter-spacing: 0.5px;
}
}
.status-badge {
position: absolute;
bottom: 8px;
left: 8px;
padding: 5px 12px;
border-radius: 14px;
backdrop-filter: blur(12px);
z-index: 10;
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4), 0 0 12px rgba(66, 202, 77, 0.3);
.status-text {
font-size: 11px;
font-weight: 600;
color: #ffffff;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
letter-spacing: 0.5px;
}
// 进行中状态 - 使用主色调高亮
&.status-processing {
background: linear-gradient(135deg, rgba(0, 167, 14, 0.95) 0%, rgba(0, 2, 0, 0.95) 100%);
box-shadow:
0 2px 12px rgba(66, 202, 77, 0.5),
0 0 20px rgba(66, 202, 77, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.25);
animation: pulse-glow 2s ease-in-out infinite;
border-color: rgba(66, 202, 77, 0.4);
}
// 已完成状态
&.status-completed {
background: linear-gradient(135deg, rgba(66, 202, 77, 0.9) 0%, rgba(56, 176, 69, 0.9) 100%);
box-shadow:
0 2px 8px rgba(66, 202, 77, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
border-color: rgba(66, 202, 77, 0.3);
}
// 失败状态 - 使用红色
&.status-failed {
background: linear-gradient(135deg, rgba(255, 107, 107, 0.95) 0%, rgba(238, 90, 111, 0.95) 100%);
box-shadow:
0 2px 12px rgba(255, 107, 107, 0.5),
0 0 20px rgba(255, 107, 107, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.25);
border-color: rgba(255, 107, 107, 0.4);
}
}
// 脉冲光晕动画
@keyframes pulse-glow {
0%, 100% {
box-shadow:
0 2px 10px rgba(66, 202, 77, 0.5),
0 0 16px rgba(66, 202, 77, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
50% {
box-shadow:
0 2px 12px rgba(66, 202, 77, 0.7),
0 0 20px rgba(66, 202, 77, 0.6),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
}
}
.item-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
min-width: 0;
.info-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
.item-prompt {
flex: 1;
font-size: 15px;
color: #ffffff;
line-height: 1.5;
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-word;
}
.item-actions {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border-radius: 6px;
transition: all 0.2s ease;
.iconfont {
font-size: 18px;
color: #8f9bb3;
transition: color 0.2s ease;
}
&:active {
background: rgba(66, 202, 77, 0.1);
.iconfont {
color: #42ca4d;
}
}
}
}
.info-meta {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: auto;
.meta-tags {
display: flex;
gap: 8px;
flex-wrap: wrap;
.meta-tag {
padding: 4px 10px;
background: rgba(61, 68, 88, 0.4);
border-radius: 8px;
font-size: 11px;
color: #8f9bb3;
border: 1px solid rgba(255, 255, 255, 0.05);
backdrop-filter: blur(8px);
transition: all 0.2s ease;
&:first-child {
background: rgba(66, 202, 77, 0.15);
color: #42ca4d;
border-color: rgba(66, 202, 77, 0.3);
}
}
}
.meta-time {
font-size: 12px;
color: #6b7589;
flex-shrink: 0;
margin-left: 8px;
}
}
.progress-bar {
height: 4px;
background: rgba(61, 68, 88, 0.4);
border-radius: 4px;
overflow: hidden;
margin-top: 12px;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #42ca4d 0%, #38b045 100%);
transition: width 0.3s ease;
box-shadow: 0 0 8px rgba(66, 202, 77, 0.5);
border-radius: 4px;
}
}
.item-remark {
margin-top: 12px;
padding: 10px 12px;
background: rgba(255, 107, 107, 0.1);
border: 1px solid rgba(255, 107, 107, 0.2);
border-radius: 8px;
backdrop-filter: blur(8px);
.remark-text {
font-size: 13px;
color: #ff6b6b;
line-height: 1.5;
word-break: break-word;
}
}
}
}
}
// 加载更多
.load-more {
padding: 20px;
text-align: center;
.load-text {
font-size: 13px;
color: #42ca4d;
}
}
// 操作菜单
.action-sheet {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: flex-end;
z-index: 9999;
.action-content {
width: 100%;
background: #1a1f2e;
border-radius: 16px 16px 0 0;
padding-bottom: env(safe-area-inset-bottom);
.action-title {
padding: 16px;
text-align: center;
font-size: 13px;
color: #8f9bb3;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.action-item {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
.action-icon {
font-size: 18px;
color: #ffffff;
}
.action-text {
font-size: 15px;
color: #ffffff;
}
}
.action-cancel {
margin-top: 8px;
padding: 16px;
text-align: center;
font-size: 15px;
color: #ffffff;
background: rgba(61, 68, 88, 0.3);
}
}
}
</style>