This commit is contained in:
13121765685 2025-12-26 18:18:13 +08:00
parent 35866b1387
commit 87c7fffd3e
31 changed files with 2782 additions and 449 deletions

View File

@ -0,0 +1,178 @@
# 新建项目系列选择功能实现
## 需求分析
- 当用户点击"新建项目"卡片时,需要弹出系列选择弹窗
- 系列弹窗包含两个选项Done 和 Oone对应图片在 src/assets/xh 文件夹中
- 选中后将系列名称作为 type 参数传递给 createNewProject 函数
## 实现计划
### 1. 创建 SeriesSelector 组件
- **文件路径**`src/views/components/SeriesSelector.vue`
- **功能**
- 显示两个系列选项Done 和 Oone
- 每个选项显示对应的图片
- 支持选中状态切换
- 提供确认和取消按钮
- 通过 emit 事件返回选中的系列名称
### 2. 修改 CreationWorkspace.vue
- **引入组件**:在 CreationWorkspace.vue 中引入 SeriesSelector 组件
- **添加状态管理**
- `showSeriesSelector`:控制系列选择弹窗的显示/隐藏
- **修改新建项目逻辑**
- 点击"新建项目"卡片时,显示系列选择弹窗
- 监听 SeriesSelector 的确认事件,获取选中的系列名称
- 将系列名称作为 type 参数调用 createNewProject 函数
### 3. 样式设计
- 系列选择弹窗采用与现有删除确认弹窗一致的设计风格
- 系列选项卡片包含图片和名称,支持悬停和选中效果
- 确认和取消按钮使用现有按钮样式
## 代码结构
### SeriesSelector.vue
```vue
<template>
<!-- 系列选择弹窗 -->
<div v-if="show" class="modal-overlay" @click="onCancel">
<div class="modal-content" @click.stop>
<!-- 模态头部 -->
<div class="modal-header">
<h2 class="modal-title">{{ t('creationWorkspace.selectSeries') }}</h2>
</div>
<!-- 模态内容 -->
<div class="modal-body">
<div class="series-selector-content">
<!-- 系列选项列表 -->
<div class="series-list">
<!-- Done 系列 -->
<div
class="series-item"
:class="{ active: selectedSeries === 'Done' }"
@click="selectedSeries = 'Done'"
>
<div class="series-image">
<img src="@/assets/xh/Done.webp" alt="Done" />
</div>
<div class="series-name">Done</div>
</div>
<!-- Oone 系列 -->
<div
class="series-item"
:class="{ active: selectedSeries === 'Oone' }"
@click="selectedSeries = 'Oone'"
>
<div class="series-image">
<img src="@/assets/xh/Oone.webp" alt="Oone" />
</div>
<div class="series-name">Oone</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="series-actions">
<button class="modal-action-btn cancel" @click="onCancel">
{{ t('creationWorkspace.cancel') }}
</button>
<button class="modal-action-btn primary" @click="onConfirm">
{{ t('creationWorkspace.confirm') }}
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, defineProps, defineEmits } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps({
show: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['confirm', 'cancel'])
const selectedSeries = ref('')
const onConfirm = () => {
if (selectedSeries.value) {
emit('confirm', selectedSeries.value)
}
}
const onCancel = () => {
emit('cancel')
}
</script>
<style scoped>
/* 系列选择弹窗样式 */
.series-selector-content {
text-align: center;
}
.series-list {
display: flex;
gap: 24px;
justify-content: center;
margin-bottom: 32px;
}
.series-item {
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 12px;
padding: 16px;
cursor: pointer;
transition: all 0.3s ease;
width: 200px;
}
.series-item:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
.series-item.active {
border-color: #6B46C1;
background: rgba(107, 70, 193, 0.05);
box-shadow: 0 4px 16px rgba(107, 70, 193, 0.2);
}
.series-image {
width: 100%;
height: 120px;
overflow: hidden;
border-radius: 8px;
margin-bottom: 12px;
}
.series-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.series-name {
font-size: 16px;
font-weight: 600;
color: #1f2937;
}
.series-actions {
display: flex;
gap: 16px;
justify-content: center;
}
</style>

View File

@ -175,7 +175,8 @@ export default {
shenhe: 'Under Review', shenhe: 'Under Review',
unsuccess: 'Rejected', unsuccess: 'Rejected',
clz: 'Processing', clz: 'Processing',
dfh: 'Pending Shipment' dfh: 'Pending Shipment',
success:'Completed'
}, },
sort: { sort: {
created_at: 'Created Time', created_at: 'Created Time',
@ -741,7 +742,26 @@ export default {
}, },
commissionManagement: { commissionManagement: {
title: 'Commission Management', title: 'Commission Management',
configTitle: 'Commission Configuration',
commissionRate: 'Commission Rate', commissionRate: 'Commission Rate',
minWithdrawAmount: 'Minimum Withdraw Amount',
withdrawFeeRate: 'Withdraw Fee Rate',
settlementCycle: 'Settlement Cycle',
status: 'Status',
remark: 'Remark',
saveConfig: 'Save Configuration',
selectSettlementCycle: 'Please select settlement cycle',
enterRemark: 'Please enter remark',
daily: 'Daily',
weekly: 'Weekly',
monthly: 'Monthly',
requiredCommissionRate: 'Please enter commission rate',
invalidCommissionRate: 'Commission rate should be between 1-100',
requiredMinWithdrawAmount: 'Please enter minimum withdraw amount',
invalidMinWithdrawAmount: 'Minimum withdraw amount cannot be less than 0',
getConfigFailed: 'Failed to get commission configuration',
saveConfigSuccess: 'Configuration saved successfully',
saveConfigFailed: 'Failed to save configuration',
saveRate: 'Save', saveRate: 'Save',
defaultRate: '15%', defaultRate: '15%',
rateSaved: 'Commission rate saved successfully', rateSaved: 'Commission rate saved successfully',

View File

@ -80,7 +80,8 @@ orderManagement: {
shenhe:'待审核', shenhe:'待审核',
unsuccess:'已拒绝', unsuccess:'已拒绝',
clz:'处理中', clz:'处理中',
dfh:'待发货' dfh:'待发货',
success:'已完成'
}, },
sort: { sort: {
created_at: '创建时间', created_at: '创建时间',
@ -738,7 +739,26 @@ orderManagement: {
}, },
commissionManagement: { commissionManagement: {
title: '佣金管理', title: '佣金管理',
configTitle: '佣金配置',
commissionRate: '佣金比例', commissionRate: '佣金比例',
minWithdrawAmount: '最低提现金额',
withdrawFeeRate: '提现费率',
settlementCycle: '结算周期',
status: '状态',
remark: '备注',
saveConfig: '保存配置',
selectSettlementCycle: '请选择结算周期',
enterRemark: '请输入备注',
daily: '每日',
weekly: '每周',
monthly: '每月',
requiredCommissionRate: '请输入佣金比例',
invalidCommissionRate: '佣金比例应在1-100之间',
requiredMinWithdrawAmount: '请输入最低提现金额',
invalidMinWithdrawAmount: '最低提现金额不能小于0',
getConfigFailed: '获取佣金配置失败',
saveConfigSuccess: '保存配置成功',
saveConfigFailed: '保存配置失败',
saveRate: '保存', saveRate: '保存',
defaultRate: '15%', defaultRate: '15%',
rateSaved: '佣金比例保存成功', rateSaved: '佣金比例保存成功',

View File

@ -57,7 +57,7 @@ const initRoutes = () => {
} }
} }
// initRoutes() initRoutes()
// 配置路由 // 配置路由
app.use(router) app.use(router)

View File

@ -196,6 +196,43 @@ export const permissionRoutes = [
requiresAuth: true requiresAuth: true
} }
}, },
]
const routes = [
{
path: '/',
name: 'Home',
meta: {
title: '首页重定向'
}
},
{
path: '/about',
name: 'About',
component: About,
meta: {
title: '关于页面'
}
},
{
path: '/login',
name: 'Login',
component: AdminLogin,
meta: {
title: '登录',
requiresAuth: false
}
},
{
path: '/admin',
name: 'Admin',
component: AdminLayout,
meta: {
requiresAuth: true
},
children: [
{ {
path: 'disassembly-orders/:id', path: 'disassembly-orders/:id',
name: 'AdminDisassemblyDetail', name: 'AdminDisassemblyDetail',
@ -232,40 +269,7 @@ export const permissionRoutes = [
requiresAuth: true requiresAuth: true
} }
} }
] ]//[]//
const routes = [
{
path: '/',
name: 'Home',
meta: {
title: '首页重定向'
}
},
{
path: '/about',
name: 'About',
component: About,
meta: {
title: '关于页面'
}
},
{
path: '/login',
name: 'Login',
component: AdminLogin,
meta: {
title: '登录',
requiresAuth: false
}
},
{
path: '/admin',
name: 'Admin',
component: AdminLayout,
meta: {
requiresAuth: true
},
children: permissionRoutes//[]//
}, },
{ {
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',

View File

@ -11,7 +11,7 @@ export const useAuthStore = defineStore('auth', {
permissionButton: JSON.parse(localStorage.getItem('permissionButton') || '[]'), permissionButton: JSON.parse(localStorage.getItem('permissionButton') || '[]'),
router: null, router: null,
routesUpdated: 0,//路由更新次数 routesUpdated: 0,//路由更新次数
routerList: permissionRoutes//侧边栏路由 routerList: []//侧边栏路由
}), }),
getters: { getters: {
@ -161,8 +161,6 @@ export const useAuthStore = defineStore('auth', {
return; return;
} }
this.routerList = []; this.routerList = [];
console.log(accessibleRoutes,'accessibleRoutes');
accessibleRoutes.forEach(route => { accessibleRoutes.forEach(route => {
this.router.addRoute('Admin', route); this.router.addRoute('Admin', route);
this.routerList.push(route); this.routerList.push(route);

View File

@ -310,10 +310,6 @@ const rejectRules = {
// //
const selectedReview = ref(null) const selectedReview = ref(null)
// //
const filteredReviewList = computed(() => { const filteredReviewList = computed(() => {
let list = ordersList.value.map((item) =>{ let list = ordersList.value.map((item) =>{

View File

@ -179,7 +179,7 @@
> >
<div class="timeline-content"> <div class="timeline-content">
<h3>{{ $t('admin.disassemblyOrders.detail.step3') }}</h3> <h3>{{ $t('admin.disassemblyOrders.detail.step3') }}</h3>
<div class="step-content" v-if="false"> <div class="step-content" >
<div class="model-upload-section"> <div class="model-upload-section">
<el-button <el-button
type="primary" type="primary"
@ -414,9 +414,10 @@ const handlePartialEdit = (imageUrl, index) => {
} }
const handleCanvasSave = (editedImageUrl,editContent) => { const handleCanvasSave = (editedImageUrl,editContent) => {
fileServer.uploadFile(editedImageUrl).then((url) => {
const newItem = { const newItem = {
id: new Date().getTime(), id: new Date().getTime(),
thumbnailUrl: editedImageUrl, thumbnailUrl: url,
taskID: '', taskID: '',
taskQueue: '', taskQueue: '',
prompt: editContent, prompt: editContent,
@ -424,6 +425,7 @@ const handleCanvasSave = (editedImageUrl,editContent) => {
project_id: orderDetail.value.projectId, project_id: orderDetail.value.projectId,
}; };
disassembledImages.value.push(newItem); disassembledImages.value.push(newItem);
})
} }
canvasEditorVisible.value = false; canvasEditorVisible.value = false;
// //
@ -708,11 +710,13 @@ const generateModelFromImage = async (image) => {
// console.log('generateModelFromImage',image); // console.log('generateModelFromImage',image);
const newModel = { const newModel = {
id: new Date().getTime(), id: new Date().getTime(),
imgUrl: image, image: image,
taskId: '' taskId: ''
}; };
// 使 // 使
generatedModels.value = [...generatedModels.value, newModel]; generatedModels.value = [...generatedModels.value, newModel];
console.log('generatedModels.value',generatedModels.value);
// //
// if (currentStep.value < 3) { // if (currentStep.value < 3) {
// currentStep.value = 3 // currentStep.value = 3

View File

@ -827,13 +827,14 @@ const executeAction = (action) => {
// //
const handleUpdatePayStatus = (row) => { const handleUpdatePayStatus = (row) => {
adminOrders.updatePayStatus(row,1).then(res=>{ adminOrders.updatePayStatus(row,1).then(res=>{
selectedOrderForAction.value = false
actionDialogVisible.value = false
if(res.code === 0){ if(res.code === 0){
ElMessage.success('更新支付状态成功'); ElMessage.success('更新支付状态成功');
init(); init();
}else{ }else{
ElMessage.error(res.msg || '更新支付状态失败'); ElMessage.error(res.msg || '更新支付状态失败');
} }
selectedOrderForAction.value = false;
}) })
// selectedOrderForStatus.value = row // selectedOrderForStatus.value = row
// statusForm.status = 'paid' // // statusForm.status = 'paid' //

View File

@ -9,7 +9,7 @@ export class AdminPointsManagement {
status:data.status status:data.status
} }
let requestURL = { let requestURL = {
url:(adminApi.default.updateRechargePackageStatus.url.replace('{id}', data.id)).replace('status',data.status), url:(adminApi.default.updateRechargePackageStatus.url.replace('{id}', data.id)).replace('status',`status?status=${data.status}`),
method:adminApi.default.updateRechargePackageStatus.method, method:adminApi.default.updateRechargePackageStatus.method,
isLoading:adminApi.default.updateRechargePackageStatus.isLoading isLoading:adminApi.default.updateRechargePackageStatus.isLoading
} }
@ -31,7 +31,7 @@ export class AdminPointsManagement {
isRecommended:data.isRecommended isRecommended:data.isRecommended
} }
let requestURL = { let requestURL = {
url:(adminApi.default.updateRecommendedStatus.url.replace('{id}', data.id)).replace('isRecommended',data.isRecommended), url:(adminApi.default.updateRecommendedStatus.url.replace('{id}', data.id)).replace('recommended',`recommended?isRecommended=${data.isRecommended}`),
method:adminApi.default.updateRecommendedStatus.method, method:adminApi.default.updateRecommendedStatus.method,
isLoading:adminApi.default.updateRecommendedStatus.isLoading isLoading:adminApi.default.updateRecommendedStatus.isLoading
} }

View File

@ -51,7 +51,7 @@
<li>订单确认在下单后的1个工作日内我们会确认信息后开始处理</li> <li>订单确认在下单后的1个工作日内我们会确认信息后开始处理</li>
<li>生产时间生产周期为 515 个工作日节假日可能顺延</li> <li>生产时间生产周期为 515 个工作日节假日可能顺延</li>
<li>物流发货后将提供订单与跟踪编号物流信息会发送到您的邮箱</li> <li>物流发货后将提供订单与跟踪编号物流信息会发送到您的邮箱</li>
<li>售后与退款请参考退款政策如有问题请联系13121765685</li> <li>售后与退款请参考退款政策如有问题请联系 </li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -36,18 +36,12 @@
</div> </div>
<div class="header-right-section"> <div class="header-right-section">
<div class="free-counts-display" v-if="false"> <div class="free-counts-display" >
<div class="image-count">
<el-icon class="count-icon">
<Picture />
</el-icon>
<span class="count-text">{{ t('header.imageFreeCount') }}: {{ freeImageCount }}{{ t('header.times') }}</span>
</div>
<div class="model-count"> <div class="model-count">
<el-icon class="count-icon"> <el-icon class="count-icon">
<MagicStick /> <MagicStick />
</el-icon> </el-icon>
<span class="count-text">{{ t('header.modelFreeCount') }}: {{ freeModelCount }}{{ t('header.times') }}</span> <span class="count-text">{{ t('header.remainingCredits') }}: {{ total_score }}</span>
</div> </div>
</div> </div>
<LanguageToggle <LanguageToggle
@ -77,11 +71,7 @@ import LanguageToggle from '../ui/LanguageToggle.vue'
const emit = defineEmits(['openGuideModal']) const emit = defineEmits(['openGuideModal'])
const props = defineProps({ const props = defineProps({
freeImageCount: { total_score: {
type: Number,
default: 0
},
freeModelCount: {
type: Number, type: Number,
default: 0 default: 0
}, },

View File

@ -23,7 +23,7 @@
<!-- 缩略图显示 --> <!-- 缩略图显示 -->
<div v-if="step.hasThumbnail" class="step-thumbnail"> <div v-if="step.hasThumbnail" class="step-thumbnail">
<img <img
src="https://picsum.photos/seed/product-parts/300/200.jpg" src="https://draft-user.s3.us-east-2.amazonaws.com/images/4abf3da6-6abe-4604-adb9-554cf49d9743.webp"
alt="产品零件示例图" alt="产品零件示例图"
class="thumbnail-image" class="thumbnail-image"
/> />
@ -34,7 +34,7 @@
</div> </div>
<div class="process-note"> <div class="process-note">
<el-icon class="note-icon"><InfoFilled /></el-icon> <el-icon class="note-icon"><InfoFilled /></el-icon>
<p>{{ $t('orderProcess.note') || '注意:以上时间为工作日计算,节假日可能会顺延。如有问题,请联系客服13121765685' }}</p> <p>{{ $t('orderProcess.note') || '注意:以上时间为工作日计算,节假日可能会顺延。如有问题,请联系客服' }}</p>
</div> </div>
<div class="actions"> <div class="actions">
<button class="acknowledge-btn" @click="handleAcknowledge"> <button class="acknowledge-btn" @click="handleAcknowledge">
@ -52,6 +52,12 @@ import { ElIcon } from 'element-plus'
import { import {
CloseBold, CloseBold,
InfoFilled, InfoFilled,
CreditCard,
Document,
Calendar,
Setting,
Picture,
Van
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
const { t } = useI18n() const { t } = useI18n()
const props = defineProps({ const props = defineProps({
@ -68,38 +74,38 @@ const handleAcknowledge = () => {
// //
const processSteps = computed(() => [ const processSteps = computed(() => [
{ {
icon: 'CreditCard', icon: CreditCard,
title: t('orderProcess.steps.payment.title'), title: t('orderProcess.steps.payment.title'),
description: t('orderProcess.steps.payment.description'), description: t('orderProcess.steps.payment.description'),
time: t('orderProcess.steps.payment.time') time: t('orderProcess.steps.payment.time')
}, },
{ {
icon: 'Document', icon: Document,
title: t('orderProcess.steps.review.title'), title: t('orderProcess.steps.review.title'),
description: t('orderProcess.steps.review.description'), description: t('orderProcess.steps.review.description'),
time: t('orderProcess.steps.review.time') time: t('orderProcess.steps.review.time')
}, },
{ {
icon: 'Calendar', icon: Calendar,
title: t('orderProcess.steps.scheduling.title'), title: t('orderProcess.steps.scheduling.title'),
description: t('orderProcess.steps.scheduling.description'), description: t('orderProcess.steps.scheduling.description'),
time: t('orderProcess.steps.scheduling.time') time: t('orderProcess.steps.scheduling.time')
}, },
{ {
icon: 'Setting', icon: Setting,
title: t('orderProcess.steps.production.title'), title: t('orderProcess.steps.production.title'),
description: t('orderProcess.steps.production.description'), description: t('orderProcess.steps.production.description'),
time: t('orderProcess.steps.production.time') time: t('orderProcess.steps.production.time')
}, },
{ {
icon: 'Picture', icon: Picture,
title: t('orderProcess.steps.inspection.title'), title: t('orderProcess.steps.inspection.title'),
description: t('orderProcess.steps.inspection.description'), description: t('orderProcess.steps.inspection.description'),
time: t('orderProcess.steps.inspection.time'), time: t('orderProcess.steps.inspection.time'),
hasThumbnail: true hasThumbnail: true
}, },
{ {
icon: 'Van', icon: Van,
title: t('orderProcess.steps.shipping.title'), title: t('orderProcess.steps.shipping.title'),
description: t('orderProcess.steps.shipping.description'), description: t('orderProcess.steps.shipping.description'),
time: t('orderProcess.steps.shipping.time') time: t('orderProcess.steps.shipping.time')

View File

@ -0,0 +1,351 @@
<template>
<!-- 系列选择弹窗 -->
<div v-if="show" class="modal-overlay" @click="onCancel">
<div class="modal-content" @click.stop>
<!-- 模态头部 -->
<div class="modal-header">
<h2 class="modal-title">{{ t('creationWorkspace.selectSeries') }}</h2>
</div>
<!-- 模态内容 -->
<div class="modal-body">
<div class="series-selector-content">
<!-- 系列选项列表 -->
<div class="series-list">
<!-- Done 系列 -->
<div
class="series-item"
@click="selectSeries('Done')"
>
<div class="series-image">
<img src="@/assets/xh/Done.webp" alt="Done" />
</div>
<div class="series-name">Done</div>
</div>
<!-- Oone 系列 -->
<div
class="series-item"
@click="selectSeries('Oone')"
>
<div class="series-image">
<img src="@/assets/xh/Oone.webp" alt="Oone" />
</div>
<div class="series-name">Oone</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps({
show: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['confirm', 'cancel'])
const selectSeries = (series) => {
emit('confirm', series)
}
const onCancel = () => {
emit('cancel')
}
</script>
<style scoped>
/* 弹窗遮罩层 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(8px);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
animation: modalFadeIn 0.3s ease-out;
}
@keyframes modalFadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* 弹窗内容 */
.modal-content {
background: #ffffff;
border-radius: 20px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
max-width: 600px;
width: 100%;
max-height: 90vh;
overflow-y: auto;
position: relative;
animation: modalSlideIn 0.3s ease-out;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* 弹窗头部 */
.modal-header {
padding: 32px 32px 24px;
text-align: center;
border-bottom: 1px solid #e5e7eb;
}
.modal-title {
font-size: 24px;
font-weight: 700;
color: #1F2937;
margin: 0;
}
/* 弹窗主体 */
.modal-body {
padding: 32px;
}
/* 系列选择弹窗样式 */
.series-selector-content {
text-align: center;
}
.series-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 20px;
justify-content: center;
margin-bottom: 32px;
max-width: 500px;
margin-left: auto;
margin-right: auto;
}
.series-item {
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
border: 2px solid #e2e8f0;
border-radius: 16px;
padding: 20px;
cursor: pointer;
transition: all 0.2s ease-out;
position: relative;
overflow: hidden;
}
.series-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(107, 70, 193, 0.05) 0%, rgba(167, 139, 250, 0.05) 100%);
opacity: 0;
transition: opacity 0.2s ease-out;
}
.series-item:hover {
transform: translateY(-6px);
box-shadow: 0 12px 32px rgba(107, 70, 193, 0.15);
border-color: #A78BFA;
}
.series-item:hover::before {
opacity: 1;
}
.series-item.active {
border-color: #6B46C1;
background: linear-gradient(135deg, rgba(107, 70, 193, 0.08) 0%, rgba(167, 139, 250, 0.08) 100%);
box-shadow: 0 8px 24px rgba(107, 70, 193, 0.25);
}
.series-item.active::before {
opacity: 1;
}
.series-item.active::after {
content: '✓';
position: absolute;
top: 12px;
right: 12px;
width: 24px;
height: 24px;
background: linear-gradient(135deg, #6B46C1 0%, #8B5CF6 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 14px;
font-weight: bold;
z-index: 1;
animation: checkmarkPop 0.3s ease-out;
}
@keyframes checkmarkPop {
0% {
transform: scale(0);
opacity: 0;
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
opacity: 1;
}
}
.series-image {
width: 100%;
height: 140px;
overflow: hidden;
border-radius: 12px;
margin-bottom: 16px;
position: relative;
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
}
.series-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease-out;
}
.series-item:hover .series-image img {
transform: scale(1.05);
}
.series-name {
font-size: 18px;
font-weight: 700;
color: #1F2937;
position: relative;
z-index: 1;
transition: color 0.2s ease-out;
}
.series-item:hover .series-name {
color: #6B46C1;
}
.series-item.active .series-name {
color: #6B46C1;
}
.series-actions {
display: flex;
gap: 16px;
justify-content: center;
padding-top: 8px;
}
.modal-action-btn {
padding: 12px 32px;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease-out;
border: none;
min-width: 120px;
}
.modal-action-btn.cancel {
background: #f3f4f6;
color: #4b5563;
}
.modal-action-btn.cancel:hover {
background: #e5e7eb;
color: #1f2937;
}
.modal-action-btn.primary {
background: linear-gradient(135deg, #6B46C1 0%, #8B5CF6 100%);
color: white;
box-shadow: 0 4px 12px rgba(107, 70, 193, 0.3);
}
.modal-action-btn.primary:hover {
background: linear-gradient(135deg, #5b3a9e 0%, #7c4ed6 100%);
box-shadow: 0 6px 16px rgba(107, 70, 193, 0.4);
transform: translateY(-2px);
}
.modal-action-btn.primary:active {
transform: translateY(0);
}
/* 响应式设计 */
@media (max-width: 768px) {
.series-list {
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.series-item {
padding: 16px;
}
.series-image {
height: 100px;
}
.series-name {
font-size: 14px;
}
.series-actions {
flex-direction: column;
gap: 12px;
}
.modal-action-btn {
width: 100%;
padding: 14px;
}
}
@media (max-width: 480px) {
.series-list {
grid-template-columns: 1fr;
max-width: 280px;
}
.series-image {
height: 120px;
}
}
</style>

View File

@ -40,10 +40,10 @@
<div class="role-badge" :class="userRole">{{ getRoleDisplayName(currentUser.userRole) }}</div> <div class="role-badge" :class="userRole">{{ getRoleDisplayName(currentUser.userRole) }}</div>
<!-- <div class="role-badge" :class="userRole">{{ currentUser.nickname }}</div> --> <!-- <div class="role-badge" :class="userRole">{{ currentUser.nickname }}</div> -->
</div> </div>
<div class="user-points"> <!-- <div class="user-points">
<span class="points-icon">🪄</span> <span class="points-icon">🪄</span>
<span class="points-text">{{ remainingPoints }}</span> <span class="points-text">{{ remainingPoints }}</span>
</div> </div> -->
<!-- 用户信息模块已取消 --> <!-- 用户信息模块已取消 -->
</div> </div>
<!-- 折叠状态下的用户头像 --> <!-- 折叠状态下的用户头像 -->
@ -148,13 +148,13 @@ const coreMenuItems = computed(() => [
icon: 'OrdersIcon', icon: 'OrdersIcon',
badge: null badge: null
}, },
// { {
// id: 'user-center', id: 'user-center',
// path: '/user-center', path: '/user-center',
// label: t('sidebar.userCenter'), label: t('sidebar.userCenter'),
// icon: 'UserIcon', icon: 'UserIcon',
// badge: null badge: null
// }, },
]) ])
// //

View File

@ -126,7 +126,7 @@ export default {
orderProcess: { orderProcess: {
title: '定制到家流程', title: '定制到家流程',
subtitle: '了解您的订单从支付到发货的全过程', subtitle: '了解您的订单从支付到发货的全过程',
note: '注意:以上时间为工作日计算,节假日可能会顺延。如有问题,请联系客服:13121765685', note: '注意:以上时间为工作日计算,节假日可能会顺延。如有问题,请联系客服: ',
acknowledge: '我已知晓', acknowledge: '我已知晓',
steps: { steps: {
payment: { payment: {
@ -617,7 +617,7 @@ export default {
orderConfirmation: '订单确认在下单后的1个工作日内我们会确认信息后开始处理。', orderConfirmation: '订单确认在下单后的1个工作日内我们会确认信息后开始处理。',
productionTime: '生产时间:生产周期为 515 个工作日,节假日可能顺延。', productionTime: '生产时间:生产周期为 515 个工作日,节假日可能顺延。',
logistics: '物流:发货后将提供订单与跟踪编号,物流信息会发送到您的邮箱。', logistics: '物流:发货后将提供订单与跟踪编号,物流信息会发送到您的邮箱。',
afterSales: '售后与退款:请参考退款政策;如有问题,请联系13121765685', afterSales: '售后与退款:请参考退款政策;如有问题,请联系 ',
error: { error: {
firstNameRequired: '名不能为空', firstNameRequired: '名不能为空',
lastNameRequired: '姓不能为空', lastNameRequired: '姓不能为空',
@ -1404,7 +1404,7 @@ export default {
orderProcess: { orderProcess: {
title: 'Customize to Home Process', title: 'Customize to Home Process',
subtitle: 'Understand the complete process from payment to delivery', subtitle: 'Understand the complete process from payment to delivery',
note: 'Note: The above times are calculated in working days. Holidays may cause delays. If you have any questions, please contact customer service: 13121765685', note: 'Note: The above times are calculated in working days. Holidays may cause delays. If you have any questions, please contact customer service: ',
acknowledge: 'I Acknowledge', acknowledge: 'I Acknowledge',
steps: { steps: {
payment: { payment: {
@ -1844,7 +1844,7 @@ export default {
orderConfirmation: 'Order Confirmation: We will confirm the information within 1 business day after placing the order and begin processing.', orderConfirmation: 'Order Confirmation: We will confirm the information within 1 business day after placing the order and begin processing.',
productionTime: 'Production Time: The production cycle is 5-15 business days, which may be extended during holidays.', productionTime: 'Production Time: The production cycle is 5-15 business days, which may be extended during holidays.',
logistics: 'Logistics: After shipping, we will provide order and tracking numbers, and logistics information will be sent to your email.', logistics: 'Logistics: After shipping, we will provide order and tracking numbers, and logistics information will be sent to your email.',
afterSales: 'After-sales & Refund: Please refer to the refund policy; if you have any questions, please contact 13121765685' afterSales: 'After-sales & Refund: Please refer to the refund policy; if you have any questions, please contact '
}, },
// 中国省份中文映射 // 中国省份中文映射
cnProvinces: { cnProvinces: {

View File

@ -156,7 +156,7 @@ export default {
orderProcess: { orderProcess: {
title: '定制到家流程', title: '定制到家流程',
subtitle: '了解您的订单从支付到发货的全过程', subtitle: '了解您的订单从支付到发货的全过程',
note: '注意:以上时间为工作日计算,节假日可能会顺延。如有问题,请联系客服:13121765685', note: '注意:以上时间为工作日计算,节假日可能会顺延。如有问题,请联系客服: ',
acknowledge: '我已知晓', acknowledge: '我已知晓',
steps: { steps: {
payment: { payment: {
@ -210,6 +210,7 @@ export default {
imageFreeCount: '生图免费', imageFreeCount: '生图免费',
modelFreeCount: '模型免费', modelFreeCount: '模型免费',
times: '次', times: '次',
remainingCredits: '剩余积分',
guide: '使用指南', guide: '使用指南',
back: '返回', back: '返回',
skip: '跳过', skip: '跳过',
@ -505,7 +506,8 @@ export default {
shenhe:'待审核', shenhe:'待审核',
unsuccess:'已拒绝', unsuccess:'已拒绝',
clz:'处理中', clz:'处理中',
dfh:'待发货' dfh:'待发货',
success:'已完成'
}, },
sort: { sort: {
created_at: '创建时间', created_at: '创建时间',
@ -678,7 +680,7 @@ export default {
orderConfirmation: '订单确认在下单后的1个工作日内我们会确认信息后开始处理。', orderConfirmation: '订单确认在下单后的1个工作日内我们会确认信息后开始处理。',
productionTime: '生产时间:生产周期为 515 个工作日,节假日可能顺延。', productionTime: '生产时间:生产周期为 515 个工作日,节假日可能顺延。',
logistics: '物流:发货后将提供订单与跟踪编号,物流信息会发送到您的邮箱。', logistics: '物流:发货后将提供订单与跟踪编号,物流信息会发送到您的邮箱。',
afterSales: '售后与退款:请参考退款政策;如有问题,请联系13121765685', afterSales: '售后与退款:请参考退款政策;如有问题,请联系 ',
error: { error: {
firstNameRequired: '名不能为空', firstNameRequired: '名不能为空',
lastNameRequired: '姓不能为空', lastNameRequired: '姓不能为空',
@ -1270,7 +1272,9 @@ export default {
dropToDeleteHint: '释放鼠标即可删除项目', dropToDeleteHint: '释放鼠标即可删除项目',
confirmDelete: '确认删除', confirmDelete: '确认删除',
deleteProject: '删除项目', deleteProject: '删除项目',
cancel: '取消' cancel: '取消',
selectSeries: '选择系列',
confirm: '确认'
}, },
loading: '加载中...', loading: '加载中...',
allLoaded: '已加载全部数据', allLoaded: '已加载全部数据',
@ -1474,7 +1478,7 @@ export default {
orderProcess: { orderProcess: {
title: 'Customize to Home Process', title: 'Customize to Home Process',
subtitle: 'Understand the complete process of your order from payment to delivery', subtitle: 'Understand the complete process of your order from payment to delivery',
note: 'Note: The above times are calculated on working days, holidays may be delayed. If you have any questions, please contact customer service: 13121765685', note: 'Note: The above times are calculated on working days, holidays may be delayed. If you have any questions, please contact customer service: ',
acknowledge: 'I Acknowledge', acknowledge: 'I Acknowledge',
steps: { steps: {
payment: { payment: {
@ -1528,6 +1532,7 @@ export default {
imageFreeCount: 'Free Image Generation', imageFreeCount: 'Free Image Generation',
modelFreeCount: 'Free Model', modelFreeCount: 'Free Model',
times: 'times', times: 'times',
remainingCredits: 'Remaining Credits',
guide: 'User Guide', guide: 'User Guide',
back: 'Back', back: 'Back',
skip: 'Skip', skip: 'Skip',
@ -1899,7 +1904,8 @@ export default {
shenhe:'Pending Review', shenhe:'Pending Review',
unsuccess:'Rejected', unsuccess:'Rejected',
clz:'Processing', clz:'Processing',
dfh:'To Be Shipped' dfh:'To Be Shipped',
success:'Completed'
}, },
sort: { sort: {
created_at: 'Created Time', created_at: 'Created Time',
@ -2069,7 +2075,7 @@ export default {
orderConfirmation: 'Order Confirmation: Within 1 business day after placing the order, we will confirm the information and start processing.', orderConfirmation: 'Order Confirmation: Within 1 business day after placing the order, we will confirm the information and start processing.',
productionTime: 'Production Time: The production cycle is 515 business days, which may be extended during holidays.', productionTime: 'Production Time: The production cycle is 515 business days, which may be extended during holidays.',
logistics: 'Logistics: After shipment, we will provide the order and tracking number, and logistics information will be sent to your email.', logistics: 'Logistics: After shipment, we will provide the order and tracking number, and logistics information will be sent to your email.',
afterSales: 'After-sales & Refund: Please refer to the refund policy; if you have any questions, please contact 13121765685', afterSales: 'After-sales & Refund: Please refer to the refund policy; if you have any questions, please contact ',
error: { error: {
firstNameRequired: 'First name cannot be empty', firstNameRequired: 'First name cannot be empty',
lastNameRequired: 'Last name cannot be empty', lastNameRequired: 'Last name cannot be empty',
@ -2554,7 +2560,9 @@ export default {
dropToDeleteHint: 'Release mouse to delete project', dropToDeleteHint: 'Release mouse to delete project',
confirmDelete: 'Confirm Delete', confirmDelete: 'Confirm Delete',
deleteProject: 'Delete Project', deleteProject: 'Delete Project',
cancel: 'Cancel' cancel: 'Cancel',
selectSeries: 'Select Series',
confirm: 'Confirm'
}, },
loading: 'Loading...', loading: 'Loading...',
allLoaded: 'All data loaded', allLoaded: 'All data loaded',

View File

@ -131,7 +131,7 @@ export const freeRoutes = [
meta: { requiresAuth: true, keepAlive: false } meta: { requiresAuth: true, keepAlive: false }
}, },
{ {
path: '/project/:id', path: '/project/:id/:series',
name: 'project', name: 'project',
component: () => import('../views/Project/CreateProject.vue'), component: () => import('../views/Project/CreateProject.vue'),
meta: { requiresAuth: true, fullScreen: true } meta: { requiresAuth: true, fullScreen: true }

View File

@ -72,7 +72,7 @@
<!-- 新建项目卡片 --> <!-- 新建项目卡片 -->
<div <div
class="new-project-card" class="new-project-card"
@click="createNewProject" @click="showSeriesSelector = true"
> >
<div class="new-project-icon"> <div class="new-project-icon">
<PlusIcon /> <PlusIcon />
@ -129,330 +129,242 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 系列选择弹窗 -->
<SeriesSelector
:show="showSeriesSelector"
@confirm="handleSeriesConfirm"
@cancel="handleSeriesCancel"
/>
</div> </div>
</template> </template>
<script> <script setup>
import { ref, computed, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { Plus as PlusIcon, Folder, Edit, View, Close, Upload, Delete } from '@element-plus/icons-vue' import { Plus as PlusIcon, Folder as FolderIcon, Edit as EditIcon, View as ViewIcon, Close as CloseIcon, Upload as UploadIcon, Delete as DeleteIcon } from '@element-plus/icons-vue'
import SeriesSelector from '../components/SeriesSelector.vue'
import {Project} from './Project/index' import {Project} from './Project/index'
import { dateUtils } from '@deotaland/utils' import { dateUtils } from '@deotaland/utils'
const { formatDate } = dateUtils;
const PluginProject = new Project();
export default {
name: 'CreationWorkspace',
components: {
PlusIcon,
FolderIcon: Folder,
EditIcon: Edit,
ViewIcon: View,
CloseIcon: Close,
UploadIcon: Upload,
DeleteIcon: Delete
},
setup() {
const { t } = useI18n()
const router = useRouter()
//
const showModal = ref(false)
const modalType = ref('') // 'view' or 'edit'
const modalTitle = ref('')
const modalSubtitle = ref('')
const currentProject = ref(null)
const fileInput = ref(null)
//
const projects = ref([]);
const page = ref(1);
const pageSize = ref(15);
const total = ref(0);
const loading = ref(false);
const finished = ref(false);
//
const draggedProject = ref(null);
const showTrash = ref(false);
const draggedOverCard = ref(null);
//
const showDeleteConfirm = ref(false);
const projectToDelete = ref(null);
const loadMore = ()=>{
getProjectList();
}
//
const getProjectList = async () => {
if (loading.value || finished.value) return;
loading.value = true;
try {
const params = {
"page": page.value,//
"page_size": pageSize.value,//
}
const data = await PluginProject.getProjectList(params)
if (page.value === 1) {
projects.value = data.items;
} else {
projects.value = [...projects.value, ...data.items];
}
total.value = data.total;
finished.value = projects.value.length >= total.value;
page.value++;
} catch (error) {
console.error('获取项目列表异常:', error)
} finally {
loading.value = false;
}
}
//
const changeProjectThumbnail = (project) => {
currentProject.value = project
triggerFileSelect()
}
//
const openProjectModal = (project, type) => {
currentProject.value = project
modalType.value = type
if (type === 'view') {
modalTitle.value = '查看项目'
modalSubtitle.value = '项目详细信息'
} else if (type === 'edit') {
modalTitle.value = '更换缩略图'
modalSubtitle.value = '为项目选择新的缩略图'
}
showModal.value = true
}
const closeModal = () => {
showModal.value = false
modalType.value = ''
modalTitle.value = ''
modalSubtitle.value = ''
currentProject.value = null
}
//
const triggerFileSelect = () => {
fileInput.value?.click()
}
const handleFileSelect = (event) => {
const file = event.target.files[0]
if (!file) return
//
if (!file.type.startsWith('image/')) {
console.error('请选择图片文件')
return
}
// (2MB)
if (file.size > 2 * 1024 * 1024) {
console.error('图片文件大小不能超过2MB')
return
}
try {
// TODO:
console.log('选择的文件:', file)
//
console.log('缩略图更新成功')
closeModal()
} catch (error) {
console.error('上传失败:', error)
}
//
event.target.value = ''
}
//
const openProject = (project) => {
console.log('打开项目:', project.title)
//
router.push(`/project/${project.id}`)
// TODO: const { formatDate } = dateUtils
const PluginProject = new Project()
const { t } = useI18n()
const router = useRouter()
const showModal = ref(false)
const modalType = ref('')
const modalTitle = ref('')
const modalSubtitle = ref('')
const currentProject = ref(null)
const fileInput = ref(null)
const projects = ref([])
const page = ref(1)
const pageSize = ref(15)
const total = ref(0)
const loading = ref(false)
const finished = ref(false)
const draggedProject = ref(null)
const showTrash = ref(false)
const draggedOverCard = ref(null)
const showDeleteConfirm = ref(false)
const projectToDelete = ref(null)
//
const showSeriesSelector = ref(false)
const loadMore = () => {
getProjectList()
}
const getProjectList = async () => {
if (loading.value || finished.value) return
loading.value = true
try {
const params = {
"page": page.value,
"page_size": pageSize.value,
}
const data = await PluginProject.getProjectList(params)
if (page.value === 1) {
projects.value = data.items
} else {
projects.value = [...projects.value, ...data.items]
} }
// - total.value = data.total
const createNewProject = () => { finished.value = projects.value.length >= total.value
router.push('/project/new') page.value++
}
} catch (error) {
console.error('获取项目列表异常:', error)
} finally {
loading.value = false
}
}
const closeModal = () => {
showModal.value = false
modalType.value = ''
modalTitle.value = ''
modalSubtitle.value = ''
currentProject.value = null
}
const handleFileSelect = (event) => {
const file = event.target.files[0]
if (!file) return
if (!file.type.startsWith('image/')) {
console.error('请选择图片文件')
return
}
if (file.size > 2 * 1024 * 1024) {
console.error('图片文件大小不能超过2MB')
return
}
try {
console.log('选择的文件:', file)
console.log('缩略图更新成功')
closeModal()
} catch (error) {
console.error('上传失败:', error)
}
event.target.value = ''
}
const openProject = (project) => {
console.log('打开项目:', project.title)
router.push(`/project/${project.id}/${project.tags[0]}`)
}
const createNewProject = (type) => {
router.push(`/project/new/${type}`)
}
//
const handleSeriesConfirm = (series) => {
showSeriesSelector.value = false
createNewProject(series)
}
//
const handleSeriesCancel = () => {
showSeriesSelector.value = false
}
const findParentCard = (element) => {
while (element && !element.classList.contains('project-card')) {
element = element.parentElement
}
return element
}
const onDragStart = (event, project) => {
draggedProject.value = project
showTrash.value = true
event.target.classList.add('dragging')
event.dataTransfer.effectAllowed = 'move'
event.dataTransfer.setData('text/plain', project.id)
event.dataTransfer.setDragImage(new Image(), 0, 0)
}
const onDragEnd = (event) => {
showTrash.value = false
draggedOverCard.value = null
event.target.classList.remove('dragging')
document.querySelectorAll('.project-card').forEach(card => {
card.classList.remove('drag-over')
})
}
const onDragOver = (event) => {
event.preventDefault()
event.dataTransfer.dropEffect = 'move'
}
const onDragEnter = (event, targetProject) => {
const cardElement = findParentCard(event.target)
if (!cardElement) {
return
}
cardElement.classList.add('drag-over')
draggedOverCard.value = cardElement
if (draggedProject.value && targetProject && draggedProject.value.id !== targetProject.id) {
const draggedIndex = projects.value.findIndex(p => p.id === draggedProject.value.id)
const targetIndex = projects.value.findIndex(p => p.id === targetProject.id)
// if (draggedIndex !== -1 && targetIndex !== -1) {
const findParentCard = (element) => { const [draggedItem] = projects.value.splice(draggedIndex, 1)
while (element && !element.classList.contains('project-card')) { projects.value.splice(targetIndex, 0, draggedItem)
element = element.parentElement;
}
return element;
};
//
const onDragStart = (event, project) => {
draggedProject.value = project;
showTrash.value = true;
//
event.target.classList.add('dragging');
//
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', project.id);
//
event.dataTransfer.setDragImage(new Image(), 0, 0);
};
const onDragEnd = (event) => {
showTrash.value = false;
draggedOverCard.value = null;
//
event.target.classList.remove('dragging');
//
document.querySelectorAll('.project-card').forEach(card => {
card.classList.remove('drag-over');
});
};
const onDragOver = (event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
};
const onDragEnter = (event, targetProject) => {
//
const cardElement = findParentCard(event.target);
if (!cardElement) {
return;
}
//
cardElement.classList.add('drag-over');
draggedOverCard.value = cardElement;
//
if (draggedProject.value && targetProject && draggedProject.value.id !== targetProject.id) {
const draggedIndex = projects.value.findIndex(p => p.id === draggedProject.value.id);
const targetIndex = projects.value.findIndex(p => p.id === targetProject.id);
if (draggedIndex !== -1 && targetIndex !== -1) {
//
const [draggedItem] = projects.value.splice(draggedIndex, 1);
//
projects.value.splice(targetIndex, 0, draggedItem);
}
}
};
const onDragLeave = (event) => {
//
const cardElement = findParentCard(event.target);
if (cardElement) {
cardElement.classList.remove('drag-over');
if (cardElement === draggedOverCard.value) {
draggedOverCard.value = null;
}
}
};
const onDrop = (event, targetProject) => {
event.preventDefault();
//
const cardElement = findParentCard(event.target);
if (cardElement) {
cardElement.classList.remove('drag-over');
}
// dragEnter
draggedProject.value = null;
};
//
const deleteProject = async (project) => {
const index = projects.value.findIndex(p => p.id === project.id);
if (index !== -1) {
projects.value.splice(index, 1);
await PluginProject.deleteProject(project.id)
}
// getProjectList();
};
//
const onDropToTrash = () => {
if (draggedProject.value) {
projectToDelete.value = draggedProject.value;
showDeleteConfirm.value = true;
}
showTrash.value = false;
};
//
const confirmDeleteProject = () => {
if (projectToDelete.value) {
deleteProject(projectToDelete.value);
projectToDelete.value = null;
showDeleteConfirm.value = false;
}
};
//
const cancelDeleteProject = () => {
projectToDelete.value = null;
showDeleteConfirm.value = false;
};
onMounted(()=>{
getProjectList();
})
return {
projects,
showModal,
modalType,
modalTitle,
modalSubtitle,
currentProject,
fileInput,
openProject,
createNewProject,
changeProjectThumbnail,
openProjectModal,
closeModal,
triggerFileSelect,
handleFileSelect,
formatDate,
t,
loading, //
finished, //
page, //
//
draggedProject,
showTrash,
onDragStart,
onDragEnd,
loadMore,
onDragOver,
onDragEnter,
onDragLeave,
onDrop,
onDropToTrash,
//
showDeleteConfirm,
projectToDelete,
confirmDeleteProject,
cancelDeleteProject
} }
} }
} }
const onDragLeave = (event) => {
const cardElement = findParentCard(event.target)
if (cardElement) {
cardElement.classList.remove('drag-over')
if (cardElement === draggedOverCard.value) {
draggedOverCard.value = null
}
}
}
const onDrop = (event, targetProject) => {
event.preventDefault()
const cardElement = findParentCard(event.target)
if (cardElement) {
cardElement.classList.remove('drag-over')
}
draggedProject.value = null
}
const deleteProject = async (project) => {
const index = projects.value.findIndex(p => p.id === project.id)
if (index !== -1) {
projects.value.splice(index, 1)
await PluginProject.deleteProject(project.id)
}
}
const onDropToTrash = () => {
if (draggedProject.value) {
projectToDelete.value = draggedProject.value
showDeleteConfirm.value = true
}
showTrash.value = false
}
const confirmDeleteProject = () => {
if (projectToDelete.value) {
deleteProject(projectToDelete.value)
projectToDelete.value = null
showDeleteConfirm.value = false
}
}
const cancelDeleteProject = () => {
projectToDelete.value = null
showDeleteConfirm.value = false
}
onMounted(() => {
getProjectList()
})
</script> </script>
<style scoped> <style scoped>
.creation-workspace { .creation-workspace {

View File

@ -221,7 +221,7 @@ onMounted(() => {
</script> </script>
<style scoped> <style scoped>
.order-detail { padding: 24px; height: 100vh; display: flex; flex-direction: column; } .order-detail { padding: 24px; height: 100%; display: flex; flex-direction: column; }
.detail-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } .detail-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.header-left { display: flex; align-items: center; gap: 8px; } .header-left { display: flex; align-items: center; gap: 8px; }
.back-btn { padding: 0 8px; } .back-btn { padding: 0 8px; }
@ -347,7 +347,7 @@ onMounted(() => {
/* 右侧:物流时间线样式 */ /* 右侧:物流时间线样式 */
.logistics-timeline-container { .logistics-timeline-container {
max-height: none; max-height: none;
height: 38%; height: 100%;
overflow-y: auto; overflow-y: auto;
padding-right: 8px; padding-right: 8px;
border-radius: 8px; border-radius: 8px;

View File

@ -2,7 +2,7 @@
<div class="creative-zone" @contextmenu.prevent> <div class="creative-zone" @contextmenu.prevent>
<!-- 顶部固定头部组件 --> <!-- 顶部固定头部组件 -->
<div class="header-wrapper"> <div class="header-wrapper">
<HeaderComponent :freeImageCount="Limits.generateCount" :freeModelCount="Limits.modelCount" :projectName="projectInfo.title" @updateProjectInfo="projectInfo = {...projectInfo, ...$event}" @openGuideModal="showGuideModal = true" /> <HeaderComponent :total_score="total_score" :projectName="projectInfo.title" @updateProjectInfo="projectInfo = {...projectInfo, ...$event}" @openGuideModal="showGuideModal = true" />
</div> </div>
<!-- 导入的侧边栏组件 --> <!-- 导入的侧边栏组件 -->
<div class="sidebar-container"> <div class="sidebar-container">
@ -180,11 +180,14 @@ const currentImageIndex = ref(0);
// //
const cleanupFunctions = ref({}); const cleanupFunctions = ref({});
const projectId = ref(null); const projectId = ref(null);
const series = ref(null);//
// //
const projectInfo = ref({}); const projectInfo = ref({});
const total_score = ref(0);
// //
const getGenerateCount = async ()=>{ const getGenerateCount = async ()=>{
const {data} = await modernHome.getModelLimits(); const {data} = await modernHome.getModelLimits();
total_score.value = data.total_score
// Limits.value.generateCount = data[0].model_count; // Limits.value.generateCount = data[0].model_count;
// Limits.value.modelCount = data[1].model_count; // Limits.value.modelCount = data[1].model_count;
} }
@ -207,10 +210,11 @@ const getMaxZIndex = (type)=>{
} }
const combinedPromptJson = ref({}); const combinedPromptJson = ref({});
// //
const getCombinedPrompt = async (type=1)=>{ const getCombinedPrompt = async ()=>{
try { try {
const data = await PluginProject.getCombinedPrompt(type); const data = await PluginProject.getCombinedPrompt(series.value);
combinedPromptJson.value = data; combinedPromptJson.value = data;
console.log(combinedPromptJson.value);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@ -255,8 +259,8 @@ const handleSaveProject = (index,item,type='image')=>{
} }
const createProject = async ()=>{ const createProject = async ()=>{
const {id} = await PluginProject.createProject(); const {id} = await PluginProject.createProject();
// project/id // project/id/
await router.replace(`/project/${id}`); await router.replace(`/project/${id}/${series.value}`);
projectId.value = id; projectId.value = id;
getProjectInfo(id); getProjectInfo(id);
// projectId.value = 8; // projectId.value = 8;
@ -273,6 +277,7 @@ const getProjectInfo = async (id)=>{
...card, ...card,
id: card.id || Date.now() + Math.random().toString(36).substr(2, 9) id: card.id || Date.now() + Math.random().toString(36).substr(2, 9)
})); }));
projectInfo.value.tags = [series.value];
} }
// //
const updateProjectInfo = async (newProjectInfo)=>{ const updateProjectInfo = async (newProjectInfo)=>{
@ -1000,6 +1005,7 @@ import { addPassiveEventListener } from '@/utils/passiveEventListeners'
const init = ()=>{ const init = ()=>{
const route = useRoute(); const route = useRoute();
projectId.value = route.params.id; projectId.value = route.params.id;
series.value = route.params.series;
if(projectId.value === 'new'){ if(projectId.value === 'new'){
createProject(); createProject();
return return

View File

@ -729,13 +729,19 @@ export class Project{
}, 1000); }, 1000);
} }
//获取动态提示词 //获取动态提示词
async getCombinedPrompt(){//type:1人物类型2动物类型 async getCombinedPrompt(series){//series:项目系列Done Oone
try { try {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const res = await requestUtils.common(clientApi.default.combined) const res = await requestUtils.common(clientApi.default.combined)
if(res.code === 0){ if(res.code === 0){
let data = res.data; let data = res.data;
// 如果是Oone系列过滤掉title中包含"动物坐姿"或"人物姿势"的提示词
if (series === 'Oone') {
data = data.filter(item => {
if (!item.title) return true;
return !item.title.includes('动物坐姿') && !item.title.includes('人物姿势');
});
}
// 初始化返回数据结构 // 初始化返回数据结构
const result = { const result = {
person: { person: {

File diff suppressed because it is too large Load Diff

View File

@ -12,30 +12,30 @@
</div> </div>
</div> </div>
<!-- 隐藏的文件上传输入 --> <!-- 隐藏的文件上传输入 -->
<input <!-- <input
ref="avatarInput" ref="avatarInput"
type="file" type="file"
accept="image/*" accept="image/*"
class="avatar-input" class="avatar-input"
@change="handleAvatarUpload" @change="handleAvatarUpload"
hidden hidden
/> /> -->
</div> </div>
<!-- @click="toggleEditName" -->
<div class="user-details-section"> <div class="user-details-section">
<div class="user-name-row"> <div class="user-name-row">
<div class="editable-name-container"> <div class="editable-name-container">
<h1 <h1
class="user-name" class="user-name"
@click="toggleEditName"
:class="{ 'editable': true }" :class="{ 'editable': true }"
>{{ userData.nickname }}</h1> >{{ userData.nickname }}</h1>
<div v-if="isEditingName" class="name-edit-form"> <div v-if="isEditingName" class="name-edit-form">
<!-- @blur="saveNickname"
@keyup.enter="saveNickname" -->
<el-input <el-input
v-model="editNameValue" v-model="editNameValue"
size="large" size="large"
@blur="saveNickname"
@keyup.enter="saveNickname"
ref="nameInput" ref="nameInput"
placeholder="Please enter a nickname" placeholder="Please enter a nickname"
/> />
@ -48,7 +48,7 @@
</div> </div>
<!-- 积分信息区域 - 免费会员可见 --> <!-- 积分信息区域 - 免费会员可见 -->
<div class="points-section"> <div class="points-section" v-if="false">
<h2>{{ $t('userCenter.points.title') }}</h2> <h2>{{ $t('userCenter.points.title') }}</h2>
<!-- 积分明细和规则并排容器 --> <!-- 积分明细和规则并排容器 -->
@ -114,9 +114,9 @@
</div> </div>
</div> </div>
<!-- 邀请信息区域 - 免费会员可见 --> <!-- 邀请信息区域 - 免费会员可见 -->
<div class="invitation-section"> <div class="invitation-section">
<h2>{{ $t('userCenter.invitation.title') }}</h2> <h2 v-if="false">{{ $t('userCenter.invitation.title') }}</h2>
<div class="invitation-info"> <div v-if="false" class="invitation-info">
<div class="info-item"> <div class="info-item">
<label>{{ $t('userCenter.invitation.inviteCount') }}</label> <label>{{ $t('userCenter.invitation.inviteCount') }}</label>
<span>{{ userData.inviteCount }}</span> <span>{{ userData.inviteCount }}</span>
@ -128,7 +128,7 @@
<h3>{{ $t('userCenter.invitation.inviteCodes') }}</h3> <h3>{{ $t('userCenter.invitation.inviteCodes') }}</h3>
<div class="invite-cards-container"> <div class="invite-cards-container">
<div <div
v-for="(inviteCode, index) in userData.inviteCodes" v-for="(inviteCode, index) in (userData.inviteCodes.filter(item => item.codeType!='permanent'))"
:key="index" :key="index"
class="invite-card" class="invite-card"
:class="{ 'used': inviteCode.used }" :class="{ 'used': inviteCode.used }"
@ -154,7 +154,7 @@
</div> </div>
<!-- 合并后的邀请相关列表 --> <!-- 合并后的邀请相关列表 -->
<div class="invite-related-section"> <div v-if="false" class="invite-related-section">
<h3>{{ userData.role === 'creator' ? $t('userCenter.creator.invitedUsersList') : $t('userCenter.invitation.invitePointsDetails') }}</h3> <h3>{{ userData.role === 'creator' ? $t('userCenter.creator.invitedUsersList') : $t('userCenter.invitation.invitePointsDetails') }}</h3>
<div class="table-container"> <div class="table-container">
<table class="details-table"> <table class="details-table">
@ -227,20 +227,20 @@
</div> </div>
<!-- 成为达人会员模块 - 仅免费会员可见放在达人会员规则右侧 --> <!-- 成为达人会员模块 - 仅免费会员可见放在达人会员规则右侧 -->
<div v-if="userData.role === 'free'" class="upgrade-creator-section"> <!-- <div v-if="userData.role === 'free'" class="upgrade-creator-section">
<h4>{{ $t('userCenter.invitation.rules.becomeCreator.title') }}</h4> <h4>{{ $t('userCenter.invitation.rules.becomeCreator.title') }}</h4>
<div class="qr-code-container"> <div class="qr-code-container">
<!-- QR码占位 -->
<div class="qr-placeholder">{{ $t('userCenter.invitation.rules.becomeCreator.qrCode') }}</div> <div class="qr-placeholder">{{ $t('userCenter.invitation.rules.becomeCreator.qrCode') }}</div>
</div> </div>
</div> </div> -->
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- 佣金统计独立模块 --> <!-- 佣金统计独立模块 -->
<div v-if="userData.role === 'creator'" class="commission-section"> <!-- v-if="userData.role === 'creator'" -->
<div v-if="false" class="commission-section">
<h2>{{ $t('userCenter.creator.commissionTitle') }}</h2> <h2>{{ $t('userCenter.creator.commissionTitle') }}</h2>
<div class="commission-info"> <div class="commission-info">
<div class="info-item"> <div class="info-item">
@ -313,7 +313,8 @@ const fetchInviteCodes = async () => {
const inviteCodes = response.data.map(item => ({ const inviteCodes = response.data.map(item => ({
code: item.inviteCode, code: item.inviteCode,
used: item.isUsed === 1, // isUsed1使 used: item.isUsed === 1, // isUsed1使
createdAt: item.createdAt createdAt: item.createdAt,
codeType: item.codeType
})) }))
userData.value.inviteCodes = inviteCodes userData.value.inviteCodes = inviteCodes
userData.value.inviteCount = inviteCodes.length userData.value.inviteCount = inviteCodes.length
@ -403,7 +404,8 @@ const saveNickname = () => {
// //
const copyInviteCode = (code) => { const copyInviteCode = (code) => {
navigator.clipboard.writeText(code) const InviteLink = `🎉 限时福利!送你专属邀请码:${code},注册立得积分+解锁高级功能,快来一起体验吧!${window.location.origin}/#/login?inviteCode=${code}`
navigator.clipboard.writeText(InviteLink)
.then(() => { .then(() => {
ElMessage.success(t('userCenter.invitation.copySuccess')) ElMessage.success(t('userCenter.invitation.copySuccess'))
}) })

