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

1063 lines
29 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>