222
This commit is contained in:
parent
4f93347199
commit
2a992810fe
|
|
@ -269,10 +269,10 @@ const handleUserAction = async (command) => {
|
||||||
localStorage.removeItem('user')
|
localStorage.removeItem('user')
|
||||||
localStorage.removeItem('permissionButtonList')
|
localStorage.removeItem('permissionButtonList')
|
||||||
localStorage.removeItem('permissionRouter')
|
localStorage.removeItem('permissionRouter')
|
||||||
router.push('/login')
|
router.replace('/login')
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
window.location.reload();
|
// window.location.reload();
|
||||||
}, 50);
|
// }, 50);
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,12 @@ export default {
|
||||||
all: 'All',
|
all: 'All',
|
||||||
operation:'Operation',
|
operation:'Operation',
|
||||||
},
|
},
|
||||||
|
greetingCard: {
|
||||||
|
cancelButton: 'Cancel',
|
||||||
|
exportButton: 'Export',
|
||||||
|
greetingCard: 'Greeting Card',
|
||||||
|
exportSuccess: 'Export Successful',
|
||||||
|
},
|
||||||
// Shop Management
|
// Shop Management
|
||||||
shop: {
|
shop: {
|
||||||
pleaseEnterAddress: 'Please enter address',
|
pleaseEnterAddress: 'Please enter address',
|
||||||
|
|
@ -602,6 +607,7 @@ export default {
|
||||||
dateRange: 'Date Range',
|
dateRange: 'Date Range',
|
||||||
orderNumber: 'Order Number',
|
orderNumber: 'Order Number',
|
||||||
customer: 'Customer',
|
customer: 'Customer',
|
||||||
|
series: 'Series',
|
||||||
total: 'Total Amount',
|
total: 'Total Amount',
|
||||||
payment: 'Payment Method',
|
payment: 'Payment Method',
|
||||||
date: 'Order Date',
|
date: 'Order Date',
|
||||||
|
|
@ -636,6 +642,7 @@ export default {
|
||||||
logisticsStatus: 'Logistics Status',
|
logisticsStatus: 'Logistics Status',
|
||||||
currentLocation: 'Current Location',
|
currentLocation: 'Current Location',
|
||||||
noLogisticsData: 'No logistics data available',
|
noLogisticsData: 'No logistics data available',
|
||||||
|
previewCard: 'Preview Card',
|
||||||
stats: {
|
stats: {
|
||||||
total: 'Total Orders',
|
total: 'Total Orders',
|
||||||
pending: 'Pending',
|
pending: 'Pending',
|
||||||
|
|
@ -1040,6 +1047,8 @@ export default {
|
||||||
productName: 'Product Name',
|
productName: 'Product Name',
|
||||||
enterProductName: 'Enter Product Name',
|
enterProductName: 'Enter Product Name',
|
||||||
productImage: 'Product Image',
|
productImage: 'Product Image',
|
||||||
|
mark: 'Mark',
|
||||||
|
enterMark: 'Enter Product Mark',
|
||||||
imageUploadTip: 'Support JPG, PNG, GIF format, max size 5MB',
|
imageUploadTip: 'Support JPG, PNG, GIF format, max size 5MB',
|
||||||
imageTypeError: 'Please upload image file only',
|
imageTypeError: 'Please upload image file only',
|
||||||
imageSizeError: 'Image size cannot exceed 5MB',
|
imageSizeError: 'Image size cannot exceed 5MB',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
// 中文语言包
|
// 中文语言包
|
||||||
export default {
|
export default {
|
||||||
|
greetingCard: {
|
||||||
|
cancelButton: '取消',
|
||||||
|
exportButton: '导出',
|
||||||
|
greetingCard: '贺卡',
|
||||||
|
exportSuccess: '导出成功',
|
||||||
|
},
|
||||||
orderManagement: {
|
orderManagement: {
|
||||||
title: '订单',
|
title: '订单',
|
||||||
description: '查看和管理您的购买和订阅信息',
|
description: '查看和管理您的购买和订阅信息',
|
||||||
|
|
@ -454,6 +460,7 @@ orderManagement: {
|
||||||
dateRange: '日期范围',
|
dateRange: '日期范围',
|
||||||
orderNumber: '订单号',
|
orderNumber: '订单号',
|
||||||
customer: '客户',
|
customer: '客户',
|
||||||
|
series: '系列',
|
||||||
total: '总金额',
|
total: '总金额',
|
||||||
payment: '支付方式',
|
payment: '支付方式',
|
||||||
date: '下单日期',
|
date: '下单日期',
|
||||||
|
|
@ -488,6 +495,7 @@ orderManagement: {
|
||||||
logisticsStatus: '物流状态',
|
logisticsStatus: '物流状态',
|
||||||
currentLocation: '当前位置',
|
currentLocation: '当前位置',
|
||||||
noLogisticsData: '暂无物流信息',
|
noLogisticsData: '暂无物流信息',
|
||||||
|
previewCard: '预览贺卡',
|
||||||
stats: {
|
stats: {
|
||||||
total: '总订单',
|
total: '总订单',
|
||||||
pending: '待处理',
|
pending: '待处理',
|
||||||
|
|
@ -893,6 +901,8 @@ orderManagement: {
|
||||||
productName: '产品名称',
|
productName: '产品名称',
|
||||||
enterProductName: '请输入产品名称',
|
enterProductName: '请输入产品名称',
|
||||||
productImage: '产品图片',
|
productImage: '产品图片',
|
||||||
|
mark: '标识',
|
||||||
|
enterMark: '请输入产品标识',
|
||||||
imageUploadTip: '支持JPG、PNG、GIF格式,最大5MB',
|
imageUploadTip: '支持JPG、PNG、GIF格式,最大5MB',
|
||||||
imageTypeError: '请上传图片文件',
|
imageTypeError: '请上传图片文件',
|
||||||
imageSizeError: '图片大小不能超过5MB',
|
imageSizeError: '图片大小不能超过5MB',
|
||||||
|
|
|
||||||
|
|
@ -328,7 +328,6 @@ const routes = [
|
||||||
requiresAuth: true
|
requiresAuth: true
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
|
||||||
{
|
{
|
||||||
path: 'disassembly-orders/:id',
|
path: 'disassembly-orders/:id',
|
||||||
name: 'AdminDisassemblyDetail',
|
name: 'AdminDisassemblyDetail',
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export const useAuthStore = defineStore('auth', {
|
||||||
router: null,
|
router: null,
|
||||||
routesUpdated: 0,//路由更新次数
|
routesUpdated: 0,//路由更新次数
|
||||||
routerList: window.location.hostname.indexOf('local') === -1 ? [] : permissionRoutes,//侧边栏路由
|
routerList: window.location.hostname.indexOf('local') === -1 ? [] : permissionRoutes,//侧边栏路由
|
||||||
|
// routerList:[],//侧边栏路由
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ const handleLogin = async () => {
|
||||||
let data = response.data;
|
let data = response.data;
|
||||||
authStore.login(data,()=>{
|
authStore.login(data,()=>{
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
// router.push('/admin')
|
// router.replace('/')
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error(t('admin.login.loginFailed'))
|
ElMessage.error(t('admin.login.loginFailed'))
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,11 @@
|
||||||
{{ (row?.order_info?.shipping?.firstName || '-')+(row?.order_info?.shipping?.lastName || '-') }}
|
{{ (row?.order_info?.shipping?.firstName || '-')+(row?.order_info?.shipping?.lastName || '-') }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column prop="series" :label="t('admin.orders.series')" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row?.order_info?.series || '通用' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="actual_amount" :label="t('admin.orders.total')" width="120">
|
<el-table-column prop="actual_amount" :label="t('admin.orders.total')" width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
${{ row.actual_amount.toFixed(2) }}
|
${{ row.actual_amount.toFixed(2) }}
|
||||||
|
|
@ -187,6 +192,9 @@
|
||||||
<el-descriptions-item :label="t('admin.orders.customer')">
|
<el-descriptions-item :label="t('admin.orders.customer')">
|
||||||
{{ selectedOrder?.order_info?.shipping?.firstName || '-'+selectedOrder?.order_info?.shipping?.lastName || '-' }}
|
{{ selectedOrder?.order_info?.shipping?.firstName || '-'+selectedOrder?.order_info?.shipping?.lastName || '-' }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="t('admin.orders.series')">
|
||||||
|
{{ selectedOrder?.order_info?.series || '通用' }}
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item :label="t('admin.orders.total')">
|
<el-descriptions-item :label="t('admin.orders.total')">
|
||||||
${{ selectedOrder?.actual_amount?.toFixed(2) || '-' }}
|
${{ selectedOrder?.actual_amount?.toFixed(2) || '-' }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
|
@ -420,6 +428,16 @@
|
||||||
>
|
>
|
||||||
{{ t('admin.disassemblyOrders.list.disassembly') }}
|
{{ t('admin.disassemblyOrders.list.disassembly') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<!-- 预览贺卡 - 仅A1系列显示 -->
|
||||||
|
<el-button
|
||||||
|
v-if="selectedOrderForAction?.order_info?.series === 'A1'"
|
||||||
|
type="warning"
|
||||||
|
size="large"
|
||||||
|
class="action-btn"
|
||||||
|
@click="handlePreviewCard(selectedOrderForAction)"
|
||||||
|
>
|
||||||
|
{{ t('admin.orders.previewCard') }}
|
||||||
|
</el-button>
|
||||||
<!-- 待支付状态:修改支付状态为已支付 -->
|
<!-- 待支付状态:修改支付状态为已支付 -->
|
||||||
<el-button
|
<el-button
|
||||||
v-if="getStatusTagType(selectedOrderForAction).type=='dzf'"
|
v-if="getStatusTagType(selectedOrderForAction).type=='dzf'"
|
||||||
|
|
@ -474,6 +492,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<DtCardPreview
|
||||||
|
v-model="previewVisible"
|
||||||
|
:image-url="currentPreviewCard.imageUrl"
|
||||||
|
:message="currentPreviewCard.message"
|
||||||
|
:card-message="currentPreviewCard.cardMessage"
|
||||||
|
:id="currentPreviewCard.id+''" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -492,7 +516,13 @@ import {
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import {AdminOrders} from './AdminOrders'
|
import {AdminOrders} from './AdminOrders'
|
||||||
|
const previewVisible = ref(false)
|
||||||
|
const currentPreviewCard = ref({
|
||||||
|
imageUrl: '',
|
||||||
|
message: '',
|
||||||
|
cardMessage: '',
|
||||||
|
id: ''
|
||||||
|
})
|
||||||
// 物流服务实例
|
// 物流服务实例
|
||||||
const logisticsService = new LogistIcsService()
|
const logisticsService = new LogistIcsService()
|
||||||
const adminOrders = new AdminOrders()
|
const adminOrders = new AdminOrders()
|
||||||
|
|
@ -542,6 +572,15 @@ const handleDisassemble = (order) => {
|
||||||
params: { id: order.id }
|
params: { id: order.id }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预览贺卡 - 仅A1系列显示
|
||||||
|
const handlePreviewCard = (order) => {
|
||||||
|
currentPreviewCard.value.imageUrl = order?.order_info?.greetingCard?.imageUrl || ''
|
||||||
|
currentPreviewCard.value.message = order?.order_info?.greetingCard?.message || ''
|
||||||
|
currentPreviewCard.value.cardMessage = order?.order_info?.greetingCard?.cardMessage || ''
|
||||||
|
currentPreviewCard.value.id = order?.order_info?.greetingCard?.id || ''
|
||||||
|
previewVisible.value = true
|
||||||
|
}
|
||||||
// 发货表单
|
// 发货表单
|
||||||
const shippingForm = reactive({
|
const shippingForm = reactive({
|
||||||
trackingNo: '',
|
trackingNo: '',
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@
|
||||||
<el-input v-model="userForm.fullName" :placeholder="t('admin.userList.fullNamePlaceholder')" />
|
<el-input v-model="userForm.fullName" :placeholder="t('admin.userList.fullNamePlaceholder')" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="t('admin.userList.email')" prop="email">
|
<el-form-item :label="t('admin.userList.email')" prop="email">
|
||||||
<el-input v-model="userForm.email" :placeholder="t('admin.userList.emailPlaceholder')" type="email" />
|
<el-input v-model="userForm.email" :placeholder="t('admin.userList.emailPlaceholder')" type="text" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="!isEditMode" :label="t('admin.userList.password')" prop="password">
|
<el-form-item v-if="!isEditMode" :label="t('admin.userList.password')" prop="password">
|
||||||
<el-input v-model="userForm.password" :placeholder="t('admin.userList.passwordPlaceholder')" type="password" show-password />
|
<el-input v-model="userForm.password" :placeholder="t('admin.userList.passwordPlaceholder')" type="password" show-password />
|
||||||
|
|
@ -261,7 +261,7 @@ const userFormRules = reactive({
|
||||||
],
|
],
|
||||||
email: [
|
email: [
|
||||||
{ required: true, message: t('admin.userList.emailRequired'), trigger: 'blur' },
|
{ required: true, message: t('admin.userList.emailRequired'), trigger: 'blur' },
|
||||||
{ type: 'email', message: t('admin.userList.emailFormat'), trigger: 'blur' }
|
{ message: t('admin.userList.emailFormat'), trigger: 'blur' }
|
||||||
],
|
],
|
||||||
password: [
|
password: [
|
||||||
{ required: true, message: t('admin.userList.passwordRequired'), trigger: 'blur' },
|
{ required: true, message: t('admin.userList.passwordRequired'), trigger: 'blur' },
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,12 @@ export class AdminRoleManagement {
|
||||||
userId: data.id,
|
userId: data.id,
|
||||||
newPassword: data.newPassword
|
newPassword: data.newPassword
|
||||||
}
|
}
|
||||||
return requestUtils.common(adminApi.default.resetAdminUserPassword, params);
|
const requestUrl = {
|
||||||
|
method: adminApi.default.resetAdminUserPassword.method,
|
||||||
|
url: adminApi.default.resetAdminUserPassword.url.replace('{userId}', data.id)+'?newPassword='+data.newPassword,
|
||||||
|
isLoading: adminApi.default.resetAdminUserPassword.isLoading
|
||||||
|
}
|
||||||
|
return requestUtils.common(requestUrl, params);
|
||||||
}
|
}
|
||||||
//根据用户ID查询管理员用户详情
|
//根据用户ID查询管理员用户详情
|
||||||
async getAdminUserDetail(data) {
|
async getAdminUserDetail(data) {
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,13 @@
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="t('admin.productManagement.mark')">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.mark"
|
||||||
|
:placeholder="t('admin.productManagement.enterMark')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="t('admin.productManagement.description')" required>
|
<el-form-item :label="t('admin.productManagement.description')" required>
|
||||||
<el-input
|
<el-input
|
||||||
v-model="formData.description"
|
v-model="formData.description"
|
||||||
|
|
@ -314,7 +321,8 @@ const formData = ref({
|
||||||
amount: 0,
|
amount: 0,
|
||||||
currency: 'USD',
|
currency: 'USD',
|
||||||
current_price_id: '',
|
current_price_id: '',
|
||||||
image: ''
|
image: '',
|
||||||
|
mark: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 价格表单
|
// 价格表单
|
||||||
|
|
@ -364,7 +372,8 @@ const showAddDialog = () => {
|
||||||
amount: 0,
|
amount: 0,
|
||||||
currency: 'USD',
|
currency: 'USD',
|
||||||
current_price_id: '',
|
current_price_id: '',
|
||||||
image: ''
|
image: '',
|
||||||
|
mark: ''
|
||||||
}
|
}
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
@ -381,7 +390,8 @@ const showEditDialog = async (product) => {
|
||||||
amount: product.amount,
|
amount: product.amount,
|
||||||
currency: product.currency,
|
currency: product.currency,
|
||||||
current_price_id: product.current_price_id || '',
|
current_price_id: product.current_price_id || '',
|
||||||
image: response.product_info?.image || ''
|
image: response.product_info?.image || '',
|
||||||
|
mark: response.product_info?.mark || ''
|
||||||
}
|
}
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ export class ProductManagement {
|
||||||
currency: data.currency,//货币类型,默认USD
|
currency: data.currency,//货币类型,默认USD
|
||||||
product_info:{
|
product_info:{
|
||||||
image:data.image,//产品图片
|
image:data.image,//产品图片
|
||||||
|
mark:data.mark,//产品标识
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await requestUtils.common(adminApi.default.createProduct, params);
|
return await requestUtils.common(adminApi.default.createProduct, params);
|
||||||
|
|
@ -39,6 +40,7 @@ export class ProductManagement {
|
||||||
"current_price_id": data.current_price_id,//当前价格ID
|
"current_price_id": data.current_price_id,//当前价格ID
|
||||||
product_info:{
|
product_info:{
|
||||||
image:data.image,//产品图片
|
image:data.image,//产品图片
|
||||||
|
mark:data.mark,//产品标识
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await requestUtils.common(adminApi.default.updateProduct, params);
|
return await requestUtils.common(adminApi.default.updateProduct, params);
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@
|
||||||
"country-state-city": "^3.2.1",
|
"country-state-city": "^3.2.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"element-plus": "^2.11.7",
|
"element-plus": "^2.11.7",
|
||||||
"html2canvas": "^1.4.1",
|
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
"jose": "^6.1.1",
|
"jose": "^6.1.1",
|
||||||
"motion-v": "^1.7.4",
|
"motion-v": "^1.7.4",
|
||||||
|
|
@ -37,7 +36,6 @@
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"pinia-plugin-persistedstate": "^4.7.1",
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
"qrcode": "^1.5.4",
|
|
||||||
"three": "^0.180.0",
|
"three": "^0.180.0",
|
||||||
"twind": "^0.16.19",
|
"twind": "^0.16.19",
|
||||||
"vue": "^3.5.24",
|
"vue": "^3.5.24",
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,13 @@ onMounted(() => {
|
||||||
<footerCom v-if="isCn()" />
|
<footerCom v-if="isCn()" />
|
||||||
</template>
|
</template>
|
||||||
<style>
|
<style>
|
||||||
|
.qrjzt{
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: 'Georgia', 'Times New Roman', 'Playfair Display', 'Brush Script MT', cursive;
|
||||||
|
text-shadow: 0 2px 4px rgba(214, 51, 132, 0.2);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
*{
|
*{
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
outline:0;
|
outline:0;
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@
|
||||||
:placeholder="$t('checkout.ipNamePlaceholder')"
|
:placeholder="$t('checkout.ipNamePlaceholder')"
|
||||||
class="ip-name-input"
|
class="ip-name-input"
|
||||||
/>
|
/>
|
||||||
|
<div class="ip-name-hint" v-if="series=='A1'">{{ $t('checkout.ipNameHint') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-item shop-select">
|
<div class="config-item shop-select">
|
||||||
<div class="label">{{ $t('checkout.shop') }}</div>
|
<div class="label">{{ $t('checkout.shop') }}</div>
|
||||||
|
|
@ -71,6 +72,62 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 贺卡选择区域 -->
|
||||||
|
<div class="content-block greeting-card-block" >
|
||||||
|
<h2 class="block-title greeting-card-title">
|
||||||
|
<span class="title-icon">💝</span>
|
||||||
|
{{ $t('checkout.greetingCard') }}
|
||||||
|
</h2>
|
||||||
|
<div class="greeting-card-section">
|
||||||
|
<div v-if="loadingGreetingCards" class="loading-greeting-cards">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
<span>{{ $t('checkout.loading') }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="greetingCards.length === 0" class="no-greeting-cards">
|
||||||
|
<span>{{ $t('checkout.noGreetingCards') }}</span>
|
||||||
|
</div>
|
||||||
|
<el-select
|
||||||
|
v-else
|
||||||
|
v-model="selectedGreetingCard"
|
||||||
|
:placeholder="$t('checkout.greetingCardPlaceholder')"
|
||||||
|
class="greeting-card-select"
|
||||||
|
value-key="id"
|
||||||
|
clearable
|
||||||
|
teleported
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<div class="heka qrjzt" v-if="selectedGreetingCard" >
|
||||||
|
<img
|
||||||
|
:src="selectedGreetingCard.imageUrl"
|
||||||
|
class="greeting-card-prefix"
|
||||||
|
/>
|
||||||
|
{{ selectedGreetingCard.cardMessage }}
|
||||||
|
</div>
|
||||||
|
<span v-else class="greeting-card-prefix-placeholder">💝</span>
|
||||||
|
</template>
|
||||||
|
<el-option
|
||||||
|
v-for="card in greetingCards"
|
||||||
|
:key="card.id"
|
||||||
|
:value="card"
|
||||||
|
class="greeting-card-option"
|
||||||
|
>
|
||||||
|
<div class="greeting-card-option-content">
|
||||||
|
<img :src="card.imageUrl" :alt="card.name" class="greeting-card-thumb" />
|
||||||
|
<div class="greeting-card-info">
|
||||||
|
<!-- <div class="greeting-card-name">{{ card.name }}</div> -->
|
||||||
|
<div class="greeting-card-message qrjzt">{{ card.cardMessage }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
<div v-if="selectedGreetingCard" class="greeting-card-preview">
|
||||||
|
<div class="preview-card">
|
||||||
|
<div class="preview-message qrjzt">{{ selectedGreetingCard.message }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 优惠券选择区域 -->
|
<!-- 优惠券选择区域 -->
|
||||||
<div class="content-block">
|
<div class="content-block">
|
||||||
<h2 class="block-title">
|
<h2 class="block-title">
|
||||||
|
|
@ -208,7 +265,6 @@
|
||||||
<button class="buy-btn" v-if="!isWeChatBrowser()" :disabled="isPayButtonDisabled" @click="goShopify">{{ $t('checkout.buy') }}</button>
|
<button class="buy-btn" v-if="!isWeChatBrowser()" :disabled="isPayButtonDisabled" @click="goShopify">{{ $t('checkout.buy') }}</button>
|
||||||
<button class="buy-btn" v-else @click="goShopify">{{ $t('checkout.buy') }}</button>
|
<button class="buy-btn" v-else @click="goShopify">{{ $t('checkout.buy') }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stripe Payment Overlay -->
|
<!-- Stripe Payment Overlay -->
|
||||||
<div v-if="showStripe" class="stripe-overlay" @click="onStripeCancel">
|
<div v-if="showStripe" class="stripe-overlay" @click="onStripeCancel">
|
||||||
<div class="stripe-card" @click.stop>
|
<div class="stripe-card" @click.stop>
|
||||||
|
|
@ -238,8 +294,10 @@ import { useI18n } from 'vue-i18n'
|
||||||
import { PayServer,isWeChatBrowser } from '@deotaland/utils'
|
import { PayServer,isWeChatBrowser } from '@deotaland/utils'
|
||||||
import { requestUtils,clientApi,environmentUtils,WechatBus } from '@deotaland/utils'
|
import { requestUtils,clientApi,environmentUtils,WechatBus } from '@deotaland/utils'
|
||||||
import { PurchaseModal as PurchaseModalClass } from './index.js'
|
import { PurchaseModal as PurchaseModalClass } from './index.js'
|
||||||
|
import { GreetingCard } from '@/views/GreetingCard/index.js'
|
||||||
const payserver = new PayServer();
|
const payserver = new PayServer();
|
||||||
const purchaseModal = new PurchaseModalClass();
|
const purchaseModal = new PurchaseModalClass();
|
||||||
|
const greetingCard = new GreetingCard();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelData: { type: Object, default: () => ({}) },
|
modelData: { type: Object, default: () => ({}) },
|
||||||
show: { type: Boolean, default: false },
|
show: { type: Boolean, default: false },
|
||||||
|
|
@ -247,7 +305,6 @@ const props = defineProps({
|
||||||
})
|
})
|
||||||
const emit = defineEmits(['close'])
|
const emit = defineEmits(['close'])
|
||||||
const onClose = () => emit('close')
|
const onClose = () => emit('close')
|
||||||
const addons = ref({ matte:false, gloss:false, base:false })
|
|
||||||
const qty = ref(1)
|
const qty = ref(1)
|
||||||
const ipName = ref('doll')
|
const ipName = ref('doll')
|
||||||
const contact = ref({ emailOrPhone:'', subscribe:false })
|
const contact = ref({ emailOrPhone:'', subscribe:false })
|
||||||
|
|
@ -264,6 +321,11 @@ const voucherList = ref([])
|
||||||
const selectedVoucher = ref(null)
|
const selectedVoucher = ref(null)
|
||||||
const loadingVouchers = ref(false)
|
const loadingVouchers = ref(false)
|
||||||
const discount_amount= ref(0);
|
const discount_amount= ref(0);
|
||||||
|
|
||||||
|
// 贺卡列表数据
|
||||||
|
const greetingCards = ref([])
|
||||||
|
const selectedGreetingCard = ref(null);
|
||||||
|
const loadingGreetingCards = ref(false);
|
||||||
// 省州映射数据已移至国际化文件
|
// 省州映射数据已移至国际化文件
|
||||||
const amountCents = computed(() => {
|
const amountCents = computed(() => {
|
||||||
let base = price.value // 默认价格,移除了尺寸相关定价
|
let base = price.value // 默认价格,移除了尺寸相关定价
|
||||||
|
|
@ -308,6 +370,28 @@ const getVoucherList = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getGreetingCardList = async () => {
|
||||||
|
loadingGreetingCards.value = true
|
||||||
|
try {
|
||||||
|
const res = await greetingCard.getMyGreetingCardList()
|
||||||
|
if (res.code === 200) {
|
||||||
|
const rows = res.rows || []
|
||||||
|
greetingCards.value = rows.map(card => ({
|
||||||
|
id: card.id,
|
||||||
|
name: card.title,
|
||||||
|
imageUrl: card.extraInfo?.image?.replace(/`/g, '').trim() || '',
|
||||||
|
message: card?.content,
|
||||||
|
cardMessage: card.extraInfo?.cardMessage || ''
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取贺卡列表失败:', error)
|
||||||
|
greetingCards.value = []
|
||||||
|
} finally {
|
||||||
|
loadingGreetingCards.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onVoucherSelect = (voucher) => {
|
const onVoucherSelect = (voucher) => {
|
||||||
if (selectedVoucher.value?.id === voucher.id) {
|
if (selectedVoucher.value?.id === voucher.id) {
|
||||||
selectedVoucher.value = null
|
selectedVoucher.value = null
|
||||||
|
|
@ -346,7 +430,7 @@ const getPrice = async () => {
|
||||||
const res = await requestUtils.common(clientApi.default.getProductList)
|
const res = await requestUtils.common(clientApi.default.getProductList)
|
||||||
if(res.code === 0){
|
if(res.code === 0){
|
||||||
const data = res.data.list || []
|
const data = res.data.list || []
|
||||||
const item = data.find(item => item.name === props.series)
|
const item = data.find(item => item.product_info?.mark === props.series)
|
||||||
if(item){
|
if(item){
|
||||||
price.value = item.price?.amount || 0
|
price.value = item.price?.amount || 0
|
||||||
unt.value = item.price?.currency || ''
|
unt.value = item.price?.currency || ''
|
||||||
|
|
@ -382,6 +466,7 @@ const goShopify = () => {//用户点击购买
|
||||||
}
|
}
|
||||||
// 整理用户输入的信息
|
// 整理用户输入的信息
|
||||||
const order_info = {
|
const order_info = {
|
||||||
|
series: props.series,
|
||||||
quantity: qty.value,
|
quantity: qty.value,
|
||||||
ipName: ipName.value,
|
ipName: ipName.value,
|
||||||
contact: {
|
contact: {
|
||||||
|
|
@ -400,7 +485,8 @@ const goShopify = () => {//用户点击购买
|
||||||
address2: shipping.value.address2,
|
address2: shipping.value.address2,
|
||||||
phone: shipping.value.phone
|
phone: shipping.value.phone
|
||||||
},
|
},
|
||||||
modelData:project_details
|
modelData:project_details,
|
||||||
|
greetingCard:selectedGreetingCard.value,
|
||||||
}
|
}
|
||||||
let params ={
|
let params ={
|
||||||
quantity:qty.value,
|
quantity:qty.value,
|
||||||
|
|
@ -490,6 +576,7 @@ onMounted(() => {
|
||||||
getPrice();
|
getPrice();
|
||||||
getVoucherList();
|
getVoucherList();
|
||||||
fetchShopList();
|
fetchShopList();
|
||||||
|
getGreetingCardList();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
})
|
})
|
||||||
watch(() => shipping.value.country, () => { updateStates() })
|
watch(() => shipping.value.country, () => { updateStates() })
|
||||||
|
|
@ -802,6 +889,13 @@ const updateStates = () => {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ip-name-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255,255,255,0.6);
|
||||||
|
margin-top: 4px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
.shop-select-input :deep(.el-select__popper) {
|
.shop-select-input :deep(.el-select__popper) {
|
||||||
background: rgba(17,24,39,0.95);
|
background: rgba(17,24,39,0.95);
|
||||||
border-color: rgba(255,255,255,0.2);
|
border-color: rgba(255,255,255,0.2);
|
||||||
|
|
@ -828,7 +922,9 @@ const updateStates = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-vouchers,
|
.loading-vouchers,
|
||||||
.no-vouchers {
|
.no-vouchers,
|
||||||
|
.loading-greeting-cards,
|
||||||
|
.no-greeting-cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -938,6 +1034,219 @@ const updateStates = () => {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 贺卡选择区域 - 情人节主题 */
|
||||||
|
.greeting-card-block {
|
||||||
|
background: linear-gradient(135deg, #fff5f7 0%, #ffe4e8 50%, #ffd1dc 100%);
|
||||||
|
border: 2px solid rgba(255, 182, 193, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-title {
|
||||||
|
color: #d63384;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-select__wrapper) {
|
||||||
|
background: linear-gradient(135deg, #ffffff 0%, #fff5f7 100%);
|
||||||
|
border: 2px solid #ffb6c1;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 12px rgba(255, 107, 157, 0.15);
|
||||||
|
height: 56px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #d63384;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-select__wrapper:hover) {
|
||||||
|
border-color: #ff6b9d;
|
||||||
|
box-shadow: 0 6px 16px rgba(255, 107, 157, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-select__placeholder) {
|
||||||
|
color: #ff8a80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-icon) {
|
||||||
|
color: #ff6b9d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-select__prefix) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-prefix {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 1px solid #ffb6c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-prefix-placeholder {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-select__input) {
|
||||||
|
margin-left: 8px;
|
||||||
|
color: #d63384;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-select__wrapper) {
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-select-dropdown__item) {
|
||||||
|
padding: 0 12px;
|
||||||
|
height: auto;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-select-dropdown__item::after) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-select-dropdown) {
|
||||||
|
max-height: 300px !important;
|
||||||
|
z-index: 9999 !important;
|
||||||
|
position: relative !important;
|
||||||
|
background: #ffffff !important;
|
||||||
|
border: 1px solid #ffb6c1 !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
box-shadow: 0 8px 24px rgba(255, 107, 157, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-select-dropdown__list) {
|
||||||
|
padding: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-select-dropdown__wrap) {
|
||||||
|
max-height: 300px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-select :deep(.el-popper) {
|
||||||
|
z-index: 9999 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-option {
|
||||||
|
padding: 8px 12px !important;
|
||||||
|
height: auto !important;
|
||||||
|
line-height: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-option:hover {
|
||||||
|
background-color: #fff5f7 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-option.is-selected {
|
||||||
|
background-color: #ffe4e8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-option-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 4px 0;
|
||||||
|
visibility: visible !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
min-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-thumb {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
min-width: 50px;
|
||||||
|
border-radius: 8px;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 2px solid #ffb6c1;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #d63384;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-message {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting-card-preview {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-card {
|
||||||
|
background: linear-gradient(135deg, #ffffff 0%, #fff5f7 100%);
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 8px 24px rgba(255, 107, 157, 0.2);
|
||||||
|
border: 2px solid #ffb6c1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-message {
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #4a5568;
|
||||||
|
line-height: 1.6;
|
||||||
|
text-align: center;
|
||||||
|
background: linear-gradient(135deg, #fff5f7 0%, #ffe4e8 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 12px;
|
||||||
|
background: linear-gradient(135deg, #ff6b9d 0%, #ff8a80 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 4px 12px rgba(255, 107, 157, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
/* Info Row Layout */
|
/* Info Row Layout */
|
||||||
.info-row {
|
.info-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
@ -1470,7 +1779,12 @@ const updateStates = () => {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
.heka{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #ff8a80;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
0% { transform: rotate(0deg); }
|
0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 是否需要挂钩 - 仅当series为E1或A1时显示 -->
|
||||||
<!-- 是否需要挂钩 - 仅当series为E1时显示 -->
|
<div class="form-section" v-if="series === 'E1'||series === 'A1'">
|
||||||
<div class="form-section" v-if="series === 'E1'">
|
|
||||||
<div class="expression-info">
|
<div class="expression-info">
|
||||||
<span class="expression-description">
|
<span class="expression-description">
|
||||||
{{ $t('iPandCardLeft.needHook') }}
|
{{ $t('iPandCardLeft.needHook') }}
|
||||||
|
|
|
||||||
|
|
@ -802,6 +802,7 @@ export default {
|
||||||
quantity: '数量',
|
quantity: '数量',
|
||||||
ipName: 'IP名称',
|
ipName: 'IP名称',
|
||||||
ipNamePlaceholder: '请输入IP名称',
|
ipNamePlaceholder: '请输入IP名称',
|
||||||
|
ipNameHint: '地台的铭牌中会使用当前名称',
|
||||||
shop: '店铺',
|
shop: '店铺',
|
||||||
chooseShop: '选择店铺',
|
chooseShop: '选择店铺',
|
||||||
buy: '购买',
|
buy: '购买',
|
||||||
|
|
@ -815,6 +816,8 @@ export default {
|
||||||
noVouchers: '暂无可用优惠券',
|
noVouchers: '暂无可用优惠券',
|
||||||
minOrder: '最低订单金额',
|
minOrder: '最低订单金额',
|
||||||
expireAt: '过期时间',
|
expireAt: '过期时间',
|
||||||
|
greetingCard: '选择贺卡',
|
||||||
|
greetingCardPlaceholder: '选择一张贺卡',
|
||||||
error: {
|
error: {
|
||||||
firstNameRequired: '名不能为空',
|
firstNameRequired: '名不能为空',
|
||||||
lastNameRequired: '姓不能为空',
|
lastNameRequired: '姓不能为空',
|
||||||
|
|
@ -1601,6 +1604,9 @@ export default {
|
||||||
messageLabel: '祝福寄语',
|
messageLabel: '祝福寄语',
|
||||||
messagePlaceholder: '写下您想对TA说的情话...',
|
messagePlaceholder: '写下您想对TA说的情话...',
|
||||||
messageRequired: '请输入祝福寄语',
|
messageRequired: '请输入祝福寄语',
|
||||||
|
cardMessageLabel: '贺卡寄语',
|
||||||
|
cardMessagePlaceholder: '写下您想对TA说的话...',
|
||||||
|
cardMessageRequired: '请输入贺卡寄语',
|
||||||
imageRequired: '请上传一张图片',
|
imageRequired: '请上传一张图片',
|
||||||
cancelButton: '取消',
|
cancelButton: '取消',
|
||||||
saveButton: '保存',
|
saveButton: '保存',
|
||||||
|
|
@ -2562,6 +2568,7 @@ export default {
|
||||||
quantity: 'Quantity',
|
quantity: 'Quantity',
|
||||||
ipName: 'IP Name',
|
ipName: 'IP Name',
|
||||||
ipNamePlaceholder: 'Please enter IP name',
|
ipNamePlaceholder: 'Please enter IP name',
|
||||||
|
ipNameHint: 'The current name will be used on the nameplate',
|
||||||
shop: 'Shop',
|
shop: 'Shop',
|
||||||
chooseShop: 'Select Shop',
|
chooseShop: 'Select Shop',
|
||||||
buy: 'Buy',
|
buy: 'Buy',
|
||||||
|
|
@ -2575,6 +2582,8 @@ export default {
|
||||||
noVouchers: 'No available vouchers',
|
noVouchers: 'No available vouchers',
|
||||||
minOrder: 'Minimum order amount',
|
minOrder: 'Minimum order amount',
|
||||||
expireAt: 'Expiration date',
|
expireAt: 'Expiration date',
|
||||||
|
greetingCard: 'Select Greeting Card',
|
||||||
|
greetingCardPlaceholder: 'Select a greeting card',
|
||||||
error: {
|
error: {
|
||||||
firstNameRequired: 'First name cannot be empty',
|
firstNameRequired: 'First name cannot be empty',
|
||||||
lastNameRequired: 'Last name cannot be empty',
|
lastNameRequired: 'Last name cannot be empty',
|
||||||
|
|
@ -3195,6 +3204,9 @@ export default {
|
||||||
messageLabel: 'Blessing Message',
|
messageLabel: 'Blessing Message',
|
||||||
messagePlaceholder: 'Write your love message...',
|
messagePlaceholder: 'Write your love message...',
|
||||||
messageRequired: 'Please enter a blessing message',
|
messageRequired: 'Please enter a blessing message',
|
||||||
|
cardMessageLabel: 'Card Message',
|
||||||
|
cardMessagePlaceholder: 'Write your message to your loved one...',
|
||||||
|
cardMessageRequired: 'Please enter a card message',
|
||||||
imageRequired: 'Please upload an image',
|
imageRequired: 'Please upload an image',
|
||||||
cancelButton: 'Cancel',
|
cancelButton: 'Cancel',
|
||||||
saveButton: 'Save',
|
saveButton: 'Save',
|
||||||
|
|
|
||||||
|
|
@ -288,12 +288,11 @@ router.beforeEach(async (to, from, next) => {
|
||||||
// console.log(info,'infoinfo');
|
// console.log(info,'infoinfo');
|
||||||
const user_role = authStore.user?.userRole;
|
const user_role = authStore.user?.userRole;
|
||||||
if ((user_role == 1 || user_role == 2) && router.getRoutes().length == routes.length) {
|
if ((user_role == 1 || user_role == 2) && router.getRoutes().length == routes.length) {
|
||||||
// 添加动态路由
|
|
||||||
addDynamicRoutes();
|
addDynamicRoutes();
|
||||||
if (isDynamicRoute(to.path)) {
|
if (isDynamicRoute(to.path)) {
|
||||||
next('/czhome')
|
next('/czhome')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
router.push(to.path)
|
router.push({ path: to.path, query: to.query })
|
||||||
}, 20);
|
}, 20);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -257,8 +257,7 @@ const openProject = (project) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const createNewProject = (series) => {
|
const createNewProject = (series) => {
|
||||||
console.log(series,'seriesseriesseries');
|
router.push(`/project/new/${series.product_info.mark}`)
|
||||||
router.push(`/project/new/${series.name}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理系列选择确认
|
// 处理系列选择确认
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,17 @@
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:rows="4"
|
:rows="4"
|
||||||
:placeholder="$t('greetingCard.messagePlaceholder')"
|
:placeholder="$t('greetingCard.messagePlaceholder')"
|
||||||
maxlength="200"
|
:maxlength="messageMaxLength"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('greetingCard.cardMessageLabel')" prop="cardMessage">
|
||||||
|
<el-input
|
||||||
|
v-model="form.cardMessage"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
:placeholder="$t('greetingCard.cardMessagePlaceholder')"
|
||||||
|
:maxlength="cardMessageMaxLength"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
@ -147,35 +157,43 @@
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- Card Preview Dialog -->
|
<!-- Card Preview Dialog -->
|
||||||
<CardPreview v-model="previewVisible" :image-url="currentPreviewCard.imageUrl" :message="currentPreviewCard.message" />
|
<DtCardPreview :card-message="currentPreviewCard.cardMessage" :id="currentPreviewCard.id" v-model="previewVisible" :image-url="currentPreviewCard.imageUrl" :message="currentPreviewCard.message" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed } from 'vue';
|
import { ref, reactive, computed, onMounted } from 'vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { Plus, Edit, Delete, View, UploadFilled } from '@element-plus/icons-vue';
|
import { Plus, Edit, Delete, View, UploadFilled } from '@element-plus/icons-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import CardPreview from './components/CardPreview.vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
const { t } = useI18n();
|
import {FileServer} from '@deotaland/utils'
|
||||||
|
import { GreetingCard } from './index.js';
|
||||||
|
const fileServer = new FileServer();
|
||||||
|
const greetingCardApi = new GreetingCard();
|
||||||
|
const { t, locale } = useI18n();
|
||||||
// --- State ---
|
// --- State ---
|
||||||
const greetingCards = ref([
|
const greetingCards = ref([]);
|
||||||
{
|
|
||||||
id: 1,
|
// 加载贺卡列表
|
||||||
imageUrl: 'https://images.unsplash.com/photo-1518199266791-5375a83190b7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80',
|
const loadGreetingCards = async () => {
|
||||||
message: '在这个特别的日子里,想告诉你:你是我生命中最美好的遇见,愿我们的爱情永远甜蜜如初。',
|
try {
|
||||||
createTime: '2023-08-22T10:00:00.000Z'
|
const result = await greetingCardApi.getMyGreetingCardList();
|
||||||
},
|
greetingCards.value = result.rows.map(card => ({
|
||||||
{
|
id: card.id,
|
||||||
id: 2,
|
imageUrl: card.extraInfo.image.replace(/`/g, '').trim(),
|
||||||
imageUrl: 'https://images.unsplash.com/photo-1529333166437-7750a6dd5a70?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80',
|
message: card.content,
|
||||||
message: '情人节快乐!愿我们的爱情像玫瑰一样绽放,永远充满芬芳与美丽。',
|
createTime: card.createdAt,
|
||||||
createTime: '2023-08-20T14:30:00.000Z'
|
cardMessage: card.extraInfo.cardMessage,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(error.message || t('greetingCard.loadError'));
|
||||||
}
|
}
|
||||||
]);
|
};
|
||||||
|
// 页面加载时获取贺卡列表
|
||||||
|
onMounted(() => {
|
||||||
|
loadGreetingCards();
|
||||||
|
});
|
||||||
|
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
const isEdit = ref(false);
|
const isEdit = ref(false);
|
||||||
|
|
@ -185,19 +203,23 @@ const deleteDialogVisible = ref(false);
|
||||||
const currentDeleteCardId = ref(null);
|
const currentDeleteCardId = ref(null);
|
||||||
const currentPreviewCard = reactive({
|
const currentPreviewCard = reactive({
|
||||||
imageUrl: '',
|
imageUrl: '',
|
||||||
message: ''
|
message: '',
|
||||||
|
id: null,
|
||||||
|
cardMessage:''
|
||||||
});
|
});
|
||||||
const formRef = ref(null);
|
const formRef = ref(null);
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
id: null,
|
id: null,
|
||||||
imageUrl: '',
|
imageUrl: '',
|
||||||
message: ''
|
message: '',
|
||||||
|
cardMessage: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const rules = computed(() => ({
|
const rules = computed(() => ({
|
||||||
imageUrl: [{ required: true, message: t('greetingCard.imageRequired'), trigger: 'change' }],
|
imageUrl: [{ required: true, message: t('greetingCard.imageRequired'), trigger: 'change' }],
|
||||||
message: [{ required: true, message: t('greetingCard.messageRequired'), trigger: 'blur' }]
|
message: [{ required: true, message: t('greetingCard.messageRequired'), trigger: 'blur' }],
|
||||||
|
cardMessage: [{ required: true, message: t('greetingCard.cardMessageRequired'), trigger: 'blur' }]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const dialogWidth = computed(() => {
|
const dialogWidth = computed(() => {
|
||||||
|
|
@ -210,17 +232,23 @@ const deleteDialogWidth = computed(() => {
|
||||||
return window.innerWidth < 768 ? '90%' : '400px';
|
return window.innerWidth < 768 ? '90%' : '400px';
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Actions ---
|
const messageMaxLength = computed(() => {
|
||||||
|
return locale.value === 'zh' ? 150 : 200;
|
||||||
|
});
|
||||||
|
|
||||||
|
const cardMessageMaxLength = computed(() => {
|
||||||
|
return locale.value === 'zh' ? 10 : 30;
|
||||||
|
});
|
||||||
|
// --- Actions ---
|
||||||
const formatDate = (date) => {
|
const formatDate = (date) => {
|
||||||
return dayjs(date).format('YYYY-MM-DD HH:mm');
|
return dayjs(date).format('YYYY-MM-DD HH:mm');
|
||||||
};
|
};
|
||||||
|
|
||||||
const openAddDialog = () => {
|
const openAddDialog = () => {
|
||||||
isEdit.value = false;
|
isEdit.value = false;
|
||||||
form.id = null;
|
form.id = null;
|
||||||
form.imageUrl = '';
|
form.imageUrl = '';
|
||||||
form.message = '';
|
form.message = '';
|
||||||
|
form.cardMessage = '';
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -229,12 +257,15 @@ const editCard = (card) => {
|
||||||
form.id = card.id;
|
form.id = card.id;
|
||||||
form.imageUrl = card.imageUrl;
|
form.imageUrl = card.imageUrl;
|
||||||
form.message = card.message;
|
form.message = card.message;
|
||||||
|
form.cardMessage = card.cardMessage || '';
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const previewCardAction = (card) => {
|
const previewCardAction = (card) => {
|
||||||
currentPreviewCard.imageUrl = card.imageUrl;
|
currentPreviewCard.imageUrl = card.imageUrl;
|
||||||
currentPreviewCard.message = card.message;
|
currentPreviewCard.message = card.message;
|
||||||
|
currentPreviewCard.id = card.id;
|
||||||
|
currentPreviewCard.cardMessage = card.cardMessage;
|
||||||
previewVisible.value = true;
|
previewVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -243,28 +274,34 @@ const openDeleteDialog = (card) => {
|
||||||
deleteDialogVisible.value = true;
|
deleteDialogVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmDelete = () => {
|
const confirmDelete = async () => {
|
||||||
if (currentDeleteCardId.value) {
|
if (currentDeleteCardId.value) {
|
||||||
greetingCards.value = greetingCards.value.filter(card => card.id !== currentDeleteCardId.value);
|
try {
|
||||||
ElMessage.success(t('greetingCard.deleteSuccess'));
|
await greetingCardApi.deleteGreetingCard({
|
||||||
deleteDialogVisible.value = false;
|
id: currentDeleteCardId.value
|
||||||
currentDeleteCardId.value = null;
|
});
|
||||||
|
greetingCards.value = greetingCards.value.filter(card => card.id !== currentDeleteCardId.value);
|
||||||
|
ElMessage.success(t('greetingCard.deleteSuccess'));
|
||||||
|
deleteDialogVisible.value = false;
|
||||||
|
currentDeleteCardId.value = null;
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(error.message || t('greetingCard.deleteError'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFileChange = (uploadFile) => {
|
const handleFileChange = (uploadFile) => {
|
||||||
const file = uploadFile.raw;
|
const file = uploadFile.raw;
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
const isImage = file.type.startsWith('image/');
|
const isImage = file.type.startsWith('image/');
|
||||||
if (!isImage) {
|
if (!isImage) {
|
||||||
ElMessage.error('请上传图片文件');
|
ElMessage.error('请上传图片文件');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = async (e) => {
|
||||||
form.imageUrl = e.target.result;
|
const url = await fileServer.uploadFile(e.target.result);
|
||||||
|
form.imageUrl = url;
|
||||||
if (formRef.value) {
|
if (formRef.value) {
|
||||||
formRef.value.validateField('imageUrl');
|
formRef.value.validateField('imageUrl');
|
||||||
}
|
}
|
||||||
|
|
@ -275,30 +312,33 @@ const handleFileChange = (uploadFile) => {
|
||||||
const saveCard = async () => {
|
const saveCard = async () => {
|
||||||
if (!formRef.value) return;
|
if (!formRef.value) return;
|
||||||
|
|
||||||
await formRef.value.validate((valid) => {
|
await formRef.value.validate(async (valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
saving.value = true;
|
saving.value = true;
|
||||||
// Simulate API call
|
try {
|
||||||
setTimeout(() => {
|
|
||||||
if (isEdit.value) {
|
if (isEdit.value) {
|
||||||
const index = greetingCards.value.findIndex(c => c.id === form.id);
|
await greetingCardApi.updateGreetingCard({
|
||||||
if (index !== -1) {
|
id: form.id,
|
||||||
greetingCards.value[index] = { ...greetingCards.value[index], ...form };
|
content: form.message,
|
||||||
ElMessage.success(t('greetingCard.updateSuccess'));
|
image: form.imageUrl,
|
||||||
}
|
cardMessage: form.cardMessage
|
||||||
|
});
|
||||||
|
ElMessage.success(t('greetingCard.updateSuccess'));
|
||||||
} else {
|
} else {
|
||||||
const newCard = {
|
await greetingCardApi.createGreetingCard({
|
||||||
id: Date.now(),
|
content: form.message,
|
||||||
imageUrl: form.imageUrl,
|
image: form.imageUrl,
|
||||||
message: form.message,
|
cardMessage: form.cardMessage
|
||||||
createTime: new Date().toISOString()
|
});
|
||||||
};
|
|
||||||
greetingCards.value.unshift(newCard);
|
|
||||||
ElMessage.success(t('greetingCard.createSuccess'));
|
ElMessage.success(t('greetingCard.createSuccess'));
|
||||||
}
|
}
|
||||||
|
await loadGreetingCards();
|
||||||
saving.value = false;
|
saving.value = false;
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
}, 500);
|
} catch (error) {
|
||||||
|
saving.value = false;
|
||||||
|
ElMessage.error(error.message || t('greetingCard.saveError'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { requestUtils,clientApi } from "@deotaland/utils";
|
||||||
|
export class GreetingCard {
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
// 创建贺卡
|
||||||
|
async createGreetingCard(data) {
|
||||||
|
let parmas = {
|
||||||
|
title:'贺卡',//写死不需要传递
|
||||||
|
content:data.content,
|
||||||
|
extraInfo:{
|
||||||
|
image:data.image,//上传的图片
|
||||||
|
cardMessage:data.cardMessage//贺卡内容
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requestUtils.common(clientApi.default.CREATE_GREETING_CARD,parmas)
|
||||||
|
}
|
||||||
|
// 更新贺卡
|
||||||
|
async updateGreetingCard(data) {
|
||||||
|
let parmas = {
|
||||||
|
id:data.id,
|
||||||
|
title:'贺卡',//写死不需要传递
|
||||||
|
content:data.content,//贺语内容
|
||||||
|
extraInfo:{
|
||||||
|
image:data.image,//上传的图片
|
||||||
|
cardMessage:data.cardMessage//贺卡内容
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requestUtils.common(clientApi.default.UPDATE_GREETING_CARD,parmas)
|
||||||
|
}
|
||||||
|
// 删除贺卡
|
||||||
|
async deleteGreetingCard(data) {
|
||||||
|
let parmas = {
|
||||||
|
id:data.id,
|
||||||
|
}
|
||||||
|
const requestUrl = {
|
||||||
|
method: clientApi.default.DELETE_GREETING_CARD.method,
|
||||||
|
url: clientApi.default.DELETE_GREETING_CARD.url.replace('{id}',data.id),
|
||||||
|
isLoading: clientApi.default.DELETE_GREETING_CARD.isLoading,
|
||||||
|
}
|
||||||
|
return requestUtils.common(requestUrl,parmas)
|
||||||
|
}
|
||||||
|
// 查询贺卡详情
|
||||||
|
async getGreetingCardDetail(data) {
|
||||||
|
let parmas = {
|
||||||
|
id:data.id,
|
||||||
|
}
|
||||||
|
const requestUrl = {
|
||||||
|
method: clientApi.default.GET_GREETING_CARD_DETAIL.method,
|
||||||
|
url: clientApi.default.GET_GREETING_CARD_DETAIL.url.replace('{id}',data.id),
|
||||||
|
isLoading: clientApi.default.GET_GREETING_CARD_DETAIL.isLoading,
|
||||||
|
}
|
||||||
|
return requestUtils.common(requestUrl,parmas)
|
||||||
|
/**
|
||||||
|
返回示例:
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"id": 9007199254740991,
|
||||||
|
"title": "string",
|
||||||
|
"content": "string",
|
||||||
|
"creatorId": 9007199254740991,
|
||||||
|
"creatorNickname": "string",
|
||||||
|
"extraInfo": {
|
||||||
|
"image": "string",//上传的图片
|
||||||
|
"cardMessage": "string"//贺卡内容
|
||||||
|
},
|
||||||
|
"createdAt": "2026-01-20T03:11:14.573Z",
|
||||||
|
"updatedAt": "2026-01-20T03:11:14.573Z"
|
||||||
|
},
|
||||||
|
"message": "操作成功"
|
||||||
|
}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
// 查询我的贺卡列表
|
||||||
|
async getMyGreetingCardList() {
|
||||||
|
let parmas = {
|
||||||
|
}
|
||||||
|
const requestUrl = {
|
||||||
|
method: clientApi.default.GET_MY_GREETING_CARD_LIST.method,
|
||||||
|
url: clientApi.default.GET_MY_GREETING_CARD_LIST.url,
|
||||||
|
isLoading: clientApi.default.GET_MY_GREETING_CARD_LIST.isLoading,
|
||||||
|
}
|
||||||
|
return requestUtils.common(requestUrl,parmas)
|
||||||
|
/**
|
||||||
|
返回示例:
|
||||||
|
{
|
||||||
|
"total": 9007199254740991,
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"id": 9007199254740991,
|
||||||
|
"title": "string",
|
||||||
|
"content": "string",
|
||||||
|
"creatorId": 9007199254740991,
|
||||||
|
"creatorNickname": "string",
|
||||||
|
"extraInfo": {
|
||||||
|
"image": "string",//上传的图片
|
||||||
|
"cardMessage": "string"//贺卡内容
|
||||||
|
},
|
||||||
|
"createdAt": "2026-01-20T03:11:32.331Z",
|
||||||
|
"updatedAt": "2026-01-20T03:11:32.331Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"code": 1073741824,
|
||||||
|
"msg": "string"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1136,7 +1136,7 @@ const init = ()=>{
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
projectId.value = route.params.id;
|
projectId.value = route.params.id;
|
||||||
series.value = route.params.series;
|
series.value = route.params.series;
|
||||||
if(series.value!='D1'&&series.value!='E1'){
|
if(series.value!='D1'&&series.value!='E1'&&series.value!='A1'){
|
||||||
series.value = 'D1';
|
series.value = 'D1';
|
||||||
router.replace(`/project/${projectId.value}/${series.value}`);
|
router.replace(`/project/${projectId.value}/${series.value}`);
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -730,7 +730,7 @@ export class Project {
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
//获取动态提示词
|
//获取动态提示词
|
||||||
async getCombinedPrompt(series,config={}) {//series:项目系列D1 E1
|
async getCombinedPrompt(series,config={}) {//series:项目系列D1 E1 A1
|
||||||
try {
|
try {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const res = await requestUtils.common(clientApi.default.combined)
|
const res = await requestUtils.common(clientApi.default.combined)
|
||||||
|
|
@ -742,12 +742,12 @@ export class Project {
|
||||||
return !item.title.includes('头部挂钩');
|
return !item.title.includes('头部挂钩');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (series === 'E1') {
|
if (series === 'E1'||series === 'A1') {
|
||||||
data = data.filter(item => {
|
data = data.filter(item => {
|
||||||
return !item.title.includes('动物坐姿') && !item.title.includes('人物姿势') && item.type != 'D1';
|
return !item.title.includes('动物坐姿') && !item.title.includes('人物姿势') && item.type != 'D1';
|
||||||
});
|
});
|
||||||
} else if (series === 'D1') {// 如果是Done系列,过滤掉type为E1的提示词
|
} else if (series === 'D1') {// 如果是Done系列,过滤掉type为E1和A1的提示词
|
||||||
data = data.filter(item => item.type !== 'E1');
|
data = data.filter(item => (item.type !== 'E1'&&item.type !== 'A1'));
|
||||||
}
|
}
|
||||||
// 初始化返回数据结构
|
// 初始化返回数据结构
|
||||||
const result = {
|
const result = {
|
||||||
|
|
@ -763,7 +763,7 @@ export class Project {
|
||||||
// 按sortOrder排序
|
// 按sortOrder排序
|
||||||
data.sort((a, b) => a.sortOrder - b.sortOrder);
|
data.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||||
// 处理person和general类型的数据
|
// 处理person和general类型的数据
|
||||||
const personAndGeneral = data.filter(item => item.type === 'person' || item.type === 'general' || item.type === 'E1');
|
const personAndGeneral = data.filter(item => item.type === 'person' || item.type === 'general' || item.type === 'E1'|| item.type === 'A1');
|
||||||
personAndGeneral.forEach(item => {
|
personAndGeneral.forEach(item => {
|
||||||
// 拼接content
|
// 拼接content
|
||||||
result.person.content += item.content;
|
result.person.content += item.content;
|
||||||
|
|
@ -780,7 +780,7 @@ export class Project {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 处理animal和general类型的数据
|
// 处理animal和general类型的数据
|
||||||
const animalAndGeneral = data.filter(item => item.type === 'animal' || item.type === 'general' || item.type === 'E1');
|
const animalAndGeneral = data.filter(item => item.type === 'animal' || item.type === 'general' || item.type === 'E1'|| item.type === 'A1');
|
||||||
animalAndGeneral.forEach(item => {
|
animalAndGeneral.forEach(item => {
|
||||||
// 拼接content
|
// 拼接content
|
||||||
result.animal.content += item.content;
|
result.animal.content += item.content;
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,17 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
|
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
import { useThemeStore } from '@/stores/theme'
|
import { useThemeStore } from '@/stores/theme'
|
||||||
import * as THREE from 'three'
|
import * as THREE from 'three'
|
||||||
import { Hands } from '@mediapipe/hands'
|
import { Hands } from '@mediapipe/hands'
|
||||||
import { Camera } from '@mediapipe/camera_utils'
|
import { Camera } from '@mediapipe/camera_utils'
|
||||||
|
import { GreetingCard } from '../GreetingCard/index.js'
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
|
const route = useRoute()
|
||||||
const themeStore = useThemeStore()
|
const themeStore = useThemeStore()
|
||||||
|
const greetingCard = new GreetingCard()
|
||||||
|
|
||||||
const canvasContainer = ref(null)
|
const canvasContainer = ref(null)
|
||||||
const videoElement = ref(null)
|
const videoElement = ref(null)
|
||||||
|
|
@ -47,20 +51,10 @@ const showImage = ref(false)
|
||||||
const isMobile = ref(window.innerWidth < 768)
|
const isMobile = ref(window.innerWidth < 768)
|
||||||
const cameraInitFailed = ref(false)
|
const cameraInitFailed = ref(false)
|
||||||
const isManuallyTriggered = ref(false)
|
const isManuallyTriggered = ref(false)
|
||||||
|
|
||||||
// 1. 文字排版:保持较宽的布局
|
// 1. 文字排版:保持较宽的布局
|
||||||
const confessionText = computed(() => {
|
const confessionText = ref(``)
|
||||||
if (locale.value === 'zh') {
|
const confessionImage = ref('')
|
||||||
return isMobile.value
|
const isImageLoaded = ref(false)
|
||||||
? `Dear, Meeting you is the luckiest thing in my life. Your smile is my greatest motivation, I want to accompany you through every season. Happy Valentine's Day, I love you!💖`
|
|
||||||
: `Dear, Meeting you is the luckiest thing in my life. Your smile is my greatest motivation, I want to accompany you through every season. Happy Valentine's Day, I love you!💖`
|
|
||||||
} else {
|
|
||||||
return 'I LOVE YOU\nFOREVER & ALWAYS'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const confessionImage = ref('https://images.unsplash.com/photo-1518199266791-5375a83190b7?w=400&h=400&fit=crop')
|
|
||||||
|
|
||||||
let scene, camera, renderer, particles
|
let scene, camera, renderer, particles
|
||||||
let hands, cameraUtils
|
let hands, cameraUtils
|
||||||
let animationId
|
let animationId
|
||||||
|
|
@ -107,17 +101,13 @@ const initThreeScene = () => {
|
||||||
scene = new THREE.Scene()
|
scene = new THREE.Scene()
|
||||||
const themeColors = getThemeColors()
|
const themeColors = getThemeColors()
|
||||||
scene.background = new THREE.Color(themeColors.background)
|
scene.background = new THREE.Color(themeColors.background)
|
||||||
|
|
||||||
camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000)
|
camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000)
|
||||||
|
|
||||||
// 移动端视野拉远 (110),适配宽文字
|
// 移动端视野拉远 (110),适配宽文字
|
||||||
camera.position.z = isMobile.value ? 110 : 60
|
camera.position.z = isMobile.value ? 110 : 60
|
||||||
|
|
||||||
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
|
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
|
||||||
renderer.setSize(width, height)
|
renderer.setSize(width, height)
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
|
||||||
canvasContainer.value.appendChild(renderer.domElement)
|
canvasContainer.value.appendChild(renderer.domElement)
|
||||||
|
|
||||||
createParticles()
|
createParticles()
|
||||||
animate()
|
animate()
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +167,6 @@ const createParticles = () => {
|
||||||
const getTextPositions = (text) => {
|
const getTextPositions = (text) => {
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement('canvas')
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
|
|
||||||
// 1. 设置字体
|
// 1. 设置字体
|
||||||
const fontSize = isMobile.value ? 36 : 50
|
const fontSize = isMobile.value ? 36 : 50
|
||||||
const font = `bold ${fontSize}px "Noto Serif SC", serif`
|
const font = `bold ${fontSize}px "Noto Serif SC", serif`
|
||||||
|
|
@ -187,7 +176,7 @@ const getTextPositions = (text) => {
|
||||||
|
|
||||||
// 设定最大宽度:手机端屏幕宽度的 80%,PC 端固定宽度
|
// 设定最大宽度:手机端屏幕宽度的 80%,PC 端固定宽度
|
||||||
// 这里我们用一个虚拟像素值,比如手机 350px,PC 800px
|
// 这里我们用一个虚拟像素值,比如手机 350px,PC 800px
|
||||||
const maxLineWidth = isMobile.value ? 350 : 800
|
const maxLineWidth = isMobile.value ? 450 : 800
|
||||||
|
|
||||||
const rawLines = text.split('\n') // 先保留用户手动输入的换行
|
const rawLines = text.split('\n') // 先保留用户手动输入的换行
|
||||||
const lines = []
|
const lines = []
|
||||||
|
|
@ -244,9 +233,7 @@ const getTextPositions = (text) => {
|
||||||
// === 动态计算偏移量 ===
|
// === 动态计算偏移量 ===
|
||||||
// 计算文字在 3D 世界中的总高度
|
// 计算文字在 3D 世界中的总高度
|
||||||
const textWorldHeight = lines.length * lineHeight * 0.08
|
const textWorldHeight = lines.length * lineHeight * 0.08
|
||||||
|
|
||||||
let shiftY = isMobile.value ? -12 : -15 // 默认值
|
let shiftY = isMobile.value ? -12 : -15 // 默认值
|
||||||
|
|
||||||
if (isMobile.value) {
|
if (isMobile.value) {
|
||||||
// 模拟 CSS 逻辑计算图片实际像素高度
|
// 模拟 CSS 逻辑计算图片实际像素高度
|
||||||
const vw = window.innerWidth
|
const vw = window.innerWidth
|
||||||
|
|
@ -277,7 +264,7 @@ const getTextPositions = (text) => {
|
||||||
const imgBottomWorldY = (1 - 2 * imgBottomRatio) * (visibleHeight / 2)
|
const imgBottomWorldY = (1 - 2 * imgBottomRatio) * (visibleHeight / 2)
|
||||||
|
|
||||||
// 设定文字顶部在图片底部下方 5 单位处
|
// 设定文字顶部在图片底部下方 5 单位处
|
||||||
const safeTopY = imgBottomWorldY - 5
|
const safeTopY = imgBottomWorldY - 0
|
||||||
|
|
||||||
// 计算需要的中心偏移量
|
// 计算需要的中心偏移量
|
||||||
// Top = Center + HalfHeight => Center = Top - HalfHeight
|
// Top = Center + HalfHeight => Center = Top - HalfHeight
|
||||||
|
|
@ -544,6 +531,27 @@ const handleResize = () => {
|
||||||
renderer.setSize(width, height)
|
renderer.setSize(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const preloadImage = (url) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!url) {
|
||||||
|
resolve(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const img = new Image()
|
||||||
|
img.onload = () => {
|
||||||
|
isImageLoaded.value = true
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
|
img.onerror = () => {
|
||||||
|
console.error('Image preload failed:', url)
|
||||||
|
isImageLoaded.value = false
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
img.src = url
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
watch(locale, () => {
|
watch(locale, () => {
|
||||||
if (isGestureDetected.value) {
|
if (isGestureDetected.value) {
|
||||||
isAssembling = false
|
isAssembling = false
|
||||||
|
|
@ -558,11 +566,27 @@ watch(() => themeStore.isDark, () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
initThreeScene()
|
initThreeScene()
|
||||||
initHandTracking()
|
initHandTracking()
|
||||||
initCamera()
|
initCamera()
|
||||||
window.addEventListener('resize', handleResize)
|
window.addEventListener('resize', handleResize)
|
||||||
|
console.log(route,'routeroute');
|
||||||
|
const cardId = route.query.id
|
||||||
|
if (cardId) {
|
||||||
|
try {
|
||||||
|
const response = await greetingCard.getGreetingCardDetail({ id: cardId })
|
||||||
|
if (response.success && response.data) {
|
||||||
|
confessionText.value = response.data.content
|
||||||
|
confessionImage.value = response.data.extraInfo?.image || ''
|
||||||
|
if (confessionImage.value) {
|
||||||
|
await preloadImage(confessionImage.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch greeting card detail:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
|
@ -706,7 +730,7 @@ html.dark .confession-card-container {
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.image-display {
|
.image-display {
|
||||||
top: 12%;
|
top: 3%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confession-image {
|
.confession-image {
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,8 @@ export default defineConfig({
|
||||||
// 配置代理解决CORS问题
|
// 配置代理解决CORS问题
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'https://api.deotaland.ai',
|
// target: 'https://api.deotaland.ai',
|
||||||
// target: 'http://api.deotaland.local',
|
target: 'http://api.deotaland.local',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, '')
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
FROM node:22-alpine
|
FROM node:22-alpine
|
||||||
|
|
||||||
# 安装 pnpm
|
# 安装 pnpm
|
||||||
RUN npm install -g pnpm
|
RUN corepack enable && corepack prepare pnpm@9.3.0 --activate
|
||||||
|
|
||||||
# 设置工作目录
|
# 设置工作目录
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# 使用官方 Node.js 镜像
|
||||||
|
FROM node:22-alpine
|
||||||
|
|
||||||
|
# 安装 pnpm
|
||||||
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# 先复制配置文件以利用 Docker 缓存
|
||||||
|
COPY pnpm-workspace.yaml ./
|
||||||
|
COPY package.json ./
|
||||||
|
COPY pnpm-lock.yaml ./
|
||||||
|
COPY turbo.json ./
|
||||||
|
|
||||||
|
# 复制所有 workspace 包和应用
|
||||||
|
COPY packages ./packages
|
||||||
|
COPY apps ./apps
|
||||||
|
|
||||||
|
# 使用 pnpm 安装依赖
|
||||||
|
RUN pnpm install
|
||||||
|
|
||||||
|
# 先构建需要构建的包
|
||||||
|
RUN pnpm --filter @deotaland/ui build
|
||||||
|
|
||||||
|
# 构建应用
|
||||||
|
RUN pnpm run build
|
||||||
|
|
||||||
|
# 确认构建输出存在
|
||||||
|
CMD ["sh", "-c", "find /build -name 'dist' -type d -exec ls -la {} \\;"]
|
||||||
|
|
@ -41,5 +41,10 @@
|
||||||
"element-plus"
|
"element-plus"
|
||||||
],
|
],
|
||||||
"author": "Deotaland AI Team",
|
"author": "Deotaland AI Team",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-to-image-more": "^3.7.2",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
|
"qrcode": "^1.5.4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -346,7 +346,6 @@ async function fetchImage(url) {
|
||||||
});
|
});
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
const imgurl = URL.createObjectURL(blob);
|
const imgurl = URL.createObjectURL(blob);
|
||||||
console.log(imgurl,'imgurlimgurlimgurl');
|
|
||||||
return imgurl;
|
return imgurl;
|
||||||
}
|
}
|
||||||
const handleTouchMove = (e) => {
|
const handleTouchMove = (e) => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="visible"
|
v-model="visible"
|
||||||
:title="$t('greetingCard.previewDialogTitle')"
|
|
||||||
:width="dialogWidth"
|
:width="dialogWidth"
|
||||||
custom-class="card-preview-dialog"
|
custom-class="card-preview-dialog"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
|
|
@ -12,8 +11,11 @@
|
||||||
<div class="preview-container">
|
<div class="preview-container">
|
||||||
<div ref="cardRef" class="card-preview">
|
<div ref="cardRef" class="card-preview">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="card-image-section">
|
<div class="card-image-section" :style="{ height: imageHeight + 'px' }">
|
||||||
<img :src="imageUrl" alt="Card Image" class="preview-image" />
|
<div v-if="!urlImage" class="image-loading">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
</div>
|
||||||
|
<img v-else-if="urlImage" :src="urlImage" alt="Card Image" class="preview-image" @load="handleImageLoad" @error="handleImageError" />
|
||||||
</div>
|
</div>
|
||||||
<div class="card-message-section">
|
<div class="card-message-section">
|
||||||
<p class="preview-message">{{ $t('greetingCard.cardMessage') }}</p>
|
<p class="preview-message">{{ $t('greetingCard.cardMessage') }}</p>
|
||||||
|
|
@ -36,15 +38,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, nextTick, computed } from 'vue';
|
import { ref, watch, nextTick, computed, onMounted } from 'vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { Download } from '@element-plus/icons-vue';
|
import { Download } from '@element-plus/icons-vue';
|
||||||
import QRCode from 'qrcode';
|
import QRCode from 'qrcode';
|
||||||
import html2canvas from 'html2canvas';
|
import domtoimage from 'dom-to-image-more';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
@ -59,9 +59,29 @@ const props = defineProps({
|
||||||
default: ''
|
default: ''
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const urlImage = ref('');
|
||||||
|
const imageLoading = ref(true);
|
||||||
|
const imageHeight = ref(280);
|
||||||
|
|
||||||
|
watch(() => props.imageUrl, async (newVal) => {
|
||||||
|
imageLoading.value = true;
|
||||||
|
urlImage.value = '';
|
||||||
|
urlImage.value = await fetchImage(newVal);
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleImageLoad = (e) => {
|
||||||
|
imageLoading.value = false;
|
||||||
|
const img = e.target;
|
||||||
|
if (img && img.naturalHeight) {
|
||||||
|
imageHeight.value = Math.min(img.naturalHeight, 400);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImageError = () => {
|
||||||
|
imageLoading.value = false;
|
||||||
|
ElMessage.error('图片加载失败');
|
||||||
|
};
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const cardRef = ref(null);
|
const cardRef = ref(null);
|
||||||
const qrcodeRef = ref(null);
|
const qrcodeRef = ref(null);
|
||||||
|
|
@ -113,11 +133,45 @@ const exportImage = async () => {
|
||||||
exporting.value = true;
|
exporting.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
const canvas = await html2canvas(cardRef.value, {
|
const canvas = await html2canvas(cardRef.value, {
|
||||||
scale: 2,
|
scale: 2,
|
||||||
useCORS: true,
|
useCORS: true,
|
||||||
backgroundColor: '#fff5f7',
|
allowTaint: true,
|
||||||
logging: false
|
backgroundColor: null,
|
||||||
|
logging: false,
|
||||||
|
imageTimeout: 0,
|
||||||
|
removeContainer: true,
|
||||||
|
foreignObjectRendering: false,
|
||||||
|
onclone: (clonedDoc) => {
|
||||||
|
const clonedCard = clonedDoc.querySelector('.card-preview');
|
||||||
|
if (clonedCard) {
|
||||||
|
clonedCard.style.width = '360px';
|
||||||
|
clonedCard.style.maxWidth = '360px';
|
||||||
|
clonedCard.style.boxShadow = '0 8px 32px rgba(255, 107, 157, 0.2)';
|
||||||
|
clonedCard.style.borderRadius = '0px';
|
||||||
|
}
|
||||||
|
const clonedImageSection = clonedDoc.querySelector('.card-image-section');
|
||||||
|
if (clonedImageSection) {
|
||||||
|
clonedImageSection.style.height = imageHeight.value + 'px';
|
||||||
|
// clonedImageSection.style.background = 'linear-gradient(135deg, #ffe4e8 0%, #fff5f7 100%)';
|
||||||
|
}
|
||||||
|
const clonedImage = clonedDoc.querySelector('.preview-image');
|
||||||
|
if (clonedImage) {
|
||||||
|
clonedImage.style.objectFit = 'cover';
|
||||||
|
clonedImage.style.width = '100%';
|
||||||
|
clonedImage.style.height = '100%';
|
||||||
|
}
|
||||||
|
const clonedMessageSection = clonedDoc.querySelector('.card-message-section');
|
||||||
|
if (clonedMessageSection) {
|
||||||
|
clonedMessageSection.style.background = 'linear-gradient(135deg, #fff5f7 0%, #ffe4e8 100%)';
|
||||||
|
}
|
||||||
|
const clonedQrcodeSection = clonedDoc.querySelector('.card-qrcode-section');
|
||||||
|
if (clonedQrcodeSection) {
|
||||||
|
clonedQrcodeSection.style.background = 'linear-gradient(135deg, #fff5f7 0%, #ffe4e8 100%)';
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
|
|
@ -133,7 +187,18 @@ const exportImage = async () => {
|
||||||
exporting.value = false;
|
exporting.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
async function fetchImage(url) {
|
||||||
|
const cacheBusterUrl = url + '?v=1.0.0';
|
||||||
|
const response = await fetch(cacheBusterUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
mode: 'cors',
|
||||||
|
credentials: 'omit',
|
||||||
|
cache: 'no-cache'
|
||||||
|
});
|
||||||
|
const blob = await response.blob();
|
||||||
|
const imgurl = URL.createObjectURL(blob);
|
||||||
|
return imgurl;
|
||||||
|
}
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
};
|
};
|
||||||
|
|
@ -146,7 +211,6 @@ const handleClose = () => {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: linear-gradient(135deg, #fff5f7 0%, #ffe4e8 100%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-preview {
|
.card-preview {
|
||||||
|
|
@ -167,15 +231,53 @@ const handleClose = () => {
|
||||||
|
|
||||||
.card-image-section {
|
.card-image-section {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 280px;
|
min-height: 200px;
|
||||||
|
max-height: 400px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
transition: height 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-loading {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #ffe4e8 0%, #fff5f7 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid rgba(214, 51, 132, 0.2);
|
||||||
|
border-top-color: #d63384;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-image {
|
.preview-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
animation: fadeIn 0.5s ease-in-out;
|
||||||
|
background-color: none;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-message-section {
|
.card-message-section {
|
||||||
|
|
@ -249,7 +351,8 @@ const handleClose = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-image-section {
|
.card-image-section {
|
||||||
height: 240px;
|
min-height: 180px;
|
||||||
|
max-height: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-message-section {
|
.card-message-section {
|
||||||
|
|
@ -0,0 +1,409 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
:width="dialogWidth"
|
||||||
|
custom-class="card-preview-dialog"
|
||||||
|
destroy-on-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
append-to-body
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<div class="preview-container">
|
||||||
|
<div ref="cardRef" class="card-preview">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="card-image-section" :style="{ height: imageHeight + 'px' }">
|
||||||
|
<div v-if="!urlImage" class="image-loading">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
</div>
|
||||||
|
<img v-else-if="urlImage" :src="urlImage" alt="Card Image" class="preview-image" @load="handleImageLoad" @error="handleImageError" />
|
||||||
|
</div>
|
||||||
|
<div class="card-message-section">
|
||||||
|
<p class="preview-message">{{ cardMessage }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-qrcode-section">
|
||||||
|
<div ref="qrcodeRef" class="qrcode-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleClose">{{ $t('greetingCard.cancelButton') }}</el-button>
|
||||||
|
<el-button type="primary" class="export-btn" @click="exportImage" :loading="exporting">
|
||||||
|
<el-icon><Download /></el-icon> {{ $t('greetingCard.exportButton') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, nextTick, computed, onMounted } from 'vue';
|
||||||
|
import { Download } from '@element-plus/icons-vue';
|
||||||
|
import { ElMessage, ElIcon, ElButton, ElInput, ElDialog } from 'element-plus'
|
||||||
|
import QRCode from 'qrcode';
|
||||||
|
import html2canvas from 'html2canvas';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
imageUrl: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
cardMessage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const urlImage = ref('');
|
||||||
|
const imageLoading = ref(true);
|
||||||
|
const imageHeight = ref(280);
|
||||||
|
|
||||||
|
watch(() => props.imageUrl, async (newVal) => {
|
||||||
|
imageLoading.value = true;
|
||||||
|
urlImage.value = '';
|
||||||
|
urlImage.value = await fetchImage(newVal);
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
const handleImageLoad = (e) => {
|
||||||
|
imageLoading.value = false;
|
||||||
|
const img = e.target;
|
||||||
|
if (img && img.naturalHeight) {
|
||||||
|
imageHeight.value = Math.min(img.naturalHeight, 400);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImageError = () => {
|
||||||
|
imageLoading.value = false;
|
||||||
|
ElMessage.error('图片加载失败');
|
||||||
|
};
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
const visible = ref(false);
|
||||||
|
const cardRef = ref(null);
|
||||||
|
const qrcodeRef = ref(null);
|
||||||
|
const exporting = ref(false);
|
||||||
|
|
||||||
|
|
||||||
|
const dialogWidth = computed(() => {
|
||||||
|
if (typeof window === 'undefined') return '400px';
|
||||||
|
return window.innerWidth < 768 ? '95%' : '400px';
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (newVal) => {
|
||||||
|
visible.value = newVal;
|
||||||
|
if (newVal) {
|
||||||
|
nextTick(() => {
|
||||||
|
generateQRCode();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(visible, (newVal) => {
|
||||||
|
emit('update:modelValue', newVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
const generateQRCode = async () => {
|
||||||
|
const Loadurl = `${window.location.origin}/#/confession-electronic-card?id=${props.id}`;
|
||||||
|
if (!qrcodeRef.value) return;
|
||||||
|
try {
|
||||||
|
qrcodeRef.value.innerHTML = '';
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
await QRCode.toCanvas(canvas,Loadurl, {
|
||||||
|
width: 70,
|
||||||
|
margin: 0,
|
||||||
|
color: {
|
||||||
|
dark: '#d63384',
|
||||||
|
light: '#ffffff'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
qrcodeRef.value.appendChild(canvas);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('QR Code generation failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportImage = async () => {
|
||||||
|
if (!cardRef.value) return;
|
||||||
|
|
||||||
|
exporting.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const canvas = await html2canvas(cardRef.value, {
|
||||||
|
scale: 2, // 保持高分辨率
|
||||||
|
useCORS: true,
|
||||||
|
allowTaint: true,
|
||||||
|
backgroundColor: '#ffffff', // 建议显式设置为白色,避免透明背景在某些查看器下变黑
|
||||||
|
logging: false,
|
||||||
|
imageTimeout: 0,
|
||||||
|
removeContainer: true,
|
||||||
|
foreignObjectRendering: false,
|
||||||
|
onclone: (clonedDoc) => {
|
||||||
|
const clonedCard = clonedDoc.querySelector('.card-preview');
|
||||||
|
if (clonedCard) {
|
||||||
|
clonedCard.style.width = '360px';
|
||||||
|
clonedCard.style.maxWidth = '360px';
|
||||||
|
clonedCard.style.boxShadow = 'none'; // 导出时通常建议去掉很重的阴影,或者保留看你需求
|
||||||
|
clonedCard.style.borderRadius = '0px';
|
||||||
|
}
|
||||||
|
|
||||||
|
const clonedImageSection = clonedDoc.querySelector('.card-image-section');
|
||||||
|
if (clonedImageSection) {
|
||||||
|
clonedImageSection.style.height = imageHeight.value + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
const clonedImage = clonedDoc.querySelector('.preview-image');
|
||||||
|
if (clonedImage) {
|
||||||
|
// --- 关键修改开始 ---
|
||||||
|
// 强制移除动画和过渡效果,确保图片完全显示
|
||||||
|
clonedImage.style.animation = 'none';
|
||||||
|
clonedImage.style.transition = 'none';
|
||||||
|
clonedImage.style.opacity = '1';
|
||||||
|
// --- 关键修改结束 ---
|
||||||
|
clonedImage.style.objectFit = 'cover';// 确保图片填充容器
|
||||||
|
clonedImage.style.width = '100%';
|
||||||
|
clonedImage.style.height = '100%';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文字部分背景
|
||||||
|
const clonedMessageSection = clonedDoc.querySelector('.card-message-section');
|
||||||
|
if (clonedMessageSection) {
|
||||||
|
clonedMessageSection.style.background = 'linear-gradient(135deg, #fff5f7 0%, #ffe4e8 100%)';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理二维码部分背景
|
||||||
|
const clonedQrcodeSection = clonedDoc.querySelector('.card-qrcode-section');
|
||||||
|
if (clonedQrcodeSection) {
|
||||||
|
clonedQrcodeSection.style.background = 'linear-gradient(135deg, #fff5f7 0%, #ffe4e8 100%)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.download = `greeting-card-${Date.now()}.png`;
|
||||||
|
link.href = canvas.toDataURL('image/png');
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
ElMessage.success(t('greetingCard.exportSuccess'));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Export failed:', error);
|
||||||
|
ElMessage.error(t('greetingCard.exportFailed'));
|
||||||
|
} finally {
|
||||||
|
exporting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
async function fetchImage(url) {
|
||||||
|
const cacheBusterUrl = url + '?v=1.0.0';
|
||||||
|
const response = await fetch(cacheBusterUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
mode: 'cors',
|
||||||
|
credentials: 'omit',
|
||||||
|
cache: 'no-cache'
|
||||||
|
});
|
||||||
|
const blob = await response.blob();
|
||||||
|
const imgurl = URL.createObjectURL(blob);
|
||||||
|
return imgurl;
|
||||||
|
}
|
||||||
|
const handleClose = () => {
|
||||||
|
visible.value = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.preview-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-preview {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 360px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 8px 32px rgba(255, 107, 157, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image-section {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 200px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
transition: height 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-loading {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #ffe4e8 0%, #fff5f7 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid rgba(214, 51, 132, 0.2);
|
||||||
|
border-top-color: #d63384;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
animation: fadeIn 0.5s ease-in-out;
|
||||||
|
background-color: none;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-message-section {
|
||||||
|
padding: 24px 20px;
|
||||||
|
background: linear-gradient(135deg, #fff5f7 0%, #ffe4e8 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-message {
|
||||||
|
margin: 0;
|
||||||
|
color: #d63384;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: 'Georgia', 'Times New Roman', 'Playfair Display', 'Brush Script MT', cursive;
|
||||||
|
text-shadow: 0 2px 4px rgba(214, 51, 132, 0.2);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-qrcode-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 0;
|
||||||
|
background: linear-gradient(135deg, #fff5f7 0%, #ffe4e8 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode-container canvas {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-btn {
|
||||||
|
background: linear-gradient(135deg, #ff6b9d 0%, #ff8a80 100%);
|
||||||
|
border: none;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 12px rgba(255, 107, 157, 0.3);
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-btn:hover {
|
||||||
|
background: linear-gradient(135deg, #ff5a8d 0%, #ff7a70 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 20px rgba(255, 107, 157, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.preview-container {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-preview {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-image-section {
|
||||||
|
min-height: 180px;
|
||||||
|
max-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-message-section {
|
||||||
|
padding: 20px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-message {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-family: 'Georgia', 'Times New Roman', 'Playfair Display', 'Brush Script MT', cursive;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-qrcode-section {
|
||||||
|
bottom: 12px;
|
||||||
|
right: 12px;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrcode-container canvas {
|
||||||
|
width: 60px !important;
|
||||||
|
height: 60px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer .el-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-btn {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
|
import './style.css'
|
||||||
// UI组件库入口文件
|
// UI组件库入口文件
|
||||||
import LoadingCom from './components/LoadingCom/index.vue'
|
import LoadingCom from './components/LoadingCom/index.vue'
|
||||||
|
import CardPreview from './components/CardPreview/CardPreview.vue'
|
||||||
import CanvasEditor from './components/CanvasEditor/CanvasEditor.vue'
|
import CanvasEditor from './components/CanvasEditor/CanvasEditor.vue'
|
||||||
|
|
||||||
import './style.css'
|
|
||||||
|
|
||||||
// 创建带有Dt前缀的组件
|
// 创建带有Dt前缀的组件
|
||||||
const DtLoadingCom = {
|
const DtLoadingCom = {
|
||||||
...LoadingCom,
|
...LoadingCom,
|
||||||
|
|
@ -20,17 +20,26 @@ const DtCanvasEditor = {
|
||||||
app.component('DtCanvasEditor', DtCanvasEditor)
|
app.component('DtCanvasEditor', DtCanvasEditor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const DtCardPreview = {
|
||||||
|
...CardPreview,
|
||||||
|
name: 'DtCardPreview',
|
||||||
|
install(app) {
|
||||||
|
app.component('DtCardPreview', DtCardPreview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 组件列表
|
// 组件列表
|
||||||
const components = [
|
const components = [
|
||||||
DtLoadingCom,
|
DtLoadingCom,
|
||||||
DtCanvasEditor
|
DtCanvasEditor,
|
||||||
|
DtCardPreview
|
||||||
]
|
]
|
||||||
|
|
||||||
// 导出组件
|
// 导出组件
|
||||||
export {
|
export {
|
||||||
DtLoadingCom,
|
DtLoadingCom,
|
||||||
DtCanvasEditor
|
DtCanvasEditor,
|
||||||
|
DtCardPreview
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量注册组件的函数
|
// 批量注册组件的函数
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
import { fileURLToPath, URL } from 'node:url'
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
build: {
|
build: {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
const greetingCardApi = {
|
||||||
|
GENERATE_IMAGE:{url:'/api-core/front/gemini/generate-image',method:'POST',isLoading: true,},// 生图模型任务创建
|
||||||
|
CREATE_GREETING_CARD:{url:'/api-base/greeting-card',method:'POST',isLoading: true,},// 创建贺卡
|
||||||
|
UPDATE_GREETING_CARD:{url:'/api-base/greeting-card/update',method:'POST',isLoading: true,},// 更新贺卡
|
||||||
|
DELETE_GREETING_CARD:{url:'/api-base/greeting-card/delete/{id}',method:'POST',isLoading: true,},// 删除贺卡
|
||||||
|
GET_GREETING_CARD_DETAIL:{url:'/api-base/greeting-card/public/{id}',method:'GET',isLoading: true,},// 查询贺卡详情
|
||||||
|
GET_MY_GREETING_CARD_LIST:{url:'/api-base/greeting-card/my-list',method:'GET',isLoading: true,},// 查询我的贺卡列表
|
||||||
|
}
|
||||||
|
export default greetingCardApi;
|
||||||
|
|
@ -11,6 +11,7 @@ import rechargeconfig from './rechargeconfig.js';
|
||||||
import voucher from './voucher.js';
|
import voucher from './voucher.js';
|
||||||
import shop from './shop.js';
|
import shop from './shop.js';
|
||||||
import agreement from './agreement.js';
|
import agreement from './agreement.js';
|
||||||
|
import greetingCard from './greetingCard.js';
|
||||||
export default {
|
export default {
|
||||||
...meshy,
|
...meshy,
|
||||||
...login,
|
...login,
|
||||||
|
|
@ -25,4 +26,5 @@ export default {
|
||||||
...voucher,
|
...voucher,
|
||||||
...shop,
|
...shop,
|
||||||
...agreement,
|
...agreement,
|
||||||
|
...greetingCard,
|
||||||
};
|
};
|
||||||
|
|
@ -45,9 +45,8 @@ export class MeshyServer extends FileServer {
|
||||||
ai_model: 'latest',
|
ai_model: 'latest',
|
||||||
enable_pbr: false,
|
enable_pbr: false,
|
||||||
should_remesh: false,
|
should_remesh: false,
|
||||||
should_texture: false,//是否生成纹理
|
should_texture: false,
|
||||||
save_pre_remeshed_model: true,
|
save_pre_remeshed_model: true,
|
||||||
// target_polycount:300000,
|
|
||||||
...config
|
...config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const getEnvBaseURL = () => {
|
||||||
// }
|
// }
|
||||||
var baseURL = '';
|
var baseURL = '';
|
||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
if(hostname=='localhost'||hostname=='192.168.101.2'){
|
if(hostname=='localhost'||hostname=='192.168.0.146'){
|
||||||
baseURL = '/api'
|
baseURL = '/api'
|
||||||
}else if(hostname.indexOf('deotaland.ai')>-1||hostname.indexOf('deota.cn')>-1){
|
}else if(hostname.indexOf('deotaland.ai')>-1||hostname.indexOf('deota.cn')>-1){
|
||||||
baseURL = 'https://api.deotaland.ai'
|
baseURL = 'https://api.deotaland.ai'
|
||||||
|
|
|
||||||
|
|
@ -177,9 +177,6 @@ importers:
|
||||||
element-plus:
|
element-plus:
|
||||||
specifier: ^2.11.7
|
specifier: ^2.11.7
|
||||||
version: 2.11.7(vue@3.5.24)
|
version: 2.11.7(vue@3.5.24)
|
||||||
html2canvas:
|
|
||||||
specifier: ^1.4.1
|
|
||||||
version: 1.4.1
|
|
||||||
install:
|
install:
|
||||||
specifier: ^0.13.0
|
specifier: ^0.13.0
|
||||||
version: 0.13.0
|
version: 0.13.0
|
||||||
|
|
@ -201,9 +198,6 @@ importers:
|
||||||
pinia-plugin-persistedstate:
|
pinia-plugin-persistedstate:
|
||||||
specifier: ^4.7.1
|
specifier: ^4.7.1
|
||||||
version: 4.7.1(pinia@3.0.4)
|
version: 4.7.1(pinia@3.0.4)
|
||||||
qrcode:
|
|
||||||
specifier: ^1.5.4
|
|
||||||
version: 1.5.4
|
|
||||||
three:
|
three:
|
||||||
specifier: ^0.180.0
|
specifier: ^0.180.0
|
||||||
version: 0.180.0
|
version: 0.180.0
|
||||||
|
|
@ -280,9 +274,18 @@ importers:
|
||||||
|
|
||||||
packages/ui:
|
packages/ui:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
dom-to-image-more:
|
||||||
|
specifier: ^3.7.2
|
||||||
|
version: 3.7.2
|
||||||
element-plus:
|
element-plus:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.11.7(vue@3.5.24)
|
version: 2.11.7(vue@3.5.24)
|
||||||
|
html2canvas:
|
||||||
|
specifier: ^1.4.1
|
||||||
|
version: 1.4.1
|
||||||
|
qrcode:
|
||||||
|
specifier: ^1.5.4
|
||||||
|
version: 1.5.4
|
||||||
vue:
|
vue:
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.5.24
|
version: 3.5.24
|
||||||
|
|
@ -2242,6 +2245,10 @@ packages:
|
||||||
entities: 2.2.0
|
entities: 2.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/dom-to-image-more@3.7.2:
|
||||||
|
resolution: {integrity: sha512-uQf+pHv6eQhgfI8t2bFuinV0KsPyT8TZgCLwcSU8uBVgN9v6leb0mMpvp6HQAlAcplP3NCcGjxbdqef6pTzvmw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/dom7@3.0.0:
|
/dom7@3.0.0:
|
||||||
resolution: {integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==}
|
resolution: {integrity: sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue