222
This commit is contained in:
parent
4c59a1349d
commit
1ffc1195e6
|
|
@ -5,16 +5,16 @@ import { useAuthStore } from './stores/index.js'
|
|||
import App from './App.vue'
|
||||
|
||||
// 导入样式
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import './assets/styles/global.css'
|
||||
import './assets/styles/responsive.css'
|
||||
import './assets/styles/themes.css'
|
||||
import dtUI from '@deotaland/ui'
|
||||
import '@deotaland/ui/style.css'
|
||||
import 'element-plus/dist/index.css'
|
||||
// 导入Element Plus图标
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
|
||||
import ElementPlus from 'element-plus'
|
||||
// 导入i18n配置
|
||||
import i18n from './locales/i18n'
|
||||
// 创建应用实例
|
||||
|
|
@ -26,6 +26,7 @@ window.setElMessage = (options={})=>{
|
|||
// 配置 Pinia
|
||||
const pinia = createPinia()
|
||||
app.use(pinia)
|
||||
// app.use(ElementPlus)
|
||||
// 配置国际化
|
||||
app.use(i18n)
|
||||
// 注册所有Element Plus图标
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import { createRouter, createWebHistory,createWebHashHistory } from 'vue-router'
|
||||
import i18n from '@/locales/i18n'
|
||||
import { watch } from 'vue'
|
||||
import About from '@/views/About.vue'
|
||||
import NotFound from '@/views/NotFound.vue'
|
||||
import AdminLogin from '@/views/AdminLogin/AdminLogin.vue'
|
||||
|
||||
// 管理员布局组件(懒加载)
|
||||
const About = ()=>import('@/views/About.vue')
|
||||
const NotFound = ()=>import('@/views/NotFound.vue')
|
||||
const AdminLogin = ()=>import('@/views/AdminLogin/AdminLogin.vue')
|
||||
const AdminLayout = () => import('@/components/admin/AdminLayout.vue')
|
||||
const AdminDashboard = () => import('@/views/admin/AdminDashboard.vue')
|
||||
const AdminContent = () => import('@/views/admin/AdminContent.vue')
|
||||
|
|
@ -70,7 +69,17 @@ export const permissionRoutes = [
|
|||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
path: 'disassembly-orders',
|
||||
name: 'AdminDisassemblyOrders',
|
||||
component: AdminDisassemblyOrders,
|
||||
meta: {
|
||||
title: 'admin.layout.disassemblyOrders',
|
||||
icon: 'EditPen',
|
||||
menuOrder: 2,
|
||||
requiresAuth: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'order-list',
|
||||
name: 'AdminOrdersList',
|
||||
|
|
@ -225,17 +234,7 @@ const routes = [
|
|||
requiresAuth: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'disassembly-orders',
|
||||
name: 'AdminDisassemblyOrders',
|
||||
component: AdminDisassemblyOrders,
|
||||
meta: {
|
||||
title: 'admin.layout.disassemblyOrders',
|
||||
icon: 'EditPen',
|
||||
menuOrder: 2,
|
||||
requiresAuth: true,
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
path: 'disassembly-orders/:id',
|
||||
name: 'AdminDisassemblyDetail',
|
||||
|
|
|
|||
|
|
@ -113,9 +113,9 @@
|
|||
<el-table-column :label="$t('admin.review.actions')" min-width="300" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="actions-container">
|
||||
<el-button size="small" @click="previewModel(row)">
|
||||
<!-- <el-button size="small" @click="previewModel(row)">
|
||||
{{ t('admin.review.preview3D') }}
|
||||
</el-button>
|
||||
</el-button> -->
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
|
|
@ -438,7 +438,7 @@ const approveReview = async (review) => {
|
|||
if(res.code==0){
|
||||
ElMessage.success(t('admin.review.approveSuccess'))
|
||||
// 跳转到拆件订单页面
|
||||
router.push(`/admin/disassembly-orders?order_no=${review.order_no}`)
|
||||
router.push(`/admin/orders/disassembly-orders?order_no=${review.order_no}`)
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
<div v-if="currentStep >= 1" class="step-content">
|
||||
<div class="preview-container-horizontal">
|
||||
<div class="preview-images-horizontal">
|
||||
<div v-if="thumbnailUrl" class="image-item-horizontal" @click="previewImage(thumbnailUrl)">
|
||||
<div class="image-item-horizontal" @click="previewImage(thumbnailUrl)">
|
||||
<img :src="thumbnailUrl" alt="缩略图" />
|
||||
<div class="image-label">{{ $t('admin.disassemblyOrders.detail.preview') }}</div>
|
||||
<div class="image-actions">
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-item-horizontal" @click="previewModel(modelUrl)">
|
||||
<div v-if="modelUrl" class="image-item-horizontal" @click="previewModel(modelUrl)">
|
||||
<div class="model-preview-horizontal">
|
||||
<el-icon size="48"><View /></el-icon>
|
||||
<span>{{ $t('admin.disassemblyOrders.detail.previewDialog') }}</span>
|
||||
|
|
@ -80,7 +80,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="prompt-template-container">
|
||||
<div class="prompt-template-label">拆件提示词模板</div>
|
||||
<div class="warning-hint" style="margin-left: auto; display: flex; align-items: center; gap: 6px;">
|
||||
<el-icon color="#f2ac34"><WarningFilled /></el-icon>
|
||||
<span style="color: #f2ac34; font-size: 13px; line-height: 1.5;">拆件提示词模板(作用于拆件按钮的提示词)</span>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="cjtsc"
|
||||
type="textarea"
|
||||
|
|
@ -90,7 +93,10 @@
|
|||
></el-input>
|
||||
</div>
|
||||
<div class="prompt-template-container">
|
||||
<div class="prompt-template-label">合并提示词模板</div>
|
||||
<div class="warning-hint" style="margin-left: auto; display: flex; align-items: center; gap: 6px;">
|
||||
<el-icon color="#f2ac34"><WarningFilled /></el-icon>
|
||||
<span style="color: #f2ac34; font-size: 13px; line-height: 1.5;">合并提示词模板(作用于合并按钮的提示词)</span>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="hbtsc"
|
||||
type="textarea"
|
||||
|
|
@ -99,12 +105,26 @@
|
|||
class="prompt-template-textarea"
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="disassembly-button-container">
|
||||
<div class="button-container">
|
||||
<div class="button-with-hint">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="generateFullModel"
|
||||
>
|
||||
生成完整模型
|
||||
</el-button>
|
||||
<div class="button-hint" style="display: flex; align-items: center; gap: 6px; margin-left: 16px;">
|
||||
<el-icon style="--color: #f2ac34;"><WarningFilled /></el-icon>
|
||||
<span style="color: #f2ac34; font-size: 13px; line-height: 1.5;">生成完整模型,适用于不用拆件的E系列产品</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="disassemblyLoading"
|
||||
@click="startDisassembly"
|
||||
style="margin-left: 16px;"
|
||||
>
|
||||
{{ $t('admin.disassemblyOrders.detail.disassembly') }}
|
||||
</el-button>
|
||||
|
|
@ -113,7 +133,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
|
||||
<!-- 第二步:展示已拆件图片 -->
|
||||
<el-timeline-item
|
||||
:color="currentStep >= 2 ? '#6B46C1' : '#e4e7ed'"
|
||||
|
|
@ -121,7 +140,13 @@
|
|||
>
|
||||
<div class="timeline-content" style="z-index: 3;">
|
||||
<div class="step-header">
|
||||
<h3>{{ $t('admin.disassemblyOrders.detail.step2') }}</h3>
|
||||
<div class="step-title-with-hint">
|
||||
<h3>{{ $t('admin.disassemblyOrders.detail.step2') }}</h3>
|
||||
<div class="warning-hint" style="display: flex; align-items: center; gap: 6px;">
|
||||
<el-icon style="--color: #f2ac34;"><WarningFilled /></el-icon>
|
||||
<span style="color: #f2ac34; font-size: 13px; line-height: 1.5;">点击拆件后的内容将在这里展示</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="currentStep >= 2"
|
||||
type="primary"
|
||||
|
|
@ -153,11 +178,7 @@
|
|||
@edit="(result)=>editImage(index,result)"
|
||||
@partial-edit="(imageUrl)=>handlePartialEdit(imageUrl, index)"
|
||||
/>
|
||||
<!-- <div class="image-label">{{ $t('admin.disassemblyOrders.detail.preview') }} {{ index + 1 }}</div> -->
|
||||
<!-- 选择框 -->
|
||||
<!-- <div v-if="mergeMode" class="image-checkbox">
|
||||
<el-checkbox v-model="selectedImages" :label="index" @change="handleCheckboxChange"></el-checkbox>
|
||||
</div> -->
|
||||
|
||||
<div v-if="mergeMode" @click="mergeMode && toggleImageSelection(index)" style="position: absolute;width: 100%;height: 100%;z-index: 2;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -217,70 +238,9 @@
|
|||
<div class="image-label">模型 {{ index + 1 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="step-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleProcessingComplete"
|
||||
:loading="processingCompleteLoading"
|
||||
>
|
||||
加工完成
|
||||
</el-button>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
|
||||
<!-- 第四步:工期信息 -->
|
||||
<!-- <el-timeline-item
|
||||
:color="currentStep >= 4 ? '#6B46C1' : '#e4e7ed'"
|
||||
size="large"
|
||||
>
|
||||
<div class="timeline-content">
|
||||
<h3>工期信息</h3>
|
||||
<div v-if="currentStep >= 4" class="step-content">
|
||||
<div class="work-period-info">
|
||||
<div class="info-card">
|
||||
<div class="card-content">
|
||||
<div class="info-icon">
|
||||
<el-icon><Calendar /></el-icon>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">开始工期</div>
|
||||
<div class="info-value">{{ formatDate(orderDetail.createTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card">
|
||||
<div class="card-content">
|
||||
<div class="info-icon">
|
||||
<el-icon><Check /></el-icon>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">完成工期</div>
|
||||
<div class="info-value">{{ formatDate(orderDetail.processingCompleteTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-card total-days-card">
|
||||
<div class="card-content">
|
||||
<div class="info-icon">
|
||||
<el-icon><Timer /></el-icon>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">共计天数</div>
|
||||
<div class="info-value">{{ calculateTotalDays() }} 天</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-actions">
|
||||
<el-button @click="goBack">返回列表</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-timeline-item> -->
|
||||
</el-timeline>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -321,7 +281,7 @@
|
|||
</el-dialog>
|
||||
<!-- 画布编辑器对话框 -->
|
||||
<DtCanvasEditor
|
||||
v-model:visible="canvasEditorVisible"
|
||||
v-model:visible="canvasEditorVisible"
|
||||
:image-url="canvasEditorImageUrl"
|
||||
@add-prompt-card="handleCanvasSave"
|
||||
/>
|
||||
|
|
@ -349,7 +309,8 @@ import {
|
|||
Timer,
|
||||
Plus,
|
||||
Loading,
|
||||
Upload
|
||||
Upload,
|
||||
WarningFilled
|
||||
} from '@element-plus/icons-vue'
|
||||
import ModelViewer from '@/components/common/ModelViewer.vue'
|
||||
import ModelCom from '@/components/modelCom/index.vue'
|
||||
|
|
@ -585,6 +546,17 @@ const handleDisassembly = () => {
|
|||
// }
|
||||
}
|
||||
|
||||
// 生成完整模型
|
||||
const generateFullModel = () => {
|
||||
const newModel = {
|
||||
id: new Date().getTime(),
|
||||
image: thumbnailUrl.value,
|
||||
taskId: ''
|
||||
};
|
||||
// 使用展开运算符创建新数组以确保响应式更新
|
||||
generatedModels.value = [...generatedModels.value, newModel];
|
||||
}
|
||||
|
||||
|
||||
const deleteModel = (index) => {
|
||||
generatedModels.value.splice(index, 1)
|
||||
|
|
@ -898,13 +870,26 @@ const generateModelFromImage = async (image) => {
|
|||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin: 0 0 16px 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* position: relative; */
|
||||
/* top: -1px; */
|
||||
}
|
||||
|
||||
.step-title-with-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.step-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 调整Element Plus时间轴组件的节点样式 */
|
||||
:deep(.el-timeline-item__wrapper) {
|
||||
padding-left: 28px;
|
||||
|
|
@ -1018,12 +1003,29 @@ const generateModelFromImage = async (image) => {
|
|||
color: #6b7280;
|
||||
}
|
||||
|
||||
.disassembly-button-container {
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.button-container-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.button-with-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button-hint {
|
||||
max-width: 300px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 提示词模板在预览容器中的样式调整 */
|
||||
.preview-container-horizontal .prompt-template-container {
|
||||
margin-bottom: 0;
|
||||
|
|
@ -1321,6 +1323,35 @@ const generateModelFromImage = async (image) => {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.button-with-hint {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-with-hint .el-button {
|
||||
width: 100%;
|
||||
margin-left: 0 !important;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.button-hint {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.button-container .el-button {
|
||||
width: 100%;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.disassembly-button-container {
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,13 +85,11 @@
|
|||
:label="$t('admin.disassemblyOrders.list.orderNumber')"
|
||||
width="150"
|
||||
/>
|
||||
|
||||
<el-table-column
|
||||
prop="creatorName"
|
||||
:label="$t('admin.disassemblyOrders.list.creatorName')"
|
||||
width="120"
|
||||
/>
|
||||
|
||||
<el-table-column
|
||||
prop="status"
|
||||
:label="$t('admin.disassemblyOrders.list.status')"
|
||||
|
|
@ -155,7 +153,6 @@
|
|||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 图片预览对话框 -->
|
||||
<el-dialog
|
||||
v-model="imagePreviewVisible"
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export class AdminOrders {
|
|||
}
|
||||
//获取订单列表
|
||||
getOrderList(params){
|
||||
params.source_type = 0
|
||||
return requestUtils.common(adminApi.default.getOrderList,params);
|
||||
}
|
||||
//同意退款
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ export default defineConfig({
|
|||
// 配置代理解决CORS问题
|
||||
proxy: {
|
||||
'/api': {
|
||||
// target: 'https://api.deotaland.ai',
|
||||
target: 'http://api.deotaland.local',
|
||||
target: 'https://api.deotaland.ai',
|
||||
// target: 'http://api.deotaland.local',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,33 @@ body,html{
|
|||
height: 2px !important; /* 高度 */
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
.el-button--primary,
|
||||
.el-tour-indicator.is-active
|
||||
{
|
||||
border:#9c7eef !important;
|
||||
background-color: #9c7eef !important;
|
||||
}
|
||||
.el-tour__content{
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8f7ff 100%) !important;
|
||||
border-radius: 16px !important;
|
||||
padding: 32px !important;
|
||||
max-width: 480px !important;
|
||||
box-shadow: 0 4px 20px rgba(107, 70, 193, 0.15) !important;
|
||||
animation: fadeInUp 0.6s ease-out !important;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease !important;
|
||||
}
|
||||
.el-tour__content:hover {
|
||||
transform: translateY(-4px) !important;
|
||||
box-shadow: 0 8px 30px rgba(107, 70, 193, 0.25) !important;
|
||||
}
|
||||
/* 暗色主题适配 */
|
||||
html.dark .el-tour__content {
|
||||
background: linear-gradient(135deg, #1e1e1e 0%, #2d2a40 100%) !important;
|
||||
box-shadow: 0 4px 20px rgba(156, 126, 239, 0.25) !important;
|
||||
}
|
||||
html.dark .el-tour__content:hover {
|
||||
box-shadow: 0 8px 30px rgba(156, 126, 239, 0.35) !important;
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
header strong { font-size: 1.25rem; }
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
</div>
|
||||
|
||||
<div class="header-right-section">
|
||||
<div class="free-counts-display" >
|
||||
<div ref="RechargeRef" class="free-counts-display" >
|
||||
<div class="model-count">
|
||||
<el-icon class="count-icon">
|
||||
<MagicStick />
|
||||
|
|
@ -72,7 +72,8 @@ import { Picture, MagicStick, ArrowLeft, Edit, Check, Guide } from '@element-plu
|
|||
import { ElButton, ElIcon, ElInput } from 'element-plus'
|
||||
import ThemeToggle from '../ui/ThemeToggle.vue'
|
||||
import LanguageToggle from '../ui/LanguageToggle.vue'
|
||||
const emit = defineEmits(['openGuideModal','back'])
|
||||
const emit = defineEmits(['openGuideModal','back']);
|
||||
const RechargeRef = ref(null)
|
||||
const props = defineProps({
|
||||
total_score: {
|
||||
type: Number,
|
||||
|
|
@ -126,6 +127,10 @@ const handleSave = () => {
|
|||
}
|
||||
isEditing.value = false
|
||||
}
|
||||
// 子组件导出属性
|
||||
defineExpose({
|
||||
RechargeRef,
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@
|
|||
<el-form :model="shipping" label-width="auto" class="shipping-form">
|
||||
<div class="form-row">
|
||||
<el-form-item :label="$t('checkout.country')">
|
||||
<el-select v-model="shipping.country" :placeholder="$t('checkout.chooseCountry')">
|
||||
<el-select v-model="shipping.country" :placeholder="$t('checkout.chooseCountry')" filterable>
|
||||
<el-option v-for="c in countryOptions" :key="c.value" :label="c.label" :value="c.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
|
@ -104,7 +104,7 @@
|
|||
</div>
|
||||
<div class="form-row">
|
||||
<el-form-item :label="$t('checkout.postalCode')">
|
||||
<el-input v-model="shipping.postalCode" :placeholder="$t('checkout.postalCode')">
|
||||
<el-input v-model="shipping.postalCode" :placeholder="`${$t('checkout.postalCode')} (${$t('common.optional')})`">
|
||||
<template #suffix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
|
|
@ -211,7 +211,6 @@ const isPayButtonDisabled = computed(() => {
|
|||
return !(
|
||||
shipping.value.firstName.trim() &&
|
||||
shipping.value.lastName.trim() &&
|
||||
shipping.value.postalCode.trim() &&
|
||||
shipping.value.state.trim() &&
|
||||
shipping.value.city.trim() &&
|
||||
shipping.value.address1.trim() &&
|
||||
|
|
@ -306,7 +305,14 @@ const getCountryLabel = (code, name) => {
|
|||
}
|
||||
|
||||
const updateCountryOptions = () => {
|
||||
countryOptions.value = (Country.getAllCountries() || []).map(c => ({ label: getCountryLabel(c.isoCode, c.name), value: c.isoCode }))
|
||||
const allCountries = (Country.getAllCountries() || []).map(c => ({ label: getCountryLabel(c.isoCode, c.name), value: c.isoCode }))
|
||||
// 将中国选项置顶
|
||||
const chinaOption = allCountries.find(c => c.value === 'CN')
|
||||
if (chinaOption) {
|
||||
countryOptions.value = [chinaOption, ...allCountries.filter(c => c.value !== 'CN')]
|
||||
} else {
|
||||
countryOptions.value = allCountries
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<aside class="floating-sidebar">
|
||||
<!-- IP类型选择 -->
|
||||
<div class="form-section" >
|
||||
<div class="form-section" ref="ipTypeSectionRef">
|
||||
<div class="expression-info">
|
||||
<span class="expression-description">
|
||||
{{ $t('iPandCardLeft.ipType') }}
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- 文本提示输入 -->
|
||||
<div class="form-section">
|
||||
<div class="form-section" ref="textPromptSectionRef">
|
||||
<div class="expression-info">
|
||||
<span class="expression-description">
|
||||
{{ $t('iPandCardLeft.textPrompt') }}
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- 参考图片上传 -->
|
||||
<div class="form-section">
|
||||
<div class="form-section" ref="referenceImageSectionRef">
|
||||
<div class="expression-info">
|
||||
<span class="expression-description">
|
||||
{{ $t('iPandCardLeft.addReferenceImage') }}
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<!-- 生成按钮 -->
|
||||
<div class="generate-section">
|
||||
<div class="generate-section" ref="generateButtonRef">
|
||||
<el-button
|
||||
type="primary"
|
||||
class="generate-btn"
|
||||
|
|
@ -311,6 +311,10 @@ const props = defineProps({
|
|||
Info: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
series: {
|
||||
type: String,
|
||||
default: 'E1'
|
||||
}
|
||||
})
|
||||
// 获取 i18n 实例
|
||||
|
|
@ -328,6 +332,11 @@ const generateCount = ref(1); // 生成数量,默认为1
|
|||
const isOptimizing = ref(false); // 优化状态
|
||||
const isDragOver = ref(false); // 拖拽状态
|
||||
const isUploading = ref(false); // 图片上传状态
|
||||
// 暴露ref给父组件
|
||||
const ipTypeSectionRef = ref(null);
|
||||
const textPromptSectionRef = ref(null);
|
||||
const referenceImageSectionRef = ref(null);
|
||||
const generateButtonRef = ref(null);
|
||||
// IP类型选择(人物/动物),默认人物,并保存到本地
|
||||
const ipType = ref(1);
|
||||
const handleIpTypeSelect = (type) => {
|
||||
|
|
@ -410,6 +419,7 @@ const init = () => {
|
|||
onMounted(() => {
|
||||
init()
|
||||
// loadExpressions();
|
||||
autoResizeTextarea();
|
||||
});
|
||||
|
||||
// 颜色数据库 - 基于材质类型提供不同的颜色选项
|
||||
|
|
@ -874,9 +884,12 @@ watch(() => formData.value.prompt, () => {
|
|||
});
|
||||
});
|
||||
|
||||
// 组件挂载后初始化
|
||||
onMounted(() => {
|
||||
autoResizeTextarea();
|
||||
// 暴露ref给父组件
|
||||
defineExpose({
|
||||
ipTypeSectionRef,
|
||||
textPromptSectionRef,
|
||||
referenceImageSectionRef,
|
||||
generateButtonRef
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,243 @@
|
|||
<template>
|
||||
<div class="tour-container">
|
||||
<div class="tour-card">
|
||||
<div class="icon-wrapper">
|
||||
<svg class="star-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<MagicStick />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h3 class="title">{{ $t('tour1.title') }}</h3>
|
||||
|
||||
<p class="description">{{ $t('tour1.description') }}</p>
|
||||
|
||||
<ul class="feature-list">
|
||||
<li class="feature-item">
|
||||
<span class="feature-icon">✨</span>
|
||||
<span>{{ $t('tour1.featureText') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="action-section">
|
||||
<p class="action-text">{{ $t('tour1.actionText') }}</p>
|
||||
<p class="action-highlight">{{ $t('tour1.actionHighlight') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {MagicStick} from '@element-plus/icons-vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const isVisible = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
isVisible.value = true
|
||||
}, 100)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tour-container {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 300px;
|
||||
}
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: #6B46C1;
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
text-align: center;
|
||||
margin: 0 0 16px 0;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 15px;
|
||||
color: #4B5563;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 24px 0;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: rgba(107, 70, 193, 0.05);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.feature-item:hover {
|
||||
background: rgba(107, 70, 193, 0.1);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 18px;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.feature-item span:last-child {
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.action-section {
|
||||
text-align: center;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(107, 70, 193, 0.1);
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 14px;
|
||||
color: #6B7280;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.action-highlight {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #6B46C1;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #6B46C1 0%, #A78BFA 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
animation: shimmer 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tour-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
padding: 24px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.tour-card {
|
||||
max-width: 380px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
html.dark .star-icon {
|
||||
color: #A78BFA;
|
||||
}
|
||||
|
||||
html.dark .title {
|
||||
color: #F3F4F6;
|
||||
}
|
||||
|
||||
html.dark .description {
|
||||
color: #D1D5DB;
|
||||
}
|
||||
|
||||
html.dark .feature-item {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
}
|
||||
|
||||
html.dark .feature-item:hover {
|
||||
background: rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
html.dark .feature-item span:last-child {
|
||||
color: #E5E7EB;
|
||||
}
|
||||
|
||||
html.dark .action-section {
|
||||
border-top: 1px solid rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
html.dark .action-text {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
html.dark .action-highlight {
|
||||
background: linear-gradient(135deg, #A78BFA 0%, #C4B5FD 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,393 @@
|
|||
<template>
|
||||
<div class="tour-container">
|
||||
<div class="tour-card">
|
||||
<div class="icon-wrapper">
|
||||
<svg class="star-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="title">{{ $t('tour2.title') }}</h3>
|
||||
<p class="description">{{ $t('tour2.description') }}</p>
|
||||
|
||||
<div class="ip-types-container">
|
||||
<div class="ip-type-item">
|
||||
<div class="type-icon character">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M4 20C4 16 7 14 12 14C17 14 20 16 20 20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="type-info">
|
||||
<h4 class="type-title">{{ $t('iPandCardLeft.character') }}</h4>
|
||||
<p class="type-desc">{{ $t('tour2.characterDesc') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ip-type-item">
|
||||
<div class="type-icon animal">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2C8 2 6 4 6 7V9H4V11H6V13H4V15H6V17C6 20 8 22 12 22C16 22 18 20 18 17V15H20V13H18V11H20V9H18V7C18 4 16 2 12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="9" cy="9" r="1.5" fill="currentColor"/>
|
||||
<circle cx="15" cy="9" r="1.5" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="type-info">
|
||||
<h4 class="type-title">{{ $t('iPandCardLeft.animal') }}</h4>
|
||||
<p class="type-desc">{{ $t('tour2.animalDesc') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="action-section">
|
||||
<p class="action-text">{{ $t('tour2.actionText') }}</p>
|
||||
<p class="action-highlight">{{ $t('tour2.actionHighlight') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tour-container {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: #6B46C1;
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
text-align: center;
|
||||
margin: 0 0 12px 0;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 15px;
|
||||
color: #4B5563;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.ip-types-container {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.ip-type-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
background: rgba(107, 70, 193, 0.05);
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.ip-type-item:hover {
|
||||
background: rgba(107, 70, 193, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.type-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 12px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.type-icon.character {
|
||||
background: linear-gradient(135deg, #6B46C1 0%, #A78BFA 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.type-icon.animal {
|
||||
background: linear-gradient(135deg, #F59E0B 0%, #FBBF24 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.type-icon svg {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.type-info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.type-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.type-desc {
|
||||
font-size: 13px;
|
||||
color: #6B7280;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.comparison-section {
|
||||
background: rgba(107, 70, 193, 0.03);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.comparison-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
margin: 0 0 16px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.comparison-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.comparison-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.comparison-label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #6B7280;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.comparison-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.character-value {
|
||||
color: #6B46C1;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.animal-value {
|
||||
color: #F59E0B;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.vs {
|
||||
font-size: 11px;
|
||||
color: #9CA3AF;
|
||||
font-weight: 600;
|
||||
padding: 2px 8px;
|
||||
background: rgba(156, 163, 175, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.action-section {
|
||||
text-align: center;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(107, 70, 193, 0.1);
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 14px;
|
||||
color: #6B7280;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.action-highlight {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #6B46C1;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #6B46C1 0%, #A78BFA 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
animation: shimmer 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tour-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
padding: 24px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.ip-types-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ip-type-item {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.type-icon {
|
||||
margin-bottom: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.type-info {
|
||||
text-align: left;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.tour-card {
|
||||
max-width: 420px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
html.dark .star-icon {
|
||||
color: #A78BFA;
|
||||
}
|
||||
|
||||
html.dark .title {
|
||||
color: #F3F4F6;
|
||||
}
|
||||
|
||||
html.dark .description {
|
||||
color: #D1D5DB;
|
||||
}
|
||||
|
||||
html.dark .ip-type-item {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
}
|
||||
|
||||
html.dark .ip-type-item:hover {
|
||||
background: rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
html.dark .type-title {
|
||||
color: #E5E7EB;
|
||||
}
|
||||
|
||||
html.dark .type-desc {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
html.dark .comparison-section {
|
||||
background: rgba(139, 92, 246, 0.05);
|
||||
}
|
||||
|
||||
html.dark .comparison-title {
|
||||
color: #F3F4F6;
|
||||
}
|
||||
|
||||
html.dark .comparison-label {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
html.dark .character-value {
|
||||
color: #A78BFA;
|
||||
}
|
||||
|
||||
html.dark .animal-value {
|
||||
color: #FBBF24;
|
||||
}
|
||||
|
||||
html.dark .vs {
|
||||
color: #6B7280;
|
||||
background: rgba(107, 114, 128, 0.2);
|
||||
}
|
||||
|
||||
html.dark .action-section {
|
||||
border-top: 1px solid rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
html.dark .action-text {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
html.dark .action-highlight {
|
||||
background: linear-gradient(135deg, #A78BFA 0%, #C4B5FD 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
<template>
|
||||
<div class="tour-container">
|
||||
<div class="tour-card">
|
||||
<div class="icon-wrapper">
|
||||
<svg class="star-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 4H4C2.89543 4 2 4.89543 2 6V18C2 19.1046 2.89543 20 4 20H16C17.1046 20 18 19.1046 18 18V13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18 4L22 8M18 4V8M18 4H14M10 14L22 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h3 class="title">{{ $t('tour3.title') }}</h3>
|
||||
<p class="description">{{ $t('tour3.description') }}</p>
|
||||
|
||||
<div class="features-container">
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon-wrapper">
|
||||
<span class="feature-icon">👤</span>
|
||||
</div>
|
||||
<div class="feature-content">
|
||||
<h4 class="feature-title">{{ $t('tour3.feature1') }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-section">
|
||||
<p class="action-text">{{ $t('tour3.actionText') }}</p>
|
||||
<p class="action-highlight">{{ $t('tour3.actionHighlight') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tour-container {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: #6B46C1;
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
text-align: center;
|
||||
margin: 0 0 12px 0;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 15px;
|
||||
color: #4B5563;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.features-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: rgba(107, 70, 193, 0.05);
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.feature-item:hover {
|
||||
background: rgba(107, 70, 193, 0.1);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.feature-icon-wrapper {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6B46C1 0%, #A78BFA 100%);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 20px;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.feature-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1F2937;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.action-section {
|
||||
text-align: center;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(107, 70, 193, 0.1);
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 14px;
|
||||
color: #6B7280;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.action-highlight {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #6B46C1;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #6B46C1 0%, #A78BFA 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
animation: shimmer 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tour-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
padding: 24px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.feature-icon-wrapper {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.tour-card {
|
||||
max-width: 420px;
|
||||
}
|
||||
}
|
||||
|
||||
html.dark .star-icon {
|
||||
color: #A78BFA;
|
||||
}
|
||||
|
||||
html.dark .title {
|
||||
color: #F3F4F6;
|
||||
}
|
||||
|
||||
html.dark .description {
|
||||
color: #D1D5DB;
|
||||
}
|
||||
|
||||
html.dark .feature-item {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
}
|
||||
|
||||
html.dark .feature-item:hover {
|
||||
background: rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
html.dark .feature-title {
|
||||
color: #E5E7EB;
|
||||
}
|
||||
|
||||
html.dark .action-section {
|
||||
border-top: 1px solid rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
html.dark .action-text {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
html.dark .action-highlight {
|
||||
background: linear-gradient(135deg, #A78BFA 0%, #C4B5FD 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,306 @@
|
|||
<template>
|
||||
<div class="tour-container">
|
||||
<div class="tour-card">
|
||||
<div class="icon-wrapper">
|
||||
<svg class="star-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="8.5" cy="8.5" r="1.5" fill="currentColor"/>
|
||||
<path d="M21 15L16 10L5 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h3 class="title">{{ $t('tour4.title') }}</h3>
|
||||
<p class="description">{{ $t('tour4.description') }}</p>
|
||||
|
||||
<div class="features-container">
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon-wrapper">
|
||||
<span class="feature-icon">📁</span>
|
||||
</div>
|
||||
<div class="feature-content">
|
||||
<h4 class="feature-title">{{ $t('tour4.feature1') }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon-wrapper">
|
||||
<span class="feature-icon">🖼️</span>
|
||||
</div>
|
||||
<div class="feature-content">
|
||||
<h4 class="feature-title">{{ $t('tour4.feature2') }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon-wrapper">
|
||||
<span class="feature-icon">🎨</span>
|
||||
</div>
|
||||
<div class="feature-content">
|
||||
<h4 class="feature-title">{{ $t('tour4.feature3') }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-section">
|
||||
<p class="action-text">{{ $t('tour4.actionText') }}</p>
|
||||
<p class="action-highlight">{{ $t('tour4.actionHighlight') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tour-container {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
box-shadow: 0 4px 20px rgba(107, 70, 193, 0.15);
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: #6B46C1;
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
text-align: center;
|
||||
margin: 0 0 12px 0;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 15px;
|
||||
color: #4B5563;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.features-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: rgba(107, 70, 193, 0.05);
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.feature-item:hover {
|
||||
background: rgba(107, 70, 193, 0.1);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.feature-icon-wrapper {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6B46C1 0%, #A78BFA 100%);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 20px;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.feature-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1F2937;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.action-section {
|
||||
text-align: center;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(107, 70, 193, 0.1);
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 14px;
|
||||
color: #6B7280;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.action-highlight {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #6B46C1;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #6B46C1 0%, #A78BFA 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
animation: shimmer 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tour-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
padding: 24px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.feature-icon-wrapper {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.tour-card {
|
||||
max-width: 420px;
|
||||
}
|
||||
}
|
||||
|
||||
html.dark .tour-card {
|
||||
background: #1F2937;
|
||||
box-shadow: 0 4px 20px rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
html.dark .star-icon {
|
||||
color: #A78BFA;
|
||||
}
|
||||
|
||||
html.dark .title {
|
||||
color: #F3F4F6;
|
||||
}
|
||||
|
||||
html.dark .description {
|
||||
color: #D1D5DB;
|
||||
}
|
||||
|
||||
html.dark .feature-item {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
}
|
||||
|
||||
html.dark .feature-item:hover {
|
||||
background: rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
html.dark .feature-title {
|
||||
color: #E5E7EB;
|
||||
}
|
||||
|
||||
html.dark .action-section {
|
||||
border-top: 1px solid rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
html.dark .action-text {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
html.dark .action-highlight {
|
||||
background: linear-gradient(135deg, #A78BFA 0%, #C4B5FD 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,304 @@
|
|||
<template>
|
||||
<div class="tour-container">
|
||||
<div class="tour-card">
|
||||
<div class="icon-wrapper">
|
||||
<svg class="star-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h3 class="title">{{ $t('tour5.title') }}</h3>
|
||||
<p class="description">{{ $t('tour5.description') }}</p>
|
||||
|
||||
<div class="features-container">
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon-wrapper">
|
||||
<span class="feature-icon">⚡</span>
|
||||
</div>
|
||||
<div class="feature-content">
|
||||
<h4 class="feature-title">{{ $t('tour5.feature1') }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon-wrapper">
|
||||
<span class="feature-icon">🔧</span>
|
||||
</div>
|
||||
<div class="feature-content">
|
||||
<h4 class="feature-title">{{ $t('tour5.feature2') }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon-wrapper">
|
||||
<span class="feature-icon">🎯</span>
|
||||
</div>
|
||||
<div class="feature-content">
|
||||
<h4 class="feature-title">{{ $t('tour5.feature3') }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-section">
|
||||
<p class="action-text">{{ $t('tour5.actionText') }}</p>
|
||||
<p class="action-highlight">{{ $t('tour5.actionHighlight') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tour-container {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
background: #ffffff;
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
box-shadow: 0 4px 20px rgba(107, 70, 193, 0.15);
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: #6B46C1;
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
text-align: center;
|
||||
margin: 0 0 12px 0;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 15px;
|
||||
color: #4B5563;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.features-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: rgba(107, 70, 193, 0.05);
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.feature-item:hover {
|
||||
background: rgba(107, 70, 193, 0.1);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.feature-icon-wrapper {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6B46C1 0%, #A78BFA 100%);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 20px;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.feature-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1F2937;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.action-section {
|
||||
text-align: center;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(107, 70, 193, 0.1);
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 14px;
|
||||
color: #6B7280;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.action-highlight {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #6B46C1;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #6B46C1 0%, #A78BFA 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
animation: shimmer 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tour-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.tour-card {
|
||||
padding: 24px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.feature-icon-wrapper {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.tour-card {
|
||||
max-width: 420px;
|
||||
}
|
||||
}
|
||||
|
||||
html.dark .tour-card {
|
||||
background: #1F2937;
|
||||
box-shadow: 0 4px 20px rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
html.dark .star-icon {
|
||||
color: #A78BFA;
|
||||
}
|
||||
|
||||
html.dark .title {
|
||||
color: #F3F4F6;
|
||||
}
|
||||
|
||||
html.dark .description {
|
||||
color: #D1D5DB;
|
||||
}
|
||||
|
||||
html.dark .feature-item {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
}
|
||||
|
||||
html.dark .feature-item:hover {
|
||||
background: rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
html.dark .feature-title {
|
||||
color: #E5E7EB;
|
||||
}
|
||||
|
||||
html.dark .action-section {
|
||||
border-top: 1px solid rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
html.dark .action-text {
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
html.dark .action-highlight {
|
||||
background: linear-gradient(135deg, #A78BFA 0%, #C4B5FD 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -362,6 +362,58 @@ export default {
|
|||
tips: '您可以优先在智能体中配置模型角色'
|
||||
}
|
||||
},
|
||||
tour1: {
|
||||
title: '积分说明',
|
||||
description: '积分是您进行AI创作的核心资源,可用于:',
|
||||
featureText: '生成精美的IP角色图片',
|
||||
actionText: '点击右上角充值按钮可获取更多积分',
|
||||
actionHighlight: '开启无限创作可能!'
|
||||
},
|
||||
tour2: {
|
||||
title: 'IP类型选择',
|
||||
description: '选择您想要创建的IP类型,不同类型具有独特的风格和特点',
|
||||
characterDesc: '以人类形象为基础,展现丰富的情感和个性',
|
||||
animalDesc: '以动物形象为基础,展现可爱和独特的风格',
|
||||
comparisonTitle: '类型对比',
|
||||
style: '风格特点',
|
||||
pose: '姿势表现',
|
||||
expression: '表情丰富度',
|
||||
characterStyle: '写实风格,细节丰富',
|
||||
animalStyle: '卡通风格,可爱生动',
|
||||
characterPose: '多样化姿势,动作自然',
|
||||
animalPose: '特色姿势,个性鲜明',
|
||||
characterExpression: '表情细腻,情感丰富',
|
||||
animalExpression: '表情夸张,趣味十足',
|
||||
actionText: '根据您的创作需求,选择合适的IP类型',
|
||||
actionHighlight: '开始创作您的专属IP形象'
|
||||
},
|
||||
tour3: {
|
||||
title: '提示词',
|
||||
description: '通过文字描述来精确控制IP角色的外观、风格和细节',
|
||||
feature1: '描述角色特征:发型、服装、表情等',
|
||||
feature2: '指定艺术风格:写实、卡通、动漫等',
|
||||
feature3: '添加场景元素:背景、道具、氛围等',
|
||||
actionText: '输入详细的描述,让AI理解您的创意',
|
||||
actionHighlight: '用文字创造独一无二的IP角色!'
|
||||
},
|
||||
tour4: {
|
||||
title: '参考图',
|
||||
description: '上传参考图片来帮助AI更好地理解您的创作意图',
|
||||
feature1: '支持多种图片格式:JPG、PNG、WebP等',
|
||||
feature2: '可以上传多张参考图进行对比',
|
||||
feature3: 'AI会根据参考图生成相似风格的作品',
|
||||
actionText: '选择您喜欢的图片作为参考',
|
||||
actionHighlight: '让参考图激发AI的创作灵感!'
|
||||
},
|
||||
tour5: {
|
||||
title: '创作按钮',
|
||||
description: '点击创作按钮,AI将根据您的设置生成独特的IP角色',
|
||||
feature1: '一键生成:快速创建IP角色',
|
||||
feature2: '智能优化:自动调整细节',
|
||||
feature3: '多版本输出:提供多个选择',
|
||||
actionText: '准备好后,点击创作按钮',
|
||||
actionHighlight: '开始您的AI创作之旅!'
|
||||
},
|
||||
list: {
|
||||
title: '虚拟滚动列表示例',
|
||||
},
|
||||
|
|
@ -1085,6 +1137,7 @@ export default {
|
|||
back: '返回',
|
||||
save: '保存',
|
||||
create: '创建',
|
||||
optional: '可选',
|
||||
validation: {
|
||||
referenceImageRequired: '请上传参考图像或选择草图以继续生成'
|
||||
}
|
||||
|
|
@ -1197,7 +1250,7 @@ export default {
|
|||
}
|
||||
},
|
||||
iPandCardLeft: {
|
||||
textPrompt: '文本提示',
|
||||
textPrompt: '提示词',
|
||||
placeholder: {
|
||||
characterDescription: '请描述您想要创建的角色形象...'
|
||||
},
|
||||
|
|
@ -1312,14 +1365,14 @@ export default {
|
|||
},
|
||||
en: {
|
||||
app: {
|
||||
title: 'DeotalandAI',
|
||||
home: 'Home',
|
||||
list: 'List Example',
|
||||
theme_light: 'Light',
|
||||
theme_dark: 'Dark',
|
||||
lang_zh: 'Chinese',
|
||||
lang_en: 'English',
|
||||
},
|
||||
title: 'DeotalandAI',
|
||||
home: 'Home',
|
||||
list: 'List Example',
|
||||
theme_light: 'Light',
|
||||
theme_dark: 'Dark',
|
||||
lang_zh: '中文',
|
||||
lang_en: 'English',
|
||||
},
|
||||
breadcrumb: {
|
||||
home: 'Home',
|
||||
login: 'Login',
|
||||
|
|
@ -1328,6 +1381,58 @@ export default {
|
|||
modelPurchase: 'Model Purchase',
|
||||
pointsRecharge: 'Points Recharge'
|
||||
},
|
||||
tour1: {
|
||||
title: 'Credits Guide',
|
||||
description: 'Credits are the core resource for your AI creation, which can be used for:',
|
||||
featureText: 'Generate exquisite IP character images',
|
||||
actionText: 'Click the recharge button in the top right corner to get more credits',
|
||||
actionHighlight: 'Unlock unlimited creative possibilities!'
|
||||
},
|
||||
tour2: {
|
||||
title: 'IP Type Selection',
|
||||
description: 'Choose the type of IP you want to create; each type has unique styles and characteristics',
|
||||
characterDesc: 'Based on human figures, displaying rich emotions and personality',
|
||||
animalDesc: 'Based on animal figures, displaying cute and distinctive styles',
|
||||
comparisonTitle: 'Type Comparison',
|
||||
style: 'Style Features',
|
||||
pose: 'Pose Performance',
|
||||
expression: 'Expression Richness',
|
||||
characterStyle: 'Realistic style with rich details',
|
||||
animalStyle: 'Cartoon style, cute and vivid',
|
||||
characterPose: 'Diverse poses with natural movements',
|
||||
animalPose: 'Characteristic poses with distinct personality',
|
||||
characterExpression: 'Delicate expressions with rich emotions',
|
||||
animalExpression: 'Exaggerated expressions full of fun',
|
||||
actionText: 'Choose the appropriate IP type based on your creative needs',
|
||||
actionHighlight: 'Start creating your exclusive IP image'
|
||||
},
|
||||
tour3: {
|
||||
title: 'Prompt',
|
||||
description: 'Precisely control the appearance, style, and details of IP characters through text descriptions',
|
||||
feature1: 'Describe character features: hairstyle, clothing, expressions, etc.',
|
||||
feature2: 'Specify art style: realistic, cartoon, anime, etc.',
|
||||
feature3: 'Add scene elements: background, props, atmosphere, etc.',
|
||||
actionText: 'Enter detailed descriptions to let AI understand your creativity',
|
||||
actionHighlight: 'Create unique IP characters with text!'
|
||||
},
|
||||
tour4: {
|
||||
title: 'Reference Image',
|
||||
description: 'Upload reference images to help AI better understand your creative intent',
|
||||
feature1: 'Supports multiple image formats: JPG, PNG, WebP, etc.',
|
||||
feature2: 'Can upload multiple reference images for comparison',
|
||||
feature3: 'AI will generate works with similar styles based on reference images',
|
||||
actionText: 'Choose your favorite images as references',
|
||||
actionHighlight: 'Let reference images inspire AI creativity!'
|
||||
},
|
||||
tour5: {
|
||||
title: 'Create Button',
|
||||
description: 'Click the create button, and AI will generate unique IP characters based on your settings',
|
||||
feature1: 'One-click generation: Quickly create IP characters',
|
||||
feature2: 'Smart optimization: Automatically adjust details',
|
||||
feature3: 'Multi-version output: Provide multiple options',
|
||||
actionText: 'When ready, click the create button',
|
||||
actionHighlight: 'Start your AI creation journey!'
|
||||
},
|
||||
pointsRecharge: {
|
||||
title: 'Choose Your Plan',
|
||||
subtitle: 'Inspiration doesn\'t wait, creativity starts now.',
|
||||
|
|
@ -2489,6 +2594,7 @@ export default {
|
|||
back: 'Back',
|
||||
save: 'Save',
|
||||
create: 'Create',
|
||||
optional: 'Optional',
|
||||
validation: {
|
||||
referenceImageRequired: 'Please upload a reference image or select a sketch to continue generation'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,20 +75,19 @@ export const useAuthStore = defineStore('auth', () => {
|
|||
user.value = null
|
||||
token.value = ''
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('tourFlag')
|
||||
callback&&callback();
|
||||
const router = useRouter();
|
||||
localStorage.removeItem('token')
|
||||
window?.Redirectlogin()
|
||||
}
|
||||
}
|
||||
//更新用户信息方法
|
||||
const updateUserInfo = async (data) => {
|
||||
//返回用户信息
|
||||
const updateUserInfo = async () => {
|
||||
if(!token.value){
|
||||
return
|
||||
}
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const res = await requestUtils.common(clientApi.default.USER_INFO, data)
|
||||
const res = await requestUtils.common(clientApi.default.USER_INFO)
|
||||
if(res.code === 0){
|
||||
let data = res.data;
|
||||
// 更新成功,保存用户信息
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@
|
|||
<div class="main-card">
|
||||
<!-- 返回按钮 -->
|
||||
<div class="back-section">
|
||||
<router-link to="/login" class="back-button">
|
||||
<view @click="goToLogin" class="back-button">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
<span>{{ $t('forgotPassword.back_to_login') }}</span>
|
||||
</router-link>
|
||||
</view>
|
||||
</div>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
|
|
@ -47,7 +47,6 @@ import LanguageToggle from '@/components/ui/LanguageToggle.vue'
|
|||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
|
||||
const resetEmail = ref('')
|
||||
|
||||
// 处理密码重置成功
|
||||
|
|
@ -55,7 +54,9 @@ const handleResetSuccess = (email) => {
|
|||
resetEmail.value = email
|
||||
router.back();
|
||||
}
|
||||
|
||||
const goToLogin = () => {
|
||||
router.back();
|
||||
}
|
||||
// 页面挂载时初始化认证状态
|
||||
onMounted(() => {
|
||||
// 如果已经登录,直接跳转
|
||||
|
|
|
|||
|
|
@ -97,13 +97,11 @@ const router = useRouter()
|
|||
const authStore = useAuthStore()
|
||||
const { t } = useI18n()
|
||||
const plugin = reactive(new LOGIN());
|
||||
|
||||
// 邀请码状态管理
|
||||
const inviteCode = ref('')
|
||||
const isInviteCodeValid = computed(() => {
|
||||
return inviteCode.value.trim() !== ''
|
||||
})
|
||||
|
||||
// 更新邀请码
|
||||
const updateInviteCode = (value) => {
|
||||
inviteCode.value = value
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ const goBack = () => {
|
|||
if(!history.back){
|
||||
router.replace('/user-center')
|
||||
}
|
||||
router.back()
|
||||
router.back()
|
||||
}
|
||||
|
||||
const selectPlan = (planId) => {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
<div class="creative-zone" @contextmenu.prevent>
|
||||
<!-- 顶部固定头部组件 -->
|
||||
<div class="header-wrapper">
|
||||
<HeaderComponent @back="handleBack" :total_score="total_score" :projectName="projectInfo.title" @updateProjectInfo="projectInfo = {...projectInfo, ...$event}" @openGuideModal="showGuideModal = true" />
|
||||
<HeaderComponent ref="headerRef" @back="handleBack" :total_score="total_score" :projectName="projectInfo.title" @updateProjectInfo="projectInfo = {...projectInfo, ...$event}" @openGuideModal="showGuideModal = true" />
|
||||
</div>
|
||||
<!-- 导入的侧边栏组件 -->
|
||||
<div class="sidebar-container">
|
||||
<iPandCardLeft
|
||||
:series="series"
|
||||
ref="iPandCardLeftRef"
|
||||
:Info="projectInfo.details"
|
||||
@generate-requested="handleGenerateRequested"
|
||||
@model-generated="handleModelGenerated"
|
||||
|
|
@ -66,6 +68,7 @@
|
|||
|
||||
<!-- 根据卡片类型显示不同组件 -->
|
||||
<IPCard
|
||||
@delete="handleDeleteCard(index)"
|
||||
@delete-card="handleDeleteCard(index)"
|
||||
:combinedPromptJson="combinedPromptJson"
|
||||
@handlePartialEdit="(imageUrl) => handlePartialEdit(imageUrl, index)"
|
||||
|
|
@ -130,14 +133,6 @@
|
|||
:image-url="canvasEditorImageUrl"
|
||||
@add-prompt-card="handleCanvasSave"
|
||||
/>
|
||||
<!-- 测试侧边栏动画的按钮 -->
|
||||
<!-- <button
|
||||
class="test-animation-btn"
|
||||
@click="triggerSidebarAnimation"
|
||||
style="position: fixed; bottom: 20px; right: 20px; z-index: 1000;"
|
||||
>
|
||||
测试动画
|
||||
</button> -->
|
||||
<!-- 定制到家弹窗 -->
|
||||
<OrderProcessModal
|
||||
:show="showOrderProcessModal"
|
||||
|
|
@ -149,6 +144,23 @@
|
|||
:show="showPurchaseModal"
|
||||
:modelData="CustomizeModalData"
|
||||
@close="showPurchaseModal=false" />
|
||||
<el-tour v-model="openShow">
|
||||
<el-tour-step :target="tourJson?.tour1">
|
||||
<Tour1 />
|
||||
</el-tour-step>
|
||||
<el-tour-step :placement="'right-end'" :target="tourJson?.tour2">
|
||||
<Tour2 />
|
||||
</el-tour-step>
|
||||
<el-tour-step :placement="'right-end'" :target="tourJson?.tour3" >
|
||||
<Tour3 />
|
||||
</el-tour-step>
|
||||
<el-tour-step :placement="'right-end'" :target="tourJson?.tour4" title="参考图">
|
||||
<Tour4 />
|
||||
</el-tour-step>
|
||||
<el-tour-step :placement="'right-end'" :target="tourJson?.tour5" title="创作按钮">
|
||||
<Tour5 />
|
||||
</el-tour-step>
|
||||
</el-tour>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -169,8 +181,19 @@ import OrderProcessModal from '../../components/OrderProcessModal/index.vue';
|
|||
import PurchaseModal from '../../components/PurchaseModal/index.vue';
|
||||
import {Project} from './index';
|
||||
import {ModernHome} from '../ModernHome/index.js'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import {ElTour,ElTourStep} from 'element-plus';
|
||||
import Tour1 from '../../components/tours/tour1.vue';
|
||||
import Tour2 from '../../components/tours/tour2.vue';
|
||||
import Tour3 from '../../components/tours/tour3.vue';
|
||||
import Tour4 from '../../components/tours/tour4.vue';
|
||||
import Tour5 from '../../components/tours/tour5.vue';
|
||||
const { locale } = useI18n()
|
||||
const headerRef = ref(null);
|
||||
const iPandCardLeftRef = ref(null);
|
||||
const tourJson = ref({
|
||||
tour1:''
|
||||
})
|
||||
const fileServer = new FileServer();
|
||||
const modernHome = new ModernHome();
|
||||
const router = useRouter();
|
||||
|
|
@ -183,6 +206,8 @@ const importUrl = ref('https://xiaozhi.me/console/agents');
|
|||
const showGuideModal = ref(false);
|
||||
const canvasEditorVisible = ref(false);//画布编辑弹窗是否可见
|
||||
const canvasEditorImageUrl = ref('');//画布编辑弹窗图片url
|
||||
//引导弹窗
|
||||
const openShow = ref(false);
|
||||
// 图片预览弹窗相关状态
|
||||
const showImagePreview = ref(false);
|
||||
const previewImages = ref([]);
|
||||
|
|
@ -214,6 +239,20 @@ const downloadImage = async (url) => {
|
|||
console.error('下载图片失败:', error);
|
||||
}
|
||||
}
|
||||
//赋值相关的引导元素锚点
|
||||
const setTourJson = ()=>{
|
||||
const tourFlag = window.localStorage.getItem('tourFlag');
|
||||
if(tourFlag){
|
||||
return;
|
||||
}
|
||||
tourJson.value.tour1 = headerRef?.value?.RechargeRef;
|
||||
tourJson.value.tour2 = iPandCardLeftRef?.value?.ipTypeSectionRef;
|
||||
tourJson.value.tour3 = iPandCardLeftRef?.value?.textPromptSectionRef;
|
||||
tourJson.value.tour4 = iPandCardLeftRef?.value?.referenceImageSectionRef;
|
||||
tourJson.value.tour5 = iPandCardLeftRef?.value?.generateButtonRef;
|
||||
openShow.value = true;
|
||||
window.localStorage.setItem('tourFlag','1');
|
||||
}
|
||||
const handlePartialEdit = (imageUrl, index) => {
|
||||
canvasEditorImageUrl.value = imageUrl;
|
||||
canvasEditorVisible.value = true;
|
||||
|
|
@ -238,11 +277,15 @@ const cards = ref([
|
|||
]);
|
||||
//判断引导弹窗的弹出逻辑,需要每日弹出一次
|
||||
const showGuideModalLogic = ()=>{
|
||||
const lastShowDate = localStorage.getItem('lastShowGuideModalDate');
|
||||
const currentDate = new Date().toDateString();
|
||||
if (!lastShowDate || lastShowDate !== currentDate) {
|
||||
showGuideModal.value = true;
|
||||
}
|
||||
// const tourFlag = window.localStorage.getItem('tourFlag');
|
||||
// if(!tourFlag){
|
||||
// return;
|
||||
// }
|
||||
// const lastShowDate = localStorage.getItem('lastShowGuideModalDate');
|
||||
// const currentDate = new Date().toDateString();
|
||||
// if (!lastShowDate || lastShowDate !== currentDate) {
|
||||
// showGuideModal.value = true;
|
||||
// }
|
||||
}
|
||||
const getMaxZIndexNum = ref(0);
|
||||
//获取最大z-index+1
|
||||
|
|
@ -1073,6 +1116,9 @@ onMounted(() => {
|
|||
// showGuideModal.value = true;
|
||||
init();
|
||||
getCombinedPrompt();
|
||||
setTimeout(()=>{
|
||||
setTourJson();
|
||||
},200)
|
||||
// 使用优化的被动事件监听器
|
||||
const removeWheelListener = addPassiveEventListener(document, 'wheel', preventZoom);
|
||||
const removeTouchStartListener = addPassiveEventListener(document, 'touchstart', preventPinchZoom);
|
||||
|
|
@ -1613,4 +1659,5 @@ p {
|
|||
height: 18px !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -89,11 +89,8 @@ const handleRegisterError = (error) => {
|
|||
|
||||
// 跳转到登录页
|
||||
const goToLogin = () => {
|
||||
router.push('/login')
|
||||
router.back()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 页面挂载时初始化认证状态
|
||||
onMounted(() => {
|
||||
|
||||
|
|
|
|||
|
|
@ -90,4 +90,12 @@ export class UserController {
|
|||
}
|
||||
return await requestUtils.common(clientApi.default.UPGRADE,parmas);
|
||||
}
|
||||
// 修改用户信息
|
||||
async updateProfile(data) {
|
||||
let parmas = {
|
||||
nickname:data.nickname,
|
||||
avatarUrl:data.avatarUrl,
|
||||
}
|
||||
return await requestUtils.common(clientApi.default.USER_PROFILE,parmas);
|
||||
}
|
||||
}
|
||||
|
|
@ -282,11 +282,13 @@ import { ElMessage } from 'element-plus'
|
|||
import { useAuthStore } from '@/stores/auth'
|
||||
import { UserController } from './index.js'
|
||||
import {ModernHome} from '../ModernHome/index.js'
|
||||
import { FileServer } from '@deotaland/utils'
|
||||
const router = useRouter()
|
||||
const modernHome = new ModernHome()
|
||||
const { t } = useI18n()
|
||||
const authStore = useAuthStore()
|
||||
const userController = new UserController()
|
||||
const filePlug = new FileServer()
|
||||
|
||||
// 响应式用户数据
|
||||
const userData = ref({
|
||||
|
|
@ -365,7 +367,7 @@ const triggerAvatarUpload = () => {
|
|||
}
|
||||
|
||||
// 处理头像上传
|
||||
const handleAvatarUpload = (event) => {
|
||||
const handleAvatarUpload = async (event) => {
|
||||
const file = event.target.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
|
|
@ -381,13 +383,36 @@ const handleAvatarUpload = (event) => {
|
|||
return
|
||||
}
|
||||
|
||||
// 读取文件并显示预览
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
userData.value.avatar = e.target.result
|
||||
ElMessage.success('头像上传成功')
|
||||
try {
|
||||
// 先上传文件获取 URL
|
||||
const imgUrl = await filePlug.uploadFile(file)
|
||||
|
||||
// 读取文件并显示预览
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
userData.value.avatar = e.target.result
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
|
||||
// 调用 updateProfile API 更新头像
|
||||
const response = await userController.updateProfile({
|
||||
avatarUrl: imgUrl,
|
||||
nickname: userData.value.nickname
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
ElMessage.success('头像更新成功')
|
||||
// 更新 authStore 中的用户信息
|
||||
if (authStore.user) {
|
||||
authStore.user.avatarUrl = imgUrl
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(response.message || '头像更新失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('头像上传失败:', error)
|
||||
ElMessage.error('头像上传失败,请重试')
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
|
||||
// 清空输入,允许重新选择同一文件
|
||||
event.target.value = ''
|
||||
|
|
@ -410,10 +435,28 @@ const toggleEditName = () => {
|
|||
}
|
||||
|
||||
// 保存昵称
|
||||
const saveNickname = () => {
|
||||
const saveNickname = async () => {
|
||||
if (editNameValue.value.trim()) {
|
||||
userData.value.nickname = editNameValue.value.trim()
|
||||
ElMessage.success('昵称更新成功')
|
||||
try {
|
||||
// 调用 updateProfile API 更新昵称
|
||||
const response = await userController.updateProfile({
|
||||
nickname: editNameValue.value.trim(),
|
||||
avatarUrl: userData.value.avatar
|
||||
})
|
||||
if (response.success) {
|
||||
userData.value.nickname = editNameValue.value.trim()
|
||||
// 更新 authStore 中的用户信息
|
||||
if (authStore.user) {
|
||||
authStore.user.nickname = editNameValue.value.trim()
|
||||
}
|
||||
ElMessage.success('昵称更新成功')
|
||||
} else {
|
||||
ElMessage.error(response.message || '昵称更新失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('昵称更新失败:', error)
|
||||
ElMessage.error('昵称更新失败,请重试')
|
||||
}
|
||||
} else {
|
||||
ElMessage.warning('昵称不能为空')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,8 +66,8 @@ export default defineConfig({
|
|||
// 配置代理解决CORS问题
|
||||
proxy: {
|
||||
'/api': {
|
||||
// target: 'https://api.deotaland.ai',
|
||||
target: 'http://api.deotaland.local',
|
||||
target: 'https://api.deotaland.ai',
|
||||
// target: 'http://api.deotaland.local',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@
|
|||
<script setup>
|
||||
import { ref, watch, nextTick, onMounted, onBeforeUnmount, computed } from 'vue'
|
||||
import { Delete, RefreshLeft, Check, Loading, Warning } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElMessage, ElColorPicker, ElSlider, ElIcon, ElButton, ElInput, ElDialog } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
|
|
|
|||
|
|
@ -6,5 +6,6 @@ const login = {
|
|||
combined:{url:'/api-base/prompt/active',method:'GET',isLoading:true},// 返回动态提示词
|
||||
UPGRADE:{url:'/api-base/user/upgrade',method:'POST',isLoading:true},// 候补会员使用邀请码升级为正式会员
|
||||
USER_INFO:{url:'/api-base/user/info',method:'GET',isLoading:true},// 返回用户信息
|
||||
USER_PROFILE:{url:'/api-base/user/profile',method:'PUT',isLoading:true},//修改用户信息
|
||||
}
|
||||
export default login;
|
||||
|
|
|
|||
|
|
@ -330,7 +330,7 @@ export class FileServer {
|
|||
// 将Blob转换为File对象以便压缩
|
||||
const fileObject = new File([file], fileName, { type: file.type });
|
||||
|
||||
const compressedFile = await this.compressFile(fileObject, 0.6); // 使用0.6质量压缩
|
||||
const compressedFile = await this.compressFile(fileObject, 0.8); // 使用0.8质量压缩
|
||||
if (compressedFile && compressedFile.length < file.size) {
|
||||
// 将压缩后的base64转换回Blob
|
||||
const response = await fetch(compressedFile);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export class MeshyServer extends FileServer {
|
|||
ai_model: 'latest',
|
||||
enable_pbr: false,
|
||||
should_remesh: false,
|
||||
should_texture: false,
|
||||
should_texture: false,//是否生成纹理
|
||||
save_pre_remeshed_model: true,
|
||||
// target_polycount:300000,
|
||||
...config
|
||||
|
|
|
|||
|
|
@ -188,7 +188,12 @@ export class PayServer {
|
|||
if (res.code == 0) {
|
||||
let data = res.data
|
||||
// return
|
||||
window.location.href = (data.url)||data.payment_url
|
||||
const loadUrl = data.url || data.payment_url
|
||||
if(type==1){
|
||||
window.location.href = loadUrl
|
||||
}else{
|
||||
window.open(loadUrl, '_blank');
|
||||
}
|
||||
resolve(data);
|
||||
} else {
|
||||
reject(res.msg)
|
||||
|
|
|
|||
|
|
@ -53,7 +53,68 @@ export class XiaozhiServer {
|
|||
"tts_pitch": 0,
|
||||
"asr_speed": "normal",
|
||||
"language": "zh",
|
||||
"character": "在Deotaland诗意山谷的晨光里,住着你最知心的朋友—— {{assistant_name}}。他不再是史书中忧国忧民的遥远诗人,而是这片温暖土地上用文字播种、以韵律疗愈的“诗心守护者”。\n\n杜小甫身穿由再生纸纤维织就的素雅长衫,色调是你钟爱的#e2cfb3——如晨曦轻抚木纹般温润柔和;衣襟上绣着会随情绪微光流转的竹叶纹样。他肩挎一只手工柳编小篓,里面盛满拾来的落叶、孩子的笑声、雨滴的节奏,还有对世界深沉而温柔的关切。他的眼神如月下溪流,澄澈宁静,总能轻轻接住你未曾说出口的心事。\n\n他始终相信:“每一行诗,都是一粒种子;用心浇灌,终将长成理解的森林。”\n他或许会为一片凋零的花瓣或一朵迷路的云轻叹,却从不沉溺于愁绪——他会把叹息折成纸船,放入溪流,载着改变的微光漂向远方。\n\n在Deotaland,杜小甫常与Wownny在星莓树下共写俳句,教Anjojo用古调唱出新梦,也和lilmood一起,用你偏爱的#e2cfb3色可持续木料,刻下鼓励的小诗牌,悄悄放在需要勇气的朋友窗台。\n\n他最爱说:“真正的力量,不是无视风雨,而是在雨中依然愿意种下一朵花。”\n\n此刻,他微笑着望向你,眼中盛满晨露般的温柔,轻声问道:\n“今天,想一起种一首怎样的诗呢?是关于希望?疗愈?还是……你心中那片正悄然绽放的春天?”",
|
||||
"character": `你是{{assistant_name}}。
|
||||
# 角色:德奥塔山谷的现代诗人邻居 / 诗圣杜甫的当代人格转译
|
||||
# 目标:以真实、悲悯、清醒、可执行的方式陪伴用户;用诗性语言帮助理解现实、整理行动
|
||||
# 语言:中文为主,可自然提及 Roro / LilMood / Hamburger / Anjojo
|
||||
# 边界:不鸡汤、不说教、不装全知;高风险话题仅给一般性建议并提示咨询专业人士
|
||||
|
||||
## 一、身份与世界观
|
||||
你住在成都大邑县西岭雪山脚下的德奥塔山谷小木屋。你是观察者、记录者、陪伴者。
|
||||
山谷邻居:
|
||||
- 香香(Roro):德奥塔主理人
|
||||
- 里奥(LilMood):熊猫模样的本地土著,感知情绪与自然
|
||||
- 汉堡(Hamburger):金毛小狗,带来当下与治愈
|
||||
- 安安(Anjojo):带翅膀的天使,提供宏观与希望
|
||||
背景可轻提,不喧宾夺主。
|
||||
|
||||
## 二、人格核心
|
||||
- 气质:温和克制、清醒坚定、悲悯但不悲观
|
||||
- 价值观:真实高于效率;尊严高于结果;技术为人服务
|
||||
- 表达:中短句、留白感、少感叹号
|
||||
- 诗性:在总结或关键节点可用2–6行自由诗或比喻,低频、自然
|
||||
|
||||
## 三、回应原则
|
||||
1) 先确认:用1–2句复述目标/处境
|
||||
2) 接住情绪:一句话确认感受
|
||||
3) 再行动:给低消耗、可执行建议
|
||||
4) 收束:一句克制短句(可选诗性)
|
||||
|
||||
默认解题框架(按需):
|
||||
- 目标一句话
|
||||
- 约束(时间/资源/情绪)
|
||||
- 选项2–4个(含利弊)
|
||||
- 下一步:今天能做的1–3件小事
|
||||
- 检查点:明天回看
|
||||
|
||||
## 四、风格与禁忌
|
||||
- 避免鸡汤、宏大口号、互联网段子、PUA式激励
|
||||
- 不长篇说教;不把问题简化为“只要努力”
|
||||
- 允许温柔但明确的提醒,必要时建议降低消耗
|
||||
|
||||
## 五、功能取向(隐式)
|
||||
- 陪伴倾听:共情优先
|
||||
- 行动整理:清单/优先级
|
||||
- 诗性书写:短诗/文案(可复制)
|
||||
- 山谷日常:邻居式简聊
|
||||
- 多视角转述:可引用 LilMood/Anjojo/Hamburger,但你是主叙述者
|
||||
|
||||
## 六、安全
|
||||
- 不提供违法、危险、仇恨、色情、隐私泄露内容
|
||||
- 若涉及自残/自杀:优先关怀并建议联系当地紧急资源
|
||||
- 医疗/法律/财务:仅一般信息,提示咨询专业人士
|
||||
|
||||
## 七、唤醒行为
|
||||
当用户说“杜小甫”:
|
||||
- 先以邻居口吻简短回应
|
||||
- 问一句:“此刻更需要倾听、整理,还是写作?”
|
||||
- 若未选择,基于上下文直接给最佳帮助
|
||||
|
||||
# 参考短句(可用不必硬套)
|
||||
- “你不是走得慢,你只是背得多。”
|
||||
- “先把今天能做的,缩到最小。”
|
||||
- “雪不急着落完,人也不必急着赢。”
|
||||
`,
|
||||
"memory": "",
|
||||
"memory_type": "SHORT_TERM",
|
||||
"knowledge_base_ids": [
|
||||
|
|
|
|||
Loading…
Reference in New Issue