支付优化
CI/CD / build (push) Successful in 4m59s Details

This commit is contained in:
13121765685 2026-01-14 14:11:56 +08:00
parent 4551a1537d
commit 57edd9bbf9
10 changed files with 254 additions and 22 deletions

View File

@ -0,0 +1,37 @@
# 实现步骤
1. **修改导航组件**:在 `AppSidebar.vue` 中添加备案号图标项
- 在 `coreMenuItems` 数组末尾添加一个新的菜单项
- 使用自定义 SVG 实现圆形叹号图标(或使用现有图标组件)
2. **实现悬浮弹窗功能**
- 添加一个响应式状态来控制弹窗的显示/隐藏
- 实现鼠标悬停和点击事件处理
- 设计弹窗样式,确保在右侧悬浮显示
- 添加备案号文本:"蜀ICP备2024078618号-2"
3. **样式设计**
- 设计圆形图标样式,与现有导航项保持一致
- 设计弹窗样式,包含背景、边框、阴影等
- 确保弹窗在不同屏幕尺寸下正确显示
4. **响应式处理**
- 桌面端:鼠标悬停显示弹窗
- 移动端:点击图标显示/隐藏弹窗
- 添加媒体查询,确保在不同设备上的兼容性
5. **代码优化**
- 确保代码符合项目的代码规范
- 添加适当的注释
- 测试交互效果
# 预期效果
- 在导航菜单中添加一个圆形叹号图标
- 鼠标悬停或点击图标时,在右侧显示包含备案号的悬浮弹窗
- 弹窗具有良好的视觉效果和交互体验
- 适配移动端、桌面端和平板端
# 文件修改
- `d:\work\Aiproject\DeotalandAi\apps\frontend\src\components\layout\AppSidebar.vue`

View File

