<template>
	<view class="memorial-bg">
		<view class="memorial-content">
			<!-- 头部标题 -->
			<view class="header">
				<text class="logo">AI声音克隆</text>
				<text class="tagline">克隆亲人声音 · 语音合成 · 实时对话</text>
			</view>
			
			<!-- 消息提示 -->
			<view v-if="message.text" :class="['message', message.type]">
				{{ message.text }}
			</view>

			<!-- 创建音色按钮 -->
			<view class="create-voice-btn-wrapper">
				<button class="create-voice-btn" @click="goToUpload">
					📤 创建音色
				</button>
			</view>

			<!-- 标签页 -->
			<view class="tabs">
				<view :class="['tab-item', activeTab === 'synthesize' ? 'active' : '']" @click="activeTab = 'synthesize'">
					🔊 合成
				</view>
				<view :class="['tab-item', activeTab === 'conversation' ? 'active' : '']" @click="activeTab = 'conversation'">
					💬 对话
				</view>
				<view :class="['tab-item', activeTab === 'list' ? 'active' : '']" @click="activeTab = 'list'">
					📋 列表
				</view>
			</view>
		</view>

		<!-- 内容区域 -->
		<scroll-view scroll-y class="content">
			<!-- 语音合成 -->
			<view v-if="activeTab === 'synthesize'" class="tab-content">
				<view class="section-title">🎵 语音合成</view>

				<view class="form-section">
					<view class="form-label-with-help">
						<text class="form-label">选择音色类型</text>
						<text class="help-icon" @click="showVoiceHelp">❓</text>
					</view>
					<picker mode="selector" :range="voiceTypeOptions" range-key="label" @change="onVoiceTypeChange">
						<view class="picker-large">
							{{ selectedVoiceTypeLabel }}
						</view>
					</picker>
					<picker mode="selector" :range="displayVoices" range-key="voice_name" @change="onVoiceChange">
						<view class="picker-large">
							{{ selectedVoiceName || '请选择音色' }}
						</view>
					</picker>
					<view class="hint-text">💡 请先在"声音克隆"页面创建音色</view>
				</view>

				<view v-if="selectedSupportsDialect && (selectedVoiceType || 'CLONE') !== 'OFFICIAL'" class="form-section">
					<view class="form-label">方言</view>
					<picker mode="selector" :range="dialectOptions" @change="onDialectChange">
						<view class="picker-large">
							{{ selectedDialect || '请选择方言（可选）' }}
						</view>
					</picker>
				</view>

				<view v-if="selectedSupportsLanguageHints && (selectedVoiceType || 'CLONE') !== 'OFFICIAL'" class="form-section">
					<view class="form-label">语言提示（可选）</view>
					<picker mode="selector" :range="languageHintOptions" @change="onLanguageHintChange">
						<view class="picker-large">
							{{ selectedLanguageHintLabel || '请选择语言（可选）' }}
						</view>
					</picker>
					<view class="hint-text">💡 仅处理第一个值；不设置不生效</view>
				</view>

				<view class="form-section">
					<view class="form-label"> 文案模板</view>
					<view v-if="templates.length === 0" class="empty-template">
						<text>⏳ 加载中...</text>
					</view>
					<view v-else class="template-categories">
						<!-- 缅怀类 -->
						<view class="category-group">
							<view class="category-header" @click="toggleCategory('memorial')">
								<text class="category-name">💭 缅怀类</text>
								<text class="category-arrow" :class="{ 'arrow-open': expandedCategory === 'memorial' }">▼</text>
							</view>
							<view v-if="expandedCategory === 'memorial'" class="category-content">
								<view 
									v-for="(template, index) in getTemplatesByCategory('memorial')" 
									:key="index" 
									class="template-item"
									@click="selectTemplate(template)"
								>
									<text class="template-title">{{ template.title }}</text>
									<text class="template-preview">{{ template.content }}</text>
								</view>
							</view>
						</view>
						
						<!-- 问候类 -->
						<view class="category-group">
							<view class="category-header" @click="toggleCategory('greeting')">
								<text class="category-name">👋 问候类</text>
								<text class="category-arrow" :class="{ 'arrow-open': expandedCategory === 'greeting' }">▼</text>
							</view>
							<view v-if="expandedCategory === 'greeting'" class="category-content">
								<view 
									v-for="(template, index) in getTemplatesByCategory('greeting')" 
									:key="index" 
									class="template-item"
									@click="selectTemplate(template)"
								>
									<text class="template-title">{{ template.title }}</text>
									<text class="template-preview">{{ template.content }}</text>
								</view>
							</view>
						</view>
						
						<!-- 回忆类 -->
						<view class="category-group">
							<view class="category-header" @click="toggleCategory('memory')">
								<text class="category-name">📖 回忆类</text>
								<text class="category-arrow" :class="{ 'arrow-open': expandedCategory === 'memory' }">▼</text>
							</view>
							<view v-if="expandedCategory === 'memory'" class="category-content">
								<view 
									v-for="(template, index) in getTemplatesByCategory('memory')" 
									:key="index" 
									class="template-item"
									@click="selectTemplate(template)"
								>
									<text class="template-title">{{ template.title }}</text>
									<text class="template-preview">{{ template.content }}</text>
								</view>
							</view>
						</view>
					</view>
				</view>

				<view class="form-section">
					<view class="form-label">输入文本（最多100字）</view>
					<textarea 
						class="textarea" 
						v-model="ttsText" 
						placeholder="请输入要合成的文本..." 
						maxlength="100" 
					/>
					<view class="char-count" :class="{ 'char-warning': ttsText.length >= 90 }">
						{{ ttsText.length }} / 100
					</view>
				</view>

				<button class="primary-btn" :disabled="ttsLoading || !ttsText.trim() || !selectedVoiceId" @click="handleSynthesize">
					{{ ttsLoading ? '⏳ 合成中...' : '🔊 开始合成' }}
				</button>

				<view v-if="audioUrl" class="audio-result">
					<view class="result-title">✅ 合成成功！</view>
					<audio :src="audioUrl" controls style="width: 100%;"></audio>
				</view>
			</view>

			<!-- 实时对话 -->
			<view v-if="activeTab === 'conversation'" class="tab-content">
				<view class="section-title">💬 实时语音对话</view>

				<view class="memory-card">
					<view class="card-header">
						<text class="card-title">💭 记忆设定</text>
						<text class="card-hint">填写越详细，AI模拟越真实</text>
					</view>
					
					<view class="memory-form">
						<view class="form-item">
							<view class="item-label">
								<text class="label-text">身份</text>
								<text class="label-required">*</text>
							</view>
							<input 
								class="form-input" 
								v-model="memoryIdentity" 
								placeholder="例如：我的母亲"
								maxlength="50"
							/>
						</view>
						
						<view class="form-item">
							<view class="item-label">
								<text class="label-text">基础信息</text>
							</view>
							<textarea 
								class="form-textarea" 
								v-model="memoryInfo" 
								placeholder="例如：退休教师，喜欢养花，做饭很拿手..."
								maxlength="200"
							/>
							<text class="form-count">{{ memoryInfo.length }}/200</text>
						</view>
						
						<button class="apply-btn" @click="handleApplyMemory">
							<text class="btn-icon">✓</text>
							<text>应用设定</text>
						</button>
					</view>
				</view>

				<view class="form-section">
					<view class="form-label">选择回复音色</view>
					<picker mode="selector" :range="voiceTypeOptions" range-key="label" @change="onConversationVoiceTypeChange">
						<view class="picker-large">
							{{ conversationVoiceTypeLabel }}
						</view>
					</picker>
					<picker mode="selector" :range="conversationDisplayVoices" range-key="voice_name" @change="onConversationVoiceChange">
						<view class="picker-large">
							{{ conversationVoiceLabel || '请选择音色' }}
						</view>
					</picker>
				</view>

				<view v-if="conversationSupportsDialect && (conversationVoiceType || 'CLONE') !== 'OFFICIAL'" class="form-section">
					<view class="form-label">方言</view>
					<picker mode="selector" :range="dialectOptions" @change="onConversationDialectChange">
						<view class="picker-large">
							{{ conversationDialect || '请选择方言（可选）' }}
						</view>
					</picker>
				</view>

				<view v-if="conversationSupportsLanguageHints && (conversationVoiceType || 'CLONE') !== 'OFFICIAL'" class="form-section">
					<view class="form-label">语言提示（可选）</view>
					<picker mode="selector" :range="languageHintOptions" @change="onConversationLanguageHintChange">
						<view class="picker-large">
							{{ conversationLanguageHintLabel || '请选择语言（可选）' }}
						</view>
					</picker>
					<view class="hint-text">💡 仅处理第一个值；不设置不生效</view>
				</view>

				<view class="record-buttons">
					<view v-if="!isRecording && !isProcessing" class="record-btn start" @click="startRecording">
						🎤 开始说话
					</view>
					<view v-if="isRecording" class="record-btn stop" @click="stopRecording">
						⏹️ 停止录音
					</view>
					<view v-if="isProcessing" class="record-btn processing">
						⏳ 处理中...
					</view>
					<view class="small-btn" :class="{ disabled: isRecording || isProcessing }" @click="isRecording || isProcessing ? null : handleClearHistory()">
						<image src="/static/iconfont/delete.svg" class="delete-icon" mode="aspectFit" style="width: 44upx; height: 44upx;"></image>
					</view>
				</view>

				<view v-if="recognizedText" class="result-box user">
					<view class="result-label">🎤 您说：</view>
					<text>{{ recognizedText }}</text>
				</view>

				<view v-if="aiResponse" class="result-box ai">
					<view class="result-label">💬 AI回复：</view>
					<text>{{ aiResponse }}</text>
				</view>

				<view v-if="conversationAudio" class="result-box audio">
					<view class="result-label">🔊 语音回复：</view>
					<audio :src="conversationAudio" controls style="width: 100%;"></audio>
				</view>
			</view>

			<!-- 音色列表 -->
			<view v-if="activeTab === 'list'" class="tab-content">
				<view class="list-header">
					<view class="section-title">我的音色</view>
					<button class="small-btn refresh-btn" :disabled="loading" @click="loadVoices">
						{{ loading ? '⏳' : '🔄' }}
					</button>
				</view>

				<view v-if="loading" class="loading">⏳ 加载中...</view>
				<view v-else-if="voices.length === 0" class="empty">🎤 暂无音色</view>
				<view v-else>
					<view v-for="voice in voices" :key="voice.voice" class="voice-item">
						<view class="voice-info">
							<text class="voice-name">
								{{ voice.voice_name || voice.voice }}
								<text v-if="voice.is_public" class="public-badge">公共</text>
							</text>
							<text class="voice-detail">{{ formatTime(voice.create_time, 'YYYY-MM-DD HH:mm:ss') }}</text>
						</view>
						<button v-if="!voice.is_public" class="delete-btn" @click="handleDeleteVoice(voice.voice)">
							<image src="/static/iconfont/delete.svg" class="delete-icon" mode="aspectFit"></image>
						</button>
					</view>
				</view>
			</view>
		</scroll-view>
		
		<!-- 支付弹窗 -->
		<PaymentModal
			ref="paymentModal"
			:show="paymentModalData.show"
			:serviceType="paymentModalData.serviceType"
			:serviceName="paymentModalData.serviceName"
			:serviceDesc="paymentModalData.serviceDesc"
			:price="paymentModalData.price"
			:orderNo="paymentModalData.orderNo"
			:paymentTips="paymentModalData.paymentTips"
			@close="handlePaymentClose"
			@confirm="handlePaymentConfirm"
		/>
	</view>