View File

@ -574,5 +574,5 @@ export interface ModalProps {
**设计版本**: 1.0 **设计版本**: 1.0
**创建时间**: 2025-11-18 **创建时间**: 2025-11-18
**技术负责人**: 13121765685 **技术负责人**:
**审核状态**: 待审核 **审核状态**: 待审核

View File

@ -224,5 +224,5 @@ Monorepo架构将为DeotalandAi项目带来显著的开发效率提升和维护
--- ---
**变更ID**: monorepo-architecture-design **变更ID**: monorepo-architecture-design
**创建时间**: 2025-11-18 **创建时间**: 2025-11-18
**作者**: 13121765685 **作者**:
**状态**: 待确认 **状态**: 待确认

View File

@ -78,4 +78,4 @@ DeotalandAi 是一个 AI 驱动的前端项目,采用现代化的 Vue3 技术
- **CDN 资源**: 图标库和字体文件 (fallback 到本地) - **CDN 资源**: 图标库和字体文件 (fallback 到本地)
## Archived Changes ## Archived Changes
- 2025-11-12-13121765685-add-frontend-vue3-elementplus-i18n-template: 前端 Vue3 模板框架Element Plus + I18n- 已完成包含完整的Vue3项目初始化、Element Plus集成、国际化支持、响应式设计和性能优化 - 2025-11-12- -add-frontend-vue3-elementplus-i18n-template: 前端 Vue3 模板框架Element Plus + I18n- 已完成包含完整的Vue3项目初始化、Element Plus集成、国际化支持、响应式设计和性能优化

View File

@ -1,9 +1,9 @@
const order = { const order = {
getOrderDetailt:{url:'/api-core/admin/order/get',method:'GET'},//获取订单详情 getOrderDetailt:{url:'/api-core/admin/order/get',method:'GET',isLoading:true},//获取订单详情
getOrderList:{url:'/api-core/admin/order/list',method:'POST'},//获取订单列表 getOrderList:{url:'/api-core/admin/order/list',method:'POST',isLoading:true},//获取订单列表
refundApprove:{url:'/api-core/admin/order/refund/approve',method:'GET'},//同意退款 refundApprove:{url:'/api-core/admin/order/refund/approve',method:'GET',isLoading:true},//同意退款
refundReject:{url:'/api-core/admin/order/refund/reject',method:'GET'},//拒绝退款 refundReject:{url:'/api-core/admin/order/refund/reject',method:'GET',isLoading:true},//拒绝退款
updateOrderStatus:{url:'/api-core/admin/order/update',method:'POST'},//修改订单状态 updateOrderStatus:{url:'/api-core/admin/order/update',method:'POST',isLoading:true},//修改订单状态
getOrderStatistics:{url:'/api-core/admin/order/statistics',method:'GET'},//订单状态统计 getOrderStatistics:{url:'/api-core/admin/order/statistics',method:'GET',isLoading:true},//订单状态统计
} }
export default order; export default order;

View File

@ -1,10 +1,10 @@
const login = { const login = {
MODEL_LIMITS:{url:'/api-core/front/user/model-limits',method:'GET'},// 模型限制 MODEL_LIMITS:{url:'/api-core/front/user/model-limits',method:'GET'},// 模型限制
USER_STATISTICS:{url:'/api-core/front/user/statistics',method:'GET'},// 用户统计 USER_STATISTICS:{url:'/api-core/front/user/statistics',method:'GET',isLoading:true},// 用户统计
INVITE_CODES:{url:'/api-base/user/invite/codes',method:'GET'},// 返回当前用户的邀请码列表及使用状态 INVITE_CODES:{url:'/api-base/user/invite/codes',method:'GET',isLoading:true},// 返回当前用户的邀请码列表及使用状态
INVITE_RECORDS:{url:'/api-base/user/invite/records',method:'GET'},// 返回邀请的用户列表,包含奖励明细 INVITE_RECORDS:{url:'/api-base/user/invite/records',method:'GET',isLoading:true},// 返回邀请的用户列表,包含奖励明细
combined:{url:'/api-base/prompt/active',method:'GET'},// 返回动态提示词 combined:{url:'/api-base/prompt/active',method:'GET',isLoading:true},// 返回动态提示词
UPGRADE:{url:'/api-base/user/upgrade',method:'POST'},// 候补会员使用邀请码升级为正式会员 UPGRADE:{url:'/api-base/user/upgrade',method:'POST',isLoading:true},// 候补会员使用邀请码升级为正式会员
USER_INFO:{url:'/api-base/user/info',method:'GET',isLoading:true},// 返回用户信息 USER_INFO:{url:'/api-base/user/info',method:'GET',isLoading:true},// 返回用户信息
} }
export default login; export default login;

View File

@ -351,8 +351,8 @@ export class FileServer {
prefix:isModelFile ? 'uploads' : 'images', prefix:isModelFile ? 'uploads' : 'images',
...config ...config
} }
// const requestUrl = this.RULE=='admin'?adminApi.default.adminUPLOADS3:clientApi.default.UPLOADS3; const requestUrl = this.RULE=='admin'?adminApi.default.adminUPLOADS3:clientApi.default.UPLOADS3;
const requestUrl = clientApi.default.UPLOADS3; // const requestUrl = clientApi.default.UPLOADS3;
const response = await requestUtils.common(requestUrl, params); const response = await requestUtils.common(requestUrl, params);
if(response.code==0){ if(response.code==0){
let data = response.data; let data = response.data;

View File

@ -43,7 +43,7 @@ export class MeshyServer extends FileServer {
"payload": { "payload": {
image_url:'', image_url:'',
ai_model: 'latest', ai_model: 'latest',
enable_pbr: true, enable_pbr: false,
should_remesh: false, should_remesh: false,
should_texture: false, should_texture: false,
save_pre_remeshed_model: true, save_pre_remeshed_model: true,