✨ add user dashboard page
10
components.d.ts
vendored
@@ -13,6 +13,7 @@ declare module 'vue' {
|
||||
ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||
AButton: typeof import('ant-design-vue/es')['Button']
|
||||
ACard: typeof import('ant-design-vue/es')['Card']
|
||||
ACardMeta: typeof import('ant-design-vue/es')['CardMeta']
|
||||
ACascader: typeof import('ant-design-vue/es')['Cascader']
|
||||
AccountSetting: typeof import('./src/views/User/AccountSetting/AccountSetting.vue')['default']
|
||||
AccountSettingHome: typeof import('./src/views/User/AccountSetting/components/AccountSettingHome/AccountSettingHome.vue')['default']
|
||||
@@ -20,6 +21,7 @@ declare module 'vue' {
|
||||
AccountSettingSidebar: typeof import('./src/views/User/AccountSetting/components/AccountSettingSidebar/AccountSettingSidebar.vue')['default']
|
||||
AccountSettingStorage: typeof import('./src/views/User/AccountSetting/components/AccountSettingStorage/AccountSettingStorage.vue')['default']
|
||||
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
||||
ACol: typeof import('ant-design-vue/es')['Col']
|
||||
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||
ADrawer: typeof import('ant-design-vue/es')['Drawer']
|
||||
@@ -53,6 +55,7 @@ declare module 'vue' {
|
||||
ARadio: typeof import('ant-design-vue/es')['Radio']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
||||
ARow: typeof import('ant-design-vue/es')['Row']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||
ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
|
||||
@@ -91,6 +94,7 @@ declare module 'vue' {
|
||||
DownOutlined: typeof import('@ant-design/icons-vue')['DownOutlined']
|
||||
DynamicTitle: typeof import('./src/components/DynamicTitle/DynamicTitle.vue')['default']
|
||||
EditOutlined: typeof import('@ant-design/icons-vue')['EditOutlined']
|
||||
EllipsisOutlined: typeof import('@ant-design/icons-vue')['EllipsisOutlined']
|
||||
EyeInvisibleOutlined: typeof import('@ant-design/icons-vue')['EyeInvisibleOutlined']
|
||||
EyeOutlined: typeof import('@ant-design/icons-vue')['EyeOutlined']
|
||||
FileImageOutlined: typeof import('@ant-design/icons-vue')['FileImageOutlined']
|
||||
@@ -98,6 +102,8 @@ declare module 'vue' {
|
||||
Folder: typeof import('./src/components/Folder/Folder.vue')['default']
|
||||
ForgetPage: typeof import('./src/views/Forget/ForgetPage.vue')['default']
|
||||
GradientText: typeof import('./src/components/MyUI/GradientText/GradientText.vue')['default']
|
||||
Heatmap: typeof import('./src/components/Heatmap/Heatmap.vue')['default']
|
||||
HeatmapPro: typeof import('./src/components/HeatmapPro/HeatmapPro.vue')['default']
|
||||
ImageShare: typeof import('./src/views/Share/ImageShare/ImageShare.vue')['default']
|
||||
ImageToolbar: typeof import('./src/components/ImageToolbar/ImageToolbar.vue')['default']
|
||||
ImageUpload: typeof import('./src/components/ImageUpload/ImageUpload.vue')['default']
|
||||
@@ -106,6 +112,7 @@ declare module 'vue' {
|
||||
LeftOutlined: typeof import('@ant-design/icons-vue')['LeftOutlined']
|
||||
LinkOutlined: typeof import('@ant-design/icons-vue')['LinkOutlined']
|
||||
LoadingGraphic: typeof import('./src/components/LoadingGraphic/LoadingGraphic.vue')['default']
|
||||
LoadingOutlined: typeof import('@ant-design/icons-vue')['LoadingOutlined']
|
||||
LocationAlbumDetail: typeof import('./src/views/Album/LocationAlbum/LocationAlbumDetail.vue')['default']
|
||||
LocationAlbumIndex: typeof import('./src/views/Album/LocationAlbum/LocationAlbumIndex.vue')['default']
|
||||
LocationAlbumList: typeof import('./src/views/Album/LocationAlbum/LocationAlbumList.vue')['default']
|
||||
@@ -136,6 +143,7 @@ declare module 'vue' {
|
||||
Rate: typeof import('./src/components/MyUI/Rate/Rate.vue')['default']
|
||||
RecentUpload: typeof import('./src/views/Photograph/RecentUpload/RecentUpload.vue')['default']
|
||||
RecyclingBin: typeof import('./src/views/RecyclingBin/RecyclingBin.vue')['default']
|
||||
RedoOutlined: typeof import('@ant-design/icons-vue')['RedoOutlined']
|
||||
ReplyInput: typeof import('./src/components/CommentReply/src/ReplyInput/ReplyInput.vue')['default']
|
||||
ReplyList: typeof import('./src/components/CommentReply/src/ReplyList/ReplyList.vue')['default']
|
||||
ReplyReply: typeof import('./src/components/CommentReply/src/ReplyReplyInput/ReplyReply.vue')['default']
|
||||
@@ -145,12 +153,14 @@ declare module 'vue' {
|
||||
SearchOutlined: typeof import('@ant-design/icons-vue')['SearchOutlined']
|
||||
SearchResult: typeof import('./src/views/Photograph/SearchResult/SearchResult.vue')['default']
|
||||
SendOutlined: typeof import('@ant-design/icons-vue')['SendOutlined']
|
||||
SettingOutlined: typeof import('@ant-design/icons-vue')['SettingOutlined']
|
||||
SharePhoneUpload: typeof import('./src/views/Phone/SharePhoneUpload/SharePhoneUpload.vue')['default']
|
||||
ShareSidebar: typeof import('./src/views/Share/ShareViewList/ShareSidebar.vue')['default']
|
||||
ShareUpload: typeof import('./src/views/Share/ImageShare/ShareUpload.vue')['default']
|
||||
ShareViewList: typeof import('./src/views/Share/ShareViewList/index.vue')['default']
|
||||
Spin: typeof import('./src/components/MyUI/Spin/Spin.vue')['default']
|
||||
StarButton: typeof import('./src/components/StarButton/StarButton.vue')['default']
|
||||
StorageCard: typeof import('./src/views/User/AccountSetting/components/AccountSettingStorage/StorageCard.vue')['default']
|
||||
TabletOutlined: typeof import('@ant-design/icons-vue')['TabletOutlined']
|
||||
ThingAlbumDetail: typeof import('./src/views/Album/ThingAlbum/ThingAlbumDetail.vue')['default']
|
||||
ThingAlbumIndex: typeof import('./src/views/Album/ThingAlbum/ThingAlbumIndex.vue')['default']
|
||||
|
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@alova/adapter-axios": "^2.0.13",
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@fcli/vue-calendar-map": "^1.0.2",
|
||||
"@intlify/eslint-plugin-vue-i18n": "^3.2.0",
|
||||
"@mediapipe/face_detection": "^0.4.1646425229",
|
||||
"@mediapipe/face_mesh": "^0.4.1633559619",
|
||||
@@ -55,6 +56,7 @@
|
||||
"jszip": "^3.10.1",
|
||||
"less": "^4.2.2",
|
||||
"localforage": "^1.10.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"nsfwjs": "^4.2.1",
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import {service} from "@/utils/alova/service.ts";
|
||||
import {service} from "@/utils/alova/service_app.ts";
|
||||
|
||||
export const uploadImage = (data: any) => {
|
||||
return service.Post('/api/auth/phone/upload', {
|
||||
@@ -26,3 +26,37 @@ export const sharePhoneUploadApi = (data: any) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param formData
|
||||
* @param headers
|
||||
*/
|
||||
export const uploadFile = (formData: any, headers: any) => {
|
||||
return service.Post('/api/auth/storage/uploads', formData, {
|
||||
headers: headers,
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
signature: false,
|
||||
},
|
||||
name: "upload-file",
|
||||
});
|
||||
};
|
||||
|
||||
export const albumListApi = (type: number, sort: boolean, headers: any) => {
|
||||
return service.Post('/api/auth/storage/album/list', {
|
||||
type: type,
|
||||
sort: sort,
|
||||
}, {
|
||||
headers: headers,
|
||||
cacheFor: {
|
||||
expire: 60 * 60 * 24 * 7,
|
||||
mode: "restore",
|
||||
},
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
signature: false,
|
||||
},
|
||||
hitSource: ["create-album", "rename-album", "delete-album", "album-share"],
|
||||
});
|
||||
};
|
||||
|
@@ -350,6 +350,7 @@ export const getStorageConfigListApi = () => {
|
||||
signature: false,
|
||||
},
|
||||
name: "storage-config-list",
|
||||
hitSource: ["delete-storage-config", "add-storage-config"]
|
||||
});
|
||||
};
|
||||
/**
|
||||
@@ -509,3 +510,54 @@ export const downloadAlbumImagesApi = (id: number, provider: string, bucket: str
|
||||
name: "download-album-images",
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 获取用户存储配置
|
||||
*/
|
||||
export const listUserStorageConfigApi = () => {
|
||||
return service.Post('/api/auth/storage/user/storage/list', {}, {
|
||||
cacheFor: {
|
||||
expire: 60 * 60 * 24 * 7,
|
||||
mode: "restore",
|
||||
},
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
signature: false,
|
||||
},
|
||||
name: "list-user-storage-config",
|
||||
hitSource: ["delete-storage-config", "add-storage-config"],
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 创建存储配置
|
||||
* @param id
|
||||
* @param provider
|
||||
* @param bucket
|
||||
*/
|
||||
export const deleteStorageConfigApi = (id: number, provider: string, bucket: string) => {
|
||||
return service.Post('/api/auth/storage/config/delete', {
|
||||
id: id,
|
||||
provider: provider,
|
||||
bucket: bucket,
|
||||
}, {
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
signature: false,
|
||||
},
|
||||
name: "delete-storage-config",
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 创建存储配置
|
||||
* @param params
|
||||
*/
|
||||
export const addStorageConfigApi = (params: any) => {
|
||||
return service.Post('/api/auth/storage/config/add', {
|
||||
...params,
|
||||
}, {
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
signature: false,
|
||||
},
|
||||
name: "add-storage-config",
|
||||
});
|
||||
};
|
||||
|
@@ -76,10 +76,6 @@ html {
|
||||
}
|
||||
}
|
||||
|
||||
// 取消antd table 最后一行的边框
|
||||
.ant-table-wrapper .ant-table:not(.ant-table-bordered) .ant-table-tbody > tr:last-child > td {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
// 空白内容样式
|
||||
.empty-content {
|
||||
@@ -90,7 +86,3 @@ html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
//:not(button) > svg:not([color]) {
|
||||
// color: var(--text-color) !important;
|
||||
//}
|
||||
|
1
src/assets/svgs/account_security.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1741150116560" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18568" width="200" height="200"><path d="M512 231.575273a729.774545 729.774545 0 0 1-109.882182 55.924363 482.059636 482.059636 0 0 1-126.417454 31.115637v239.848727c0 115.397818 167.773091 247.330909 236.299636 247.330909S748.299636 673.861818 748.299636 558.464V318.615273a476.555636 476.555636 0 0 1-126.033454-31.115637A719.127273 719.127273 0 0 1 512 231.575273z m176.046545 218.577454L496.244364 669.521455l-125.672728-105.89091 39.389091-45.288727L488.727273 586.426182l152.808727-175.255273z" fill="#4E8CEE" p-id="18569"></path><path d="M512 0a512 512 0 1 0 512 512A512 512 0 0 0 512 0z m0 866.443636c-103.563636 0-296.168727-157.533091-296.168727-306.804363V261.504h29.533091a415.499636 415.499636 0 0 0 134.295272-29.090909 584.448 584.448 0 0 0 114.606546-60.648727L512 157.533091l17.722182 12.602182a581.306182 581.306182 0 0 0 114.932363 62.231272 415.895273 415.895273 0 0 0 133.899637 28.753455h29.533091v296.948364C808.157091 708.910545 615.563636 866.443636 512 866.443636z" fill="#B4D1FF" p-id="18570"></path></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
src/assets/svgs/deleted-circle.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1741079927040" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5318" width="200" height="200"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#FDEBED" p-id="5319"></path><path d="M729.6 384H294.4c-7.68 0-12.8-5.12-12.8-12.8v-25.6c0-7.68 5.12-12.8 12.8-12.8h115.2v-25.6c0-14.08 11.52-25.6 25.6-25.6h153.6c14.08 0 25.6 11.52 25.6 25.6v25.6h115.2c7.68 0 12.8 5.12 12.8 12.8v25.6c0 7.68-5.12 12.8-12.8 12.8z m-371.2 38.4h307.2c28.16 0 51.2 23.04 51.2 51.2v217.6c0 28.16-23.04 51.2-51.2 51.2H358.4c-28.16 0-51.2-23.04-51.2-51.2V473.6c0-28.16 23.04-51.2 51.2-51.2z m192 243.2c0 7.68 5.12 12.8 12.8 12.8s12.8-5.12 12.8-12.8V537.6c0-7.68-5.12-12.8-12.8-12.8s-12.8 5.12-12.8 12.8v128z m-102.4 0c0 7.68 5.12 12.8 12.8 12.8s12.8-5.12 12.8-12.8V537.6c0-7.68-5.12-12.8-12.8-12.8s-12.8 5.12-12.8 12.8v128z" fill="#EC3A4E" p-id="5320"></path></svg>
|
After Width: | Height: | Size: 913 B |
1
src/assets/svgs/email_security.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1741150908905" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23961" width="200" height="200"><path d="M922.88 164.522667a42.666667 42.666667 0 0 0-35.84-8.533334 116.906667 116.906667 0 0 1-93.866667-19.626666 42.666667 42.666667 0 0 0-50.346666 0 116.906667 116.906667 0 0 1-93.866667 19.626666 42.666667 42.666667 0 0 0-51.626667 41.813334v141.226666a197.12 197.12 0 0 0 78.506667 157.866667l66.986667 49.493333a42.666667 42.666667 0 0 0 50.346666 0l66.986667-49.493333a197.12 197.12 0 0 0 78.506667-157.866667v-141.226666a42.666667 42.666667 0 0 0-15.786667-33.28zM853.333333 339.029333a111.36 111.36 0 0 1-42.666666 89.173334l-42.666667 29.866666-42.666667-30.72a111.36 111.36 0 0 1-42.666666-89.173333v-93.44a199.253333 199.253333 0 0 0 85.333333-23.04 199.253333 199.253333 0 0 0 85.333333 23.04z m42.666667 256a42.666667 42.666667 0 0 0-42.666667 42.666667v128a42.666667 42.666667 0 0 1-42.666666 42.666667H213.333333a42.666667 42.666667 0 0 1-42.666666-42.666667v-407.04l250.88 250.88a127.744 127.744 0 0 0 181.76-0.853333l-29.866667-30.293334-31.573333-29.013333a42.666667 42.666667 0 0 1-59.733334 0l-251.306666-250.88H469.333333a42.666667 42.666667 0 0 0 0-85.333333H213.333333a128 128 0 0 0-128 128v426.666666a128 128 0 0 0 128 128h597.333334a128 128 0 0 0 128-128v-128a42.666667 42.666667 0 0 0-42.666667-42.666666z" fill="#0070FF" p-id="23962"></path></svg>
|
After Width: | Height: | Size: 1.4 KiB |
1
src/assets/svgs/file-size.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1741144027158" class="icon" viewBox="0 0 1032 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10401" width="200" height="200"><path d="M779.491772 953.616478H254.137244a177.907534 177.907534 0 0 1-177.678272-177.678271V249.437367a177.907534 177.907534 0 0 1 177.678272-177.678272H779.491772a177.907534 177.907534 0 0 1 177.678272 177.678272v526.50084A177.907534 177.907534 0 0 1 779.491772 953.616478zM254.137244 152.000896a97.551103 97.551103 0 0 0-97.436472 97.436471v526.50084a97.551103 97.551103 0 0 0 97.436472 97.436471H779.491772a97.551103 97.551103 0 0 0 97.436472-97.436471V249.437367a97.551103 97.551103 0 0 0-97.436472-97.436471z" fill="#FDD345" p-id="10402"></path><path d="M483.858054 751.407142h-66.944587L285.431546 608.118213h-1.146311v143.288929h-47.801187v-441.3299h47.801187v279.699988h1.146311l125.177208-136.41106h62.588604L334.837569 597.113624zM581.638419 708.305832h-1.146311v43.10131h-47.686556v-441.3299h47.686556v195.675361h1.146311a112.911676 112.911676 0 0 1 103.168029-59.378932 109.243479 109.243479 0 0 1 89.756185 40.006269q32.440613 40.006269 32.440613 107.294749 0 74.854136-36.338072 119.789545a121.165118 121.165118 0 0 1-99.499832 44.935408 98.009627 98.009627 0 0 1-89.526923-50.09381z m-1.146311-120.248069v41.611105a88.609874 88.609874 0 0 0 23.957909 62.703235A85.629464 85.629464 0 0 0 733.639315 685.03571q24.645696-33.1284 24.645696-91.704914a120.133438 120.133438 0 0 0-22.926229-77.949177 76.115079 76.115079 0 0 0-62.244711-28.199261 84.597783 84.597783 0 0 0-67.28848 28.657785 106.377701 106.377701 0 0 0-25.333483 72.21762z" fill="#FDD345" p-id="10403"></path></svg>
|
After Width: | Height: | Size: 1.6 KiB |
1
src/assets/svgs/image-icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1741144431170" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14627" width="200" height="200"><path d="M739.9936 878.8992H364.7488c-113.1008 0-204.8-91.6992-204.8-204.8V417.7408c0-113.1008 91.6992-204.8 204.8-204.8h375.2448c113.1008 0 204.8 91.6992 204.8 204.8v256.3584c0 113.1008-91.6992 204.8-204.8 204.8z" fill="#FFF7E6" p-id="14628"></path><path d="M944.7936 444.928c-14.1312 0-25.6-11.4688-25.6-25.6v-10.24c0-14.1312 11.4688-25.6 25.6-25.6s25.6 11.4688 25.6 25.6v10.24c0 14.1312-11.4688 25.6-25.6 25.6z" fill="#44454A" p-id="14629"></path><path d="M290.7648 878.8992h443.5456c116.224 0 210.4832-94.2592 210.4832-210.4832v-123.904c-61.44-55.296-151.0912-95.1808-284.16-76.7488-160.7168 22.272-249.9072 137.0112-296.2432 263.9872-80.128-99.1744-208.7936-97.5872-284.16-86.0672v22.6816c0.0512 116.2752 94.2592 210.5344 210.5344 210.5344z" fill="#FD973F" p-id="14630"></path><path d="M944.7936 464.896c-14.1312 0-25.6 11.4688-25.6 25.6v2.2528c-72.7552-47.4112-160.6144-64.3584-262.0416-50.3296-135.936 18.8416-239.5648 102.5536-301.6192 242.944-77.056-68.864-179.0464-75.0592-249.6512-68.5568V352.9728c0-100.352 81.664-182.016 182.016-182.016h449.28c95.3856 0 175.1552 74.4448 181.6064 169.472a25.61536 25.61536 0 0 0 27.2896 23.808 25.61536 25.61536 0 0 0 23.808-27.2896c-8.2432-121.8048-110.4896-217.2416-232.704-217.2416H287.8976c-128.6144 0-233.216 104.6016-233.216 233.216v318.3104c0 128.6144 104.6016 233.216 233.216 233.216h449.3312c128.6144 0 233.216-104.6016 233.216-233.216V490.496c-0.0512-14.1312-11.52-25.6-25.6512-25.6zM290.7648 853.2992c-101.9392 0-184.8832-82.944-184.8832-184.8832v-0.1536c68.352-7.0656 173.0048-1.536 238.6432 79.616a25.66656 25.66656 0 0 0 24.1152 9.1648 25.7024 25.7024 0 0 0 19.8656-16.4864c53.3504-146.2272 146.1248-229.4784 275.712-247.3984 102.1952-14.1312 187.904 7.0656 255.0272 63.0784v112.1792c0 101.9392-82.944 184.8832-184.8832 184.8832H290.7648z" fill="#44454A" p-id="14631"></path><path d="M299.9296 369.8688m-79.4624 0a79.4624 79.4624 0 1 0 158.9248 0 79.4624 79.4624 0 1 0-158.9248 0Z" fill="#FD973F" p-id="14632"></path><path d="M299.9296 474.9312c-57.9072 0-105.0624-47.104-105.0624-105.0624 0-57.9072 47.104-105.0624 105.0624-105.0624 57.9072 0 105.0624 47.104 105.0624 105.0624s-47.1552 105.0624-105.0624 105.0624z m0-158.8736c-29.696 0-53.8624 24.1664-53.8624 53.8624s24.1664 53.8624 53.8624 53.8624S353.792 399.616 353.792 369.92c0-29.7472-24.1664-53.8624-53.8624-53.8624z" fill="#44454A" p-id="14633"></path></svg>
|
After Width: | Height: | Size: 2.5 KiB |
1
src/assets/svgs/login_security.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1741152058412" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="43893" width="200" height="200"><path d="M793.144889 220.302222q-8.049778 0.398222-16.213333 0.398222a353.735111 353.735111 0 0 1-241.009778-94.378666 47.530667 47.530667 0 0 0-64.256 0 353.735111 353.735111 0 0 1-241.009778 94.378666c-3.413333 0-6.798222 0-10.183111-0.170666a48.355556 48.355556 0 0 0-50.119111 48.355555v295.310223c0 178.232889 136.533333 230.883556 313.656889 324.266666a48.981333 48.981333 0 0 0 45.653333 0c25.6-13.454222 50.062222-26.026667 73.585778-38.257778a145.351111 145.351111 0 0 1-11.690667-35.413333 28.444444 28.444444 0 0 0 0-55.523555 146.176 146.176 0 0 1 47.786667-82.773334 28.444444 28.444444 0 0 0 48.042666-27.790222 147.143111 147.143111 0 0 1 95.544889 0 28.444444 28.444444 0 0 0 31.544889 36.494222 243.911111 243.911111 0 0 0 28.842667-121.002666V268.657778a48.184889 48.184889 0 0 0-50.176-48.355556z m-231.054222 239.985778a66.417778 66.417778 0 0 1-18.517334 18.545778v135.736889a36.750222 36.750222 0 0 1-73.500444 0v-135.736889a66.389333 66.389333 0 1 1 92.017778-18.545778z" fill="#2172F6" p-id="43894"></path><path d="M773.006222 903.480889a23.978667 23.978667 0 0 1 40.448-23.409778 122.88 122.88 0 0 0 40.220445-69.660444 23.978667 23.978667 0 0 1 0-46.734223 122.908444 122.908444 0 0 0-40.220445-69.660444 23.978667 23.978667 0 0 1-40.448-23.409778 123.790222 123.790222 0 0 0-80.384 0 23.978667 23.978667 0 0 1-40.448 23.409778 123.050667 123.050667 0 0 0-40.220444 69.660444 23.978667 23.978667 0 0 1 0 46.734223 123.079111 123.079111 0 0 0 40.220444 69.660444 23.978667 23.978667 0 0 1 40.448 23.409778 123.221333 123.221333 0 0 0 80.384 0z m-92.928-116.423111a52.736 52.736 0 1 1 52.764445 52.764444 52.764444 52.764444 0 0 1-52.764445-52.764444z" fill="#2172F6" p-id="43895"></path></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
src/assets/svgs/password_security.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1741151981616" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="36372" width="200" height="200"><path d="M128 0h768C981.357714 0 1024 42.642286 1024 128v768c0 85.357714-42.642286 128-128 128H128C42.642286 1024 0 981.357714 0 896V128C0 42.642286 42.642286 0 128 0z" fill="#4C84FF" p-id="36373"></path><path d="M704 398.189714h-31.963429v-64.950857c0-89.746286-71.68-162.596571-160.036571-162.596571-88.283429 0-159.963429 72.850286-159.963429 162.596571v64.950857h-32.036571c-35.181714 0-64 29.257143-64 65.097143v282.331429c0 35.766857 28.818286 65.097143 64 65.097143h384c35.181714 0 64-29.257143 64-65.097143v-282.331429a64.731429 64.731429 0 0 0-64-65.097143zM512 682.715429a85.577143 85.577143 0 0 1-85.357714-85.357715A85.577143 85.577143 0 0 1 512 512a85.577143 85.577143 0 0 1 85.357714 85.357714A85.577143 85.577143 0 0 1 512 682.642286z m-95.963429-284.525715v-64.950857c0-53.979429 42.861714-97.572571 95.963429-97.572571 53.101714 0 96.036571 43.52 96.036571 97.572571v64.950857H416.036571z" fill="#FFFFFF" p-id="36374"></path></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
src/assets/svgs/phone_security.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1741151722442" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="31745" width="200" height="200"><path d="M511.982392 0s-225.676102 228.353615-424.237072 153.000747v484.927368a649.870673 649.870673 0 0 0 424.237072 386.071885 649.870673 649.870673 0 0 0 424.237071-386.071885v-484.927368c-198.60347 75.352868-424.237071-153.000747-424.237071-153.000747z" fill="#3388FF" p-id="31746"></path><path d="M332.971518 266.2213m62.092803 0l233.836142 0q62.092803 0 62.092803 62.092803l0 345.824189q0 62.092803-62.092803 62.092803l-233.836142 0q-62.092803 0-62.092803-62.092803l0-345.824189q0-62.092803 62.092803-62.092803Z" fill="#FFFFFF" p-id="31747"></path><path d="M509.049877 614.127999m-37.697684 0a37.697684 37.697684 0 1 0 75.395368 0 37.697684 37.697684 0 1 0-75.395368 0Z" fill="#3388FF" p-id="31748"></path><path d="M403.649363 316.966548m22.56761 0l165.623309 0q22.56761 0 22.56761 22.56761l0 0q0 22.56761-22.56761 22.56761l-165.623309 0q-22.56761 0-22.56761-22.56761l0 0q0-22.56761 22.56761-22.56761Z" fill="#3388FF" p-id="31749"></path></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
src/assets/svgs/share-icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1741143907669" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5846" width="200" height="200"><path d="M758.8352 945.2032H272.5376c-107.264 0-194.2016-86.9376-194.2016-194.2016V264.7552c0-107.264 86.9376-194.2016 194.2016-194.2016h486.2464c107.264 0 194.2016 86.9376 194.2016 194.2016v486.2464c0.0512 107.264-86.8864 194.2016-194.1504 194.2016z" fill="#4FE8CB" p-id="5847"></path><path d="M722.7392 623.1552c-57.344 0-104.0384 46.6432-104.0384 103.9872s46.6432 103.9872 104.0384 103.9872c57.344 0 103.9872-46.6432 103.9872-103.9872s-46.6432-103.9872-103.9872-103.9872z m0 156.8256c-29.1328 0-52.8384-23.7056-52.8384-52.7872s23.7056-52.7872 52.8384-52.7872 52.7872 23.7056 52.7872 52.7872-23.6544 52.7872-52.7872 52.7872zM595.8656 631.3472L457.472 556.032c4.3008-12.4928 6.7072-25.9072 6.7072-39.8848 0-13.6704-2.2528-26.7776-6.4-39.0144l190.5664-105.2672c18.8928 19.4048 45.2608 31.5392 74.4448 31.5392 22.1696 0 43.3152-6.8608 61.1328-19.8656a25.62048 25.62048 0 0 0 5.632-35.7888 25.66656 25.66656 0 0 0-35.7888-5.632 52.27008 52.27008 0 0 1-31.0272 10.0864c-29.1328 0-52.8384-23.7056-52.8384-52.7872s23.7056-52.7872 52.8384-52.7872 52.7872 23.7056 52.7872 52.7872c0 14.1312 11.4688 25.6 25.6 25.6s25.6-11.4688 25.6-25.6c0-57.344-46.6432-103.9872-103.9872-103.9872s-104.0384 46.6432-104.0384 103.9872c0 9.7792 1.4336 19.2 3.9936 28.16L431.616 433.0496c-22.4256-24.3712-54.5792-39.6288-90.2144-39.6288-67.6864 0-122.7264 55.04-122.7264 122.7264s55.04 122.7264 122.7264 122.7264c35.328 0 67.1744-15.0016 89.6-38.9632l140.4416 76.3904c3.8912 2.0992 8.0896 3.1232 12.1856 3.1232 9.0624 0 17.8688-4.8128 22.528-13.3632 6.7584-12.3904 2.1504-27.9552-10.2912-34.7136z m-254.464-43.6736c-39.4752 0-71.5264-32.1024-71.5264-71.5264 0-39.424 32.1024-71.5264 71.5264-71.5264 39.424 0 71.5264 32.1024 71.5264 71.5264 0 39.424-32.1024 71.5264-71.5264 71.5264z" fill="#F7F8F8" p-id="5848"></path></svg>
|
After Width: | Height: | Size: 1.9 KiB |
406
src/components/HeatmapPro/HeatmapPro.vue
Normal file
@@ -0,0 +1,406 @@
|
||||
<template>
|
||||
<div ref="containerRef" class="contribution-container">
|
||||
<!-- 颜色图例 -->
|
||||
<div class="legend">
|
||||
<span style="color: #999999">低</span>
|
||||
<div v-for="(color, index) in colorLevels" :key="index" class="legend-item">
|
||||
<a-tooltip :title="color.description">
|
||||
<div class="legend-block" :style="{ backgroundColor: color.color }"/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<span style="color: #999999">高</span>
|
||||
</div>
|
||||
|
||||
<!-- 主图表区域 -->
|
||||
<div class="chart-wrapper">
|
||||
<!-- 月份坐标轴 -->
|
||||
<div class="month-axis" v-if="monthLabels.length">
|
||||
<div
|
||||
v-for="(month, index) in monthLabels"
|
||||
:key="index"
|
||||
class="month-label"
|
||||
:style="{ left: month.position + '%' }"
|
||||
>
|
||||
{{ month.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 贡献图主体 -->
|
||||
<div class="chart-body">
|
||||
<!-- 星期坐标轴 -->
|
||||
<div class="weekday-axis">
|
||||
<div v-for="(weekday, index) in weekdays" :key="index">{{ weekday }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 贡献格子 -->
|
||||
<div class="weeks-container" :style="{ gap: cellGap + 'px' }">
|
||||
<div
|
||||
v-for="(week, wi) in visibleWeeks"
|
||||
:key="wi"
|
||||
class="week-column"
|
||||
:style="{ gap: cellGap + 'px' }"
|
||||
>
|
||||
<a-tooltip
|
||||
v-for="(day, di) in week"
|
||||
:key="di"
|
||||
:mouseEnterDelay="0.3"
|
||||
:title="`${formatTooltip(day.date)}: ${day.count} 次上传`"
|
||||
>
|
||||
<div
|
||||
class="day-cell"
|
||||
:style="{
|
||||
width: cellSize + 'px',
|
||||
height: cellSize + 'px',
|
||||
backgroundColor: getColor(day.count)
|
||||
}"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, onMounted, onBeforeUnmount} from 'vue';
|
||||
import {
|
||||
format,
|
||||
startOfWeek,
|
||||
addDays,
|
||||
eachDayOfInterval,
|
||||
getMonth,
|
||||
parseISO,
|
||||
getYear
|
||||
} from 'date-fns';
|
||||
import {debounce} from 'lodash-es';
|
||||
import type {PropType} from 'vue';
|
||||
import {zhCN} from 'date-fns/locale';
|
||||
|
||||
interface Contribution {
|
||||
date: string
|
||||
count: number
|
||||
}
|
||||
|
||||
interface ColorLevel {
|
||||
color: string
|
||||
min: number
|
||||
max: number
|
||||
description: string
|
||||
}
|
||||
|
||||
interface MonthLabel {
|
||||
name: string
|
||||
position: number
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
contributions: {
|
||||
type: Array as PropType<Contribution[]>,
|
||||
required: true
|
||||
},
|
||||
colorLevels: {
|
||||
type: Array as PropType<ColorLevel[]>,
|
||||
default: () => [
|
||||
{
|
||||
color: '#ebedf0',
|
||||
min: 0,
|
||||
max: 0,
|
||||
description: '没有上传'
|
||||
},
|
||||
{
|
||||
color: '#9be9a8',
|
||||
min: 1,
|
||||
max: 3,
|
||||
description: '1-3 上传'
|
||||
},
|
||||
{
|
||||
color: '#40c463',
|
||||
min: 4,
|
||||
max: 5,
|
||||
description: '4-5 上传'
|
||||
},
|
||||
{
|
||||
color: '#30a14e',
|
||||
min: 6,
|
||||
max: 7,
|
||||
description: '6-7 上传'
|
||||
},
|
||||
{
|
||||
color: '#216e39',
|
||||
min: 8,
|
||||
max: Infinity,
|
||||
description: '8+ 上传'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const containerRef = ref<HTMLElement>();
|
||||
const cellSize = ref(12);
|
||||
const cellGap = ref(3);
|
||||
const weekdays = ['Mon', 'Wed', 'Fri'];
|
||||
const visibleWeeks = ref<Array<Array<{ date: Date; count: number }>>>([]);
|
||||
|
||||
// 新增响应式变量
|
||||
const chartMaxWidth = ref(0);
|
||||
|
||||
// 修改后的updateSize函数
|
||||
const updateSize = debounce(() => {
|
||||
if (!containerRef.value) return;
|
||||
|
||||
const container = containerRef.value;
|
||||
const containerWidth = container.offsetWidth - 60; // 增加边距
|
||||
const containerHeight = container.offsetHeight - 80;
|
||||
|
||||
// 动态计算最大宽度
|
||||
chartMaxWidth.value = containerWidth - 40;
|
||||
|
||||
// 重新计算单元格尺寸
|
||||
const maxCellSize = Math.min(
|
||||
(containerWidth - 40) / 54, // 更精确的计算
|
||||
containerHeight / 7 - cellGap.value
|
||||
);
|
||||
|
||||
cellSize.value = Math.max(8, Math.min(14, maxCellSize));
|
||||
cellGap.value = Math.max(2, cellSize.value * 0.15);
|
||||
}, 150);
|
||||
|
||||
// 日期有效性检查
|
||||
const isValidDate = (date: Date) => {
|
||||
return date instanceof Date && !isNaN(date.getTime());
|
||||
};
|
||||
|
||||
// 生成标准的GitHub风格日期网格
|
||||
const generateDateGrid = () => {
|
||||
const today = new Date();
|
||||
const startDate = startOfWeek(addDays(today, -358)); // 52周前
|
||||
const endDate = today;
|
||||
|
||||
const weeksArray: Date[][] = [];
|
||||
let currentWeekStart = startOfWeek(startDate, {weekStartsOn: 0}); // 从周日开始
|
||||
|
||||
while (currentWeekStart <= endDate) {
|
||||
const weekEnd = addDays(currentWeekStart, 6);
|
||||
const weekDays = eachDayOfInterval({
|
||||
start: currentWeekStart,
|
||||
end: weekEnd > endDate ? endDate : weekEnd
|
||||
}).filter(date => isValidDate(date));
|
||||
|
||||
if (weekDays.length > 0) {
|
||||
weeksArray.push(weekDays);
|
||||
}
|
||||
|
||||
currentWeekStart = addDays(currentWeekStart, 7);
|
||||
}
|
||||
|
||||
return weeksArray.slice(-52); // 严格52周
|
||||
};
|
||||
|
||||
// 处理贡献数据
|
||||
const processContributions = () => {
|
||||
const contributionMap = new Map(
|
||||
props.contributions
|
||||
.filter(c => {
|
||||
try {
|
||||
const date = parseISO(c.date);
|
||||
return isValidDate(date);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.map(c => [format(parseISO(c.date), 'yyyy-MM-dd'), c.count])
|
||||
);
|
||||
|
||||
return generateDateGrid()
|
||||
.map(week =>
|
||||
week.map(date => ({
|
||||
date,
|
||||
count: contributionMap.get(format(date, 'yyyy-MM-dd')) || 0
|
||||
}))
|
||||
)
|
||||
.filter(week => week.length > 0);
|
||||
};
|
||||
|
||||
// 生成月份标签
|
||||
const monthLabels = computed(() => {
|
||||
const labels = new Map<string, MonthLabel>();
|
||||
const totalWeeks = visibleWeeks.value.length;
|
||||
|
||||
visibleWeeks.value.forEach((week, weekIndex) => {
|
||||
if (week.length === 0) return;
|
||||
|
||||
const firstDate = week[0].date;
|
||||
if (!isValidDate(firstDate)) return;
|
||||
|
||||
const monthYear = `${getYear(firstDate)}-${getMonth(firstDate)}`;
|
||||
if (!labels.has(monthYear)) {
|
||||
labels.set(monthYear, {
|
||||
name: format(firstDate, 'MMM'),
|
||||
position: (weekIndex / totalWeeks) * 100
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(labels.values());
|
||||
});
|
||||
|
||||
// 颜色匹配逻辑
|
||||
const getColor = (count: number) => {
|
||||
return (
|
||||
props.colorLevels.find(l => count >= l.min && count <= l.max)?.color ||
|
||||
'#ebedf0'
|
||||
);
|
||||
};
|
||||
|
||||
// 格式化工具提示
|
||||
const formatTooltip = (date: Date) => {
|
||||
return format(date, 'yyyy年MM月dd日 EEEE', {locale: zhCN});
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
visibleWeeks.value = processContributions();
|
||||
updateSize();
|
||||
|
||||
const observer = new ResizeObserver(debounce(() => {
|
||||
updateSize();
|
||||
visibleWeeks.value = processContributions();
|
||||
}, 200));
|
||||
|
||||
if (containerRef.value) observer.observe(containerRef.value);
|
||||
onBeforeUnmount(() => observer.disconnect());
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.contribution-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 300px;
|
||||
padding: 20px 15px; /* 调整左右padding */
|
||||
box-sizing: border-box;
|
||||
overflow-x: auto; /* 允许横向滚动 */
|
||||
}
|
||||
|
||||
.legend {
|
||||
position: absolute; /* 粘性定位 */
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 15px; /* 增加底部间距 */
|
||||
z-index: 2; /* 确保在图上层级 */
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
|
||||
& + & {
|
||||
margin-left: 4px; /* 增加色块间距 */
|
||||
}
|
||||
}
|
||||
|
||||
.legend-block {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
position: relative;
|
||||
margin-top: 45px; /* 增加顶部间距 */
|
||||
margin-left: 40px;
|
||||
min-width: 520px; /* 最小宽度保证布局 */
|
||||
}
|
||||
|
||||
.month-axis {
|
||||
position: absolute;
|
||||
top: -25px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.month-label {
|
||||
position: absolute;
|
||||
font-size: 11px;
|
||||
color: #586069;
|
||||
transform: translateX(-50%);
|
||||
pointer-events: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.weekday-axis {
|
||||
position: absolute;
|
||||
left: -35px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
font-size: 10px;
|
||||
color: #586069;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.weeks-container {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
padding-right: 20px; /* 增加右侧padding */
|
||||
}
|
||||
|
||||
/* 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.contribution-container {
|
||||
padding: 15px 10px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
top: 5px;
|
||||
padding: 6px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.legend-block {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.legend-text {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
margin-top: 35px;
|
||||
margin-left: 30px;
|
||||
min-width: 480px;
|
||||
}
|
||||
}
|
||||
|
||||
.week-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.day-cell {
|
||||
border-radius: 15%;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.day-cell:hover {
|
||||
transform: scale(1.15);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
@@ -216,6 +216,11 @@ const editImages = async () => {
|
||||
image.onload = () => {
|
||||
imageStore.imageEditVisible = true;
|
||||
};
|
||||
image.onerror = () => {
|
||||
message.warning("图片已过期,请刷新后重试");
|
||||
imageStore.selected = [];
|
||||
imageStore.imageEditVisible = false;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -285,7 +290,6 @@ async function addImagesToAlbum(albumId: number) {
|
||||
*/
|
||||
function handleImageEditClose() {
|
||||
imageStore.selected = [];
|
||||
image.src = '';
|
||||
imageStore.imageEditVisible = false;
|
||||
}
|
||||
|
||||
|
@@ -1,6 +0,0 @@
|
||||
export const ProviderIcon = {
|
||||
"ali": "/provider_icon/ali.svg",
|
||||
"tencent": "/provider_icon/tencent.svg",
|
||||
"minio": "/provider_icon/minio.svg",
|
||||
"huawei": "/provider_icon/huawei.svg",
|
||||
};
|
50
src/constant/provider_map.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
export const ProviderIcon = {
|
||||
"ali": "/provider_icon/ali.svg",
|
||||
"tencent": "/provider_icon/tencent.svg",
|
||||
"minio": "/provider_icon/minio.svg",
|
||||
"huawei": "/provider_icon/huawei.svg",
|
||||
};
|
||||
|
||||
export const ProviderNameMap = {
|
||||
"ali": "阿里云",
|
||||
"tencent": "腾讯云",
|
||||
"minio": "MinIO",
|
||||
"huawei": "华为云",
|
||||
};
|
||||
export const ProviderColorMap = {
|
||||
'ali': 'orange',
|
||||
'tencent': 'blue',
|
||||
'minio': 'red',
|
||||
'huawei': 'red',
|
||||
};
|
||||
|
||||
export const AliRegionMap = {
|
||||
"cn-hangzhou": "杭州",
|
||||
"cn-shanghai": "上海",
|
||||
"cn-qingdao": "青岛",
|
||||
"cn-beijing": "北京",
|
||||
"cn-zhangjiakou": "张家口",
|
||||
"cn-huhehaote": "呼和浩特",
|
||||
"cn-shenzhen": "深圳",
|
||||
"cn-chengdu": "成都",
|
||||
"cn-heyuan": "河源",
|
||||
"cn-wulanchabu": "乌兰察布",
|
||||
"cn-ningxia": "宁夏",
|
||||
"cn-shaoxing": "绍兴",
|
||||
"cn-jiaxing": "嘉兴",
|
||||
"cn-zhengzhou": "郑州",
|
||||
"cn-hongkong": "香港",
|
||||
"ap-southeast-1": "新加坡",
|
||||
"ap-southeast-2": "悉尼",
|
||||
"ap-southeast-3": "吉隆坡",
|
||||
"ap-southeast-5": "雅加达",
|
||||
"ap-northeast-1": "东京",
|
||||
"ap-south-1": "孟买",
|
||||
"eu-west-1": "伦敦",
|
||||
"eu-central-1": "法兰克福",
|
||||
"us-west-1": "硅谷",
|
||||
"us-east-1": "弗吉尼亚",
|
||||
"us-east-2": "米兰",
|
||||
"us-west-2": "弗吉尼亚",
|
||||
"me-east-1": "迪拜",
|
||||
};
|
@@ -155,7 +155,7 @@ import wenhao from "@/assets/svgs/wenhao.svg";
|
||||
import useStore from "@/store";
|
||||
import ImageUpload from "@/components/ImageUpload/ImageUpload.vue";
|
||||
import {getStorageConfigListApi} from "@/api/storage";
|
||||
import {ProviderIcon} from "@/constant/provider_icon.ts";
|
||||
import {ProviderIcon} from "@/constant/provider_map.ts";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
@@ -9,6 +9,7 @@
|
||||
:style="{borderRadius: borderRadius, boxShadow: boxShadow}"
|
||||
:placeholder="searchStore.searchOption[0]? '#'+searchStore.searchOption[0] : '搜索...'"
|
||||
@pressEnter="search"
|
||||
:allowClear="true "
|
||||
>
|
||||
<template #suffix>
|
||||
<AButton size="small" type="text" shape="circle" @click.prevent>
|
||||
@@ -55,6 +56,38 @@
|
||||
style="border-radius: 20px"
|
||||
/>
|
||||
</div>
|
||||
<div class="header-search-picture" v-if="searchStore.searchOption[0] === 'picture'">
|
||||
<AUpload
|
||||
v-model:file-list="fileList"
|
||||
name="avatar"
|
||||
list-type="picture-card"
|
||||
class="avatar-uploader"
|
||||
:show-upload-list="false"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="customRequest"
|
||||
>
|
||||
<img v-if="iconUrl" :src="iconUrl" alt="avatar" style="width: 100%; height: 100%;border-radius: 8px"/>
|
||||
<div v-else>
|
||||
<loading-outlined v-if="loading"></loading-outlined>
|
||||
<plus-outlined v-else></plus-outlined>
|
||||
<div class="ant-upload-text">Upload</div>
|
||||
</div>
|
||||
</AUpload>
|
||||
<div class="header-search-picture-info" v-if="fileInfo">
|
||||
<AFlex :vertical="false" align="center" gap="small">
|
||||
<span style="font-size: 13px; color: #999999">文件名:</span>
|
||||
<span style="font-size: 16px; font-weight: bolder">{{ fileInfo.name }}</span>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="center" gap="small">
|
||||
<span style="font-size: 13px; color: #999999">类型:</span>
|
||||
<ATag>{{ fileInfo.type }}</ATag>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="center" gap="small">
|
||||
<span style="font-size: 13px; color: #999999">大小:</span>
|
||||
<ATag>{{ bytesToSize(fileInfo.size) }}</ATag>
|
||||
</AFlex>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <AFlex :vertical="false" align="center" justify="space-between" class="header-search-content-header">-->
|
||||
<!-- <span>搜索历史</span>-->
|
||||
<!-- <AButton type="text" size="small" style="color: #707072">清空搜索历史</AButton>-->
|
||||
@@ -79,6 +112,12 @@ import useStore from "@/store";
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import dayjs, {Dayjs} from 'dayjs';
|
||||
import {imageSearchApi} from "@/api/storage";
|
||||
import {NSFWJS} from "nsfwjs";
|
||||
import {initNSFWJs, predictNSFW} from "@/utils/tfjs/nsfw.ts";
|
||||
import {message} from "ant-design-vue";
|
||||
import i18n from "@/locales";
|
||||
import {bytesToSize} from "@/utils/imageUtils/bytesToSize.ts";
|
||||
import {cocoSsdPredict} from "@/utils/tfjs/mobilenet.ts";
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
const borderRadius = ref('20px');
|
||||
@@ -152,7 +191,6 @@ async function search() {
|
||||
keyword: searchStore.searchValue,
|
||||
provider: uploadStore.storageSelected?.[0],
|
||||
bucket: uploadStore.storageSelected?.[1],
|
||||
input_image: "123"
|
||||
};
|
||||
const res: any = await imageSearchApi(params);
|
||||
if (res && res.code === 200) {
|
||||
@@ -161,11 +199,44 @@ async function search() {
|
||||
path: '/main/photo/search/list', query: {
|
||||
type: searchStore.searchOption[0],
|
||||
keyword: searchStore.searchValue,
|
||||
provider: uploadStore.storageSelected?.[0],
|
||||
bucket: uploadStore.storageSelected?.[1],
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const fileList = ref([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const iconUrl = ref<string>('');
|
||||
const image = new Image();
|
||||
|
||||
const beforeUpload = async (file: any) => {
|
||||
loading.value = true;
|
||||
image.src = URL.createObjectURL(file);
|
||||
// 图片 NSFW 检测
|
||||
const nsfw: NSFWJS = await initNSFWJs();
|
||||
const isNSFW: boolean = await predictNSFW(nsfw, image);
|
||||
if (isNSFW) {
|
||||
message.error(i18n.global.t('comment.illegalImage'));
|
||||
return false;
|
||||
}
|
||||
image.onload = async () => {
|
||||
|
||||
};
|
||||
const cocoResults: any = await cocoSsdPredict(image);
|
||||
searchStore.searchValue = cocoResults.map(item => item.class).join(',');
|
||||
|
||||
iconUrl.value = URL.createObjectURL(file);
|
||||
loading.value = false;
|
||||
return true;
|
||||
};
|
||||
const fileInfo = ref<any>();
|
||||
|
||||
function customRequest(file: any) {
|
||||
|
||||
fileInfo.value = file.file;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
@@ -10,6 +10,8 @@ import {createPersistedStatePlugin} from 'pinia-plugin-persistedstate-2';
|
||||
import VueDOMPurifyHTML from 'vue-dompurify-html';
|
||||
import {registerDirectives} from "@/directives";
|
||||
|
||||
import VueCalendarMap from '@fcli/vue-calendar-map';
|
||||
|
||||
const pinia: Pinia = createPinia();
|
||||
const installPersistedStatePlugin = createPersistedStatePlugin();
|
||||
pinia.use((context) => installPersistedStatePlugin(context));
|
||||
@@ -20,4 +22,5 @@ app.use(router);
|
||||
app.use(i18n);
|
||||
app.use(GoCaptcha);
|
||||
app.use(VueDOMPurifyHTML);
|
||||
app.use(VueCalendarMap);
|
||||
app.mount('#app');
|
||||
|
@@ -1,13 +1,12 @@
|
||||
import UserCenterHome from "@/views/User/PersonalCenter/components/UserCenterHome/UserCenterHome.vue";
|
||||
import UserCenterDynamic from "@/views/User/PersonalCenter/components/UserCenterDynamic/UserCenterDynamic.vue";
|
||||
import UserCenterInfo from "@/views/User/PersonalCenter/components/UserCenterInfo/UserCenterInfo.vue";
|
||||
import UserCenterAnalysis from "@/views/User/PersonalCenter/components/UserCenterAnalysis/UserCenterAnalysis.vue";
|
||||
import UserCenterShare from "@/views/User/PersonalCenter/components/UserCenterShare/UserCenterShare.vue";
|
||||
import UserCenterSetting from "@/views/User/PersonalCenter/components/UserCenterSetting/UserCenterSetting.vue";
|
||||
|
||||
import AccountSettingHome from "@/views/User/AccountSetting/components/AccountSettingHome/AccountSettingHome.vue";
|
||||
import AccountSettingInfo from "@/views/User/AccountSetting/components/AccountSettingInfo/AccountSettingInfo.vue";
|
||||
import AccountSettingStorage from "@/views/User/AccountSetting/components/AccountSettingStorage/AccountSettingStorage.vue";
|
||||
import AccountSettingStorage
|
||||
from "@/views/User/AccountSetting/components/AccountSettingStorage/AccountSettingStorage.vue";
|
||||
|
||||
export default [
|
||||
{
|
||||
|
||||
@@ -38,33 +37,6 @@ export default [
|
||||
title: '动态'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/main/user/center/info',
|
||||
name: 'UserCenterInfo',
|
||||
component: UserCenterInfo,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '个人信息'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/main/user/center/analysis',
|
||||
name: 'UserCenterAnalysis',
|
||||
component: UserCenterAnalysis,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '数据分析'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/main/user/center/share',
|
||||
name: 'UserCenterShare',
|
||||
component: UserCenterShare,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '我的分享'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/main/user/center/setting',
|
||||
name: 'UserCenterSetting',
|
||||
|
101
src/utils/alova/service_app.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import {createAlova} from 'alova';
|
||||
import CryptoJS from 'crypto-js';
|
||||
import VueHook from 'alova/vue';
|
||||
import useStore from "@/store";
|
||||
import {localforageStorageAdapter} from "@/utils/alova/adapter/localforageStorageAdapter.ts";
|
||||
import {createServerTokenAuthentication} from "alova/client";
|
||||
import {AxiosError, AxiosResponse} from "axios";
|
||||
import {message, Modal} from "ant-design-vue";
|
||||
import i18n from "@/locales";
|
||||
import {axiosRequestAdapter} from "@alova/adapter-axios";
|
||||
import {refreshToken} from "@/api/user";
|
||||
import generateKeySecretSignature from "@/utils/signature/signature.ts";
|
||||
import {handleErrorCode} from "@/utils/errorCode/errorCodeHandler.ts";
|
||||
|
||||
|
||||
const {onAuthRequired, onResponseRefreshToken} = createServerTokenAuthentication<typeof VueHook,
|
||||
typeof axiosRequestAdapter>({
|
||||
refreshTokenOnSuccess: {
|
||||
// 在请求前触发,将接收到method参数,并返回boolean表示token是否过期
|
||||
isExpired: (response: AxiosResponse<any, any>, _method: any) => {
|
||||
const code = response.data.code;
|
||||
return code === 401;
|
||||
},
|
||||
|
||||
// 当token过期时触发,在此函数中触发刷新token
|
||||
handler: async () => {
|
||||
// 刷新token
|
||||
const user = useStore().user;
|
||||
const res: any = await refreshToken();
|
||||
if (res && res.code === 200) {
|
||||
const {access_token, expire_at} = res.data;
|
||||
user.token.accessToken = access_token;
|
||||
user.token.expireAt = expire_at;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
export const service = createAlova({
|
||||
timeout: 10000,
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
statesHook: VueHook,
|
||||
// 请求适配器
|
||||
requestAdapter: axiosRequestAdapter(),
|
||||
l2Cache: localforageStorageAdapter,
|
||||
cacheLogger: import.meta.env.VITE_NODE_ENV === 'development',
|
||||
cacheFor: null,
|
||||
// 设置全局的请求拦截器
|
||||
beforeRequest: onAuthRequired(async (method: any) => {
|
||||
// if (!method.meta?.ignoreToken) {
|
||||
// const user = useStore().user;
|
||||
// method.config.headers.Authorization = `${import.meta.env.VITE_APP_TOKEN_KEY} ${user.token.accessToken}`;
|
||||
// method.config.headers['X-UID'] = user.user.uid;
|
||||
// // method.config.headers['X-Expire-At'] = user.token.expireAt;
|
||||
// }
|
||||
const lang = useStore().lang;
|
||||
method.config.headers['Accept-Language'] = lang.lang || 'zh';
|
||||
|
||||
// 令牌
|
||||
method.config.headers['X-Nonce'] = CryptoJS.lib.WordArray.random(16).toString();
|
||||
// 签名
|
||||
if (method.meta?.signature) {
|
||||
method.config.headers['X-Content-Security'] = generateKeySecretSignature(0, method.type, method.url, method.config.params, method.data);
|
||||
}
|
||||
}),
|
||||
// 响应拦截器
|
||||
responded: onResponseRefreshToken({
|
||||
onSuccess: async (response: AxiosResponse, _method: any) => {
|
||||
if (response.data instanceof Blob) {
|
||||
return response;
|
||||
}
|
||||
const {code} = response.data;
|
||||
if (code === 403) {
|
||||
localStorage.removeItem('user');
|
||||
Modal.warning({
|
||||
title: i18n.global.t('error.loginExpired'),
|
||||
content: i18n.global.t('error.authTokenExpired'),
|
||||
onOk() {
|
||||
setTimeout(() => {
|
||||
window.location.href = '/login';
|
||||
}, 1000);
|
||||
},
|
||||
});
|
||||
return Promise.reject();
|
||||
}
|
||||
return response.data;
|
||||
},
|
||||
onError:
|
||||
(error: AxiosError, _method: any) => {
|
||||
const {response} = error;
|
||||
if (response) {
|
||||
handleErrorCode(response.status);
|
||||
}
|
||||
if (!window.navigator.onLine) {
|
||||
message.error(i18n.global.t('error.networkError')).then();
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<div class="upload-controller">
|
||||
<AButton type="text" shape="circle" size="middle">
|
||||
<template #icon>
|
||||
<APopover placement="bottom" trigger="click">
|
||||
<APopover placement="bottomRight" trigger="click">
|
||||
<template #content>
|
||||
<UploadSetting/>
|
||||
</template>
|
||||
@@ -66,7 +66,7 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import setting from "@/assets/svgs/setting.svg";
|
||||
import {albumListApi, uploadFile} from "@/api/storage";
|
||||
import {albumListApi, uploadFile} from "@/api/phone";
|
||||
import useStore from "@/store";
|
||||
import empty from "@/assets/svgs/empty.svg";
|
||||
import {useRequest} from "alova/client";
|
||||
@@ -74,21 +74,21 @@ import imageCompression from "browser-image-compression";
|
||||
import {generateThumbnail} from "@/utils/imageUtils/generateThumb.ts";
|
||||
import UploadSetting from "@/components/ImageUpload/UploadSetting.vue";
|
||||
|
||||
// const route = useRoute();
|
||||
const route = useRoute();
|
||||
|
||||
const fileList = ref([]);
|
||||
const predicting = ref<boolean>(false);
|
||||
const progressPercent = ref<number>(0);
|
||||
const progressStatus = ref<string>('active');
|
||||
|
||||
// const accessToken = computed(() => {
|
||||
// const token = route.query.token;
|
||||
// return Array.isArray(token) ? token[0] : token;
|
||||
// });
|
||||
// const userId = computed(() => {
|
||||
// const uid = route.query.user_id;
|
||||
// return Array.isArray(uid) ? uid[0] : uid;
|
||||
// });
|
||||
const accessToken = computed(() => {
|
||||
const token = route.query.token;
|
||||
return Array.isArray(token) ? token[0] : token;
|
||||
});
|
||||
const userId = computed(() => {
|
||||
const uid = route.query.user_id;
|
||||
return Array.isArray(uid) ? uid[0] : uid;
|
||||
});
|
||||
|
||||
|
||||
const upload = useStore().upload;
|
||||
@@ -100,8 +100,8 @@ const {send: getAlbumList} = useRequest(albumListApi, {
|
||||
immediate: false,
|
||||
debounce: 500,
|
||||
}).onSuccess((res: any) => {
|
||||
if (res && res.code === 200) {
|
||||
albumList.value = res.data.albums;
|
||||
if (res.data && res.data.code === 200) {
|
||||
albumList.value = res.data.data.albums;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -142,7 +142,10 @@ async function customUploadRequest(file: any) {
|
||||
}
|
||||
},
|
||||
);
|
||||
submitFile(formData).then((response: any) => {
|
||||
submitFile(formData, {
|
||||
'Authorization': `Bearer ${accessToken.value}`,
|
||||
'X-UID': userId.value,
|
||||
}).then((response: any) => {
|
||||
if (response && response.code === 200) {
|
||||
file.onSuccess(response.data, file);
|
||||
} else {
|
||||
@@ -153,7 +156,10 @@ async function customUploadRequest(file: any) {
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getAlbumList(0, true);
|
||||
getAlbumList(0, true, {
|
||||
'Authorization': `Bearer ${accessToken.value}`,
|
||||
'X-UID': userId.value,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
@@ -24,10 +24,29 @@ import useStore from "@/store";
|
||||
import ImageToolbar from "@/components/ImageToolbar/ImageToolbar.vue";
|
||||
|
||||
import ImageWaterfallList from "@/components/ImageWaterfallList/ImageWaterfallList.vue";
|
||||
import {imageSearchApi} from "@/api/storage";
|
||||
|
||||
const searchStore = useStore().search;
|
||||
const imageStore = useStore().image;
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
async function search() {
|
||||
const params: any = {
|
||||
type: route.query.type as string,
|
||||
keyword: route.query.keyword as string,
|
||||
provider: route.query.provider as string,
|
||||
bucket: route.query.bucket as string,
|
||||
};
|
||||
const res: any = await imageSearchApi(params);
|
||||
if (res && res.code === 200) {
|
||||
searchStore.searchResult = res.data.records;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
searchStore.searchResult = [];
|
||||
});
|
||||
|
@@ -1,11 +1,212 @@
|
||||
<template>
|
||||
<div class="account-setting-home">
|
||||
<div class="account-setting-home-info">
|
||||
<div class="account-setting-home-info-avatar">
|
||||
<AAvatar :size="80" :src="userStore.user.avatar"/>
|
||||
</div>
|
||||
<div class="account-setting-home-info-content">
|
||||
<AFlex :vertical="false" align="center" justify="space-between" gap="small">
|
||||
<span class="account-setting-home-info-content-name">{{ userStore.user.nickname }}</span>
|
||||
<ATag color="success" :bordered="false">正式会员</ATag>
|
||||
<img src="/level_icon/icon/lv1.png" style="width: 40px" alt="level">
|
||||
</AFlex>
|
||||
<AProgress :percent="50" :stroke-color="{
|
||||
'0%': '#108ee9',
|
||||
'100%': '#87d068',
|
||||
}" :showInfo="false" style="width: 500px" :size="15"/>
|
||||
<AFlex :vertical="false" align="flex-start" justify="space-between" style="width: 500px">
|
||||
<img src="/level_icon/icon/lv1.png" class="avatar-level-icon" alt="level">
|
||||
<img src="/level_icon/icon/lv2.png" class="avatar-level-icon" alt="level">
|
||||
</AFlex>
|
||||
</div>
|
||||
</div>
|
||||
<ADivider/>
|
||||
<div class="account-setting-home-content">
|
||||
<div class="account-setting-home-content-header">
|
||||
<AAvatar size="default" :src="accountSecurity"/>
|
||||
<span style="font-size: 20px; font-weight: bold;color: #333">账号安全</span>
|
||||
</div>
|
||||
<div class="account-setting-home-content-section">
|
||||
<AFlex :vertical="false" align="center" justify="space-between" :gap="50">
|
||||
<div class="account-setting-home-content-section-item">
|
||||
<div class="account-setting-home-content-section-item-avatar">
|
||||
<AAvatar shape="circle" :size="80" :src="emailSecurity"/>
|
||||
</div>
|
||||
<div class="account-setting-home-content-section-item-content">
|
||||
<span style="font-size: 18px; font-weight: bold;color: #333;margin-top: 10px">我的邮箱</span>
|
||||
<span style="font-size: 14px; color: #666;">绑定邮箱后即可使用邮箱登录</span>
|
||||
<AFlex :vertical="false" align="center" justify="space-between" gap="large">
|
||||
<AButton type="primary" size="small" :disabled="true">已绑定邮箱</AButton>
|
||||
<AButton type="link" size="small">更改邮箱</AButton>
|
||||
</AFlex>
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-setting-home-content-section-item">
|
||||
<div class="account-setting-home-content-section-item-avatar">
|
||||
<AAvatar shape="circle" :size="80" :src="phoneSecurity"/>
|
||||
</div>
|
||||
<div class="account-setting-home-content-section-item-content">
|
||||
<span style="font-size: 18px; font-weight: bold;color: #333;margin-top: 10px">我的手机</span>
|
||||
<span style="font-size: 14px; color: #666;">绑定手机后即可使用手机号登录</span>
|
||||
<AFlex :vertical="false" align="center" justify="space-between" gap="large">
|
||||
<AButton type="primary" size="small" :disabled="true">已绑定手机</AButton>
|
||||
<AButton type="link" size="small">更改手机</AButton>
|
||||
</AFlex>
|
||||
</div>
|
||||
</div>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="center" justify="space-between" :gap="50">
|
||||
<div class="account-setting-home-content-section-item">
|
||||
<div class="account-setting-home-content-section-item-avatar">
|
||||
<AAvatar shape="circle" :size="80" :src="passwordSecurity"/>
|
||||
</div>
|
||||
<div class="account-setting-home-content-section-item-content">
|
||||
<span style="font-size: 18px; font-weight: bold;color: #333;margin-top: 10px">我的密保</span>
|
||||
<span style="font-size: 14px; color: #666;">设置密保,账号更安全</span>
|
||||
<AFlex :vertical="false" align="center" justify="space-between" gap="large">
|
||||
<AButton type="primary" size="small" :disabled="true">已绑定密保</AButton>
|
||||
<AButton type="link" size="small">查看并设置</AButton>
|
||||
</AFlex>
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-setting-home-content-section-item">
|
||||
<div class="account-setting-home-content-section-item-avatar">
|
||||
<AAvatar shape="circle" :size="80" :src="loginSecurity"/>
|
||||
</div>
|
||||
<div class="account-setting-home-content-section-item-content">
|
||||
<span style="font-size: 18px; font-weight: bold;color: #333;margin-top: 10px">三方登录</span>
|
||||
<span style="font-size: 14px; color: #666;">绑定三方账号,安全登录</span>
|
||||
<AFlex :vertical="false" align="center" justify="space-between" gap="large">
|
||||
<AButton type="primary" size="small" :disabled="true">已绑定三方账号</AButton>
|
||||
<AButton type="link" size="small">取消绑定</AButton>
|
||||
</AFlex>
|
||||
</div>
|
||||
</div>
|
||||
</AFlex>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import useStore from "@/store";
|
||||
import accountSecurity from "@/assets/svgs/account_security.svg";
|
||||
import emailSecurity from "@/assets/svgs/email_security.svg";
|
||||
import phoneSecurity from "@/assets/svgs/phone_security.svg";
|
||||
import passwordSecurity from "@/assets/svgs/password_security.svg";
|
||||
import loginSecurity from "@/assets/svgs/login_security.svg";
|
||||
|
||||
const userStore = useStore().user;
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
666
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.account-setting-home {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
.account-setting-home-info {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding-top: 40px;
|
||||
|
||||
.account-setting-home-info-avatar {
|
||||
width: 130px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
|
||||
}
|
||||
|
||||
.account-setting-home-info-content {
|
||||
width: calc(100% - 100px);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
.account-setting-home-info-content-name {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.avatar-level-icon {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account-setting-home-content {
|
||||
width: 100%;
|
||||
height: 70%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
|
||||
.account-setting-home-content-header {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.account-setting-home-content-section {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
|
||||
.account-setting-home-content-section-item {
|
||||
width: 330px;
|
||||
height: 100px;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
.account-setting-home-content-section-item-avatar {
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.account-setting-home-content-section-item-content {
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,11 +1,163 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="account-setting-info">
|
||||
<div class="account-setting-info-header">
|
||||
<span style="font-size: 20px;font-weight: bold;color: #333333;">我的信息</span>
|
||||
</div>
|
||||
<div class="account-setting-info-body">
|
||||
<AForm :model="formState" layout="vertical">
|
||||
<AFormItem label="用户名" name="username">
|
||||
<AInput v-model:value="formState.username" placeholder="请输入用户名" :clearable="true">
|
||||
<template #prefix>
|
||||
<UserOutlined/>
|
||||
</template>
|
||||
</AInput>
|
||||
</AFormItem>
|
||||
<AFormItem label="昵称" name="nickname">
|
||||
<AInput v-model:value="formState.nickname" placeholder="请输入昵称">
|
||||
<template #prefix>
|
||||
<SmileOutlined/>
|
||||
</template>
|
||||
</AInput>
|
||||
</AFormItem>
|
||||
<AFormItem label="邮箱" name="email">
|
||||
<AInput v-model:value="formState.email" placeholder="请输入邮箱">
|
||||
<template #prefix>
|
||||
<MailOutlined/>
|
||||
</template>
|
||||
</AInput>
|
||||
</AFormItem>
|
||||
<AFormItem label="电话" name="phone">
|
||||
<AInput v-model:value="formState.phone" placeholder="请输入电话">
|
||||
<template #prefix>
|
||||
<PhoneOutlined/>
|
||||
</template>
|
||||
</AInput>
|
||||
</AFormItem>
|
||||
<AFormItem label="性别" name="gender">
|
||||
<ASelect v-model:value="formState.gender" placeholder="请选择性别">
|
||||
<template #prefix>
|
||||
<ManOutlined/>
|
||||
</template>
|
||||
<ASelectOption :value="0">未知</ASelectOption>
|
||||
<ASelectOption :value="1">男</ASelectOption>
|
||||
<ASelectOption :value="2">女</ASelectOption>
|
||||
</ASelect>
|
||||
</AFormItem>
|
||||
<AFormItem label="介绍" name="introduce">
|
||||
<ATextarea v-model:value="formState.introduce" placeholder="请输入个人介绍" :rows="4">
|
||||
<template #prefix>
|
||||
<EditOutlined/>
|
||||
</template>
|
||||
</ATextarea>
|
||||
</AFormItem>
|
||||
<AFormItem label="博客" name="blog">
|
||||
<AInput v-model:value="formState.blog" placeholder="请输入博客地址">
|
||||
<template #prefix>
|
||||
<GlobalOutlined/>
|
||||
</template>
|
||||
</AInput>
|
||||
</AFormItem>
|
||||
<AFormItem label="地址" name="location">
|
||||
<AInput v-model:value="formState.location" placeholder="请输入地址">
|
||||
<template #prefix>
|
||||
<EnvironmentOutlined/>
|
||||
</template>
|
||||
</AInput>
|
||||
</AFormItem>
|
||||
<AFormItem label="公司" name="company">
|
||||
<AInput v-model:value="formState.company" placeholder="请输入公司">
|
||||
<template #prefix>
|
||||
<BankOutlined/>
|
||||
</template>
|
||||
</AInput>
|
||||
</AFormItem>
|
||||
<AFormItem :wrapper-col="{ span: 24 }">
|
||||
<div class="form-actions">
|
||||
<AButton type="primary" @click="handleSubmit">保存</AButton>
|
||||
</div>
|
||||
</AFormItem>
|
||||
</AForm>
|
||||
</div>
|
||||
</div>
|
||||
<ADivider/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
UserOutlined,
|
||||
SmileOutlined,
|
||||
MailOutlined,
|
||||
PhoneOutlined,
|
||||
ManOutlined,
|
||||
EditOutlined,
|
||||
GlobalOutlined,
|
||||
EnvironmentOutlined,
|
||||
BankOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
const formState = reactive({
|
||||
username: '',
|
||||
nickname: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
gender: 0,
|
||||
introduce: '',
|
||||
blog: '',
|
||||
location: '',
|
||||
company: '',
|
||||
});
|
||||
|
||||
const handleSubmit = () => {
|
||||
// 提交表单逻辑
|
||||
console.log('表单数据:', formState);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.account-setting-info {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.account-setting-info-header {
|
||||
padding: 0 24px;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.account-setting-info-body {
|
||||
padding: 24px;
|
||||
width: 100%;
|
||||
height: calc(100% - 50px);
|
||||
|
||||
:deep(.ant-form) {
|
||||
max-width: 800px;
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.ant-input,
|
||||
.ant-input-password,
|
||||
.ant-select-selector {
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 24px;
|
||||
|
||||
.ant-btn {
|
||||
width: 120px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -47,6 +47,9 @@ const menuCSSStyle: any = reactive({
|
||||
});
|
||||
const router = useRouter();
|
||||
|
||||
// const route = useRoute();
|
||||
|
||||
|
||||
function handleClick({key}) {
|
||||
menuStore.accountSettingMenu = key;
|
||||
router.push(`/main/user/setting/${key}`);
|
||||
|
@@ -1,11 +1,209 @@
|
||||
<template>
|
||||
<div class="account-setting-storage">
|
||||
<div class="account-setting-storage-header">
|
||||
<AButton type="primary" size="middle" shape="default" @click="drawerVisible = true">
|
||||
新增存储
|
||||
</AButton>
|
||||
<AButton type="default" size="middle" shape="circle" @click="getStorageList">
|
||||
<template #icon>
|
||||
<RedoOutlined />
|
||||
</template>
|
||||
</AButton>
|
||||
</div>
|
||||
<div class="account-setting-storage-body" v-if="storageList && storageList.length>0">
|
||||
<StorageCard v-for="(item, index) in storageList" :key="index" :storage="item"/>
|
||||
</div>
|
||||
<div class="account-setting-storage-empty" v-else>
|
||||
<AEmpty description="暂无存储策略"/>
|
||||
</div>
|
||||
<ADrawer v-model:open="drawerVisible" placement="right" width="40%" title="新增存储策略">
|
||||
<AForm :model="formState" layout="vertical" @finish="onFinish" :rules="rules" ref="formRef"
|
||||
class="two-col-form">
|
||||
<AFormItem label="存储商" name="provider" class="form-item">
|
||||
<ASelect v-model:value="formState.provider" placeholder="请选择存储商">
|
||||
<ASelectOption v-for="provider in providers" :key="provider.value" :value="provider.value">
|
||||
{{ provider.label }}
|
||||
</ASelectOption>
|
||||
</ASelect>
|
||||
</AFormItem>
|
||||
|
||||
<AFormItem label="地址" name="endpoint" class="form-item">
|
||||
<AInput v-model:value="formState.endpoint" placeholder="请输入地址">
|
||||
<template #addonBefore>
|
||||
<ASelect v-model:value="protocol" style="width: 90px">
|
||||
<ASelectOption value="http://">Http://</ASelectOption>
|
||||
<ASelectOption value="https://">Https://</ASelectOption>
|
||||
</ASelect>
|
||||
</template>
|
||||
</AInput>
|
||||
</AFormItem>
|
||||
<AFormItem label="密钥Key" name="access_key" class="form-item">
|
||||
<AInput v-model:value="formState.access_key" placeholder="请输入密钥Key"/>
|
||||
</AFormItem>
|
||||
<AFormItem label="密钥Secret" name="secret_key" class="form-item">
|
||||
<AInputPassword v-model:value="formState.secret_key" placeholder="请输入密钥Secret"/>
|
||||
</AFormItem>
|
||||
|
||||
<AFormItem label="存储桶" name="bucket" class="form-item">
|
||||
<AInput v-model:value="formState.bucket" placeholder="请输入存储桶名称"/>
|
||||
</AFormItem>
|
||||
<AFormItem label="地域" name="region" class="form-item">
|
||||
<ASelect v-model:value="formState.region" placeholder="请选择地域" :disabled="!formState.provider">
|
||||
<ASelectOption
|
||||
v-for="region in regionsByProvider[formState.provider] || []"
|
||||
:key="region.value"
|
||||
:value="region.value"
|
||||
>
|
||||
{{ region.label }}
|
||||
</ASelectOption>
|
||||
</ASelect>
|
||||
</AFormItem>
|
||||
<AFormItem label="容量" name="capacity" class="form-item">
|
||||
<AInputNumber style="width: 100%;" v-model:value="formState.capacity" placeholder="请输入容量"/>
|
||||
</AFormItem>
|
||||
</AForm>
|
||||
<template #footer>
|
||||
<AFlex :vertical="false" align="center" justify="end" gap="large">
|
||||
<AButton type="default" @click="cancel">取消</AButton>
|
||||
<AButton type="primary" @click="onFinish()">提交</AButton>
|
||||
</AFlex>
|
||||
</template>
|
||||
</ADrawer>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
import StorageCard from "@/views/User/AccountSetting/components/AccountSettingStorage/StorageCard.vue";
|
||||
import {addStorageConfigApi, listUserStorageConfigApi} from "@/api/storage";
|
||||
import {message} from "ant-design-vue";
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const protocol = ref('https://');
|
||||
const rules = {
|
||||
provider: [{required: true, message: '存储商不能为空'}],
|
||||
bucket: [{required: true, message: '存储桶不能为空'}],
|
||||
endpoint: [{required: true, message: '地址不能为空'}],
|
||||
access_key: [{required: true, message: '密钥Key不能为空'}],
|
||||
secret_key: [{required: true, message: '密钥Secret不能为空'}],
|
||||
region: [{required: true, message: '地域不能为空'}],
|
||||
capacity: [{required: true, message: '容量不能为空'}],
|
||||
};
|
||||
const providers = [
|
||||
{value: 'ali', label: '阿里云OSS'},
|
||||
{value: 'tencent', label: '腾讯云COS'},
|
||||
{value: 'minio', label: 'Minio'},
|
||||
{value: 'huawei', label: '华为云OBS'},
|
||||
];
|
||||
|
||||
const regionsByProvider = {
|
||||
ali: [
|
||||
{value: 'oss-cn-hangzhou', label: '华东1(杭州)'},
|
||||
{value: 'oss-cn-shanghai', label: '华东2(上海)'},
|
||||
{value: 'oss-cn-qingdao', label: '华北1(青岛)'},
|
||||
{value: 'oss-cn-shenzhen', label: '华南1(深圳)'},
|
||||
],
|
||||
tencent: [
|
||||
{value: 'ap-beijing', label: '北京'},
|
||||
{value: 'ap-shanghai', label: '上海'},
|
||||
{value: 'ap-guangzhou', label: '广州'},
|
||||
{value: 'ap-chengdu', label: '成都'},
|
||||
],
|
||||
huawei: [
|
||||
{value: 'cn-north-1', label: '华北-北京一'},
|
||||
{value: 'cn-east-3', label: '华东-上海一'},
|
||||
{value: 'cn-south-1', label: '华南-广州'},
|
||||
],
|
||||
minio: [
|
||||
{value: 'us-east-1', label: '默认地域'},
|
||||
{value: 'custom', label: '自定义地域'},
|
||||
],
|
||||
};
|
||||
const formRef = ref();
|
||||
const formState = ref({
|
||||
provider: '',
|
||||
bucket: '',
|
||||
endpoint: '',
|
||||
access_key: '',
|
||||
secret_key: '',
|
||||
region: '',
|
||||
capacity: null,
|
||||
});
|
||||
|
||||
const onFinish = async () => {
|
||||
const valid = await formRef.value.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
const res: any = await addStorageConfigApi(formState.value);
|
||||
if (res && res.code === 200) {
|
||||
await getStorageList();
|
||||
drawerVisible.value = false;
|
||||
formRef.value.resetFields();
|
||||
} else {
|
||||
message.warn("新增存储策略失败");
|
||||
}
|
||||
};
|
||||
|
||||
function cancel() {
|
||||
formRef.value.resetFields();
|
||||
drawerVisible.value = false;
|
||||
};
|
||||
const storageList = ref<any[]>([]);
|
||||
|
||||
// 获取存储列表
|
||||
async function getStorageList() {
|
||||
const res: any = await listUserStorageConfigApi();
|
||||
if (res && res.code === 200) {
|
||||
storageList.value = res.data.records;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getStorageList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.account-setting-storage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.account-setting-storage-header {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.account-setting-storage-body {
|
||||
width: 100%;
|
||||
height: calc(100vh - 150px);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
align-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.account-setting-storage-empty {
|
||||
width: 100%;
|
||||
height: calc(100vh - 150px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.two-col-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div class="account-setting-storage-card">
|
||||
<div class="account-setting-storage-card-header">
|
||||
<div style="width: 60px; height: 60px;">
|
||||
<AAvatar :size="60" shape="circle" :src="ProviderIcon[storage.provider]"/>
|
||||
</div>
|
||||
<AFlex :vertical="true" align="flex-start" justify="space-between"
|
||||
style="height: 60px;width: 230px;">
|
||||
<div style="height: 60px;width: 230px;overflow: auto">
|
||||
<span
|
||||
style="font-size: 18px; font-weight: bold;">{{ storage.bucket }}</span>
|
||||
</div>
|
||||
<AFlex :vertical="false" align="center" justify="flex-start" style="height: 60px;width: 230px;overflow: auto">
|
||||
<ATag :color="ProviderColorMap[storage.provider]">{{ ProviderNameMap[storage.provider] }}</ATag>
|
||||
<ATag v-if="storage.endpoint">{{ storage.endpoint }}</ATag>
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
<APopconfirm
|
||||
title="确认删除?"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
placement="bottomRight"
|
||||
@confirm="deleteStorage(storage.id, storage.provider, storage.bucket)"
|
||||
>
|
||||
<template #icon>
|
||||
<question-circle-outlined style="color: red"/>
|
||||
</template>
|
||||
<AButton type="text" size="small" class="delete-icon"
|
||||
>
|
||||
<template #icon>
|
||||
<AAvatar size="small" shape="circle" :src="deleted"/>
|
||||
</template>
|
||||
</AButton>
|
||||
</APopconfirm>
|
||||
</div>
|
||||
<div class="account-setting-storage-card-content">
|
||||
<div class="account-setting-storage-card-content-item">
|
||||
<AAvatar size="small" shape="square" :src="bucket"/>
|
||||
<span style="color: #999999; font-size: 14px;">{{ storage.capacity }}GB</span>
|
||||
</div>
|
||||
<div class="account-setting-storage-card-content-item">
|
||||
<AAvatar size="small" shape="circle" :src="location"/>
|
||||
<span style="color: #999999; font-size: 14px;">{{ AliRegionMap[storage.region] }}</span>
|
||||
</div>
|
||||
<div class="account-setting-storage-card-content-item">
|
||||
<AAvatar size="small" shape="circle" :src="time"/>
|
||||
<span style="color: #999999; font-size: 14px;">{{ storage.created_at }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {defineProps} from 'vue';
|
||||
import bucket from "@/assets/svgs/bucket.svg";
|
||||
import time from "@/assets/svgs/time.svg";
|
||||
import location from "@/assets/svgs/location-album.svg";
|
||||
import deleted from "@/assets/svgs/deleted-circle.svg";
|
||||
|
||||
import {message} from "ant-design-vue";
|
||||
import {deleteStorageConfigApi} from "@/api/storage";
|
||||
import {AliRegionMap, ProviderColorMap, ProviderIcon, ProviderNameMap} from "@/constant/provider_map.ts";
|
||||
|
||||
defineProps({
|
||||
storage: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 删除存储配置
|
||||
*/
|
||||
async function deleteStorage(id: number, provider: string, bucket: string) {
|
||||
const res: any = await deleteStorageConfigApi(id, provider, bucket);
|
||||
if (res && res.code === 200) {
|
||||
message.success("删除成功");
|
||||
} else {
|
||||
message.error("删除失败");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.account-setting-storage-card {
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
border-radius: 10px;
|
||||
background-color: white;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&:hover .delete-icon {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.account-setting-storage-card-header {
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
transform: scale(0);
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
.account-setting-storage-card-content {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
|
||||
.account-setting-storage-card-content-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -34,24 +34,6 @@
|
||||
</template>
|
||||
<span class="ant-menu-item-title">动态</span>
|
||||
</AMenuItem>
|
||||
<AMenuItem key="info" :style="menuCSSStyle" title="个人信息">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" size="small" :src="personal_info"/>
|
||||
</template>
|
||||
<span class="ant-menu-item-title">个人信息</span>
|
||||
</AMenuItem>
|
||||
<AMenuItem key="analysis" :style="menuCSSStyle" title="数据分析">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" size="small" :src="data_analysis"/>
|
||||
</template>
|
||||
<span class="ant-menu-item-title">数据分析</span>
|
||||
</AMenuItem>
|
||||
<AMenuItem key="share" :style="menuCSSStyle" title="我的分享">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" size="small" :src="share"/>
|
||||
</template>
|
||||
<span class="ant-menu-item-title">我的分享</span>
|
||||
</AMenuItem>
|
||||
<AMenuItem key="setting" :style="menuCSSStyle" title="设置">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" size="small" :src="setting"/>
|
||||
@@ -60,7 +42,8 @@
|
||||
</AMenuItem>
|
||||
</AMenu>
|
||||
<div class="personal-center-content-container">
|
||||
<router-view/>
|
||||
<router-view>
|
||||
</router-view>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,11 +52,8 @@
|
||||
import Header from "@/layout/default/Header/Header.vue";
|
||||
import useStore from "@/store";
|
||||
import home from "@/assets/svgs/home.svg";
|
||||
import data_analysis from "@/assets/svgs/data_analysis.svg";
|
||||
import dynamic from "@/assets/svgs/dynamic.svg";
|
||||
import share from "@/assets/svgs/share.svg";
|
||||
import setting from "@/assets/svgs/setting.svg";
|
||||
import personal_info from "@/assets/svgs/personal-center.svg";
|
||||
|
||||
|
||||
const userStore = useStore().user;
|
||||
@@ -92,7 +72,7 @@ function handleClick({key}) {
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.personal-center {
|
||||
background-color: #eaeef6;
|
||||
//background-color: #eaeef6;
|
||||
|
||||
.personal-center-header {
|
||||
width: 100%;
|
||||
@@ -169,9 +149,10 @@ function handleClick({key}) {
|
||||
}
|
||||
|
||||
.personal-center-content-container {
|
||||
width: 100%;
|
||||
height: calc(100vh - 250px);
|
||||
display: flex;
|
||||
width: calc(100% - 40px);
|
||||
height: calc(100vh - 290px);
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
@@ -1,11 +1,307 @@
|
||||
<template>
|
||||
<div class="user-center-dynamic" ref="chartRef">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const chartRef = ref<any>(null);
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
const chartInstance = echarts.init(chartRef.value);
|
||||
|
||||
const colorList = ["#9E87FF", "#73DDFF", "#fe9a8b", "#F56948", "#9E87FF"];
|
||||
const option = {
|
||||
backgroundColor: "#fff",
|
||||
title: {
|
||||
text: "最近七天分享统计",
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
fontWeight: 400,
|
||||
},
|
||||
left: "center",
|
||||
top: "5%",
|
||||
},
|
||||
legend: {
|
||||
icon: "circle",
|
||||
top: "5%",
|
||||
right: "5%",
|
||||
itemWidth: 6,
|
||||
itemGap: 20,
|
||||
textStyle: {
|
||||
color: "#556677",
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
label: {
|
||||
show: true,
|
||||
backgroundColor: "#fff",
|
||||
color: "#556677",
|
||||
borderColor: "rgba(0,0,0,0)",
|
||||
shadowColor: "rgba(0,0,0,0)",
|
||||
shadowOffsetY: 0,
|
||||
},
|
||||
lineStyle: {
|
||||
width: 0,
|
||||
},
|
||||
},
|
||||
backgroundColor: "#fff",
|
||||
textStyle: {
|
||||
color: "#5c6c7c",
|
||||
},
|
||||
padding: [10, 10],
|
||||
extraCssText: "box-shadow: 1px 0 2px 0 rgba(163,163,163,0.5)",
|
||||
},
|
||||
grid: {
|
||||
top: "15%",
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: "category",
|
||||
data: ["2025-3-5", "2025-3-6", "2025-3-7", "2025-3-8", "2025-3-9", "2025-3-10", "2025-3-11"],
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: "#DCE2E8",
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0,
|
||||
textStyle: {
|
||||
color: "#556677",
|
||||
},
|
||||
// 默认x轴字体大小
|
||||
fontSize: 12,
|
||||
// margin:文字到x轴的距离
|
||||
margin: 15,
|
||||
},
|
||||
axisPointer: {
|
||||
label: {
|
||||
// padding: [11, 5, 7],
|
||||
padding: [0, 0, 10, 0],
|
||||
/*
|
||||
除了padding[0]建议必须是0之外,其他三项可随意设置
|
||||
|
||||
和CSSpadding相同,[上,右,下,左]
|
||||
|
||||
如果需要下边线超出文字,设左右padding即可,注:左右padding最好相同
|
||||
|
||||
padding[2]的10:
|
||||
|
||||
10 = 文字距下边线的距离 + 下边线的宽度
|
||||
|
||||
如:UI图中文字距下边线距离为7 下边线宽度为2
|
||||
|
||||
则padding: [0, 0, 9, 0]
|
||||
|
||||
*/
|
||||
// 这里的margin和axisLabel的margin要一致!
|
||||
margin: 15,
|
||||
// 移入时的字体大小
|
||||
fontSize: 12,
|
||||
backgroundColor: {
|
||||
type: "linear",
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: "#fff", // 0% 处的颜色
|
||||
},
|
||||
{
|
||||
// offset: 0.9,
|
||||
offset: 0.86,
|
||||
/*
|
||||
0.86 = (文字 + 文字距下边线的距离)/(文字 + 文字距下边线的距离 + 下边线的宽度)
|
||||
|
||||
*/
|
||||
color: "#fff", // 0% 处的颜色
|
||||
},
|
||||
{
|
||||
offset: 0.86,
|
||||
color: "#33c0cd", // 0% 处的颜色
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: "#33c0cd", // 100% 处的颜色
|
||||
},
|
||||
],
|
||||
global: false, // 缺省为 false
|
||||
},
|
||||
},
|
||||
},
|
||||
boundaryGap: false,
|
||||
},
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "#DCE2E8",
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
color: "#556677",
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "value",
|
||||
position: "right",
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
color: "#556677",
|
||||
},
|
||||
formatter: "{value}",
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "#DCE2E8",
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "浏览次数",
|
||||
type: "line",
|
||||
data: [10, 10, 30, 12, 15, 3, 7],
|
||||
symbolSize: 1,
|
||||
symbol: "circle",
|
||||
smooth: true,
|
||||
yAxisIndex: 0,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
width: 5,
|
||||
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
|
||||
{
|
||||
offset: 0,
|
||||
color: "#9effff",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: "#9E87FF",
|
||||
},
|
||||
]),
|
||||
shadowColor: "rgba(158,135,255, 0.3)",
|
||||
shadowBlur: 10,
|
||||
shadowOffsetY: 20,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: colorList[0],
|
||||
borderColor: colorList[0],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "浏览人数",
|
||||
type: "line",
|
||||
data: [5, 12, 11, 14, 25, 16, 10],
|
||||
symbolSize: 1,
|
||||
symbol: "circle",
|
||||
smooth: true,
|
||||
yAxisIndex: 0,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
width: 5,
|
||||
color: new echarts.graphic.LinearGradient(1, 1, 0, 0, [
|
||||
{
|
||||
offset: 0,
|
||||
color: "#73DD39",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: "#73DDFF",
|
||||
},
|
||||
]),
|
||||
shadowColor: "rgba(115,221,255, 0.3)",
|
||||
shadowBlur: 10,
|
||||
shadowOffsetY: 20,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: colorList[1],
|
||||
borderColor: colorList[1],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "发布次数",
|
||||
type: "line",
|
||||
data: [150, 120, 170, 140, 500, 160, 110],
|
||||
symbolSize: 1,
|
||||
yAxisIndex: 1,
|
||||
symbol: "circle",
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
width: 5,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{
|
||||
offset: 0,
|
||||
color: "#fe9a",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: "#fe9a8b",
|
||||
},
|
||||
]),
|
||||
shadowColor: "rgba(254,154,139, 0.3)",
|
||||
shadowBlur: 10,
|
||||
shadowOffsetY: 20,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: colorList[2],
|
||||
borderColor: colorList[2],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
chartInstance.setOption(option);
|
||||
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
if (chartRef.value) {
|
||||
const chartInstance = echarts.getInstanceByDom(chartRef.value);
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.user-center-dynamic {
|
||||
width: calc(100vw - 40px);
|
||||
height: calc(100vh - 290px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,11 +1,256 @@
|
||||
<template>
|
||||
<div class="user-center-home">
|
||||
<div class="user-center-home-left">
|
||||
<div class="user-center-home-left-top">
|
||||
<div class="user-center-home-left-top-card"
|
||||
style="background: linear-gradient(102.74deg, rgb(66, 230, 171) -7.03%, rgb(103, 235, 187) 97.7%);">
|
||||
<div class="user-center-home-left-top-card-top">
|
||||
<div class="user-center-home-left-top-card-top-avatar">
|
||||
<AAvatar :size="60" shape="square" :src="imageIcon"/>
|
||||
</div>
|
||||
<div class="user-center-home-left-top-card-top-name">
|
||||
<span style="font-size: 2.8vh;color: rgba(255, 255, 255, 0.6); font-weight: bold;">图片总数</span>
|
||||
<span style="font-size: 3.8vh;font-weight: bold;color: #ffffff">50</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-center-home-left-top-card-bottom">
|
||||
<span style="font-size: 2.3vh;color: rgba(255, 255, 255, 0.6); font-weight: bold;">今日上传</span>
|
||||
<span style="font-size: 3vh;font-weight: bold;color: #ffffff">+10</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-center-home-left-top-card"
|
||||
style="background: linear-gradient(101.63deg, rgb(82, 138, 250) -12.83%, rgb(122, 167, 255) 100%);">
|
||||
<div class="user-center-home-left-top-card-top">
|
||||
<div class="user-center-home-left-top-card-top-avatar">
|
||||
<AAvatar :size="60" shape="square" :src="shareIcon"/>
|
||||
</div>
|
||||
<div class="user-center-home-left-top-card-top-name">
|
||||
<span style="font-size: 2.8vh;color: rgba(255, 255, 255, 0.6); font-weight: bold;">分享总数</span>
|
||||
<span style="font-size: 3.8vh;font-weight: bold;color: white">50</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-center-home-left-top-card-bottom">
|
||||
<span style="font-size: 2.3vh;color: rgba(255, 255, 255, 0.6); font-weight: bold;">今日上传</span>
|
||||
<span style="font-size: 2.8vh;font-weight: bold;color: white">+10</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-center-home-left-top-card"
|
||||
style="background: linear-gradient(102.99deg, rgb(126, 92, 255) 3.18%, rgb(162, 139, 255) 102.52%);">
|
||||
<div class="user-center-home-left-top-card-top">
|
||||
<div class="user-center-home-left-top-card-top-avatar">
|
||||
<AAvatar :size="60" shape="square" :src="fileSize"/>
|
||||
</div>
|
||||
<div class="user-center-home-left-top-card-top-name">
|
||||
<span style="font-size: 2.8vh;color: rgba(255, 255, 255, 0.6); font-weight: bold;">文件总量</span>
|
||||
<span style="font-size: 3.8vh;font-weight: bold;color: white">50</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-center-home-left-top-card-bottom">
|
||||
<span style="font-size: 2.3vh;color: rgba(255, 255, 255, 0.6);font-weight: bold;">今日上传</span>
|
||||
<span style="font-size: 2.8vh;font-weight: bold;color: white">+10</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-center-home-left-bottom">
|
||||
<span style="font-size: 16px; font-weight: bold; margin-left: 20px;">文件上传热力图</span>
|
||||
<HeatmapPro :contributions="timeValue" :width="'100%'" :height="'100%'"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-center-home-right">
|
||||
<ACard class="user-center-home-right-card" :hoverable="false">
|
||||
|
||||
</ACard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
import HeatmapPro from "@/components/HeatmapPro/HeatmapPro.vue";
|
||||
import imageIcon from "@/assets/svgs/image-icon.svg";
|
||||
import shareIcon from "@/assets/svgs/share-icon.svg";
|
||||
import fileSize from "@/assets/svgs/file-size.svg";
|
||||
|
||||
const timeValue = [
|
||||
{date: "2024-08-02", count: 1},
|
||||
{date: "2024-08-03", count: 2},
|
||||
{date: "2024-08-04", count: 3},
|
||||
{date: "2024-08-05", count: 4},
|
||||
{date: "2024-08-06", count: 5},
|
||||
{date: "2024-08-07", count: 6},
|
||||
{date: "2024-08-08", count: 5},
|
||||
{date: "2024-08-15", count: 8},
|
||||
{date: "2024-08-22", count: 3},
|
||||
{date: "2024-08-29", count: 4},
|
||||
{date: "2024-09-05", count: 6},
|
||||
{date: "2024-09-28", count: 6},
|
||||
{date: "2024-09-22", count: 6},
|
||||
{date: "2024-09-23", count: 6},
|
||||
{date: "2024-09-24", count: 6},
|
||||
{date: "2024-10-04", count: 6},
|
||||
{date: "2024-10-02", count: 6},
|
||||
{date: "2024-10-10", count: 6},
|
||||
{date: "2024-10-11", count: 6},
|
||||
{date: "2024-10-17", count: 6},
|
||||
{date: "2024-10-19", count: 6},
|
||||
{date: "2024-10-23", count: 6},
|
||||
{date: "2024-10-27", count: 6},
|
||||
{date: "2024-10-28", count: 6},
|
||||
{date: "2024-10-29", count: 6},
|
||||
{date: "2024-11-22", count: 6},
|
||||
{date: "2024-11-30", count: 6},
|
||||
{date: "2024-12-08", count: 6},
|
||||
{date: "2024-12-16", count: 6},
|
||||
{date: "2024-12-24", count: 6},
|
||||
{date: "2025-01-01", count: 6},
|
||||
{date: "2025-01-09", count: 6},
|
||||
{date: "2025-01-16", count: 6},
|
||||
{date: "2025-01-22", count: 6},
|
||||
{date: "2025-01-28", count: 6},
|
||||
{date: "2025-02-03", count: 6},
|
||||
{date: "2025-02-09", count: 6},
|
||||
{date: "2025-02-15", count: 6},
|
||||
{date: "2025-02-21", count: 6},
|
||||
{date: "2025-03-21", count: 6},
|
||||
{date: "2025-03-22", count: 6},
|
||||
{date: "2025-03-23", count: 6},
|
||||
{date: "2025-03-24", count: 6},
|
||||
{date: "2025-03-25", count: 6},
|
||||
{date: "2025-03-26", count: 6},
|
||||
{date: "2025-03-27", count: 6},
|
||||
{date: "2025-03-28", count: 6},
|
||||
{date: "2025-03-31", count: 6},
|
||||
{date: "2025-04-03", count: 6},
|
||||
{date: "2025-04-07", count: 6},
|
||||
{date: "2025-04-04", count: 6},
|
||||
{date: "2025-04-10", count: 6},
|
||||
{date: "2025-04-11", count: 6},
|
||||
{date: "2025-04-14", count: 6},
|
||||
{date: "2025-04-17", count: 6},
|
||||
{date: "2025-04-18", count: 6},
|
||||
{date: "2025-04-21", count: 6},
|
||||
{date: "2025-04-24", count: 6},
|
||||
{date: "2025-04-25", count: 6},
|
||||
{date: "2025-04-28", count: 6},
|
||||
{date: "2025-05-01", count: 6},
|
||||
{date: "2025-05-02", count: 6},
|
||||
{date: "2025-05-05", count: 6},
|
||||
{date: "2025-05-08", count: 6},
|
||||
{date: "2025-05-09", count: 6},
|
||||
{date: "2025-05-12", count: 6},
|
||||
{date: "2025-05-15", count: 6},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
6666
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.user-center-home {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: 40px;
|
||||
|
||||
.user-center-home-left {
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 20px;
|
||||
|
||||
.user-center-home-left-top {
|
||||
width: 100%;
|
||||
height: 25vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
|
||||
|
||||
.user-center-home-left-top-card {
|
||||
width: 28%;
|
||||
height: 90%;
|
||||
background-color: #fff;
|
||||
border-radius: 1.8vh;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
padding-inline: 10px;
|
||||
padding-block: 10px;
|
||||
gap: 10px;
|
||||
border: 1px solid #eee;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.user-center-home-left-top-card-top {
|
||||
width: 100%;
|
||||
height: 60%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
.user-center-home-left-top-card-top-avatar {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.user-center-home-left-top-card-top-name {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.user-center-home-left-top-card-bottom {
|
||||
width: 100%;
|
||||
height: 40%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-center-home-left-bottom {
|
||||
width: 100%;
|
||||
height: 58%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
background-color: #fff;
|
||||
border-radius: 1.8vh;
|
||||
border: 1px solid #eee;
|
||||
padding-block: 20px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.user-center-home-right {
|
||||
width: 39%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
|
||||
.user-center-home-right-card {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
//margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,11 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
@@ -1,11 +1,67 @@
|
||||
<template>
|
||||
<div class="user-center-setting">
|
||||
<h2>用户设置</h2>
|
||||
<div class="user-center-setting-content">
|
||||
<div class="user-center-setting-item">
|
||||
<span class="user-center-setting-item-title">是否开启AI识别</span>
|
||||
<ASwitch/>
|
||||
</div>
|
||||
<div class="user-center-setting-item">
|
||||
<span class="user-center-setting-item-title">是否开启手机上传</span>
|
||||
<ASwitch/>
|
||||
</div>
|
||||
<div class="user-center-setting-item">
|
||||
<span class="user-center-setting-item-title">是否公开个人资料</span>
|
||||
<ASwitch/>
|
||||
</div>
|
||||
<div class="user-center-setting-item">
|
||||
<span class="user-center-setting-item-title">是否公开个人资料</span>
|
||||
<ASwitch/>
|
||||
</div>
|
||||
<div class="user-center-setting-item">
|
||||
<span class="user-center-setting-item-title">是否开启搜索记录</span>
|
||||
<ASwitch/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.user-center-setting {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
|
||||
.user-center-setting-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
gap: 50px;
|
||||
|
||||
.user-center-setting-item {
|
||||
width: 350px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.user-center-setting-item-title {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 22px;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,11 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
@@ -9,7 +9,6 @@ import {AntDesignVueResolver} from 'unplugin-vue-components/resolvers';
|
||||
import AutoImport from 'unplugin-auto-import/vite';
|
||||
import {chunkSplitPlugin} from 'vite-plugin-chunk-split';
|
||||
import vitePluginBundleObfuscator from 'vite-plugin-bundle-obfuscator';
|
||||
|
||||
const defaultObfuscatorConfig: any = {
|
||||
excludes: [],
|
||||
enable: true,
|
||||
|