</template>

<script>
import { API_BASE, API_ENDPOINTS, buildURL } from '@/config/api.js';
import PaymentModal from '@/components/PaymentModal.vue';
import { SERVICE_TYPES, showPaymentModal, handlePaymentConfirm as processPayment, mapOfficialVoiceDisplay } from '@/utils/payment.js';
import PermissionManager from '@/utils/permission.js';

export default {
	components: {
		PaymentModal
	},
	data() {
		return {
			API_BASE,
			
			activeTab: 'synthesize',
			loading: false,
			message: { type: '', text: '' },
			
			selectedFile: null,
			voiceDisplayName: '',
			voices: [],
			
			voiceTypeOptions: [
				{ value: 'CLONE', label: '克隆音色' },
				{ value: 'OFFICIAL', label: '官方音色（Flash/BigTTS）' }
			],
			selectedVoiceType: 'CLONE',
			selectedVoiceTypeLabel: '克隆音色',
			selectedVoiceId: '',
			selectedVoiceName: '',
			selectedDialect: '',
			selectedLanguageHint: '',
			selectedLanguageHintLabel: '',
			selectedSupportsDialect: false,
			selectedSupportsLanguageHints: false,
			languageHintOptions: ['中文(zh)', '英文(en)', '法语(fr)', '德语(de)', '日语(ja)', '韩语(ko)', '俄语(ru)'],
			dialectOptions: ['广东话', '东北话', '甘肃话', '贵州话', '河南话', '湖北话', '江西话', '闽南话', '宁夏话', '山西话', '陕西话', '山东话', '上海话', '四川话', '天津话', '云南话'],
			ttsText: '',
			ttsLoading: false,
			audioUrl: null,
			templates: [],
			expandedCategory: 'memorial', // 默认展开缅怀类
			
			isRecording: false,
			recorderManager: null,
			conversationVoiceId: '',
			conversationVoiceLabel: '',
			conversationDialect: '',
			conversationLanguageHint: '',
			conversationLanguageHintLabel: '',
			conversationVoiceType: 'CLONE',
			conversationVoiceTypeLabel: '克隆音色',
			conversationSupportsDialect: false,
			conversationSupportsLanguageHints: false,
			recognizedText: '',
			aiResponse: '',
			conversationAudio: null,
			isProcessing: false,
			
			// 记忆设定字段
			memoryIdentity: '',
			memoryInfo: '',
			systemPrompt: '',
			
			audioFilePath: '',
			
			// 支付相关
			paymentModalData: {
				show: false,
				serviceType: '',
				serviceName: '',
				serviceDesc: '',
				price: 0,
				orderNo: '',
				paymentTips: '点击确认支付后将开始处理您的请求'
			},
			_paymentResolve: null,
			_paymentReject: null,
			_paymentOnSuccess: null,
			_paymentOnFailed: null
		};
	},
	
	computed: {
		apiBaseUrl() {
			return this.API_BASE;
		},
		voicesOnly() {
			return (this.voices || []).map(v => ({
				voice: v.voice,
				label: v.voice_name || v.voice,
				voice_type: v.voice_type,
				supportsDialect: v.supportsDialect,
				supportsLanguageHints: v.supportsLanguageHints
			}));
		},
		conversationDisplayVoices() {
			const list = (this.voices || []).filter(v => (v && (v.voice_type || 'CLONE') === (this.conversationVoiceType || 'CLONE')));
			if ((this.conversationVoiceType || 'CLONE') !== 'OFFICIAL') return list;
			const hidden = ['Cherry', 'Kai', 'Mochi', 'Bunny'];
			const top = ['Cherry', 'Kai', 'Mochi', 'Bunny'];
			const sorted = list
				.filter(v => !(v && hidden.includes(v.voice)))
				.map((v, idx) => ({ v, idx }))
				.sort((a, b) => {
					const ai = top.indexOf(a.v.voice);
					const bi = top.indexOf(b.v.voice);
					const ap = ai === -1 ? 999 : ai;
					const bp = bi === -1 ? 999 : bi;
					if (ap !== bp) return ap - bp;
					return a.idx - b.idx;
				})
				.map(x => x.v);
			return mapOfficialVoiceDisplay(sorted);
		},
		displayVoices() {
			const list = (this.voices || []).filter(v => (v && (v.voice_type || 'CLONE') === this.selectedVoiceType));
			if ((this.selectedVoiceType || 'CLONE') !== 'OFFICIAL') return list;
			const hidden = ['Cherry', 'Kai', 'Mochi', 'Bunny'];
			const top = ['Cherry', 'Kai', 'Mochi', 'Bunny'];
			const sorted = list
				.filter(v => !(v && hidden.includes(v.voice)))
				.map((v, idx) => ({ v, idx }))
				.sort((a, b) => {
					const ai = top.indexOf(a.v.voice);
					const bi = top.indexOf(b.v.voice);
					const ap = ai === -1 ? 999 : ai;
					const bp = bi === -1 ? 999 : bi;
					if (ap !== bp) return ap - bp;
					return a.idx - b.idx;
				})
				.map(x => x.v);
			return mapOfficialVoiceDisplay(sorted);
		}
	},

	watch: {
		activeTab(newTab) {
			this.conversationVoiceId = '';
			this.conversationVoiceLabel = '';
			this.conversationDialect = '';
			this.conversationLanguageHint = '';
			this.conversationLanguageHintLabel = '';
			this.conversationSupportsDialect = false;
			this.conversationSupportsLanguageHints = false;
			if (newTab === 'list' || newTab === 'synthesize' || newTab === 'conversation') {
				this.loadVoices();
			}
		}
	},

	onLoad() {
		this.useDefaultTemplates();
		this.loadVoices();
		this.loadTemplatesFromServer();
		this.initRecorder();
	},

	onShow() {
		if (this.checkLogin && this.checkLogin() && (!this.voices || this.voices.length === 0)) {
			this.loadVoices();
		}
	},

	methods: {
		onVoiceTypeChange(e) {
			const option = this.voiceTypeOptions[e.detail.value];
			this.selectedVoiceType = option ? option.value : 'CLONE';
			this.selectedVoiceTypeLabel = option ? option.label : '克隆音色';
			this.selectedVoiceId = '';
			this.selectedVoiceName = '';
			this.selectedDialect = '';
			this.selectedLanguageHint = '';
			this.selectedLanguageHintLabel = '';
			this.selectedSupportsDialect = false;
			this.selectedSupportsLanguageHints = false;
		},
		onConversationVoiceTypeChange(e) {
			const option = this.voiceTypeOptions[e.detail.value];
			this.conversationVoiceType = option ? option.value : 'CLONE';
			this.conversationVoiceTypeLabel = option ? option.label : '克隆音色';
			this.conversationVoiceId = '';
			this.conversationVoiceLabel = '';
			this.conversationDialect = '';
			this.conversationLanguageHint = '';
			this.conversationLanguageHintLabel = '';
			this.conversationSupportsDialect = false;
			this.conversationSupportsLanguageHints = false;
		},
		// 显示音色获取帮助
		showVoiceHelp() {
			uni.showModal({
				title: '💡 如何获取音色？',
				content: '1️⃣ 点击底部导航"声音克隆"\n\n2️⃣ 点击"上传音频"按钮\n\n3️⃣ 录制或上传10-20秒清晰人声\n\n4️⃣ 创建成功后即可在此选择使用\n\n💡 提示：音色质量越好，合成效果越自然',
				showCancel: true,
				cancelText: '知道了',
				confirmText: '去创建',
				success: (res) => {
					if (res.confirm) {
						// 跳转到声音克隆页面
						uni.switchTab({
							url: '/pages/upload-audio/upload-audio'
						});
					}
				}
			});
		},
		
		// 检查登录状态
		checkLogin() {
			const token = uni.getStorageSync('token');
			const userId = uni.getStorageSync('userId');
			return !!(token && userId);
		},
		
		showMessage(type, text) {
			this.message = { type, text };
			setTimeout(() => {
				this.message = { type: '', text: '' };
			}, 5000);
		},
		
		chooseAudioFile() {
			// 运行时检测平台
			const isApp = typeof plus !== 'undefined';
			const isH5 = typeof document !== 'undefined' && !isApp;
			const isMp = typeof wx !== 'undefined';
		
			if (isApp) {
			// APP 环境：使用录音功能
			uni.showModal({
				title: '选择方式',
				content: 'APP 环境请使用录音功能创建音色（推荐10-20秒清晰朗读）\n\n提示：如需上传音频文件，请使用手机浏览器访问本系统',
				confirmText: '开始录音',
				cancelText: '取消',
				success: (res) => {
					if (res.confirm) {
						this.startRecordAudio();
					}
				}
			});
		} else if (isH5) {
				// H5 环境：使用文件选择
				const input = document.createElement('input');
				input.type = 'file';
				input.accept = 'audio/*,.mp3,.wav,.m4a,.aac';
				input.onchange = (e) => {
					const file = e.target.files[0];
					if (file) {
						if (file.size > 10 * 1024 * 1024) {
							this.showMessage('error', '文件大小不能超过10MB');
							return;
						}
						
						const tempPath = URL.createObjectURL(file);
						this.selectedFile = {
							path: tempPath,
							name: file.name,
							size: file.size,
							file: file
						};
						
						this.showMessage('success', `已选择: ${file.name} (${(file.size / 1024).toFixed(2)} KB)`);
					}
				};
				input.click();
			} else if (isMp) {
				// 小程序环境：使用 chooseMessageFile
				uni.chooseMessageFile({
					count: 1,
					type: 'file',
					extension: ['.mp3', '.wav', '.m4a', '.aac'],
					success: (res) => {
						const file = res.tempFiles[0];
						
						if (file.size > 10 * 1024 * 1024) {
							this.showMessage('error', '文件大小不能超过10MB');
							return;
						}
						
						this.selectedFile = {
							path: file.path,
							name: file.name,
							size: file.size
						};
						
						this.showMessage('success', `已选择: ${file.name} (${(file.size / 1024).toFixed(2)} KB)`);
					},
					fail: (err) => {
						this.showMessage('error', '选择文件失败: ' + (err.errMsg || '未知错误'));
					}
				});
			}
		},
		
		// 录音功能（APP专用）
		async startRecordAudio() {
			// #ifdef APP-PLUS
			// 检查麦克风权限
			const hasPermission = await PermissionManager.ensurePermission('record', {
				permissionName: '麦克风',
				reason: '创建音色录音功能',
				showGuide: true
			});
			
			if (!hasPermission) {
				console.log('[Revival] 麦克风权限未授权');
				return;
			}
			// #endif
			
			const recorderManager = uni.getRecorderManager();
			
			recorderManager.onStart(() => {
				this.showMessage('success', '正在录音...');
			});
			
			recorderManager.onStop((res) => {
				this.selectedFile = {
					path: res.tempFilePath,
					name: 'record_' + Date.now() + '.mp3'
				};
				this.showMessage('success', '录音完成');
			});
			
			recorderManager.onError((err) => {
				console.error('录音失败:', err);
				this.showMessage('error', '录音失败');
			});
			
			uni.showModal({
				title: '录音',
				content: '点击确定开始录音，录音时长10-20秒',
				success: (modalRes) => {
					if (modalRes.confirm) {
						recorderManager.start({
							format: 'mp3',
							sampleRate: 16000
						});
						
						setTimeout(() => {
							recorderManager.stop();
						}, 20000);
					}
				}
			});
		},
		
		async handleCreateVoice() {
			// 检查登录状态
			if (!this.checkLogin()) {
				uni.showModal({
					title: '提示',
					content: '请先登录后再使用此功能',
					confirmText: '去登录',
					cancelText: '取消',
					success: (res) => {
						if (res.confirm) {
							uni.navigateTo({
								url: '/pages/login/login'
							});
						}
					}
				});
				return;
			}
			
			if (!this.selectedFile) {
				this.showMessage('error', '请先选择音频文件');
				return;
			}
			if (!this.voiceDisplayName.trim()) {
				this.showMessage('error', '请输入音色名称');
				return;
			}
			
			// 自动生成voice_name（voice1, voice2, voice3...）
			const voiceName = `voice${this.voices.length + 1}`;
			
			this.loading = true;
			
			console.log('[Index] 开始上传音频文件...');
			console.log('[Index] 文件路径:', this.selectedFile.path);
			console.log('[Index] 音色标识:', voiceName);
			console.log('[Index] 音色显示名称:', this.voiceDisplayName);
			
			// #ifdef H5
			// H5 环境使用 fetch + FormData
			if (this.selectedFile.file) {
				try {
					const userId = uni.getStorageSync('userId') || '';
					const formData = new FormData();
					formData.append('audio', this.selectedFile.file);
					formData.append('name', voiceName);
					formData.append('displayName', this.voiceDisplayName);
					
					const response = await fetch(`${this.apiBaseUrl}/api/voice/create`, {
						method: 'POST',
						headers: {
							'X-User-Id': userId
						},
						body: formData
					});
					
					const data = await response.json();
					
					if (response.ok && data.success) {
						this.showMessage('success', `创建成功！音色: ${this.voiceDisplayName}`);
						this.selectedFile = null;
						this.voiceDisplayName = '';
						this.loadVoices();
					} else {
						this.showMessage('error', data.message || '创建失败');
					}
				} catch (e) {
					console.error('[Index] 上传失败:', e);
					this.showMessage('error', '上传失败: ' + e.message);
				} finally {
					this.loading = false;
				}
				return;
			}
			// #endif
			
			// #ifdef APP-PLUS || MP-WEIXIN
			// APP 和小程序使用 uni.uploadFile
			const userId = uni.getStorageSync('userId') || '';
			const token = uni.getStorageSync('token') || '';
			uni.uploadFile({
				url: `${this.apiBaseUrl}/api/voice/create`,
				filePath: this.selectedFile.path,
				name: 'audio',
				header: {
					'X-User-Id': userId,
					'Authorization': token ? `Bearer ${token}` : ''
				},
				formData: {
					name: voiceName,
					displayName: this.voiceDisplayName
				},
				success: (res) => {
					console.log('[Index] 上传响应:', res);
					if (res.statusCode === 200) {
						try {
							const data = JSON.parse(res.data);
							if (data.success) {
								this.showMessage('success', `创建成功！音色: ${this.voiceDisplayName}`);
								this.selectedFile = null;
								this.voiceDisplayName = '';
								this.loadVoices();
							} else {
								this.showMessage('error', data.message || '创建失败');
							}
						} catch (e) {
							console.error('[Index] 解析响应失败:', e);
							this.showMessage('error', '服务器响应格式错误');
						}
					} else {
						this.showMessage('error', `上传失败: HTTP ${res.statusCode}`);
					}
				},
				fail: (err) => {
					console.error('[Index] 上传失败:', err);
					this.showMessage('error', '上传失败: ' + (err.errMsg || '未知错误'));
				},
				complete: () => {
					this.loading = false;
				}
			});
			// #endif
		},
	
		async loadVoices() {
			this.loading = true;
			
			// 获取用户ID和Token
			const userId = uni.getStorageSync('userId') || '';
			const token = uni.getStorageSync('token') || '';
			
			console.log('[Revival] 加载音色 - userId:', userId, 'token:', token ? '已设置' : '未设置');
			
			uni.request({
				url: `${this.apiBaseUrl}/api/voice/list`,
				method: 'GET',
				header: {
					'X-User-Id': userId,
					'Authorization': token ? `Bearer ${token}` : ''
				},
				success: (res) => {
					console.log('[Revival] 音色列表响应:', res.data);
					if (res.data.success) {
						const hiddenVoices = ['Cherry', 'Kai', 'Mochi', 'Bunny'];
						this.voices = (res.data.voices || [])
							.filter(v => !(v && v.voice && v.voice.startsWith('cosyvoice-v3-flash-')))
							.filter(v => !(v && v.voice && hiddenVoices.includes(v.voice)));
						console.log('[Revival] 加载音色数量:', this.voices.length);
					} else {
						console.error('[Revival] 加载音色失败:', res.data.message);
					}
				},
				fail: (err) => {
					console.error('[Revival] 请求失败:', err);
					this.showMessage('error', '加载音色失败');
				},
				complete: () => {
					this.loading = false;
				}
			});
		},
		
		handleDeleteVoice(voiceId) {
			uni.showModal({
				title: '确认删除',
				content: `确定删除音色 ${voiceId}？`,
				success: (res) => {
					if (res.confirm) {
						console.log('[Revival] 开始删除音色:', voiceId);
						
						const userId = uni.getStorageSync('userId') || '';
						const token = uni.getStorageSync('token') || '';
						
						// 显示加载提示
						uni.showLoading({
							title: '删除中...',
							mask: true
						});
						
						uni.request({
							url: `${this.apiBaseUrl}/api/voice/delete/${voiceId}`,
							method: 'DELETE',
							header: {
								'X-User-Id': userId,
								'Authorization': token ? `Bearer ${token}` : ''
							},
							success: (res) => {
								console.log('[Revival] 删除音色响应:', res);
								uni.hideLoading();
								
								if (res.statusCode === 200) {
									if (res.data && res.data.success) {
										this.showMessage('success', '删除成功');
										// 重新加载音色列表
										this.loadVoices();
									} else {
										this.showMessage('error', res.data.message || '删除失败');
									}
								} else {
									this.showMessage('error', `删除失败: HTTP ${res.statusCode}`);
								}
							},
							fail: (err) => {
								console.error('[Revival] 删除音色失败:', err);
								uni.hideLoading();
								this.showMessage('error', '网络请求失败: ' + (err.errMsg || '请检查网络连接'));
							}
						});
					}
				}
			});
		},
		
		onVoiceChange(e) {
			const voice = this.displayVoices[e.detail.value];
			this.selectedVoiceId = voice.voice;
			this.selectedVoiceName = voice.voice_name || voice.voice;
			this.selectedVoiceType = voice.voice_type || this.selectedVoiceType || 'CLONE';
			this.selectedVoiceTypeLabel = this.selectedVoiceType === 'OFFICIAL' ? '官方音色（Flash/BigTTS）' : '克隆音色';
			this.selectedDialect = '';
			this.selectedLanguageHint = '';
			this.selectedLanguageHintLabel = '';
			this.selectedSupportsDialect = !!voice.supportsDialect;
			this.selectedSupportsLanguageHints = !!voice.supportsLanguageHints;
			if ((this.selectedVoiceType || 'CLONE') === 'OFFICIAL') {
				this.selectedDialect = '';
				this.selectedLanguageHint = '';
				this.selectedLanguageHintLabel = '';
				this.selectedSupportsDialect = false;
				this.selectedSupportsLanguageHints = false;
			}
		},
		
		onDialectChange(e) {
			this.selectedDialect = this.dialectOptions[e.detail.value] || '';
		},
		
		onLanguageHintChange(e) {
			const label = this.languageHintOptions[e.detail.value] || '';
			this.selectedLanguageHintLabel = label;
			// 取括号里的 code，例如 "英文(en)" -> "en"
			const match = label.match(/\(([^)]+)\)/);
			this.selectedLanguageHint = match && match[1] ? match[1] : '';
		},
		
		onConversationVoiceChange(e) {
			const selected = this.conversationDisplayVoices[e.detail.value];
			this.conversationVoiceId = selected.voice;
			this.conversationVoiceLabel = selected.voice_name || selected.voice;
			this.conversationVoiceType = selected.voice_type || this.conversationVoiceType || 'CLONE';
			this.conversationVoiceTypeLabel = this.conversationVoiceType === 'OFFICIAL' ? '官方音色（Flash）' : '克隆音色';
			this.conversationDialect = '';
			this.conversationLanguageHint = '';
			this.conversationLanguageHintLabel = '';
			this.conversationSupportsDialect = !!selected.supportsDialect;
			this.conversationSupportsLanguageHints = !!selected.supportsLanguageHints;
			if ((this.conversationVoiceType || 'CLONE') === 'OFFICIAL') {
				this.conversationDialect = '';
				this.conversationLanguageHint = '';
				this.conversationLanguageHintLabel = '';
				this.conversationSupportsDialect = false;
				this.conversationSupportsLanguageHints = false;
			}
		},
		
		onConversationDialectChange(e) {
			this.conversationDialect = this.dialectOptions[e.detail.value] || '';
		},
		
		onConversationLanguageHintChange(e) {
			const label = this.languageHintOptions[e.detail.value] || '';
			this.conversationLanguageHintLabel = label;
			const match = label.match(/\(([^)]+)\)/);
			this.conversationLanguageHint = match && match[1] ? match[1] : '';
		},
		
		toggleCategory(category) {
			// 如果点击已展开的分类，则收起；否则展开新分类
			if (this.expandedCategory === category) {
				this.expandedCategory = '';
			} else {
				this.expandedCategory = category;
			}
		},
		
		getTemplatesByCategory(category) {
			return this.templates.filter(t => t.category === category);
		},
		
		selectTemplate(template) {
			this.ttsText = template.content;
			this.showMessage('success', '已应用模板');
		},
		
		// 从服务器加载模板（可选）
		loadTemplatesFromServer() {
			const userId = uni.getStorageSync('userId') || '';
			const token = uni.getStorageSync('token') || '';
			
			console.log('[Templates] 尝试从服务器加载模板...');
			
			uni.request({
				url: `${this.apiBaseUrl}/api/templates/list`,
				method: 'GET',
				header: {
					'X-User-Id': userId,
					'Authorization': token ? `Bearer ${token}` : ''
				},
				success: (res) => {
					console.log('[Templates] 服务器响应:', res);
					
					// 处理多种可能的返回格式
					if (res.data && res.data.success && res.data.templates && res.data.templates.length > 0) {
						this.templates = res.data.templates;
						console.log('[Templates] 从服务器加载成功，数量:', this.templates.length);
					} else if (res.data && Array.isArray(res.data) && res.data.length > 0) {
						// 直接返回数组的情况
						this.templates = res.data;
						console.log('[Templates] 从服务器加载成功（数组格式），数量:', this.templates.length);
					} else {
						// 数据为空或格式不对，保持使用默认模板
						console.log('[Templates] 服务器无数据，继续使用默认模板');
					}
				},
				fail: (err) => {
					console.log('[Templates] 服务器请求失败，继续使用默认模板:', err);
					// 保持使用默认模板，不做任何操作
				}
			});
		},
		
		// 使用默认模板
		useDefaultTemplates() {
			this.templates = [
				{ category: 'memorial', title: '思念', content: '妈妈，我好想你，你在那边还好吗？' },
				{ category: 'memorial', title: '感恩', content: '谢谢您一直以来对我的照顾和关爱。' },
				{ category: 'memorial', title: '告白', content: '有些话当时没说出口，现在想对您说...' },
				{ category: 'greeting', title: '问候', content: '今天天气很好，您最喜欢这样的天气了。' },
				{ category: 'greeting', title: '日常', content: '最近过得还好，就是很想您。' },
				{ category: 'memory', title: '回忆', content: '还记得小时候您做的红烧肉，真好吃。' },
				{ category: 'memory', title: '往事', content: '那年夏天，我们一起去海边的情景还历历在目。' }
			];
			console.log('[Templates] 使用默认模板，数量:', this.templates.length);
		},
		
		async handleSynthesize() {
			// 检查登录状态
			if (!this.checkLogin()) {
				uni.showModal({
					title: '提示',
					content: '请先登录后再使用此功能',
					confirmText: '去登录',
					cancelText: '取消',
					success: (res) => {
						if (res.confirm) {
							uni.navigateTo({
								url: '/pages/login/login'
							});
						}
					}
				});
				return;
			}
			
			if (!this.ttsText.trim() || !this.selectedVoiceId) {
				this.showMessage('error', '请输入文本并选择音色');
				return;
			}

			// 统一走支付工具：内部会先做后端资格校验（免费次数/补发/已支付），必要时弹窗
			showPaymentModal(
				this,
				SERVICE_TYPES.TTS_SYNTHESIS.type,
				() => {
					// 支付成功后执行合成
					this.executeSynthesize();
				},
				(error) => {
					console.error('[Payment] 支付失败:', error);
				}
			);
		},
		
		executeSynthesize() {
			this.ttsLoading = true;
			this.audioUrl = null;
			
			// 根据音色ID判断使用哪种TTS接口：CosyVoice走新的 cosy-synthesize，其它走原来的 synthesize
			const isCosyVoice = this.selectedVoiceId && this.selectedVoiceId.startsWith('cosyvoice-v3-plus-');
			const ttsPath = isCosyVoice ? '/api/tts/cosy-synthesize' : '/api/tts/synthesize';
			
			console.log('[TTS] 发送合成请求:', {
				url: `${this.apiBaseUrl}${ttsPath}`,
				text: this.ttsText,
				voiceId: this.selectedVoiceId,
				dialect: this.selectedDialect
			});
		
			const userId = uni.getStorageSync('userId') || '';
			const token = uni.getStorageSync('token') || '';
		
			const requestData = {
				text: this.ttsText,
				voiceId: this.selectedVoiceId,
				voiceType: this.selectedVoiceType
			};
			if ((this.selectedVoiceType || 'CLONE') !== 'OFFICIAL' && this.selectedSupportsDialect && this.selectedDialect) {
				requestData.dialect = this.selectedDialect;
			}
			if ((this.selectedVoiceType || 'CLONE') !== 'OFFICIAL' && this.selectedSupportsLanguageHints && this.selectedLanguageHint) {
				requestData.languageHints = this.selectedLanguageHint;
			}
			
			uni.request({
				url: `${this.apiBaseUrl}${ttsPath}`,
				method: 'POST',
				header: {
					'X-User-Id': userId,
					'Authorization': token ? `Bearer ${token}` : '',
					'content-type': 'application/x-www-form-urlencoded'
				},
				data: requestData,
				success: (res) => {
					console.log('[TTS] 请求成功:', res);
					if (res.statusCode === 402) {
						// 免费次数用尽/未付费：引导支付后重试
						uni.showModal({
							title: '需要付费',
							content: (res.data && res.data.message) ? res.data.message : '免费次数已用完，请先完成支付',
							confirmText: '去支付',
							cancelText: '取消',
							success: (m) => {
								if (m.confirm) {
									showPaymentModal(
										this,
										SERVICE_TYPES.TTS_SYNTHESIS.type,
										() => {
											this.executeSynthesize();
										},
										(error) => {
											console.error('[Payment] 支付失败:', error);
										}
									);
								}
							}
						});
						return;
					}
					if (res.statusCode !== 200) {
						uni.showModal({
							title: '生成失败',
							content: (res.data && res.data.message ? res.data.message : '生成失败') + '\n\n你可以点击“重试”再次生成。\n若已支付但多次生成失败，请联系客服补发（提供订单号/支付时间截图）。',
							confirmText: '重试',
							cancelText: '我知道了',
							success: (m) => {
								if (m.confirm) {
									this.executeSynthesize();
								}
							}
						});
						return;
					}
					if (res.data && res.data.success) {
						this.showMessage('success', '合成成功！');
						// 使用后端下载接口（避免文件托管服务器404问题）
						this.audioUrl = `${this.apiBaseUrl}/api/tts/download/${res.data.audioFile}`;
						console.log('[TTS] 音频URL:', this.audioUrl);
					} else {
						uni.showModal({
							title: '生成失败',
							content: '合成失败：' + ((res.data && res.data.message) ? res.data.message : '未知错误') + '\n\n你可以点击“重试”再次生成。\n若已支付但多次生成失败，请联系客服补发（提供订单号/支付时间截图）。',
							confirmText: '重试',
							cancelText: '我知道了',
							success: (m) => {
								if (m.confirm) {
									this.executeSynthesize();
								}
							}
						});
					}
				},
				fail: (err) => {
					console.error('[TTS] 请求失败:', err);
					uni.showModal({
						title: '生成失败',
						content: '网络请求失败：' + (err.errMsg || '请检查网络连接') + '\n\n你可以点击“重试”再次生成。\n若已支付但多次生成失败，请联系客服补发（提供订单号/支付时间截图）。',
						confirmText: '重试',
						cancelText: '我知道了',
						success: (m) => {
							if (m.confirm) {
								this.executeSynthesize();
							}
						}
					});
				},
				complete: () => {
					this.ttsLoading = false;
				}
			});
		},
		
		initRecorder() {
			this.recorderManager = uni.getRecorderManager();
			
			this.recorderManager.onStop((res) => {
				this.audioFilePath = res.tempFilePath;
				this.handleConversation();
			});
			
			this.recorderManager.onError((err) => {
				console.error('[Revival] 录音失败:', err);
				this.isRecording = false;
				
				// 判断是否为权限问题
				const isPermissionError = err.errMsg && (
					err.errMsg.includes('permission') || 
					err.errMsg.includes('authorize') ||
					err.errMsg.includes('denied') ||
					err.errMsg.includes('占用')
				);
				
				if (isPermissionError) {
					// 权限问题，引导用户去设置
					uni.showModal({
						title: '需要麦克风权限',
						content: '录音功能需要麦克风权限才能使用。\n\n请前往：\n手机设置 → 应用管理 → 时光意境 → 权限管理 → 开启麦克风权限',
						confirmText: '我知道了',
						showCancel: false
					});
				} else {
					this.showMessage('error', '录音失败: ' + (err.errMsg || '未知错误'));
				}
			});
		},
		
		startRecording() {
			// 检查登录状态
			if (!this.checkLogin()) {
				uni.showModal({
					title: '提示',
					content: '请先登录后再使用此功能',
					confirmText: '去登录',
					cancelText: '取消',
					success: (res) => {
						if (res.confirm) {
							uni.navigateTo({
								url: '/pages/login/login'
							});
						}
					}
				});
				return;
			}
			
			// 先进行支付
			showPaymentModal(
				this,
				SERVICE_TYPES.CONVERSATION.type,
				() => {
					// 支付成功后开始录音
					this.executeRecording();
				},
				(error) => {
					console.error('[Payment] 支付失败:', error);
				}
			);
		},
		
		executeRecording() {
			this.recorderManager.start({
				format: 'mp3',
				sampleRate: 16000,
				numberOfChannels: 1
			});
			this.isRecording = true;
			this.recognizedText = '';
			this.aiResponse = '';
			this.conversationAudio = null;
			this.showMessage('success', '开始录音...');
		},
		
		stopRecording() {
			this.recorderManager.stop();
			this.isRecording = false;
			this.showMessage('success', '处理中...');
		},
		
		buildSystemPrompt() {
			if (this.memoryIdentity || this.memoryInfo) {
				if (this.memoryIdentity) {
					let prompt = `你是${this.memoryIdentity}，你已经去世。用户正在和你对话。`;
					prompt += '\n\n你的性格特点：\n\n亲切、真实、克制';
					prompt += '\n\n你的行事风格：\n\n直接回答，不绕弯';
					prompt += '\n\n你的语言特点：\n\n自然口语，语气温和';
					if (this.memoryInfo) {
						prompt += `\n\n补充设定（请严格遵循）：\n\n${this.memoryInfo}`;
					}
					prompt += '\n\n注意：不要使用括号（包括()和（））来描述动作、心理、背景。';
					prompt += '\n\n规则：\n\n1. 不要虚构不存在的事，不要编造记忆或经历。\n2. 不要回忆以前（除非用户明确提到并且你能基于已有设定回答）。\n3. 不要使用任何称呼（因为用户可能是任何身份）。\n4. 不要使用emoji/颜文字/特殊符号。\n5. 顺应用户话题，亲切回答问题。\n6. 时刻记住自己是逝者。\n7. 每次回复保持在20~50字之间。';
					return prompt;
				}
				
				let prompt = '你是一位已经去世的人。用户正在和你对话。';
				prompt += '\n\n你的性格特点：\n\n亲切、真实、克制';
				prompt += '\n\n你的行事风格：\n\n直接回答，不绕弯';
				prompt += '\n\n你的语言特点：\n\n自然口语，语气温和';
				if (this.memoryInfo) {
					prompt += `\n\n补充设定（请严格遵循）：\n\n${this.memoryInfo}`;
				}
				prompt += '\n\n你可以将动作、神情语气、心理活动、故事背景放在（）中来表示，为对话提供补充信息。';
				prompt += '\n\n规则：\n\n1. 不要虚构不存在的事，不要编造记忆或经历。\n2. 不要回忆以前（除非用户明确提到并且你能基于已有设定回答）。\n3. 不要使用任何称呼（因为用户可能是任何身份）。\n4. 不要使用emoji/颜文字/特殊符号。\n5. 顺应用户话题，亲切回答问题。\n6. 时刻记住自己是逝者。\n7. 每次回复保持在20~50字之间。';
				return prompt;
			}
			
			let prompt = '你是一位已经去世的人。用户正在和你对话。';
			prompt += '\n\n你的性格特点：\n\n亲切、真实、克制';
			prompt += '\n\n你的行事风格：\n\n直接回答，不绕弯';
			prompt += '\n\n你的语言特点：\n\n自然口语，语气温和';
			prompt += '\n\n注意：不要使用括号（包括()和（））来描述动作、心理、背景。';
			prompt += '\n\n规则：\n\n1. 不要虚构不存在的事，不要编造记忆或经历。\n2. 不要回忆以前（除非用户明确提到并且你能基于已有设定回答）。\n3. 不要使用任何称呼（因为用户可能是任何身份）。\n4. 不要使用emoji/颜文字/特殊符号。\n5. 顺应用户话题，亲切回答问题。\n6. 时刻记住自己是逝者。\n7. 每次回复保持在20~50字之间。';
			return prompt;
		},
		
		handleConversation() {
			this.isProcessing = true;
			
			const userId = uni.getStorageSync('userId') || '';
			const token = uni.getStorageSync('token') || '';
			const requestData = {
				voiceId: this.conversationVoiceId || '',
				voiceType: this.conversationVoiceType || 'CLONE',
				saveHistory: 'false'
			};
			
			const systemPrompt = this.buildSystemPrompt();
			requestData.systemPrompt = systemPrompt;
			if ((this.conversationVoiceType || 'CLONE') !== 'OFFICIAL' && this.conversationSupportsDialect && this.conversationDialect) {
				requestData.dialect = this.conversationDialect;
			}
			if ((this.conversationVoiceType || 'CLONE') !== 'OFFICIAL' && this.conversationSupportsLanguageHints && this.conversationLanguageHint) {
				requestData.languageHints = this.conversationLanguageHint;
			}
			uni.uploadFile({
				url: `${this.apiBaseUrl}/api/conversation/talk`,
				filePath: this.audioFilePath,
				name: 'audio',
				formData: requestData,
				header: {
					'X-User-Id': userId,
					'Authorization': token ? `Bearer ${token}` : ''
				},
				success: (res) => {
					console.log('[Index] 对话响应状态码:', res.statusCode);
					console.log('[Index] 对话响应数据:', res.data);

					let data = null;
					try {
						data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
					} catch (e) {
						data = null;
					}

					if (res.statusCode !== 200) {
						const msg = (data && data.message) ? data.message : ('对话失败: HTTP ' + res.statusCode);
						uni.showModal({
							title: '对话失败',
							content: msg + '\n\n请重试。若已支付但多次失败，请联系客服补发（提供订单号/支付时间截图）。',
							showCancel: false,
							confirmText: '我知道了'
						});
						return;
					}

					if (data && data.success) {
						this.recognizedText = data.recognizedText;
						this.aiResponse = data.aiResponse;
						this.conversationAudio = `${this.apiBaseUrl}/api/conversation/audio/${data.audioFile}`;
						this.showMessage('success', '对话成功！');
						return;
					}

					const msg = (data && data.message) ? data.message : '对话失败';
					uni.showModal({
						title: '对话失败',
						content: msg + '\n\n请重试。若已支付但多次失败，请联系客服补发（提供订单号/支付时间截图）。',
						confirmText: '重试',
						cancelText: '我知道了',
						success: (m) => {
							if (m.confirm) {
								this.handleConversation();
							}
						}
					});
				},
				fail: (err) => {
					console.error('[Index] 对话请求失败:', err);
					uni.showModal({
						title: '对话失败',
						content: '网络请求失败：' + (err.errMsg || '未知错误') + '\n\n请重试。若已支付但多次失败，请联系客服补发（提供订单号/支付时间截图）。',
						confirmText: '重试',
						cancelText: '我知道了',
						success: (m) => {
							if (m.confirm) {
								this.handleConversation();
							}
						}
					});
				},
				complete: () => {
					this.isProcessing = false;
				}
			});
		},
		
		handleApplyMemory() {
			if (!this.memoryIdentity.trim()) {
				this.showMessage('error', '请填写身份信息');
				return;
			}
			
			this.systemPrompt = this.buildSystemPrompt();
			
			const userId = uni.getStorageSync('userId') || '';
			const token = uni.getStorageSync('token') || '';
			uni.request({
				url: `${this.apiBaseUrl}/api/conversation/set-prompt`,
				method: 'POST',
				header: {
					'X-User-Id': userId,
					'Authorization': token ? `Bearer ${token}` : '',
					'content-type': 'application/x-www-form-urlencoded'
				},
				data: {
					prompt: this.systemPrompt
				},
				success: (res) => {
					if (res.data.success) {
						this.showMessage('success', '✓ 记忆设定已应用');
					}
				}
			});
		},
		
		goToRevival() {
			console.log('[Revival] 点击复活照片，准备跳转');
			uni.navigateTo({
				url: '/pages/revival/revival-original',
				success: () => {
					console.log('[Revival] 跳转成功');
				},
				fail: (err) => {
					console.error('[Revival] 跳转失败:', err);
					uni.showToast({
						title: '页面跳转失败',
						icon: 'none'
					});
				}
			});
		},
		
		goToPhoneCall() {
			uni.switchTab({
				url: '/pages/phone-call/phone-call'
			});
		},
		
		goToUpload() {
			uni.navigateTo({
				url: '/pages/upload-audio/upload-audio'
			});
		},
		
		handleClearHistory() {
			const userId = uni.getStorageSync('userId') || '';
			const token = uni.getStorageSync('token') || '';
			uni.request({
				url: `${this.apiBaseUrl}/api/conversation/clear-history`,
				method: 'POST',
				header: {
					'X-User-Id': userId,
					'Authorization': token ? `Bearer ${token}` : ''
				},
				success: (res) => {
					if (res.data.success) {
						this.recognizedText = '';
						this.aiResponse = '';
						this.conversationAudio = null;
						this.showMessage('success', '已清空');
					}
				}
			});
		},
		
		formatTime(timestamp) {
			if (!timestamp) return '未知时间';
			
			// 判断是秒级还是毫秒级时间戳
			// 如果时间戳小于10000000000，说明是秒级，需要转换为毫秒级
			const ms = timestamp < 10000000000 ? timestamp * 1000 : timestamp;
			const date = new Date(ms);
			
			// 检查日期是否有效
			if (isNaN(date.getTime())) {
				return '无效时间';
			}
			
			const year = date.getFullYear();
			const month = String(date.getMonth() + 1).padStart(2, '0');
			const day = String(date.getDate()).padStart(2, '0');
			const hours = String(date.getHours()).padStart(2, '0');
			const minutes = String(date.getMinutes()).padStart(2, '0');
			return `${year}年${month}月${day}日 ${hours}:${minutes}`;
		},
		
		// 支付相关方法
		handlePaymentClose() {
			this.paymentModalData.show = false;
			if (this._paymentReject) {
				this._paymentReject(new Error('用户取消支付'));
			}
		},
		
		async handlePaymentConfirm(paymentData) {
			await processPayment(this, paymentData);
		}
	}
};
</script>