@ -6,6 +6,7 @@ import AppHeader from '@/components/layout/AppHeader.vue'
import AppSidebar from '@/components/layout/AppSidebar.vue' import AppSidebar from '@/components/layout/AppSidebar.vue'
import { useAuthStore } from '@/stores/auth'; import { useAuthStore } from '@/stores/auth';
import footerCom from './components/footerBeiAn/index.vue'; import footerCom from './components/footerBeiAn/index.vue';
import {isWeChatBrowser} from '@deotaland/utils';
const authStore = useAuthStore(); const authStore = useAuthStore();
authStore.updateUserInfo() authStore.updateUserInfo()
const route = useRoute() const route = useRoute()
@ -42,6 +43,9 @@ const closeMethods = {
qmLoading.value = false; qmLoading.value = false;
} }
} }
const isCn =()=>{
return window.location.href.indexOf('cn')>-1
}
window.setElLoading = (qp=false)=>{ window.setElLoading = (qp=false)=>{
if(qp){ if(qp){
qmLoading.value = true qmLoading.value = true
@ -96,7 +100,7 @@ onMounted(() => {
<div v-else> <div v-else>
<router-view /> <router-view />
</div> </div>
<footerCom /> <footerCom v-if="isCn()" />
</template> </template>
<style> <style>
*{ *{

View File

@ -236,7 +236,7 @@ import StripePaymentForm from '@/components/StripePaymentForm.vue'
import { Country, State } from 'country-state-city' import { Country, State } from 'country-state-city'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { PayServer } from '@deotaland/utils' import { PayServer } from '@deotaland/utils'
import { requestUtils,clientApi } from '@deotaland/utils' import { requestUtils,clientApi,environmentUtils } from '@deotaland/utils'
import { PurchaseModal as PurchaseModalClass } from './index.js' import { PurchaseModal as PurchaseModalClass } from './index.js'
const payserver = new PayServer(); const payserver = new PayServer();
const purchaseModal = new PurchaseModalClass(); const purchaseModal = new PurchaseModalClass();
@ -249,9 +249,11 @@ const emit = defineEmits(['close'])
const onClose = () => emit('close') const onClose = () => emit('close')
const addons = ref({ matte:false, gloss:false, base:false }) const addons = ref({ matte:false, gloss:false, base:false })
const qty = ref(1) const qty = ref(1)
const ipName = ref('') const ipName = ref('doll')
const contact = ref({ emailOrPhone:'', subscribe:false }) const contact = ref({ emailOrPhone:'', subscribe:false })
const shipping = ref({ country:'US', firstName:'', lastName:'', postalCode:'', state:'', city:'', address1:'', address2:'', phone:'', saveInfo:false }) //
const initialCountry = ref('US')
const shipping = ref({ country: initialCountry.value, firstName:'', lastName:'', postalCode:'', state:'', city:'', address1:'', address2:'', phone:'', saveInfo:false })
const countryOptions = ref([]) const countryOptions = ref([])
const stateOptions = ref([]) const stateOptions = ref([])
const showStripe = ref(false) const showStripe = ref(false)
@ -284,7 +286,6 @@ const isPayButtonDisabled = computed(() => {
shipping.value.city.trim() && shipping.value.city.trim() &&
shipping.value.address1.trim() && shipping.value.address1.trim() &&
shipping.value.phone.trim() && shipping.value.phone.trim() &&
contact.value.emailOrPhone.trim() &&
ipName.value.trim() ipName.value.trim()
) )
}) })
@ -458,6 +459,17 @@ const updateCountryOptions = () => {
onMounted(() => { onMounted(() => {
try { try {
//
const envResult = environmentUtils.quickDetectEnvironment()
if (envResult.isDomestic) {
initialCountry.value = 'CN'
//
const savedShipping = localStorage.getItem('purchase_shipping')
if (!savedShipping) {
shipping.value.country = 'CN'
}
}
const s = localStorage.getItem('purchase_shipping') const s = localStorage.getItem('purchase_shipping')
if (s) Object.assign(shipping.value, JSON.parse(s)) if (s) Object.assign(shipping.value, JSON.parse(s))
const c = localStorage.getItem('purchase_contact') const c = localStorage.getItem('purchase_contact')

View File

@ -26,6 +26,32 @@
</transition> </transition>
</div> </div>
</li> </li>
<!-- 备案号图标 -->
<li v-if="false">
<div
class="nav-item record-item"
@mouseenter="showRecordPopup = true"
@mouseleave="showRecordPopup = false"
>
<div class="nav-icon record-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<path fill="currentColor" d="M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768zm0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896z"/>
<path fill="currentColor" d="M480 384v384a32 32 0 0 0 64 0V384a32 32 0 0 0-64 0zm0-128a32 32 0 0 0-32 32v32a32 32 0 0 0 64 0v-32a32 32 0 0 0-32-32z"/>
</svg>
</div>
<!-- 备案号悬浮弹窗 -->
<div
v-if="showRecordPopup"
class="record-popup"
@mouseenter="showRecordPopup = true"
@mouseleave="showRecordPopup = false"
>
<div class="record-content">
蜀ICP备2024078618号-2
</div>
</div>
</div>
</li>
</ul> </ul>
</nav> </nav>
@ -107,6 +133,7 @@ const authStore = useAuthStore()
// //
const isMobile = ref(window.innerWidth < 768) const isMobile = ref(window.innerWidth < 768)
const remainingPoints = ref(1280) const remainingPoints = ref(1280)
const showRecordPopup = ref(false)
// //
const currentUser = computed(() => authStore.user) const currentUser = computed(() => authStore.user)
@ -212,7 +239,8 @@ onUnmounted(() => {
border-right: 1px solid var(--border-color, #e5e7eb); border-right: 1px solid var(--border-color, #e5e7eb);
/* transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); */ /* transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); */
position: relative; position: relative;
overflow: hidden; overflow-y: auto;
overflow-x: visible;
z-index: 100; z-index: 100;
} }
@ -788,4 +816,143 @@ onUnmounted(() => {
.sidebar-nav::-webkit-scrollbar-thumb:hover { .sidebar-nav::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary, #6b7280); background: var(--text-secondary, #6b7280);
} }
/* 备案号图标样式 */
.record-item {
cursor: pointer;
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 8px 0 !important;
min-height: auto !important;
overflow: visible !important;
}
.record-item::before {
content: none !important;
}
.record-item:hover {
background: transparent !important;
border: none !important;
box-shadow: none !important;
transform: none !important;
overflow: visible !important;
}
.record-icon svg {
width: 28px;
height: 28px;
transition: all 0.3s ease;
}
.record-item:hover .record-icon svg {
transform: scale(1.1);
filter: drop-shadow(0 0 8px rgba(139, 92, 246, 0.5));
}
/* 备案号悬浮弹窗样式 */
.record-popup {
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
margin-left: 16px;
background: var(--sidebar-bg, #ffffff);
border: 1px solid var(--border-color, #e5e7eb);
border-radius: 8px;
padding: 12px 16px;
box-shadow: 0 8px 32px rgba(107, 70, 193, 0.16),
0 2px 8px rgba(107, 70, 193, 0.08);
z-index: 1000;
min-width: 200px;
white-space: nowrap;
backdrop-filter: blur(12px);
animation: fadeIn 0.2s ease;
}
.record-popup::before {
content: '';
position: absolute;
right: 100%;
top: 50%;
transform: translateY(-50%);
border: 8px solid transparent;
border-right-color: var(--border-color, #e5e7eb);
}
.record-popup::after {
content: '';
position: absolute;
right: 100%;
top: 50%;
transform: translateY(-50%);
border: 7px solid transparent;
border-right-color: var(--sidebar-bg, #ffffff);
margin-right: -1px;
}
.record-content {
font-size: 14px;
color: var(--text-primary, #1f2937);
text-align: center;
font-weight: 500;
}
/* 动画 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-50%) translateX(-8px);
}
to {
opacity: 1;
transform: translateY(-50%) translateX(0);
}
}
/* 移动端样式调整 */
@media (max-width: 767px) {
.record-popup {
left: 50%;
top: 100%;
transform: translateX(-50%);
margin-left: 0;
margin-top: 12px;
}
.record-popup::before,
.record-popup::after {
right: 50%;
top: 0;
transform: translateY(-100%) translateX(50%);
}
.record-popup::before {
border: 8px solid transparent;
border-bottom-color: var(--border-color, #e5e7eb);
}
.record-popup::after {
border: 7px solid transparent;
border-bottom-color: var(--sidebar-bg, #ffffff);
margin-top: -1px;
}
}
/* 折叠状态下的弹窗位置调整 */
.app-sidebar:not(.sidebar-mobile) .record-popup {
left: calc(100% + 16px);
}
/* 深色主题适配 */
.dark .record-popup {
background: var(--sidebar-bg, #1f2937);
color: var(--text-primary, #f9fafb);
}
.dark .record-popup::after {
border-right-color: var(--sidebar-bg, #1f2937);
border-bottom-color: var(--sidebar-bg, #1f2937);
}
</style> </style>

View File

@ -1,5 +1,14 @@
import { isWeChatBrowser } from '@deotaland/utils' import { isWeChatBrowser } from '@deotaland/utils'
let savedLang = localStorage.getItem('lang') || (isWeChatBrowser()?'zh':'en') const isZh = ()=>{
if(isWeChatBrowser()){
return true
}
if(window.location.href.indexOf('cn')>-1){
return true
}
return false
}
let savedLang = localStorage.getItem('lang') || (isZh()?'zh':'en')
export default { export default {
legacy: false, legacy: false,
locale: savedLang, locale: savedLang,

View File

@ -153,7 +153,7 @@ const goToPhoneLogin = () => {
// //
onMounted(() => { onMounted(() => {
// detectEnvironment() // detectEnvironment()
isWeChatBrowser()&&goToPhoneLogin() // isWeChatBrowser()&&goToPhoneLogin()
}) })
</script> </script>

View File

@ -35,30 +35,30 @@
{{ t('nav.land') }} {{ t('nav.land') }}
</a> --> </a> -->
<a <!-- <a
href="#" href="#"
class="text-sm font-medium text-gray-300 hover:text-white transition-colors" class="text-sm font-medium text-gray-300 hover:text-white transition-colors"
> >
{{ t('nav.creator') }} {{ t('nav.creator') }}
</a> </a> -->
<a <!-- <a
href="#" href="#"
class="text-sm font-medium text-gray-300 hover:text-white transition-colors" class="text-sm font-medium text-gray-300 hover:text-white transition-colors"
> >
{{ t('nav.done') }} {{ t('nav.done') }}
</a> </a> -->
<a <!-- <a
href="#" href="#"
class="text-sm font-medium text-gray-300 hover:text-white transition-colors" class="text-sm font-medium text-gray-300 hover:text-white transition-colors"
> >
{{ t('nav.about') }} {{ t('nav.about') }}
</a> </a> -->
<router-link <!-- <router-link
to="/points-recharge" to="/points-recharge"
class="text-sm font-medium text-gray-300 hover:text-white transition-colors" class="text-sm font-medium text-gray-300 hover:text-white transition-colors"
> >
pricing pricing
</router-link> </router-link> -->
</nav> </nav>
<!-- Right Action & Mobile Toggle --> <!-- Right Action & Mobile Toggle -->
@ -519,12 +519,12 @@
</div> </div>
<!-- Company --> <!-- Company -->
<div class="flex flex-col gap-4"> <!-- <div class="flex flex-col gap-4">
<h4 class="font-semibold text-gray-500 text-sm uppercase tracking-wider">{{ t('footer.company') }}</h4> <h4 class="font-semibold text-gray-500 text-sm uppercase tracking-wider">{{ t('footer.company') }}</h4>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<a href="#" class="text-gray-300 hover:text-white text-sm">{{ t('footer.about') }}</a> <a href="#" class="text-gray-300 hover:text-white text-sm">{{ t('footer.about') }}</a>
</div> </div>
</div> </div> -->
<!-- Action --> <!-- Action -->
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">

View File

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

View File

@ -155,7 +155,6 @@ export class PayServer {
payReducerUrl = `${window.location.origin}/#/order-management` payReducerUrl = `${window.location.origin}/#/order-management`
pamras = { pamras = {
product_id: orderInfo.product_id, product_id: orderInfo.product_id,
shop_id: orderInfo.shop_id || '',
"methods": [ "methods": [
"card" "card"
], ],
@ -167,6 +166,9 @@ export class PayServer {
"order_info": orderInfo.order_info, "order_info": orderInfo.order_info,
"coupon_ids": orderInfo.coupon_ids || [], "coupon_ids": orderInfo.coupon_ids || [],
} }
if(orderInfo.shop_id){
pamras.shop_id = orderInfo.shop_id
}
break; break;
case 2: case 2:
payReducerUrl = `${window.location.origin}/#/points-recharge` payReducerUrl = `${window.location.origin}/#/points-recharge`
@ -182,6 +184,7 @@ export class PayServer {
default: default:
break; break;
} }
let requestUrl = { let requestUrl = {
1: clientApi.default.createPayorOrder, 1: clientApi.default.createPayorOrder,
2: clientApi.default.CREATE_RECHARGE_ORDER, 2: clientApi.default.CREATE_RECHARGE_ORDER,

View File

@ -19,7 +19,7 @@ const getEnvBaseURL = () => {
const hostname = window.location.hostname; const hostname = window.location.hostname;
if(hostname=='localhost'){ if(hostname=='localhost'){
baseURL = '/api' baseURL = '/api'
}else if(hostname.indexOf('deotaland.ai')>-1){ }else if(hostname.indexOf('deotaland.ai')>-1||hostname.indexOf('deota.cn')>-1){
baseURL = 'https://api.deotaland.ai' baseURL = 'https://api.deotaland.ai'
}else if(hostname.indexOf('deotaland.local')>-1){ }else if(hostname.indexOf('deotaland.local')>-1){
baseURL = 'http://api.deotaland.local' baseURL = 'http://api.deotaland.local'