|
|
@ -1,4 +1,24 @@
|
|||
# 开发环境配置
|
||||
# 生产环境配置
|
||||
VITE_BASE_URL=/api
|
||||
VITE_DEV_MODE=true
|
||||
VITE_LOG_LEVEL=info
|
||||
VITE_PROJECTTYPE=admin
|
||||
# Vercel 部署环境变量配置示例
|
||||
# 复制此文件为 .env.local 并填入实际值
|
||||
|
||||
# Google AI API Key(用于 AI 功能)
|
||||
VITE_GOOGLE_API_KEY=your_google_api_key_here
|
||||
|
||||
|
||||
# Stripe 支付配置
|
||||
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_your_stripe_publishable_key
|
||||
|
||||
# 应用配置
|
||||
VITE_APP_TITLE=DeotalandAI
|
||||
VITE_APP_DESCRIPTION=AI-Powered Creation Platform
|
||||
|
||||
# 开发环境配置
|
||||
VITE_DEV_MODE=false
|
||||
VITE_LOG_LEVEL=error
|
||||
|
||||
# 生产环境配置(在 Vercel 中设置)
|
||||
# NODE_ENV=production
|
||||
# VERCEL=true
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
# 生产环境配置
|
||||
VITE_BASE_URL=https://api.deotaland.ai
|
||||
# Vercel 部署环境变量配置示例
|
||||
# 复制此文件为 .env.local 并填入实际值
|
||||
|
||||
# 开发环境变量配置
|
||||
# Google AI API Key(用于 AI 功能)
|
||||
VITE_GOOGLE_API_KEY=your_google_api_key_here
|
||||
VITE_PROJECTTYPE=admin
|
||||
# 基础API地址(生产环境)
|
||||
VITE_BASE_URL=https://api.deotaland.ai
|
||||
|
||||
# 基础API地址(备用)
|
||||
VITE_APP_BASE_API=https://api.deotaland.ai
|
||||
|
||||
# Stripe 支付配置
|
||||
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_your_stripe_publishable_key
|
||||
|
|
@ -15,9 +16,5 @@ VITE_APP_TITLE=DeotalandAI
|
|||
VITE_APP_DESCRIPTION=AI-Powered Creation Platform
|
||||
|
||||
# 开发环境配置
|
||||
VITE_DEV_MODE=false
|
||||
VITE_LOG_LEVEL=error
|
||||
|
||||
# 生产环境配置(在 Vercel 中设置)
|
||||
# NODE_ENV=production
|
||||
# VERCEL=true
|
||||
VITE_DEV_MODE=true
|
||||
VITE_LOG_LEVEL=debug
|
||||
|
|
@ -151,6 +151,11 @@ onUnmounted(() => {
|
|||
</template>
|
||||
|
||||
<style scoped>
|
||||
*{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* 应用主容器 */
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
<el-dropdown @command="handleUserAction">
|
||||
<span class="user-info">
|
||||
<el-avatar :size="32" class="user-avatar">
|
||||
{{ username.charAt(0).toUpperCase() }}
|
||||
{{ username?.charAt(0)?.toUpperCase() || 'A' }}
|
||||
</el-avatar>
|
||||
<span class="username">{{ username }}</span>
|
||||
<el-icon><ArrowDown /></el-icon>
|
||||
|
|
@ -220,15 +220,11 @@ const handleUserAction = async (command) => {
|
|||
ElMessage.info(t('messages.settingsComingSoon'))
|
||||
break
|
||||
case 'logout':
|
||||
try {
|
||||
Plug.logout(()=>{
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
router.replace('/login')
|
||||
router.push('/login')
|
||||
})
|
||||
} catch {
|
||||
// 用户取消
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,11 +68,19 @@ import { View, Close } from '@element-plus/icons-vue'
|
|||
import ModelViewer from '@/components/common/ModelViewer.vue'
|
||||
import { MeshyServer } from '@deotaland/utils'
|
||||
const meshServer = new MeshyServer()
|
||||
const isLoading = ref(false);
|
||||
const props = defineProps({
|
||||
taskId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
imgUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
projectId: {
|
||||
required: true
|
||||
},
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: '100%'
|
||||
|
|
@ -88,7 +96,7 @@ const props = defineProps({
|
|||
})
|
||||
// 模型URL
|
||||
const modelUrl = ref('')
|
||||
const emit = defineEmits(['preview', 'delete', 'load', 'error'])//preview事件用于预览模型
|
||||
const emit = defineEmits(['preview', 'delete', 'load', 'error','save'])//preview事件用于预览模型
|
||||
const { t } = useI18n()
|
||||
// 状态管理
|
||||
// 是否显示初始化蒙层
|
||||
|
|
@ -141,10 +149,24 @@ watch(() => initializationProgress.value, (newVal) => {
|
|||
}
|
||||
})
|
||||
const loadmodelUrl = ()=>{
|
||||
meshServer.createModelTask({
|
||||
image_url:props.imgUrl
|
||||
},(result)=>{
|
||||
showInitializationOverlay.value = true;
|
||||
if(props.taskId){
|
||||
startModelTask(props.taskId)
|
||||
}
|
||||
else if(props.imgUrl){
|
||||
meshServer.createModelTask({
|
||||
image_url:props.imgUrl,
|
||||
project_id:props.projectId
|
||||
},(result)=>{
|
||||
emit('save',result)
|
||||
startModelTask(result)
|
||||
},()=>{
|
||||
emit('delete')
|
||||
},{
|
||||
})
|
||||
}
|
||||
}
|
||||
const startModelTask = (result) => {
|
||||
showInitializationOverlay.value = true;
|
||||
meshServer.getModelTaskStatus(result,(modelurl)=>{
|
||||
setInitializationProgress(100)
|
||||
modelUrl.value = modelurl;
|
||||
|
|
@ -153,16 +175,9 @@ const loadmodelUrl = ()=>{
|
|||
},(progress)=>{
|
||||
setInitializationProgress(progress)
|
||||
})
|
||||
},()=>{
|
||||
emit('delete')
|
||||
},{
|
||||
|
||||
})
|
||||
}
|
||||
onMounted(()=>{
|
||||
if(props.imgUrl){
|
||||
loadmodelUrl()
|
||||
}
|
||||
})
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
|
|
|
|||
|
|
@ -123,7 +123,71 @@ export default {
|
|||
logout: 'Logout',
|
||||
logoutSuccess: 'Logout successful'
|
||||
},
|
||||
|
||||
orderManagement: {
|
||||
title: 'Order Management',
|
||||
description: 'View and manage your purchases and subscriptions',
|
||||
createOrder: 'Create Order',
|
||||
filters: {
|
||||
status: 'Status Filter',
|
||||
search: 'Search Orders',
|
||||
sort: 'Sort By'
|
||||
},
|
||||
searchPlaceholder: 'Search order ID, customer name...',
|
||||
stats: {
|
||||
totalOrders: 'Total Orders',
|
||||
pending: 'Pending',
|
||||
completed: 'Completed',
|
||||
revenue: 'Total Revenue'
|
||||
},
|
||||
ordersList: 'Orders List',
|
||||
actions: {
|
||||
view: 'View Details',
|
||||
payNow: 'Pay Now',
|
||||
cancel: 'Cancel'
|
||||
},
|
||||
empty: {
|
||||
title: 'No Orders Yet',
|
||||
description: 'You don\'t have any order records yet',
|
||||
action: 'Create Order'
|
||||
},
|
||||
status: {
|
||||
dsh: 'Pending Review',
|
||||
yjj:'Rejected',
|
||||
all: 'All',
|
||||
pending: 'Pending',
|
||||
paid: 'Paid',
|
||||
processing: 'Processing',
|
||||
shipped: 'Shipped',
|
||||
delivered: 'Delivered',
|
||||
completed: 'Completed',
|
||||
cancelled: 'Cancelled',
|
||||
refunded: 'Refunded',
|
||||
expired: 'Expired',
|
||||
shenhe: 'Under Review',
|
||||
unsuccess: 'Rejected',
|
||||
clz: 'Processing',
|
||||
dfh: 'Pending Shipment'
|
||||
},
|
||||
sort: {
|
||||
created_at: 'Created Time',
|
||||
total: 'Order Total',
|
||||
status: 'Order Status',
|
||||
customer: 'Customer Name'
|
||||
},
|
||||
payment: {
|
||||
pending: 'Pending Payment',
|
||||
paid: 'Paid',
|
||||
failed: 'Payment Failed',
|
||||
refunded: 'Refunded'
|
||||
},
|
||||
refundStatus:{
|
||||
wtk:'No Refund',
|
||||
sqtk:'Refund Requested',
|
||||
jjtk:'Refund Rejected',
|
||||
tytk:'Refund Approved',
|
||||
ytk:'Refunded'
|
||||
},
|
||||
},
|
||||
admin: {
|
||||
title: 'Admin Panel',
|
||||
login: {
|
||||
|
|
@ -225,6 +289,7 @@ export default {
|
|||
}
|
||||
},
|
||||
orders: {
|
||||
image:'Product Image',
|
||||
title: 'Order Management',
|
||||
export: 'Export Orders',
|
||||
search: 'Search Orders',
|
||||
|
|
@ -270,7 +335,10 @@ export default {
|
|||
rejected: 'Rejected',
|
||||
processing: 'Processing',
|
||||
readyToShip: 'Ready to Ship',
|
||||
shipped: 'Shipped'
|
||||
shipped: 'Shipped',
|
||||
completed: 'Completed',
|
||||
pending: 'Pending',
|
||||
cancelled: 'Cancelled'
|
||||
},
|
||||
paymentOptions: {
|
||||
alipay: 'Alipay',
|
||||
|
|
@ -282,7 +350,7 @@ export default {
|
|||
title: 'IP Review Management',
|
||||
subtitle: 'Manage creator-submitted IP content review process',
|
||||
list: 'Review List',
|
||||
search: 'Search Creator',
|
||||
search: 'Search Order',
|
||||
status: 'Review Status',
|
||||
dateRange: 'Date Range',
|
||||
orderPrice: 'Order Price',
|
||||
|
|
@ -420,6 +488,7 @@ export default {
|
|||
createTime: 'Create Time',
|
||||
actions: 'Actions',
|
||||
disassembly: 'Disassembly',
|
||||
completeDisassembly: 'Complete Disassembly',
|
||||
disassemblyType: 'Disassembly Type',
|
||||
refresh: 'Refresh',
|
||||
noData: 'No disassembly orders',
|
||||
|
|
@ -488,7 +557,11 @@ export default {
|
|||
disassembleTitle: 'Disassembly Confirmation',
|
||||
alreadyProcessing: 'This order is already being processed, please do not repeat the operation',
|
||||
uploadSuccess: 'Image uploaded successfully',
|
||||
uploadFailed: 'Image upload failed'
|
||||
uploadFailed: 'Image upload failed',
|
||||
completeDisassemblyConfirm: 'Are you sure to complete the disassembly of this order?',
|
||||
completeDisassemblyTitle: 'Complete Disassembly Confirmation',
|
||||
completeDisassemblySuccess: 'Disassembly completed successfully',
|
||||
completeDisassemblyError: 'Failed to complete disassembly, please try again'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,102 @@ export default {
|
|||
warning: '警告',
|
||||
info: '提示'
|
||||
},
|
||||
|
||||
orderManagement: {
|
||||
title: '订单',
|
||||
description: '查看和管理您的购买和订阅信息',
|
||||
createOrder: '创建订单',
|
||||
filters: {
|
||||
status: '状态筛选',
|
||||
search: '搜索订单',
|
||||
sort: '排序方式'
|
||||
},
|
||||
searchPlaceholder: '搜索订单号、客户名称...',
|
||||
stats: {
|
||||
totalOrders: '总订单数',
|
||||
pending: '待处理',
|
||||
completed: '已完成',
|
||||
revenue: '总收入'
|
||||
},
|
||||
ordersList: '订单列表',
|
||||
actions: {
|
||||
view: '查看详情',
|
||||
payNow: '立即支付',
|
||||
pay: '立即支付',
|
||||
cancel: '取消订单',
|
||||
confirm: '确认收货',
|
||||
more: '更多操作',
|
||||
downloadInvoice: '下载发票',
|
||||
viewTracking: '查看物流',
|
||||
contactSeller: '联系卖家'
|
||||
},
|
||||
empty: {
|
||||
title: '暂无订单',
|
||||
description: '您还没有任何订单记录',
|
||||
action: '创建订单'
|
||||
},
|
||||
refundStatus:{
|
||||
wtk:'无退款',
|
||||
sqtk:'申请退款',
|
||||
jjtk:'拒绝退款',
|
||||
tytk:'同意退款',
|
||||
ytk:'已退款'
|
||||
},
|
||||
status: {
|
||||
yjj:'已拒绝',
|
||||
dsh: '待审核',
|
||||
all: '全部',
|
||||
pending: '待处理',
|
||||
paid: '已支付',
|
||||
processing: '处理中',
|
||||
shipped: '已发货',
|
||||
delivered: '已送达',
|
||||
completed: '已完成',
|
||||
cancelled: '已取消',
|
||||
refunded: '已退款',
|
||||
expired: '已过期',
|
||||
shenhe:'待审核',
|
||||
unsuccess:'已拒绝',
|
||||
clz:'处理中',
|
||||
dfh:'待发货'
|
||||
},
|
||||
sort: {
|
||||
created_at: '创建时间',
|
||||
total: '订单总额',
|
||||
status: '订单状态',
|
||||
customer: '客户名称'
|
||||
},
|
||||
order: {
|
||||
products: '商品列表',
|
||||
shipping: '收货信息',
|
||||
recipient: '收件人',
|
||||
phone: '联系电话',
|
||||
address: '收货地址',
|
||||
payment: '支付信息',
|
||||
paymentMethod: '支付方式',
|
||||
paymentStatus: '支付状态',
|
||||
paidAt: '支付时间',
|
||||
tracking: '物流信息',
|
||||
courier: '快递公司',
|
||||
trackingNumber: '快递单号'
|
||||
},
|
||||
payment: {
|
||||
pending: '待支付',
|
||||
paid: '已支付',
|
||||
failed: '支付失败',
|
||||
refunded: '已退款'
|
||||
}
|
||||
,
|
||||
countdown: {
|
||||
remaining: '剩余支付时间',
|
||||
expired: '已超时'
|
||||
}
|
||||
,
|
||||
expiredNotice: '订单已超时,无法支付,请重新下单'
|
||||
},
|
||||
// 应用
|
||||
app: {
|
||||
title: 'Vue3 前端设计师工具',
|
||||
description: '现代化、响应式的前端设计工具'
|
||||
title: 'DeotalandAiAdmin',
|
||||
description: ''
|
||||
},
|
||||
|
||||
// 导航
|
||||
|
|
@ -194,6 +285,7 @@ export default {
|
|||
}
|
||||
},
|
||||
orders: {
|
||||
image:'商品图片',
|
||||
title: '订单管理',
|
||||
export: '导出订单',
|
||||
search: '搜索订单',
|
||||
|
|
@ -239,7 +331,10 @@ export default {
|
|||
rejected: '已拒绝',
|
||||
processing: '待处理',
|
||||
readyToShip: '待发货',
|
||||
shipped: '已发货'
|
||||
shipped: '已发货',
|
||||
completed: '已完成',
|
||||
pending: '待处理',
|
||||
cancelled: '已取消'
|
||||
},
|
||||
paymentOptions: {
|
||||
alipay: '支付宝',
|
||||
|
|
@ -251,7 +346,7 @@ export default {
|
|||
title: 'IP审核管理',
|
||||
subtitle: '管理创作者提交的IP内容审核流程',
|
||||
list: '审核列表',
|
||||
search: '搜索创作者',
|
||||
search: '搜索订单',
|
||||
status: '审核状态',
|
||||
dateRange: '日期范围',
|
||||
orderPrice: '订单价格',
|
||||
|
|
@ -338,6 +433,7 @@ export default {
|
|||
createTime: '创建时间',
|
||||
actions: '操作',
|
||||
disassembly: '拆件',
|
||||
completeDisassembly: '完成拆件',
|
||||
disassemblyType: '拆件类型',
|
||||
refresh: '刷新',
|
||||
noData: '暂无拆件订单',
|
||||
|
|
@ -406,7 +502,11 @@ export default {
|
|||
disassembleTitle: '拆件确认',
|
||||
alreadyProcessing: '该订单正在拆件中,请勿重复操作',
|
||||
uploadSuccess: '图片上传成功',
|
||||
uploadFailed: '图片上传失败'
|
||||
uploadFailed: '图片上传失败',
|
||||
completeDisassemblyConfirm: '确定要完成拆件此订单吗?',
|
||||
completeDisassemblyTitle: '完成拆件确认',
|
||||
completeDisassemblySuccess: '拆件完成成功',
|
||||
completeDisassemblyError: '拆件完成失败,请重试'
|
||||
}
|
||||
},
|
||||
users: {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ import i18n from './locales/i18n'
|
|||
|
||||
// 创建应用实例
|
||||
const app = createApp(App)
|
||||
|
||||
window.setElMessage = (options={})=>{
|
||||
ElMessage[options.type || 'info'](options.message || '请求失败')
|
||||
}
|
||||
// 配置 Pinia
|
||||
const pinia = createPinia()
|
||||
app.use(pinia)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import AdminLogin from '@/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')
|
||||
const AdminOrders = () => import('@/views/admin/AdminOrders.vue')
|
||||
const AdminOrders = () => import('@/views/admin/AdminOrders/AdminOrders.vue')
|
||||
const AdminUsers = () => import('@/views/admin/AdminUsers.vue')
|
||||
const AdminContentReview = () => import('@/views/admin/AdminContentReview.vue')
|
||||
const AdminDisassemblyOrders = () => import('@/views/admin/AdminDisassemblyOrders.vue')
|
||||
|
|
@ -134,9 +134,9 @@ const router = createRouter({
|
|||
|
||||
// 路由守卫 - 认证检查
|
||||
router.beforeEach((to, from, next) => {
|
||||
localStorage.setItem('token','123')
|
||||
next();
|
||||
return
|
||||
// localStorage.setItem('token','123')
|
||||
// next();
|
||||
// return
|
||||
// 设置页面标题
|
||||
if (to.meta?.title) {
|
||||
document.title = to.meta.title
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ export const useAuthStore = defineStore('auth', {
|
|||
logout() {
|
||||
this.token = ''
|
||||
this.user = null
|
||||
|
||||
// 清除持久化存储
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('user')
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
|
||||
import { requestUtils,clientApi } from '@deotaland/utils'
|
||||
import { requestUtils,adminApi } from '@deotaland/utils'
|
||||
export class AdminLogin {
|
||||
constructor() {
|
||||
}
|
||||
//获取验证码
|
||||
async getCaptchaCode(callback) {
|
||||
const res = await requestUtils.common(clientApi.default.CAPTCHA_CODE);
|
||||
if (res.code === 200) {
|
||||
const res = await requestUtils.common(adminApi.default.CAPTCHA_CODE);
|
||||
// 确保base64数据包含正确的data URL前缀
|
||||
let imgData = res.data.img;
|
||||
if (imgData && !imgData.startsWith('data:image')) {
|
||||
|
|
@ -16,14 +15,17 @@ export class AdminLogin {
|
|||
uuid:res.data.uuid,
|
||||
img:imgData,
|
||||
});
|
||||
}
|
||||
}
|
||||
//退出登录
|
||||
logout(callback) {
|
||||
requestUtils.common(clientApi.default.LOGOUT).then(res=>{
|
||||
if(res.code === 200){
|
||||
// 清除localStorage中的token
|
||||
requestUtils.common(adminApi.default.LOGOUT).then(res=>{
|
||||
localStorage.removeItem('token');
|
||||
callback&&callback();
|
||||
}
|
||||
}).catch(err=>{
|
||||
console.log('退出登录失败:', err);
|
||||
localStorage.removeItem('token');
|
||||
callback&&callback();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
<h1 class="login-title">{{ t('admin.login.title') }}</h1>
|
||||
<p class="login-subtitle">{{ t('admin.login.welcome') }}</p>
|
||||
</div>
|
||||
|
||||
<el-form
|
||||
ref="loginFormRef"
|
||||
:model="loginForm"
|
||||
|
|
@ -97,17 +96,15 @@ import { useRouter } from 'vue-router'
|
|||
import { ElMessage } from 'element-plus'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAuthStore } from '@/stores'
|
||||
import { UserFilled, Lock, Key, Loading } from '@element-plus/icons-vue'
|
||||
import { requestUtils,clientApi } from '@deotaland/utils'
|
||||
import { Lock, Key, Loading } from '@element-plus/icons-vue'
|
||||
import { requestUtils,adminApi } from '@deotaland/utils'
|
||||
import {AdminLogin} from './AdminLogin'
|
||||
const PLUG = new AdminLogin();
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const loginFormRef = ref()
|
||||
const loading = ref(false)
|
||||
|
||||
const loginForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
|
|
@ -139,22 +136,20 @@ const loginRules = {
|
|||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
await loginFormRef.value.validate()
|
||||
loading.value = true
|
||||
// 发送登录请求
|
||||
const response = await requestUtils.common(clientApi.default.LOGIN, {
|
||||
const response = await requestUtils.common(adminApi.default.LOGIN, {
|
||||
username: loginForm.username,
|
||||
password: loginForm.password,
|
||||
code: loginForm.code,
|
||||
uuid: uuid.value
|
||||
}
|
||||
)
|
||||
if(response.code !== 200){
|
||||
ElMessage.error(response.msg || t('admin.login.loginFailed'))
|
||||
if(response.code !== 0){
|
||||
// ElMessage.error(response.msg || t('admin.login.loginFailed'))
|
||||
return
|
||||
}
|
||||
let data = response.data;
|
||||
|
|
@ -174,7 +169,6 @@ const uuid = ref('');
|
|||
const getCaptchaCode = async () => {
|
||||
PLUG.getCaptchaCode((data)=>{
|
||||
codeImg.value = data.img;
|
||||
console.log(codeImg.value);
|
||||
uuid.value = data.uuid;
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="admin-content-review">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="review-stats">
|
||||
<div class="stat-card">
|
||||
<!-- <div class="stat-card">
|
||||
<div class="stat-icon total">
|
||||
<el-icon><Document /></el-icon>
|
||||
</div>
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
<div class="stat-number">{{ reviewStats.total }}</div>
|
||||
<div class="stat-label">{{ t('admin.review.stats.total') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon pending">
|
||||
|
|
@ -42,17 +42,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选和搜索 -->
|
||||
<div class="review-filters">
|
||||
<div class="filter-group">
|
||||
<el-select v-model="selectedStatus" :placeholder="t('admin.review.status')" clearable>
|
||||
<el-option :label="t('admin.review.statusOptions.pending')" value="pending" />
|
||||
<el-option :label="t('admin.review.statusOptions.approved')" value="approved" />
|
||||
<el-option :label="t('admin.review.statusOptions.rejected')" value="rejected" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="search-group">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
|
|
@ -70,12 +61,14 @@
|
|||
</div>
|
||||
|
||||
<!-- 审核列表 -->
|
||||
<div class="review-table">
|
||||
<div class="review-table" :style="{ height: tableHeight + 'px' }">
|
||||
<el-table
|
||||
:data="filteredReviewList"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
v-loading="loading"
|
||||
:max-height="tableHeight"
|
||||
:height="tableHeight"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="creatorName" :label="t('admin.review.creator')" min-width="140" />
|
||||
|
|
@ -107,19 +100,11 @@
|
|||
</el-table-column>
|
||||
<el-table-column prop="status" :label="t('admin.review.status')" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusTagType(row.status)">
|
||||
{{ t(`admin.review.statusOptions.${row.status}`) }}
|
||||
<el-tag :type="getStatusTagType(row).status">
|
||||
{{ t(`${getStatusTagType(row).label}`) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="rejectionReason" :label="t('admin.review.rejectionReason')" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.status === 'rejected'" class="rejection-text">
|
||||
{{ row.rejectionReason || '-' }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" :label="t('admin.review.createTime')" min-width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.createTime) }}
|
||||
|
|
@ -135,7 +120,6 @@
|
|||
size="small"
|
||||
type="primary"
|
||||
@click="approveReview(row)"
|
||||
v-if="row.status === 'pending'"
|
||||
>
|
||||
{{ t('admin.review.approve') }}
|
||||
</el-button>
|
||||
|
|
@ -143,7 +127,6 @@
|
|||
size="small"
|
||||
type="danger"
|
||||
@click="rejectReview(row)"
|
||||
v-if="row.status === 'pending'"
|
||||
>
|
||||
{{ t('admin.review.reject') }}
|
||||
</el-button>
|
||||
|
|
@ -270,6 +253,8 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { orderStatus } from '@deotaland/utils'
|
||||
import {AdminOrders} from './AdminOrders/AdminOrders'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
|
@ -285,7 +270,7 @@ import {
|
|||
ZoomOut,
|
||||
Link
|
||||
} from '@element-plus/icons-vue'
|
||||
|
||||
const adminOrders = new AdminOrders();
|
||||
// 引入 useRouter
|
||||
import { useRouter } from 'vue-router'
|
||||
import ModelViewer from '@/components/common/ModelViewer.vue'
|
||||
|
|
@ -298,8 +283,8 @@ const loading = ref(false)
|
|||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const totalReviews = ref(0)
|
||||
const selectedStatus = ref('')
|
||||
const searchQuery = ref('')
|
||||
const tableHeight = ref(600)
|
||||
|
||||
// 对话框状态
|
||||
const previewImageVisible = ref(false)
|
||||
|
|
@ -326,74 +311,49 @@ const rejectRules = {
|
|||
// 选中行数据
|
||||
const selectedReview = ref(null)
|
||||
|
||||
// 统计数据
|
||||
const reviewStats = ref({
|
||||
total: 156,
|
||||
pending: 23,
|
||||
approved: 108,
|
||||
rejected: 25
|
||||
})
|
||||
|
||||
// 模拟审核数据
|
||||
const reviewList = ref([
|
||||
{
|
||||
id: 1,
|
||||
creatorName: t('admin.review.creatorStudioA'),
|
||||
thumbnail: '',
|
||||
orderPrice: 299.99,
|
||||
status: 'pending',
|
||||
rejectionReason: '',
|
||||
createTime: '2025-11-18 10:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
creatorName: t('admin.review.creatorStudioB'),
|
||||
thumbnail: '',
|
||||
orderPrice: 459.99,
|
||||
status: 'approved',
|
||||
rejectionReason: '',
|
||||
createTime: '2025-11-18 09:15:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
creatorName: t('admin.review.creatorStudioC'),
|
||||
thumbnail: '',
|
||||
orderPrice: 189.99,
|
||||
status: 'rejected',
|
||||
rejectionReason: t('admin.review.nonComplianceReason'),
|
||||
createTime: '2025-11-18 08:45:00'
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
// 计算属性
|
||||
const filteredReviewList = computed(() => {
|
||||
let list = reviewList.value
|
||||
|
||||
if (selectedStatus.value) {
|
||||
list = list.filter(item => item.status === selectedStatus.value)
|
||||
let list = ordersList.value.map((item) =>{
|
||||
return {
|
||||
...item,
|
||||
creatorName: (item.order_info?.shipping?.firstName || '')+(item.order_info?.shipping?.lastName || '') ,
|
||||
thumbnail: item.order_info?.modelData?.imageUrl || '',
|
||||
orderPrice: item.actual_amount,
|
||||
createTime: item.created_at,
|
||||
modelUrl: item.order_info?.modelData?.modelUrl || '',
|
||||
}
|
||||
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
list = list.filter(item =>
|
||||
item.creatorName.toLowerCase().includes(query)
|
||||
)
|
||||
}
|
||||
|
||||
totalReviews.value = list.length
|
||||
return list.slice(
|
||||
(currentPage.value - 1) * pageSize.value,
|
||||
currentPage.value * pageSize.value
|
||||
)
|
||||
});
|
||||
return list
|
||||
|
||||
})
|
||||
// 统计数据
|
||||
const reviewStats = ref({
|
||||
total: 0,
|
||||
pending: 0,
|
||||
approved: 0,
|
||||
rejected: 0
|
||||
})
|
||||
const getOrderStatistics = async () => {
|
||||
adminOrders.getOrderStatistics().then(res=>{
|
||||
if(res.code==0){
|
||||
let data = res.data.approve_maps
|
||||
reviewStats.value = {
|
||||
total: data.total_approve_count,
|
||||
pending: data.pending_count,
|
||||
approved: data.pass_count,
|
||||
rejected: data.failure_count,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 方法
|
||||
const refresh = () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
loading.value = false
|
||||
ElMessage.success(t('admin.common.refreshSuccess'))
|
||||
}, 1000)
|
||||
init();
|
||||
}
|
||||
|
||||
const handleSizeChange = (size) => {
|
||||
|
|
@ -403,6 +363,7 @@ const handleSizeChange = (size) => {
|
|||
|
||||
const handleCurrentChange = (page) => {
|
||||
currentPage.value = page
|
||||
init();
|
||||
}
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
|
|
@ -420,12 +381,7 @@ const formatDate = (dateString) => {
|
|||
}
|
||||
|
||||
const getStatusTagType = (status) => {
|
||||
const typeMap = {
|
||||
pending: 'warning',
|
||||
approved: 'success',
|
||||
rejected: 'danger'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
return orderStatus.getOrderStatusOptions(status)
|
||||
}
|
||||
|
||||
const previewThumbnail = (imageUrl) => {
|
||||
|
|
@ -467,12 +423,10 @@ const previewModel = (review) => {
|
|||
// 设置3D模型URL,如果review中有modelUrl则使用,否则使用默认模型
|
||||
if (review.modelUrl) {
|
||||
currentModelUrl.value = review.modelUrl
|
||||
} else {
|
||||
currentModelUrl.value = '/src/assets/demo/model.glb'
|
||||
}
|
||||
preview3DVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 审批同意审核
|
||||
const approveReview = async (review) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
|
|
@ -484,19 +438,13 @@ const approveReview = async (review) => {
|
|||
type: 'success'
|
||||
}
|
||||
)
|
||||
|
||||
// 更新状态
|
||||
review.status = 'approved'
|
||||
reviewStats.value.pending--
|
||||
reviewStats.value.approved++
|
||||
|
||||
ElMessage.success(t('admin.review.approveSuccess'))
|
||||
|
||||
// 跳转到拆件订单页面
|
||||
setTimeout(() => {
|
||||
router.push('/admin/disassembly-orders')
|
||||
}, 1500)
|
||||
|
||||
adminOrders.updateOrderStatus(1,'',{id:review.id}).then(res=>{
|
||||
if(res.code==0){
|
||||
ElMessage.success(t('admin.review.approveSuccess'))
|
||||
// 跳转到拆件订单页面
|
||||
router.push(`/admin/disassembly-orders?order_no=${review.order_no}`)
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
// 用户取消
|
||||
}
|
||||
|
|
@ -510,27 +458,85 @@ const rejectReview = (review) => {
|
|||
|
||||
const confirmRejectReview = async () => {
|
||||
if (!rejectFormRef.value) return
|
||||
|
||||
try {
|
||||
await rejectFormRef.value.validate()
|
||||
|
||||
// 更新状态
|
||||
selectedReview.value.status = 'rejected'
|
||||
selectedReview.value.rejectionReason = rejectForm.value.reason
|
||||
reviewStats.value.pending--
|
||||
reviewStats.value.rejected++
|
||||
|
||||
rejectDialogVisible.value = false
|
||||
ElMessage.success(t('admin.review.rejectSuccess'))
|
||||
adminOrders.updateOrderStatus(0,rejectForm.value.reason,{id:selectedReview.value.id}).then(res=>{
|
||||
if(res.code==0){
|
||||
ElMessage.success(t('admin.review.rejectSuccess'))
|
||||
// 跳转到拆件订单页面
|
||||
init();
|
||||
rejectDialogVisible.value = false;
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
// 验证失败
|
||||
}
|
||||
}
|
||||
const ortherjson = ref({
|
||||
order_status:[0],
|
||||
refund_status:[0],
|
||||
payment_status:[1]
|
||||
});
|
||||
const ordersList = ref([]);
|
||||
const getList = ()=>{
|
||||
loading.value = true
|
||||
let params = {
|
||||
...ortherjson.value,
|
||||
page_size: pageSize.value,
|
||||
page: currentPage.value,
|
||||
order_no:searchQuery.value
|
||||
}
|
||||
adminOrders.getOrderList(params).then(res=>{
|
||||
let data = res.data || [];
|
||||
ordersList.value = data.items;
|
||||
loading.value = false
|
||||
totalReviews.value = data.total;
|
||||
// ordersList.value = [...data.items,...data.items];
|
||||
// orderStats.value.pending = data.total || 0;
|
||||
})
|
||||
}
|
||||
const init = ()=>{
|
||||
getOrderStatistics();
|
||||
getList()
|
||||
}
|
||||
|
||||
|
||||
// 根据窗口大小调整表格高度
|
||||
const updateTableHeight = () => {
|
||||
const windowHeight = window.innerHeight
|
||||
const headerHeight = 200 // 估算头部区域高度
|
||||
const paginationHeight = 80 // 估算分页区域高度
|
||||
const padding = 40 // 页面padding
|
||||
|
||||
if (window.innerWidth <= 768) {
|
||||
// 移动端使用较小的高度
|
||||
tableHeight.value = Math.max(400, windowHeight - headerHeight - paginationHeight - padding)
|
||||
} else {
|
||||
// 桌面端使用标准高度
|
||||
tableHeight.value = Math.min(600, windowHeight - headerHeight - paginationHeight - padding-160)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 检查路由参数中的order_no
|
||||
const routeQuery = router.currentRoute.value.query
|
||||
if (routeQuery.order_no) {
|
||||
searchQuery.value = routeQuery.order_no
|
||||
}
|
||||
|
||||
// 页面加载时的初始化操作
|
||||
init();
|
||||
|
||||
// 设置初始表格高度
|
||||
updateTableHeight()
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', updateTableHeight)
|
||||
})
|
||||
|
||||
// 组件卸载时移除监听器
|
||||
import { onUnmounted } from 'vue'
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', updateTableHeight)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
@ -658,6 +664,32 @@ onMounted(() => {
|
|||
.review-table :deep(.el-table) {
|
||||
width: 100% !important;
|
||||
table-layout: auto;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.review-table :deep(.el-table__body-wrapper) {
|
||||
overflow-y: auto;
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
|
||||
/* 优化滚动条样式 */
|
||||
.review-table :deep(.el-table__body-wrapper::-webkit-scrollbar) {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.review-table :deep(.el-table__body-wrapper::-webkit-scrollbar-track) {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.review-table :deep(.el-table__body-wrapper::-webkit-scrollbar-thumb) {
|
||||
background: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.review-table :deep(.el-table__body-wrapper::-webkit-scrollbar-thumb:hover) {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
|
||||
.review-table :deep(.el-table__header) {
|
||||
|
|
@ -672,6 +704,7 @@ onMounted(() => {
|
|||
padding: 8px 12px;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.review-table :deep(.el-table__row) {
|
||||
|
|
@ -694,6 +727,7 @@ onMounted(() => {
|
|||
}
|
||||
|
||||
.review-table :deep(.el-table__header th .cell) {
|
||||
text-align: center;
|
||||
white-space: nowrap !important;
|
||||
word-break: keep-all;
|
||||
overflow: hidden;
|
||||
|
|
@ -706,7 +740,7 @@ onMounted(() => {
|
|||
gap: 6px;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
|
|
@ -880,11 +914,17 @@ onMounted(() => {
|
|||
.review-table :deep(.el-table) {
|
||||
width: auto !important;
|
||||
min-width: 800px;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.review-table :deep(.el-table__body-wrapper) {
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
|
||||
.review-table :deep(.el-table__cell) {
|
||||
padding: 6px 8px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
|
|
|
|||
|
|
@ -267,6 +267,21 @@ const getActivityIcon = (type) => {
|
|||
return icons[type] || Bell
|
||||
}
|
||||
|
||||
// 刷新数据方法
|
||||
const refreshData = () => {
|
||||
// 模拟数据刷新
|
||||
stats.value.totalUsers += Math.floor(Math.random() * 10)
|
||||
stats.value.totalOrders += Math.floor(Math.random() * 5)
|
||||
stats.value.pendingReviews = Math.floor(Math.random() * 100)
|
||||
stats.value.revenue += Math.floor(Math.random() * 1000)
|
||||
|
||||
// 刷新销售数据
|
||||
salesData.value = salesData.value.map(item => ({
|
||||
...item,
|
||||
value: item.value + Math.floor(Math.random() * 2000) - 1000
|
||||
}))
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 模拟数据刷新
|
||||
setInterval(() => {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ export class AdminDisassemblyDetail {
|
|||
constructor() {
|
||||
}
|
||||
//拆件
|
||||
async disassemble(imgurl,callback,errorCallback) {
|
||||
async disassemble(imgurl,callback,errorCallback,config) {
|
||||
try{
|
||||
const result = await gimiServer.handleGenerateImage(imgurl, prompt.Hairseparation)
|
||||
const result = await gimiServer.handleGenerateImage(imgurl, prompt.Hairseparation,config)
|
||||
console.log('resultresult',result);
|
||||
callback(result)
|
||||
}catch(error){
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@
|
|||
<div class="timeline-progress">
|
||||
<span class="progress-text">进度</span>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{ width: `${(currentStep / 4) * 100}%` }"></div>
|
||||
<div class="progress-fill" :style="{ width: `${(currentStep / 3) * 100}%` }"></div>
|
||||
</div>
|
||||
<span class="progress-step">{{ currentStep }}/4</span>
|
||||
<span class="progress-step">{{ currentStep }}/3</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
<div v-if="currentStep >= 1" class="step-content">
|
||||
<div class="preview-container-horizontal">
|
||||
<div class="preview-images-horizontal">
|
||||
<div class="image-item-horizontal" @click="previewImage(thumbnailUrl)">
|
||||
<div v-if="thumbnailUrl" 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">
|
||||
|
|
@ -84,8 +84,8 @@
|
|||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="startDisassembly"
|
||||
:loading="disassemblyLoading"
|
||||
@click="startDisassembly"
|
||||
>
|
||||
{{ $t('admin.disassemblyOrders.detail.disassembly') }}
|
||||
</el-button>
|
||||
|
|
@ -152,22 +152,25 @@
|
|||
<div v-if="currentStep >= 3" class="step-content">
|
||||
<div class="generated-models">
|
||||
<div
|
||||
v-for="(imgurl, index) in generatedModels"
|
||||
:key="index"
|
||||
v-for="(item, index) in generatedModels"
|
||||
:key="item.id"
|
||||
class="model-item"
|
||||
>
|
||||
<ModelCom
|
||||
@preview="previewModel"
|
||||
:img-url="imgurl"
|
||||
:img-url="item.image"
|
||||
:task-id="item.taskId"
|
||||
width="100%"
|
||||
height="150px"
|
||||
:project-id="orderDetail.projectId"
|
||||
@save="(result)=>saveModel(index,result)"
|
||||
:show-delete="true"
|
||||
@delete="deleteModel(index)"
|
||||
/>
|
||||
<div class="image-label">模型 {{ index + 1 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-actions">
|
||||
<!-- <div class="step-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleProcessingComplete"
|
||||
|
|
@ -175,13 +178,13 @@
|
|||
>
|
||||
加工完成
|
||||
</el-button>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
|
||||
<!-- 第四步:工期信息 -->
|
||||
<el-timeline-item
|
||||
<!-- <el-timeline-item
|
||||
:color="currentStep >= 4 ? '#6B46C1' : '#e4e7ed'"
|
||||
size="large"
|
||||
>
|
||||
|
|
@ -230,7 +233,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline-item> -->
|
||||
</el-timeline>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -276,9 +279,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { ref, reactive, onMounted, onUnmounted, watch} from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {AdminOrders} from '../AdminOrders/AdminOrders.js'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
ArrowLeft,
|
||||
|
|
@ -293,10 +297,12 @@ import {
|
|||
import ModelViewer from '@/components/common/ModelViewer.vue'
|
||||
import ModelCom from '@/components/modelCom/index.vue'
|
||||
import { AdminDisassemblyDetail } from './AdminDisassemblyDetail.js';
|
||||
import { MeshyServer } from '@deotaland/utils';
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const Plug = new AdminDisassemblyDetail();
|
||||
const adminOrders = new AdminOrders();
|
||||
// 订单详情
|
||||
const orderDetail = ref({
|
||||
orderNumber: '',
|
||||
|
|
@ -306,6 +312,10 @@ const orderDetail = ref({
|
|||
status: 'pending',
|
||||
processingCompleteTime: null
|
||||
})
|
||||
const saveModel = (index,result)=>{
|
||||
console.log(index,result,'currentStepcurrentStep');
|
||||
generatedModels.value[index].taskId = result;
|
||||
}
|
||||
|
||||
// 当前步骤
|
||||
const currentStep = ref(1)
|
||||
|
|
@ -315,7 +325,6 @@ const disassemblyLoading = ref(false)
|
|||
const generateStep2Loading = ref(false)
|
||||
const processingCompleteLoading = ref(false)
|
||||
const submitShippingLoading = ref(false)
|
||||
|
||||
// 预览相关
|
||||
const imagePreviewVisible = ref(false)
|
||||
const modelPreviewVisible = ref(false)
|
||||
|
|
@ -337,29 +346,35 @@ const modelInitializationStates = ref([])
|
|||
const modelInitializationProgress = ref([])
|
||||
|
||||
// 资源路径
|
||||
const thumbnailUrl = ref('/src/assets/demo/suoluetu.png')
|
||||
const modelUrl = ref('/src/assets/demo/model.glb')
|
||||
const generatedModelUrl = ref('/src/assets/demo/model.glb')
|
||||
const thumbnailUrl = ref('');
|
||||
const modelUrl = ref('')
|
||||
const generatedModelUrl = ref('')
|
||||
// 存储多个生成的模型
|
||||
const generatedModels = ref([])
|
||||
// 拆件后的图片(模拟)
|
||||
const disassembledImages = ref([
|
||||
])
|
||||
|
||||
// 发货表单
|
||||
const shippingForm = reactive({
|
||||
logisticsCompany: '',
|
||||
trackingNumber: '',
|
||||
shippingAddress: '',
|
||||
contactPhone: '',
|
||||
notes: ''
|
||||
})
|
||||
|
||||
watch(()=>[generatedModels.value,disassembledImages.value],()=>{
|
||||
saveDisassemblyProgress()
|
||||
}, {deep: true})
|
||||
// 预览模型
|
||||
const previewModel = (url) => {
|
||||
previewModelUrl.value = url
|
||||
modelPreviewVisible.value = true
|
||||
}
|
||||
//保存拆件进度
|
||||
const saveDisassemblyProgress = () => {
|
||||
let project_details = {
|
||||
...orderData.value.project_details,
|
||||
disassembledImages: disassembledImages.value,
|
||||
generatedModels: generatedModels.value
|
||||
};
|
||||
let params = {
|
||||
id:orderData.value.id,
|
||||
project_details:project_details
|
||||
}
|
||||
adminOrders.saveDisassemblyProgress(params);
|
||||
}
|
||||
|
||||
// 获取状态标签类型
|
||||
const getStatusType = (status) => {
|
||||
|
|
@ -404,21 +419,10 @@ const formatDate = (dateString) => {
|
|||
})
|
||||
}
|
||||
|
||||
// 计算总天数
|
||||
const calculateTotalDays = () => {
|
||||
if (!orderDetail.value.createTime || !orderDetail.value.processingCompleteTime) {
|
||||
return 0
|
||||
}
|
||||
const startDate = new Date(orderDetail.value.createTime)
|
||||
const endDate = new Date(orderDetail.value.processingCompleteTime)
|
||||
const diffTime = Math.abs(endDate - startDate)
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||
return diffDays
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
router.push('/admin/disassembly-orders')
|
||||
router.back();
|
||||
}
|
||||
|
||||
// 预览图片
|
||||
|
|
@ -451,28 +455,31 @@ const handleImageUpload = (file) => {
|
|||
}
|
||||
reader.readAsDataURL(file.raw || file)
|
||||
}
|
||||
|
||||
// 取消图片上传
|
||||
const cancelImageUpload = () => {
|
||||
showImageUpload.value = false
|
||||
}
|
||||
|
||||
const startDisassembly = () => {
|
||||
for(let i = 0;i<4;i++){
|
||||
for(let i = 0;i<1;i++){
|
||||
handleDisassembly();
|
||||
}
|
||||
}
|
||||
// 执行拆件
|
||||
const handleDisassembly = () => {
|
||||
disassemblyLoading.value = true
|
||||
Plug.disassemble(thumbnailUrl.value, (result) => {
|
||||
Plug.disassemble(thumbnailUrl.value,(result) => {
|
||||
// 更新拆件图片
|
||||
disassembledImages.value.push(result)
|
||||
currentStep.value = 2
|
||||
if(currentStep.value==1){
|
||||
currentStep.value = 2
|
||||
}
|
||||
disassemblyLoading.value = false
|
||||
},(error) => {
|
||||
disassemblyLoading.value = false
|
||||
ElMessage.error('拆件失败,请稍后重试')
|
||||
},{
|
||||
project_id:orderDetail.value.projectId,
|
||||
role:'admin',
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -488,11 +495,6 @@ const deleteModel = (index) => {
|
|||
}
|
||||
).then(() => {
|
||||
generatedModels.value.splice(index, 1)
|
||||
// 清理对应的加载状态
|
||||
modelLoadingStates.value.splice(index, 1)
|
||||
modelLoadingProgress.value.splice(index, 1)
|
||||
modelInitializationStates.value.splice(index, 1)
|
||||
modelInitializationProgress.value.splice(index, 1)
|
||||
ElMessage.success('模型已删除')
|
||||
}).catch(() => {
|
||||
// 用户取消删除
|
||||
|
|
@ -531,68 +533,47 @@ const handleProcessingComplete = () => {
|
|||
// 用户取消
|
||||
})
|
||||
}
|
||||
|
||||
// 导出模型
|
||||
const handleExport = (format) => {
|
||||
ElMessage.success(`${t('admin.disassemblyOrders.detail.messages.exportStart')} ${format}`)
|
||||
// 这里应该调用实际的导出API
|
||||
}
|
||||
|
||||
// 处理发货
|
||||
const handleShipping = () => {
|
||||
currentStep.value = 4
|
||||
}
|
||||
|
||||
// 提交发货信息
|
||||
const submitShipping = () => {
|
||||
// 验证表单
|
||||
if (!shippingForm.logisticsCompany || !shippingForm.trackingNumber ||
|
||||
!shippingForm.shippingAddress || !shippingForm.contactPhone) {
|
||||
ElMessage.warning(t('admin.disassemblyOrders.detail.messages.requiredFields'))
|
||||
return
|
||||
}
|
||||
|
||||
submitShippingLoading.value = true
|
||||
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
// 这里应该调用实际的提交API
|
||||
currentStep.value = 4
|
||||
submitShippingLoading.value = false
|
||||
ElMessage.success(t('admin.disassemblyOrders.detail.messages.shippingSuccess'))
|
||||
|
||||
// 返回订单列表
|
||||
setTimeout(() => {
|
||||
router.push('/admin/disassembly-orders')
|
||||
}, 1500)
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
const orderId = ref('');
|
||||
const orderData = ref({});
|
||||
// 组件挂载时获取订单信息
|
||||
onMounted(() => {
|
||||
const orderId = route.params.id
|
||||
// 这里应该根据订单ID获取订单详情
|
||||
console.log('Order ID:', orderId)
|
||||
|
||||
// 模拟获取订单详情
|
||||
// 在实际应用中,这里应该调用API获取订单详情
|
||||
onMounted(async () => {
|
||||
MeshyServer.pollingEnabled = true;
|
||||
orderId.value = route.params.id
|
||||
const result = await adminOrders.getOrderDetail({
|
||||
id:orderId.value
|
||||
})
|
||||
let data = result.data;
|
||||
orderDetail.value = {
|
||||
orderNumber: `ORD20231100${orderId}`,
|
||||
customerName: '张三',
|
||||
modelName: '测试模型',
|
||||
createTime: '2023-11-20 10:30:00',
|
||||
orderNumber: data.order_no,
|
||||
customerName: data.order_info.shipping.firstName+' '+data.order_info.shipping.lastName,
|
||||
modelName: data.order_info.ipName,
|
||||
createTime: data.created_at,
|
||||
status: 'pending',
|
||||
processingCompleteTime: null
|
||||
processingCompleteTime: null,
|
||||
projectId:data.project_id,
|
||||
}
|
||||
thumbnailUrl.value = data.order_info.modelData.imageUrl;
|
||||
modelUrl.value = data.order_info.modelData.modelUrl;
|
||||
orderData.value = data;
|
||||
disassembledImages.value = data.project_details.disassembledImages || [];
|
||||
generatedModels.value = data.project_details.generatedModels || [];
|
||||
|
||||
// 模拟已有部分步骤完成的情况
|
||||
// 可以根据订单状态设置currentStep
|
||||
// currentStep.value = 2 // 如果已完成拆件
|
||||
// currentStep.value = 3 // 如果已生成模型
|
||||
// 根据数组长度自动判断当前步骤
|
||||
if (generatedModels.value.length > 0) {
|
||||
// 如果有生成的模型,直接进入第3步
|
||||
currentStep.value = 3;
|
||||
} else if (disassembledImages.value.length > 0) {
|
||||
// 如果有拆件图片但没有模型,进入第2步
|
||||
currentStep.value = 2;
|
||||
} else {
|
||||
// 否则保持在第1步
|
||||
currentStep.value = 1;
|
||||
}
|
||||
})
|
||||
|
||||
// 组件卸载时清理资源
|
||||
onUnmounted(() => {
|
||||
MeshyServer.pollingEnabled = false;
|
||||
// 清理预览状态
|
||||
imagePreviewVisible.value = false
|
||||
modelPreviewVisible.value = false
|
||||
|
|
@ -631,7 +612,11 @@ const hideImageActions = () => {
|
|||
}
|
||||
// 从单张图片生成模型并进入第三步
|
||||
const generateModelFromImage = async (image) => {
|
||||
generatedModels.value.push(image);
|
||||
generatedModels.value.push({
|
||||
id:new Date().getTime(),
|
||||
image:image,
|
||||
taskId:''
|
||||
});
|
||||
// 如果是第一次生成模型,进入第三步
|
||||
if (currentStep.value < 3) {
|
||||
currentStep.value = 3
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@
|
|||
<div class="stat-label">{{ t('admin.disassemblyOrders.stats.pending') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<!-- <div class="stat-card">
|
||||
<div class="stat-icon processing">
|
||||
<el-icon><Loading /></el-icon>
|
||||
</div>
|
||||
|
|
@ -29,49 +28,21 @@
|
|||
<div class="stat-number">{{ disassemblyStats.processing }}</div>
|
||||
<div class="stat-label">{{ t('admin.disassemblyOrders.stats.processing') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 筛选搜索区域 -->
|
||||
<div class="disassembly-filters">
|
||||
<div class="filter-group">
|
||||
<el-select
|
||||
v-model="selectedStatus"
|
||||
:placeholder="$t('admin.disassemblyOrders.filters.status')"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
:label="$t('admin.disassemblyOrders.filters.allStatus')"
|
||||
value=""
|
||||
/>
|
||||
<el-option
|
||||
:label="$t('admin.disassemblyOrders.detail.statusOptions.pending')"
|
||||
value="pending"
|
||||
/>
|
||||
<el-option
|
||||
:label="$t('admin.disassemblyOrders.detail.statusOptions.processing')"
|
||||
value="processing"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="search-group">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
:placeholder="$t('admin.disassemblyOrders.filters.searchPlaceholder')"
|
||||
:placeholder="$t('admin.review.search')"
|
||||
prefix-icon="Search"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="filter-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="fetchOrders"
|
||||
>
|
||||
<el-icon><Search /></el-icon>
|
||||
{{ $t('admin.disassemblyOrders.filters.filter') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="resetFilters"
|
||||
>
|
||||
|
|
@ -112,9 +83,9 @@
|
|||
:label="$t('admin.disassemblyOrders.list.status')"
|
||||
width="100"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
<template #default="{row}">
|
||||
<el-tag :type="getStatusTagType(row).status">
|
||||
{{ t(`${getStatusTagType(row).label}`) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
|
@ -144,6 +115,14 @@
|
|||
>
|
||||
{{ $t('admin.disassemblyOrders.list.disassembly') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
:icon="Check"
|
||||
@click="handleCompleteDisassembly(scope.row)"
|
||||
>
|
||||
{{ $t('admin.disassemblyOrders.list.completeDisassembly') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
|
@ -156,7 +135,7 @@
|
|||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="filteredOrdersList.length"
|
||||
:total="totalReviews"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
|
|
@ -166,20 +145,24 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { orderStatus } from '@deotaland/utils'
|
||||
import {AdminOrders} from './AdminOrders/AdminOrders'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Refresh, Search, Document, Clock, Loading, Check, Van, Tools } from '@element-plus/icons-vue'
|
||||
|
||||
const adminOrders = new AdminOrders();
|
||||
const getStatusTagType = (status) => {
|
||||
return orderStatus.getOrderStatusOptions(status)
|
||||
}
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
|
||||
const totalReviews = ref(0)
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const ordersList = ref([])
|
||||
const selectedStatus = ref('')
|
||||
const searchQuery = ref('')
|
||||
|
|
@ -191,92 +174,46 @@ const disassemblyStats = ref({
|
|||
processing: 0
|
||||
})
|
||||
|
||||
// 模拟数据
|
||||
const mockOrders = [
|
||||
{
|
||||
id: 1,
|
||||
orderNumber: 'ORD202311001',
|
||||
creatorName: '张三',
|
||||
status: 'pending',
|
||||
createTime: '2023-11-20 10:30:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
orderNumber: 'ORD202311002',
|
||||
creatorName: '李四',
|
||||
status: 'processing',
|
||||
createTime: '2023-11-20 09:15:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
orderNumber: 'ORD202311003',
|
||||
creatorName: '王五',
|
||||
status: 'pending',
|
||||
createTime: '2023-11-19 16:45:00'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
orderNumber: 'ORD202311004',
|
||||
creatorName: '赵六',
|
||||
status: 'processing',
|
||||
createTime: '2023-11-19 14:20:00'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
orderNumber: 'ORD202311005',
|
||||
creatorName: '钱七',
|
||||
status: 'pending',
|
||||
createTime: '2023-11-18 11:10:00'
|
||||
}
|
||||
]
|
||||
|
||||
// 计算属性 - 筛选后的订单列表
|
||||
const filteredOrdersList = computed(() => {
|
||||
let filtered = [...ordersList.value]
|
||||
|
||||
// 按状态筛选
|
||||
if (selectedStatus.value) {
|
||||
filtered = filtered.filter(order => order.status === selectedStatus.value)
|
||||
}
|
||||
|
||||
// 按搜索关键词筛选
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
filtered = filtered.filter(order =>
|
||||
order.orderNumber.toLowerCase().includes(query) ||
|
||||
order.creatorName.toLowerCase().includes(query)
|
||||
)
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
const getOrderStatistics = async () => {
|
||||
adminOrders.getOrderStatistics().then(res=>{
|
||||
if(res.code==0){
|
||||
let data = res.data.make_maps
|
||||
disassemblyStats.value = {
|
||||
total:res.data.total,
|
||||
pending: data.pending_count,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleString()
|
||||
}
|
||||
|
||||
const ortherjson = ref({
|
||||
order_status:[2],
|
||||
refund_status:[0],
|
||||
payment_status:[1]
|
||||
});
|
||||
// 获取订单列表
|
||||
const fetchOrders = () => {
|
||||
loading.value = true
|
||||
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
// 这里应该是API调用,现在使用模拟数据
|
||||
ordersList.value = mockOrders
|
||||
total.value = mockOrders.length
|
||||
|
||||
// 计算统计数据
|
||||
const stats = {
|
||||
total: mockOrders.length,
|
||||
pending: mockOrders.filter(order => order.status === 'pending').length,
|
||||
processing: mockOrders.filter(order => order.status === 'processing').length
|
||||
}
|
||||
disassemblyStats.value = stats
|
||||
|
||||
loading.value = false
|
||||
}, 500)
|
||||
loading.value = true
|
||||
let params = {
|
||||
...ortherjson.value,
|
||||
page_size: pageSize.value,
|
||||
page: currentPage.value,
|
||||
order_no:searchQuery.value
|
||||
}
|
||||
adminOrders.getOrderList(params).then(res=>{
|
||||
let data = res.data || [];
|
||||
ordersList.value = data.items;
|
||||
loading.value = false;
|
||||
totalReviews.value = data.total || 0;
|
||||
// ordersList.value = [...data.items,...data.items];
|
||||
// orderStats.value.pending = data.total || 0;
|
||||
})
|
||||
}
|
||||
|
||||
// 刷新订单列表
|
||||
|
|
@ -292,42 +229,19 @@ const resetFilters = () => {
|
|||
fetchOrders()
|
||||
}
|
||||
|
||||
// 计算属性 - 分页后的订单列表
|
||||
//订单列表
|
||||
const paginatedOrders = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize.value
|
||||
const end = start + pageSize.value
|
||||
return filteredOrdersList.value.slice(start, end)
|
||||
let list = ordersList.value.map(item=>{
|
||||
return {
|
||||
...item,
|
||||
orderNumber:item.order_no,
|
||||
creatorName:(item.order_info.shipping.firstName||'')+(item.order_info.shipping.lastName||''),
|
||||
createTime:item.created_at
|
||||
}
|
||||
})
|
||||
return list;
|
||||
})
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'pending': t('admin.disassemblyOrders.detail.statusOptions.pending'),
|
||||
'processing': t('admin.disassemblyOrders.detail.statusOptions.processing')
|
||||
}
|
||||
return statusMap[status] || status
|
||||
}
|
||||
|
||||
// 获取拆解类型文本
|
||||
const getDisassemblyTypeText = (type) => {
|
||||
const typeMap = {
|
||||
'full': t('admin.disassemblyOrders.detail.typeOptions.full'),
|
||||
'partial': t('admin.disassemblyOrders.detail.typeOptions.partial'),
|
||||
'custom': t('admin.disassemblyOrders.detail.typeOptions.custom')
|
||||
}
|
||||
return typeMap[type] || type
|
||||
}
|
||||
|
||||
// 获取拆解类型标签类型
|
||||
const getDisassemblyTypeTag = (type) => {
|
||||
const typeMap = {
|
||||
'full': 'success',
|
||||
'partial': 'warning',
|
||||
'custom': 'info'
|
||||
}
|
||||
return typeMap[type] || 'info'
|
||||
}
|
||||
|
||||
// 处理拆件操作
|
||||
const handleDisassemble = (order) => {
|
||||
// 跳转到拆件页面
|
||||
|
|
@ -337,15 +251,37 @@ const handleDisassemble = (order) => {
|
|||
})
|
||||
}
|
||||
|
||||
// 获取状态标签类型
|
||||
const getStatusType = (status) => {
|
||||
const statusMap = {
|
||||
pending: 'warning',
|
||||
processing: 'primary'
|
||||
// 处理完成拆件操作
|
||||
const handleCompleteDisassembly = async (order) => {
|
||||
try {
|
||||
// 显示确认对话框
|
||||
await ElMessageBox.confirm(
|
||||
t('admin.disassemblyOrders.messages.completeDisassemblyConfirm'),
|
||||
t('admin.disassemblyOrders.messages.completeDisassemblyTitle'),
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
adminOrders.completeDisassembly({
|
||||
id:order.id
|
||||
}).then(res=>{
|
||||
if(res.code==0){
|
||||
ElMessage.success(t('admin.disassemblyOrders.messages.completeDisassemblySuccess'))
|
||||
fetchOrders()
|
||||
}
|
||||
})
|
||||
// 刷新订单列表
|
||||
fetchOrders()
|
||||
} catch (error) {
|
||||
// 用户取消操作或发生错误
|
||||
if (error !== 'cancel') {
|
||||
console.error('完成拆件失败:', error)
|
||||
ElMessage.error(t('admin.disassemblyOrders.messages.completeDisassemblyError'))
|
||||
}
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
}
|
||||
|
||||
// 分页相关
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
|
|
@ -359,7 +295,13 @@ const handleCurrentChange = (val) => {
|
|||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
// 获取路由参数中的order_no并赋值给searchQuery
|
||||
const route = useRoute()
|
||||
if (route.query.order_no) {
|
||||
searchQuery.value = route.query.order_no
|
||||
}
|
||||
fetchOrders()
|
||||
getOrderStatistics()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
@ -497,6 +439,7 @@ onMounted(() => {
|
|||
padding: 8px 12px;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.disassembly-table :deep(.el-table__row) {
|
||||
|
|
@ -513,7 +456,7 @@ onMounted(() => {
|
|||
font-weight: 600;
|
||||
border-bottom: 2px solid #e5e7eb;
|
||||
white-space: nowrap !important;
|
||||
text-align: center;
|
||||
text-align: center !important;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -523,6 +466,15 @@ onMounted(() => {
|
|||
word-break: keep-all;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
/* 表格内容居中 */
|
||||
.disassembly-table :deep(.el-table__body td .cell) {
|
||||
text-align: center !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 操作按钮容器 */
|
||||
|
|
@ -531,13 +483,26 @@ onMounted(() => {
|
|||
gap: 6px;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.actions-container .el-button {
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
/* 完成拆件按钮样式 */
|
||||
.actions-container .el-button--success {
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
border: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.actions-container .el-button--success:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
|
|
@ -595,6 +560,19 @@ onMounted(() => {
|
|||
.disassembly-table :deep(.el-table) {
|
||||
min-width: 800px;
|
||||
}
|
||||
|
||||
/* 移动端操作按钮适配 */
|
||||
.actions-container {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.actions-container .el-button {
|
||||
width: 100%;
|
||||
min-width: auto;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) and (max-width: 1024px) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
import { adminApi,requestUtils} from '@deotaland/utils';
|
||||
|
||||
export class AdminOrders {
|
||||
constructor() {
|
||||
// 初始化防抖函数
|
||||
this.debouncedSaveProgress = this.createDebouncedSaveProgress();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建防抖的保存进度函数
|
||||
* 防止频繁调用API,延迟1秒执行
|
||||
*/
|
||||
createDebouncedSaveProgress() {
|
||||
let timeout;
|
||||
return (params) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(async () => {
|
||||
try {
|
||||
const result = await requestUtils.common(adminApi.default.updateOrderStatus, params);
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}, 1000); // 1秒防抖延迟
|
||||
});
|
||||
};
|
||||
}
|
||||
//获取订单详情
|
||||
getOrderDetail(params){
|
||||
return requestUtils.common(adminApi.default.getOrderDetailt,params);
|
||||
}
|
||||
//获取订单列表
|
||||
getOrderList(params){
|
||||
return requestUtils.common(adminApi.default.getOrderList,params);
|
||||
}
|
||||
//同意退款
|
||||
refundApprove(params){
|
||||
return requestUtils.common(adminApi.default.refundApprove,params);
|
||||
}
|
||||
//拒绝退款
|
||||
refundReject(params){
|
||||
return requestUtils.common(adminApi.default.refundReject,params);
|
||||
}
|
||||
//同意/拒绝订单
|
||||
updateOrderStatus(type=0,audit_reject_reason='',data){//0未通过,1已通过
|
||||
let params = {
|
||||
id:data.id,
|
||||
order_status:type==0?1:2
|
||||
}
|
||||
if(audit_reject_reason&&type==0){
|
||||
params.audit_reject_reason = audit_reject_reason;
|
||||
}
|
||||
return requestUtils.common(adminApi.default.updateOrderStatus,params);
|
||||
}
|
||||
//完成拆件
|
||||
completeDisassembly(data){
|
||||
let params = {
|
||||
order_status:3,
|
||||
id:data.id
|
||||
}
|
||||
return requestUtils.common(adminApi.default.updateOrderStatus,params);
|
||||
}
|
||||
//订单统计
|
||||
getOrderStatistics(){
|
||||
return requestUtils.common(adminApi.default.getOrderStatistics);
|
||||
}
|
||||
//保存拆件进度(带防抖功能)
|
||||
saveDisassemblyProgress(params){
|
||||
// 使用防抖函数来避免频繁调用API
|
||||
return this.debouncedSaveProgress(params);
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
<el-icon><Money /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-number">¥{{ orderStats.revenue.toLocaleString() }}</div>
|
||||
<div class="stat-number">¥{{ orderStats.revenue }}</div>
|
||||
<div class="stat-label">{{ t('admin.orders.stats.revenue') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -52,31 +52,11 @@
|
|||
clearable
|
||||
>
|
||||
<el-option
|
||||
:label="t('admin.orders.statusOptions.pendingConfirmation')"
|
||||
value="pendingConfirmation"
|
||||
/>
|
||||
<el-option
|
||||
:label="t('admin.orders.statusOptions.confirmed')"
|
||||
value="confirmed"
|
||||
/>
|
||||
<el-option
|
||||
:label="t('admin.orders.statusOptions.rejected')"
|
||||
value="rejected"
|
||||
/>
|
||||
<el-option
|
||||
:label="t('admin.orders.statusOptions.processing')"
|
||||
value="processing"
|
||||
/>
|
||||
<el-option
|
||||
:label="t('admin.orders.statusOptions.readyToShip')"
|
||||
value="readyToShip"
|
||||
/>
|
||||
<el-option
|
||||
:label="t('admin.orders.statusOptions.shipped')"
|
||||
value="shipped"
|
||||
v-for="value in orderStatus.selectList('2')"
|
||||
:label="t(value.label)"
|
||||
:value="value.key"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
|
|
@ -88,7 +68,6 @@
|
|||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="search-group">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
|
|
@ -98,7 +77,7 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div class="filter-actions">
|
||||
<!-- <div class="filter-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleExportOrders"
|
||||
|
|
@ -112,43 +91,45 @@
|
|||
<el-icon><Refresh /></el-icon>
|
||||
{{ t('admin.common.refresh') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<div class="orders-list">
|
||||
<el-table
|
||||
:data="filteredOrdersList"
|
||||
stripe
|
||||
:data="ordersList"
|
||||
style="width: 100%"
|
||||
height="500"
|
||||
v-loading="loading"
|
||||
@row-click="handleOrderDetail"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<!-- <el-table-column type="selection" width="55" /> -->
|
||||
<el-table-column prop="id" label="ID" width="100" />
|
||||
<el-table-column prop="orderNumber" :label="t('admin.orders.orderNumber')" width="150" />
|
||||
<el-table-column prop="customerName" :label="t('admin.orders.customer')" width="120" />
|
||||
<el-table-column prop="totalAmount" :label="t('admin.orders.total')" width="120">
|
||||
<el-table-column prop="order_no" :label="t('admin.orders.orderNumber')" width="150"/>
|
||||
<el-table-column prop="customerName" :label="t('admin.orders.customer')" width="120">
|
||||
<template #default="{ row }">
|
||||
¥{{ row.totalAmount.toFixed(2) }}
|
||||
{{ (row?.order_info?.shipping?.firstName || '-')+(row?.order_info?.shipping?.lastName || '-') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="actual_amount" :label="t('admin.orders.total')" width="120">
|
||||
<template #default="{ row }">
|
||||
¥{{ row.actual_amount.toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" :label="t('admin.orders.status')" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusTagType(row.status)">
|
||||
{{ t(`admin.orders.statusOptions.${row.status}`) }}
|
||||
<el-tag :type="getStatusTagType(row).status">
|
||||
{{ t(`${getStatusTagType(row).label}`) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="paymentMethod" :label="t('admin.orders.payment')" width="100">
|
||||
<template #default="{ row }">
|
||||
{{ t(`admin.orders.paymentOptions.${row.paymentMethod}`) }}
|
||||
</template>
|
||||
<el-table-column prop="payment_method" :label="t('admin.orders.payment')" width="100">
|
||||
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderDate" :label="t('admin.orders.date')" width="180">
|
||||
<el-table-column prop="payment_time" :label="t('admin.orders.date')" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.orderDate) }}
|
||||
{{ row.payment_time ? formatDate(row.payment_time) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('admin.orders.actions')" width="120" fixed="right">
|
||||
|
|
@ -189,26 +170,37 @@
|
|||
<h4>{{ t('admin.orders.basicInfo') }}</h4>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item :label="t('admin.orders.orderNumber')">
|
||||
{{ selectedOrder.orderNumber }}
|
||||
{{ selectedOrder.order_no }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('admin.orders.customer')">
|
||||
{{ selectedOrder.customerName }}
|
||||
{{ selectedOrder?.order_info?.shipping?.firstName || '-'+selectedOrder?.order_info?.shipping?.lastName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('admin.orders.total')">
|
||||
¥{{ selectedOrder.totalAmount.toFixed(2) }}
|
||||
¥{{ selectedOrder?.actual_amount?.toFixed(2) || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('admin.orders.status')">
|
||||
<el-tag :type="getStatusTagType(selectedOrder.status)">
|
||||
{{ t(`admin.orders.statusOptions.${selectedOrder.status}`) }}
|
||||
<el-tag :type="getStatusTagType(selectedOrder).status">
|
||||
{{ t(`${getStatusTagType(selectedOrder).label}`) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4>{{ t('admin.orders.items') }}</h4>
|
||||
<el-table :data="selectedOrder.items" stripe>
|
||||
<el-table :data="[
|
||||
{
|
||||
name: selectedOrder?.order_info?.ipName || '-',
|
||||
quantity: selectedOrder?.order_info?.quantity || '-',
|
||||
price: selectedOrder?.actual_amount || '-',
|
||||
image: selectedOrder?.order_info?.modelData?.imageUrl || '-',
|
||||
}
|
||||
]" stripe>
|
||||
<el-table-column prop="name" :label="t('admin.orders.itemName')" />
|
||||
<el-table-column prop="image" :label="t('admin.orders.image')" >
|
||||
<template #default="{ row }">
|
||||
<img :src="row.image" alt="Item Image" style="width: 50px; height: 50px; cursor: pointer;" @click="previewImage(row.image)">
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="quantity" :label="t('admin.orders.quantity')" width="100" />
|
||||
<el-table-column prop="price" :label="t('admin.orders.price')" width="120">
|
||||
<template #default="{ row }">
|
||||
|
|
@ -228,9 +220,9 @@
|
|||
>
|
||||
<el-form v-if="selectedOrderForStatus" :model="statusForm" label-width="100px">
|
||||
<el-form-item :label="t('admin.orders.currentStatus')">
|
||||
<el-tag :type="getStatusTagType(selectedOrderForStatus.status)">
|
||||
{{ t(`admin.orders.statusOptions.${selectedOrderForStatus.status}`) }}
|
||||
</el-tag>
|
||||
<el-tag :type="getStatusTagType(selectedOrder).status">
|
||||
{{ t(`${getStatusTagType(selectedOrder).label}`) }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('admin.orders.newStatus')" prop="status">
|
||||
<el-select v-model="statusForm.status" :placeholder="t('admin.orders.selectStatus')">
|
||||
|
|
@ -320,6 +312,17 @@
|
|||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 图片预览对话框 -->
|
||||
<el-dialog
|
||||
v-model="imagePreviewVisible"
|
||||
:title="t('admin.review.previewImage')"
|
||||
width="50%"
|
||||
class="image-preview-dialog"
|
||||
>
|
||||
<div class="image-preview-container">
|
||||
<img :src="previewImageUrl" class="preview-image" alt="Preview Image" />
|
||||
</div>
|
||||
</el-dialog>
|
||||
<!-- 操作选择对话框 -->
|
||||
<el-dialog
|
||||
v-model="actionDialogVisible"
|
||||
|
|
@ -329,23 +332,21 @@
|
|||
<div v-if="selectedOrderForAction">
|
||||
<el-descriptions :column="1" border class="order-info">
|
||||
<el-descriptions-item :label="t('admin.orders.orderNumber')">
|
||||
{{ selectedOrderForAction.orderNumber }}
|
||||
{{ selectedOrderForAction.order_no }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('admin.orders.customer')">
|
||||
{{ selectedOrderForAction.customerName }}
|
||||
{{ selectedOrderForAction?.order_info?.shipping?.firstName || '-' }} {{ selectedOrderForAction?.order_info?.shipping?.lastName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('admin.orders.status')">
|
||||
<el-tag :type="getStatusTagType(selectedOrderForAction.status)">
|
||||
{{ t(`admin.orders.statusOptions.${selectedOrderForAction.status}`) }}
|
||||
</el-tag>
|
||||
<el-tag :type="getStatusTagType(selectedOrderForAction).status">
|
||||
{{ t(`${getStatusTagType(selectedOrderForAction).label}`) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="t('admin.orders.customerNote')" v-if="selectedOrderForAction.customerNote">
|
||||
<!-- <el-descriptions-item :label="t('admin.orders.customerNote')" v-if="selectedOrderForAction.customerNote">
|
||||
<div class="customer-note">{{ selectedOrderForAction.customerNote }}</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions-item> -->
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider>{{ t('admin.orders.availableActions') }}</el-divider>
|
||||
|
||||
<div class="action-options">
|
||||
<!-- 查看详情 - 所有状态都可用 -->
|
||||
<el-button
|
||||
|
|
@ -356,10 +357,18 @@
|
|||
>
|
||||
{{ t('admin.orders.view') }}
|
||||
</el-button>
|
||||
|
||||
<!-- 查看拆件 -->
|
||||
<el-button
|
||||
type="success"
|
||||
size="large"
|
||||
class="action-btn"
|
||||
@click="handleDisassemble(selectedOrderForAction)"
|
||||
>
|
||||
{{ t('admin.disassemblyOrders.list.disassembly') }}
|
||||
</el-button>
|
||||
<!-- 待确认状态:确认操作 -->
|
||||
<el-button
|
||||
v-if="selectedOrderForAction.status === 'pendingConfirmation'"
|
||||
v-if="getStatusTagType(selectedOrderForAction).type=='dsh'"
|
||||
type="success"
|
||||
size="large"
|
||||
class="action-btn"
|
||||
|
|
@ -370,7 +379,7 @@
|
|||
|
||||
<!-- 待处理状态:处理操作 -->
|
||||
<el-button
|
||||
v-if="selectedOrderForAction.status === 'processing'"
|
||||
v-if="getStatusTagType(selectedOrderForAction).type=='clz'"
|
||||
type="warning"
|
||||
size="large"
|
||||
class="action-btn"
|
||||
|
|
@ -378,10 +387,9 @@
|
|||
>
|
||||
{{ t('admin.orders.process') }}
|
||||
</el-button>
|
||||
|
||||
<!-- 待发货状态:发货操作 -->
|
||||
<el-button
|
||||
v-if="selectedOrderForAction.status === 'readyToShip'"
|
||||
v-if="getStatusTagType(selectedOrderForAction).type=='dfh'"
|
||||
type="primary"
|
||||
size="large"
|
||||
class="action-btn"
|
||||
|
|
@ -389,10 +397,9 @@
|
|||
>
|
||||
{{ t('admin.orders.ship') }}
|
||||
</el-button>
|
||||
|
||||
<!-- 已发货状态:查看物流 -->
|
||||
<el-button
|
||||
v-if="selectedOrderForAction.status === 'shipped'"
|
||||
v-if="getStatusTagType(selectedOrderForAction).type=='yfh'"
|
||||
type="success"
|
||||
size="large"
|
||||
class="action-btn"
|
||||
|
|
@ -406,245 +413,145 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed, onMounted, reactive } from 'vue'
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, reactive,watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { orderStatus } from '@deotaland/utils'
|
||||
import {
|
||||
Download,
|
||||
ShoppingCart,
|
||||
Clock,
|
||||
Check,
|
||||
Money,
|
||||
Search,
|
||||
Refresh,
|
||||
View,
|
||||
Tools,
|
||||
Van,
|
||||
Box
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {AdminOrders} from './AdminOrders'
|
||||
const adminOrders = new AdminOrders()
|
||||
// 组合式函数
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchQuery = ref('')
|
||||
const selectedStatus = ref('')
|
||||
const dateRange = ref([])
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const totalOrders = ref(0)
|
||||
const detailDialogVisible = ref(false)
|
||||
const statusDialogVisible = ref(false)
|
||||
const shippingDialogVisible = ref(false)
|
||||
const logisticsDialogVisible = ref(false)
|
||||
const actionDialogVisible = ref(false)
|
||||
const selectedOrder = ref(null)
|
||||
const selectedOrderForStatus = ref(null)
|
||||
const selectedOrderForAction = ref(null)
|
||||
const selectedOrders = ref([]) // 多选选中的订单
|
||||
const imagePreviewVisible = ref(false) // 图片预览对话框显示状态
|
||||
const previewImageUrl = ref('') // 预览图片URL
|
||||
const ortherjson = ref({});
|
||||
watch(selectedStatus, () => {
|
||||
ortherjson.value = orderStatus.selectOrderStatusOptions(selectedStatus.value)
|
||||
init();
|
||||
})
|
||||
watch(dateRange, () => {
|
||||
if(Array.isArray(dateRange.value) && dateRange.value.length === 2){
|
||||
init();
|
||||
}
|
||||
})
|
||||
watch(searchQuery, () => {
|
||||
init();
|
||||
})
|
||||
const statusForm = reactive({
|
||||
status: ''
|
||||
})
|
||||
// 处理拆件操作
|
||||
const handleDisassemble = (order) => {
|
||||
// 跳转到拆件页面
|
||||
router.push({
|
||||
name: 'AdminDisassemblyDetail',
|
||||
params: { id: order.id }
|
||||
})
|
||||
}
|
||||
// 发货表单
|
||||
const shippingForm = reactive({
|
||||
trackingNumber: '',
|
||||
carrier: '',
|
||||
note: ''
|
||||
})
|
||||
|
||||
export default {
|
||||
name: 'AdminOrders',
|
||||
components: {
|
||||
Download,
|
||||
ShoppingCart,
|
||||
Clock,
|
||||
Check,
|
||||
Money,
|
||||
Search,
|
||||
Refresh,
|
||||
View,
|
||||
Tools,
|
||||
Van,
|
||||
Box
|
||||
// 物流活动时间线
|
||||
const logisticsActivities = ref([
|
||||
{
|
||||
content: '您的订单已发货',
|
||||
timestamp: '2023-12-01 10:00:00',
|
||||
type: 'primary'
|
||||
},
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const searchQuery = ref('')
|
||||
const selectedStatus = ref('')
|
||||
const dateRange = ref([])
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const totalOrders = ref(0)
|
||||
const detailDialogVisible = ref(false)
|
||||
const statusDialogVisible = ref(false)
|
||||
const shippingDialogVisible = ref(false)
|
||||
const logisticsDialogVisible = ref(false)
|
||||
const actionDialogVisible = ref(false)
|
||||
const selectedOrder = ref(null)
|
||||
const selectedOrderForStatus = ref(null)
|
||||
const selectedOrderForAction = ref(null)
|
||||
const selectedOrders = ref([]) // 多选选中的订单
|
||||
|
||||
const statusForm = reactive({
|
||||
status: ''
|
||||
})
|
||||
|
||||
// 发货表单
|
||||
const shippingForm = reactive({
|
||||
trackingNumber: '',
|
||||
carrier: '',
|
||||
note: ''
|
||||
})
|
||||
|
||||
// 物流活动时间线
|
||||
const logisticsActivities = ref([
|
||||
{
|
||||
content: '您的订单已发货',
|
||||
timestamp: '2023-12-01 10:00:00',
|
||||
type: 'primary'
|
||||
},
|
||||
{
|
||||
content: '您的订单已到达【北京转运中心】',
|
||||
timestamp: '2023-12-01 14:30:00',
|
||||
type: 'success'
|
||||
},
|
||||
{
|
||||
content: '您的订单正在派送中',
|
||||
timestamp: '2023-12-02 09:00:00',
|
||||
type: 'warning'
|
||||
},
|
||||
{
|
||||
content: '您的订单已签收',
|
||||
timestamp: '2023-12-02 16:30:00',
|
||||
type: 'success'
|
||||
}
|
||||
])
|
||||
{
|
||||
content: '您的订单已到达【北京转运中心】',
|
||||
timestamp: '2023-12-01 14:30:00',
|
||||
type: 'success'
|
||||
},
|
||||
{
|
||||
content: '您的订单正在派送中',
|
||||
timestamp: '2023-12-02 09:00:00',
|
||||
type: 'warning'
|
||||
},
|
||||
{
|
||||
content: '您的订单已签收',
|
||||
timestamp: '2023-12-02 16:30:00',
|
||||
type: 'success'
|
||||
}
|
||||
])
|
||||
|
||||
// 模拟订单数据
|
||||
const ordersList = ref([
|
||||
{
|
||||
id: 1,
|
||||
orderNumber: 'ORD-2024-001',
|
||||
customerName: '张三',
|
||||
totalAmount: 1299.00,
|
||||
status: 'shipped',
|
||||
paymentMethod: 'alipay',
|
||||
orderDate: '2024-01-15 10:30:00',
|
||||
customerNote: '请在工作日送货,周末不在家',
|
||||
items: [
|
||||
{ name: 'AI智能音箱', quantity: 1, price: 799.00 },
|
||||
{ name: '智能插座', quantity: 2, price: 250.00 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
orderNumber: 'ORD-2024-002',
|
||||
customerName: '李四',
|
||||
totalAmount: 2599.00,
|
||||
status: 'readyToShip',
|
||||
paymentMethod: 'wechat',
|
||||
orderDate: '2024-01-14 16:20:00',
|
||||
customerNote: '需要发票,抬头:XX科技有限公司',
|
||||
items: [
|
||||
{ name: '智能门锁', quantity: 1, price: 1999.00 },
|
||||
{ name: '智能摄像头', quantity: 1, price: 600.00 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
orderNumber: 'ORD-2024-003',
|
||||
customerName: '王五',
|
||||
totalAmount: 899.00,
|
||||
status: 'processing',
|
||||
paymentMethod: 'credit',
|
||||
orderDate: '2024-01-13 09:15:00',
|
||||
customerNote: '请提前一天电话联系',
|
||||
items: [
|
||||
{ name: '智能灯泡', quantity: 3, price: 299.00 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
orderNumber: 'ORD-2024-004',
|
||||
customerName: '赵六',
|
||||
totalAmount: 1599.00,
|
||||
status: 'pendingConfirmation',
|
||||
paymentMethod: 'alipay',
|
||||
orderDate: '2024-01-12 14:45:00',
|
||||
customerNote: '收货地址:XX市XX区XX街道XX小区X号楼X单元XXX室',
|
||||
items: [
|
||||
{ name: '智能扫地机器人', quantity: 1, price: 1599.00 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
orderNumber: 'ORD-2024-005',
|
||||
customerName: '孙七',
|
||||
totalAmount: 799.00,
|
||||
status: 'processing',
|
||||
paymentMethod: 'wechat',
|
||||
orderDate: '2024-01-11 11:30:00',
|
||||
customerNote: '商品是礼物,请用精美包装',
|
||||
items: [
|
||||
{ name: '智能体重秤', quantity: 1, price: 799.00 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
orderNumber: 'ORD-2024-006',
|
||||
customerName: '周八',
|
||||
totalAmount: 1299.00,
|
||||
status: 'rejected',
|
||||
paymentMethod: 'credit',
|
||||
orderDate: '2024-01-10 15:20:00',
|
||||
customerNote: '需要开具增值税专用发票',
|
||||
items: [
|
||||
{ name: '智能手环', quantity: 1, price: 1299.00 }
|
||||
]
|
||||
}
|
||||
])
|
||||
// 模拟订单数据
|
||||
const ordersList = ref()
|
||||
|
||||
// 统计数据
|
||||
const orderStats = ref({
|
||||
total: 156,
|
||||
pending: 23,
|
||||
completed: 128,
|
||||
revenue: 156789
|
||||
})
|
||||
// 统计数据
|
||||
const orderStats = ref({
|
||||
total: 0,
|
||||
pending: 0,
|
||||
completed: 0,
|
||||
revenue: 0
|
||||
})
|
||||
|
||||
// 计算属性
|
||||
const filteredOrdersList = computed(() => {
|
||||
let result = ordersList.value
|
||||
|
||||
if (searchQuery.value) {
|
||||
result = result.filter(item =>
|
||||
item.orderNumber.includes(searchQuery.value) ||
|
||||
item.customerName.includes(searchQuery.value)
|
||||
)
|
||||
const getOrderStatistics = async () => {
|
||||
adminOrders.getOrderStatistics().then(res=>{
|
||||
if(res.code==0){
|
||||
let data = res.data.all_maps
|
||||
orderStats.value = {
|
||||
total:data.total_count,
|
||||
pending: data.pending_count,
|
||||
completed: data.over_count,
|
||||
revenue: data.total_money,
|
||||
}
|
||||
|
||||
if (selectedStatus.value) {
|
||||
result = result.filter(item => item.status === selectedStatus.value)
|
||||
}
|
||||
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
result = result.filter(item => {
|
||||
const orderDate = new Date(item.orderDate)
|
||||
const startDate = new Date(dateRange.value[0])
|
||||
const endDate = new Date(dateRange.value[1])
|
||||
return orderDate >= startDate && orderDate <= endDate
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const availableStatuses = computed(() => {
|
||||
const allStatuses = [
|
||||
{ value: 'pendingConfirmation', label: t('admin.orders.statusOptions.pendingConfirmation') },
|
||||
{ value: 'rejected', label: t('admin.orders.statusOptions.rejected') },
|
||||
{ value: 'processing', label: t('admin.orders.statusOptions.processing') },
|
||||
{ value: 'readyToShip', label: t('admin.orders.statusOptions.readyToShip') },
|
||||
{ value: 'shipped', label: t('admin.orders.statusOptions.shipped') }
|
||||
]
|
||||
return allStatuses
|
||||
})
|
||||
|
||||
// 方法
|
||||
const getStatusTagType = (status) => {
|
||||
const statusMap = {
|
||||
pendingConfirmation: 'info', // 待确认 - 蓝色
|
||||
rejected: 'danger', // 已拒绝 - 红色
|
||||
processing: 'warning', // 待处理 - 橙色
|
||||
readyToShip: 'primary', // 待发货 - 主色调
|
||||
shipped: 'success' // 已发货 - 绿色
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
}
|
||||
})
|
||||
}
|
||||
const availableStatuses = computed(() => {
|
||||
const allStatuses = [
|
||||
{ value: 'pendingConfirmation', label: t('admin.orders.statusOptions.pendingConfirmation') },
|
||||
{ value: 'rejected', label: t('admin.orders.statusOptions.rejected') },
|
||||
{ value: 'processing', label: t('admin.orders.statusOptions.processing') },
|
||||
{ value: 'readyToShip', label: t('admin.orders.statusOptions.readyToShip') },
|
||||
{ value: 'shipped', label: t('admin.orders.statusOptions.shipped') }
|
||||
]
|
||||
return allStatuses
|
||||
})
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleString('zh-CN')
|
||||
}
|
||||
// 方法
|
||||
const getStatusTagType = (order) => {
|
||||
return orderStatus.getOrderStatusOptions(order)
|
||||
}
|
||||
|
||||
const handleExportOrders = () => {
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
const handleExportOrders = () => {
|
||||
ElMessage.success('Orders exported successfully')
|
||||
}
|
||||
|
||||
|
|
@ -653,98 +560,98 @@ const refresh = () => {
|
|||
ElMessage.success('Data refreshed successfully')
|
||||
}
|
||||
|
||||
const handleOrderDetail = (row) => {
|
||||
selectedOrder.value = row
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
const handleOrderDetail = (row) => {
|
||||
selectedOrder.value = row
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleViewOrder = (row) => {
|
||||
selectedOrder.value = row
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleUpdateStatus = (row) => {
|
||||
selectedOrderForStatus.value = row
|
||||
statusForm.value.status = row.status
|
||||
statusDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleStatusUpdate = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确定要更新订单状态吗?',
|
||||
'确认更新',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
ElMessage.success('订单状态更新成功')
|
||||
statusDialogVisible.value = false
|
||||
} catch {
|
||||
// 用户取消更新
|
||||
const handleViewOrder = (row) => {
|
||||
selectedOrder.value = row
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
const handleStatusUpdate = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确定要更新订单状态吗?',
|
||||
'确认更新',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
}
|
||||
)
|
||||
ElMessage.success('订单状态更新成功')
|
||||
statusDialogVisible.value = false
|
||||
} catch {
|
||||
// 用户取消更新
|
||||
}
|
||||
}
|
||||
|
||||
// 确认订单
|
||||
const handleConfirmOrder = (row) => {
|
||||
// 跳转到订单审核页面
|
||||
router.push('/admin/content-review')
|
||||
}
|
||||
|
||||
// 处理订单
|
||||
const handleProcessOrder = (row) => {
|
||||
// 跳转到拆解订单页面
|
||||
router.push('/admin/disassembly-orders')
|
||||
}
|
||||
|
||||
// 发货订单
|
||||
const handleShipOrder = (row) => {
|
||||
selectedOrder.value = row
|
||||
shippingDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 查看物流
|
||||
const handleViewLogistics = (row) => {
|
||||
selectedOrder.value = row
|
||||
logisticsDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 确认发货
|
||||
const confirmShipOrder = () => {
|
||||
if (!shippingForm.trackingNumber || !shippingForm.carrier) {
|
||||
ElMessage.warning('请填写必要的发货信息')
|
||||
return
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
const orderIndex = orders.value.findIndex(order => order.id === selectedOrder.value.id)
|
||||
if (orderIndex !== -1) {
|
||||
orders.value[orderIndex].status = 'shipped'
|
||||
orders.value[orderIndex].trackingNumber = shippingForm.trackingNumber
|
||||
orders.value[orderIndex].carrier = shippingForm.carrier
|
||||
}
|
||||
|
||||
ElMessage.success('发货成功')
|
||||
shippingDialogVisible.value = false
|
||||
|
||||
// 重置表单
|
||||
shippingForm.trackingNumber = ''
|
||||
shippingForm.carrier = ''
|
||||
shippingForm.note = ''
|
||||
}
|
||||
// 确认订单
|
||||
const handleConfirmOrder = (row) => {
|
||||
// 跳转到订单审核页面
|
||||
router.push(`/admin/content-review?order_no=${row.order_no}`)
|
||||
}
|
||||
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
currentPage.value = 1
|
||||
}
|
||||
// 处理订单
|
||||
const handleProcessOrder = (row) => {
|
||||
// 跳转到拆解订单页面
|
||||
router.push(`/admin/disassembly-orders?order_no=${row.order_no}`)
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
currentPage.value = val
|
||||
}
|
||||
// 发货订单
|
||||
const handleShipOrder = (row) => {
|
||||
selectedOrder.value = row
|
||||
shippingDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 多选处理函数
|
||||
// 查看物流
|
||||
const handleViewLogistics = (row) => {
|
||||
selectedOrder.value = row
|
||||
logisticsDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 确认发货
|
||||
const confirmShipOrder = () => {
|
||||
if (!shippingForm.trackingNumber || !shippingForm.carrier) {
|
||||
ElMessage.warning('请填写必要的发货信息')
|
||||
return
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
const orderIndex = ordersList.value.findIndex(order => order.id === selectedOrder.value.id)
|
||||
if (orderIndex !== -1) {
|
||||
ordersList.value[orderIndex].status = 'shipped'
|
||||
ordersList.value[orderIndex].trackingNumber = shippingForm.trackingNumber
|
||||
ordersList.value[orderIndex].carrier = shippingForm.carrier
|
||||
}
|
||||
|
||||
ElMessage.success('发货成功')
|
||||
shippingDialogVisible.value = false
|
||||
|
||||
// 重置表单
|
||||
shippingForm.trackingNumber = ''
|
||||
shippingForm.carrier = ''
|
||||
shippingForm.note = ''
|
||||
}
|
||||
|
||||
// 图片预览
|
||||
const previewImage = (url) => {
|
||||
if (!url || url === '-') return
|
||||
previewImageUrl.value = url
|
||||
imagePreviewVisible.value = true
|
||||
}
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
currentPage.value = 1
|
||||
}
|
||||
// 分页处理函数
|
||||
const handleCurrentChange = (val) => {
|
||||
currentPage.value = val
|
||||
getList();
|
||||
}
|
||||
|
||||
// 多选处理函数
|
||||
const handleSelectionChange = (selection) => {
|
||||
selectedOrders.value = selection
|
||||
}
|
||||
|
|
@ -777,67 +684,41 @@ const executeAction = (action) => {
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
totalOrders.value = ordersList.value.length
|
||||
})
|
||||
|
||||
return {
|
||||
t,
|
||||
loading,
|
||||
searchQuery,
|
||||
selectedStatus,
|
||||
dateRange,
|
||||
currentPage,
|
||||
pageSize,
|
||||
totalOrders,
|
||||
detailDialogVisible,
|
||||
statusDialogVisible,
|
||||
shippingDialogVisible,
|
||||
logisticsDialogVisible,
|
||||
actionDialogVisible,
|
||||
selectedOrder,
|
||||
selectedOrderForStatus,
|
||||
selectedOrderForAction,
|
||||
selectedOrders,
|
||||
statusForm,
|
||||
shippingForm,
|
||||
logisticsActivities,
|
||||
ordersList,
|
||||
orderStats,
|
||||
filteredOrdersList,
|
||||
availableStatuses,
|
||||
getStatusTagType,
|
||||
formatDate,
|
||||
handleExportOrders,
|
||||
refresh,
|
||||
handleOrderDetail,
|
||||
handleViewOrder,
|
||||
handleUpdateStatus,
|
||||
handleStatusUpdate,
|
||||
handleConfirmOrder,
|
||||
handleProcessOrder,
|
||||
handleShipOrder,
|
||||
handleViewLogistics,
|
||||
confirmShipOrder,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange,
|
||||
handleActionSelect,
|
||||
executeAction
|
||||
}
|
||||
const getList = ()=>{
|
||||
let params = {
|
||||
...ortherjson.value,
|
||||
order_no: searchQuery.value,
|
||||
page_size: pageSize.value,
|
||||
page: currentPage.value
|
||||
}
|
||||
if(dateRange.value.length === 2){
|
||||
params.startDate = dateRange.value[0];
|
||||
params.endDate = dateRange.value[1];
|
||||
}
|
||||
adminOrders.getOrderList(params).then(res=>{
|
||||
let data = res.data || [];
|
||||
ordersList.value = data.items;
|
||||
// ordersList.value = [...data.items,...data.items];
|
||||
orderStats.value.total = data.total || 0;
|
||||
totalOrders.value = data.total || 0;
|
||||
// orderStats.value.pending = data.total || 0;
|
||||
})
|
||||
}
|
||||
const init = ()=>{
|
||||
getList();
|
||||
getOrderStatistics();
|
||||
}
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
init();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-orders {
|
||||
padding: 20px;
|
||||
background-color: #f8fafc;
|
||||
min-height: calc(100vh - 60px);
|
||||
height: calc(90vh - 60px);
|
||||
}
|
||||
|
||||
/* 统计卡片样式 */
|
||||
|
|
@ -1101,4 +982,34 @@ const executeAction = (action) => {
|
|||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* 图片预览对话框样式 */
|
||||
.image-preview-dialog :deep(.el-dialog__body) {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.image-preview-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
cursor: zoom-out;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.preview-image:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
# 开发环境变量配置
|
||||
# Google AI API Key(用于 AI 功能)
|
||||
VITE_GOOGLE_API_KEY=your_google_api_key_here
|
||||
|
||||
VITE_PROJECTTYPE=client
|
||||
# 基础API地址(生产环境)
|
||||
VITE_BASE_URL=https://api.deotaland.ai
|
||||
VITE_BASE_URL=/api
|
||||
# VITE_BASE_URL=http://192.168.0.174:9000
|
||||
|
||||
# 基础API地址(备用)
|
||||
VITE_APP_BASE_API=https://api.deotaland.ai
|
||||
VITE_APP_BASE_API=/api
|
||||
|
||||
# Stripe 支付配置
|
||||
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_51SUf06BzlmfuPpixQn3nBDvLcO2qTyeqseM1wcwPcTfGo2Rivggc0axNbFyPrVCfoKIfWuuzIeBzUQl3Fn4Hz0Ea008vLhvv5g
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# 生产环境配置
|
||||
VITE_BASE_URL=https://api.deotaland.ai
|
||||
VITE_PROJECTTYPE=client
|
||||
# Vercel 部署环境变量配置示例
|
||||
# 复制此文件为 .env.local 并填入实际值
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/public/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' data: blob: https://*.stripe.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://m.stripe.network; font-src 'self' https://fonts.gstatic.com; connect-src 'self' blob: data: https://api.stripe.com https://r.stripe.com https://m.stripe.network; script-src 'self' https://js.stripe.com; frame-src https://js.stripe.com;" /> -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
|
@ -12,7 +11,6 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@
|
|||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"@google/genai": "^1.27.0",
|
||||
"@stripe/stripe-js": "^4.8.0",
|
||||
"@twind/core": "^1.1.3",
|
||||
"@twind/preset-autoprefix": "^1.0.7",
|
||||
"@twind/preset-tailwind": "^1.1.4",
|
||||
"@types/three": "^0.180.0",
|
||||
"@vuelidate/core": "^2.0.3",
|
||||
"@vuelidate/validators": "^2.0.4",
|
||||
|
|
@ -24,7 +27,9 @@
|
|||
"jose": "^6.1.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"three": "^0.180.0",
|
||||
"twind": "^0.16.19",
|
||||
"vue": "^3.5.24",
|
||||
"vue-countup-v3": "^1.4.2",
|
||||
"vue-i18n": "^11.1.12",
|
||||
|
|
@ -34,7 +39,11 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/feather": "^1.2.1",
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"terser": "^5.44.1",
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-icons": "^22.5.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@ const route = useRoute()
|
|||
const isLoginPage = computed(() => route.path === '/login')
|
||||
// 判断当前是否为全屏页面
|
||||
const isFullScreenPage = computed(() => route.meta.fullScreen)
|
||||
|
||||
// 判断当前是否为首页
|
||||
const isHomePage = computed(() => route.path === '/')
|
||||
// Reactive theme state
|
||||
const themeState = ref(false)
|
||||
|
||||
// Initialize theme state from localStorage and DOM
|
||||
function initializeTheme() {
|
||||
if (typeof window !== 'undefined') {
|
||||
|
|
@ -39,15 +39,18 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div id="app-container" :class="{
|
||||
<div v-if="!isHomePage" id="app-container" :class="{
|
||||
'login-mode': isLoginPage,
|
||||
'fullscreen-mode': isFullScreenPage
|
||||
'fullscreen-mode': isFullScreenPage,
|
||||
'homepage-mode': isHomePage
|
||||
}">
|
||||
<!-- 登录页面全屏显示 -->
|
||||
<main v-if="isLoginPage">
|
||||
<router-view />
|
||||
</main>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 全屏页面(如创建项目) -->
|
||||
<main v-else-if="isFullScreenPage" class="fullscreen-content">
|
||||
<router-view />
|
||||
|
|
@ -68,6 +71,9 @@ onMounted(() => {
|
|||
</template>
|
||||
</MainLayout>
|
||||
</div>
|
||||
<div v-else>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
@ -76,10 +82,10 @@ header strong { font-size: 1.25rem; }
|
|||
/* 应用容器基础样式 */
|
||||
#app-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
min-height: 98vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
/* 移除 overflow: hidden 以允许页面滚动 */
|
||||
}
|
||||
|
||||
/* 登录模式样式 - 全屏显示 */
|
||||
|
|
@ -105,6 +111,7 @@ header strong { font-size: 1.25rem; }
|
|||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
overflow: auto !important; /* 允许全屏内容滚动 */
|
||||
}
|
||||
|
||||
.fullscreen-content {
|
||||
|
|
@ -112,7 +119,24 @@ header strong { font-size: 1.25rem; }
|
|||
height: 100vh !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
overflow: auto !important; /* 确保全屏内容可以滚动 */
|
||||
}
|
||||
|
||||
/* 首页全屏滚动模式 - 使用浏览器原生滚动 */
|
||||
#app-container.homepage-mode {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow: visible; /* 允许浏览器原生滚动 */
|
||||
}
|
||||
|
||||
.homepage-content {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: visible; /* 使用浏览器原生滚动 */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 修复暗色主题下按钮偏移问题 */
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 5.2 MiB |
|
After Width: | Height: | Size: 5.4 MiB |
|
After Width: | Height: | Size: 5.5 MiB |
|
After Width: | Height: | Size: 8.0 MiB |
|
After Width: | Height: | Size: 12 MiB |
|
After Width: | Height: | Size: 346 KiB |
|
|
@ -262,7 +262,7 @@ const handleGenerateImage = async () => {
|
|||
服装结构:确保上衣、下装、外套、鞋履、配饰等所有服装元素与参考图一致,保持准确的比例、形状与层次。
|
||||
眼睛位置与第一张参考图一致,材质表现逼真,保留参考图中的细节,比如还原参考图中的头发。
|
||||
`:``}
|
||||
角色特征:Q 版萌系造型,头身比例夸张(大头小身),神态纯真,服饰设计融合童话风与复古感,色彩搭配和谐且富有层次.
|
||||
角色特征:Q 版萌系造型,头身比例夸张(大头小身),神态纯真,服饰设计融合童话风与复古感(简化一下复杂衣服纹理,只保留特征),色彩搭配和谐且富有层次.
|
||||
Note: The image should not have white borders.
|
||||
完整度:不要简化、修改或重新设计服装,需忠于原设计。
|
||||
适配3D打印:请保持服装边缘、装饰等细节略微加厚、避免过细结构,以提高打印稳定性。
|
||||
|
|
|
|||
|
|
@ -6,12 +6,7 @@
|
|||
<span class="order-number">#{{ order.order_no }}</span>
|
||||
</div>
|
||||
<el-tag :type="statusType.status" size="small">
|
||||
<el-icon v-if="true" class="status-icon"><Clock /></el-icon>
|
||||
<el-icon v-else-if="order.status === 'paid'" class="status-icon"><Check /></el-icon>
|
||||
<el-icon v-else-if="order.status === 'shipped'" class="status-icon"><Van /></el-icon>
|
||||
<el-icon v-else-if="order.status === 'completed'" class="status-icon"><CircleCheck /></el-icon>
|
||||
<el-icon v-else-if="order.status === 'expired'" class="status-icon"><Warning /></el-icon>
|
||||
{{ statusType.label }}
|
||||
{{ t(statusType.label) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<!-- 卡片内容 -->
|
||||
|
|
@ -55,7 +50,6 @@
|
|||
<el-icon><View /> </el-icon>
|
||||
{{ t('orderManagement.actions.view') }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-if="statusType.type === 'dzf'"
|
||||
type="primary"
|
||||
|
|
@ -78,10 +72,9 @@
|
|||
<el-icon><Close /> </el-icon>
|
||||
{{ t('orderManagement.actions.cancel') }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-if="statusType.type === 'yfh'"
|
||||
type="success"
|
||||
type="success"
|
||||
size="small"
|
||||
@click="handleConfirm"
|
||||
:loading="isConfirming"
|
||||
|
|
@ -96,7 +89,7 @@
|
|||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
<el-dropdown-item
|
||||
v-for="action in moreActions"
|
||||
:key="action.key"
|
||||
@click="handleMoreAction(action.key)"
|
||||
|
|
@ -130,6 +123,7 @@ import {
|
|||
Download,
|
||||
ChatLineSquare
|
||||
} from '@element-plus/icons-vue'
|
||||
import { orderStatus } from '@deotaland/utils'
|
||||
|
||||
export default {
|
||||
name: 'OrderCard',
|
||||
|
|
@ -176,61 +170,8 @@ export default {
|
|||
const handleResize = () => {
|
||||
updateCardWidth()
|
||||
}
|
||||
|
||||
const statusType = computed(() => {
|
||||
const payment_statusMap = {
|
||||
0: ['danger','orderManagement.payment.pending','dzf'],
|
||||
1: ['success','orderManagement.payment.paid','yzf'],
|
||||
3: ['danger','orderManagement.payment.failed','zfsb'],
|
||||
4: ['danger','orderManagement.status.expired','zfgq']
|
||||
}
|
||||
const order_statusMap = {
|
||||
0:['warning','orderManagement.status.shenhe','dsh'],
|
||||
1: ['warning','orderManagement.status.unsuccess','wtg'],
|
||||
2: ['info','orderManagement.status.clz','clz'],
|
||||
3: ['info','orderManagement.status.dfh','dfh'],
|
||||
4: ['info','orderManagement.status.delivered','yfh'],
|
||||
5: ['success','orderManagement.status.success','ywc'],
|
||||
6: ['info','orderManagement.status.cancelled','yqx'],
|
||||
}
|
||||
const refund_statusMap = {
|
||||
0:['info','orderManagement.refundStatus.wtk','wtk'],
|
||||
1: ['info','orderManagement.refundStatus.sqtk','sqtk'],
|
||||
2: ['warning','orderManagement.refundStatus.jjtk','jjtk'],
|
||||
3: ['success','orderManagement.refundStatus.tytk','tytk'],
|
||||
4: ['info','orderManagement.refundStatus.ytk','ytk'],
|
||||
}
|
||||
// deo_order.payment_status IS '支付状态: 0待支付 1已支付 3支付失败 4支付过期';
|
||||
// deo_order.order_status IS '订单状态: 0待审核 1审核未通过 2处理中 3待发货 4已发货 5已完成 6已取消';
|
||||
// deo_order.refund_status IS '退款状态: 0无退款 1申请退款 2拒绝退款 3同意退款 4已退款';
|
||||
let status = '';
|
||||
let label = '';
|
||||
let type='';
|
||||
let order_status = props.order.order_status;
|
||||
let refund_status = props.order.refund_status;
|
||||
let payment_status = props.order.payment_status;
|
||||
if(order_status==6){
|
||||
status = order_statusMap[order_status][0]
|
||||
label = order_statusMap[order_status][1]
|
||||
type = order_statusMap[order_status][2]
|
||||
}if(payment_status!=1 && order_status!=6){
|
||||
status = payment_statusMap[payment_status][0]
|
||||
label = payment_statusMap[payment_status][1]
|
||||
type = payment_statusMap[payment_status][2]
|
||||
}else if(order_status!=1||order_status!=6){
|
||||
status = order_statusMap[order_status][0]
|
||||
label = order_statusMap[order_status][1]
|
||||
type = order_statusMap[order_status][2]
|
||||
}else{
|
||||
status = refund_statusMap[refund_status][0]
|
||||
label = refund_statusMap[refund_status][1]
|
||||
type = refund_statusMap[refund_status][2]
|
||||
}
|
||||
return {
|
||||
status:status,
|
||||
label: t(label),
|
||||
type:type
|
||||
};
|
||||
return orderStatus.getOrderStatusOptions(props.order)
|
||||
})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
<template>
|
||||
<div v-if="show" class="purchase-overlay" @click="onClose">
|
||||
<div class="purchase-container" @click.stop>
|
||||
<!-- 支付中蒙层 -->
|
||||
<div v-if="showPayingOverlay" class="paying-overlay">
|
||||
<div class="paying-content">
|
||||
<div class="paying-spinner"></div>
|
||||
<div class="paying-text">{{ 'PayLoading' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="close-button" @click="onClose" aria-label="关闭">
|
||||
<el-icon class="close-icon"><CloseBold /></el-icon>
|
||||
</button>
|
||||
|
|
@ -186,6 +194,7 @@ const countryOptions = ref([])
|
|||
const stateOptions = ref([])
|
||||
const showStripe = ref(false)
|
||||
const { locale } = useI18n()
|
||||
const showPayingOverlay = ref(false)
|
||||
|
||||
// 省州映射数据已移至国际化文件
|
||||
const amountCents = computed(() => {
|
||||
|
|
@ -246,8 +255,16 @@ const goShopify = () => {//用户点击购买
|
|||
project_details:project_details,
|
||||
order_info:order_info,
|
||||
}
|
||||
// 显示支付中蒙层
|
||||
showPayingOverlay.value = true
|
||||
|
||||
// 5秒后隐藏蒙层
|
||||
setTimeout(() => {
|
||||
showPayingOverlay.value = false
|
||||
}, 5000)
|
||||
|
||||
// 在控制台打印整理后的信息
|
||||
payserver.createPayorOrder(params)
|
||||
payserver.createPayorOrder(params);
|
||||
}
|
||||
|
||||
const saveLocal = () => {
|
||||
|
|
@ -1062,4 +1079,87 @@ const updateStates = () => {
|
|||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 支付中蒙层样式 */
|
||||
.paying-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 1004;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.paying-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding: 40px;
|
||||
background: rgba(17, 24, 39, 0.95);
|
||||
border: 1px solid rgba(139, 92, 246, 0.3);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.paying-spinner {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 4px solid rgba(139, 92, 246, 0.2);
|
||||
border-top: 4px solid #8b5cf6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.paying-text {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 响应式设计 - 支付蒙层 */
|
||||
@media (max-width: 768px) {
|
||||
.paying-content {
|
||||
padding: 30px 20px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.paying-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.paying-text {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.paying-content {
|
||||
padding: 24px 16px;
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
.paying-spinner {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.paying-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -319,7 +319,6 @@ export default {
|
|||
emit('reset-success', state.email)
|
||||
})
|
||||
} catch (error) {
|
||||
ElMessage.error(t('forgotPassword.reset_processing_error'))
|
||||
} finally {
|
||||
state.isSubmitting = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ const loginWithidToken = async (idToken) => {
|
|||
const res = await requestUtils.common(clientApi.default.OAUTH_GOOGLE,{
|
||||
googleIdToken:idToken
|
||||
})
|
||||
if(res.code === 200){
|
||||
// 登录成功,保存token和用户信息
|
||||
let data = res.data;
|
||||
authStore.loginSuccess(data,()=>{
|
||||
|
|
@ -103,7 +102,6 @@ const loginWithidToken = async (idToken) => {
|
|||
return
|
||||
emit('success', res.data.user)
|
||||
return res
|
||||
}
|
||||
return res
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
|
|
@ -189,15 +187,13 @@ onMounted(() => {
|
|||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 按钮文字 */
|
||||
.button-text {
|
||||
flex: 1;
|
||||
/* flex: 1; */
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
/* 加载状态指示器 */
|
||||
.loading-spinner {
|
||||
position: absolute;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
type="button"
|
||||
class="send-code-button"
|
||||
@click="handleSendVerificationCode"
|
||||
:disabled="isCodeSending"
|
||||
:disabled="isCodeSending || countdown > 0"
|
||||
:class="{ 'countdown-active': countdown > 0 }"
|
||||
>
|
||||
<span v-if="countdown > 0">{{ countdown }}s</span>
|
||||
|
|
@ -213,10 +213,8 @@ const validateEmail = () => {
|
|||
const validatePassword = () => {
|
||||
if (!form.value.password) {
|
||||
passwordError.value = t('register.password_empty_error')
|
||||
} else if (form.value.password.length < 8) {
|
||||
} else if (form.value.password.length < 6) {
|
||||
passwordError.value = t('register.password_min_error')
|
||||
} else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(form.value.password)) {
|
||||
passwordError.value = t('register.password_complexity_error')
|
||||
} else {
|
||||
passwordError.value = ''
|
||||
}
|
||||
|
|
@ -247,7 +245,7 @@ const handleRegister = () => {
|
|||
emailCode: form.value.verificationCode,
|
||||
password: form.value.password
|
||||
},()=>{
|
||||
// emit('success')
|
||||
emit('success')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -261,6 +261,8 @@ import { ElMessage } from 'element-plus';
|
|||
import cz1 from '../../assets/material/cz1.jpg'
|
||||
import humanTypeImg from '../../assets/sketches/tcww.png'
|
||||
import animalTypeImg from '../../assets/sketches/dwww.png'
|
||||
import { FileServer } from '@deotaland/utils';
|
||||
const filePlug = new FileServer();
|
||||
// 定义事件
|
||||
const emit = defineEmits(['image-generated', 'model-generated', 'generate-requested', 'import-character', 'navigate-back', 'updateProjectInfo']);
|
||||
const props = defineProps({
|
||||
|
|
@ -692,13 +694,15 @@ const triggerFileUpload = () => {
|
|||
const handleFileChange = async (event) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
console.log('待上传文件:', file);
|
||||
const imgUrl = await filePlug.uploadFile(file)
|
||||
// console.log('待上传文件:', file);
|
||||
// 创建本地图片预览URL
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
formData.value.previewImage = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
// const reader = new FileReader();
|
||||
// reader.onload = (e) => {
|
||||
formData.value.previewImage = imgUrl;
|
||||
// };
|
||||
// reader.readAsDataURL(file);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -47,21 +47,21 @@
|
|||
<div class="user-menu" v-if="currentUser">
|
||||
<el-dropdown trigger="click" @command="handleUserCommand">
|
||||
<div class="user-avatar">
|
||||
<el-avatar :size="32" :src="currentUser.avatar">
|
||||
<el-avatar :size="32" :src="currentUser.avatarUrl">
|
||||
<el-icon><UserIcon /></el-icon>
|
||||
</el-avatar>
|
||||
<span class="user-name">{{ currentUser.name || currentUser.email }}</span>
|
||||
<span class="user-name">{{ currentUser.nickname || currentUser.email }}</span>
|
||||
<ChevronDownIcon class="dropdown-icon" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="profile">
|
||||
<!-- <el-dropdown-item command="profile">
|
||||
<UserIcon class="dropdown-item-icon" />
|
||||
{{ t('header.profile') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="settings">
|
||||
{{ t('header.settings') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-item> -->
|
||||
<el-dropdown-item divided command="logout">
|
||||
<LogoutIcon class="dropdown-item-icon" />
|
||||
{{ t('header.logout') }}
|
||||
|
|
@ -317,8 +317,9 @@ export default {
|
|||
break
|
||||
case 'logout':
|
||||
try {
|
||||
await authStore.logout()
|
||||
router.push('/login')
|
||||
await authStore.logout(()=>{
|
||||
router.push('/login')
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('登出失败:', error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ export default {
|
|||
const coreMenuItems = computed(() => [
|
||||
{
|
||||
id: 'dashboard',
|
||||
path: '/',
|
||||
path: '/czhome',
|
||||
label: t('sidebar.dashboard'),
|
||||
icon: 'DashboardIcon',
|
||||
badge: null
|
||||
|
|
|
|||
|
|
@ -161,8 +161,8 @@ export default {
|
|||
.main-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
min-height: 98vh;
|
||||
/* 移除 overflow: hidden 以允许页面滚动 */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ export default {
|
|||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 120px;
|
||||
height: 100%;
|
||||
/* height: 100%; */
|
||||
background: var(--sidebar-bg, #ffffff);
|
||||
border-right: 1px solid var(--border-color, #e5e7eb);
|
||||
z-index: 1000;
|
||||
|
|
@ -219,8 +219,8 @@ export default {
|
|||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
/* 移除 overflow: hidden 以允许页面滚动 */
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
|
|
@ -237,18 +237,17 @@ export default {
|
|||
height: 64px;
|
||||
background: var(--header-bg, #ffffff);
|
||||
border-bottom: 1px solid var(--border-color, #e5e7eb);
|
||||
z-index: 100;
|
||||
z-index: 999;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/* 页面内容 */
|
||||
.page-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
/* overflow-y: auto; */
|
||||
background: var(--content-bg, #f8fafc);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height:90vh;
|
||||
}
|
||||
|
||||
/* 移动端样式 */
|
||||
|
|
|
|||
|
|
@ -183,6 +183,8 @@ const TaskStatus = (result)=>{
|
|||
}
|
||||
// 组件挂载时,如果有图片URL但没有模型URL,自动生成模型
|
||||
onMounted(() => {
|
||||
// handleGenerateModel();
|
||||
// return
|
||||
switch (props.cardData.status) {
|
||||
case 'loading':
|
||||
handleGenerateModel();
|
||||
|
|
|
|||
|
|
@ -441,6 +441,8 @@ export default {
|
|||
ytk:'已退款'
|
||||
},
|
||||
status: {
|
||||
yjj:'已拒绝',
|
||||
dsh:'待审核',
|
||||
all: '全部',
|
||||
pending: '待处理',
|
||||
paid: '已支付',
|
||||
|
|
@ -481,13 +483,17 @@ export default {
|
|||
paid: '已支付',
|
||||
failed: '支付失败',
|
||||
refunded: '已退款'
|
||||
}
|
||||
,
|
||||
},
|
||||
cancelConfirm: {
|
||||
title: '取消订单',
|
||||
message: '确定要取消此订单吗?此操作不可恢复。'
|
||||
},
|
||||
cancelSuccess: '订单取消成功',
|
||||
cancelFail: '订单取消失败',
|
||||
countdown: {
|
||||
remaining: '剩余支付时间',
|
||||
expired: '已超时'
|
||||
}
|
||||
,
|
||||
},
|
||||
expiredNotice: '订单已超时,无法支付,请重新下单'
|
||||
},
|
||||
logistics: {
|
||||
|
|
@ -1392,7 +1398,7 @@ export default {
|
|||
description: 'All your creative projects will be displayed here'
|
||||
}
|
||||
},
|
||||
orderManagement: {
|
||||
orderManagement: {
|
||||
title: 'Order Management',
|
||||
description: 'View and manage your purchases and subscriptions',
|
||||
createOrder: 'Create Order',
|
||||
|
|
@ -1419,21 +1425,43 @@ export default {
|
|||
description: 'You don\'t have any order records yet',
|
||||
action: 'Create Order'
|
||||
},
|
||||
status: {
|
||||
all: 'All',
|
||||
pending: 'Pending',
|
||||
processing: 'Processing',
|
||||
shipped: 'Shipped',
|
||||
delivered: 'Delivered',
|
||||
cancelled: 'Cancelled',
|
||||
refunded: 'Refunded'
|
||||
},
|
||||
status: {
|
||||
yjj:'Rejected',
|
||||
dsh: 'Pending Review',
|
||||
all: 'All',
|
||||
pending: 'Pending',
|
||||
paid: 'Paid',
|
||||
processing: 'Processing',
|
||||
shipped: 'Shipped',
|
||||
delivered: 'Delivered',
|
||||
completed: 'Completed',
|
||||
cancelled: 'Cancelled',
|
||||
refunded: 'Refunded',
|
||||
expired: 'Expired',
|
||||
shenhe: 'Under Review',
|
||||
unsuccess: 'Rejected',
|
||||
clz: 'Processing',
|
||||
dfh: 'Pending Shipment'
|
||||
},
|
||||
sort: {
|
||||
created_at: 'Created Time',
|
||||
total: 'Order Total',
|
||||
status: 'Order Status',
|
||||
customer: 'Customer Name'
|
||||
}
|
||||
},
|
||||
payment: {
|
||||
pending: 'Pending Payment',
|
||||
paid: 'Paid',
|
||||
failed: 'Payment Failed',
|
||||
refunded: 'Refunded'
|
||||
},
|
||||
refundStatus:{
|
||||
wtk:'No Refund',
|
||||
sqtk:'Refund Requested',
|
||||
jjtk:'Refund Rejected',
|
||||
tytk:'Refund Approved',
|
||||
ytk:'Refunded'
|
||||
},
|
||||
},
|
||||
deviceSettings: {
|
||||
title: 'Device Settings',
|
||||
|
|
@ -1526,13 +1554,17 @@ export default {
|
|||
paid: 'Paid',
|
||||
failed: 'Payment Failed',
|
||||
refunded: 'Refunded'
|
||||
}
|
||||
,
|
||||
},
|
||||
cancelConfirm: {
|
||||
title: 'Cancel Order',
|
||||
message: 'Are you sure you want to cancel this order? This action cannot be undone.'
|
||||
},
|
||||
cancelSuccess: 'Order cancelled successfully',
|
||||
cancelFail: 'Failed to cancel order',
|
||||
countdown: {
|
||||
remaining: 'Time left to pay',
|
||||
expired: 'Expired'
|
||||
}
|
||||
,
|
||||
},
|
||||
expiredNotice: 'Order expired, cannot pay. Please create a new order'
|
||||
},
|
||||
logistics: {
|
||||
|
|
|
|||
|
|
@ -3,24 +3,38 @@ import App from './App.vue'
|
|||
|
||||
// Styles
|
||||
import 'normalize.css'
|
||||
import './styles/base.css'
|
||||
import './styles/theme.css'
|
||||
import './styles/tailwind.css' // Tailwind CSS 样式
|
||||
// import './styles/base.css'
|
||||
// import './styles/theme.css'
|
||||
// Enable Element Plus dark theme CSS variables when html has the `dark` class
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||
|
||||
// Plugins
|
||||
import ElementPlus from 'element-plus'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import i18nConfig from './locales/index.js'
|
||||
import router from './router'
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import VueLazyload from 'vue3-lazyload'
|
||||
import 'element-plus/dist/index.css'
|
||||
import {ElMessage,ElLoading } from 'element-plus'
|
||||
const app = createApp(App)
|
||||
|
||||
window.setElMessage = (options={})=>{
|
||||
ElMessage[options.type || 'info'](options.message || '请求失败')
|
||||
}
|
||||
window.setElLoading = ()=>{
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: 'Loading',
|
||||
background: 'rgba(0, 0, 0, 0.4)',
|
||||
})
|
||||
return loading
|
||||
// loading.close()
|
||||
}
|
||||
// Pinia
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
app.use(pinia)
|
||||
|
||||
// i18n
|
||||
|
|
@ -89,7 +103,6 @@ scrollbarStyle.textContent = `
|
|||
|
||||
// Element Plus ElMessage uses default styles - no custom overrides needed
|
||||
document.head.appendChild(scrollbarStyle)
|
||||
|
||||
// Import message-fix styles last to ensure project dark overrides don't break
|
||||
// Element Plus ElMessage appearance.
|
||||
// import './styles/element-fix.css'
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const routes = [
|
|||
path: '/',
|
||||
name: 'home',
|
||||
component: home,
|
||||
meta: { fullScreen: true }
|
||||
meta: { fullScreen: false }
|
||||
},
|
||||
{
|
||||
path: '/czhome',
|
||||
|
|
@ -128,8 +128,8 @@ const router = createRouter({
|
|||
|
||||
// 路由守卫
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
window.localStorage.setItem('token','123')
|
||||
return next()
|
||||
// window.localStorage.setItem('token','123')
|
||||
// return next()
|
||||
// 检查是否需要登录
|
||||
if (to.meta.requiresAuth) {
|
||||
const token = localStorage.getItem('token')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
// Pinia 持久化测试文件
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import { useAuthStore } from '../auth'
|
||||
import { useThemeStore } from '../theme'
|
||||
|
||||
// 创建测试用的 pinia 实例
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
|
||||
// 测试持久化功能
|
||||
export function testPersistence() {
|
||||
console.log('=== Pinia 持久化测试开始 ===')
|
||||
|
||||
// 测试 auth store 持久化
|
||||
const authStore = useAuthStore(pinia)
|
||||
console.log('1. Auth Store 初始状态:', {
|
||||
token: authStore.token,
|
||||
user: authStore.user
|
||||
})
|
||||
|
||||
// 模拟登录
|
||||
authStore.loginSuccess({
|
||||
accessToken: 'test-token-12345',
|
||||
user: { id: 1, name: '测试用户' }
|
||||
})
|
||||
|
||||
console.log('2. 登录后状态:', {
|
||||
token: authStore.token,
|
||||
user: authStore.user
|
||||
})
|
||||
|
||||
// 测试 theme store 持久化
|
||||
const themeStore = useThemeStore(pinia)
|
||||
console.log('3. Theme Store 初始状态:', {
|
||||
isDark: themeStore.isDark,
|
||||
theme: themeStore.theme
|
||||
})
|
||||
|
||||
// 切换主题
|
||||
themeStore.toggleTheme()
|
||||
|
||||
console.log('4. 主题切换后状态:', {
|
||||
isDark: themeStore.isDark,
|
||||
theme: themeStore.theme
|
||||
})
|
||||
|
||||
console.log('5. localStorage 中的数据:')
|
||||
console.log('- auth:', localStorage.getItem('auth'))
|
||||
console.log('- theme:', localStorage.getItem('theme'))
|
||||
|
||||
console.log('=== Pinia 持久化测试完成 ===')
|
||||
}
|
||||
|
||||
// 清理测试数据
|
||||
export function cleanupTestData() {
|
||||
localStorage.removeItem('auth')
|
||||
localStorage.removeItem('theme')
|
||||
localStorage.removeItem('payment')
|
||||
localStorage.removeItem('orders')
|
||||
localStorage.removeItem('agents')
|
||||
console.log('测试数据已清理')
|
||||
}
|
||||
|
|
@ -602,4 +602,10 @@ export const useAgentStore = defineStore('agents', () => {
|
|||
refresh,
|
||||
reset
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
key: 'agents',
|
||||
storage: localStorage,
|
||||
paths: ['searchQuery', 'statusFilter', 'deviceBoundFilter', 'sortBy', 'sortOrder', 'pagination.page', 'pagination.pageSize']
|
||||
}
|
||||
})
|
||||
|
|
@ -1,17 +1,16 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref} from 'vue'
|
||||
import { clientApi, requestUtils } from '@deotaland/utils'
|
||||
import { useRouter } from 'vue-router';
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
// 状态定义
|
||||
const user = ref({})
|
||||
const token = ref('')
|
||||
const loading = ref(false)
|
||||
// 登录方法
|
||||
const login = async (data,callback=null) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await requestUtils.common(clientApi.default.LOGIN, data)
|
||||
if(res.code === 200){
|
||||
if(res.code === 0){
|
||||
let data = res.data;
|
||||
// 登录成功,保存token和用户信息
|
||||
loginSuccess(data,callback)
|
||||
|
|
@ -21,7 +20,6 @@ export const useAuthStore = defineStore('auth', () => {
|
|||
console.error('登录失败:', error)
|
||||
throw error
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
//登录成功方法
|
||||
|
|
@ -33,36 +31,40 @@ export const useAuthStore = defineStore('auth', () => {
|
|||
}
|
||||
// 登出方法
|
||||
const logout = async (callback) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await requestUtils.common(clientApi.default.LOGOUT)
|
||||
if(res.code === 200){
|
||||
// 登出成功,清除token和用户信息
|
||||
user.value = null
|
||||
token.value = ''
|
||||
localStorage.removeItem('token')
|
||||
callback&&callback();
|
||||
return res
|
||||
}
|
||||
|
||||
return res
|
||||
} catch (error) {
|
||||
console.error('登出失败:', error)
|
||||
throw error
|
||||
} finally {
|
||||
loading.value = false
|
||||
user.value = null
|
||||
token.value = ''
|
||||
localStorage.removeItem('token')
|
||||
callback&&callback();
|
||||
const router = useRouter();
|
||||
localStorage.removeItem('token')
|
||||
router.replace({ name: 'login' });
|
||||
}
|
||||
}
|
||||
// 获取用户信息
|
||||
const getUserInfo = () => {
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
token,
|
||||
login,
|
||||
logout,
|
||||
getUserInfo,
|
||||
loginSuccess,
|
||||
loading
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
storage: {
|
||||
setItem(key, value) {
|
||||
localStorage.setItem(key, value);
|
||||
},
|
||||
getItem(key) {
|
||||
return localStorage.getItem(key);
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -419,4 +419,10 @@ export const useOrderStore = defineStore('orders', () => {
|
|||
exportOrders,
|
||||
initSampleData
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
key: 'orders',
|
||||
storage: localStorage,
|
||||
paths: ['filters', 'pagination']
|
||||
}
|
||||
})
|
||||
|
|
@ -473,4 +473,10 @@ export const usePaymentStore = defineStore('payment', () => {
|
|||
clearError,
|
||||
resetState
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
key: 'payment',
|
||||
storage: localStorage,
|
||||
paths: ['paymentMethods', 'coupons']
|
||||
}
|
||||
})
|
||||
|
|
@ -51,4 +51,10 @@ export const useThemeStore = defineStore('theme', () => {
|
|||
setTheme,
|
||||
initTheme
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
key: 'theme',
|
||||
storage: localStorage,
|
||||
paths: ['isDark']
|
||||
}
|
||||
})
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/* Tailwind CSS 基础样式 */
|
||||
@import "tailwindcss";
|
||||
|
||||
/* 自定义基础样式 */
|
||||
@layer base {
|
||||
/* 设置默认字体 */
|
||||
html {
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
/* 设置基础文本颜色 */
|
||||
body {
|
||||
@apply text-gray-800 bg-gray-50;
|
||||
}
|
||||
|
||||
/* 暗黑模式基础样式 */
|
||||
html.dark {
|
||||
@apply bg-gray-900;
|
||||
}
|
||||
|
||||
html.dark body {
|
||||
@apply text-gray-200 bg-gray-900;
|
||||
}
|
||||
}
|
||||
|
|
@ -841,10 +841,11 @@ onUnmounted(() => {
|
|||
|
||||
<style scoped>
|
||||
.add-agent-page {
|
||||
min-height: 100vh;
|
||||
height: 90vh;
|
||||
background: var(--el-bg-color-page);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 页面头部样式 */
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@
|
|||
style="display: none"
|
||||
@change="handleFileSelect"
|
||||
/>
|
||||
|
||||
<el-scrollbar height="88vh" @end-reached="loadMore">
|
||||
<!-- 项目卡片网格 -->
|
||||
<div class="projects-grid">
|
||||
|
||||
<!-- 项目卡片 -->
|
||||
<div
|
||||
v-for="project in projects"
|
||||
|
|
@ -79,11 +80,10 @@
|
|||
<span class="new-project-text">{{ $t('creationWorkspace.createNewProject') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</el-scrollbar>
|
||||
<!-- 初始加载蒙层 -->
|
||||
<div v-if="loading && page === 1" class="overlay-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<p class="loading-text">{{ $t('loading') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 加载更多状态 -->
|
||||
|
|
@ -251,7 +251,9 @@ export default {
|
|||
// 二次确认相关状态
|
||||
const showDeleteConfirm = ref(false);
|
||||
const projectToDelete = ref(null);
|
||||
|
||||
const loadMore = ()=>{
|
||||
getProjectList();
|
||||
}
|
||||
// 获取项目列表
|
||||
const getProjectList = async () => {
|
||||
if (loading.value || finished.value) return;
|
||||
|
|
@ -280,52 +282,6 @@ export default {
|
|||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 节流函数,限制滚动事件触发频率
|
||||
const throttle = (func, delay) => {
|
||||
let timeoutId;
|
||||
let lastExecTime = 0;
|
||||
|
||||
return function(...args) {
|
||||
const currentTime = Date.now();
|
||||
const elapsed = currentTime - lastExecTime;
|
||||
|
||||
const execute = () => {
|
||||
lastExecTime = currentTime;
|
||||
func.apply(this, args);
|
||||
};
|
||||
|
||||
if (elapsed >= delay) {
|
||||
execute();
|
||||
} else {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(execute, delay - elapsed);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// 优化后的滚动加载更多,进一步减少计算量
|
||||
const handleScroll = throttle(() => {
|
||||
// 只在非加载状态下执行
|
||||
if (loading.value || finished.value) return;
|
||||
|
||||
try {
|
||||
// 使用更高效的方式获取滚动位置
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||
const scrollHeight = document.documentElement.scrollHeight;
|
||||
const clientHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
|
||||
// 简化计算,只检查是否接近底部
|
||||
const distanceToBottom = scrollHeight - (scrollTop + clientHeight);
|
||||
|
||||
// 当距离底部小于200px时加载更多,增加距离减少触发频率
|
||||
if (distanceToBottom < 200) {
|
||||
getProjectList();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('滚动计算出错:', error);
|
||||
}
|
||||
}, 300); // 增加节流时间到300ms,进一步减少触发频率
|
||||
// 更改项目缩略图
|
||||
const changeProjectThumbnail = (project) => {
|
||||
currentProject.value = project
|
||||
|
|
@ -528,22 +484,7 @@ export default {
|
|||
};
|
||||
onMounted(()=>{
|
||||
getProjectList();
|
||||
// 添加滚动事件监听,使用capture阶段提高性能
|
||||
window.addEventListener('scroll', handleScroll, { passive: true, capture: false });
|
||||
console.log('滚动事件已绑定');
|
||||
})
|
||||
|
||||
// 组件卸载时移除滚动事件监听
|
||||
onUnmounted(()=>{
|
||||
window.removeEventListener('scroll', handleScroll, { passive: true, capture: false });
|
||||
console.log('滚动事件已移除');
|
||||
})
|
||||
|
||||
// 测试滚动功能的辅助函数
|
||||
const testScroll = () => {
|
||||
console.log('测试滚动功能');
|
||||
handleScroll();
|
||||
}
|
||||
return {
|
||||
projects,
|
||||
showModal,
|
||||
|
|
@ -561,7 +502,6 @@ export default {
|
|||
handleFileSelect,
|
||||
formatDate,
|
||||
t,
|
||||
testScroll, // 导出测试函数,方便调试
|
||||
loading, // 导出加载状态
|
||||
finished, // 导出加载完成状态
|
||||
page, // 导出当前页码
|
||||
|
|
@ -571,6 +511,7 @@ export default {
|
|||
showTrash,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
loadMore,
|
||||
onDragOver,
|
||||
onDragEnter,
|
||||
onDragLeave,
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const resetEmail = ref('')
|
|||
// 处理密码重置成功
|
||||
const handleResetSuccess = (email) => {
|
||||
resetEmail.value = email
|
||||
ElMessage.success(t('forgotPassword.reset_success_title'))
|
||||
router.back();
|
||||
}
|
||||
|
||||
// 页面挂载时初始化认证状态
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
<div class="google-login-section">
|
||||
<GoogleOAuthButton
|
||||
@success="handleLoginSuccess"
|
||||
:loading="authStore.loading"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -38,7 +37,6 @@
|
|||
<div class="email-login-section">
|
||||
<LoginForm
|
||||
@login="handleLogin"
|
||||
:loading="authStore.loading"
|
||||
/>
|
||||
</div>
|
||||
<!-- 角色信息展示 -->
|
||||
|
|
@ -121,13 +119,7 @@ const handleLogin = async (data) => {
|
|||
const handleLoginSuccess = (userData) => {
|
||||
console.log('登录成功:', userData)
|
||||
// 跳转到默认页面或用户角色对应页面
|
||||
if (authStore.isCreator) {
|
||||
router.push('/creator')
|
||||
} else if (authStore.isAdmin) {
|
||||
router.push('/admin')
|
||||
} else {
|
||||
router.push('/dashboard')
|
||||
}
|
||||
router.push('/czhome')
|
||||
}
|
||||
|
||||
// 跳转到忘记密码页面
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default class Login {
|
|||
}
|
||||
async login(data) {
|
||||
this.authStore.login(data,()=>{
|
||||
this.router.push({ name: 'home' })
|
||||
this.router.push({ name: 'czhome' })
|
||||
// this.refreshGoogleRefreshToken()
|
||||
});
|
||||
}
|
||||
|
|
@ -19,10 +19,8 @@ export default class Login {
|
|||
email:item.email,
|
||||
purpose:item.purpose||'register' //forgot-password
|
||||
}).then(res=>{
|
||||
if(res.code === 200){
|
||||
ElMessage.success('验证码发送成功');
|
||||
callback&&callback();
|
||||
}
|
||||
})
|
||||
}
|
||||
//确认注册功能
|
||||
|
|
@ -33,23 +31,19 @@ export default class Login {
|
|||
"password": data.password
|
||||
}
|
||||
requestUtils.common(clientApi.default.REGISTER,params).then(res=>{
|
||||
if(res.code === 200){
|
||||
let data = res.data;
|
||||
this.authStore.loginSuccess(data,()=>{
|
||||
this.router.push({ name: 'home' })
|
||||
})
|
||||
// this.authStore.loginSuccess(data,()=>{
|
||||
// this.router.push({ name: 'home' })
|
||||
// })
|
||||
ElMessage.success('注册成功');
|
||||
callback&&callback();
|
||||
}
|
||||
})
|
||||
}
|
||||
//刷新googleRefreshToken
|
||||
refreshGoogleRefreshToken(callback){
|
||||
requestUtils.common(clientApi.default.REFRESH_TOKEN).then(res=>{
|
||||
if(res.code === 200){
|
||||
ElMessage.success('刷新成功');
|
||||
callback&&callback();
|
||||
}
|
||||
})
|
||||
}
|
||||
//登出
|
||||
|
|
@ -66,10 +60,8 @@ export default class Login {
|
|||
"newPassword": data.password
|
||||
}
|
||||
requestUtils.common(clientApi.default.FORGOT_PASSWORD,params).then(res=>{
|
||||
if(res.code === 200){
|
||||
ElMessage.success('密码修改成功');
|
||||
callback&&callback();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -231,7 +231,7 @@ export default {
|
|||
.modern-home {
|
||||
padding: 24px;
|
||||
background: var(--bg-color, #f9fafb);
|
||||
min-height: 100vh;
|
||||
/* min-height: 100vh; */
|
||||
}
|
||||
|
||||
/* 欢迎区域 */
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
type="primary"
|
||||
size="large"
|
||||
class="action-btn primary-btn create-btn-large"
|
||||
@click="navigateToFeature({ path: '/project' })">
|
||||
@click="navigateToFeature({ path: '/creation-workspace' })">
|
||||
{{ t('home.welcome.startCreating') }}
|
||||
</el-button>
|
||||
<div v-else class="guest-actions">
|
||||
|
|
@ -255,7 +255,7 @@ export default {
|
|||
.modern-home {
|
||||
padding: 24px;
|
||||
background: var(--bg-color, #f9fafb);
|
||||
min-height: 100vh;
|
||||
/* min-height: 100vh; */
|
||||
}
|
||||
|
||||
/* 欢迎区域 */
|
||||
|
|
|
|||
|
|
@ -4,23 +4,23 @@
|
|||
<div class="header-left">
|
||||
<el-button text @click="goBack" class="back-btn">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
{{ t('orderManagement.title') }}#{{ order.id }}
|
||||
{{ t('orderManagement.title') }}#{{ order.order_no }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-tag :type="statusTagType" size="large">{{ statusLabel }}</el-tag>
|
||||
<el-tag :type="statusType.status" size="large">{{ t(statusType.label) }}</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="detail-grid">
|
||||
<div class="detail-card">
|
||||
<h3 class="card-title">{{ t('orderManagement.order.products') }}</h3>
|
||||
<div class="products-list">
|
||||
<div v-for="p in order.products" :key="p.id" class="product-item">
|
||||
<img :src="p.image" :alt="p.name" class="product-image" @error="handleImageError" />
|
||||
<div class="product-item">
|
||||
<img :src="order?.order_info?.modelData?.imageUrl" :alt="order.order_info.ipName" class="product-image" />
|
||||
<div class="product-info">
|
||||
<span class="name">{{ p.name }}</span>
|
||||
<span class="qty">x{{ p.quantity }}</span>
|
||||
<span class="name">{{ order.order_info.ipName }}</span>
|
||||
<span style="margin-top: 10px;" class="qty">x{{ order.order_info.quantity }}</span>
|
||||
</div>
|
||||
<div class="price">¥{{ (p.price || 0).toLocaleString() }}</div>
|
||||
<div class="price">¥{{order.actual_amount}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -28,23 +28,20 @@
|
|||
<div class="detail-card">
|
||||
<h3 class="card-title">{{ t('orderManagement.order.payment') }}</h3>
|
||||
<div class="payment-info">
|
||||
<p><strong>{{ t('orderManagement.order.paymentMethod') }}:</strong> {{ getPaymentMethodLabel(order.payment?.method) }}</p>
|
||||
<p><strong>{{ t('orderManagement.order.paymentMethod') }}:</strong> {{order.currency}}</p>
|
||||
<p><strong>{{ t('orderManagement.order.paymentStatus') }}:</strong>
|
||||
<el-tag :type="order.payment?.status === 'paid' ? 'success' : 'warning'" size="small">
|
||||
{{ getPaymentStatusLabel(order.payment?.status) }}
|
||||
</el-tag>
|
||||
{{ t(statusType.label) }}
|
||||
</p>
|
||||
<p v-if="order.payment?.paidAt"><strong>{{ t('orderManagement.order.paidAt') }}:</strong> {{ formatDate(order.payment?.paidAt) }}</p>
|
||||
<p v-if="order.status === 'pending' && !isExpired" class="countdown">
|
||||
<p v-if="order.payment_time"><strong>{{ t('orderManagement.order.paidAt') }}:</strong> {{ formatDate(order.payment_time) }}</p>
|
||||
<!-- <p v-if="false" class="countdown">
|
||||
{{ t('orderManagement.countdown.remaining') }}: {{ countdownText }}
|
||||
</p>
|
||||
<el-tag v-if="order.status === 'pending' && isExpired" type="danger" size="small">{{ t('orderManagement.countdown.expired') }}</el-tag>
|
||||
</p> -->
|
||||
</div>
|
||||
<div v-if="order.status === 'pending' && isExpired" class="expired-notice">
|
||||
<div v-if="statusType.type == 'zfgq'" class="expired-notice" style="margin-top: 10px;">
|
||||
<el-icon><Warning /></el-icon>
|
||||
<span>{{ t('orderManagement.expiredNotice') }}</span>
|
||||
</div>
|
||||
<div v-if="order.status === 'pending' && !isExpired" class="payment-form">
|
||||
<!-- <div v-if="order.status === 'pending' && !isExpired" class="payment-form">
|
||||
<StripePaymentForm
|
||||
:amount="toCents(order.amount || order.total || 0)"
|
||||
:currency="'usd'"
|
||||
|
|
@ -54,14 +51,14 @@
|
|||
@payment-error="onPaymentError"
|
||||
@cancel="onPaymentCancel"
|
||||
/>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<h3 class="card-title">{{ t('orderManagement.order.shipping') }}</h3>
|
||||
<p><strong>{{ t('orderManagement.order.recipient') }}:</strong> {{ order.shipping?.recipientName || '-' }}</p>
|
||||
<p><strong>{{ t('orderManagement.order.phone') }}:</strong> {{ order.shipping?.phone || '-' }}</p>
|
||||
<p><strong>{{ t('orderManagement.order.address') }}:</strong> {{ order.shipping?.address || '-' }}</p>
|
||||
<p style="margin-top: 16px;"><strong>{{ t('orderManagement.order.recipient') }}:</strong> {{ shipping?.firstName+shipping?.lastName || '-' }}</p>
|
||||
<p style="margin-top: 16px;"><strong>{{ t('orderManagement.order.phone') }}:</strong> {{ shipping?.phone || '-' }}</p>
|
||||
<p style="margin-top: 16px;"><strong>{{ t('orderManagement.order.address') }}:</strong> {{ shipping?.countryLabel+shipping?.address1+shipping?.address2+shipping?.city || '-' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -76,131 +73,49 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import StripePaymentForm from '@/components/StripePaymentForm.vue'
|
||||
import { orderStatus } from '@deotaland/utils'
|
||||
import LogisticsTimeline from '@/components/LogisticsTimeline.vue'
|
||||
import { useOrderStore } from '@/stores/orders'
|
||||
import { ArrowLeft, Warning } from '@element-plus/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'OrderDetail',
|
||||
components: { StripePaymentForm, LogisticsTimeline, ArrowLeft, Warning },
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const orderStore = useOrderStore()
|
||||
|
||||
const order = ref(null)
|
||||
const isExpired = ref(false)
|
||||
const countdownText = ref('')
|
||||
let timer = null
|
||||
|
||||
const statusTagType = computed(() => {
|
||||
const map = { pending: 'warning', paid: 'success', shipped: 'primary', completed: 'success', expired: 'danger', cancelled: 'danger' }
|
||||
return map[order.value?.status] || 'info'
|
||||
})
|
||||
const statusLabel = computed(() => t(`orderManagement.status.${order.value?.status}`))
|
||||
|
||||
const formatDate = (d) => {
|
||||
if (!d) return '-'
|
||||
const dd = new Date(d)
|
||||
return dd.toLocaleDateString() + ' ' + dd.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
|
||||
const getPaymentMethodLabel = (method) => {
|
||||
const map = { stripe: 'Stripe', alipay: '支付宝', wechat: '微信支付', card: '信用卡', bank: '银行转账' }
|
||||
return map[method] || method || '-'
|
||||
}
|
||||
const getPaymentStatusLabel = (status) => {
|
||||
const map = {
|
||||
pending: t('orderManagement.payment.pending'),
|
||||
paid: t('orderManagement.payment.paid'),
|
||||
failed: t('orderManagement.payment.failed'),
|
||||
refunded: t('orderManagement.payment.refunded')
|
||||
}
|
||||
return map[status] || status || '-'
|
||||
}
|
||||
|
||||
const toCents = (amount) => Math.round((amount || 0) * 100)
|
||||
|
||||
const updateCountdown = () => {
|
||||
const expiresAt = order.value?.expiresAt ? new Date(order.value.expiresAt).getTime() : null
|
||||
if (!expiresAt || order.value?.status !== 'pending') {
|
||||
isExpired.value = false
|
||||
countdownText.value = ''
|
||||
return
|
||||
}
|
||||
const now = Date.now()
|
||||
const remaining = Math.max(0, expiresAt - now)
|
||||
if (remaining <= 0) {
|
||||
isExpired.value = true
|
||||
countdownText.value = '00:00'
|
||||
stopTimer()
|
||||
orderStore.updateOrder(order.value.id, { status: 'expired' })
|
||||
return
|
||||
}
|
||||
const m = Math.floor(remaining / 60000)
|
||||
const s = Math.floor((remaining % 60000) / 1000)
|
||||
countdownText.value = `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
|
||||
}
|
||||
const startTimer = () => { updateCountdown(); stopTimer(); timer = setInterval(updateCountdown, 1000) }
|
||||
const stopTimer = () => { if (timer) { clearInterval(timer); timer = null } }
|
||||
|
||||
const onPaymentSuccess = ({ orderId }) => {
|
||||
orderStore.updateOrder(orderId, {
|
||||
status: 'paid',
|
||||
payment: {
|
||||
...(orderStore.getOrder(orderId)?.payment || {}),
|
||||
status: 'paid',
|
||||
paidAt: new Date().toISOString(),
|
||||
method: 'stripe'
|
||||
}
|
||||
})
|
||||
order.value = orderStore.getOrder(orderId)
|
||||
}
|
||||
const onPaymentError = () => {}
|
||||
const onPaymentCancel = () => {}
|
||||
|
||||
const handleImageError = (e) => { e.target.src = '/src/assets/demo.png' }
|
||||
|
||||
onMounted(() => {
|
||||
if (!orderStore.orders || orderStore.orders.length === 0) orderStore.initSampleData()
|
||||
const id = route.params.orderId
|
||||
const o = orderStore.getOrder(id)
|
||||
if (!o) {
|
||||
router.replace({ name: 'order-management' })
|
||||
return
|
||||
}
|
||||
order.value = o
|
||||
if (order.value.status === 'pending') startTimer()
|
||||
})
|
||||
const goBack = () => {
|
||||
router.push({ name: 'order-management' })
|
||||
}
|
||||
onUnmounted(() => stopTimer())
|
||||
return {
|
||||
t,
|
||||
order,
|
||||
statusTagType,
|
||||
statusLabel,
|
||||
countdownText,
|
||||
isExpired,
|
||||
formatDate,
|
||||
getPaymentMethodLabel,
|
||||
getPaymentStatusLabel,
|
||||
toCents,
|
||||
onPaymentSuccess,
|
||||
onPaymentError,
|
||||
onPaymentCancel,
|
||||
handleImageError,
|
||||
goBack
|
||||
}
|
||||
}
|
||||
import {OrderManagement} from './OrderManagement/OrderManagement';
|
||||
const orderManagement = new OrderManagement();
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const order = ref(null)
|
||||
const shipping = ref({});
|
||||
const formatDate = (d) => {
|
||||
if (!d) return '-'
|
||||
const dd = new Date(d)
|
||||
return dd.toLocaleDateString() + ' ' + dd.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
const order_id = ref('')
|
||||
const statusType = computed(() => {
|
||||
return orderStatus.getOrderStatusOptions(order.value)
|
||||
})
|
||||
const init = () => {
|
||||
// 这里应该加载订单数据
|
||||
// 示例:order.value = await getOrder(order_id.value)
|
||||
let parmas = {
|
||||
id:order_id.value
|
||||
}
|
||||
orderManagement.getOrderDetail(parmas).then((res) => {
|
||||
order.value = res.data
|
||||
shipping.value = order.value?.order_info?.shipping
|
||||
})
|
||||
}
|
||||
const goBack = () => {
|
||||
router.push({ name: 'order-management' })
|
||||
}
|
||||
onMounted(() => {
|
||||
const id = route.params.orderId
|
||||
order_id.value = id
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
@ -221,7 +136,7 @@ export default {
|
|||
.price { font-weight: 600; }
|
||||
.payment-info { display: grid; row-gap: 6px; }
|
||||
.payment-info p { margin: 0; display: flex; align-items: center; }
|
||||
.payment-info p strong { display: inline-block; min-width: 96px; margin-right: 8px; }
|
||||
.payment-info p strong { display: inline-block; min-width: 66px; margin-right: 8px; }
|
||||
.countdown { font-size: 14px; color: var(--text-secondary, #6b7280); display: flex; align-items: center; }
|
||||
.detail-card p { display: flex; align-items: center; }
|
||||
.timeline-section { margin-top: 16px; }
|
||||
|
|
|
|||
|
|
@ -12,20 +12,20 @@
|
|||
:class="['status-tab', { active: selectedStatus === status.key }]"
|
||||
@click="selectStatus(status.key)"
|
||||
>
|
||||
{{ status.label }}
|
||||
<el-badge
|
||||
{{ t(status.label) }}
|
||||
<!-- <el-badge
|
||||
v-if="status.count !== undefined"
|
||||
:value="status.count"
|
||||
:max="99"
|
||||
:type="selectedStatus === status.key ? 'primary' : 'info'"
|
||||
/>
|
||||
/> -->
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">{{ t('orderManagement.filters.search') }}</label>
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
v-model="order_no"
|
||||
:placeholder="t('orderManagement.searchPlaceholder')"
|
||||
class="search-input"
|
||||
clearable
|
||||
|
|
@ -37,13 +37,13 @@
|
|||
</div>
|
||||
<div class="filter-group">
|
||||
<label class="filter-label">{{ t('orderManagement.filters.sort') }}</label>
|
||||
<el-select v-model="sortBy" class="sort-select">
|
||||
<el-select v-model="sort_by" class="sort-select">
|
||||
<el-option v-for="opt in sortOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<el-scrollbar height="88vh" @end-reached="loadMore">
|
||||
<div class="orders-grid">
|
||||
<OrderCard
|
||||
v-for="order in order_list"
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
@expired-order="handleExpiredOrder"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</el-scrollbar>
|
||||
<div v-if="order_list.length === 0" class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<el-icon><DocumentDelete /></el-icon>
|
||||
|
|
@ -76,6 +76,7 @@ import OrderCard from '@/components/OrderCard.vue'
|
|||
import StripePaymentForm from '@/components/StripePaymentForm.vue'
|
||||
import { useOrderStore } from '@/stores/orders'
|
||||
import {OrderManagement} from './OrderManagement';
|
||||
import {orderStatus} from '@deotaland/utils'
|
||||
const orderPlug = new OrderManagement()
|
||||
export default {
|
||||
name: 'OrderManagement',
|
||||
|
|
@ -89,40 +90,22 @@ export default {
|
|||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const orderStore = useOrderStore()
|
||||
|
||||
const paymentDialogVisible = ref(false)
|
||||
const currentPaymentOrder = ref(null)
|
||||
|
||||
const ordersToShow = computed(() => orderStore.paginatedOrders || [])
|
||||
const selectedStatus = ref('all')
|
||||
const searchQuery = ref('')
|
||||
const sortBy = ref('created_at')
|
||||
const statusFilters = ref([
|
||||
{ key: 'all', label: t('orderManagement.status.all'), count: 0 },
|
||||
{ key: 'pending', label: t('orderManagement.status.pending'), count: 0 },
|
||||
{ key: 'paid', label: t('orderManagement.status.paid'), count: 0 },
|
||||
{ key: 'processing', label: t('orderManagement.status.processing'), count: 0 },
|
||||
{ key: 'shipped', label: t('orderManagement.status.shipped'), count: 0 },
|
||||
{ key: 'delivered', label: t('orderManagement.status.delivered'), count: 0 },
|
||||
{ key: 'completed', label: t('orderManagement.status.completed'), count: 0 },
|
||||
{ key: 'cancelled', label: t('orderManagement.status.cancelled'), count: 0 },
|
||||
{ key: 'refunded', label: t('orderManagement.status.refunded'), count: 0 },
|
||||
{ key: 'expired', label: t('orderManagement.status.expired'), count: 0 }
|
||||
])
|
||||
const statusFilters = orderStatus.selectList('1');
|
||||
const sortOptions = ref([
|
||||
{ label: t('orderManagement.sort.created_at'), value: 'created_at' },
|
||||
{ label: t('orderManagement.sort.total'), value: 'amount' },
|
||||
// { label: t('orderManagement.sort.status'), value: 'status' },
|
||||
// { label: t('orderManagement.sort.customer'), value: 'customerName' }
|
||||
])
|
||||
|
||||
const viewOrderDetails = (orderData) => {
|
||||
console.log(orderData);
|
||||
router.push({ name: 'order-detail', params: { orderId:orderData.id } })
|
||||
}
|
||||
const handlePayOrder = (orderData) => {
|
||||
window.location.href = orderData.stripe_url
|
||||
}
|
||||
|
||||
const onPaymentSuccess = ({ orderId }) => {
|
||||
orderStore.updateOrder(orderId, {
|
||||
status: 'paid',
|
||||
|
|
@ -181,54 +164,72 @@ export default {
|
|||
const page_size = ref(10);
|
||||
//订单筛选
|
||||
const order_no = ref('');
|
||||
const loading = ref(false);
|
||||
const finished = ref(false);
|
||||
//排序方式
|
||||
const sort_by = ref('')
|
||||
const sort_order = ref('asc');//asc/desc
|
||||
const sort_by = ref('created_at')
|
||||
const sort_order = ref('desc');//asc/desc
|
||||
const order_list = ref([]);
|
||||
const total = ref(0);
|
||||
//剩余筛选条件
|
||||
const sxtjjson = ref({});
|
||||
const loadMore = ()=>{
|
||||
getOrderList();
|
||||
}
|
||||
//获取订单列表
|
||||
const getOrderList = ()=>{
|
||||
if (loading.value || finished.value) return;
|
||||
loading.value = true;
|
||||
orderPlug.getOrderList({
|
||||
page: page.value,
|
||||
page_size: page_size.value,
|
||||
order_no: order_no.value,
|
||||
sort_by: sort_by.value,
|
||||
sort_order: sort_order.value
|
||||
sort_order: sort_order.value,
|
||||
...sxtjjson.value
|
||||
}).then(res=>{
|
||||
if(res.code==0){
|
||||
let data = res.data;
|
||||
order_list.value = data.items||[];
|
||||
if (page.value === 1) {
|
||||
order_list.value = data.items;
|
||||
} else {
|
||||
order_list.value = [...order_list.value, ...data.items];
|
||||
}
|
||||
total.value = data.total;
|
||||
finished.value = order_list.value.length >= total.value;
|
||||
page.value++;
|
||||
}
|
||||
})
|
||||
}
|
||||
const init = ()=>{
|
||||
getOrderList();
|
||||
loading.value = false;
|
||||
finished.value = false;
|
||||
order_list.value = [];
|
||||
page.value = 1;
|
||||
page_size.value = 10;
|
||||
getOrderList();
|
||||
}
|
||||
watch(order_no, () => {
|
||||
// 防抖:300ms 内无新输入再触发
|
||||
clearTimeout(window._orderNoDebounce)
|
||||
window._orderNoDebounce = setTimeout(() => {
|
||||
init()
|
||||
}, 300)
|
||||
})
|
||||
onMounted(() => {
|
||||
// orderStore.initSampleData()
|
||||
// updateStatusCounts()
|
||||
init();
|
||||
})
|
||||
|
||||
const selectStatus = (status) => {
|
||||
selectedStatus.value = status
|
||||
orderStore.updateFilters({ status: status === 'all' ? null : status })
|
||||
updateStatusCounts()
|
||||
const json = orderStatus.selectOrderStatusOptions(status);
|
||||
sxtjjson.value = json;
|
||||
selectedStatus.value = status;
|
||||
init()
|
||||
}
|
||||
|
||||
|
||||
const updateStatusCounts = () => {
|
||||
const list = orderStore.orders || []
|
||||
statusFilters.value = statusFilters.value.map(f => ({
|
||||
...f,
|
||||
count: f.key === 'all' ? list.length : list.filter(o => o.status === f.key).length
|
||||
}))
|
||||
}
|
||||
|
||||
watch(searchQuery, () => {
|
||||
orderStore.updateFilters({ search: searchQuery.value })
|
||||
})
|
||||
|
||||
watch(sortBy, () => {
|
||||
orderStore.updateFilters({ sortBy: sortBy.value })
|
||||
watch(sort_by, () => {
|
||||
init();
|
||||
})
|
||||
|
||||
return {
|
||||
|
|
@ -237,8 +238,6 @@ export default {
|
|||
currentPaymentOrder,
|
||||
ordersToShow,
|
||||
selectedStatus,
|
||||
searchQuery,
|
||||
sortBy,
|
||||
statusFilters,
|
||||
sortOptions,
|
||||
viewOrderDetails,
|
||||
|
|
@ -250,8 +249,10 @@ export default {
|
|||
onPaymentCancel,
|
||||
toCents,
|
||||
selectStatus,
|
||||
updateStatusCounts,
|
||||
order_list,
|
||||
order_no,
|
||||
sort_by,
|
||||
loadMore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div class="creative-zone" :style="{ '--grid-size': `${gridSize}px` }">
|
||||
<div class="sidebar-overlay" :class="{ 'sidebar-overlay-active': showSidebarOverlay }"></div>
|
||||
<!-- 顶部固定头部组件 -->
|
||||
<div class="header-wrapper">
|
||||
<HeaderComponent :projectName="projectInfo.title" @updateProjectInfo="projectInfo = {...projectInfo, ...$event}" @openGuideModal="showGuideModal = true" />
|
||||
</div>
|
||||
|
||||
<!-- 导入的侧边栏组件 -->
|
||||
<div class="sidebar-container">
|
||||
<iPandCardLeft
|
||||
|
|
@ -100,6 +100,15 @@
|
|||
@close="closeGuideModal"
|
||||
@complete="completeGuide"
|
||||
/>
|
||||
|
||||
<!-- 测试侧边栏动画的按钮 -->
|
||||
<!-- <button
|
||||
class="test-animation-btn"
|
||||
@click="triggerSidebarAnimation"
|
||||
style="position: fixed; bottom: 20px; right: 20px; z-index: 1000;"
|
||||
>
|
||||
测试动画
|
||||
</button> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -125,6 +134,20 @@ const selectedModel = ref(null);
|
|||
const showImportModal = ref(false);
|
||||
const importUrl = ref('https://xiaozhi.me/console/agents');
|
||||
const showGuideModal = ref(false);
|
||||
|
||||
// 侧边栏过渡动画蒙层状态
|
||||
const showSidebarOverlay = ref(false);
|
||||
|
||||
// 控制侧边栏蒙层显示/隐藏的方法
|
||||
const showSidebarTransition = () => {
|
||||
showSidebarOverlay.value = true;
|
||||
};
|
||||
|
||||
const hideSidebarTransition = () => {
|
||||
showSidebarOverlay.value = false;
|
||||
};
|
||||
|
||||
|
||||
// 事件监听器清理函数存储
|
||||
const cleanupFunctions = ref({});
|
||||
const projectId = ref(null);
|
||||
|
|
@ -143,7 +166,6 @@ watch(()=>[projectInfo.value,cards.value], () => {
|
|||
//保存卡片项目
|
||||
const handleSaveProject = (index,item,type='image')=>{
|
||||
let cardItem = cards.value[index];
|
||||
|
||||
switch(type){
|
||||
case 'image':
|
||||
cardItem.imageUrl = item.imageUrl;
|
||||
|
|
@ -164,16 +186,20 @@ const createProject = async ()=>{
|
|||
getProjectInfo(id);
|
||||
// projectId.value = 8;
|
||||
// getProjectInfo(8)
|
||||
|
||||
}
|
||||
//获取项目信息
|
||||
const getProjectInfo = async (id)=>{
|
||||
showSidebarTransition();
|
||||
const data = await PluginProject.getProject(id);
|
||||
console.log(data,'data');
|
||||
projectInfo.value = {...data};
|
||||
// 为没有id的卡片添加唯一id
|
||||
cards.value = [...projectInfo.value.details.node_card].map(card => ({
|
||||
...card,
|
||||
id: card.id || Date.now() + Math.random().toString(36).substr(2, 9)
|
||||
}));
|
||||
hideSidebarTransition();
|
||||
}
|
||||
//更新项目信息
|
||||
const updateProjectInfo = async (newProjectInfo)=>{
|
||||
|
|
@ -753,7 +779,6 @@ const preventPinchZoom = (e) => {
|
|||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
// 导入被动事件监听器工具
|
||||
import { addPassiveEventListener } from '@/utils/passiveEventListeners'
|
||||
const init = ()=>{
|
||||
|
|
@ -860,6 +885,134 @@ html.dark .sidebar-container {
|
|||
border-bottom-color: rgba(75, 85, 99, 0.8);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 侧边栏过渡动画蒙层 */
|
||||
.sidebar-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(107, 70, 193, 0.1);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
z-index: 9999;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 蒙层激活状态 */
|
||||
.sidebar-overlay-active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* 转圈加载动画 */
|
||||
.sidebar-overlay::before {
|
||||
content: '';
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 4px solid rgba(255, 255, 255, 0.3);
|
||||
border-top: 4px solid #ffffff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
box-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.sidebar-overlay::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.1);
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
animation: spin 1.5s linear infinite reverse;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 暗色主题下的蒙层效果 */
|
||||
html.dark .sidebar-overlay {
|
||||
background: rgba(31, 41, 55, 0.85);
|
||||
}
|
||||
|
||||
html.dark .sidebar-overlay::before {
|
||||
border: 4px solid rgba(255, 255, 255, 0.2);
|
||||
border-top: 4px solid #A78BFA;
|
||||
box-shadow: 0 0 20px rgba(167, 139, 250, 0.5);
|
||||
}
|
||||
|
||||
html.dark .sidebar-overlay::after {
|
||||
border: 2px solid rgba(167, 139, 250, 0.1);
|
||||
border-bottom: 2px solid rgba(167, 139, 250, 0.4);
|
||||
}
|
||||
|
||||
/* 测试动画按钮样式 */
|
||||
.test-animation-btn {
|
||||
background: linear-gradient(135deg, #6B46C1 0%, #7C3AED 50%, #A78BFA 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow:
|
||||
0 4px 12px rgba(107, 70, 193, 0.3),
|
||||
0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.test-animation-btn:hover {
|
||||
transform: translateY(-2px) scale(1.05);
|
||||
box-shadow:
|
||||
0 8px 25px rgba(107, 70, 193, 0.4),
|
||||
0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.test-animation-btn:active {
|
||||
transform: translateY(0) scale(0.98);
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
html.dark .test-animation-btn {
|
||||
background: linear-gradient(135deg, #A78BFA 0%, #8B5CF6 50%, #6B46C1 100%);
|
||||
box-shadow:
|
||||
0 4px 12px rgba(167, 139, 250, 0.3),
|
||||
0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
html.dark .test-animation-btn:hover {
|
||||
box-shadow:
|
||||
0 8px 25px rgba(167, 139, 250, 0.4),
|
||||
0 4px 12px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
/* 响应式设计 - 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.test-animation-btn {
|
||||
padding: 10px 20px;
|
||||
font-size: 12px;
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
.sidebar-overlay {
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
}
|
||||
}
|
||||
.creative-zone::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export class Project{
|
|||
}
|
||||
//更新项目(带防抖处理,三秒内只执行最后一次请求)
|
||||
async updateProject(projectId,projectConfig) {
|
||||
console.log(projectConfig,'projectConfig');
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// 存储最新的请求参数
|
||||
const params = {
|
||||
|
|
@ -45,7 +46,6 @@ export class Project{
|
|||
if (this.updateTimer) {
|
||||
clearTimeout(this.updateTimer);
|
||||
}
|
||||
|
||||
// 设置新的3秒定时器
|
||||
this.updateTimer = setTimeout(async () => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -79,16 +79,7 @@ const { t } = useI18n()
|
|||
|
||||
// 处理注册成功
|
||||
const handleRegisterSuccess = (userData) => {
|
||||
console.log('注册成功:', userData)
|
||||
|
||||
// 注册成功后跳转到登录页或直接登录
|
||||
if (authStore.isCreator) {
|
||||
router.push('/creator')
|
||||
} else if (authStore.isAdmin) {
|
||||
router.push('/admin')
|
||||
} else {
|
||||
router.push('/dashboard')
|
||||
}
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
// 处理注册错误
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
isScrolled ? 'bg-black/90 backdrop-blur-md py-4' : 'bg-transparent py-6'
|
||||
]"
|
||||
>
|
||||
<div class="w-full px-6 flex items-center justify-between">
|
||||
<div class="container mx-auto px-6 flex items-center justify-between">
|
||||
<!-- Logo -->
|
||||
<a href="#" class="text-2xl font-bold tracking-tighter text-white">
|
||||
Deotaland
|
||||
|
|
@ -27,12 +27,12 @@
|
|||
|
||||
<!-- Right Action & Mobile Toggle -->
|
||||
<div class="flex items-center gap-4">
|
||||
<a
|
||||
href="#start"
|
||||
class="hidden md:inline-flex items-center justify-center px-5 py-2 text-sm font-semibold text-black bg-white rounded-full hover:bg-gray-200 transition-colors"
|
||||
<button
|
||||
@click="$router.push('/czhome')"
|
||||
class="hidden md:inline-flex items-center justify-center px-5 py-2 text-sm font-semibold text-black bg-white rounded-full hover:bg-gray-200 transition-colors cursor-pointer"
|
||||
>
|
||||
Start Now
|
||||
</a>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="md:hidden text-white"
|
||||
|
|
@ -54,12 +54,12 @@
|
|||
>
|
||||
{{ link.name }}
|
||||
</a>
|
||||
<a
|
||||
href="#start"
|
||||
class="w-full text-center py-3 text-black bg-white rounded-full font-bold"
|
||||
<button
|
||||
@click="$router.push('/czhome')"
|
||||
class="w-full text-center py-3 text-black bg-white rounded-full font-bold cursor-pointer"
|
||||
>
|
||||
Start Now
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
@ -74,22 +74,22 @@
|
|||
<div class="absolute inset-0 flex items-center justify-center overflow-hidden">
|
||||
<div
|
||||
:style="{ scale }"
|
||||
class="origin-center flex items-center justify-center transition-transform duration-300"
|
||||
class="origin-center flex items-center justify-center"
|
||||
>
|
||||
<!-- Grid Layout -->
|
||||
<div class="grid grid-cols-3 md:grid-cols-5 gap-4 md:gap-6 w-[200vw] md:w-[140vw] h-auto p-4">
|
||||
<!-- Row 1 -->
|
||||
<!-- --- ROW 1 --- -->
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[0]" class="w-full h-full object-cover opacity-60" alt="Robot Companion" /></div>
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[1]" class="w-full h-full object-cover opacity-60" alt="Electronics" /></div>
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[2]" class="w-full h-full object-cover opacity-60" alt="Retro Bot" /></div>
|
||||
<div class="hidden md:block aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[3]" class="w-full h-full object-cover opacity-60" alt="Toy Bot" /></div>
|
||||
<div class="hidden md:block aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[4]" class="w-full h-full object-cover opacity-60" alt="Cyberpunk" /></div>
|
||||
|
||||
<!-- Row 2 (Middle) -->
|
||||
<!-- --- ROW 2 (Middle) --- -->
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[5]" class="w-full h-full object-cover opacity-60" alt="Interactive" /></div>
|
||||
<div class="hidden md:block aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[6]" class="w-full h-full object-cover opacity-60" alt="Small Bot" /></div>
|
||||
|
||||
<!-- CENTER HERO IMAGE (Always Visible) -->
|
||||
|
||||
<!-- --- CENTER HERO IMAGE (Always Visible) --- -->
|
||||
<div class="col-span-1 row-span-1 aspect-[9/16] rounded-xl overflow-hidden shadow-2xl relative z-10 bg-gray-800 border border-gray-700">
|
||||
<img :src="heroImage" class="w-full h-full object-cover" alt="Main Hero Robot" />
|
||||
</div>
|
||||
|
|
@ -97,7 +97,7 @@
|
|||
<div class="hidden md:block aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[7]" class="w-full h-full object-cover opacity-60" alt="3D Print" /></div>
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[8]" class="w-full h-full object-cover opacity-60" alt="Glowing Eye" /></div>
|
||||
|
||||
<!-- Row 3 -->
|
||||
<!-- --- ROW 3 --- -->
|
||||
<div class="hidden md:block aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[9]" class="w-full h-full object-cover opacity-60" alt="Tech Texture" /></div>
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[10]" class="w-full h-full object-cover opacity-60" alt="Robot Hand" /></div>
|
||||
<div class="aspect-[9/16] rounded-xl overflow-hidden bg-gray-900/50"><img :src="gridImages[11]" class="w-full h-full object-cover opacity-60" alt="Circuit" /></div>
|
||||
|
|
@ -126,12 +126,12 @@
|
|||
>
|
||||
Explore More
|
||||
</a>
|
||||
<a
|
||||
href="#login"
|
||||
class="px-9 py-4 rounded-full bg-white text-black font-semibold hover:bg-gray-200 transition-all text-lg shadow-[0_0_20px_rgba(255,255,255,0.3)]"
|
||||
<button
|
||||
@click="$router.push('/czhome')"
|
||||
class="px-9 py-4 rounded-full bg-white text-black font-semibold hover:bg-gray-200 transition-all text-lg shadow-[0_0_20px_rgba(255,255,255,0.3)] cursor-pointer"
|
||||
>
|
||||
Start Now
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -144,9 +144,9 @@
|
|||
<!-- Background gradient hint -->
|
||||
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-[800px] h-[500px] bg-gray-900/50 blur-[120px] rounded-full pointer-events-none" />
|
||||
|
||||
<div class="w-full px-6 relative z-10">
|
||||
<div class="container mx-auto px-6 relative z-10">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl md:text-5xl font-bold mb-4">
|
||||
<h2 class="text-4xl md:text-5xl font-bold mb-4 text-white">
|
||||
Your Creation canvas – DeotaBoard
|
||||
</h2>
|
||||
<p class="text-gray-400 text-lg md:text-xl">
|
||||
|
|
@ -237,7 +237,7 @@
|
|||
|
||||
<!-- Companionship Section -->
|
||||
<section class="py-24 bg-black border-t border-gray-900">
|
||||
<div class="w-full px-6 flex flex-col items-center text-center">
|
||||
<div class="container mx-auto px-6 flex flex-col items-center text-center">
|
||||
|
||||
<h2 class="text-4xl md:text-6xl font-bold mb-6 tracking-tight">
|
||||
Born for Personal Companionship
|
||||
|
|
@ -253,7 +253,7 @@
|
|||
|
||||
<a
|
||||
href="https://deotaland.com"
|
||||
class="inline-flex items-center justify-center px-8 py-4 text-base font-bold text-black bg-white rounded-full hover:scale-105 transition-transform duration-200"
|
||||
class="inline-flex items-center justify-center px-8 py-4 text-base font-bold text-white bg-purple-600 rounded-full hover:scale-105 transition-transform duration-200"
|
||||
>
|
||||
See Robot Examples
|
||||
</a>
|
||||
|
|
@ -261,18 +261,28 @@
|
|||
<!-- Optional Visual Element below -->
|
||||
<div class="mt-16 w-full max-w-4xl h-64 md:h-96 rounded-3xl overflow-hidden relative">
|
||||
<img
|
||||
src="https://picsum.photos/1200/600?grayscale"
|
||||
:src="lopi"
|
||||
alt="Robot Companion Context"
|
||||
class="w-full h-full object-cover opacity-40 hover:opacity-60 transition-opacity duration-500"
|
||||
class="w-full h-full object-cover opacity-90 hover:opacity-100 transition-opacity duration-500"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black via-transparent to-transparent"></div>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/10 via-transparent to-transparent"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Middle Text Section -->
|
||||
<section class="py-12 bg-black text-center">
|
||||
<div class="container mx-auto px-6 max-w-3xl">
|
||||
<p class="text-lg md:text-xl text-gray-400 leading-relaxed">
|
||||
Explore our community creations and get inspired to build your own unique AI robot companion.
|
||||
Each robot has its own personality and story, waiting to be discovered and loved.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Robot Cards Section -->
|
||||
<section class="py-20 bg-black overflow-hidden">
|
||||
<div class="w-full px-6">
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-6xl mx-auto">
|
||||
<div
|
||||
v-for="card in cards"
|
||||
|
|
@ -284,7 +294,7 @@
|
|||
:alt="card.title"
|
||||
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-90" />
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-90" />
|
||||
<div class="absolute bottom-0 left-0 p-6">
|
||||
<h3 class="text-xl font-bold text-white mb-1">{{ card.title }}</h3>
|
||||
<p class="text-sm font-medium text-gray-300">{{ card.user }}</p>
|
||||
|
|
@ -296,7 +306,7 @@
|
|||
|
||||
<!-- Features Section -->
|
||||
<section class="py-24 bg-gray-900/30">
|
||||
<div class="w-full px-6">
|
||||
<div class="container mx-auto px-6">
|
||||
|
||||
<div class="mb-16 text-center md:text-left">
|
||||
<h2 class="text-4xl md:text-5xl font-bold text-white mb-4">
|
||||
|
|
@ -412,7 +422,7 @@
|
|||
<!-- Action -->
|
||||
<div class="flex flex-col gap-4">
|
||||
<button
|
||||
@click="() => window.scrollTo({ top: 0, behavior: 'smooth' })"
|
||||
@click="scrollToTop"
|
||||
class="text-gray-300 hover:text-white text-sm text-left"
|
||||
>
|
||||
Back to top
|
||||
|
|
@ -430,15 +440,21 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
|
||||
import lopi from '@/assets/home/lopi.jpg'
|
||||
import dog from '@/assets/home/dog.jpg'
|
||||
import dog3 from '@/assets/home/dog3.jpg'
|
||||
import qdog from '@/assets/home/qdog.jpg'
|
||||
import footer1 from '@/assets/home/footer1.png'
|
||||
import footer2 from '@/assets/home/footer2.png'
|
||||
import footer3 from '@/assets/home/footer3.png'
|
||||
// Navbar state
|
||||
const isScrolled = ref(false);
|
||||
const isMobileMenuOpen = ref(false);
|
||||
|
||||
// Hero section state
|
||||
const containerRef = ref<HTMLElement | null>(null);
|
||||
const containerRef = ref(null);
|
||||
const scrollYProgress = ref(0);
|
||||
|
||||
// Scroll event handler
|
||||
|
|
@ -447,16 +463,12 @@ const handleScroll = () => {
|
|||
|
||||
// Calculate scroll progress for hero section
|
||||
if (containerRef.value) {
|
||||
const containerRect = containerRef.value.getBoundingClientRect();
|
||||
const viewportHeight = window.innerHeight;
|
||||
const scrollPosition = window.scrollY;
|
||||
const containerTop = containerRef.value.offsetTop;
|
||||
const containerHeight = containerRef.value.offsetHeight;
|
||||
|
||||
// Calculate progress from 0 to 1 as we scroll through the container
|
||||
// The original React code uses offset: ["start start", "end end"] which means:
|
||||
// - start start: when container starts entering viewport
|
||||
// - end end: when container finishes exiting viewport
|
||||
const progress = Math.max(0, Math.min(1,
|
||||
(scrollPosition - containerTop + viewportHeight) /
|
||||
(containerHeight + viewportHeight)
|
||||
|
|
@ -466,13 +478,19 @@ const handleScroll = () => {
|
|||
}
|
||||
};
|
||||
|
||||
// Scroll to top function
|
||||
const scrollToTop = () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
};
|
||||
|
||||
// Scale transformation based on scroll progress
|
||||
// Original React code: useTransform(scrollYProgress, [0, 0.8], [3.5, 1])
|
||||
const scale = computed(() => {
|
||||
// Zoom out from scale 3.5 to 1 based on scroll (0 to 0.8 progress)
|
||||
// When progress reaches 0.8, we've completed the zoom out
|
||||
const mappedProgress = Math.min(scrollYProgress.value / 0.8, 1);
|
||||
return 3.5 - (mappedProgress * 2.5);
|
||||
let initNum = window.innerWidth < 768 ? 3.5 : 5.5;
|
||||
let outNum = window.innerWidth < 768 ? 2.5 : 4.9;
|
||||
return initNum - (mappedProgress * outNum);
|
||||
});
|
||||
|
||||
// Nav links
|
||||
|
|
@ -512,15 +530,15 @@ const gridImages = [
|
|||
];
|
||||
|
||||
// Creation Canvas Images
|
||||
const refImage = "https://images.unsplash.com/photo-1541364983171-a8ba01e95cfc?auto=format&fit=crop&q=80&w=600";
|
||||
const model3dImage = ""; // Empty for now
|
||||
const realRobotImage = "https://s1.aigei.com/prevfiles/723506e4d8a84b838dbdb237a79cfee5.png?e=2051020800&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:GzjIZgVXP8PT14vf14QLBQfgHGw=";
|
||||
const refImage = dog;
|
||||
const model3dImage = qdog;
|
||||
const realRobotImage = dog3;
|
||||
|
||||
// Robot Cards
|
||||
const cards = [
|
||||
{ id: 1, title: 'Custom Robot', user: '@Wownny wolf', img: 'https://picsum.photos/300/400?random=30' },
|
||||
{ id: 2, title: 'Custom Robot', user: '@Lil Moods', img: 'https://picsum.photos/300/400?random=31' },
|
||||
{ id: 3, title: 'Custom Robot', user: '@Deo Monkey', img: 'https://picsum.photos/300/400?random=32' },
|
||||
{ id: 1, title: 'Custom Robot', user: '@Wownny wolf', img:footer3 },
|
||||
{ id: 2, title: 'Custom Robot', user: '@Lil Moods', img: footer2 },
|
||||
{ id: 3, title: 'Custom Robot', user: '@Deo Monkey', img:footer1 },
|
||||
];
|
||||
|
||||
// Features List
|
||||
|
|
@ -551,4 +569,4 @@ onUnmounted(() => {
|
|||
|
||||
<style scoped>
|
||||
/* Additional custom styles can be added here */
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
// 自定义颜色主题
|
||||
colors: {
|
||||
primary: {
|
||||
DEFAULT: '#6B46C1', // 深紫色
|
||||
light: '#A78BFA', // 浅紫色
|
||||
dark: '#553C9A', // 更深的紫色
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: '#1F2937', // 深灰色
|
||||
light: '#4B5563', // 中灰色
|
||||
lighter: '#9CA3AF', // 浅灰色
|
||||
},
|
||||
background: {
|
||||
DEFAULT: '#F3F4F6', // 浅灰色背景
|
||||
dark: '#111827', // 深色背景
|
||||
card: '#FFFFFF', // 卡片背景
|
||||
}
|
||||
},
|
||||
// 响应式断点配置
|
||||
screens: {
|
||||
'xs': '475px', // 超小屏
|
||||
'sm': '640px', // 小屏(手机横屏)
|
||||
'md': '768px', // 中屏(平板)
|
||||
'lg': '1024px', // 大屏(桌面)
|
||||
'xl': '1280px', // 超大屏
|
||||
'2xl': '1536px', // 超超大屏
|
||||
},
|
||||
// 字体配置
|
||||
fontFamily: {
|
||||
'sans': ['Inter', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
// 间距配置(8px网格系统)
|
||||
spacing: {
|
||||
'18': '4.5rem',
|
||||
'88': '22rem',
|
||||
'128': '32rem',
|
||||
},
|
||||
// 圆角配置
|
||||
borderRadius: {
|
||||
'4xl': '2rem',
|
||||
},
|
||||
// 阴影配置
|
||||
boxShadow: {
|
||||
'card': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||||
'card-hover': '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
|
||||
},
|
||||
// 动画配置
|
||||
animation: {
|
||||
'fade-in': 'fadeIn 0.2s ease-in-out',
|
||||
'slide-up': 'slideUp 0.3s ease-out',
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' },
|
||||
},
|
||||
slideUp: {
|
||||
'0%': { transform: 'translateY(10px)', opacity: '0' },
|
||||
'100%': { transform: 'translateY(0)', opacity: '1' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
// 暗黑模式支持
|
||||
darkMode: 'class',
|
||||
// 兼容性配置
|
||||
corePlugins: {
|
||||
preflight: true,
|
||||
},
|
||||
}
|
||||
|
|
@ -23,8 +23,11 @@
|
|||
"access": "restricted"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
"axios": "^1.0.0",
|
||||
"dayjs": "^1.11.0"
|
||||
"dayjs": "^1.11.0",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"element-plus": "^2.12.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
const login = {
|
||||
GENERATE_IMAGE_ADMIN:{url:'/api-core/admin/gemini/generate-image',method:'POST'},// 生图模型
|
||||
}
|
||||
export default login;
|
||||
|
|
@ -1,4 +1,10 @@
|
|||
import login from './login.js';
|
||||
import order from './order.js';
|
||||
import gemini from './gemini.js';
|
||||
import meshy from './meshy.js';
|
||||
export default {
|
||||
...login
|
||||
...login,
|
||||
...order,
|
||||
...gemini,
|
||||
...meshy,
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
const login = {
|
||||
LOGIN:{url:'/admin/login',method:'POST'},// 登录
|
||||
CAPTCHA_CODE:{url:'/captcha/code',method:'GET'},// 后台验证码
|
||||
LOGOUT:{url:'/admin/logout',method:'POST'},// 管理端登出
|
||||
LOGIN:{url:'/api-base/admin/login',method:'POST'},// 登录
|
||||
CAPTCHA_CODE:{url:'/api-base/captcha/code',method:'GET'},// 后台验证码
|
||||
LOGOUT:{url:'/api-base/admin/logout',method:'POST'},// 管理端登出
|
||||
}
|
||||
export default login;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
const login = {
|
||||
IMAGE_TO_3DADMIN:{url:'/api-core/admin/meshy/image-to-3d',method:'POST'},// 图片转3D模型任务提交
|
||||
// FIND_TASK_IDADMIN:{url:'/api-core/front/mesh/image-to-3d/TASKID',method:'GET'},// 获取3D模型任务查询
|
||||
FIND_TASK_IDADMIN:{url:'/api-core/admin/meshy/ai-record/TASKID',method:'GET'},// 获取3D模型任务查询
|
||||
}
|
||||
export default login;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
const order = {
|
||||
getOrderDetailt:{url:'/api-core/admin/order/get',method:'GET'},//获取订单详情
|
||||
getOrderList:{url:'/api-core/admin/order/list',method:'POST'},//获取订单列表
|
||||
refundApprove:{url:'/api-core/admin/order/refund/approve',method:'GET'},//同意退款
|
||||
refundReject:{url:'/api-core/admin/order/refund/reject',method:'GET'},//拒绝退款
|
||||
updateOrderStatus:{url:'/api-core/admin/order/update',method:'POST'},//修改订单状态
|
||||
getOrderStatistics:{url:'/api-core/admin/order/statistics',method:'GET'},//订单状态统计
|
||||
}
|
||||
export default order;
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
const login = {
|
||||
LOGIN:{url:'/user/login',method:'POST'},// 登录
|
||||
LOGOUT:{url:'/user/logout',method:'POST'},// 登出
|
||||
REGISTER:{url:'/user/register',method:'POST'},// 注册
|
||||
SEND_EMAIL_CODE:{url:'/user/send-email-code',method:'POST'},// 发送邮箱验证码
|
||||
OAUTH_GOOGLE:{url:'/user/oauth/google',method:'POST'},// google弹窗授权
|
||||
FORGOT_PASSWORD:{url:'/user/forgot-password',method:'POST'},// 修改密码
|
||||
REFRESH_TOKEN:{url:'/user/oauth/google/refresh',method:'POST'},// googleRefreshToken刷新
|
||||
LOGIN:{url:'/api-base/user/login',method:'POST'},// 登录
|
||||
LOGOUT:{url:'/api-base/user/logout',method:'POST',isLoading:true},// 登出
|
||||
REGISTER:{url:'/api-base/user/register',method:'POST'},// 注册
|
||||
SEND_EMAIL_CODE:{url:'/api-base/user/send-email-code',method:'POST'},// 发送邮箱验证码
|
||||
OAUTH_GOOGLE:{url:'/api-base/user/oauth/google',method:'POST'},// google弹窗授权
|
||||
FORGOT_PASSWORD:{url:'/api-base/user/forgot-password',method:'POST'},// 修改密码
|
||||
REFRESH_TOKEN:{url:'/api-base/user/oauth/google/refresh',method:'POST'},// googleRefreshToken刷新
|
||||
}
|
||||
export default login;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
const login = {
|
||||
UPLOAD:{url:'/api-core/front/data/upload',method:'POST'},// 文件上传
|
||||
UPLOADS3:{url:'/api-core/front/s3/get-presigned-post',method:'POST'},// 文件直传S3
|
||||
IMAGE_TO_3D:{url:'/api-core/front/mesh/image-to-3d',method:'POST'},// 图片转3D模型任务提交
|
||||
FIND_TASK_ID:{url:'/api-core/front/mesh/image-to-3d/TASKID',method:'GET'},// 获取3D模型任务查询
|
||||
// FIND_TASK_ID:{url:'/api-core/front/mesh/image-to-3d/TASKID',method:'GET'},// 获取3D模型任务查询
|
||||
FIND_TASK_ID:{url:'/api-core/front/mesh/ai-record/TASKID',method:'GET'},// 获取3D模型任务查询
|
||||
}
|
||||
export default login;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
const order = {
|
||||
getOrderList:{url:'/api-core/front/order/list',method:'GET'},// 获取订单列表
|
||||
getOrderList:{url:'/api-core/front/order/list',method:'POST'},// 获取订单列表
|
||||
getOrderDetail:{url:'/api-core/front/order/get',method:'GET'},// 获取订单详情
|
||||
orderCancel:{url:'/api-core/front/order/cancel',method:'POST'},// 取消订单支付
|
||||
receiveAddress:{url:'/api-core/front/order/receive',method:'POST'},// 确认收货
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ import * as fileUtils from './utils/file.js'
|
|||
import * as validateUtils from './utils/validate.js'
|
||||
import * as formatUtils from './utils/format.js'
|
||||
import { request as requestUtils } from './utils/request.js'
|
||||
import * as adminApi from './api/frontend/index.js';
|
||||
import * as adminApi from './api/FrontendDesigner/index.js';
|
||||
import * as clientApi from './api/frontend/index.js';
|
||||
import { MeshyServer } from './servers/meshyserver.js';
|
||||
import { GiminiServer } from './servers/giminiserver.js';
|
||||
import { FileServer } from './servers/fileserver.js';
|
||||
import prompt from './servers/prompt.js';
|
||||
import { PayServer } from './servers/payserver.js';
|
||||
import * as orderStatus from './utils/orderStatus.js';
|
||||
// 合并所有工具函数
|
||||
const deotalandUtils = {
|
||||
string: stringUtils,
|
||||
|
|
@ -28,12 +30,14 @@ const deotalandUtils = {
|
|||
validate: validateUtils,
|
||||
format: formatUtils,
|
||||
request: requestUtils,
|
||||
FileServer,
|
||||
adminApi,
|
||||
clientApi,
|
||||
MeshyServer,
|
||||
GiminiServer,
|
||||
prompt,
|
||||
PayServer,
|
||||
orderStatus,
|
||||
// 全局常用方法
|
||||
debounce: stringUtils.debounce || createDebounce(),
|
||||
throttle: stringUtils.throttle || createThrottle(),
|
||||
|
|
@ -53,6 +57,7 @@ export {
|
|||
objectUtils,
|
||||
dateUtils,
|
||||
fileUtils,
|
||||
FileServer,
|
||||
validateUtils,
|
||||
formatUtils,
|
||||
requestUtils,
|
||||
|
|
@ -62,6 +67,7 @@ export {
|
|||
prompt,
|
||||
GiminiServer,
|
||||
PayServer,
|
||||
orderStatus,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { requestUtils,clientApi } from "../index";
|
||||
import { request as requestUtils } from '../utils/request.js'
|
||||
import * as clientApi from '../api/frontend/index.js'
|
||||
let urlRule = 'https://api.deotaland.aiIMGURL'
|
||||
export class FileServer {
|
||||
//文件上传缓存映射 - 静态属性,所有实例共享
|
||||
|
|
@ -199,44 +200,122 @@ export class FileServer {
|
|||
//轮询获取并发时的线上文件映射
|
||||
async pollFileCacheMap(cacheKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let pollCount = 0;
|
||||
const maxPollCount = 20;
|
||||
const interval = setInterval(() => {
|
||||
if (FileServer.fileCacheMap.get(cacheKey)!='loading') {
|
||||
pollCount++;
|
||||
if (!(FileServer.fileCacheMap.get(cacheKey))) {
|
||||
resolve('loading');
|
||||
} else if (FileServer.fileCacheMap.get(cacheKey) != 'loading') {
|
||||
clearInterval(interval);
|
||||
resolve(FileServer.fileCacheMap.get(cacheKey));
|
||||
}
|
||||
else if (pollCount >= maxPollCount) {
|
||||
clearInterval(interval);
|
||||
resolve('loading');
|
||||
}
|
||||
}, 1000); // 每1秒检查一次
|
||||
});
|
||||
}
|
||||
//上传文件
|
||||
async uploadFile(url) {
|
||||
// 判断参数是否为File对象,如果是则先转换为base64
|
||||
if (url instanceof File) {
|
||||
try {
|
||||
url = await this.fileToBase64FromFile(url);
|
||||
} catch (error) {
|
||||
console.error('File对象转base64失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const cacheKey = url.slice(-8);
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if(FileServer.fileCacheMap.has(cacheKey)&&FileServer.fileCacheMap.get(cacheKey)!='loading'){
|
||||
resolve(this.concatUrl(FileServer.fileCacheMap.get(cacheKey)));
|
||||
// 如果是网络路径直接返回
|
||||
if (typeof url === 'string' && (url.startsWith('http://') || url.startsWith('https://'))) {
|
||||
resolve(url);
|
||||
return;
|
||||
}
|
||||
if(FileServer.fileCacheMap.has(cacheKey)&&FileServer.fileCacheMap.get(cacheKey)!='loading'){
|
||||
// resolve(this.concatUrl(FileServer.fileCacheMap.get(cacheKey)));
|
||||
resolve(FileServer.fileCacheMap.get(cacheKey));
|
||||
return;
|
||||
}
|
||||
let loadUrl = null;
|
||||
if(FileServer.fileCacheMap.get(cacheKey)=='loading'){
|
||||
const loadUrl = await this.pollFileCacheMap(cacheKey);
|
||||
resolve(this.concatUrl(loadUrl));
|
||||
return;
|
||||
loadUrl = await this.pollFileCacheMap(cacheKey);
|
||||
}
|
||||
if(loadUrl!='loading'&&loadUrl!=null){
|
||||
// resolve(this.concatUrl(loadUrl));
|
||||
resolve(loadUrl);
|
||||
return
|
||||
}
|
||||
FileServer.fileCacheMap.set(cacheKey,'loading');
|
||||
const file = await this.fileToBlob(url);//将文件或者base64文件转为blob对象
|
||||
const formData = new FormData();
|
||||
let file = await this.fileToBlob(url);//将文件或者base64文件转为blob对象
|
||||
// 检查文件大小,如果超过10MB则进行压缩
|
||||
const maxSizeInBytes = 10 * 1024 * 1024; // 10MB
|
||||
if (file.size > maxSizeInBytes) {
|
||||
try {
|
||||
console.log(`文件大小为 ${(file.size / 1024 / 1024).toFixed(2)}MB,超过10MB限制,开始压缩...`);
|
||||
|
||||
// 将Blob转换为File对象以便压缩
|
||||
const fileName = this.extractFileName(url);
|
||||
const fileObject = new File([file], fileName, { type: file.type });
|
||||
|
||||
const compressedFile = await this.compressFile(fileObject, 0.7); // 使用0.7质量压缩
|
||||
if (compressedFile && compressedFile.length < file.size) {
|
||||
// 将压缩后的base64转换回Blob
|
||||
const response = await fetch(compressedFile);
|
||||
const compressedBlob = await response.blob();
|
||||
file = compressedBlob;
|
||||
console.log(`文件压缩成功,压缩后大小为 ${(file.size / 1024 / 1024).toFixed(2)}MB`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('文件压缩失败,使用原文件上传:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// const formData = new FormData();
|
||||
// 从URL中提取文件名,如果没有则使用默认文件名
|
||||
const fileName = this.extractFileName(url);
|
||||
formData.append('file', file, fileName);
|
||||
// formData.append('file', file, fileName);
|
||||
try {
|
||||
const response = await requestUtils.upload(clientApi.default.UPLOAD.url, formData);
|
||||
// const response = await requestUtils.upload(clientApi.default.UPLOAD.url, formData);
|
||||
let params = {
|
||||
filename:fileName,
|
||||
file_type:file.type.split('/')[0],
|
||||
prefix:'images'
|
||||
}
|
||||
const response = await requestUtils.common(clientApi.default.UPLOADS3, params);
|
||||
if(response.code==0){
|
||||
let data = response.data;
|
||||
if(data.url){
|
||||
// 截取后八位作为缓存 key
|
||||
FileServer.fileCacheMap.set(cacheKey, data.url);
|
||||
resolve(urlRule.replace('IMGURL',data.url));
|
||||
let {url,fields,file_key,file_url } = data;
|
||||
const formData = new FormData();
|
||||
for (const key in fields) {
|
||||
formData.append(key, fields[key]);
|
||||
}
|
||||
formData.append('file', file, fileName);
|
||||
// 上传到S3
|
||||
const uploadResponse = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
mode: 'cors' // 明确指定CORS模式
|
||||
});
|
||||
// 注意:S3可能返回204或303状态码表示成功
|
||||
if (uploadResponse.status === 204 || uploadResponse.status === 303 || uploadResponse.ok) {
|
||||
if(file_url){
|
||||
// 截取后八位作为缓存 key
|
||||
FileServer.fileCacheMap.set(cacheKey, file_url);
|
||||
// resolve(urlRule.replace('IMGURL',file_url));
|
||||
resolve(file_url);
|
||||
}
|
||||
} else {
|
||||
reject(errorMsg);
|
||||
}
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
//删除对应键值
|
||||
FileServer.fileCacheMap.delete(cacheKey);
|
||||
reject(error);
|
||||
console.error('上传文件失败:', error);
|
||||
throw error;
|
||||
|
|
@ -281,4 +360,16 @@ export class FileServer {
|
|||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
// File对象转为base64格式
|
||||
async fileToBase64FromFile(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onloadend = () => {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,24 @@
|
|||
import { requestUtils, clientApi } from '../index';
|
||||
import { request as requestUtils } from '../utils/request.js'
|
||||
import * as clientApi from '../api/frontend/index.js'
|
||||
import * as adminApi from '../api/FrontendDesigner'
|
||||
import { GoogleGenAI, Type, Modality } from "@google/genai";
|
||||
//拆件提示词
|
||||
const API_KEY = 'AIzaSyBmPgJKMnG7afAXR9JW14I5XSkOd_NwCVM';
|
||||
const ai = API_KEY ? new GoogleGenAI({ apiKey: API_KEY }) : null;
|
||||
import { FileServer } from './fileserver';
|
||||
// 获取环境变量中的
|
||||
const getPorjectType = () => {
|
||||
// 浏览器环境
|
||||
if (typeof window !== 'undefined') {
|
||||
// Vite 环境变量
|
||||
return import.meta.env.VITE_PROJECTTYPE;
|
||||
}
|
||||
// Node.js 环境
|
||||
if (typeof process !== 'undefined') {
|
||||
return process.env.VITE_PROJECTTYPE ;
|
||||
}
|
||||
};
|
||||
export class GiminiServer extends FileServer {
|
||||
RULE = getPorjectType();
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
|
@ -128,7 +142,7 @@ export class GiminiServer extends FileServer {
|
|||
})
|
||||
};
|
||||
//线上生成模型
|
||||
async generateImageFromMultipleImagesOnline(baseImages, prompt,config){
|
||||
async generateImageFromMultipleImagesOnline(baseImages, prompt,config={}){
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// 标准化输入:确保 baseImages 是数组
|
||||
baseImages = Array.isArray(baseImages) ? baseImages : [baseImages];
|
||||
|
|
@ -163,7 +177,7 @@ export class GiminiServer extends FileServer {
|
|||
// }
|
||||
const params = {
|
||||
"aspect_ratio": "9:16",
|
||||
"model": "gemini-2.5-flash-image",
|
||||
"model": "gemini-2.5-flash-image",//models/gemini-3-pro-image-preview
|
||||
"location": "global",
|
||||
"vertexai": true,
|
||||
...config,
|
||||
|
|
@ -172,7 +186,8 @@ const params = {
|
|||
{ text: prompt }
|
||||
]
|
||||
}
|
||||
const response = await requestUtils.common(clientApi.default.GENERATE_IMAGE, params);
|
||||
const requestUrl = this.RULE=='admin'?adminApi.default.GENERATE_IMAGE_ADMIN:clientApi.default.GENERATE_IMAGE;
|
||||
const response = await requestUtils.common(requestUrl, params);
|
||||
// const response = {
|
||||
// "code": 0,
|
||||
// "message": "",
|
||||
|
|
@ -195,7 +210,8 @@ const params = {
|
|||
return;
|
||||
}
|
||||
let data = response.data;
|
||||
let resultImg = this.concatUrl(data?.urls[0]?.url || '');
|
||||
// let resultImg = this.concatUrl(data?.urls[0]?.url || '');
|
||||
let resultImg = data?.urls[0]?.url
|
||||
// 处理响应,提取图片数据
|
||||
resolve(resultImg);
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,21 @@
|
|||
import { requestUtils,clientApi } from "../index";
|
||||
import { request as requestUtils } from '../utils/request.js'
|
||||
import * as clientApi from '../api/frontend/index.js'
|
||||
import * as adminApi from '../api/FrontendDesigner'
|
||||
import { FileServer } from './fileserver.js';
|
||||
// 获取环境变量中的
|
||||
const getPorjectType = () => {
|
||||
// 浏览器环境
|
||||
if (typeof window !== 'undefined') {
|
||||
// Vite 环境变量
|
||||
return import.meta.env.VITE_PROJECTTYPE;
|
||||
}
|
||||
// Node.js 环境
|
||||
if (typeof process !== 'undefined') {
|
||||
return process.env.VITE_PROJECTTYPE ;
|
||||
}
|
||||
};
|
||||
export class MeshyServer extends FileServer {
|
||||
RULE = getPorjectType();
|
||||
static pollingEnabled = true;
|
||||
constructor() {
|
||||
super();
|
||||
|
|
@ -25,13 +40,14 @@ export class MeshyServer extends FileServer {
|
|||
: await this.uploadFile(item.image_url);
|
||||
// let imgurl = 'https://api.deotaland.ai/upload/aabf8b4a8df447fa8c3e3f7978c523cc.png';
|
||||
params.payload.image_url = imgurl;
|
||||
const response = await requestUtils.common(clientApi.default.IMAGE_TO_3D, params);
|
||||
const requestUrl = this.RULE=='admin'?adminApi.default.IMAGE_TO_3DADMIN:clientApi.default.IMAGE_TO_3D;
|
||||
const response = await requestUtils.common(requestUrl, params);
|
||||
// const response = {
|
||||
// "code": 0,
|
||||
// "message": "",
|
||||
// "success": true,
|
||||
// "data": {
|
||||
// "result": "019ac8d3-959d-7432-ac15-b5bab5c75b74"
|
||||
// "result": "019aed9d-6e7c-78ed-b621-e84df69bb59c"
|
||||
// }
|
||||
// };
|
||||
if(response.code==0){
|
||||
|
|
@ -46,22 +62,25 @@ export class MeshyServer extends FileServer {
|
|||
//查询任务状态
|
||||
async getModelTaskStatus(result,callback,errorCallback,progressCallback){
|
||||
const requestUrl = {
|
||||
url:clientApi.default.FIND_TASK_ID.url.replace('TASKID', result),
|
||||
method:clientApi.default.FIND_TASK_ID.method,
|
||||
url:this.RULE=='admin'?adminApi.default.FIND_TASK_IDADMIN.url.replace('TASKID', result):clientApi.default.FIND_TASK_ID.url.replace('TASKID', result),
|
||||
method:this.RULE=='admin'?adminApi.default.FIND_TASK_IDADMIN.method:clientApi.default.FIND_TASK_ID.method,
|
||||
};
|
||||
|
||||
let response = await requestUtils.common(requestUrl, {});
|
||||
if(response.code==0){
|
||||
let data = response?.data
|
||||
console.log(data,'查询任务状态');
|
||||
|
||||
switch (data.status) {
|
||||
case "SUCCEEDED":
|
||||
let modelurl = data.model_url.replace("https://assets.meshy.ai", "https://api.deotaland.ai/model");
|
||||
case 1:
|
||||
// let modelurl = data.model_url.replace("https://assets.meshy.ai", "https://api.deotaland.ai/model");
|
||||
let modelurl = data.result.s3_glb_url;
|
||||
callback&&callback(modelurl);
|
||||
break;
|
||||
case "FAILED":
|
||||
case 2:
|
||||
errorCallback&&errorCallback();
|
||||
break;
|
||||
case "CANCELED":
|
||||
case 3:
|
||||
errorCallback&&errorCallback();
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { loadStripe } from '@stripe/stripe-js';
|
||||
import { requestUtils, clientApi } from '../index';
|
||||
import { request as requestUtils } from '../utils/request.js'
|
||||
import * as clientApi from '../api/frontend/index.js'
|
||||
//获取Stripe公钥
|
||||
export function getStripePublishableKey() {
|
||||
if (typeof window !== 'undefined') {
|
||||
|
|
@ -120,14 +121,16 @@ export class PayServer {
|
|||
}
|
||||
//创建订单并且支付
|
||||
async createPayorOrder(orderInfo) {
|
||||
// let payReducerUrl = 'https://www.deotaland.ai/#/order-management'
|
||||
let payReducerUrl = `${window.location.origin}/#/order-management`
|
||||
await this.init();
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let pamras = {
|
||||
"methods": [
|
||||
"card"
|
||||
],
|
||||
"success_url":"https://www.deotaland.ai/#/order-management",
|
||||
"cancel_url":"https://www.deotaland.ai/#/order-management",
|
||||
"success_url":payReducerUrl,
|
||||
"cancel_url":payReducerUrl,
|
||||
"quantity":orderInfo.quantity,
|
||||
"project_id": orderInfo.project_id,
|
||||
"project_details": orderInfo.project_details,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
export function getOrderStatusOptions(order){
|
||||
const payment_statusMap = {
|
||||
0: ['danger','orderManagement.payment.pending','dzf'],
|
||||
1: ['success','orderManagement.payment.paid','yzf'],
|
||||
3: ['danger','orderManagement.payment.failed','zfsb'],
|
||||
4: ['danger','orderManagement.status.expired','zfgq']
|
||||
}
|
||||
const order_statusMap = {
|
||||
0:['warning','orderManagement.status.dsh','dsh'],
|
||||
1: ['warning','orderManagement.status.unsuccess','wtg'],
|
||||
2: ['info','orderManagement.status.clz','clz'],
|
||||
3: ['info','orderManagement.status.dfh','dfh'],
|
||||
4: ['info','orderManagement.status.delivered','yfh'],
|
||||
5: ['success','orderManagement.status.success','ywc'],
|
||||
6: ['info','orderManagement.status.cancelled','yqx'],
|
||||
}
|
||||
const refund_statusMap = {
|
||||
0:['info','orderManagement.refundStatus.wtk','wtk'],
|
||||
1: ['info','orderManagement.refundStatus.sqtk','sqtk'],
|
||||
2: ['warning','orderManagement.refundStatus.jjtk','jjtk'],
|
||||
3: ['success','orderManagement.refundStatus.tytk','tytk'],
|
||||
4: ['info','orderManagement.refundStatus.ytk','ytk'],
|
||||
}
|
||||
// deo_order.payment_status IS '支付状态: 0待支付 1已支付 3支付失败 4支付过期';
|
||||
// deo_order.order_status IS '订单状态: 0待审核 1审核未通过 2处理中 3待发货 4已发货 5已完成 6已取消';
|
||||
// deo_order.refund_status IS '退款状态: 0无退款 1申请退款 2拒绝退款 3同意退款 4已退款';
|
||||
let status = '';
|
||||
let label = '';
|
||||
let type='';
|
||||
let order_status = order.order_status;
|
||||
let refund_status = order.refund_status;
|
||||
let payment_status = order.payment_status;
|
||||
if(refund_status==4){
|
||||
status = refund_statusMap[refund_status][0]
|
||||
label = refund_statusMap[refund_status][1]
|
||||
type = refund_statusMap[refund_status][2]
|
||||
}else if(order_status==6){
|
||||
status = order_statusMap[order_status][0]
|
||||
label = order_statusMap[order_status][1]
|
||||
type = order_statusMap[order_status][2]
|
||||
}else if(payment_status!=1 && order_status!=6){
|
||||
status = payment_statusMap[payment_status][0]
|
||||
label = payment_statusMap[payment_status][1]
|
||||
type = payment_statusMap[payment_status][2]
|
||||
}else if(order_status!=1||order_status!=6){
|
||||
status = order_statusMap[order_status][0]
|
||||
label = order_statusMap[order_status][1]
|
||||
type = order_statusMap[order_status][2]
|
||||
}else{
|
||||
status = refund_statusMap[refund_status][0]
|
||||
label = refund_statusMap[refund_status][1]
|
||||
type = refund_statusMap[refund_status][2]
|
||||
}
|
||||
return {
|
||||
status:status,
|
||||
label: label,
|
||||
type:type
|
||||
};
|
||||
}
|
||||
export const selectList = (type='1')=>{//1客户端2管理端
|
||||
let selectList = [
|
||||
{ key: 'all', label: 'orderManagement.status.all' },
|
||||
{ key: 'dzf', label: 'orderManagement.payment.pending' },
|
||||
{ key: 'yjj', label: 'orderManagement.status.yjj' },
|
||||
{ key: 'yzf', label: 'orderManagement.status.dsh' },
|
||||
{ key: 'clz', label: 'orderManagement.status.processing' },
|
||||
{ key: 'yfh', label: 'orderManagement.status.shipped' },
|
||||
{ key: 'ywc', label: 'orderManagement.status.completed' },
|
||||
{ key: 'yqx', label: 'orderManagement.status.cancelled' },
|
||||
{ key: 'ytk', label: 'orderManagement.status.refunded'},
|
||||
{ key: 'ygq', label: 'orderManagement.status.expired'}
|
||||
]
|
||||
if(type=='2'){
|
||||
selectList.push({ key: 'dfh', label: 'orderManagement.status.dfh' })
|
||||
}
|
||||
return selectList
|
||||
}
|
||||
export function selectOrderStatusOptions(status){
|
||||
let sxtjjson = {}
|
||||
switch(status){
|
||||
case 'dfh':
|
||||
sxtjjson = {
|
||||
order_status :[3],
|
||||
payment_status:[1],
|
||||
refund_status:[0]
|
||||
}
|
||||
break;
|
||||
case 'all':
|
||||
sxtjjson = {
|
||||
}
|
||||
break;
|
||||
case 'yjj':
|
||||
sxtjjson = {
|
||||
order_status :[1]
|
||||
}
|
||||
break;
|
||||
case 'dzf':
|
||||
sxtjjson = {
|
||||
payment_status:[0],
|
||||
order_status:[0]
|
||||
}
|
||||
break;
|
||||
case 'yzf':
|
||||
sxtjjson = {
|
||||
payment_status:[1],
|
||||
refund_status:[0]
|
||||
}
|
||||
break;
|
||||
case 'clz':
|
||||
sxtjjson = {
|
||||
order_status :[2],
|
||||
refund_status:[0]
|
||||
}
|
||||
break;
|
||||
case 'yfh':
|
||||
sxtjjson = {
|
||||
order_status :[4]
|
||||
}
|
||||
break;
|
||||
case 'ywc':
|
||||
sxtjjson = {
|
||||
order_status :[5]
|
||||
}
|
||||
break;
|
||||
case 'yqx':
|
||||
sxtjjson = {
|
||||
order_status :[6]
|
||||
}
|
||||
break;
|
||||
case 'ytk':
|
||||
sxtjjson = {
|
||||
refund_status:[4]
|
||||
}
|
||||
break;
|
||||
case 'ygq':
|
||||
sxtjjson = {
|
||||
payment_status:[4]
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return sxtjjson
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import axios from 'axios';
|
||||
|
||||
var ElLoading = null
|
||||
// 获取环境变量中的基础URL
|
||||
const getEnvBaseURL = () => {
|
||||
// 浏览器环境
|
||||
|
|
@ -32,10 +32,10 @@ service.interceptors.request.use(
|
|||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
// 将token添加到请求头
|
||||
// config.headers['Authorization'] = `${token}`;
|
||||
config.headers['Authorization'] = `Bearer ${token}`;
|
||||
// config.headers['token'] = `${token}`;
|
||||
config.headers['Authorization'] = `123`;
|
||||
config.headers['token'] = `123`;
|
||||
// config.headers['Authorization'] = `123`;
|
||||
// config.headers['token'] = `123`;
|
||||
// config.headers['accept-language'] = 'en';
|
||||
}
|
||||
return config;
|
||||
|
|
@ -50,14 +50,23 @@ service.interceptors.request.use(
|
|||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
if(ElLoading){
|
||||
ElLoading.close()
|
||||
}
|
||||
// 直接返回响应数据
|
||||
const res = response.data;
|
||||
if(!res.success){
|
||||
window?.setElMessage({
|
||||
message: res.message,
|
||||
type: 'error',
|
||||
})
|
||||
return Promise.reject(res.message);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
error => {
|
||||
// 响应错误处理
|
||||
let message = '网络请求失败';
|
||||
|
||||
if (error.response) {
|
||||
// 服务器返回错误状态码
|
||||
switch (error.response.status) {
|
||||
|
|
@ -173,6 +182,9 @@ export const request = {
|
|||
} else {
|
||||
requestConfig.data = data;
|
||||
}
|
||||
if(config.isLoading){
|
||||
ElLoading = window.setElLoading()
|
||||
}
|
||||
return service(requestConfig);
|
||||
},
|
||||
|
||||
|
|
|
|||
672
pnpm-lock.yaml
|
|
@ -28,13 +28,10 @@ importers:
|
|||
version: 1.10.0
|
||||
vite:
|
||||
specifier: ^7.2.2
|
||||
version: 7.2.2
|
||||
version: 7.2.2(terser@5.44.1)
|
||||
|
||||
apps/FrontendDesigner:
|
||||
dependencies:
|
||||
'@deotaland/utils':
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1
|
||||
'@element-plus/icons-vue':
|
||||
specifier: ^2.3.2
|
||||
version: 2.3.2(vue@3.5.24)
|
||||
|
|
@ -75,21 +72,21 @@ importers:
|
|||
prettier:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
terser:
|
||||
specifier: ^5.44.1
|
||||
version: 5.44.1
|
||||
unplugin-auto-import:
|
||||
specifier: ^20.2.0
|
||||
version: 20.2.0
|
||||
version: 20.2.0(@vueuse/core@14.1.0)
|
||||
unplugin-vue-components:
|
||||
specifier: ^30.0.0
|
||||
version: 30.0.0(vue@3.5.24)
|
||||
vite:
|
||||
specifier: ^7.2.2
|
||||
version: 7.2.2
|
||||
version: 7.2.2(terser@5.44.1)
|
||||
|
||||
apps/frontend:
|
||||
dependencies:
|
||||
'@deotaland/utils':
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1
|
||||
'@element-plus/icons-vue':
|
||||
specifier: ^2.3.2
|
||||
version: 2.3.2(vue@3.5.24)
|
||||
|
|
@ -99,6 +96,15 @@ importers:
|
|||
'@stripe/stripe-js':
|
||||
specifier: ^4.8.0
|
||||
version: 4.8.0
|
||||
'@twind/core':
|
||||
specifier: ^1.1.3
|
||||
version: 1.1.3
|
||||
'@twind/preset-autoprefix':
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(@twind/core@1.1.3)
|
||||
'@twind/preset-tailwind':
|
||||
specifier: ^1.1.4
|
||||
version: 1.1.4(@twind/core@1.1.3)
|
||||
'@types/three':
|
||||
specifier: ^0.180.0
|
||||
version: 0.180.0
|
||||
|
|
@ -129,9 +135,15 @@ importers:
|
|||
pinia:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(vue@3.5.24)
|
||||
pinia-plugin-persistedstate:
|
||||
specifier: ^4.7.1
|
||||
version: 4.7.1(pinia@3.0.4)
|
||||
three:
|
||||
specifier: ^0.180.0
|
||||
version: 0.180.0
|
||||
twind:
|
||||
specifier: ^0.16.19
|
||||
version: 0.16.19
|
||||
vue:
|
||||
specifier: ^3.5.24
|
||||
version: 3.5.24
|
||||
|
|
@ -154,9 +166,24 @@ importers:
|
|||
'@iconify-json/feather':
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
'@tailwindcss/postcss':
|
||||
specifier: ^4.1.17
|
||||
version: 4.1.17
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1(vite@7.2.2)(vue@3.5.24)
|
||||
autoprefixer:
|
||||
specifier: ^10.4.22
|
||||
version: 10.4.22(postcss@8.5.6)
|
||||
postcss:
|
||||
specifier: ^8.5.6
|
||||
version: 8.5.6
|
||||
tailwindcss:
|
||||
specifier: ^4.1.17
|
||||
version: 4.1.17
|
||||
terser:
|
||||
specifier: ^5.44.1
|
||||
version: 5.44.1
|
||||
unplugin-auto-import:
|
||||
specifier: ^20.2.0
|
||||
version: 20.2.0
|
||||
|
|
@ -168,7 +195,7 @@ importers:
|
|||
version: 30.0.0(vue@3.5.24)
|
||||
vite:
|
||||
specifier: ^7.2.2
|
||||
version: 7.2.2
|
||||
version: 7.2.2(terser@5.44.1)
|
||||
|
||||
packages/ui:
|
||||
dependencies:
|
||||
|
|
@ -182,6 +209,9 @@ importers:
|
|||
rimraf:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0
|
||||
terser:
|
||||
specifier: ^5.44.1
|
||||
version: 5.44.1
|
||||
|
||||
packages/utils:
|
||||
dependencies:
|
||||
|
|
@ -191,12 +221,20 @@ importers:
|
|||
dayjs:
|
||||
specifier: ^1.11.0
|
||||
version: 1.11.13
|
||||
element-plus:
|
||||
specifier: ^2.12.0
|
||||
version: 2.12.0(vue@3.5.24)
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
|
||||
packages:
|
||||
|
||||
/@alloc/quick-lru@5.2.0:
|
||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/@antfu/install-pkg@1.1.0:
|
||||
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
|
||||
dependencies:
|
||||
|
|
@ -764,6 +802,13 @@ packages:
|
|||
engines: {node: '>=6.0.0'}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/source-map@0.3.11:
|
||||
resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==}
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
dev: true
|
||||
|
||||
/@jridgewell/sourcemap-codec@1.5.5:
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
|
|
@ -1001,10 +1046,207 @@ packages:
|
|||
resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
|
||||
dev: false
|
||||
|
||||
/@tailwindcss/node@4.1.17:
|
||||
resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==}
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
enhanced-resolve: 5.18.3
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.30.2
|
||||
magic-string: 0.30.21
|
||||
source-map-js: 1.2.1
|
||||
tailwindcss: 4.1.17
|
||||
dev: true
|
||||
|
||||
/@tailwindcss/oxide-android-arm64@4.1.17:
|
||||
resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tailwindcss/oxide-darwin-arm64@4.1.17:
|
||||
resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tailwindcss/oxide-darwin-x64@4.1.17:
|
||||
resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tailwindcss/oxide-freebsd-x64@4.1.17:
|
||||
resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17:
|
||||
resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tailwindcss/oxide-linux-arm64-gnu@4.1.17:
|
||||
resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tailwindcss/oxide-linux-arm64-musl@4.1.17:
|
||||
resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tailwindcss/oxide-linux-x64-gnu@4.1.17:
|
||||
resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tailwindcss/oxide-linux-x64-musl@4.1.17:
|
||||
resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tailwindcss/oxide-wasm32-wasi@4.1.17:
|
||||
resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [wasm32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
bundledDependencies:
|
||||
- '@napi-rs/wasm-runtime'
|
||||
- '@emnapi/core'
|
||||
- '@emnapi/runtime'
|
||||
- '@tybys/wasm-util'
|
||||
- '@emnapi/wasi-threads'
|
||||
- tslib
|
||||
|
||||
/@tailwindcss/oxide-win32-arm64-msvc@4.1.17:
|
||||
resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tailwindcss/oxide-win32-x64-msvc@4.1.17:
|
||||
resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@tailwindcss/oxide@4.1.17:
|
||||
resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==}
|
||||
engines: {node: '>= 10'}
|
||||
optionalDependencies:
|
||||
'@tailwindcss/oxide-android-arm64': 4.1.17
|
||||
'@tailwindcss/oxide-darwin-arm64': 4.1.17
|
||||
'@tailwindcss/oxide-darwin-x64': 4.1.17
|
||||
'@tailwindcss/oxide-freebsd-x64': 4.1.17
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17
|
||||
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.17
|
||||
'@tailwindcss/oxide-linux-arm64-musl': 4.1.17
|
||||
'@tailwindcss/oxide-linux-x64-gnu': 4.1.17
|
||||
'@tailwindcss/oxide-linux-x64-musl': 4.1.17
|
||||
'@tailwindcss/oxide-wasm32-wasi': 4.1.17
|
||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.17
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.1.17
|
||||
dev: true
|
||||
|
||||
/@tailwindcss/postcss@4.1.17:
|
||||
resolution: {integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==}
|
||||
dependencies:
|
||||
'@alloc/quick-lru': 5.2.0
|
||||
'@tailwindcss/node': 4.1.17
|
||||
'@tailwindcss/oxide': 4.1.17
|
||||
postcss: 8.5.6
|
||||
tailwindcss: 4.1.17
|
||||
dev: true
|
||||
|
||||
/@tweenjs/tween.js@23.1.3:
|
||||
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
||||
dev: false
|
||||
|
||||
/@twind/core@1.1.3:
|
||||
resolution: {integrity: sha512-/B/aNFerMb2IeyjSJy3SJxqVxhrT77gBDknLMiZqXIRr4vNJqiuhx7KqUSRzDCwUmyGuogkamz+aOLzN6MeSLw==}
|
||||
engines: {node: '>=14.15.0'}
|
||||
peerDependencies:
|
||||
typescript: ^4.8.4
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
dev: false
|
||||
|
||||
/@twind/preset-autoprefix@1.0.7(@twind/core@1.1.3):
|
||||
resolution: {integrity: sha512-3wmHO0pG/CVxYBNZUV0tWcL7CP0wD5KpyWAQE/KOalWmOVBj+nH6j3v6Y3I3pRuMFaG5DC78qbYbhA1O11uG3w==}
|
||||
engines: {node: '>=14.15.0'}
|
||||
peerDependencies:
|
||||
'@twind/core': ^1.1.0
|
||||
typescript: ^4.8.4
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@twind/core': 1.1.3
|
||||
style-vendorizer: 2.2.3
|
||||
dev: false
|
||||
|
||||
/@twind/preset-tailwind@1.1.4(@twind/core@1.1.3):
|
||||
resolution: {integrity: sha512-zv85wrP/DW4AxgWrLfH7kyGn/KJF3K04FMLVl2AjoxZGYdCaoZDkL8ma3hzaKQ+WGgBFRubuB/Ku2Rtv/wjzVw==}
|
||||
engines: {node: '>=14.15.0'}
|
||||
peerDependencies:
|
||||
'@twind/core': ^1.1.0
|
||||
typescript: ^4.8.4
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@twind/core': 1.1.3
|
||||
dev: false
|
||||
|
||||
/@types/estree@1.0.8:
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
dev: true
|
||||
|
|
@ -1043,6 +1285,10 @@ packages:
|
|||
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
|
||||
dev: false
|
||||
|
||||
/@types/web-bluetooth@0.0.21:
|
||||
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||
dev: true
|
||||
|
||||
/@types/webxr@0.5.24:
|
||||
resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==}
|
||||
dev: false
|
||||
|
|
@ -1055,7 +1301,7 @@ packages:
|
|||
vue: ^3.2.25
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.0-beta.29
|
||||
vite: 7.2.2
|
||||
vite: 7.2.2(terser@5.44.1)
|
||||
vue: 3.5.24
|
||||
dev: true
|
||||
|
||||
|
|
@ -1178,6 +1424,17 @@ packages:
|
|||
vue-demi: 0.13.11(vue@3.5.24)
|
||||
dev: false
|
||||
|
||||
/@vueuse/core@14.1.0(vue@3.5.24):
|
||||
resolution: {integrity: sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.21
|
||||
'@vueuse/metadata': 14.1.0
|
||||
'@vueuse/shared': 14.1.0(vue@3.5.24)
|
||||
vue: 3.5.24
|
||||
dev: true
|
||||
|
||||
/@vueuse/core@9.13.0(vue@3.5.24):
|
||||
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
|
||||
dependencies:
|
||||
|
|
@ -1190,10 +1447,22 @@ packages:
|
|||
- vue
|
||||
dev: false
|
||||
|
||||
/@vueuse/metadata@14.1.0:
|
||||
resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==}
|
||||
dev: true
|
||||
|
||||
/@vueuse/metadata@9.13.0:
|
||||
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
|
||||
dev: false
|
||||
|
||||
/@vueuse/shared@14.1.0(vue@3.5.24):
|
||||
resolution: {integrity: sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
dependencies:
|
||||
vue: 3.5.24
|
||||
dev: true
|
||||
|
||||
/@vueuse/shared@9.13.0(vue@3.5.24):
|
||||
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
|
||||
dependencies:
|
||||
|
|
@ -1265,6 +1534,22 @@ packages:
|
|||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
dev: false
|
||||
|
||||
/autoprefixer@10.4.22(postcss@8.5.6):
|
||||
resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
browserslist: 4.28.1
|
||||
caniuse-lite: 1.0.30001759
|
||||
fraction.js: 5.3.4
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.1.1
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
dev: true
|
||||
|
||||
/axios@1.13.2:
|
||||
resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==}
|
||||
dependencies:
|
||||
|
|
@ -1282,6 +1567,11 @@ packages:
|
|||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
dev: false
|
||||
|
||||
/baseline-browser-mapping@2.9.4:
|
||||
resolution: {integrity: sha512-ZCQ9GEWl73BVm8bu5Fts8nt7MHdbt5vY9bP6WGnUh+r3l8M7CgfyTlwsgCbMC66BNxPr6Xoce3j66Ms5YUQTNA==}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/bignumber.js@9.3.1:
|
||||
resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
|
||||
dev: false
|
||||
|
|
@ -1306,10 +1596,26 @@ packages:
|
|||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
/browserslist@4.28.1:
|
||||
resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
baseline-browser-mapping: 2.9.4
|
||||
caniuse-lite: 1.0.30001759
|
||||
electron-to-chromium: 1.5.266
|
||||
node-releases: 2.0.27
|
||||
update-browserslist-db: 1.2.2(browserslist@4.28.1)
|
||||
dev: true
|
||||
|
||||
/buffer-equal-constant-time@1.0.1:
|
||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||
dev: false
|
||||
|
||||
/buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
dev: true
|
||||
|
||||
/call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -1323,6 +1629,10 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/caniuse-lite@1.0.30001759:
|
||||
resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==}
|
||||
dev: true
|
||||
|
||||
/chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -1363,6 +1673,10 @@ packages:
|
|||
delayed-stream: 1.0.0
|
||||
dev: false
|
||||
|
||||
/commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
dev: true
|
||||
|
||||
/concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
dev: true
|
||||
|
|
@ -1458,11 +1772,20 @@ packages:
|
|||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
dev: true
|
||||
|
||||
/defu@6.1.4:
|
||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||
dev: false
|
||||
|
||||
/delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
dev: false
|
||||
|
||||
/detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/doctrine@3.0.0:
|
||||
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
|
@ -1470,6 +1793,33 @@ packages:
|
|||
esutils: 2.0.3
|
||||
dev: true
|
||||
|
||||
/dom-serializer@1.4.1:
|
||||
resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 4.3.1
|
||||
entities: 2.2.0
|
||||
dev: false
|
||||
|
||||
/domelementtype@2.3.0:
|
||||
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
||||
dev: false
|
||||
|
||||
/domhandler@4.3.1:
|
||||
resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
|
||||
engines: {node: '>= 4'}
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
dev: false
|
||||
|
||||
/domutils@2.8.0:
|
||||
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
|
||||
dependencies:
|
||||
dom-serializer: 1.4.1
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 4.3.1
|
||||
dev: false
|
||||
|
||||
/dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -1488,6 +1838,10 @@ packages:
|
|||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium@1.5.266:
|
||||
resolution: {integrity: sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==}
|
||||
dev: true
|
||||
|
||||
/element-plus@2.11.7(vue@3.5.24):
|
||||
resolution: {integrity: sha512-Bh47wuzsqaNBNDkbtlOlZER1cGcOB8GsXp/+C9b95MOrk0wvoHUV4NKKK7xMkfYNFYdYysQ752oMhnExgAL6+g==}
|
||||
peerDependencies:
|
||||
|
|
@ -1512,12 +1866,48 @@ packages:
|
|||
- '@vue/composition-api'
|
||||
dev: false
|
||||
|
||||
/element-plus@2.12.0(vue@3.5.24):
|
||||
resolution: {integrity: sha512-M9YLSn2np9OnqrSKWsiXvGe3qnF8pd94+TScsHj1aTMCD+nSEvucXermf807qNt6hOP040le0e5Aft7E9ZfHmA==}
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
dependencies:
|
||||
'@ctrl/tinycolor': 3.6.1
|
||||
'@element-plus/icons-vue': 2.3.2(vue@3.5.24)
|
||||
'@floating-ui/dom': 1.7.4
|
||||
'@popperjs/core': /@sxzz/popperjs-es@2.11.7
|
||||
'@types/lodash': 4.17.20
|
||||
'@types/lodash-es': 4.17.12
|
||||
'@vueuse/core': 9.13.0(vue@3.5.24)
|
||||
async-validator: 4.2.5
|
||||
dayjs: 1.11.19
|
||||
lodash: 4.17.21
|
||||
lodash-es: 4.17.21
|
||||
lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21)
|
||||
memoize-one: 6.0.0
|
||||
normalize-wheel-es: 1.2.0
|
||||
vue: 3.5.24
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
dev: false
|
||||
|
||||
/emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
/emoji-regex@9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
|
||||
/enhanced-resolve@5.18.3:
|
||||
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.3.0
|
||||
dev: true
|
||||
|
||||
/entities@2.2.0:
|
||||
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
|
||||
dev: false
|
||||
|
||||
/entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
|
@ -1917,6 +2307,10 @@ packages:
|
|||
fetch-blob: 3.2.0
|
||||
dev: false
|
||||
|
||||
/fraction.js@5.3.4:
|
||||
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
|
||||
dev: true
|
||||
|
||||
/fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
dev: true
|
||||
|
|
@ -2057,6 +2451,10 @@ packages:
|
|||
engines: {node: '>= 0.4'}
|
||||
dev: false
|
||||
|
||||
/graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
dev: true
|
||||
|
||||
/graphemer@1.4.0:
|
||||
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
||||
dev: true
|
||||
|
|
@ -2099,6 +2497,15 @@ packages:
|
|||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
dev: false
|
||||
|
||||
/htmlparser2@6.1.0:
|
||||
resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 4.3.1
|
||||
domutils: 2.8.0
|
||||
entities: 2.2.0
|
||||
dev: false
|
||||
|
||||
/https-proxy-agent@7.0.6:
|
||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
|
@ -2175,6 +2582,11 @@ packages:
|
|||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
/jiti@2.6.1:
|
||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/jose@6.1.1:
|
||||
resolution: {integrity: sha512-GWSqjfOPf4cWOkBzw5THBjtGPhXKqYnfRBzh4Ni+ArTrQQ9unvmsA3oFLqaYKoKe5sjWmGu5wVKg9Ft1i+LQfg==}
|
||||
dev: false
|
||||
|
|
@ -2241,6 +2653,128 @@ packages:
|
|||
type-check: 0.4.0
|
||||
dev: true
|
||||
|
||||
/lightningcss-android-arm64@1.30.2:
|
||||
resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/lightningcss-darwin-arm64@1.30.2:
|
||||
resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/lightningcss-darwin-x64@1.30.2:
|
||||
resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/lightningcss-freebsd-x64@1.30.2:
|
||||
resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/lightningcss-linux-arm-gnueabihf@1.30.2:
|
||||
resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/lightningcss-linux-arm64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/lightningcss-linux-arm64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/lightningcss-linux-x64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/lightningcss-linux-x64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/lightningcss-win32-arm64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/lightningcss-win32-x64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/lightningcss@1.30.2:
|
||||
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
optionalDependencies:
|
||||
lightningcss-android-arm64: 1.30.2
|
||||
lightningcss-darwin-arm64: 1.30.2
|
||||
lightningcss-darwin-x64: 1.30.2
|
||||
lightningcss-freebsd-x64: 1.30.2
|
||||
lightningcss-linux-arm-gnueabihf: 1.30.2
|
||||
lightningcss-linux-arm64-gnu: 1.30.2
|
||||
lightningcss-linux-arm64-musl: 1.30.2
|
||||
lightningcss-linux-x64-gnu: 1.30.2
|
||||
lightningcss-linux-x64-musl: 1.30.2
|
||||
lightningcss-win32-arm64-msvc: 1.30.2
|
||||
lightningcss-win32-x64-msvc: 1.30.2
|
||||
dev: true
|
||||
|
||||
/local-pkg@1.1.2:
|
||||
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
|
||||
engines: {node: '>=14'}
|
||||
|
|
@ -2373,6 +2907,15 @@ packages:
|
|||
formdata-polyfill: 4.0.10
|
||||
dev: false
|
||||
|
||||
/node-releases@2.0.27:
|
||||
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
||||
dev: true
|
||||
|
||||
/normalize-range@0.1.2:
|
||||
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/normalize-wheel-es@1.2.0:
|
||||
resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==}
|
||||
dev: false
|
||||
|
|
@ -2470,6 +3013,24 @@ packages:
|
|||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/pinia-plugin-persistedstate@4.7.1(pinia@3.0.4):
|
||||
resolution: {integrity: sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==}
|
||||
peerDependencies:
|
||||
'@nuxt/kit': '>=3.0.0'
|
||||
'@pinia/nuxt': '>=0.10.0'
|
||||
pinia: '>=3.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@nuxt/kit':
|
||||
optional: true
|
||||
'@pinia/nuxt':
|
||||
optional: true
|
||||
pinia:
|
||||
optional: true
|
||||
dependencies:
|
||||
defu: 6.1.4
|
||||
pinia: 3.0.4(vue@3.5.24)
|
||||
dev: false
|
||||
|
||||
/pinia@2.2.6(vue@3.5.24):
|
||||
resolution: {integrity: sha512-vIsR8JkDN5Ga2vAxqOE2cJj4VtsHnzpR1Fz30kClxlh0yCHfec6uoMeM3e/ddqmwFUejK3NlrcQa/shnpyT4hA==}
|
||||
peerDependencies:
|
||||
|
|
@ -2524,6 +3085,10 @@ packages:
|
|||
util-deprecate: 1.0.2
|
||||
dev: true
|
||||
|
||||
/postcss-value-parser@4.2.0:
|
||||
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
||||
dev: true
|
||||
|
||||
/postcss@8.5.6:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
|
@ -2694,6 +3259,18 @@ packages:
|
|||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
/source-map-support@0.5.21:
|
||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
source-map: 0.6.1
|
||||
dev: true
|
||||
|
||||
/source-map@0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/spawn-command@0.0.2:
|
||||
resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
|
||||
dev: true
|
||||
|
|
@ -2742,6 +3319,10 @@ packages:
|
|||
js-tokens: 9.0.1
|
||||
dev: true
|
||||
|
||||
/style-vendorizer@2.2.3:
|
||||
resolution: {integrity: sha512-/VDRsWvQAgspVy9eATN3z6itKTuyg+jW1q6UoTCQCFRqPDw8bi3E1hXIKnGw5LvXS2AQPuJ7Af4auTLYeBOLEg==}
|
||||
dev: false
|
||||
|
||||
/superjson@2.2.5:
|
||||
resolution: {integrity: sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==}
|
||||
engines: {node: '>=16'}
|
||||
|
|
@ -2763,6 +3344,26 @@ packages:
|
|||
has-flag: 4.0.0
|
||||
dev: true
|
||||
|
||||
/tailwindcss@4.1.17:
|
||||
resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==}
|
||||
dev: true
|
||||
|
||||
/tapable@2.3.0:
|
||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/terser@5.44.1:
|
||||
resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.11
|
||||
acorn: 8.15.0
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
dev: true
|
||||
|
||||
/text-table@0.2.0:
|
||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||
dev: true
|
||||
|
|
@ -2854,6 +3455,20 @@ packages:
|
|||
turbo-windows-arm64: 1.10.0
|
||||
dev: true
|
||||
|
||||
/twind@0.16.19:
|
||||
resolution: {integrity: sha512-/9H7I/Xzji6UZ+x1V8/llcQSAI0bINqpZtvAqqTzuU/qAdzRCnrpaME8x57k8tUu2KbosjSQE85sSwcLBGD1DQ==}
|
||||
engines: {node: '>=10.13'}
|
||||
peerDependencies:
|
||||
typescript: ^4.1.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
htmlparser2: 6.1.0
|
||||
style-vendorizer: 2.2.3
|
||||
dev: false
|
||||
|
||||
/type-check@0.4.0:
|
||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
|
@ -2910,6 +3525,27 @@ packages:
|
|||
unplugin-utils: 0.3.1
|
||||
dev: true
|
||||
|
||||
/unplugin-auto-import@20.2.0(@vueuse/core@14.1.0):
|
||||
resolution: {integrity: sha512-vfBI/SvD9hJqYNinipVOAj5n8dS8DJXFlCKFR5iLDp2SaQwsfdnfLXgZ+34Kd3YY3YEY9omk8XQg0bwos3Q8ug==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@nuxt/kit': ^4.0.0
|
||||
'@vueuse/core': '*'
|
||||
peerDependenciesMeta:
|
||||
'@nuxt/kit':
|
||||
optional: true
|
||||
'@vueuse/core':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@vueuse/core': 14.1.0(vue@3.5.24)
|
||||
local-pkg: 1.1.2
|
||||
magic-string: 0.30.21
|
||||
picomatch: 4.0.3
|
||||
unimport: 5.5.0
|
||||
unplugin: 2.3.10
|
||||
unplugin-utils: 0.3.1
|
||||
dev: true
|
||||
|
||||
/unplugin-icons@22.5.0:
|
||||
resolution: {integrity: sha512-MBlMtT5RuMYZy4TZgqUL2OTtOdTUVsS1Mhj6G1pEzMlFJlEnq6mhUfoIt45gBWxHcsOdXJDWLg3pRZ+YmvAVWQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -2986,6 +3622,17 @@ packages:
|
|||
webpack-virtual-modules: 0.6.2
|
||||
dev: true
|
||||
|
||||
/update-browserslist-db@1.2.2(browserslist@4.28.1):
|
||||
resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
dependencies:
|
||||
browserslist: 4.28.1
|
||||
escalade: 3.2.0
|
||||
picocolors: 1.1.1
|
||||
dev: true
|
||||
|
||||
/uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
dependencies:
|
||||
|
|
@ -2996,7 +3643,7 @@ packages:
|
|||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
dev: true
|
||||
|
||||
/vite@7.2.2:
|
||||
/vite@7.2.2(terser@5.44.1):
|
||||
resolution: {integrity: sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
|
@ -3041,6 +3688,7 @@ packages:
|
|||
picomatch: 4.0.3
|
||||
postcss: 8.5.6
|
||||
rollup: 4.53.2
|
||||
terser: 5.44.1
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
|
|
|||