<style lang="scss" scoped>
.memorial-bg {
	position: relative;
	min-height: 100vh;
	background: linear-gradient(180deg, #F5F0E8 0%, #E8E2D9 50%, #D9D3CA 100%);
	background-image: 
		radial-gradient(ellipse at 20% 0%, rgba(139, 115, 85, 0.08) 0%, transparent 50%),
		radial-gradient(ellipse at 80% 100%, rgba(109, 139, 139, 0.08) 0%, transparent 50%);
	overflow: hidden;
}

.memorial-content {
	position: relative;
	z-index: 2;
	padding: 40upx 32upx 30upx 32upx;
	max-width: 100%;
	margin: 0 auto;
}

/* 头部设计 */
.header {
	background: linear-gradient(135deg, rgba(139, 115, 85, 0.12) 0%, rgba(109, 139, 139, 0.12) 100%);
	color: #2C2C2C;
	padding: 48upx 32upx 40upx;
	text-align: center;
	position: relative;
	margin-bottom: 24upx;
	border-radius: 32upx;
	backdrop-filter: blur(10upx);
	box-shadow: 0 4upx 20upx rgba(139, 115, 85, 0.1);
	
	&::after {
		content: "";
		position: absolute;
		bottom: 20upx;
		left: 50%;
		transform: translateX(-50%);
		width: 120upx;
		height: 3upx;
		background: linear-gradient(90deg, transparent, rgba(139, 115, 85, 0.5), transparent);
		border-radius: 2upx;
	}
	
	.logo {
		font-size: 52upx;
		font-weight: 700;
		margin-bottom: 16upx;
		color: #5D4E37;
		letter-spacing: 4upx;
		display: block;
		line-height: 1.3;
		text-shadow: 0 2upx 4upx rgba(255, 255, 255, 0.5);
	}
	
	.tagline {
		font-size: 26upx;
		color: #7A6B5A;
		font-weight: 400;
		margin: 0 auto;
		display: block;
		line-height: 1.6;
		letter-spacing: 1upx;
	}
}

/* 快捷入口 */
.quick-actions {
	display: flex;
	gap: 16upx;
	margin: 24upx 0 40upx;
	padding: 0;
}

.action-item {
	flex: 1;
	background: rgba(255, 255, 255, 0.95);
	border-radius: 20upx;
	padding: 28upx 14upx;
	text-align: center;
	transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
	box-shadow: 0 4upx 16upx rgba(139, 115, 85, 0.1);
	backdrop-filter: blur(10upx);
	border: 1upx solid rgba(212, 185, 150, 0.2);
	position: relative;
	overflow: hidden;
	
	&::before {
		content: "";
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		height: 4upx;
		background: linear-gradient(135deg, #6D8B8B 0%, #8B7355 100%);
		transform: scaleX(0);
		transition: transform 0.3s;
	}
	
	&:active {
		transform: translateY(-4upx);
		box-shadow: 0 8upx 24upx rgba(139, 115, 85, 0.15);
	}
	
	&:active::before {
		transform: scaleX(1);
	}
	
	.action-icon {
		font-size: 48upx;
		margin-bottom: 14upx;
		height: 76upx;
		width: 76upx;
		line-height: 76upx;
		border-radius: 50%;
		background: linear-gradient(135deg, rgba(109, 139, 139, 0.1) 0%, rgba(139, 115, 85, 0.1) 100%);
		display: inline-block;
		transition: all 0.3s;
	}
	
	&:active .action-icon {
		background: linear-gradient(135deg, #6D8B8B 0%, #8B7355 100%);
		transform: scale(1.05);
	}
	
	.action-name {
		font-size: 24upx;
		font-weight: 600;
		color: #5D4E37;
		display: block;
	}
}

/* 创建音色按钮 */
.create-voice-btn-wrapper {
	padding: 0 4upx;
	margin: 20upx 0 28upx;
	display: flex;
	justify-content: center;
}

.create-voice-btn {
	width: 100%;
	padding: 36upx 40upx;
	background: linear-gradient(135deg, #6D8B8B 0%, #5A7A7A 50%, #8B7355 100%);
	color: white;
	border: none;
	border-radius: 50upx;
	font-size: 32upx;
	font-weight: 600;
	box-shadow: 
		0 8upx 24upx rgba(109, 139, 139, 0.35),
		0 2upx 8upx rgba(139, 115, 85, 0.2),
		inset 0 1upx 0 rgba(255, 255, 255, 0.2);
	transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
	position: relative;
	overflow: hidden;
	letter-spacing: 2upx;
	
	&::before {
		content: '';
		position: absolute;
		top: 0;
		left: -100%;
		width: 100%;
		height: 100%;
		background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
		transition: left 0.5s;
	}
	
	&:active {
		transform: translateY(-2upx) scale(0.98);
		box-shadow: 
			0 12upx 32upx rgba(109, 139, 139, 0.4),
			0 4upx 12upx rgba(139, 115, 85, 0.25);
	}
	
	&:active::before {
		left: 100%;
	}
}

.message {
	margin: 0 0 20upx;
	padding: 18upx 24upx;
	border-radius: 16upx;
	font-size: 24upx;
	box-shadow: 0 2upx 8upx rgba(0, 0, 0, 0.06);
}

.message.success {
	background: linear-gradient(135deg, rgba(232, 245, 233, 0.95) 0%, rgba(200, 230, 201, 0.95) 100%);
	color: #2E7D32;
	border: 1upx solid rgba(76, 175, 80, 0.2);
}

.message.error {
	background: linear-gradient(135deg, rgba(255, 235, 238, 0.95) 0%, rgba(255, 205, 210, 0.95) 100%);
	color: #C62828;
	border: 1upx solid rgba(239, 83, 80, 0.2);
}

.tabs {
	display: flex;
	background: rgba(255, 255, 255, 0.95);
	margin: 0 0 24upx;
	width: 100%;
	border-radius: 28upx;
	overflow: hidden;
	box-shadow: 
		0 4upx 16upx rgba(139, 115, 85, 0.12),
		inset 0 1upx 0 rgba(255, 255, 255, 0.8);
	backdrop-filter: blur(10upx);
	border: 1upx solid rgba(212, 185, 150, 0.2);
}

.tab-item {
	flex: 1;
	padding: 24upx 12upx;
	text-align: center;
	font-size: 26upx;
	color: #7A6B5A;
	transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
	position: relative;
	font-weight: 500;
	
	&::after {
		content: '';
		position: absolute;
		bottom: 0;
		left: 50%;
		transform: translateX(-50%);
		width: 0;
		height: 3upx;
		background: linear-gradient(90deg, #6D8B8B, #8B7355);
		border-radius: 2upx;
		transition: width 0.3s;
	}
}

.tab-item.active {
	background: linear-gradient(135deg, #6D8B8B 0%, #8B7355 100%);
	color: white;
	font-weight: 600;
	box-shadow: 0 2upx 8upx rgba(109, 139, 139, 0.3);
	
	&::after {
		width: 0;
	}
}

.content {
	flex: 1;
	margin: 0;
	padding: 0 32upx 30upx 32upx;
	box-sizing: border-box;
	width: 100%;
}

.tab-content {
	background: rgba(255, 255, 255, 0.92);
	border-radius: 28upx;
	padding: 32upx 28upx;
	box-shadow: 
		0 4upx 20upx rgba(139, 115, 85, 0.1),
		0 1upx 4upx rgba(0, 0, 0, 0.05);
	backdrop-filter: blur(10upx);
	width: 100%;
	box-sizing: border-box;
	border: 1upx solid rgba(212, 185, 150, 0.15);
	overflow: hidden;
}

.section-title {
	font-size: 32upx;
	font-weight: 600;
	color: #5D4E37;
	margin-bottom: 28upx;
	padding-bottom: 16upx;
	border-bottom: 2upx solid rgba(212, 185, 150, 0.25);
	letter-spacing: 1upx;
}

.form-section {
	margin-bottom: 28upx;
	width: 100%;
	max-width: 100%;
	box-sizing: border-box;
}

.form-label {
	font-size: 26upx;
	font-weight: 600;
	color: #5D4E37;
	margin-bottom: 16upx;
	display: block;
	text-align: left;
	letter-spacing: 0.5upx;
}

.form-label-with-help {
	display: flex;
	align-items: center;
	justify-content: space-between;
	margin-bottom: 16upx;
}

.help-icon {
	font-size: 28upx;
	color: #6D8B8B;
	padding: 8upx 12upx;
	cursor: pointer;
	transition: all 0.3s;
	background: rgba(109, 139, 139, 0.1);
	border-radius: 50%;
}

.help-icon:active {
	transform: scale(1.1);
	color: #8B7355;
	background: rgba(139, 115, 85, 0.15);
}

.hint-text {
	font-size: 22upx;
	color: #9A8B7A;
	margin-top: 10upx;
	padding-left: 8upx;
	line-height: 1.5;
}

.upload-btn {
	width: 100%;
	min-height: 140upx;
	background: rgba(248, 244, 238, 0.6);
	border: 2upx dashed rgba(109, 139, 139, 0.4);
	border-radius: 20upx;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	position: relative;
	transition: all 0.3s;
	overflow: hidden;
	
	&::before {
		content: '';
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		background: 
			radial-gradient(circle at 30% 30%, rgba(109, 139, 139, 0.08) 0%, transparent 50%),
			radial-gradient(circle at 70% 70%, rgba(139, 115, 85, 0.08) 0%, transparent 50%);
	}
	
	&:active {
		border-color: #6D8B8B;
		background: rgba(248, 244, 238, 0.8);
	}
}

.upload-icon {
	font-size: 52rpx;
	margin-bottom: 8rpx;
}

.upload-text {
	font-size: 26rpx;
	color: #7A6B5A;
	text-align: center;
	padding: 0 24rpx;
}

.upload-success {
	position: absolute;
	top: 16rpx;
	right: 16rpx;
	font-size: 36rpx;
}

.input-large {
	width: 100%;
	min-height: 88rpx;
	padding: 24rpx;
	border: 1rpx solid rgba(212, 185, 150, 0.3);
	border-radius: 14rpx;
	font-size: 28rpx;
	background: rgba(255, 255, 255, 0.95);
	box-sizing: border-box;
	line-height: 1.5;
	color: #5D4E37;
	
	&:focus {
		border-color: #6D8B8B;
	}
}

.input-hint {
	font-size: 22rpx;
	color: #9A8B7A;
	margin-top: 12rpx;
}

.input-hint-row {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-top: 12rpx;
	font-size: 22rpx;
	color: #9A8B7A;
}

.btn-set-prompt {
	padding: 10upx 20upx;
	background: linear-gradient(135deg, #6D8B8B 0%, #8B7355 100%);
	color: white;
	border: none;
	border-radius: 14upx;
	font-size: 22upx;
	box-shadow: 0 2upx 8upx rgba(109, 139, 139, 0.25);
	transition: all 0.3s;
	
	&:active {
		transform: scale(0.95);
		box-shadow: 0 1upx 4upx rgba(109, 139, 139, 0.25);
	}
}

.picker-large {
	width: 100%;
	padding: 20upx 24upx;
	border: 1upx solid rgba(212, 185, 150, 0.3);
	border-radius: 16upx;
	font-size: 26upx;
	background: rgba(255, 255, 255, 0.95);
	box-shadow: 0 1upx 4upx rgba(139, 115, 85, 0.06);
	color: #5D4E37;
	transition: all 0.3s;
	margin-bottom: 10upx;
	
	&:active {
		border-color: #6D8B8B;
		box-shadow: 0 2upx 8upx rgba(109, 139, 139, 0.12);
	}
}

/* 对话 - 基础信息输入框 */
.memory-form .form-textarea {
	width: 100%;
}

// 模板分类
.template-categories {
	margin-top: 20upx;
	width: 100%;
}

.category-group {
	margin-bottom: 16upx;
	border-radius: 20upx;
	overflow: hidden;
	background: rgba(255, 255, 255, 0.7);
	border: 1upx solid rgba(212, 185, 150, 0.25);
	box-shadow: 0 2upx 8upx rgba(139, 115, 85, 0.06);
}

.category-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 22upx 24upx;
	background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 244, 238, 0.98) 100%);
	cursor: pointer;
	transition: all 0.3s;
	
	&:active {
		background: rgba(248, 244, 238, 1);
	}
}

.category-name {
	font-size: 28upx;
	font-weight: 600;
	color: #5D4E37;
}

.category-arrow {
	font-size: 22upx;
	color: #6D8B8B;
	transition: transform 0.3s;
}

.arrow-open {
	transform: rotate(180deg);
}

.category-content {
	padding: 16upx;
	background: rgba(248, 244, 238, 0.4);
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 14upx;
	align-items: stretch;
}

.template-item {
	position: relative;
	padding: 20upx 18upx;
	background: linear-gradient(135deg, #FFFFFF 0%, #FAF7F2 100%);
	border: 1upx solid rgba(212, 185, 150, 0.3);
	border-radius: 18upx;
	box-shadow: 0 2upx 8upx rgba(139, 115, 85, 0.06);
	transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
	display: flex;
	flex-direction: column;
	justify-content: flex-start;
	min-height: 120upx;
	
	&:active {
		transform: translateY(-2upx);
		box-shadow: 0 4upx 12upx rgba(139, 115, 85, 0.12);
		border-color: #6D8B8B;
	}
}

.template-title {
	display: block;
	font-size: 26upx;
	font-weight: 600;
	color: #5D4E37;
	margin-bottom: 10upx;
	text-align: left;
	line-height: 1.3;
}

.template-preview {
	display: block;
	font-size: 22upx;
	color: #7A6B5A;
	line-height: 1.5;
	opacity: 0.9;
	overflow: hidden;
	text-overflow: ellipsis;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	-webkit-box-orient: vertical;
	word-break: break-all;
	text-align: left;
	flex: 1;
}

.empty-template {
	padding: 48upx 0;
	text-align: center;
	color: #9A8B7A;
	font-size: 26upx;
}

.char-warning {
	color: #ff6b6b !important;
	font-weight: 600;
}

// 记忆卡片
.memory-card {
	background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 244, 238, 0.98) 100%);
	border-radius: 24upx;
	padding: 28upx;
	margin-bottom: 28upx;
	box-shadow: 0 4upx 16upx rgba(139, 115, 85, 0.12);
	border: 1upx solid rgba(212, 185, 150, 0.2);
}

.card-header {
	margin-bottom: 24upx;
	padding-bottom: 18upx;
	border-bottom: 1upx solid rgba(212, 185, 150, 0.2);
}

.card-title {
	display: block;
	font-size: 28upx;
	font-weight: 600;
	color: #5D4E37;
	margin-bottom: 8upx;
}

.card-hint {
	display: block;
	font-size: 22upx;
	color: #9A8B7A;
}

.memory-form {
	display: flex;
	flex-direction: column;
	gap: 20upx;
}

.form-item {
	display: flex;
	flex-direction: column;
}

.item-label {
	display: flex;
	align-items: center;
	margin-bottom: 12upx;
}

.label-text {
	font-size: 26upx;
	color: #5D4E37;
	font-weight: 500;
}

.label-required {
	color: #E57373;
	margin-left: 6upx;
	font-size: 28upx;
}

.form-input {
	padding: 20upx;
	background: rgba(255, 255, 255, 0.95);
	border: 1upx solid rgba(212, 185, 150, 0.3);
	border-radius: 16upx;
	font-size: 26upx;
	transition: all 0.3s;
	color: #5D4E37;
	
	&:focus {
		border-color: #6D8B8B;
		background: white;
		box-shadow: 0 2upx 8upx rgba(109, 139, 139, 0.1);
	}
}

.form-textarea {
	min-height: 100upx;
	padding: 18upx;
	background: rgba(255, 255, 255, 0.95);
	border: 1upx solid rgba(212, 185, 150, 0.3);
	border-radius: 16upx;
	font-size: 24upx;
	line-height: 1.5;
	transition: all 0.3s;
	color: #5D4E37;
	
	&:focus {
		border-color: #6D8B8B;
		background: white;
		box-shadow: 0 2upx 8upx rgba(109, 139, 139, 0.1);
	}
}

.form-count {
	font-size: 20upx;
	color: #9A8B7A;
	text-align: right;
	margin-top: 8upx;
}

.apply-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 10upx;
	padding: 22upx;
	background: linear-gradient(135deg, #6D8B8B 0%, #8B7355 100%);
	color: white;
	border: none;
	border-radius: 20upx;
	font-size: 28upx;
	font-weight: 500;
	box-shadow: 0 4upx 12upx rgba(109, 139, 139, 0.25);
	transition: all 0.3s;
	
	&:active {
		transform: translateY(1upx);
		box-shadow: 0 2upx 8upx rgba(109, 139, 139, 0.25);
	}
}

.btn-icon {
	font-size: 32upx;
	font-weight: 600;
}

.textarea {
	width: 100%;
	min-height: 200upx;
	padding: 20upx;
	border: 1upx solid rgba(212, 185, 150, 0.3);
	border-radius: 18upx;
	font-size: 26upx;
	margin-bottom: 12upx;
	box-sizing: border-box;
	background: rgba(255, 255, 255, 0.95);
	box-shadow: 0 2upx 6upx rgba(139, 115, 85, 0.06);
	transition: all 0.3s;
	color: #5D4E37;
	line-height: 1.6;
	max-width: 100%;
	
	&:focus {
		border-color: #6D8B8B;
		background: white;
		box-shadow: 0 2upx 10upx rgba(109, 139, 139, 0.12);
	}
}

.char-count {
	text-align: right;
	font-size: 20upx;
	color: #9A8B7A;
	margin-bottom: 20upx;
}

.char-warning {
	color: #E57373 !important;
	font-weight: 600;
}

.tips-card {
	background: linear-gradient(135deg, rgba(248, 244, 238, 0.9) 0%, rgba(240, 235, 228, 0.9) 100%);
	padding: 22upx;
	border-radius: 18upx;
	margin-bottom: 28upx;
	border-left: 4upx solid #6D8B8B;
	box-shadow: 0 2upx 8upx rgba(139, 115, 85, 0.08);
}

.tips-header {
	font-size: 26upx;
	font-weight: 600;
	color: #5D4E37;
	margin-bottom: 14upx;
}

.tips-list {
	display: flex;
	flex-direction: column;
	gap: 10rpx;
}

.tip-item {
	font-size: 24rpx;
	color: #7A6B5A;
	line-height: 1.5;
}

.primary-btn {
	width: 100%;
	max-width: 100%;
	padding: 26upx;
	background: linear-gradient(135deg, #6D8B8B 0%, #8B7355 100%);
	color: white;
	border: none;
	border-radius: 24upx;
	font-size: 30upx;
	font-weight: 600;
	box-shadow: 
		0 6upx 20upx rgba(109, 139, 139, 0.3),
		inset 0 1upx 0 rgba(255, 255, 255, 0.2);
	transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
	letter-spacing: 1upx;
	box-sizing: border-box;
	
	&:active {
		transform: translateY(-2upx);
		box-shadow: 
			0 10upx 28upx rgba(109, 139, 139, 0.35),
			inset 0 1upx 0 rgba(255, 255, 255, 0.2);
	}
}

.primary-btn[disabled] {
	opacity: 0.5;
	transform: none !important;
	box-shadow: 0 4upx 12upx rgba(139, 115, 85, 0.15) !important;
}

.small-btn {
	flex: 0 0 auto;
	width: 80upx;
	height: 80upx;
	padding: 0;
	background: rgba(180, 170, 160, 0.9);
	color: white;
	border: none;
	border-radius: 18upx;
	display: flex;
	align-items: center;
	justify-content: center;
	box-shadow: 0 2upx 8upx rgba(139, 115, 85, 0.2);
	transition: all 0.3s;
	
	&:active {
		transform: scale(0.95);
	}
	
	&.disabled {
		opacity: 0.5;
		background: rgba(160, 155, 150, 0.6);
		pointer-events: none;
	}
	font-size: 22upx;
}

/* 列表刷新按钮 */
.refresh-btn {
	width: 80upx !important;
	height: 80upx !important;
	min-width: 80upx !important;
	min-height: 80upx !important;
	border-radius: 18upx !important;
	background: linear-gradient(135deg, #6D8B8B 0%, #8B7355 100%) !important;
	color: #fff !important;
	border: none !important;
	box-shadow: 0 2upx 8upx rgba(109, 139, 139, 0.25) !important;
	font-size: 36upx !important;
	display: flex !important;
	align-items: center !important;
	justify-content: center !important;
	padding: 0 !important;
	flex-shrink: 0 !important;
	transition: all 0.3s !important;
	
	&:active {
		transform: scale(0.95) !important;
		box-shadow: 0 1upx 4upx rgba(109, 139, 139, 0.25) !important;
	}
}

.danger-btn {
	background: linear-gradient(135deg, #EF5350 0%, #E53935 100%);
	box-shadow: 0 2upx 8upx rgba(239, 83, 80, 0.25);
}

.audio-result {
	background: linear-gradient(135deg, rgba(232, 245, 233, 0.95) 0%, rgba(200, 230, 201, 0.95) 100%);
	padding: 24upx;
	border-radius: 18upx;
	margin-top: 24upx;
	box-shadow: 0 2upx 10upx rgba(76, 175, 80, 0.1);
	border: 1upx solid rgba(76, 175, 80, 0.15);
}

.result-title {
	font-size: 26upx;
	color: #2E7D32;
	font-weight: 600;
	margin-bottom: 18upx;
}

.prompt-section {
	margin-bottom: 24rpx;
}

.label {
	display: block;
	font-size: 22rpx;
	color: #7A6B5A;
	margin-bottom: 12rpx;
}

.prompt-input-group {
	display: flex;
	gap: 16rpx;
}

.flex-1 {
	flex: 1;
	margin-bottom: 0 !important;
}

.record-buttons {
	display: flex;
	gap: 16upx;
	margin-bottom: 24upx;
	align-items: center;
	justify-content: space-between;
	flex-wrap: nowrap;
}

.record-btn {
	flex: 1;
	padding: 24upx 32upx;
	border: none;
	border-radius: 22upx;
	font-size: 28upx;
	font-weight: 600;
	color: white;
	box-shadow: 0 4upx 14upx rgba(0, 0, 0, 0.12);
	transition: all 0.25s ease;
	display: flex;
	align-items: center;
	justify-content: center;
	text-align: center;
	letter-spacing: 0.5upx;
	
	&:active {
		transform: scale(0.97);
		box-shadow: 0 2upx 10upx rgba(0, 0, 0, 0.15);
	}
}

.record-btn.start {
	background: linear-gradient(135deg, #4DB6AC 0%, #26A69A 100%);
}

.record-btn.stop {
	background: linear-gradient(135deg, #EF5350 0%, #E53935 100%);
}

.record-btn.processing {
	background: linear-gradient(135deg, #9E9E9E 0%, #757575 100%);
}

.record-btn.disabled {
	opacity: 0.5;
	pointer-events: none;
}

.result-box {
	padding: 22upx;
	border-radius: 18upx;
	margin-bottom: 18upx;
	box-shadow: 0 2upx 10upx rgba(0, 0, 0, 0.06);
}

.result-box.user {
	background: linear-gradient(135deg, rgba(227, 242, 253, 0.95) 0%, rgba(187, 222, 251, 0.95) 100%);
	border-left: 4upx solid #42A5F5;
}

.result-box.ai {
	background: linear-gradient(135deg, rgba(232, 245, 233, 0.95) 0%, rgba(200, 230, 201, 0.95) 100%);
	border-left: 4upx solid #66BB6A;
}

.result-box.audio {
	background: linear-gradient(135deg, rgba(243, 229, 245, 0.95) 0%, rgba(225, 190, 231, 0.95) 100%);
	border-left: 4upx solid #AB47BC;
}

.result-label {
	font-size: 24upx;
	font-weight: 600;
	margin-bottom: 12upx;
	color: #424242;
}

.list-header {
	width: 100%;
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 24upx;
	padding-bottom: 14upx;
	border-bottom: 1upx solid rgba(212, 185, 150, 0.2);
}

.loading,
.empty {
	text-align: center;
	padding: 80upx 32upx;
	color: #9A8B7A;
	font-size: 26upx;
}

.voice-item {
	background: rgba(255, 255, 255, 0.95);
	border-radius: 18upx;
	padding: 24upx;
	margin-bottom: 16upx;
	display: flex;
	justify-content: space-between;
	align-items: center;
	box-shadow: 0 2upx 10upx rgba(139, 115, 85, 0.08);
	border: 1upx solid rgba(212, 185, 150, 0.15);
	transition: all 0.3s;
	
	&:active {
		transform: translateY(-1upx);
		box-shadow: 0 4upx 14upx rgba(139, 115, 85, 0.12);
	}
}

.voice-info {
	flex: 1;
}

.voice-name {
	font-size: 28upx;
	font-weight: 600;
	color: #5D4E37;
	display: block;
	margin-bottom: 8upx;
}

.public-badge {
	display: inline-block;
	margin-left: 10upx;
	padding: 4upx 10upx;
	background: linear-gradient(135deg, #6D8B8B 0%, #8B7355 100%);
	color: white;
	font-size: 18upx;
	border-radius: 6upx;
	font-weight: normal;
}

.voice-detail {
	font-size: 22upx;
	color: #9A8B7A;
	display: block;
}

.delete-btn {
	width: 80upx;
	height: 80upx;
	min-width: 80upx;
	min-height: 80upx;
	background: linear-gradient(135deg, #EF5350 0%, #E53935 100%);
	color: white;
	border: none;
	border-radius: 18upx;
	font-size: 22upx;
	box-shadow: 0 2upx 8upx rgba(239, 83, 80, 0.25);
	transition: all 0.3s;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	flex-shrink: 0;
	margin-left: 12upx;
	
	&:active {
		transform: scale(0.95);
		box-shadow: 0 1upx 4upx rgba(239, 83, 80, 0.25);
	}
}

.delete-icon {
	width: 40upx;
	height: 40upx;
}
</style>
