This commit is contained in:
13121765685 2025-12-08 11:50:56 +08:00
parent eaa8f5de1f
commit 79630d2802
86 changed files with 5213 additions and 1452 deletions

View File

@ -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

View File

@ -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

View File

@ -151,6 +151,11 @@ onUnmounted(() => {
</template>
<style scoped>
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 应用主容器 */
.app {
min-height: 100vh;

View File

@ -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
}
}

View File

@ -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({

View File

@ -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'
}
}
}

View File

@ -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: {

View File

@ -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)

View File

@ -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

View File

@ -32,7 +32,6 @@ export const useAuthStore = defineStore('auth', {
logout() {
this.token = ''
this.user = null
// 清除持久化存储
localStorage.removeItem('token')
localStorage.removeItem('user')

View File

@ -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();
})
}
}

View File

@ -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;
})
}

View File

@ -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) => {
// 3DURLreviewmodelUrl使使
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 {

View File

@ -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(() => {

View File

@ -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){

View File

@ -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

View File

@ -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_nosearchQuery
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) {

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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

View File

@ -1,5 +1,6 @@
# 生产环境配置
VITE_BASE_URL=https://api.deotaland.ai
VITE_PROJECTTYPE=client
# Vercel 部署环境变量配置示例
# 复制此文件为 .env.local 并填入实际值

View File

@ -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>

View File

@ -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",

View File

@ -0,0 +1,6 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
autoprefixer: {},
},
}

View File

@ -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;
}
/* 修复暗色主题下按钮偏移问题 */

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

View File

@ -262,7 +262,7 @@ const handleGenerateImage = async () => {
服装结构确保上衣下装外套鞋履配饰等所有服装元素与参考图一致保持准确的比例形状与层次
眼睛位置与第一张参考图一致材质表现逼真保留参考图中的细节比如还原参考图中的头发
`:``}
角色特征Q 版萌系造型头身比例夸张大头小身神态纯真服饰设计融合童话风与复古感色彩搭配和谐且富有层次.
角色特征Q 版萌系造型头身比例夸张大头小身神态纯真服饰设计融合童话风与复古感(简化一下复杂衣服纹理,只保留特征)色彩搭配和谐且富有层次.
Note: The image should not have white borders.
完整度不要简化修改或重新设计服装需忠于原设计
适配3D打印请保持服装边缘装饰等细节略微加厚避免过细结构以提高打印稳定性

View File

@ -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 />&nbsp;</el-icon>
{{ t('orderManagement.actions.view') }}
</el-button>
<el-button
v-if="statusType.type === 'dzf'"
type="primary"
@ -78,10 +72,9 @@
<el-icon><Close />&nbsp;</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)
})

View File

@ -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>

View File

@ -319,7 +319,6 @@ export default {
emit('reset-success', state.email)
})
} catch (error) {
ElMessage.error(t('forgotPassword.reset_processing_error'))
} finally {
state.isSubmitting = false
}

View File

@ -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;

View File

@ -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>

View File

@ -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);
}
};

View 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)
}

View File

@ -123,7 +123,7 @@ export default {
const coreMenuItems = computed(() => [
{
id: 'dashboard',
path: '/',
path: '/czhome',
label: t('sidebar.dashboard'),
icon: 'DashboardIcon',
badge: null

View File

@ -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;
}
/* 移动端样式 */

View File

@ -183,6 +183,8 @@ const TaskStatus = (result)=>{
}
// URLURL
onMounted(() => {
// handleGenerateModel();
// return
switch (props.cardData.status) {
case 'loading':
handleGenerateModel();

View File

@ -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: {

View File

@ -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'

View File

@ -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')

View File

@ -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('测试数据已清理')
}

View File

@ -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']
}
})

View File

@ -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);
},
},
},
})

View File

@ -419,4 +419,10 @@ export const useOrderStore = defineStore('orders', () => {
exportOrders,
initSampleData
}
}, {
persist: {
key: 'orders',
storage: localStorage,
paths: ['filters', 'pagination']
}
})

View File

@ -473,4 +473,10 @@ export const usePaymentStore = defineStore('payment', () => {
clearError,
resetState
}
}, {
persist: {
key: 'payment',
storage: localStorage,
paths: ['paymentMethods', 'coupons']
}
})

View File

@ -51,4 +51,10 @@ export const useThemeStore = defineStore('theme', () => {
setTheme,
initTheme
}
}, {
persist: {
key: 'theme',
storage: localStorage,
paths: ['isDark']
}
})

View File

@ -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;
}
}

View File

@ -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;
}
/* 页面头部样式 */

View File

@ -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,

View File

@ -53,7 +53,7 @@ const resetEmail = ref('')
//
const handleResetSuccess = (email) => {
resetEmail.value = email
ElMessage.success(t('forgotPassword.reset_success_title'))
router.back();
}
//

View File

@ -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')
}
//

View File

@ -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();
}
})
}
}

View File

@ -231,7 +231,7 @@ export default {
.modern-home {
padding: 24px;
background: var(--bg-color, #f9fafb);
min-height: 100vh;
/* min-height: 100vh; */
}
/* 欢迎区域 */

View File

@ -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; */
}
/* 欢迎区域 */

View File

@ -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; }

View File

@ -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
}
}
}

View File

@ -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};
// idid
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;

View File

@ -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 {

View File

@ -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)
}
//

View File

@ -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>

View File

@ -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,
},
}

1069
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}
}

View File

@ -0,0 +1,4 @@
const login = {
GENERATE_IMAGE_ADMIN:{url:'/api-core/admin/gemini/generate-image',method:'POST'},// 生图模型
}
export default login;

View File

@ -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,
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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'},// 确认收货

View File

@ -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,
}
/**

View File

@ -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;
});
}
}

View File

@ -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) {

View File

@ -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:

View File

@ -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,

View File

@ -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
}

View File

@ -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);
},

View File

@ -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