+
{{ t('canvas.referenceText') }}
@@ -460,10 +496,11 @@
{{ t('footer.socials') }}
@@ -513,8 +550,8 @@ import MotionCom from './motion.vue'
// import spline from './spline.vue';
import { ref, onMounted, onUnmounted, computed } from 'vue';
import Bg from './bg.vue'
-import dog from '@/assets/home/dog.webp'
-import qdog from '@/assets/home/qdog.webp'
+// import dog from '@/assets/home/dog.webp'
+// import qdog from '@/assets/home/qdog.webp'
const center = 'https://draft-user.s3.us-east-2.amazonaws.com/images/c175585a-20c2-48b3-8939-32bbdb25814b.webp'
const center1 = 'https://draft-user.s3.us-east-2.amazonaws.com/images/ecf39871-52c5-45ad-9f9e-6eafd838ce54.webp'
const center2 = 'https://draft-user.s3.us-east-2.amazonaws.com/images/f7c4454e-1781-448e-9c70-b087b64f380e.webp'
@@ -541,9 +578,12 @@ const isMobile = ref(window.innerWidth < 768);
const i18n = {
en: {
nav: {
+ // creator: 'Creator',
+ // land: 'Land',
+ // pricing: 'Pricing',
creator: 'Creator',
- land: 'Land',
- pricing: 'Pricing'
+ done: 'D one',
+ about: 'About us'
},
hero: {
title: 'Create with Deotaland',
@@ -597,9 +637,12 @@ const i18n = {
},
zh: {
nav: {
- creator: '创作者',
- land: '社区',
- pricing: '价格'
+ // creator: '创作者',
+ // land: '社区',
+ // pricing: '价格',
+ creator: 'Creator',
+ done: 'D one',
+ about: 'About us'
},
hero: {
title: '使用 Deotaland 创作',
@@ -727,9 +770,9 @@ const navLinks = [
{ name: 'Pricing', href: '#' },
];
// Creation Canvas Images
-const refImage = dog;
-const model3dImage = qdog;
-const realRobotImage = 'https://draft-user.s3.us-east-2.amazonaws.com/images/2e93945a-d20e-4a29-8c7f-d6e26260941b.webp';
+const refImage = 'https://draft-user.s3.us-east-2.amazonaws.com/images/0185a1f7-563a-4af9-9569-65d81f710c52.webp';
+const model3dImage = 'https://draft-user.s3.us-east-2.amazonaws.com/images/e5a1408b-b695-431f-b03e-0b9b06f1b82f.webp';
+const realRobotImage = 'https://draft-user.s3.us-east-2.amazonaws.com/images/ce2f6979-4b3c-499f-b179-80abbf4d7431.webp';
// Robot Cards
const cards = [
diff --git a/apps/frontend/src/views/user/index.js b/apps/frontend/src/views/user/index.js
new file mode 100644
index 0000000..ac90846
--- /dev/null
+++ b/apps/frontend/src/views/user/index.js
@@ -0,0 +1,56 @@
+import { clientApi, requestUtils } from '@deotaland/utils';
+export class UserController {
+ constructor() {
+ }
+ //返回当前用户的邀请码列表及使用状态
+ async getCodes() {
+ return await requestUtils.common(clientApi.default.INVITE_CODES);
+ /*
+ 返回示例:
+ {
+ "code": 0,
+ "success": true,
+ "data": [
+ {
+ "inviteCode": "string",
+ "codeType": "string",
+ "isUsed": 1073741824,
+ "invitedUserNickname": "string",
+ "usedAt": "2025-12-18T06:33:05.386Z",
+ "createdAt": "2025-12-18T06:33:05.386Z"
+ }
+ ],
+ "message": "操作成功"
+}
+
+ */
+ }
+ // 返回邀请的用户列表,包含奖励明细
+ async getInviteRecords() {
+ return await requestUtils.common(clientApi.default.INVITE_RECORDS);
+ /*
+ 返回示例:
+ {
+ "code": 0,
+ "success": true,
+ "data": [
+ {
+ "invitedUserId": 9007199254740991,
+ "invitedUserNickname": "string",
+ "invitedUserAvatar": "string",
+ "invitedAt": "2025-12-18T06:33:05.383Z",
+ "rewards": [
+ {
+ "rewardType": 1073741824,
+ "rewardTypeName": "string",
+ "rewardScore": 0,
+ "grantedAt": "2025-12-18T06:33:05.383Z"
+ }
+ ]
+ }
+ ],
+ "message": "操作成功"
+}
+ */
+ }
+}
\ No newline at end of file
diff --git a/apps/frontend/src/views/user/index.vue b/apps/frontend/src/views/user/index.vue
new file mode 100644
index 0000000..7180c85
--- /dev/null
+++ b/apps/frontend/src/views/user/index.vue
@@ -0,0 +1,1829 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ userData.nickname }}
+
+
+
+
+
{{ getRoleName() }}
+
+
{{ userData.email }}
+
+
+
+
+
+
{{ $t('userCenter.points.title') }}
+
+
+
+
+
+ 🪄
+ {{ $t('userCenter.points.currentPoints') }}
+ {{ userData.currentPoints }}
+
+
+
+
+
+
+
+ 🪄
+ {{ item.points }}
+
+
+ {{ $t('userCenter.points.expiryDate') }}:
+ {{ item.expiryDate }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('userCenter.points.consumptionRules.behavior') }}
+ {{ $t('userCenter.points.consumptionRules.pointsConsumption') }}
+
+
+
+
+ {{ $t('userCenter.points.consumptionRules.rules.generateImage') }}
+ 1 {{ $t('pointsRecharge.points') }}
+
+
+ {{ $t('userCenter.points.consumptionRules.rules.generateModel') }}
+ 30 {{ $t('pointsRecharge.points') }}
+
+
+
+
+
+
+ {{ $t('userCenter.points.consumptionRules.additionalRules.rule1') }}
+ {{ $t('userCenter.points.consumptionRules.additionalRules.rule2') }}
+ {{ $t('userCenter.points.consumptionRules.additionalRules.rule3') }}
+
+
+
+
+
+
+
+
{{ $t('userCenter.invitation.title') }}
+
+
+ {{ $t('userCenter.invitation.inviteCount') }}
+ {{ userData.inviteCount }}
+
+
+
+
+
+
{{ $t('userCenter.invitation.inviteCodes') }}
+
+
+
+
{{ inviteCode.code }}
+
+
+
+
+
+
+
+
+
+
+
{{ $t('userCenter.invitation.rules.title') }}
+
+
+
+
+
+
{{ $t('userCenter.invitation.rules.freeMember.title') }}
+
+ {{ $t('userCenter.invitation.rules.freeMember.reward') }}
+
+ {{ $t('userCenter.invitation.rules.freeMember.pointsReward') }}
+
+
+
+
+
+
+
+
+
{{ $t('userCenter.invitation.rules.creatorMember.title') }}
+
{{ $t('userCenter.invitation.rules.creatorMember.whenInvite') }}
+
+ {{ $t('userCenter.invitation.rules.creatorMember.permission') }}
+ {{ $t('userCenter.invitation.rules.creatorMember.commissionAbility') }}
+ {{ $t('userCenter.invitation.rules.creatorMember.immediateReward') }}
+ {{ $t('userCenter.invitation.rules.creatorMember.binding') }}
+ {{ $t('userCenter.invitation.rules.creatorMember.subsequentOrder') }}
+ {{ $t('userCenter.invitation.rules.creatorMember.commissionRate') }}
+
+
+
+
+
+
{{ $t('userCenter.invitation.rules.becomeCreator.title') }}
+
+
+
{{ $t('userCenter.invitation.rules.becomeCreator.qrCode') }}
+
+
+
+
+
+
+
+
+
+
{{ $t('userCenter.creator.commissionTitle') }}
+
+
+ {{ $t('userCenter.creator.totalConsumption') }}
+ {{ userData.totalConsumption }}
+
+
+ {{ $t('userCenter.creator.totalCommission') }}
+ {{ userData.totalCommission }}
+
+
+ {{ $t('userCenter.creator.availableCommission') }}
+ {{ userData.availableCommission }}
+
+
+
+
+
+ {{ $t('userCenter.creator.withdrawal') }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 8a4f84f..b0e3fa1 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -14,6 +14,7 @@
"import": "./dist/index.es.js",
"require": "./dist/index.umd.js"
},
+ "./dist/*": "./dist/*",
"./style.css": "./dist/ui.css"
},
"scripts": {
diff --git a/packages/ui/src/components/Button.vue b/packages/ui/src/components/Button.vue
deleted file mode 100644
index 558d40a..0000000
--- a/packages/ui/src/components/Button.vue
+++ /dev/null
@@ -1,153 +0,0 @@
-
-
- {{ text }}
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/ui/src/components/Card.vue b/packages/ui/src/components/Card.vue
deleted file mode 100644
index 0aed80b..0000000
--- a/packages/ui/src/components/Card.vue
+++ /dev/null
@@ -1,236 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/ui/src/components/Loading.vue b/packages/ui/src/components/Loading.vue
deleted file mode 100644
index 8f17ed2..0000000
--- a/packages/ui/src/components/Loading.vue
+++ /dev/null
@@ -1,351 +0,0 @@
-
-
-
-
-
-
- {{ text }}
-
-
-
- {{ description }}
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/apps/frontend/src/components/LoadingCom/index.vue b/packages/ui/src/components/LoadingCom/index.vue
similarity index 86%
rename from apps/frontend/src/components/LoadingCom/index.vue
rename to packages/ui/src/components/LoadingCom/index.vue
index 1dab9aa..58f2b8a 100644
--- a/apps/frontend/src/components/LoadingCom/index.vue
+++ b/packages/ui/src/components/LoadingCom/index.vue
@@ -1,8 +1,13 @@
+
+
\ No newline at end of file
diff --git a/packages/ui/src/components/Modal.vue b/packages/ui/src/components/Modal.vue
deleted file mode 100644
index 2179d7c..0000000
--- a/packages/ui/src/components/Modal.vue
+++ /dev/null
@@ -1,357 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/ui/src/index.js b/packages/ui/src/index.js
index 54b90d6..e599a31 100644
--- a/packages/ui/src/index.js
+++ b/packages/ui/src/index.js
@@ -1,40 +1,34 @@
// UI组件库入口文件
-import Button from './components/Button.vue'
-import Card from './components/Card.vue'
-import Modal from './components/Modal.vue'
-import Loading from './components/Loading.vue'
+import LoadingCom from './components/LoadingCom/index.vue'
+import './style.css'
// 创建带有Dt前缀的组件
-const DtButton = Object.assign({}, Button, { name: 'DtButton' })
-const DtCard = Object.assign({}, Card, { name: 'DtCard' })
-const DtModal = Object.assign({}, Modal, { name: 'DtModal' })
-const DtLoading = Object.assign({}, Loading, { name: 'DtLoading' })
+const DtLoadingCom = {
+ ...LoadingCom,
+ name: 'DtLoadingCom',
+ install(app) {
+ app.component('DtLoadingCom', DtLoadingCom)
+ }
+}
// 组件列表
const components = [
- DtButton,
- DtCard,
- DtModal,
- DtLoading
+ DtLoadingCom
]
// 导出组件
export {
- DtButton,
- DtCard,
- DtModal,
- DtLoading,
- // 同时导出原名称以保持兼容性
- Button,
- Card,
- Modal,
- Loading
+ DtLoadingCom
}
// 批量注册组件的函数
export function registerComponents(app) {
components.forEach(component => {
- app.component(component.name, component)
+ if (component.install) {
+ app.use(component)
+ } else {
+ app.component(component.name, component)
+ }
})
}
diff --git a/packages/ui/src/style.css b/packages/ui/src/style.css
new file mode 100644
index 0000000..c05753d
--- /dev/null
+++ b/packages/ui/src/style.css
@@ -0,0 +1,19 @@
+/* Deotaland UI 组件库全局样式 */
+
+/* CSS 变量定义 */
+:root {
+ --dt-primary-color: #6B46C1;
+ --dt-secondary-color: #A78BFA;
+ --dt-text-color: #1F2937;
+ --dt-bg-color: #F3F4F6;
+ --dt-border-radius: 8px;
+ --dt-transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+/* 暗色主题变量 */
+html.dark {
+ --dt-primary-color: #A78BFA;
+ --dt-secondary-color: #6B46C1;
+ --dt-text-color: #F3F4F6;
+ --dt-bg-color: #1F2937;
+}
\ No newline at end of file
diff --git a/packages/ui/vite.config.js b/packages/ui/vite.config.js
index 1ba16e9..be9450e 100644
--- a/packages/ui/vite.config.js
+++ b/packages/ui/vite.config.js
@@ -1,6 +1,7 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
+import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
plugins: [vue()],
@@ -26,6 +27,10 @@ export default defineConfig({
globals: {
vue: 'Vue',
'element-plus': 'ElementPlus'
+ },
+ assetFileNames: (assetInfo) => {
+ if (assetInfo.name === 'style.css') return 'ui.css'
+ return assetInfo.name
}
}
}
diff --git a/packages/utils/src/api/FrontendDesigner/index.js b/packages/utils/src/api/FrontendDesigner/index.js
index c0a3662..724e705 100644
--- a/packages/utils/src/api/FrontendDesigner/index.js
+++ b/packages/utils/src/api/FrontendDesigner/index.js
@@ -4,6 +4,7 @@ import gemini from './gemini.js';
import meshy from './meshy.js';
import logistics from './logistics.js';
import user from './user.js';
+import permission from './permission.js';
export default {
...login,
...order,
@@ -11,4 +12,5 @@ export default {
...meshy,
...logistics,
...user,
+ ...permission,
};
\ No newline at end of file
diff --git a/packages/utils/src/api/FrontendDesigner/permission.js b/packages/utils/src/api/FrontendDesigner/permission.js
new file mode 100644
index 0000000..c7cd249
--- /dev/null
+++ b/packages/utils/src/api/FrontendDesigner/permission.js
@@ -0,0 +1,17 @@
+const permission = {
+ updateRole: {url: '/api-base/admin/role', method: 'PUT'},//修改角色
+ addRole: {url: '/api-base/admin/role', method: 'POST'},//新增角色
+ assignRoleToUser: {url: '/api-base/admin/role/assign/{userId}', method: 'POST'},//为用户分配角色
+ getRoleDetail: {url: '/api-base/admin/role/{roleId}', method: 'GET'},//查询角色详情
+ getRolesByUserId: {url: '/api-base/admin/role/user/{userId}', method: 'GET'},//根据用户ID查询角色列表
+ getRoleList: {url: '/api-base/admin/role/list', method: 'GET'},//查询角色列表
+ deleteRole: {url: '/api-base/admin/role/{roleIds}', method: 'DELETE'},//删除角色
+ updatePermission: {url: '/api-base/admin/permission', method: 'PUT'},//修改权限
+ addPermission: {url: '/api-base/admin/permission', method: 'POST'},//新增权限
+ assignPermissionToRole: {url: '/api-base/admin/permission/assign/{roleId}', method: 'POST'},//为角色分配权限
+ getPermissionDetail: {url: '/api-base/admin/permission/{permissionId}', method: 'GET'},//查询权限详情
+ deletePermission: {url: '/api-base/admin/permission/{permissionId}', method: 'DELETE'},//删除权限
+ getPermissionList: {url: '/api-base/admin/permission/list', method: 'GET'},//查询权限列表
+ getPermissionCodesByUserId: {url: '/api-base/admin/permission/codes/user/{userId}', method: 'GET'},//根据用户ID查询权限代码集合
+}
+export default permission;
\ No newline at end of file
diff --git a/packages/utils/src/api/FrontendDesigner/user.js b/packages/utils/src/api/FrontendDesigner/user.js
index 8707c9b..a86d782 100644
--- a/packages/utils/src/api/FrontendDesigner/user.js
+++ b/packages/utils/src/api/FrontendDesigner/user.js
@@ -1,8 +1,12 @@
const order = {
- getUsersList:{url:'/api-base/admin/user/list',method:'GET'},//分页查询C端用户列表,支持按昵称、邮箱、状态筛选
+ getUsersList:{url:'/api-base/admin/user/list',method:'GET',isLoading:true},//分页查询C端用户列表,支持按昵称、邮箱、状态筛选
getUsersinvites:{url:'/api-base/admin/user/USERID/invites',method:'GET'},//分页查询指定用户邀请的人列表
- updateUserStatus:{url:'/api-base/admin/user/USERID/status',method:'PUT'},//修改用户状态(active/disabled)
- updateUserName:{url:'/api-base/admin/user/USERID',method:'PUT'},//编辑用户基本信息(昵称)
- getUserDetail:{url:'/api-base/admin/user/USERID',method:'GET'},//根据用户ID查询用户详情
+ updateUserStatus:{url:'/api-base/admin/user/USERID/status',method:'PUT',isLoading:true},//修改用户状态(active/disabled)
+ updateUserName:{url:'/api-base/admin/user/USERID',method:'PUT',isLoading:true},//编辑用户基本信息(昵称)
+ getUserDetail:{url:'/api-base/admin/user/USERID',method:'GET',isLoading:true},//根据用户ID查询用户详情
+ changeRole:{url:'/api-base/admin/user/USERID/role',method:'PUT',isLoading:true},//变更用户角色(候补会员/免费会员/达人),候补升级时自动赠送300积分
+ getInviteCodeList:{url:'/api-base/admin/invite-code/list',method:'GET'},//分页查询邀请码列表
+ generateInviteCode:{url:'/api-base/admin/invite-code/generate',method:'POST',isLoading:true},//为用户生成邀请码(支持批量生成)
+ deleteInviteCode:{url:'/api-base/admin/invite-code/CODEID',method:'DELETE',isLoading:true},//根据邀请码ID删除邀请码
}
export default order;
\ No newline at end of file
diff --git a/packages/utils/src/api/frontend/user.js b/packages/utils/src/api/frontend/user.js
index 6739c14..6e819c3 100644
--- a/packages/utils/src/api/frontend/user.js
+++ b/packages/utils/src/api/frontend/user.js
@@ -1,5 +1,7 @@
const login = {
MODEL_LIMITS:{url:'/api-core/front/user/model-limits',method:'GET'},// 模型限制
USER_STATISTICS:{url:'/api-core/front/user/statistics',method:'GET'},// 用户统计
+ INVITE_CODES:{url:'/api-base/user/invite/codes',method:'GET'},// 返回当前用户的邀请码列表及使用状态
+ INVITE_RECORDS:{url:'/api-base/user/invite/records',method:'GET'},// 返回邀请的用户列表,包含奖励明细
}
export default login;
diff --git a/packages/utils/src/index.js b/packages/utils/src/index.js
index 6ffd87b..26575f1 100644
--- a/packages/utils/src/index.js
+++ b/packages/utils/src/index.js
@@ -11,6 +11,7 @@ import * as dateUtils from './utils/date.js'
import * as fileUtils from './utils/file.js'
import * as validateUtils from './utils/validate.js'
import * as formatUtils from './utils/format.js'
+import * as environmentUtils from './utils/environment.js'
import { request as requestUtils } from './utils/request.js'
import * as adminApi from './api/FrontendDesigner/index.js';
import * as clientApi from './api/frontend/index.js';
@@ -31,6 +32,7 @@ const deotalandUtils = {
file: fileUtils,
validate: validateUtils,
format: formatUtils,
+ environment: environmentUtils,
request: requestUtils,
FileServer,
adminApi,
@@ -64,6 +66,7 @@ export {
FileServer,
validateUtils,
formatUtils,
+ environmentUtils,
requestUtils,
adminApi,
clientApi,
diff --git a/packages/utils/src/servers/fileserver.js b/packages/utils/src/servers/fileserver.js
index 1262c29..bc19a9f 100644
--- a/packages/utils/src/servers/fileserver.js
+++ b/packages/utils/src/servers/fileserver.js
@@ -297,15 +297,15 @@ export class FileServer {
let file = await this.fileToBlob(url);//将文件或者base64文件转为blob对象
// 检查文件大小,如果超过10MB则进行压缩
const maxSizeInBytes = 10 * 1024 * 1024; // 10MB
- if (file.size > maxSizeInBytes) {
+ // if (file.size > maxSizeInBytes) {
+ if (true) {
try {
- console.log(`文件大小为 ${(file.size / 1024 / 1024).toFixed(2)}MB,超过10MB限制,开始压缩...`);
-
+ console.log(`文件大小为 ${(file.size / 1024 / 1024).toFixed(2)}MB,开始压缩...`);
// 将Blob转换为File对象以便压缩
const fileName = this.extractFileName(url);
const fileObject = new File([file], fileName, { type: file.type });
- const compressedFile = await this.compressFile(fileObject, 0.7); // 使用0.7质量压缩
+ const compressedFile = await this.compressFile(fileObject, 0.2); // 使用0.2质量压缩
if (compressedFile && compressedFile.length < file.size) {
// 将压缩后的base64转换回Blob
const response = await fetch(compressedFile);
@@ -317,7 +317,6 @@ export class FileServer {
console.warn('文件压缩失败,使用原文件上传:', error.message);
}
}
-
// const formData = new FormData();
// 从URL中提取文件名,如果没有则使用默认文件名
const fileName = this.extractFileName(url);
diff --git a/packages/utils/src/servers/giminiserver.js b/packages/utils/src/servers/giminiserver.js
index 8ce103a..102f9a8 100644
--- a/packages/utils/src/servers/giminiserver.js
+++ b/packages/utils/src/servers/giminiserver.js
@@ -169,7 +169,6 @@ export class GiminiServer extends FileServer {
if (images.length > 5) {
reject(`参考图片数量不能超过5张`);
}
-
// 处理多个参考图片
const imageParts = await Promise.all(images.map(async image =>{
return await this.dataUrlToGenerativePart(image,'url');
@@ -186,7 +185,7 @@ export class GiminiServer extends FileServer {
// "aspectRatio": "9:16"
// },
"aspect_ratio": "16:9",
- "model": "gemini-3-pro-image-preview",//models/gemini-3-pro-image-preview/"gemini-2.5-flash-image",
+ "model": "doubao",//models/gemini-3-pro-image-preview/"gemini-2.5-flash-image"/"doubao",
"location": "global",
"vertexai": true,
...config,
@@ -195,6 +194,9 @@ export class GiminiServer extends FileServer {
{ text: promptStr }
]
}
+ if(params.model=='doubao'){
+ params.aspect_ratio = '768x1344';
+ }
const requestUrl = this.RULE=='admin'?adminApi.default.GENERATE_IMAGE_ADMIN:clientApi.default.GENERATE_IMAGE;
const response = await requestUtils.common(requestUrl, params);
// const response = {"code":0,"message":"","success":true,"data":{"id":2177,"message":"任务已提交,正在处理"}}
diff --git a/packages/utils/src/utils/environment.js b/packages/utils/src/utils/environment.js
new file mode 100644
index 0000000..1971244
--- /dev/null
+++ b/packages/utils/src/utils/environment.js
@@ -0,0 +1,262 @@
+/**
+ * 环境检测工具函数
+ * 用于判断当前运行环境是国内还是国外
+ */
+
+// 缓存地理位置信息,避免重复请求
+const locationCache = {
+ data: null,
+ timestamp: 0,
+ ttl: 24 * 60 * 60 * 1000 // 24小时缓存
+};
+
+/**
+ * 获取浏览器语言
+ */
+export function getBrowserLanguage() {
+ return navigator.language || navigator.userLanguage || 'en';
+}
+
+/**
+ * 判断是否为中文语言环境
+ */
+export function isChineseLanguage() {
+ const language = getBrowserLanguage();
+ return language.startsWith('zh') || language.includes('cn');
+}
+
+/**
+ * 获取当前时区
+ */
+export function getTimezone() {
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
+}
+
+/**
+ * 判断是否为中国时区
+ */
+export function isChinaTimezone() {
+ const timezone = getTimezone();
+ const chinaTimezones = [
+ 'Asia/Shanghai',
+ 'Asia/Chongqing',
+ 'Asia/Beijing',
+ 'Asia/Harbin',
+ 'Asia/Urumqi'
+ ];
+ return chinaTimezones.includes(timezone);
+}
+
+/**
+ * 通过IP获取地理位置信息
+ * @param {Object} options - 配置选项
+ * @param {number} options.timeout - 请求超时时间(毫秒)
+ * @param {string} options.api - 使用的API服务
+ * @returns {Promise
} 地理位置信息
+ */
+export async function getLocationByIP(options = {}) {
+ const { timeout = 3000, api = 'ipapi' } = options;
+
+ const apis = {
+ ipapi: {
+ url: 'https://ipapi.co/json/',
+ parser: (data) => ({
+ country: data.country_name,
+ countryCode: data.country_code,
+ city: data.city,
+ region: data.region,
+ ip: data.ip,
+ isChina: data.country_code === 'CN'
+ })
+ },
+ ipinfo: {
+ url: 'https://ipinfo.io/json',
+ parser: (data) => ({
+ country: data.country,
+ countryCode: data.country,
+ city: data.city,
+ region: data.region,
+ ip: data.ip,
+ isChina: data.country === 'CN'
+ })
+ },
+ freegeoip: {
+ url: 'https://freegeoip.app/json/',
+ parser: (data) => ({
+ country: data.country_name,
+ countryCode: data.country_code,
+ city: data.city,
+ region: data.region_name,
+ ip: data.ip,
+ isChina: data.country_code === 'CN'
+ })
+ }
+ };
+
+ const selectedApi = apis[api] || apis.ipapi;
+
+ try {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
+
+ const response = await fetch(selectedApi.url, {
+ signal: controller.signal,
+ headers: {
+ 'Accept': 'application/json'
+ }
+ });
+
+ clearTimeout(timeoutId);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return selectedApi.parser(data);
+ } catch (error) {
+ console.warn('IP地理位置检测失败:', error.message);
+ return null;
+ }
+}
+
+/**
+ * 综合判断环境是否为国内
+ * @param {Object} options - 配置选项
+ * @returns {Promise} 环境检测结果
+ */
+export async function detectEnvironment(options = {}) {
+ const { useCache = true, ipDetection = true } = options;
+
+ // 检查缓存
+ if (useCache && locationCache.data && (Date.now() - locationCache.timestamp) < locationCache.ttl) {
+ return locationCache.data;
+ }
+
+ // 1. 获取浏览器语言(最快,无网络请求)
+ const browserLanguage = getBrowserLanguage();
+ const isChineseLanguageEnv = isChineseLanguage();
+
+ // 2. 获取时区(快速,无网络请求)
+ const timezone = getTimezone();
+ const isChinaTimezoneEnv = isChinaTimezone();
+
+ // 3. IP地理位置判断(最准确,但需要网络请求)
+ let ipLocationInfo = null;
+ let isChinaIP = false;
+
+ if (ipDetection) {
+ ipLocationInfo = await getLocationByIP(options);
+ isChinaIP = ipLocationInfo ? ipLocationInfo.isChina : false;
+ }
+
+ // 4. 综合决策
+ const isDomestic = isChinaIP || (isChineseLanguageEnv && isChinaTimezoneEnv);
+
+ // 5. 确定置信度
+ let confidence = 'low';
+ if (isChinaIP) {
+ confidence = 'high';
+ } else if (isChineseLanguageEnv && isChinaTimezoneEnv) {
+ confidence = 'medium';
+ }
+
+ const result = {
+ isDomestic,
+ confidence,
+ methods: {
+ ip: isChinaIP,
+ language: isChineseLanguageEnv,
+ timezone: isChinaTimezoneEnv
+ },
+ browserLanguage,
+ timezone,
+ ipLocationInfo
+ };
+
+ // 更新缓存
+ if (useCache) {
+ locationCache.data = result;
+ locationCache.timestamp = Date.now();
+ }
+
+ return result;
+}
+
+/**
+ * 快速判断是否为国内环境(仅使用本地信息,无网络请求)
+ * @returns {Object} 快速检测结果
+ */
+export function quickDetectEnvironment() {
+ const browserLanguage = getBrowserLanguage();
+ const isChineseLanguageEnv = isChineseLanguage();
+ const timezone = getTimezone();
+ const isChinaTimezoneEnv = isChinaTimezone();
+
+ const isDomestic = isChineseLanguageEnv && isChinaTimezoneEnv;
+
+ return {
+ isDomestic,
+ confidence: 'medium',
+ methods: {
+ language: isChineseLanguageEnv,
+ timezone: isChinaTimezoneEnv
+ },
+ browserLanguage,
+ timezone
+ };
+}
+
+/**
+ * 清除地理位置缓存
+ */
+export function clearLocationCache() {
+ locationCache.data = null;
+ locationCache.timestamp = 0;
+}
+
+/**
+ * 设置缓存过期时间
+ * @param {number} ttl - 缓存时间(毫秒)
+ */
+export function setCacheTTL(ttl) {
+ locationCache.ttl = ttl;
+}
+
+/**
+ * 获取当前环境信息(同步版本,仅使用本地信息)
+ */
+export function getEnvironmentInfo() {
+ return {
+ browserLanguage: getBrowserLanguage(),
+ timezone: getTimezone(),
+ isChineseLanguage: isChineseLanguage(),
+ isChinaTimezone: isChinaTimezone(),
+ userAgent: navigator.userAgent,
+ platform: navigator.platform,
+ language: navigator.language,
+ languages: navigator.languages || []
+ };
+}
+
+/**
+ * 检查是否为移动设备
+ */
+export function isMobileDevice() {
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
+}
+
+/**
+ * 检查是否为桌面设备
+ */
+export function isDesktopDevice() {
+ return !isMobileDevice();
+}
+
+/**
+ * 检查是否为平板设备
+ */
+export function isTabletDevice() {
+ const userAgent = navigator.userAgent.toLowerCase();
+ return /ipad|android(?!.*mobile)/i.test(userAgent);
+}
\ No newline at end of file
diff --git a/packages/utils/src/utils/request.js b/packages/utils/src/utils/request.js
index e947201..2b9099e 100644
--- a/packages/utils/src/utils/request.js
+++ b/packages/utils/src/utils/request.js
@@ -58,6 +58,9 @@ service.interceptors.response.use(
if(res.code&&res.code==200){
return res;
}
+ if(res.code==1004){//重定向登录
+ window?.Redirectlogin()
+ }
if(!res.success){
window?.setElMessage({
message: res.message,
@@ -92,7 +95,6 @@ service.interceptors.response.use(
// 请求已发送但没有收到响应
message = '网络连接失败,请检查网络';
}
-
console.error(message);
return Promise.reject(error);
}
@@ -186,6 +188,7 @@ export const request = {
requestConfig.data = data;
}
if(config.isLoading&&window.setElLoading){
+
closeMethods = window.setElLoading(config.isqp)
}
return service(requestConfig);