♻️ refactored code
This commit is contained in:
@@ -1,2 +1,7 @@
|
|||||||
node_modules
|
node_modules
|
||||||
*.log
|
*.log
|
||||||
|
# 忽略大模型目录
|
||||||
|
dist/tfjs/
|
||||||
|
dist/caffemodel/
|
||||||
|
|
||||||
|
default.conf
|
||||||
|
22
Dockerfile
22
Dockerfile
@@ -1,12 +1,30 @@
|
|||||||
|
|
||||||
FROM nginx:latest
|
FROM nginx:alpine
|
||||||
|
|
||||||
LABEL maintainer="landaiqing <<landaiqing@126.com>>"
|
LABEL maintainer="landaiqing <<landaiqing@126.com>>"
|
||||||
|
|
||||||
ENV TimeZone=Asia/Shanghai
|
ENV TimeZone=Asia/Shanghai
|
||||||
|
|
||||||
|
# 创建空目录用于后续挂载
|
||||||
|
RUN mkdir -p /usr/share/nginx/html/tfjs \
|
||||||
|
&& mkdir -p /usr/share/nginx/html/caffemodel \
|
||||||
|
&& mkdir -p /etc/nginx/conf.d
|
||||||
|
|
||||||
COPY dist/ /usr/share/nginx/html/
|
COPY dist/ /usr/share/nginx/html/
|
||||||
|
|
||||||
COPY default.conf /etc/nginx/conf.d/default.conf
|
# 强制删除模型目录(确保后续挂载覆盖)
|
||||||
|
RUN rm -rf /usr/share/nginx/html/tfjs \
|
||||||
|
&& rm -rf /usr/share/nginx/html/caffemodel \
|
||||||
|
&& rm -f /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
#docker run -d \
|
||||||
|
# -v /your_host/nginx.conf:/etc/nginx/nginx.conf \
|
||||||
|
# -v /your_host/conf.d/:/etc/nginx/conf.d/ \
|
||||||
|
# -v /your_host/tfjs:/usr/share/nginx/html/tfjs \
|
||||||
|
# -v /your_host/caffemodel:/usr/share/nginx/html/caffemodel \
|
||||||
|
# --restart unless-stopped \
|
||||||
|
# -p 80:80 \
|
||||||
|
# --name your_image
|
||||||
|
# your_image
|
||||||
|
3
auto-import.d.ts
vendored
3
auto-import.d.ts
vendored
@@ -6,7 +6,10 @@
|
|||||||
// biome-ignore lint: disable
|
// biome-ignore lint: disable
|
||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
|
const AppstoreOutlined: typeof import('@ant-design/icons-vue')['AppstoreOutlined']
|
||||||
const EffectScope: typeof import('vue')['EffectScope']
|
const EffectScope: typeof import('vue')['EffectScope']
|
||||||
|
const MailOutlined: typeof import('@ant-design/icons-vue')['MailOutlined']
|
||||||
|
const SettingOutlined: typeof import('@ant-design/icons-vue')['SettingOutlined']
|
||||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||||
|
29
components.d.ts
vendored
29
components.d.ts
vendored
@@ -14,6 +14,7 @@ declare module 'vue' {
|
|||||||
AButton: typeof import('ant-design-vue/es')['Button']
|
AButton: typeof import('ant-design-vue/es')['Button']
|
||||||
ACard: typeof import('ant-design-vue/es')['Card']
|
ACard: typeof import('ant-design-vue/es')['Card']
|
||||||
ACascader: typeof import('ant-design-vue/es')['Cascader']
|
ACascader: typeof import('ant-design-vue/es')['Cascader']
|
||||||
|
AccountSetting: typeof import('./src/views/User/AccountSetting/AccountSetting.vue')['default']
|
||||||
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
||||||
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
||||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||||
@@ -28,9 +29,9 @@ declare module 'vue' {
|
|||||||
AImage: typeof import('ant-design-vue/es')['Image']
|
AImage: typeof import('ant-design-vue/es')['Image']
|
||||||
AImagePreviewGroup: typeof import('ant-design-vue/es')['ImagePreviewGroup']
|
AImagePreviewGroup: typeof import('ant-design-vue/es')['ImagePreviewGroup']
|
||||||
AInput: typeof import('ant-design-vue/es')['Input']
|
AInput: typeof import('ant-design-vue/es')['Input']
|
||||||
AInputGroup: typeof import('ant-design-vue/es')['InputGroup']
|
|
||||||
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
||||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||||
|
AlbumCard: typeof import('./src/views/Album/Phoalbum/AlbumCard.vue')['default']
|
||||||
AlbumShareModal: typeof import('./src/views/Album/Phoalbum/AlbumShareModal.vue')['default']
|
AlbumShareModal: typeof import('./src/views/Album/Phoalbum/AlbumShareModal.vue')['default']
|
||||||
AllPhoto: typeof import('./src/views/Photograph/AllPhoto/AllPhoto.vue')['default']
|
AllPhoto: typeof import('./src/views/Photograph/AllPhoto/AllPhoto.vue')['default']
|
||||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||||
@@ -43,7 +44,6 @@ declare module 'vue' {
|
|||||||
AProgress: typeof import('ant-design-vue/es')['Progress']
|
AProgress: typeof import('ant-design-vue/es')['Progress']
|
||||||
AQrcode: typeof import('ant-design-vue/es')['QRCode']
|
AQrcode: typeof import('ant-design-vue/es')['QRCode']
|
||||||
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
||||||
ArrowDownOutlined: typeof import('@ant-design/icons-vue')['ArrowDownOutlined']
|
|
||||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||||
@@ -79,15 +79,16 @@ declare module 'vue' {
|
|||||||
ForgetPage: typeof import('./src/views/Forget/ForgetPage.vue')['default']
|
ForgetPage: typeof import('./src/views/Forget/ForgetPage.vue')['default']
|
||||||
GradientText: typeof import('./src/components/MyUI/GradientText/GradientText.vue')['default']
|
GradientText: typeof import('./src/components/MyUI/GradientText/GradientText.vue')['default']
|
||||||
ImageShare: typeof import('./src/views/Share/ImageShare/ImageShare.vue')['default']
|
ImageShare: typeof import('./src/views/Share/ImageShare/ImageShare.vue')['default']
|
||||||
ImageToolbar: typeof import('./src/views/Photograph/ImageToolbar/ImageToolbar.vue')['default']
|
ImageToolbar: typeof import('./src/components/ImageToolbar/ImageToolbar.vue')['default']
|
||||||
ImageUpload: typeof import('./src/views/Photograph/ImageUpload/ImageUpload.vue')['default']
|
ImageUpload: typeof import('./src/components/ImageUpload/ImageUpload.vue')['default']
|
||||||
ItalicOutlined: typeof import('@ant-design/icons-vue')['ItalicOutlined']
|
ImageWaterfallList: typeof import('./src/components/ImageWaterfallList/ImageWaterfallList.vue')['default']
|
||||||
|
Index: typeof import('./src/views/Album/ThingAlbum/Index.vue')['default']
|
||||||
LandingPage: typeof import('./src/views/Landing/LandingPage.vue')['default']
|
LandingPage: typeof import('./src/views/Landing/LandingPage.vue')['default']
|
||||||
LeftOutlined: typeof import('@ant-design/icons-vue')['LeftOutlined']
|
LeftOutlined: typeof import('@ant-design/icons-vue')['LeftOutlined']
|
||||||
LinkOutlined: typeof import('@ant-design/icons-vue')['LinkOutlined']
|
LinkOutlined: typeof import('@ant-design/icons-vue')['LinkOutlined']
|
||||||
LoadingGraphic: typeof import('./src/components/LoadingGraphic/LoadingGraphic.vue')['default']
|
LoadingGraphic: typeof import('./src/components/LoadingGraphic/LoadingGraphic.vue')['default']
|
||||||
LocationAlbum: typeof import('./src/views/Album/LocationAlbum/LocationAlbum.vue')['default']
|
|
||||||
LocationAlbumDetail: typeof import('./src/views/Album/LocationAlbum/LocationAlbumDetail.vue')['default']
|
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']
|
LocationAlbumList: typeof import('./src/views/Album/LocationAlbum/LocationAlbumList.vue')['default']
|
||||||
LockOutlined: typeof import('@ant-design/icons-vue')['LockOutlined']
|
LockOutlined: typeof import('@ant-design/icons-vue')['LockOutlined']
|
||||||
LoginFooter: typeof import('./src/views/Login/LoginFooter.vue')['default']
|
LoginFooter: typeof import('./src/views/Login/LoginFooter.vue')['default']
|
||||||
@@ -97,13 +98,14 @@ declare module 'vue' {
|
|||||||
NotFound: typeof import('./src/views/404/NotFound.vue')['default']
|
NotFound: typeof import('./src/views/404/NotFound.vue')['default']
|
||||||
OrderedListOutlined: typeof import('@ant-design/icons-vue')['OrderedListOutlined']
|
OrderedListOutlined: typeof import('@ant-design/icons-vue')['OrderedListOutlined']
|
||||||
ParameterSetting: typeof import('./src/views/Upscale/ParameterSetting.vue')['default']
|
ParameterSetting: typeof import('./src/views/Upscale/ParameterSetting.vue')['default']
|
||||||
PeopleAlbum: typeof import('./src/views/Album/PeopleAlbum/PeopleAlbum.vue')['default']
|
|
||||||
PeopleAlbumDetail: typeof import('./src/views/Album/PeopleAlbum/PeopleAlbumDetail.vue')['default']
|
PeopleAlbumDetail: typeof import('./src/views/Album/PeopleAlbum/PeopleAlbumDetail.vue')['default']
|
||||||
|
PeopleAlbumIndex: typeof import('./src/views/Album/PeopleAlbum/PeopleAlbumIndex.vue')['default']
|
||||||
PeopleAlbumList: typeof import('./src/views/Album/PeopleAlbum/PeopleAlbumList.vue')['default']
|
PeopleAlbumList: typeof import('./src/views/Album/PeopleAlbum/PeopleAlbumList.vue')['default']
|
||||||
Phoalbum: typeof import('./src/views/Album/Phoalbum/Phoalbum.vue')['default']
|
PeopleAlbumToolbar: typeof import('./src/views/Album/PeopleAlbum/PeopleAlbumToolbar.vue')['default']
|
||||||
|
PersonalCenter: typeof import('./src/views/User/PersonalCenter/PersonalCenter.vue')['default']
|
||||||
PhoalbumDetail: typeof import('./src/views/Album/Phoalbum/PhoalbumDetail.vue')['default']
|
PhoalbumDetail: typeof import('./src/views/Album/Phoalbum/PhoalbumDetail.vue')['default']
|
||||||
|
PhoalbumIndex: typeof import('./src/views/Album/Phoalbum/PhoalbumIndex.vue')['default']
|
||||||
PhoalbumList: typeof import('./src/views/Album/Phoalbum/PhoalbumList.vue')['default']
|
PhoalbumList: typeof import('./src/views/Album/Phoalbum/PhoalbumList.vue')['default']
|
||||||
PhoneOutlined: typeof import('@ant-design/icons-vue')['PhoneOutlined']
|
|
||||||
PhotoStack: typeof import('./src/components/PhotoStack/PhotoStack.vue')['default']
|
PhotoStack: typeof import('./src/components/PhotoStack/PhotoStack.vue')['default']
|
||||||
PlusOutlined: typeof import('@ant-design/icons-vue')['PlusOutlined']
|
PlusOutlined: typeof import('@ant-design/icons-vue')['PlusOutlined']
|
||||||
PlusSquareOutlined: typeof import('@ant-design/icons-vue')['PlusSquareOutlined']
|
PlusSquareOutlined: typeof import('@ant-design/icons-vue')['PlusSquareOutlined']
|
||||||
@@ -129,14 +131,19 @@ declare module 'vue' {
|
|||||||
Spin: typeof import('./src/components/MyUI/Spin/Spin.vue')['default']
|
Spin: typeof import('./src/components/MyUI/Spin/Spin.vue')['default']
|
||||||
StarButton: typeof import('./src/components/StarButton/StarButton.vue')['default']
|
StarButton: typeof import('./src/components/StarButton/StarButton.vue')['default']
|
||||||
TabletOutlined: typeof import('@ant-design/icons-vue')['TabletOutlined']
|
TabletOutlined: typeof import('@ant-design/icons-vue')['TabletOutlined']
|
||||||
ThingAlbum: typeof import('./src/views/Album/ThingAlbum/ThingAlbum.vue')['default']
|
|
||||||
ThingAlbumDetail: typeof import('./src/views/Album/ThingAlbum/ThingAlbumDetail.vue')['default']
|
ThingAlbumDetail: typeof import('./src/views/Album/ThingAlbum/ThingAlbumDetail.vue')['default']
|
||||||
|
ThingAlbumIndex: typeof import('./src/views/Album/ThingAlbum/ThingAlbumIndex.vue')['default']
|
||||||
ThingAlbumList: typeof import('./src/views/Album/ThingAlbum/ThingAlbumList.vue')['default']
|
ThingAlbumList: typeof import('./src/views/Album/ThingAlbum/ThingAlbumList.vue')['default']
|
||||||
Tooltip: typeof import('./src/components/MyUI/Tooltip/Tooltip.vue')['default']
|
Tooltip: typeof import('./src/components/MyUI/Tooltip/Tooltip.vue')['default']
|
||||||
UploadImage: typeof import('./src/views/Upscale/UploadImage.vue')['default']
|
UploadImage: typeof import('./src/views/Upscale/UploadImage.vue')['default']
|
||||||
UploadOutlined: typeof import('@ant-design/icons-vue')['UploadOutlined']
|
|
||||||
Upscale: typeof import('./src/views/Upscale/index.vue')['default']
|
Upscale: typeof import('./src/views/Upscale/index.vue')['default']
|
||||||
UpscalePhoneUpload: typeof import('./src/views/Phone/UpscalePhoneUpload/UpscalePhoneUpload.vue')['default']
|
UpscalePhoneUpload: typeof import('./src/views/Phone/UpscalePhoneUpload/UpscalePhoneUpload.vue')['default']
|
||||||
|
UserCenterAnalysis: typeof import('./src/views/User/PersonalCenter/components/UserCenterAnalysis/UserCenterAnalysis.vue')['default']
|
||||||
|
UserCenterDynamic: typeof import('./src/views/User/PersonalCenter/components/UserCenterDynamic/UserCenterDynamic.vue')['default']
|
||||||
|
UserCenterHome: typeof import('./src/views/User/PersonalCenter/components/UserCenterHome/UserCenterHome.vue')['default']
|
||||||
|
UserCenterInfo: typeof import('./src/views/User/PersonalCenter/components/UserCenterInfo/UserCenterInfo.vue')['default']
|
||||||
|
UserCenterSetting: typeof import('./src/views/User/PersonalCenter/components/UserCenterSetting/UserCenterSetting.vue')['default']
|
||||||
|
UserCenterShare: typeof import('./src/views/User/PersonalCenter/components/UserCenterShare/UserCenterShare.vue')['default']
|
||||||
UserInfoCard: typeof import('./src/components/CommentReply/src/UserInfoCard/UserInfoCard.vue')['default']
|
UserInfoCard: typeof import('./src/components/CommentReply/src/UserInfoCard/UserInfoCard.vue')['default']
|
||||||
UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined']
|
UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined']
|
||||||
VueCompareImage: typeof import('./src/components/VueCompareImage/VueCompareImage.vue')['default']
|
VueCompareImage: typeof import('./src/components/VueCompareImage/VueCompareImage.vue')['default']
|
||||||
|
@@ -27,7 +27,7 @@ server {
|
|||||||
}
|
}
|
||||||
location /sys/ {
|
location /sys/ {
|
||||||
rewrite ^/sys/(.*)$ /$1 break;
|
rewrite ^/sys/(.*)$ /$1 break;
|
||||||
proxy_pass http://127.0.0.1:80;
|
proxy_pass http://landaiqing.cn:80;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
15
package.json
15
package.json
@@ -7,7 +7,7 @@
|
|||||||
"dev": "vite --mode development --host",
|
"dev": "vite --mode development --host",
|
||||||
"build": "vue-tsc -b --noEmit && vite build --mode production",
|
"build": "vue-tsc -b --noEmit && vite build --mode production",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"docker-build": "docker build -t schisandra/schisandra-cloud-album-front ."
|
"docker-build": "docker build -t landaiqing/schisandra-album-front:v1.0.0 ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alova/adapter-axios": "^2.0.13",
|
"@alova/adapter-axios": "^2.0.13",
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"alova": "^3.2.8",
|
"alova": "^3.2.8",
|
||||||
"animejs": "^3.2.2",
|
"animejs": "^3.2.2",
|
||||||
"ant-design-vue": "^4.2.6",
|
"ant-design-vue": "^4.2.6",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.8.1",
|
||||||
"browser-image-compression": "^2.0.2",
|
"browser-image-compression": "^2.0.2",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"nsfwjs": "^4.2.1",
|
"nsfwjs": "^4.2.1",
|
||||||
|
"opencv-qr": "^0.7.0",
|
||||||
"pinia": "^3.0.1",
|
"pinia": "^3.0.1",
|
||||||
"pinia-plugin-persistedstate-2": "^2.0.28",
|
"pinia-plugin-persistedstate-2": "^2.0.28",
|
||||||
"qrcode": "^1",
|
"qrcode": "^1",
|
||||||
@@ -77,18 +78,18 @@
|
|||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"eslint-plugin-vue": "^9.32.0",
|
"eslint-plugin-vue": "^9.32.0",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"sass": "^1.85.0",
|
"sass": "^1.85.1",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"typescript-eslint": "^8.24.1",
|
"typescript-eslint": "^8.25.0",
|
||||||
"unplugin-vue-components": "^28.4.0",
|
"unplugin-vue-components": "^28.4.0",
|
||||||
"vite": "^6.1.1",
|
"vite": "^6.2.0",
|
||||||
"vite-plugin-bundle-obfuscator": "1.4.1",
|
"vite-plugin-bundle-obfuscator": "1.4.1",
|
||||||
"vite-plugin-chunk-split": "^0.5.0",
|
"vite-plugin-chunk-split": "^0.5.0",
|
||||||
"vue-tsc": "2.2.2"
|
"vue-tsc": "2.2.4"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"vite-plugin-chunk-split": {
|
"vite-plugin-chunk-split": {
|
||||||
"vite": "^6.1.1"
|
"vite": "^6.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
public/caffemodel/detect.caffemodel
Normal file
BIN
public/caffemodel/detect.caffemodel
Normal file
Binary file not shown.
2716
public/caffemodel/detect.prototxt
Normal file
2716
public/caffemodel/detect.prototxt
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/caffemodel/sr.caffemodel
Normal file
BIN
public/caffemodel/sr.caffemodel
Normal file
Binary file not shown.
403
public/caffemodel/sr.prototxt
Normal file
403
public/caffemodel/sr.prototxt
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
layer {
|
||||||
|
name: "data"
|
||||||
|
type: "Input"
|
||||||
|
top: "data"
|
||||||
|
input_param {
|
||||||
|
shape {
|
||||||
|
dim: 1
|
||||||
|
dim: 1
|
||||||
|
dim: 224
|
||||||
|
dim: 224
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "conv0"
|
||||||
|
type: "Convolution"
|
||||||
|
bottom: "data"
|
||||||
|
top: "conv0"
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 1.0
|
||||||
|
}
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 0.0
|
||||||
|
}
|
||||||
|
convolution_param {
|
||||||
|
num_output: 32
|
||||||
|
bias_term: true
|
||||||
|
pad: 1
|
||||||
|
kernel_size: 3
|
||||||
|
group: 1
|
||||||
|
stride: 1
|
||||||
|
weight_filler {
|
||||||
|
type: "msra"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "conv0/lrelu"
|
||||||
|
type: "ReLU"
|
||||||
|
bottom: "conv0"
|
||||||
|
top: "conv0"
|
||||||
|
relu_param {
|
||||||
|
negative_slope: 0.05000000074505806
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db1/reduce"
|
||||||
|
type: "Convolution"
|
||||||
|
bottom: "conv0"
|
||||||
|
top: "db1/reduce"
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 1.0
|
||||||
|
}
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 0.0
|
||||||
|
}
|
||||||
|
convolution_param {
|
||||||
|
num_output: 8
|
||||||
|
bias_term: true
|
||||||
|
pad: 0
|
||||||
|
kernel_size: 1
|
||||||
|
group: 1
|
||||||
|
stride: 1
|
||||||
|
weight_filler {
|
||||||
|
type: "msra"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db1/reduce/lrelu"
|
||||||
|
type: "ReLU"
|
||||||
|
bottom: "db1/reduce"
|
||||||
|
top: "db1/reduce"
|
||||||
|
relu_param {
|
||||||
|
negative_slope: 0.05000000074505806
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db1/3x3"
|
||||||
|
type: "Convolution"
|
||||||
|
bottom: "db1/reduce"
|
||||||
|
top: "db1/3x3"
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 1.0
|
||||||
|
}
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 0.0
|
||||||
|
}
|
||||||
|
convolution_param {
|
||||||
|
num_output: 8
|
||||||
|
bias_term: true
|
||||||
|
pad: 1
|
||||||
|
kernel_size: 3
|
||||||
|
group: 8
|
||||||
|
stride: 1
|
||||||
|
weight_filler {
|
||||||
|
type: "msra"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db1/3x3/lrelu"
|
||||||
|
type: "ReLU"
|
||||||
|
bottom: "db1/3x3"
|
||||||
|
top: "db1/3x3"
|
||||||
|
relu_param {
|
||||||
|
negative_slope: 0.05000000074505806
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db1/1x1"
|
||||||
|
type: "Convolution"
|
||||||
|
bottom: "db1/3x3"
|
||||||
|
top: "db1/1x1"
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 1.0
|
||||||
|
}
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 0.0
|
||||||
|
}
|
||||||
|
convolution_param {
|
||||||
|
num_output: 32
|
||||||
|
bias_term: true
|
||||||
|
pad: 0
|
||||||
|
kernel_size: 1
|
||||||
|
group: 1
|
||||||
|
stride: 1
|
||||||
|
weight_filler {
|
||||||
|
type: "msra"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db1/1x1/lrelu"
|
||||||
|
type: "ReLU"
|
||||||
|
bottom: "db1/1x1"
|
||||||
|
top: "db1/1x1"
|
||||||
|
relu_param {
|
||||||
|
negative_slope: 0.05000000074505806
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db1/concat"
|
||||||
|
type: "Concat"
|
||||||
|
bottom: "conv0"
|
||||||
|
bottom: "db1/1x1"
|
||||||
|
top: "db1/concat"
|
||||||
|
concat_param {
|
||||||
|
axis: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db2/reduce"
|
||||||
|
type: "Convolution"
|
||||||
|
bottom: "db1/concat"
|
||||||
|
top: "db2/reduce"
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 1.0
|
||||||
|
}
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 0.0
|
||||||
|
}
|
||||||
|
convolution_param {
|
||||||
|
num_output: 8
|
||||||
|
bias_term: true
|
||||||
|
pad: 0
|
||||||
|
kernel_size: 1
|
||||||
|
group: 1
|
||||||
|
stride: 1
|
||||||
|
weight_filler {
|
||||||
|
type: "msra"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db2/reduce/lrelu"
|
||||||
|
type: "ReLU"
|
||||||
|
bottom: "db2/reduce"
|
||||||
|
top: "db2/reduce"
|
||||||
|
relu_param {
|
||||||
|
negative_slope: 0.05000000074505806
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db2/3x3"
|
||||||
|
type: "Convolution"
|
||||||
|
bottom: "db2/reduce"
|
||||||
|
top: "db2/3x3"
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 1.0
|
||||||
|
}
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 0.0
|
||||||
|
}
|
||||||
|
convolution_param {
|
||||||
|
num_output: 8
|
||||||
|
bias_term: true
|
||||||
|
pad: 1
|
||||||
|
kernel_size: 3
|
||||||
|
group: 8
|
||||||
|
stride: 1
|
||||||
|
weight_filler {
|
||||||
|
type: "msra"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db2/3x3/lrelu"
|
||||||
|
type: "ReLU"
|
||||||
|
bottom: "db2/3x3"
|
||||||
|
top: "db2/3x3"
|
||||||
|
relu_param {
|
||||||
|
negative_slope: 0.05000000074505806
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db2/1x1"
|
||||||
|
type: "Convolution"
|
||||||
|
bottom: "db2/3x3"
|
||||||
|
top: "db2/1x1"
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 1.0
|
||||||
|
}
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 0.0
|
||||||
|
}
|
||||||
|
convolution_param {
|
||||||
|
num_output: 32
|
||||||
|
bias_term: true
|
||||||
|
pad: 0
|
||||||
|
kernel_size: 1
|
||||||
|
group: 1
|
||||||
|
stride: 1
|
||||||
|
weight_filler {
|
||||||
|
type: "msra"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db2/1x1/lrelu"
|
||||||
|
type: "ReLU"
|
||||||
|
bottom: "db2/1x1"
|
||||||
|
top: "db2/1x1"
|
||||||
|
relu_param {
|
||||||
|
negative_slope: 0.05000000074505806
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "db2/concat"
|
||||||
|
type: "Concat"
|
||||||
|
bottom: "db1/concat"
|
||||||
|
bottom: "db2/1x1"
|
||||||
|
top: "db2/concat"
|
||||||
|
concat_param {
|
||||||
|
axis: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "upsample/reduce"
|
||||||
|
type: "Convolution"
|
||||||
|
bottom: "db2/concat"
|
||||||
|
top: "upsample/reduce"
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 1.0
|
||||||
|
}
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 0.0
|
||||||
|
}
|
||||||
|
convolution_param {
|
||||||
|
num_output: 32
|
||||||
|
bias_term: true
|
||||||
|
pad: 0
|
||||||
|
kernel_size: 1
|
||||||
|
group: 1
|
||||||
|
stride: 1
|
||||||
|
weight_filler {
|
||||||
|
type: "msra"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "upsample/reduce/lrelu"
|
||||||
|
type: "ReLU"
|
||||||
|
bottom: "upsample/reduce"
|
||||||
|
top: "upsample/reduce"
|
||||||
|
relu_param {
|
||||||
|
negative_slope: 0.05000000074505806
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "upsample/deconv"
|
||||||
|
type: "Deconvolution"
|
||||||
|
bottom: "upsample/reduce"
|
||||||
|
top: "upsample/deconv"
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 1.0
|
||||||
|
}
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 0.0
|
||||||
|
}
|
||||||
|
convolution_param {
|
||||||
|
num_output: 32
|
||||||
|
bias_term: true
|
||||||
|
pad: 1
|
||||||
|
kernel_size: 3
|
||||||
|
group: 32
|
||||||
|
stride: 2
|
||||||
|
weight_filler {
|
||||||
|
type: "msra"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "upsample/lrelu"
|
||||||
|
type: "ReLU"
|
||||||
|
bottom: "upsample/deconv"
|
||||||
|
top: "upsample/deconv"
|
||||||
|
relu_param {
|
||||||
|
negative_slope: 0.05000000074505806
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "upsample/rec"
|
||||||
|
type: "Convolution"
|
||||||
|
bottom: "upsample/deconv"
|
||||||
|
top: "upsample/rec"
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 1.0
|
||||||
|
}
|
||||||
|
param {
|
||||||
|
lr_mult: 1.0
|
||||||
|
decay_mult: 0.0
|
||||||
|
}
|
||||||
|
convolution_param {
|
||||||
|
num_output: 1
|
||||||
|
bias_term: true
|
||||||
|
pad: 0
|
||||||
|
kernel_size: 1
|
||||||
|
group: 1
|
||||||
|
stride: 1
|
||||||
|
weight_filler {
|
||||||
|
type: "msra"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "nearest"
|
||||||
|
type: "Deconvolution"
|
||||||
|
bottom: "data"
|
||||||
|
top: "nearest"
|
||||||
|
param {
|
||||||
|
lr_mult: 0.0
|
||||||
|
decay_mult: 0.0
|
||||||
|
}
|
||||||
|
convolution_param {
|
||||||
|
num_output: 1
|
||||||
|
bias_term: false
|
||||||
|
pad: 0
|
||||||
|
kernel_size: 2
|
||||||
|
group: 1
|
||||||
|
stride: 2
|
||||||
|
weight_filler {
|
||||||
|
type: "constant"
|
||||||
|
value: 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "Crop1"
|
||||||
|
type: "Crop"
|
||||||
|
bottom: "nearest"
|
||||||
|
bottom: "upsample/rec"
|
||||||
|
top: "Crop1"
|
||||||
|
}
|
||||||
|
layer {
|
||||||
|
name: "fc"
|
||||||
|
type: "Eltwise"
|
||||||
|
bottom: "Crop1"
|
||||||
|
bottom: "upsample/rec"
|
||||||
|
top: "fc"
|
||||||
|
eltwise_param {
|
||||||
|
operation: SUM
|
||||||
|
}
|
||||||
|
}
|
BIN
src/assets/images/bg.webp
Normal file
BIN
src/assets/images/bg.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
1
src/assets/svgs/data_analysis.svg
Normal file
1
src/assets/svgs/data_analysis.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg t="1740650615689" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7146" width="200" height="200"><path d="M723.2 188.8a106.88 106.88 0 1 0 213.76 0 106.88 106.88 0 1 0-213.76 0z" fill="#08979C" opacity=".3" p-id="7147"></path><path d="M857.267 361.62a151.199 151.199 0 0 1-60.938 12.816c-83.881 0-151.88-67.999-151.88-151.88 0-21.688 4.624-42.276 12.816-60.938-6.093-0.517-12.217-0.937-18.437-0.937H301.307C180.644 160.676 81.92 259.395 81.92 380.058v337.515c0 120.663 98.724 219.387 219.387 219.387h337.515c120.663 0 219.387-98.724 219.387-219.387V380.058c-0.005-6.221-0.425-12.345-0.942-18.438zM703.32 460.908l-135.004 168.76a39.357 39.357 0 0 1-55.957 5.647l-104.146-86.784-109.91 137.39a39.286 39.286 0 0 1-30.777 14.777 39.24 39.24 0 0 1-24.57-8.622 39.388 39.388 0 0 1-6.15-55.353l135.01-168.76a39.404 39.404 0 0 1 26.792-14.582 39.552 39.552 0 0 1 29.164 8.935L531.917 549.1l109.91-137.39c13.58-16.973 38.355-19.723 55.348-6.155a39.388 39.388 0 0 1 6.144 55.353z" fill="#08979C" p-id="7148"></path></svg>
|
After Width: | Height: | Size: 1.0 KiB |
1
src/assets/svgs/dynamic.svg
Normal file
1
src/assets/svgs/dynamic.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg t="1740650748717" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9995" width="200" height="200"><path d="M608.09216 61.92128c0.64 0 1.26976 0 1.69984 0.10752 1.05984 5.72928 0.84992 20.26496-8.27904 44.76928l-79.03232 214.50752c-20.48 55.48032-67.89632 95.68768-112.768 95.68768-8.38144 0-16.54784-1.3824-24.192-4.2496-50.176-18.4576-75.84768-74.26048-57.39008-124.43648 22.912-62.06464 110.11584-149.89824 186.60352-187.8784l51.24096-25.46176c23.86432-11.77088 37.34528-13.04576 42.11712-13.04576M702.08 329.14944c11.35104 0 22.49216 2.01728 33.09568 5.9392 62.06464 22.91712 149.90336 110.11584 187.87328 186.5984l25.472 51.24096c11.65824 23.4496 13.35808 37.86752 13.05088 43.70944-1.28 0.20992-3.18976 0.43008-5.9392 0.43008-7.11168 0-19.52256-1.48992-38.93248-8.61184l-214.50752-79.03232c-67.69152-24.92928-109.57824-87.62368-91.43808-136.95488 13.99296-37.85728 50.69312-63.31904 91.32544-63.31904M61.00992 413.37856c7.10656 0 19.51744 1.48992 38.93248 8.59648l214.50752 79.03744c34.47808 12.72832 63.86688 35.95776 80.73216 63.75936 15.06304 24.92416 18.88256 50.9184 10.71104 73.1904-13.99808 37.87776-50.60096 63.33952-91.22816 63.33952-11.35104 0-22.49216-2.0224-33.1008-5.9392-62.05952-22.8096-149.89824-110.12096-187.87328-186.59328L68.22912 457.5232c-11.66848-23.44448-13.36832-37.87264-13.05088-43.70944 1.26464-0.22016 3.1744-0.4352 5.83168-0.4352M606.91968 613.3504c8.38144 0 16.54784 1.3824 24.192 4.24448 50.18112 18.45248 75.84768 74.26048 57.39008 124.43648-22.8096 62.06464-110.11584 149.89824-186.5984 187.99104L450.6624 955.4944c-23.6544 11.77088-37.23776 13.04576-42.0096 13.04576-0.64 0-1.27488 0-1.69984-0.10752-1.06496-5.72928-0.84992-20.25984 8.27904-44.76928l79.03232-214.50752c20.25984-55.49568 67.78368-95.80544 112.65536-95.80544" fill="" p-id="9996"></path><path d="M608.09216 16.62464c-16.8704 0-37.86752 5.72928-62.16704 17.71008l-51.24096 25.46176C406.73792 103.50592 312.74496 199.30624 285.696 272.60416c-27.05408 73.51808 10.81856 155.62752 84.23424 182.6816a114.39104 114.39104 0 0 0 39.7824 6.99904c62.90432 0 127.40608-49.54112 155.30496-125.3888l79.03232-214.5024c24.61184-66.62144 8.27392-105.76896-35.95776-105.76896z" fill="#F48134" p-id="9997"></path><path d="M702.08 283.8528c-57.92256 0-112.66048 35.64032-133.77536 93.03552-27.04896 73.40544 26.20416 161.2544 118.28736 195.09248l214.50752 79.03232c21.11488 7.7568 39.36256 11.45856 54.63552 11.45856 51.23584 0 67.1488-41.79968 33.52064-109.58848l-25.46176-51.22048c-43.70432-87.94624-139.50464-181.9392-212.80256-208.98816a139.14112 139.14112 0 0 0-48.91136-8.82176z" fill="#61F24C" p-id="9998"></path><path d="M61.00992 367.97952c-51.34336 0-67.15392 41.79456-33.52576 109.58848l25.46176 51.23072c43.70944 87.94624 139.50464 181.9392 212.80768 208.98816a140.49792 140.49792 0 0 0 48.79872 8.68864c57.92256 0 112.66048-35.64544 133.77024-93.04064 27.05408-73.41056-26.19904-161.24416-118.28224-195.08736L115.64032 379.43808c-21.10976-7.74656-39.45984-11.45856-54.6304-11.45856z" fill="#F2515D" p-id="9999"></path><path d="M606.91968 568.05376c-62.90944 0-127.4112 49.54112-155.30496 125.38368L372.5824 907.95008c-24.50432 66.62144-8.1664 105.8816 36.0704 105.8816 16.86528 0 37.86752-5.72928 62.16192-17.72032l51.24096-25.46176c87.94624-43.70944 181.93408-139.50976 208.98304-212.80768 27.04896-73.40032-10.9312-155.52-84.34176-182.56896a112.0512 112.0512 0 0 0-39.77728-7.2192z" fill="#4678F2" p-id="10000"></path></svg>
|
After Width: | Height: | Size: 3.4 KiB |
1
src/assets/svgs/home.svg
Normal file
1
src/assets/svgs/home.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg t="1740650417427" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3483" width="200" height="200"><path d="M265.2 646.8v116.5c0 39.8 32.3 72.2 72.2 72.2h300.2V572c0-24.9 20.3-45.2 45.2-45.2 24.9 0 45.2 20.3 45.2 45.2v263.4h28.1c39.8 0 72.2-32.3 72.2-72.2V523.8c37.7 0 55.5-46.4 27.6-71.7L592.2 213.4c-25.8-23.4-65.2-23.4-91 0L237.6 452.1c-27.9 25.3-10 71.7 27.6 71.7v46.6" fill="#5CC7B9" p-id="3484"></path><path d="M700.6 848h-44.1V568.6c0-16.1-13.1-29.2-29.2-29.2s-29.2 13.1-29.2 29.2V848H281.9c-48.6 0-88.2-39.5-88.2-88.2V643.4c0-8.8 7.2-16 16-16s16 7.2 16 16v116.5c0 31 25.2 56.2 56.2 56.2h284.2V568.6c0-33.8 27.4-61.2 61.2-61.2 33.8 0 61.2 27.4 61.2 61.2V816h12.1c31 0 56.2-25.2 56.2-56.2V504.4h16c16 0 22-12.3 23.4-16.1 1.4-3.8 5.3-17-6.6-27.7L526 221.9c-19.7-17.9-49.9-17.9-69.6 0L192.9 460.6c-11.8 10.7-8.1 23.9-6.6 27.7 1.4 3.8 7.4 16.1 23.4 16.1h16V567c0 8.8-7.2 16-16 16s-16-7.2-16-16v-32.8c-16.9-4.8-30.7-17.3-37.4-34.3-8.6-22.2-2.7-47 15-63L435 198.2c15.4-14 35.4-21.7 56.2-21.7 20.8 0 40.8 7.7 56.2 21.7L811 436.8c17.7 16 23.5 40.7 15 63-6.6 17-20.5 29.5-37.4 34.3v225.8c0.1 48.6-39.4 88.1-88 88.1z m0 0" fill="#333333" p-id="3485"></path><path d="M327 645.2c-8.8 0-16-7.2-16-16v-91.4c0-8.8 7.2-16 16-16s16 7.2 16 16v91.4c0 8.8-7.1 16-16 16z m0 0M308.8 667.5c0 9.3 8.2 16.9 18.2 16.9 10.1 0 18.2-7.6 18.2-16.9 0-9.3-8.2-16.9-18.2-16.9s-18.2 7.6-18.2 16.9z m0 0" fill="#FBFFFD" p-id="3486"></path><path d="M215.2 240.6c0 12 9.7 21.8 21.8 21.8 12 0 21.8-9.7 21.8-21.8 0-12-9.7-21.8-21.8-21.8-12.1 0.1-21.8 9.8-21.8 21.8z m0 0M269.8 202.9c0 7.8 6.3 14.2 14.2 14.2 7.8 0 14.2-6.3 14.2-14.2 0-7.8-6.3-14.2-14.2-14.2-7.8 0-14.2 6.4-14.2 14.2z m0 0M756.5 181l10.7 32.9c0.6 1.7 2.2 2.9 4 2.9h34.6c4.1 0 5.8 5.2 2.5 7.6l-28 20.3c-1.5 1.1-2.1 3-1.5 4.7l10.7 32.9c1.3 3.9-3.2 7.1-6.5 4.7l-28-20.3a4.2 4.2 0 0 0-5 0L722 287c-3.3 2.4-7.7-0.8-6.5-4.7l10.7-32.9c0.6-1.7-0.1-3.6-1.5-4.7l-28-20.3c-3.3-2.4-1.6-7.6 2.5-7.6h34.6c1.8 0 3.4-1.2 4-2.9l10.7-32.9c1.3-3.8 6.8-3.8 8 0z" fill="#5CC7B9" p-id="3487"></path></svg>
|
After Width: | Height: | Size: 2.0 KiB |
69
src/components/ImageWaterfallList/ImageWaterfallList.vue
Normal file
69
src/components/ImageWaterfallList/ImageWaterfallList.vue
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<Spin size="middle" :spinning="imageStore.imageListLoading" indicator="spin-dot" tip="loading..." :rotate="true">
|
||||||
|
<div style="width:100%;height:100%;" v-if="props.imageList">
|
||||||
|
<div v-for="(itemList, index) in props.imageList" :key="index">
|
||||||
|
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
|
||||||
|
<AImagePreviewGroup>
|
||||||
|
<Vue3JustifiedLayout v-model:list="itemList.list" :options="imageStore.JustifiedLayoutOptions"
|
||||||
|
style="line-height: 0 !important;">
|
||||||
|
<template #default="{ item }">
|
||||||
|
<CheckCard :key="index"
|
||||||
|
class="photo-item"
|
||||||
|
margin="0"
|
||||||
|
border-radius="0"
|
||||||
|
v-model="imageStore.selected"
|
||||||
|
:showHoverCircle="true"
|
||||||
|
:iconSize="20"
|
||||||
|
:showSelectedEffect="true"
|
||||||
|
:value="item.id">
|
||||||
|
<AImage :src="item.thumbnail"
|
||||||
|
:alt="item.file_name"
|
||||||
|
:key="index"
|
||||||
|
:height="200"
|
||||||
|
:preview="{
|
||||||
|
src: item.url,
|
||||||
|
}"
|
||||||
|
loading="lazy">
|
||||||
|
<template #previewMask>
|
||||||
|
</template>
|
||||||
|
</AImage>
|
||||||
|
</CheckCard>
|
||||||
|
</template>
|
||||||
|
</Vue3JustifiedLayout>
|
||||||
|
</AImagePreviewGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!imageStore.imageListLoading && !props.imageList" class="empty-content">
|
||||||
|
<AEmpty :image="empty"
|
||||||
|
:image-style="{
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
}">
|
||||||
|
<template #description>
|
||||||
|
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
||||||
|
还没检测到任何图片,快去上传吧!
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</AEmpty>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Vue3JustifiedLayout from "vue3-justified-layout";
|
||||||
|
import 'vue3-justified-layout/dist/style.css';
|
||||||
|
import empty from "@/assets/svgs/empty.svg";
|
||||||
|
import useStore from "@/store";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
imageList: {
|
||||||
|
type: Array as () => any[],
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageStore = useStore().image;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
</style>
|
@@ -58,10 +58,10 @@
|
|||||||
</AFlex>
|
</AFlex>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="header-logo-popover-card" @click="router.push('/main/photo/phone')">
|
<div class="header-logo-popover-card" @click="router.push('/main/photo/upscale')">
|
||||||
<AFlex :vertical="false" align="center" justify="space-between">
|
<AFlex :vertical="false" align="center" justify="space-between">
|
||||||
<AAvatar size="small" shape="square" :src="ai"/>
|
<AAvatar size="small" shape="square" :src="ai"/>
|
||||||
<span class="header-logo-popover-text">{{ t('album.phone') }}</span>
|
<span class="header-logo-popover-text">{{ t('album.upscale') }}</span>
|
||||||
</AFlex>
|
</AFlex>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -126,6 +126,7 @@ const cardStyle = computed(() => ({
|
|||||||
font-family: "Comic Sans MS", cursive;
|
font-family: "Comic Sans MS", cursive;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
text-shadow: 0px 1px 2px rgba(0, 0, 0, .4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -112,13 +112,13 @@
|
|||||||
<ADivider/>
|
<ADivider/>
|
||||||
<div class="avatar-content-menu">
|
<div class="avatar-content-menu">
|
||||||
<AMenu>
|
<AMenu>
|
||||||
<AMenuItem key="1">
|
<AMenuItem key="1" @click="router.push('/main/user/center/home')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<AAvatar size="small" shape="circle" :src="personalCenter"/>
|
<AAvatar size="small" shape="circle" :src="personalCenter"/>
|
||||||
</template>
|
</template>
|
||||||
<span class="avatar-content-menu-item">个人中心</span>
|
<span class="avatar-content-menu-item">个人中心</span>
|
||||||
</AMenuItem>
|
</AMenuItem>
|
||||||
<AMenuItem key="2">
|
<AMenuItem key="2" @click="router.push('/main/user/setting')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<AAvatar size="small" shape="circle" :src="accountSetting"/>
|
<AAvatar size="small" shape="circle" :src="accountSetting"/>
|
||||||
</template>
|
</template>
|
||||||
@@ -154,10 +154,12 @@ import logout from "@/assets/svgs/logout.svg";
|
|||||||
import wenhao from "@/assets/svgs/wenhao.svg";
|
import wenhao from "@/assets/svgs/wenhao.svg";
|
||||||
|
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import ImageUpload from "@/views/Photograph/ImageUpload/ImageUpload.vue";
|
import ImageUpload from "@/components/ImageUpload/ImageUpload.vue";
|
||||||
import {getStorageConfigListApi} from "@/api/storage";
|
import {getStorageConfigListApi} from "@/api/storage";
|
||||||
import {ProviderIcon} from "@/constant/provider_icon.ts";
|
import {ProviderIcon} from "@/constant/provider_icon.ts";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const uploadStore = useStore().upload;
|
const uploadStore = useStore().upload;
|
||||||
const user = useStore().user;
|
const user = useStore().user;
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import PhoalbumPhoalbum from "@/views/Album/Phoalbum/Phoalbum.vue";
|
import AlbumPhoalbumIndex from "@/views/Album/Phoalbum/PhoalbumIndex.vue";
|
||||||
import PeopleAlbumPeopleAlbum from "@/views/Album/PeopleAlbum/PeopleAlbum.vue";
|
import PeoplePeopleAlbumIndex from "@/views/Album/PeopleAlbum/PeopleAlbumIndex.vue";
|
||||||
import LocationAlbum from "@/views/Album/LocationAlbum/LocationAlbumList.vue";
|
import LocationAlbum from "@/views/Album/LocationAlbum/LocationAlbumList.vue";
|
||||||
import LocationAlbumIndex from "@/views/Album/LocationAlbum/LocationAlbum.vue";
|
import LocationLocationAlbumIndex from "@/views/Album/LocationAlbum/LocationAlbumIndex.vue";
|
||||||
import ThingAlbum from "@/views/Album/ThingAlbum/ThingAlbumList.vue";
|
import ThingAlbum from "@/views/Album/ThingAlbum/ThingAlbumList.vue";
|
||||||
import ThingAlbumThingAlbum from "@/views/Album/ThingAlbum/ThingAlbum.vue";
|
import ThingThingAlbumIndex from "@/views/Album/ThingAlbum/ThingAlbumIndex.vue";
|
||||||
import Phoalbum from "@/views/Album/Phoalbum/PhoalbumList.vue";
|
import Phoalbum from "@/views/Album/Phoalbum/PhoalbumList.vue";
|
||||||
import PeopleAlbum from "@/views/Album/PeopleAlbum/PeopleAlbumList.vue";
|
import PeopleAlbum from "@/views/Album/PeopleAlbum/PeopleAlbumList.vue";
|
||||||
import PhoalbumDetail from "@/views/Album/Phoalbum/PhoalbumDetail.vue";
|
import PhoalbumDetail from "@/views/Album/Phoalbum/PhoalbumDetail.vue";
|
||||||
@@ -14,7 +14,7 @@ import ThingAlbumDetail from "@/views/Album/ThingAlbum/ThingAlbumDetail.vue";
|
|||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
path: '/main/album/albums',
|
path: '/main/album/albums',
|
||||||
component: PhoalbumPhoalbum,
|
component: AlbumPhoalbumIndex,
|
||||||
redirect: '/main/album/albums',
|
redirect: '/main/album/albums',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -39,7 +39,7 @@ export default [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/main/album/people',
|
path: '/main/album/people',
|
||||||
component: PeopleAlbumPeopleAlbum,
|
component: PeoplePeopleAlbumIndex,
|
||||||
redirect: '/main/album/people',
|
redirect: '/main/album/people',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -64,7 +64,7 @@ export default [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/main/album/location',
|
path: '/main/album/location',
|
||||||
component: LocationAlbumIndex,
|
component: LocationLocationAlbumIndex,
|
||||||
redirect: '/main/album/location',
|
redirect: '/main/album/location',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -89,7 +89,7 @@ export default [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/main/album/thing',
|
path: '/main/album/thing',
|
||||||
component: ThingAlbumThingAlbum,
|
component: ThingThingAlbumIndex,
|
||||||
redirect: '/main/album/thing',
|
redirect: '/main/album/thing',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
@@ -20,7 +20,6 @@ export default [
|
|||||||
...recycling_bin,
|
...recycling_bin,
|
||||||
...share,
|
...share,
|
||||||
...upscale,
|
...upscale,
|
||||||
|
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
path: '/main/share/list/:id',
|
path: '/main/share/list/:id',
|
||||||
|
84
src/router/modules/user.ts
Normal file
84
src/router/modules/user.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
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";
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
|
||||||
|
path: '/main/user/center',
|
||||||
|
name: 'userCenter',
|
||||||
|
redirect: '/main/user/center/home',
|
||||||
|
component: () => import('@/views/User/PersonalCenter/PersonalCenter.vue'),
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
title: '个人中心'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/main/user/center/home',
|
||||||
|
name: 'home',
|
||||||
|
component: UserCenterHome,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
title: '主页'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/main/user/center/dynamic',
|
||||||
|
name: 'dynamic',
|
||||||
|
component: UserCenterDynamic,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
title: '动态'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/main/user/center/info',
|
||||||
|
name: 'info',
|
||||||
|
component: UserCenterInfo,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
title: '个人信息'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/main/user/center/analysis',
|
||||||
|
name: 'analysis',
|
||||||
|
component: UserCenterAnalysis,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
title: '数据分析'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/main/user/center/share',
|
||||||
|
name: 'share',
|
||||||
|
component: UserCenterShare,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
title: '我的分享'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/main/user/center/setting',
|
||||||
|
name: 'setting',
|
||||||
|
component: UserCenterSetting,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
title: '设置'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/main/user/setting',
|
||||||
|
name: 'userSetting',
|
||||||
|
component: () => import('@/views/User/AccountSetting/AccountSetting.vue'),
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
title: '账户设置'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
@@ -8,6 +8,7 @@ import landing from "./modules/landing.ts";
|
|||||||
import mainRouter from "./modules/main_router.ts";
|
import mainRouter from "./modules/main_router.ts";
|
||||||
import i18n from "@/locales";
|
import i18n from "@/locales";
|
||||||
import phone_upload from "@/router/modules/phone_upload.ts";
|
import phone_upload from "@/router/modules/phone_upload.ts";
|
||||||
|
import user from "@/router/modules/user.ts";
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
...login,
|
...login,
|
||||||
@@ -15,6 +16,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
...landing,
|
...landing,
|
||||||
...mainRouter,
|
...mainRouter,
|
||||||
...phone_upload,
|
...phone_upload,
|
||||||
|
...user,
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)',
|
path: '/:pathMatch(.*)',
|
||||||
redirect: '/404',
|
redirect: '/404',
|
||||||
|
@@ -1,16 +1,12 @@
|
|||||||
import {ImageList} from "@/types/image";
|
import {ImageList} from "@/types/image";
|
||||||
|
import localForage from "localforage";
|
||||||
|
import {albumListApi, getFaceSamplesList} from "@/api/storage";
|
||||||
|
|
||||||
export const useImageStore = defineStore(
|
export const useImageStore = defineStore(
|
||||||
'image',
|
'image',
|
||||||
() => {
|
() => {
|
||||||
|
// 选择的图片列表
|
||||||
const selected = ref<number[]>([]);
|
const selected = ref<number[]>([]);
|
||||||
const tabActiveKey = ref<string>("-1");
|
|
||||||
const tabMap = reactive({
|
|
||||||
"-1": "全部相册",
|
|
||||||
"0": "我的相册",
|
|
||||||
"1": "我的分享",
|
|
||||||
"2": "我的收藏",
|
|
||||||
});
|
|
||||||
const homeTabActiveKey = ref<string>("all");
|
const homeTabActiveKey = ref<string>("all");
|
||||||
const homeTabMap = reactive({
|
const homeTabMap = reactive({
|
||||||
"all": "全部",
|
"all": "全部",
|
||||||
@@ -18,14 +14,72 @@ export const useImageStore = defineStore(
|
|||||||
"gif": "动图",
|
"gif": "动图",
|
||||||
"screenshot": "截图",
|
"screenshot": "截图",
|
||||||
});
|
});
|
||||||
// 清算模式切换
|
// 清爽模式切换
|
||||||
const switchValue = ref<boolean>(false);
|
const switchValue = ref<boolean>(false);
|
||||||
|
|
||||||
|
|
||||||
|
// 图片列表布局相关
|
||||||
|
const JustifiedLayoutOptions = reactive({
|
||||||
|
targetRowHeight: 200 // 高度
|
||||||
|
});
|
||||||
|
// 图片列表数据
|
||||||
|
const imageList = ref<any[]>([]);
|
||||||
|
const imageListLoading = ref<boolean>(false);
|
||||||
|
|
||||||
|
// 相册相关
|
||||||
|
const albumList = ref<any[]>([]);
|
||||||
|
const albumListLoading = ref<boolean>(false);
|
||||||
|
const sortSelectedKey = ref<boolean>(true);
|
||||||
|
const tabActiveKey = ref<string>("-1");
|
||||||
|
const tabMap = reactive({
|
||||||
|
"-1": "全部相册",
|
||||||
|
"0": "我的相册",
|
||||||
|
"1": "我的分享",
|
||||||
|
"2": "我的收藏",
|
||||||
|
});
|
||||||
|
|
||||||
// 相册分享弹窗相关
|
// 相册分享弹窗相关
|
||||||
const openAlbumShareDialog = ref<boolean>(false);
|
const openAlbumShareDialog = ref<boolean>(false);
|
||||||
const albumShareCoverImage = ref<string>("");
|
const albumShareCoverImage = ref<string>("");
|
||||||
const albumShareId = ref<number>(0);
|
const albumShareId = ref<number>(0);
|
||||||
|
|
||||||
|
// 人脸相册
|
||||||
|
const faceSelectedKey = ref<string>("0");
|
||||||
|
const faceSelected = ref<number[]>([]);
|
||||||
|
const faceList = ref<any[]>([]);
|
||||||
|
const faceListLoading = ref<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取人脸列表
|
||||||
|
*/
|
||||||
|
async function getFaceList() {
|
||||||
|
faceListLoading.value = true;
|
||||||
|
faceList.value = [];
|
||||||
|
const res: any = await getFaceSamplesList(parseInt(faceSelectedKey.value));
|
||||||
|
if (res && res.code === 200 && res.data.faces) {
|
||||||
|
faceList.value = res.data.faces.map(face => ({
|
||||||
|
...face,
|
||||||
|
showButton: false,
|
||||||
|
showInput: false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
faceListLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取相册列表
|
||||||
|
*/
|
||||||
|
async function getAlbumList() {
|
||||||
|
albumList.value = [];
|
||||||
|
albumListLoading.value = true;
|
||||||
|
const res: any = await albumListApi(parseInt(tabActiveKey.value), sortSelectedKey.value);
|
||||||
|
if (res && res.code === 200) {
|
||||||
|
albumList.value = res.data.albums;
|
||||||
|
}
|
||||||
|
albumListLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统计图片总数
|
* 统计图片总数
|
||||||
* @param imageList 图片列表数据
|
* @param imageList 图片列表数据
|
||||||
@@ -53,18 +107,38 @@ export const useImageStore = defineStore(
|
|||||||
homeTabActiveKey,
|
homeTabActiveKey,
|
||||||
albumShareCoverImage,
|
albumShareCoverImage,
|
||||||
albumShareId,
|
albumShareId,
|
||||||
countTotalImages,
|
imageList,
|
||||||
|
imageListLoading,
|
||||||
|
albumListLoading,
|
||||||
|
albumList,
|
||||||
|
sortSelectedKey,
|
||||||
|
faceSelectedKey,
|
||||||
|
faceSelected,
|
||||||
|
faceList,
|
||||||
|
faceListLoading,
|
||||||
openAlbumShareDialog,
|
openAlbumShareDialog,
|
||||||
|
JustifiedLayoutOptions,
|
||||||
|
countTotalImages,
|
||||||
openAlbumShareDialogHandler,
|
openAlbumShareDialogHandler,
|
||||||
|
getAlbumList,
|
||||||
|
getFaceList,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 开启数据持久化
|
// 开启数据持久化
|
||||||
persistedState: {
|
persistedState: {
|
||||||
persist: true,
|
persist: true,
|
||||||
storage: localStorage,
|
storage: localForage,
|
||||||
key: 'image',
|
key: 'image',
|
||||||
includePaths: ["tabActiveKey", "tabMap", "homeTabActiveKey", "homeTabMap", "switchValue"],
|
includePaths: [
|
||||||
|
"tabActiveKey",
|
||||||
|
"tabMap",
|
||||||
|
"homeTabActiveKey",
|
||||||
|
"homeTabMap",
|
||||||
|
"switchValue",
|
||||||
|
"faceSelectedKey",
|
||||||
|
"albumList"
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -2,8 +2,11 @@ export const useMenuStore = defineStore(
|
|||||||
'menu',
|
'menu',
|
||||||
() => {
|
() => {
|
||||||
const currentMenu = ref<string>('photo/all');
|
const currentMenu = ref<string>('photo/all');
|
||||||
|
|
||||||
|
const userCenterMenu = ref<string>('home');
|
||||||
return {
|
return {
|
||||||
currentMenu,
|
currentMenu,
|
||||||
|
userCenterMenu,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -12,7 +15,7 @@ export const useMenuStore = defineStore(
|
|||||||
persist: true,
|
persist: true,
|
||||||
storage: localStorage,
|
storage: localStorage,
|
||||||
key: 'menu',
|
key: 'menu',
|
||||||
includePaths: ['currentMenu']
|
includePaths: ['currentMenu', 'userCenterMenu']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -37,7 +37,7 @@ export const useUploadStore = defineStore(
|
|||||||
|
|
||||||
const storageSelected = ref<any[]>([]);
|
const storageSelected = ref<any[]>([]);
|
||||||
|
|
||||||
const albumSelected = ref<number>();
|
const albumSelected = ref<number>(0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开上传抽屉
|
* 打开上传抽屉
|
||||||
|
10
src/utils/QRDetection/qr_detection.ts
Normal file
10
src/utils/QRDetection/qr_detection.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import OpencvQr from "opencv-qr";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenCV QR detection class
|
||||||
|
*/
|
||||||
|
const opencvQr = new OpencvQr({
|
||||||
|
dw: "/caffemodel/detect.caffemodel",
|
||||||
|
sw: "/caffemodel/sr.caffemodel",
|
||||||
|
});
|
||||||
|
export default opencvQr;
|
@@ -13,7 +13,7 @@ const initNSFWJs = async (): Promise<NSFWJS> => {
|
|||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
console.warn("IndexedDB 中未找到模型,正在从网络加载...");
|
console.warn("IndexedDB 中未找到模型,正在从网络加载...");
|
||||||
// 如果 IndexedDB 加载失败,从 URL 加载模型并保存到 IndexedDB
|
// 如果 IndexedDB 加载失败,从 URL 加载模型并保存到 IndexedDB
|
||||||
nsfwModelCache = await nsfwjs.load("/nsfw/mobilenet_v2_mid/", {size: 224, type: "graph"});
|
nsfwModelCache = await nsfwjs.load("/tfjs/nsfw/mobilenet_v2_mid/", {size: 224, type: "graph"});
|
||||||
await nsfwModelCache.model.save("indexeddb://nsfwjs-model");
|
await nsfwModelCache.model.save("indexeddb://nsfwjs-model");
|
||||||
console.log("NSFWJS 模型已从网络加载并保存到 IndexedDB");
|
console.log("NSFWJS 模型已从网络加载并保存到 IndexedDB");
|
||||||
}
|
}
|
||||||
|
@@ -7,78 +7,39 @@
|
|||||||
<span class="location-detail-content-nav-name">{{ route.query.name }}</span>
|
<span class="location-detail-content-nav-name">{{ route.query.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ImageToolbar :selected="imageStore.selected" :imageList="albumList"/>
|
<ImageToolbar :selected="imageStore.selected" :imageList="imageList"/>
|
||||||
<div class="location-album-detail-info">
|
<div class="location-album-detail-info">
|
||||||
<span style="font-size: 14px;color: #999999">共{{ imageStore.countTotalImages(albumList) }}张照片</span>
|
<span style="font-size: 14px;color: #999999">共{{ imageStore.countTotalImages(imageList) }}张照片</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="location-album-detail-list">
|
<div class="location-album-detail-list">
|
||||||
<div style="width:100%;height:100%;" v-if="albumList.length != 0">
|
<ImageWaterfallList :image-list="imageList"/>
|
||||||
<div v-for="(itemList, index) in albumList" :key="index">
|
|
||||||
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
|
|
||||||
<AImagePreviewGroup>
|
|
||||||
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options">
|
|
||||||
<template #default="{ item }">
|
|
||||||
<CheckCard :key="index"
|
|
||||||
class="photo-item"
|
|
||||||
margin="0"
|
|
||||||
border-radius="0"
|
|
||||||
v-model="imageStore.selected"
|
|
||||||
:showHoverCircle="true"
|
|
||||||
:iconSize="20"
|
|
||||||
:showSelectedEffect="true"
|
|
||||||
:value="item.id">
|
|
||||||
<AImage :src="item.thumbnail"
|
|
||||||
:alt="item.file_name"
|
|
||||||
:key="index"
|
|
||||||
:height="200"
|
|
||||||
:previewMask="false"
|
|
||||||
:preview="{
|
|
||||||
src: item.url,
|
|
||||||
}"
|
|
||||||
loading="lazy"/>
|
|
||||||
</CheckCard>
|
|
||||||
</template>
|
|
||||||
</Vue3JustifiedLayout>
|
|
||||||
</AImagePreviewGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty :image="empty" :image-style="{width: '100%', height: '100%'}">
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
暂无照片,快去上传吧
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Vue3JustifiedLayout from "vue3-justified-layout";
|
|
||||||
import 'vue3-justified-layout/dist/style.css';
|
|
||||||
import {queryLocationDetailListApi} from "@/api/storage";
|
import {queryLocationDetailListApi} from "@/api/storage";
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
|
import ImageToolbar from "@/components/ImageToolbar/ImageToolbar.vue";
|
||||||
import empty from "@/assets/svgs/empty.svg";
|
|
||||||
|
import ImageWaterfallList from "@/components/ImageWaterfallList/ImageWaterfallList.vue";
|
||||||
|
|
||||||
const imageStore = useStore().image;
|
const imageStore = useStore().image;
|
||||||
const albumList = ref<any[]>([]);
|
const imageList = ref<any[]>([]);
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const upload = useStore().upload;
|
const upload = useStore().upload;
|
||||||
const options = reactive({
|
|
||||||
targetRowHeight: 200 // 高度
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getImageList(id: number) {
|
async function getImageList(id: number) {
|
||||||
|
imageStore.imageListLoading = true;
|
||||||
const res: any = await queryLocationDetailListApi(id, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
const res: any = await queryLocationDetailListApi(id, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||||
console.log(res);
|
console.log(res);
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
albumList.value = res.data.records;
|
imageList.value = res.data.records;
|
||||||
}
|
}
|
||||||
|
imageStore.imageListLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@@ -14,78 +14,37 @@
|
|||||||
<span style="font-size: 14px;color: #333333">{{ route.query.name }}</span>
|
<span style="font-size: 14px;color: #333333">{{ route.query.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ImageToolbar :selected="imageStore.selected" :imageList="images"/>
|
<ImageToolbar :selected="imageStore.selected" :imageList="imageList"/>
|
||||||
<div class="people-album-detail-info">
|
<div class="people-album-detail-info">
|
||||||
<span style="font-size: 14px;color: #999999">共{{ imageStore.countTotalImages(images) }}张照片</span>
|
<span style="font-size: 14px;color: #999999">共{{ imageStore.countTotalImages(imageList) }}张照片</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="people-album-detail-list">
|
<div class="people-album-detail-list">
|
||||||
<div style="width:100%;height:100%;" v-if="images &&images.length !== 0">
|
<ImageWaterfallList :image-list="imageList"/>
|
||||||
<div v-for="(itemList, index) in images" :key="index">
|
|
||||||
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
|
|
||||||
<AImagePreviewGroup>
|
|
||||||
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options">
|
|
||||||
<template #default="{ item }">
|
|
||||||
<CheckCard :key="index"
|
|
||||||
class="photo-item"
|
|
||||||
margin="0"
|
|
||||||
border-radius="0"
|
|
||||||
v-model="imageStore.selected"
|
|
||||||
:showHoverCircle="true"
|
|
||||||
:iconSize="20"
|
|
||||||
:showSelectedEffect="true"
|
|
||||||
:value="item.id">
|
|
||||||
<AImage :src="item.thumbnail"
|
|
||||||
:alt="item.file_name"
|
|
||||||
:key="index"
|
|
||||||
:height="200"
|
|
||||||
:previewMask="false"
|
|
||||||
:preview="{
|
|
||||||
src: item.url,
|
|
||||||
}"
|
|
||||||
loading="lazy"/>
|
|
||||||
</CheckCard>
|
|
||||||
</template>
|
|
||||||
</Vue3JustifiedLayout>
|
|
||||||
</AImagePreviewGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty :image="empty" :image-style="{width: '100%', height: '100%'}">
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
暂无照片,快去上传吧
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Vue3JustifiedLayout from "vue3-justified-layout";
|
|
||||||
import 'vue3-justified-layout/dist/style.css';
|
|
||||||
import {getFaceSamplesDetailList} from "@/api/storage";
|
import {getFaceSamplesDetailList} from "@/api/storage";
|
||||||
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
|
import ImageToolbar from "@/components/ImageToolbar/ImageToolbar.vue";
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import empty from "@/assets/svgs/empty.svg";
|
|
||||||
|
import ImageWaterfallList from "@/components/ImageWaterfallList/ImageWaterfallList.vue";
|
||||||
|
|
||||||
|
|
||||||
const imageStore = useStore().image;
|
const imageStore = useStore().image;
|
||||||
const images = ref<any[]>([]);
|
const imageList = ref<any[]>([]);
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const upload = useStore().upload;
|
const upload = useStore().upload;
|
||||||
|
|
||||||
const options = reactive({
|
|
||||||
targetRowHeight: 200 // 高度
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getAlbumList(id: number) {
|
async function getAlbumList(id: number) {
|
||||||
|
imageStore.imageListLoading = true;
|
||||||
const res: any = await getFaceSamplesDetailList(id, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
const res: any = await getFaceSamplesDetailList(id, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
images.value = res.data.records;
|
imageList.value = res.data.records;
|
||||||
}
|
}
|
||||||
|
imageStore.imageListLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@@ -3,70 +3,37 @@
|
|||||||
<div class="people-album-header">
|
<div class="people-album-header">
|
||||||
<ADropdown trigger="click">
|
<ADropdown trigger="click">
|
||||||
<AButton type="text" size="large" class="people-album-button">
|
<AButton type="text" size="large" class="people-album-button">
|
||||||
{{ selectedKey === '0' ? '人 物' : '已隐藏' }}
|
{{ imageStore.faceSelectedKey === '0' ? '人 物' : '已隐藏' }}
|
||||||
<DownOutlined class="people-album-icon"/>
|
<DownOutlined class="people-album-icon"/>
|
||||||
</AButton>
|
</AButton>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<AMenu selectable :selectedKeys="[selectedKey]" @select="handleSelect">
|
<AMenu selectable :selectedKeys="[imageStore.faceSelectedKey]" @select="handleSelect">
|
||||||
<AMenuItem key="0">人 物</AMenuItem>
|
<AMenuItem key="0">人 物</AMenuItem>
|
||||||
<AMenuItem key="1">已隐藏</AMenuItem>
|
<AMenuItem key="1">已隐藏</AMenuItem>
|
||||||
</AMenu>
|
</AMenu>
|
||||||
</template>
|
</template>
|
||||||
</ADropdown>
|
</ADropdown>
|
||||||
<span class="people-album-count">共<span style="color: #0e87cc">{{ faceList.length }}</span>位</span>
|
<span class="people-album-count">共<span style="color: #0e87cc">{{ imageStore.faceList.length }}</span>位</span>
|
||||||
</div>
|
</div>
|
||||||
<transition name="fade">
|
<PeopleAlbumToolbar :face-list="imageStore.faceList"/>
|
||||||
<div class="people-album-toolbar" v-show="selected.length !== 0">
|
|
||||||
<div class="people-album-toolbar-left">
|
|
||||||
<AButton type="text" shape="circle" size="large" class="people-album-toolbar-btn" @click="cancelSelectPeople">
|
|
||||||
<template #icon>
|
|
||||||
<CloseOutlined class="people-album-toolbar-icon"/>
|
|
||||||
</template>
|
|
||||||
</AButton>
|
|
||||||
<span style="font-size: 16px;font-weight: bold">
|
|
||||||
已选择 {{ selected.length }} 个人物
|
|
||||||
</span>
|
|
||||||
<AButton type="text" shape="default" class="people-album-toolbar-btn" size="middle" @click="selectAllPeople">
|
|
||||||
全选
|
|
||||||
</AButton>
|
|
||||||
</div>
|
|
||||||
<div class="people-album-toolbar-right">
|
|
||||||
|
|
||||||
<AButton type="text" shape="default" size="middle" class="people-album-toolbar-btn"
|
|
||||||
:disabled="selected.length !== 2" v-if="selectedKey === '0'">
|
|
||||||
<template #icon>
|
|
||||||
<BlockOutlined class="people-album-toolbar-icon"/>
|
|
||||||
</template>
|
|
||||||
合并人物
|
|
||||||
</AButton>
|
|
||||||
|
|
||||||
<AButton type="text" shape="default" size="middle" class="people-album-toolbar-btn" @click="hiddenFace">
|
|
||||||
<template #icon>
|
|
||||||
<EyeInvisibleOutlined class="people-album-toolbar-icon"/>
|
|
||||||
</template>
|
|
||||||
{{ selectedKey === '0' ? '隐藏人物' : '取消隐藏' }}
|
|
||||||
</AButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
<div class="people-album-container">
|
<div class="people-album-container">
|
||||||
<ASpin :spinning="loading" size="large" wrapperClassName="spin-container">
|
<Spin :spinning="imageStore.faceListLoading" size="large" indicator="spin-dot">
|
||||||
<div class="people-album-content" v-if="faceList.length !== 0">
|
<div class="people-album-content" v-if="imageStore.faceList.length !== 0">
|
||||||
<CheckCard
|
<CheckCard
|
||||||
v-for="(item, index) in faceList"
|
v-for="(item, index) in imageStore.faceList"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="handleClick(item.id, item.face_name, item.face_image)"
|
@click="handleClick(item.id, item.face_name, item.face_image)"
|
||||||
class="photo-item"
|
class="photo-item"
|
||||||
margin="0"
|
margin="0"
|
||||||
border-radius="0"
|
border-radius="0"
|
||||||
v-model="selected"
|
v-model="imageStore.faceSelected"
|
||||||
:showHoverCircle="true"
|
:showHoverCircle="true"
|
||||||
:background-color="'transparent'"
|
:background-color="'transparent'"
|
||||||
:iconSize="20"
|
:iconSize="20"
|
||||||
:showSelectedEffect="false"
|
:showSelectedEffect="false"
|
||||||
:value="item.id">
|
:value="item.id">
|
||||||
<div class="people-album-item"
|
<div class="people-album-item"
|
||||||
:class="{ 'selected-item': selected.includes(item.id) }"
|
:class="{ 'selected-item': imageStore.faceSelected.includes(item.id) }"
|
||||||
@mouseover="item.showButton = true"
|
@mouseover="item.showButton = true"
|
||||||
@mouseleave="item.showButton = false">
|
@mouseleave="item.showButton = false">
|
||||||
<div class="people-album-item-avatar">
|
<div class="people-album-item-avatar">
|
||||||
@@ -115,7 +82,7 @@
|
|||||||
</CheckCard>
|
</CheckCard>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="empty-content">
|
<div v-if="!imageStore.imageListLoading&& imageStore.faceList.length === 0" class="empty-content">
|
||||||
<AEmpty :image="empty" :image-style="{width: '100%', height: '100%'}">
|
<AEmpty :image="empty" :image-style="{width: '100%', height: '100%'}">
|
||||||
<template #description>
|
<template #description>
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
||||||
@@ -124,49 +91,32 @@
|
|||||||
</template>
|
</template>
|
||||||
</AEmpty>
|
</AEmpty>
|
||||||
</div>
|
</div>
|
||||||
</ASpin>
|
</Spin>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {getFaceSamplesList, modifyFaceSampleName, modifyFaceTypeBatch} from "@/api/storage";
|
import {modifyFaceSampleName} from "@/api/storage";
|
||||||
import empty from "@/assets/svgs/empty.svg";
|
import empty from "@/assets/svgs/empty.svg";
|
||||||
|
import useStore from "@/store";
|
||||||
|
import PeopleAlbumToolbar from "@/views/Album/PeopleAlbum/PeopleAlbumToolbar.vue";
|
||||||
|
|
||||||
|
|
||||||
const faceList = ref<any[]>([]);
|
|
||||||
const addNameInputValue = ref<string>('');
|
const addNameInputValue = ref<string>('');
|
||||||
const selectedKey = ref<string>('0');
|
|
||||||
const loading = ref<boolean>(false);
|
|
||||||
const selected = ref<any[]>([]);
|
|
||||||
|
|
||||||
/**
|
const imageStore = useStore().image;
|
||||||
* 获取人脸列表
|
|
||||||
*/
|
|
||||||
async function getFaceList(type: number = 0) {
|
|
||||||
loading.value = true;
|
|
||||||
faceList.value = [];
|
|
||||||
const res: any = await getFaceSamplesList(type);
|
|
||||||
if (res && res.code === 200 && res.data.faces) {
|
|
||||||
faceList.value = res.data.faces.map(face => ({
|
|
||||||
...face,
|
|
||||||
showButton: false,
|
|
||||||
showInput: false,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showAddNameInput(index: number) {
|
function showAddNameInput(index: number) {
|
||||||
if (faceList.value[index]) {
|
if (imageStore.faceList[index]) {
|
||||||
faceList.value[index].showInput = true;
|
imageStore.faceList[index].showInput = true;
|
||||||
faceList.value[index].showButton = false;
|
imageStore.faceList[index].showButton = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideAddNameInput(index: number) {
|
function hideAddNameInput(index: number) {
|
||||||
if (faceList.value[index]) {
|
if (imageStore.faceList[index]) {
|
||||||
faceList.value[index].showInput = false;
|
imageStore.faceList[index].showInput = false;
|
||||||
faceList.value[index].showButton = false;
|
imageStore.faceList[index].showButton = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +129,7 @@ async function modifyFaceName(id: number, index: number) {
|
|||||||
if (!addNameInputValue.value.trim()) return;
|
if (!addNameInputValue.value.trim()) return;
|
||||||
const res: any = await modifyFaceSampleName(id, addNameInputValue.value);
|
const res: any = await modifyFaceSampleName(id, addNameInputValue.value);
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
faceList.value[index].face_name = res.data.face_name;
|
imageStore.faceList[index].face_name = res.data.face_name;
|
||||||
addNameInputValue.value = '';
|
addNameInputValue.value = '';
|
||||||
hideAddNameInput(index);
|
hideAddNameInput(index);
|
||||||
}
|
}
|
||||||
@@ -190,35 +140,10 @@ async function modifyFaceName(id: number, index: number) {
|
|||||||
* @param key
|
* @param key
|
||||||
*/
|
*/
|
||||||
function handleSelect({key}) {
|
function handleSelect({key}) {
|
||||||
selectedKey.value = key;
|
imageStore.faceSelectedKey = key;
|
||||||
getFaceList(parseInt(key));
|
imageStore.getFaceList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 全选
|
|
||||||
*/
|
|
||||||
function selectAllPeople() {
|
|
||||||
selected.value = faceList.value.map((item) => item.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 取消选择
|
|
||||||
*/
|
|
||||||
function cancelSelectPeople() {
|
|
||||||
selected.value = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 隐藏人物
|
|
||||||
*/
|
|
||||||
async function hiddenFace() {
|
|
||||||
if (selected.value.length === 0) return;
|
|
||||||
const res: any = await modifyFaceTypeBatch(selected.value, selectedKey.value === '0' ? 1 : 0);
|
|
||||||
if (res && res.code === 200) {
|
|
||||||
await getFaceList();
|
|
||||||
selected.value = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -229,12 +154,12 @@ const router = useRouter();
|
|||||||
* @param name
|
* @param name
|
||||||
* @param thumb
|
* @param thumb
|
||||||
*/
|
*/
|
||||||
function handleClick(id: number, name: string | null,thumb: string | null) {
|
function handleClick(id: number, name: string | null, thumb: string | null) {
|
||||||
router.push({path: route.path + `/${id}`, query: {name: name, thumb: thumb}});
|
router.push({path: route.path + `/${id}`, query: {name: name, thumb: thumb}});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getFaceList();
|
imageStore.getFaceList();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -283,57 +208,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.people-album-toolbar {
|
|
||||||
position: fixed;
|
|
||||||
width: calc(100% - 220px);
|
|
||||||
height: 70px;
|
|
||||||
top: 70px;
|
|
||||||
z-index: 3;
|
|
||||||
display: flex;
|
|
||||||
box-sizing: border-box;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
background-image: linear-gradient(45deg, #5789ff, #5c7bff 100%);
|
|
||||||
color: #fff;
|
|
||||||
box-shadow: 0 3px 10px 0 rgba(0, 0, 0, .06);
|
|
||||||
padding: 0 20px;
|
|
||||||
|
|
||||||
.people-album-toolbar-left {
|
|
||||||
width: 50%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.people-album-toolbar-right {
|
|
||||||
height: 100%;
|
|
||||||
width: 50%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.people-album-toolbar-icon {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.people-album-toolbar-btn {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #fff;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.people-album-container {
|
.people-album-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
127
src/views/Album/PeopleAlbum/PeopleAlbumToolbar.vue
Normal file
127
src/views/Album/PeopleAlbum/PeopleAlbumToolbar.vue
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="fade">
|
||||||
|
<div class="people-album-toolbar" v-show="imageStore.faceSelected.length !== 0">
|
||||||
|
<div class="people-album-toolbar-left">
|
||||||
|
<AButton type="text" shape="circle" size="large" class="people-album-toolbar-btn" @click="cancelSelectPeople">
|
||||||
|
<template #icon>
|
||||||
|
<CloseOutlined class="people-album-toolbar-icon"/>
|
||||||
|
</template>
|
||||||
|
</AButton>
|
||||||
|
<span style="font-size: 16px;font-weight: bold">
|
||||||
|
已选择 {{ imageStore.faceSelected.length }} 个人物
|
||||||
|
</span>
|
||||||
|
<AButton type="text" shape="default" class="people-album-toolbar-btn" size="middle" @click="selectAllPeople">
|
||||||
|
全选
|
||||||
|
</AButton>
|
||||||
|
</div>
|
||||||
|
<div class="people-album-toolbar-right">
|
||||||
|
|
||||||
|
<AButton type="text" shape="default" size="middle" class="people-album-toolbar-btn"
|
||||||
|
:disabled="imageStore.faceSelected.length !== 2" v-if="imageStore.faceSelectedKey === '0'">
|
||||||
|
<template #icon>
|
||||||
|
<BlockOutlined class="people-album-toolbar-icon"/>
|
||||||
|
</template>
|
||||||
|
合并人物
|
||||||
|
</AButton>
|
||||||
|
|
||||||
|
<AButton type="text" shape="default" size="middle" class="people-album-toolbar-btn" @click="hiddenFace">
|
||||||
|
<template #icon>
|
||||||
|
<EyeInvisibleOutlined class="people-album-toolbar-icon"/>
|
||||||
|
</template>
|
||||||
|
{{ imageStore.faceSelectedKey === '0' ? '隐藏人物' : '取消隐藏' }}
|
||||||
|
</AButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import useStore from "@/store";
|
||||||
|
import {modifyFaceTypeBatch} from "@/api/storage";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
faceList: {
|
||||||
|
type: Array as PropType<any[]>,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageStore = useStore().image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全选
|
||||||
|
*/
|
||||||
|
function selectAllPeople() {
|
||||||
|
imageStore.faceSelected = props.faceList.map((item) => item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消选择
|
||||||
|
*/
|
||||||
|
function cancelSelectPeople() {
|
||||||
|
imageStore.faceSelected = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏人物
|
||||||
|
*/
|
||||||
|
async function hiddenFace() {
|
||||||
|
if (imageStore.faceSelected.length === 0) return;
|
||||||
|
const res: any = await modifyFaceTypeBatch(imageStore.faceSelected, imageStore.faceSelectedKey === '0' ? 1 : 0);
|
||||||
|
if (res && res.code === 200) {
|
||||||
|
await imageStore.getFaceList();
|
||||||
|
imageStore.faceSelected= [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.people-album-toolbar {
|
||||||
|
position: fixed;
|
||||||
|
width: calc(100% - 220px);
|
||||||
|
height: 70px;
|
||||||
|
top: 70px;
|
||||||
|
z-index: 3;
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background-image: linear-gradient(45deg, #5789ff, #5c7bff 100%);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 3px 10px 0 rgba(0, 0, 0, .06);
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
|
.people-album-toolbar-left {
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.people-album-toolbar-right {
|
||||||
|
height: 100%;
|
||||||
|
width: 50%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.people-album-toolbar-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.people-album-toolbar-btn {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
184
src/views/Album/Phoalbum/AlbumCard.vue
Normal file
184
src/views/Album/Phoalbum/AlbumCard.vue
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
<template>
|
||||||
|
<Spin tip="Loading..." :spinning="imageStore.albumListLoading" size="middle">
|
||||||
|
<div class="phoalbum-item-container" v-if="imageStore.albumList">
|
||||||
|
<div class="phoalbum-item"
|
||||||
|
v-for="(album, index) in imageStore.albumList"
|
||||||
|
:key="album.id"
|
||||||
|
@click.prevent="handleClick(album.id,album.name)"
|
||||||
|
@mouseover="isHovered = index"
|
||||||
|
@mouseleave="isHovered = null">
|
||||||
|
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
|
||||||
|
:default-src="default_cover"/>
|
||||||
|
<div class="phoalbum-item-info">
|
||||||
|
<span class="phoalbum-item-name">{{ album.name }}</span>
|
||||||
|
<span class="phoalbum-item-date">{{ album.created_at }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="phoalbum-item-operation"
|
||||||
|
:class="{ 'fade-in': isHovered === index, 'fade-out': isHovered !== index }">
|
||||||
|
<ADropdown trigger="click" @click.stop>
|
||||||
|
<AButton type="text" shape="circle" size="small" @click.prevent>
|
||||||
|
<template #icon>
|
||||||
|
<AAvatar shape="circle" size="small" :src="more"/>
|
||||||
|
</template>
|
||||||
|
</AButton>
|
||||||
|
<template #overlay>
|
||||||
|
<AMenu>
|
||||||
|
<APopover placement="left" trigger="click">
|
||||||
|
<AMenuItem key="1">重命名相册</AMenuItem>
|
||||||
|
<template #content>
|
||||||
|
<AInput :placeholder="album.name" class="phoalbum-create-input"
|
||||||
|
v-model:value="albumRenameValue">
|
||||||
|
<template #suffix>
|
||||||
|
<AButton type="text" @click.prevent size="small" style="color: #0e87cc"
|
||||||
|
@click="renameAlbum(album.id, albumRenameValue)">
|
||||||
|
确认
|
||||||
|
</AButton>
|
||||||
|
</template>
|
||||||
|
</AInput>
|
||||||
|
</template>
|
||||||
|
</APopover>
|
||||||
|
<AMenuItem key="2"
|
||||||
|
@click.prevent="imageStore.openAlbumShareDialogHandler(true,album.cover_image ?`data:image/png;base64,`+album.cover_image: ``,album.id)">
|
||||||
|
分享相册
|
||||||
|
</AMenuItem>
|
||||||
|
<AMenuItem key="3" @click.prevent="deleteAlbum(album.id)">删除相册</AMenuItem>
|
||||||
|
<AMenuItem key="4">下载相册</AMenuItem>
|
||||||
|
</AMenu>
|
||||||
|
</template>
|
||||||
|
</ADropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="empty-content">
|
||||||
|
<AEmpty :image="empty" :image-style="{ width: '100%', height: '100%' }">
|
||||||
|
<template #description>
|
||||||
|
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
||||||
|
暂无相册,快去创建一个吧!
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</AEmpty>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import default_cover from "@/assets/images/default-cover.png";
|
||||||
|
import more from "@/assets/svgs/more.svg";
|
||||||
|
import empty from "@/assets/svgs/empty.svg";
|
||||||
|
import useStore from "@/store";
|
||||||
|
import {message} from "ant-design-vue";
|
||||||
|
import {deleteAlbumApi, renameAlbumApi} from "@/api/storage";
|
||||||
|
|
||||||
|
const imageStore = useStore().image;
|
||||||
|
const isHovered = ref<number | null>(null);
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const albumRenameValue = ref<string>("");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击相册跳转到详情页
|
||||||
|
* @param id
|
||||||
|
* @param albumName
|
||||||
|
*/
|
||||||
|
function handleClick(id: number, albumName: string) {
|
||||||
|
router.push({
|
||||||
|
path: route.path + `/${id}`, query: {name: albumName}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重命名相册
|
||||||
|
* @param id
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
async function renameAlbum(id: number, name: string) {
|
||||||
|
if (name.trim() === "") {
|
||||||
|
message.warning("相册名称不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res: any = await renameAlbumApi(id, name);
|
||||||
|
if (res && res.code === 200) {
|
||||||
|
albumRenameValue.value = "";
|
||||||
|
await imageStore.getAlbumList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除相册
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
async function deleteAlbum(id: number) {
|
||||||
|
const res: any = await deleteAlbumApi(id);
|
||||||
|
if (res && res.code === 200) {
|
||||||
|
await imageStore.getAlbumList();
|
||||||
|
} else {
|
||||||
|
message.error("删除相册失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.phoalbum-item-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 40px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.phoalbum-item {
|
||||||
|
width: 200px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.phoalbum-item-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.phoalbum-item-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phoalbum-item-date {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.phoalbum-item-operation {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 10;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0);
|
||||||
|
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in {
|
||||||
|
opacity: 1; /* 显示时透明度为1 */
|
||||||
|
transform: scale(1); /* 显示时缩放为1 */
|
||||||
|
z-index: 10; /* 显示时z-index为10 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-out {
|
||||||
|
opacity: 0; /* 隐藏时透明度为0 */
|
||||||
|
transform: scale(0); /* 隐藏时缩放为0 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@@ -40,7 +40,7 @@
|
|||||||
</AButton>
|
</AButton>
|
||||||
</div>
|
</div>
|
||||||
<ImageUpload/>
|
<ImageUpload/>
|
||||||
<ImageToolbar :selected="imageStore.selected" :image-list="albumList"/>
|
<ImageToolbar :selected="imageStore.selected" :image-list="imageList"/>
|
||||||
<div class="phoalbum-detail-content">
|
<div class="phoalbum-detail-content">
|
||||||
<div class="phoalbum-detail-content-nav">
|
<div class="phoalbum-detail-content-nav">
|
||||||
<div class="phoalbum-detail-content-nav-left">
|
<div class="phoalbum-detail-content-nav-left">
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
<span class="phoalbum-detail-content-nav-name">{{ route.query.name }}</span>
|
<span class="phoalbum-detail-content-nav-name">{{ route.query.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="phoalbum-detail-content-nav-right">
|
<div class="phoalbum-detail-content-nav-right">
|
||||||
<span class="phoalbum-detail-content-nav-date">共 {{ imageStore.countTotalImages(albumList) }} 张照片</span>
|
<span class="phoalbum-detail-content-nav-date">共 {{ imageStore.countTotalImages(imageList) }} 张照片</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="phoalbum-detail-content-desc">
|
<div class="phoalbum-detail-content-desc">
|
||||||
@@ -59,82 +59,44 @@
|
|||||||
<span>相册描述</span>
|
<span>相册描述</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="phoalbum-detail-content-list">
|
<div class="phoalbum-detail-content-list">
|
||||||
<div style="width:100%;height:100%;" v-if="albumList && albumList.length !== 0">
|
<ImageWaterfallList :image-list="imageList"/>
|
||||||
<div v-for="(itemList, index) in albumList" :key="index">
|
|
||||||
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
|
|
||||||
<AImagePreviewGroup>
|
|
||||||
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options">
|
|
||||||
<template #default="{ item }">
|
|
||||||
<CheckCard :key="index"
|
|
||||||
class="photo-item"
|
|
||||||
margin="0"
|
|
||||||
border-radius="0"
|
|
||||||
v-model="imageStore.selected"
|
|
||||||
:showHoverCircle="true"
|
|
||||||
:iconSize="20"
|
|
||||||
:showSelectedEffect="true"
|
|
||||||
:value="item.id">
|
|
||||||
<AImage :src="item.thumbnail"
|
|
||||||
:alt="item.file_name"
|
|
||||||
:key="index"
|
|
||||||
:height="200"
|
|
||||||
:previewMask="false"
|
|
||||||
:preview="{
|
|
||||||
src: item.url,
|
|
||||||
}"
|
|
||||||
loading="lazy"/>
|
|
||||||
</CheckCard>
|
|
||||||
</template>
|
|
||||||
</Vue3JustifiedLayout>
|
|
||||||
</AImagePreviewGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty :image="empty">
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
暂无照片,快去上传吧
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Vue3JustifiedLayout from "vue3-justified-layout";
|
|
||||||
import 'vue3-justified-layout/dist/style.css';
|
|
||||||
import {deleteAlbumApi, queryAlbumDetailListApi, renameAlbumApi} from "@/api/storage";
|
import {deleteAlbumApi, queryAlbumDetailListApi, renameAlbumApi} from "@/api/storage";
|
||||||
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
|
import ImageToolbar from "@/components/ImageToolbar/ImageToolbar.vue";
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import empty from "@/assets/svgs/empty.svg";
|
|
||||||
import ImageUpload from "@/views/Photograph/ImageUpload/ImageUpload.vue";
|
import ImageUpload from "@/components/ImageUpload/ImageUpload.vue";
|
||||||
import {message} from "ant-design-vue";
|
import {message} from "ant-design-vue";
|
||||||
|
import ImageWaterfallList from "@/components/ImageWaterfallList/ImageWaterfallList.vue";
|
||||||
|
|
||||||
|
|
||||||
const imageStore = useStore().image;
|
const imageStore = useStore().image;
|
||||||
const albumList = ref<any[]>([]);
|
const imageList = ref<any[]>([]);
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const options = reactive({
|
|
||||||
targetRowHeight: 200 // 高度
|
|
||||||
});
|
|
||||||
const upload = useStore().upload;
|
const upload = useStore().upload;
|
||||||
|
|
||||||
|
|
||||||
async function getAlbumList(id: number) {
|
async function getImageList(id: number) {
|
||||||
|
imageStore.imageListLoading = true;
|
||||||
const res: any = await queryAlbumDetailListApi(id, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
const res: any = await queryAlbumDetailListApi(id, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
albumList.value = res.data.records;
|
imageList.value = res.data.records;
|
||||||
}
|
}
|
||||||
|
imageStore.imageListLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const idParam = route.params.id;
|
const idParam = route.params.id;
|
||||||
const albumId = Array.isArray(idParam) ? idParam[0] : idParam;
|
const albumId = Array.isArray(idParam) ? idParam[0] : idParam;
|
||||||
getAlbumList(parseInt(albumId, 10));
|
getImageList(parseInt(albumId, 10));
|
||||||
});
|
});
|
||||||
|
|
||||||
function openUploadModal(): void {
|
function openUploadModal(): void {
|
||||||
@@ -185,6 +147,7 @@ function goBack(): void {
|
|||||||
router.go(-1);
|
router.go(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.phoalbum-detail {
|
.phoalbum-detail {
|
||||||
|
@@ -30,7 +30,7 @@
|
|||||||
排序
|
排序
|
||||||
</AButton>
|
</AButton>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<AMenu selectable :selectedKeys="[selectedKey]" @select="handleSelect">
|
<AMenu selectable :selectedKeys="[imageStore.sortSelectedKey]" @select="handleSelect">
|
||||||
<AMenuItem :key="true">按时间排序</AMenuItem>
|
<AMenuItem :key="true">按时间排序</AMenuItem>
|
||||||
<AMenuItem :key="false">按名称排序</AMenuItem>
|
<AMenuItem :key="false">按名称排序</AMenuItem>
|
||||||
</AMenu>
|
</AMenu>
|
||||||
@@ -53,255 +53,21 @@
|
|||||||
@change="handleTabChange">
|
@change="handleTabChange">
|
||||||
<template #rightExtra>
|
<template #rightExtra>
|
||||||
<span
|
<span
|
||||||
style="color: #999; font-size: 12px;">已全部加载,共 {{ albumList ? albumList.length : 0 }} 个相册</span>
|
style="color: #999; font-size: 12px;">已全部加载,共 {{
|
||||||
|
imageStore.albumList ? imageStore.albumList.length : 0
|
||||||
|
}} 个相册</span>
|
||||||
</template>
|
</template>
|
||||||
<ATabPane key="-1" :tab="imageStore.tabMap[-1]">
|
<ATabPane key="-1" :tab="imageStore.tabMap[-1]">
|
||||||
<ASpin tip="Loading..." :spinning="loading" size="large" wrapperClassName="spin-container">
|
<AlbumCard/>
|
||||||
<div class="phoalbum-item-container" v-if="albumList && albumList.length != 0">
|
|
||||||
<div class="phoalbum-item"
|
|
||||||
v-for="(album, index) in albumList"
|
|
||||||
:key="album.id"
|
|
||||||
@click.prevent="handleClick(album.id,album.name)"
|
|
||||||
@mouseover="isHovered = index"
|
|
||||||
@mouseleave="isHovered = null">
|
|
||||||
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
|
|
||||||
:default-src="default_cover"/>
|
|
||||||
<div class="phoalbum-item-info">
|
|
||||||
<span class="phoalbum-item-name">{{ album.name }}</span>
|
|
||||||
<span class="phoalbum-item-date">{{ album.created_at }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="phoalbum-item-operation"
|
|
||||||
:class="{ 'fade-in': isHovered === index, 'fade-out': isHovered !== index }">
|
|
||||||
<ADropdown trigger="click" @click.stop>
|
|
||||||
<AButton type="text" shape="circle" size="small" @click.prevent>
|
|
||||||
<template #icon>
|
|
||||||
<AAvatar shape="circle" size="small" :src="more"/>
|
|
||||||
</template>
|
|
||||||
</AButton>
|
|
||||||
<template #overlay>
|
|
||||||
<AMenu>
|
|
||||||
<APopover placement="left" trigger="click">
|
|
||||||
<AMenuItem key="1">重命名相册</AMenuItem>
|
|
||||||
<template #content>
|
|
||||||
<AInput :placeholder="album.name" class="phoalbum-create-input"
|
|
||||||
v-model:value="albumRenameValue">
|
|
||||||
<template #suffix>
|
|
||||||
<AButton type="text" @click.prevent size="small" style="color: #0e87cc"
|
|
||||||
@click="renameAlbum(album.id, albumRenameValue)">
|
|
||||||
确认
|
|
||||||
</AButton>
|
|
||||||
</template>
|
|
||||||
</AInput>
|
|
||||||
</template>
|
|
||||||
</APopover>
|
|
||||||
<AMenuItem key="2"
|
|
||||||
@click.prevent="imageStore.openAlbumShareDialogHandler(true,album.cover_image ?`data:image/png;base64,`+album.cover_image: ``,album.id)">
|
|
||||||
分享相册
|
|
||||||
</AMenuItem>
|
|
||||||
<AMenuItem key="3" @click.prevent="deleteAlbum(album.id)">删除相册</AMenuItem>
|
|
||||||
<AMenuItem key="4">下载相册</AMenuItem>
|
|
||||||
</AMenu>
|
|
||||||
</template>
|
|
||||||
</ADropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty :image="empty" :image-style="{ width: '100%', height: '100%' }">
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
暂无相册,快去创建一个吧!
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
|
||||||
</ASpin>
|
|
||||||
</ATabPane>
|
</ATabPane>
|
||||||
<ATabPane key="0" :tab="imageStore.tabMap[0]">
|
<ATabPane key="0" :tab="imageStore.tabMap[0]">
|
||||||
<ASpin tip="Loading..." :spinning="loading" size="large" wrapperClassName="spin-container">
|
<AlbumCard/>
|
||||||
<div class="phoalbum-item-container" v-if="albumList && albumList.length != 0">
|
|
||||||
<div class="phoalbum-item"
|
|
||||||
v-for="(album, index) in albumList"
|
|
||||||
:key="album.id"
|
|
||||||
@click.prevent="handleClick(album.id,album.name)"
|
|
||||||
@mouseover="isHovered = index"
|
|
||||||
@mouseleave="isHovered = null">
|
|
||||||
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
|
|
||||||
:default-src="default_cover"/>
|
|
||||||
<div class="phoalbum-item-info">
|
|
||||||
<span class="phoalbum-item-name">{{ album.name }}</span>
|
|
||||||
<span class="phoalbum-item-date">{{ album.created_at }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="phoalbum-item-operation"
|
|
||||||
:class="{ 'fade-in': isHovered === index, 'fade-out': isHovered !== index }">
|
|
||||||
<ADropdown trigger="click" @click.stop>
|
|
||||||
<AButton type="text" shape="circle" size="small" @click.prevent>
|
|
||||||
<template #icon>
|
|
||||||
<AAvatar shape="circle" size="small" :src="more"/>
|
|
||||||
</template>
|
|
||||||
</AButton>
|
|
||||||
<template #overlay>
|
|
||||||
<AMenu>
|
|
||||||
<APopover placement="left" trigger="click">
|
|
||||||
<AMenuItem key="1">重命名相册</AMenuItem>
|
|
||||||
<template #content>
|
|
||||||
<AInput :placeholder="album.name" class="phoalbum-create-input"
|
|
||||||
v-model:value="albumRenameValue">
|
|
||||||
<template #suffix>
|
|
||||||
<AButton type="text" @click.prevent size="small" style="color: #0e87cc"
|
|
||||||
@click="renameAlbum(album.id, albumRenameValue)">
|
|
||||||
确认
|
|
||||||
</AButton>
|
|
||||||
</template>
|
|
||||||
</AInput>
|
|
||||||
</template>
|
|
||||||
</APopover>
|
|
||||||
<AMenuItem key="2"
|
|
||||||
@click.prevent="imageStore.openAlbumShareDialogHandler(true,album.cover_image ?`data:image/png;base64,`+album.cover_image: ``,album.id)">
|
|
||||||
分享相册
|
|
||||||
</AMenuItem>
|
|
||||||
<AMenuItem key="3" @click.prevent="deleteAlbum(album.id)">删除相册</AMenuItem>
|
|
||||||
<AMenuItem key="4">下载相册</AMenuItem>
|
|
||||||
</AMenu>
|
|
||||||
</template>
|
|
||||||
</ADropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty :image="empty" :image-style="{ width: '100%', height: '100%' }">
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
暂无相册,快去创建一个吧!
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
|
||||||
</ASpin>
|
|
||||||
</ATabPane>
|
</ATabPane>
|
||||||
<ATabPane key="1" :tab="imageStore.tabMap[1]">
|
<ATabPane key="1" :tab="imageStore.tabMap[1]">
|
||||||
<ASpin tip="Loading..." :spinning="loading" size="large" wrapperClassName="spin-container">
|
<AlbumCard/>
|
||||||
<div class="phoalbum-item-container" v-if="albumList && albumList.length != 0">
|
|
||||||
<div class="phoalbum-item"
|
|
||||||
v-for="(album, index) in albumList"
|
|
||||||
:key="album.id"
|
|
||||||
@click.prevent="handleClick(album.id,album.name)"
|
|
||||||
@mouseover="isHovered = index"
|
|
||||||
@mouseleave="isHovered = null">
|
|
||||||
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
|
|
||||||
:default-src="default_cover"/>
|
|
||||||
<div class="phoalbum-item-info">
|
|
||||||
<span class="phoalbum-item-name">{{ album.name }}</span>
|
|
||||||
<span class="phoalbum-item-date">{{ album.created_at }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="phoalbum-item-operation"
|
|
||||||
:class="{ 'fade-in': isHovered === index, 'fade-out': isHovered !== index }">
|
|
||||||
<ADropdown trigger="click" @click.stop>
|
|
||||||
<AButton type="text" shape="circle" size="small" @click.prevent>
|
|
||||||
<template #icon>
|
|
||||||
<AAvatar shape="circle" size="small" :src="more"/>
|
|
||||||
</template>
|
|
||||||
</AButton>
|
|
||||||
<template #overlay>
|
|
||||||
<AMenu>
|
|
||||||
<APopover placement="left" trigger="click">
|
|
||||||
<AMenuItem key="1">重命名相册</AMenuItem>
|
|
||||||
<template #content>
|
|
||||||
<AInput :placeholder="album.name" class="phoalbum-create-input"
|
|
||||||
v-model:value="albumRenameValue">
|
|
||||||
<template #suffix>
|
|
||||||
<AButton type="text" @click.prevent size="small" style="color: #0e87cc"
|
|
||||||
@click="renameAlbum(album.id, albumRenameValue)">
|
|
||||||
确认
|
|
||||||
</AButton>
|
|
||||||
</template>
|
|
||||||
</AInput>
|
|
||||||
</template>
|
|
||||||
</APopover>
|
|
||||||
<AMenuItem key="2"
|
|
||||||
@click.prevent="imageStore.openAlbumShareDialogHandler(true,album.cover_image ?`data:image/png;base64,`+album.cover_image: ``,album.id)">
|
|
||||||
分享相册
|
|
||||||
</AMenuItem>
|
|
||||||
<AMenuItem key="3" @click.prevent="deleteAlbum(album.id)">删除相册</AMenuItem>
|
|
||||||
<AMenuItem key="4">下载相册</AMenuItem>
|
|
||||||
</AMenu>
|
|
||||||
</template>
|
|
||||||
</ADropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty :image="empty" :image-style="{ width: '100%', height: '100%' }">
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
暂无相册,快去创建一个吧!
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
|
||||||
</ASpin>
|
|
||||||
</ATabPane>
|
</ATabPane>
|
||||||
<ATabPane key="2" :tab="imageStore.tabMap[2]">
|
<ATabPane key="2" :tab="imageStore.tabMap[2]">
|
||||||
<ASpin tip="Loading..." :spinning="loading" size="large" wrapperClassName="spin-container">
|
<AlbumCard/>
|
||||||
<div class="phoalbum-item-container" v-if="albumList && albumList.length != 0">
|
|
||||||
<div class="phoalbum-item"
|
|
||||||
v-for="(album, index) in albumList"
|
|
||||||
:key="album.id"
|
|
||||||
@click.prevent="handleClick(album.id,album.name)"
|
|
||||||
@mouseover="isHovered = index"
|
|
||||||
@mouseleave="isHovered = null">
|
|
||||||
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
|
|
||||||
:default-src="default_cover"/>
|
|
||||||
<div class="phoalbum-item-info">
|
|
||||||
<span class="phoalbum-item-name">{{ album.name }}</span>
|
|
||||||
<span class="phoalbum-item-date">{{ album.created_at }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="phoalbum-item-operation"
|
|
||||||
:class="{ 'fade-in': isHovered === index, 'fade-out': isHovered !== index }">
|
|
||||||
<ADropdown trigger="click" @click.stop>
|
|
||||||
<AButton type="text" shape="circle" size="small" @click.prevent>
|
|
||||||
<template #icon>
|
|
||||||
<AAvatar shape="circle" size="small" :src="more"/>
|
|
||||||
</template>
|
|
||||||
</AButton>
|
|
||||||
<template #overlay>
|
|
||||||
<AMenu>
|
|
||||||
<APopover placement="left" trigger="click">
|
|
||||||
<AMenuItem key="1">重命名相册</AMenuItem>
|
|
||||||
<template #content>
|
|
||||||
<AInput :placeholder="album.name" class="phoalbum-create-input"
|
|
||||||
v-model:value="albumRenameValue">
|
|
||||||
<template #suffix>
|
|
||||||
<AButton type="text" @click.prevent size="small" style="color: #0e87cc"
|
|
||||||
@click="renameAlbum(album.id, albumRenameValue)">
|
|
||||||
确认
|
|
||||||
</AButton>
|
|
||||||
</template>
|
|
||||||
</AInput>
|
|
||||||
</template>
|
|
||||||
</APopover>
|
|
||||||
<AMenuItem key="2"
|
|
||||||
@click.prevent="imageStore.openAlbumShareDialogHandler(true,album.cover_image ?`data:image/png;base64,`+album.cover_image: ``,album.id)">
|
|
||||||
分享相册
|
|
||||||
</AMenuItem>
|
|
||||||
<AMenuItem key="3" @click.prevent="deleteAlbum(album.id)">删除相册</AMenuItem>
|
|
||||||
<AMenuItem key="4">下载相册</AMenuItem>
|
|
||||||
</AMenu>
|
|
||||||
</template>
|
|
||||||
</ADropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty :image="empty" :image-style="{ width: '100%', height: '100%' }">
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
暂无相册,快去创建一个吧!
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
|
||||||
</ASpin>
|
|
||||||
</ATabPane>
|
</ATabPane>
|
||||||
</ATabs>
|
</ATabs>
|
||||||
</div>
|
</div>
|
||||||
@@ -309,27 +75,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import more from "@/assets/svgs/more.svg";
|
import {createAlbumApi} from "@/api/storage";
|
||||||
import {albumListApi, createAlbumApi, deleteAlbumApi, renameAlbumApi} from "@/api/storage";
|
|
||||||
import {message} from "ant-design-vue";
|
import {message} from "ant-design-vue";
|
||||||
import default_cover from "@/assets/images/default-cover.png";
|
|
||||||
import useStore from "@/store";
|
|
||||||
import empty from "@/assets/svgs/empty.svg";
|
|
||||||
import AlbumShareModal from "@/views/Album/Phoalbum/AlbumShareModal.vue";
|
|
||||||
|
|
||||||
const isHovered = ref<number | null>(null);
|
import useStore from "@/store";
|
||||||
|
|
||||||
|
import AlbumShareModal from "@/views/Album/Phoalbum/AlbumShareModal.vue";
|
||||||
|
import AlbumCard from "@/views/Album/Phoalbum/AlbumCard.vue";
|
||||||
|
|
||||||
|
|
||||||
const albumNameValue = ref<string>("未命名相册");
|
const albumNameValue = ref<string>("未命名相册");
|
||||||
const albumRenameValue = ref<string>("");
|
|
||||||
|
|
||||||
const selectedKey = ref<boolean>(true);
|
|
||||||
|
|
||||||
const albumList = ref<any[]>([]);
|
|
||||||
|
|
||||||
const loading = ref<boolean>(false);
|
|
||||||
|
|
||||||
const imageStore = useStore().image;
|
const imageStore = useStore().image;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建相册
|
* 创建相册
|
||||||
*/
|
*/
|
||||||
@@ -341,7 +101,7 @@ async function createAlbumSubmit() {
|
|||||||
const res: any = await createAlbumApi(albumNameValue.value);
|
const res: any = await createAlbumApi(albumNameValue.value);
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
albumNameValue.value = "未命名相册";
|
albumNameValue.value = "未命名相册";
|
||||||
await getAlbumList(parseInt(imageStore.tabActiveKey), selectedKey.value);
|
await imageStore.getAlbumList();
|
||||||
} else {
|
} else {
|
||||||
message.error("创建相册失败");
|
message.error("创建相册失败");
|
||||||
}
|
}
|
||||||
@@ -352,8 +112,8 @@ async function createAlbumSubmit() {
|
|||||||
* @param key
|
* @param key
|
||||||
*/
|
*/
|
||||||
async function handleSelect({key}) {
|
async function handleSelect({key}) {
|
||||||
selectedKey.value = key;
|
imageStore.sortSelectedKey = key;
|
||||||
await getAlbumList(parseInt(imageStore.tabActiveKey), key);
|
await imageStore.getAlbumList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -362,70 +122,12 @@ async function handleSelect({key}) {
|
|||||||
*/
|
*/
|
||||||
async function handleTabChange(activeKey: string) {
|
async function handleTabChange(activeKey: string) {
|
||||||
imageStore.tabActiveKey = activeKey;
|
imageStore.tabActiveKey = activeKey;
|
||||||
await getAlbumList(parseInt(activeKey), selectedKey.value);
|
await imageStore.getAlbumList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取相册列表
|
|
||||||
* @param type
|
|
||||||
* @param sort
|
|
||||||
*/
|
|
||||||
async function getAlbumList(type: number = 0, sort: boolean = true) {
|
|
||||||
albumList.value = [];
|
|
||||||
loading.value = true;
|
|
||||||
const res: any = await albumListApi(type, sort);
|
|
||||||
if (res && res.code === 200) {
|
|
||||||
albumList.value = res.data.albums;
|
|
||||||
}
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重命名相册
|
|
||||||
* @param id
|
|
||||||
* @param name
|
|
||||||
*/
|
|
||||||
async function renameAlbum(id: number, name: string) {
|
|
||||||
if (name.trim() === "") {
|
|
||||||
message.warning("相册名称不能为空");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const res: any = await renameAlbumApi(id, name);
|
|
||||||
if (res && res.code === 200) {
|
|
||||||
albumRenameValue.value = "";
|
|
||||||
await getAlbumList(parseInt(imageStore.tabActiveKey), selectedKey.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除相册
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
async function deleteAlbum(id: number) {
|
|
||||||
const res: any = await deleteAlbumApi(id);
|
|
||||||
if (res && res.code === 200) {
|
|
||||||
await getAlbumList(parseInt(imageStore.tabActiveKey), selectedKey.value);
|
|
||||||
} else {
|
|
||||||
message.error("删除相册失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 点击相册跳转到详情页
|
|
||||||
* @param id
|
|
||||||
* @param albumName
|
|
||||||
*/
|
|
||||||
function handleClick(id: number, albumName: string) {
|
|
||||||
router.push({
|
|
||||||
path: route.path + `/${id}`, query: {name: albumName}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getAlbumList(parseInt(imageStore.tabActiveKey), selectedKey.value);
|
imageStore.getAlbumList();
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -462,79 +164,6 @@ onMounted(() => {
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - 65px);
|
height: calc(100% - 65px);
|
||||||
|
|
||||||
.spin-container {
|
|
||||||
position: relative;
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
width: 100%;
|
|
||||||
height: 70vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phoalbum-item-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 40px;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
.phoalbum-item {
|
|
||||||
width: 200px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 15px;
|
|
||||||
padding: 10px;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.phoalbum-item-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
.phoalbum-item-name {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phoalbum-item-date {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.phoalbum-item-operation {
|
|
||||||
position: absolute;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
z-index: 10;
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0);
|
|
||||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-in {
|
|
||||||
opacity: 1; /* 显示时透明度为1 */
|
|
||||||
transform: scale(1); /* 显示时缩放为1 */
|
|
||||||
z-index: 10; /* 显示时z-index为10 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-out {
|
|
||||||
opacity: 0; /* 隐藏时透明度为0 */
|
|
||||||
transform: scale(0); /* 隐藏时缩放为0 */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,69 +9,37 @@
|
|||||||
<span class="thing-detail-content-nav-name">{{ getZhLabelNameByEnName(route.query.tag as string) }}</span>
|
<span class="thing-detail-content-nav-name">{{ getZhLabelNameByEnName(route.query.tag as string) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ImageToolbar :selected="imageStore.selected" :image-list="albumList"/>
|
<ImageToolbar :selected="imageStore.selected" :image-list="imageList"/>
|
||||||
<div class="thing-album-detail-info">
|
<div class="thing-album-detail-info">
|
||||||
<span style="font-size: 14px;color: #999999">共{{ imageStore.countTotalImages(albumList) }}张照片</span>
|
<span style="font-size: 14px;color: #999999">共{{ imageStore.countTotalImages(imageList) }}张照片</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="thing-album-detail-list">
|
<div class="thing-album-detail-list">
|
||||||
<div style="width:100%;height:100%;">
|
<ImageWaterfallList :image-list="imageList"/>
|
||||||
<div v-for="(itemList, index) in albumList" :key="index">
|
|
||||||
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
|
|
||||||
<AImagePreviewGroup>
|
|
||||||
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options">
|
|
||||||
<template #default="{ item }">
|
|
||||||
<CheckCard :key="index"
|
|
||||||
class="photo-item"
|
|
||||||
margin="0"
|
|
||||||
border-radius="0"
|
|
||||||
v-model="imageStore.selected"
|
|
||||||
:showHoverCircle="true"
|
|
||||||
:iconSize="20"
|
|
||||||
:showSelectedEffect="true"
|
|
||||||
:value="item.id">
|
|
||||||
<AImage :src="item.thumbnail"
|
|
||||||
:alt="item.file_name"
|
|
||||||
:key="index"
|
|
||||||
:height="200"
|
|
||||||
:previewMask="false"
|
|
||||||
:preview="{
|
|
||||||
src: item.url,
|
|
||||||
}"
|
|
||||||
loading="lazy"/>
|
|
||||||
</CheckCard>
|
|
||||||
</template>
|
|
||||||
</Vue3JustifiedLayout>
|
|
||||||
</AImagePreviewGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import Vue3JustifiedLayout from "vue3-justified-layout";
|
|
||||||
import 'vue3-justified-layout/dist/style.css';
|
|
||||||
import {queryThingDetailListApi} from "@/api/storage";
|
import {queryThingDetailListApi} from "@/api/storage";
|
||||||
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
|
import ImageToolbar from "@/components/ImageToolbar/ImageToolbar.vue";
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import {getZhCategoryNameByEnName, getZhLabelNameByEnName} from "@/constant/coco_ssd_label_category.ts";
|
import {getZhCategoryNameByEnName, getZhLabelNameByEnName} from "@/constant/coco_ssd_label_category.ts";
|
||||||
|
import ImageWaterfallList from "@/components/ImageWaterfallList/ImageWaterfallList.vue";
|
||||||
|
|
||||||
|
|
||||||
const imageStore = useStore().image;
|
const imageStore = useStore().image;
|
||||||
const albumList = ref<any[]>([]);
|
const imageList = ref<any[]>([]);
|
||||||
const upload = useStore().upload;
|
const upload = useStore().upload;
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const options = reactive({
|
|
||||||
targetRowHeight: 200 // 高度
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getImageList(tag_name: string) {
|
async function getImageList(tag_name: string) {
|
||||||
|
imageStore.imageListLoading = true;
|
||||||
const res: any = await queryThingDetailListApi(tag_name, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
const res: any = await queryThingDetailListApi(tag_name, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
albumList.value = res.data.records;
|
imageList.value = res.data.records;
|
||||||
}
|
}
|
||||||
|
imageStore.imageListLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@@ -5,8 +5,8 @@
|
|||||||
<AButton type="link" size="large" class="thing-album-button">事物</AButton>
|
<AButton type="link" size="large" class="thing-album-button">事物</AButton>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="thing-album-content" v-if="thingAlbumList && thingAlbumList.length>0">
|
<div class="thing-album-content" v-if="albumList && albumList.length>0">
|
||||||
<div class="thing-album-content-item" v-for="(item, index) in thingAlbumList" :key="index">
|
<div class="thing-album-content-item" v-for="(item, index) in albumList" :key="index">
|
||||||
<span class="thing-album-title">{{ getZhCategoryNameByEnName(item.category) }}</span>
|
<span class="thing-album-title">{{ getZhCategoryNameByEnName(item.category) }}</span>
|
||||||
<div class="thing-album-wrapper">
|
<div class="thing-album-wrapper">
|
||||||
<div class="thing-album-container" v-for="(tags, indexList) in item.list" :key="indexList"
|
<div class="thing-album-container" v-for="(tags, indexList) in item.list" :key="indexList"
|
||||||
@@ -40,12 +40,13 @@ import {getZhCategoryNameByEnName, getZhLabelNameByEnName} from "@/constant/coco
|
|||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import empty from "@/assets/svgs/empty.svg";
|
import empty from "@/assets/svgs/empty.svg";
|
||||||
|
|
||||||
const thingAlbumList = ref<any[]>([]);
|
|
||||||
|
|
||||||
async function getThingAlbumList(provider: string, bucket: string) {
|
const albumList = ref<any[]>([]);
|
||||||
|
|
||||||
|
async function getAlbumList(provider: string, bucket: string) {
|
||||||
const res: any = await queryThingAlbumApi(provider, bucket);
|
const res: any = await queryThingAlbumApi(provider, bucket);
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
thingAlbumList.value = res.data.records;
|
albumList.value = res.data.records;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@ function handleClick(id: string, category: string, tag: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getThingAlbumList(upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
getAlbumList(upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@@ -62,9 +62,9 @@ import warn from '@/assets/svgs/warn.svg';
|
|||||||
import remove from '@/assets/svgs/remove.svg';
|
import remove from '@/assets/svgs/remove.svg';
|
||||||
import empty from '@/assets/svgs/empty.svg';
|
import empty from '@/assets/svgs/empty.svg';
|
||||||
import {blobToBase64} from "@/utils/imageUtils/blobToBase64.ts";
|
import {blobToBase64} from "@/utils/imageUtils/blobToBase64.ts";
|
||||||
import {uploadImage} from "src/api/phone";
|
|
||||||
import {message} from "ant-design-vue";
|
import {message} from "ant-design-vue";
|
||||||
import {useI18n} from "vue-i18n";
|
import {useI18n} from "vue-i18n";
|
||||||
|
import {uploadImage} from "@/api/phone";
|
||||||
|
|
||||||
const upscale = useStore().upscale;
|
const upscale = useStore().upscale;
|
||||||
|
|
||||||
|
@@ -29,7 +29,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</APopover>
|
</APopover>
|
||||||
</div>
|
</div>
|
||||||
<image-toolbar :selected="imageStore.selected" :image-list="images"/>
|
<image-toolbar :selected="imageStore.selected" :image-list="imageList"/>
|
||||||
<div class="photo-list">
|
<div class="photo-list">
|
||||||
<ATabs size="small" :tabBarGutter="50" type="line" tabPosition="top" :tabBarStyle="{position:'unset'}"
|
<ATabs size="small" :tabBarGutter="50" type="line" tabPosition="top" :tabBarStyle="{position:'unset'}"
|
||||||
@change="handleTabChange"
|
@change="handleTabChange"
|
||||||
@@ -41,214 +41,20 @@
|
|||||||
<ATooltip title="开启后即可隐藏已添加到相册的照片" placement="bottom">
|
<ATooltip title="开启后即可隐藏已添加到相册的照片" placement="bottom">
|
||||||
<QuestionCircleOutlined/>
|
<QuestionCircleOutlined/>
|
||||||
</ATooltip>
|
</ATooltip>
|
||||||
<ASwitch size="default" v-model:checked="imageStore.switchValue"/>
|
<ASwitch size="default" v-model:checked="imageStore.switchValue" @change="handleSwitchChange"/>
|
||||||
</AFlex>
|
</AFlex>
|
||||||
</template>
|
</template>
|
||||||
<ATabPane key="all" :tab="imageStore.homeTabMap['all']">
|
<ATabPane key="all" :tab="imageStore.homeTabMap['all']">
|
||||||
<ASpin size="large" :spinning="loading" :delay="500" wrapper-class-name="spin-wrapper">
|
<ImageWaterfallList :image-list="imageList"/>
|
||||||
<div style="width:100%;height:100%;" v-if="images">
|
|
||||||
<div v-for="(itemList, index) in images" :key="index">
|
|
||||||
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
|
|
||||||
<AImagePreviewGroup>
|
|
||||||
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options"
|
|
||||||
style="line-height: 0 !important;">
|
|
||||||
<template #default="{ item }">
|
|
||||||
<CheckCard :key="index"
|
|
||||||
class="photo-item"
|
|
||||||
margin="0"
|
|
||||||
border-radius="0"
|
|
||||||
v-model="imageStore.selected"
|
|
||||||
:showHoverCircle="true"
|
|
||||||
:iconSize="20"
|
|
||||||
:showSelectedEffect="true"
|
|
||||||
:value="item.id">
|
|
||||||
<AImage :src="item.thumbnail"
|
|
||||||
:alt="item.file_name"
|
|
||||||
:key="index"
|
|
||||||
:height="200"
|
|
||||||
:preview="{
|
|
||||||
src: item.url,
|
|
||||||
}"
|
|
||||||
loading="lazy">
|
|
||||||
<template #previewMask>
|
|
||||||
</template>
|
|
||||||
</AImage>
|
|
||||||
</CheckCard>
|
|
||||||
</template>
|
|
||||||
</Vue3JustifiedLayout>
|
|
||||||
</AImagePreviewGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty :image="empty"
|
|
||||||
:image-style="{
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
}">
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
还没检测到任何图片,快去上传吧!
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
|
||||||
</ASpin>
|
|
||||||
</ATabPane>
|
</ATabPane>
|
||||||
<ATabPane key="video" :tab="imageStore.homeTabMap['video']">
|
<ATabPane key="video" :tab="imageStore.homeTabMap['video']">
|
||||||
<ASpin size="large" :spinning="loading" :delay="500">
|
<ImageWaterfallList :image-list="imageList"/>
|
||||||
<div style="width:100%;height:100%;" v-if="images">
|
|
||||||
<div v-for="(itemList, index) in images" :key="index">
|
|
||||||
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
|
|
||||||
<AImagePreviewGroup>
|
|
||||||
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options"
|
|
||||||
style="line-height: 0 !important;">
|
|
||||||
<template #default="{ item }">
|
|
||||||
<CheckCard :key="index"
|
|
||||||
class="photo-item"
|
|
||||||
margin="0"
|
|
||||||
border-radius="0"
|
|
||||||
v-model="imageStore.selected"
|
|
||||||
:showHoverCircle="true"
|
|
||||||
:iconSize="20"
|
|
||||||
:showSelectedEffect="true"
|
|
||||||
:value="item.id">
|
|
||||||
<AImage :src="item.thumbnail"
|
|
||||||
:alt="item.file_name"
|
|
||||||
:key="index"
|
|
||||||
:height="200"
|
|
||||||
:preview="{
|
|
||||||
src: item.url,
|
|
||||||
}"
|
|
||||||
loading="lazy">
|
|
||||||
<template #previewMask>
|
|
||||||
</template>
|
|
||||||
</AImage>
|
|
||||||
</CheckCard>
|
|
||||||
</template>
|
|
||||||
</Vue3JustifiedLayout>
|
|
||||||
</AImagePreviewGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty
|
|
||||||
:image="empty"
|
|
||||||
:image-style="{
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
还没检测到任何视频,快去上传吧!
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
|
||||||
</ASpin>
|
|
||||||
</ATabPane>
|
</ATabPane>
|
||||||
<ATabPane key="gif" :tab="imageStore.homeTabMap['gif']">
|
<ATabPane key="gif" :tab="imageStore.homeTabMap['gif']">
|
||||||
<ASpin size="large" :spinning="loading" :delay="500">
|
<ImageWaterfallList :image-list="imageList"/>
|
||||||
<div style="width:100%;height:100%;" v-if="images">
|
|
||||||
<div v-for="(itemList, index) in images" :key="index">
|
|
||||||
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
|
|
||||||
<AImagePreviewGroup>
|
|
||||||
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options"
|
|
||||||
style="line-height: 0 !important;">
|
|
||||||
<template #default="{ item }">
|
|
||||||
<CheckCard :key="index"
|
|
||||||
class="photo-item"
|
|
||||||
margin="0"
|
|
||||||
border-radius="0"
|
|
||||||
v-model="imageStore.selected"
|
|
||||||
:showHoverCircle="true"
|
|
||||||
:iconSize="20"
|
|
||||||
:showSelectedEffect="true"
|
|
||||||
:value="item.id">
|
|
||||||
<AImage :src="item.thumbnail"
|
|
||||||
:alt="item.file_name"
|
|
||||||
:key="index"
|
|
||||||
:height="200"
|
|
||||||
:preview="{
|
|
||||||
src: item.url,
|
|
||||||
}"
|
|
||||||
loading="lazy">
|
|
||||||
<template #previewMask>
|
|
||||||
</template>
|
|
||||||
</AImage>
|
|
||||||
</CheckCard>
|
|
||||||
</template>
|
|
||||||
</Vue3JustifiedLayout>
|
|
||||||
</AImagePreviewGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty
|
|
||||||
:image="empty"
|
|
||||||
:image-style="{
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
还没检测到任何动图,快去上传吧!
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
|
||||||
</ASpin>
|
|
||||||
</ATabPane>
|
</ATabPane>
|
||||||
<ATabPane key="screenshot" :tab="imageStore.homeTabMap['screenshot']">
|
<ATabPane key="screenshot" :tab="imageStore.homeTabMap['screenshot']">
|
||||||
<ASpin size="large" :spinning="loading" :delay="500">
|
<ImageWaterfallList :image-list="imageList"/>
|
||||||
<div style="width:100%;height:100%;" v-if="images">
|
|
||||||
<div v-for="(itemList, index) in images" :key="index">
|
|
||||||
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
|
|
||||||
<AImagePreviewGroup>
|
|
||||||
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options"
|
|
||||||
style="line-height: 0 !important;">
|
|
||||||
<template #default="{ item }">
|
|
||||||
<CheckCard :key="index"
|
|
||||||
class="photo-item"
|
|
||||||
margin="0"
|
|
||||||
border-radius="0"
|
|
||||||
v-model="imageStore.selected"
|
|
||||||
:showHoverCircle="true"
|
|
||||||
:iconSize="20"
|
|
||||||
:showSelectedEffect="true"
|
|
||||||
:value="item.id">
|
|
||||||
<AImage :src="item.thumbnail"
|
|
||||||
:alt="item.file_name"
|
|
||||||
:key="index"
|
|
||||||
:height="200"
|
|
||||||
:preview="{
|
|
||||||
src: item.url,
|
|
||||||
}"
|
|
||||||
loading="lazy">
|
|
||||||
<template #previewMask>
|
|
||||||
</template>
|
|
||||||
</AImage>
|
|
||||||
</CheckCard>
|
|
||||||
</template>
|
|
||||||
</Vue3JustifiedLayout>
|
|
||||||
</AImagePreviewGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty
|
|
||||||
:image="empty"
|
|
||||||
:image-style="{
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
还没检测到任何屏幕截图,快去上传吧!
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
|
||||||
</ASpin>
|
|
||||||
</ATabPane>
|
</ATabPane>
|
||||||
</ATabs>
|
</ATabs>
|
||||||
</div>
|
</div>
|
||||||
@@ -257,40 +63,34 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import ImageUpload from "@/components/ImageUpload/ImageUpload.vue";
|
||||||
import Vue3JustifiedLayout from "vue3-justified-layout";
|
|
||||||
import 'vue3-justified-layout/dist/style.css';
|
|
||||||
import ImageUpload from "@/views/Photograph/ImageUpload/ImageUpload.vue";
|
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import {createAlbumApi, queryAllImagesApi} from "@/api/storage";
|
import {createAlbumApi, queryAllImagesApi} from "@/api/storage";
|
||||||
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
|
|
||||||
import empty from "@/assets/svgs/empty.svg";
|
|
||||||
import {message} from "ant-design-vue";
|
import {message} from "ant-design-vue";
|
||||||
|
import ImageWaterfallList from "@/components/ImageWaterfallList/ImageWaterfallList.vue";
|
||||||
|
|
||||||
const imageStore = useStore().image;
|
const imageStore = useStore().image;
|
||||||
|
|
||||||
|
|
||||||
const upload = useStore().upload;
|
const upload = useStore().upload;
|
||||||
|
|
||||||
const options = reactive({
|
|
||||||
targetRowHeight: 200 // 高度
|
|
||||||
});
|
|
||||||
|
|
||||||
const images = ref<any[]>([]);
|
const imageList = ref<any[]>([]);
|
||||||
const loading = ref<boolean>(false);
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有图片
|
* 获取所有图片
|
||||||
*/
|
*/
|
||||||
async function getAllImages(type: string) {
|
async function getAllImages(type: string) {
|
||||||
images.value = [];
|
imageList.value = [];
|
||||||
loading.value = true;
|
imageStore.imageListLoading = true;
|
||||||
const res: any = await queryAllImagesApi(type, imageStore.switchValue, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
const res: any = await queryAllImagesApi(type, imageStore.switchValue, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
images.value = res.data.records;
|
imageList.value = res.data.records;
|
||||||
}
|
}
|
||||||
loading.value = false;
|
imageStore.imageListLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTabChange(activeKey: string) {
|
async function handleTabChange(activeKey: string) {
|
||||||
@@ -319,6 +119,10 @@ async function createAlbumSubmit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleSwitchChange(_checked: boolean) {
|
||||||
|
getAllImages(imageStore.homeTabActiveKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getAllImages(imageStore.homeTabActiveKey);
|
getAllImages(imageStore.homeTabActiveKey);
|
||||||
@@ -362,7 +166,4 @@ onMounted(() => {
|
|||||||
box-shadow: 0 0 10px 0 rgba(77, 167, 255, 0.89);
|
box-shadow: 0 0 10px 0 rgba(77, 167, 255, 0.89);
|
||||||
}
|
}
|
||||||
|
|
||||||
.spin-wrapper {
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@@ -29,52 +29,10 @@
|
|||||||
</template>
|
</template>
|
||||||
</APopover>
|
</APopover>
|
||||||
</div>
|
</div>
|
||||||
<image-toolbar :selected="imageStore.selected" :image-list="images"/>
|
<image-toolbar :selected="imageStore.selected" :image-list="imageList"/>
|
||||||
<div class="photo-list">
|
<div class="photo-list">
|
||||||
<div style="width:100%;height:100%;" v-if="images.length !== 0">
|
<div class="photo-list-container">
|
||||||
<div v-for="(itemList, index) in images" :key="index">
|
<ImageWaterfallList :image-list="imageList"/>
|
||||||
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
|
|
||||||
<AImagePreviewGroup>
|
|
||||||
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options">
|
|
||||||
<template #default="{ item }">
|
|
||||||
<CheckCard :key="index"
|
|
||||||
class="photo-item"
|
|
||||||
margin="0"
|
|
||||||
border-radius="0"
|
|
||||||
v-model="imageStore.selected"
|
|
||||||
:showHoverCircle="true"
|
|
||||||
:iconSize="20"
|
|
||||||
:showSelectedEffect="true"
|
|
||||||
:value="item.id">
|
|
||||||
<AImage :src="item.thumbnail"
|
|
||||||
:alt="item.file_name"
|
|
||||||
:key="index"
|
|
||||||
style="height: 200px"
|
|
||||||
:preview="{
|
|
||||||
src: item.url,
|
|
||||||
}"
|
|
||||||
loading="lazy">
|
|
||||||
<template #previewMask>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</AImage>
|
|
||||||
</CheckCard>
|
|
||||||
</template>
|
|
||||||
</Vue3JustifiedLayout>
|
|
||||||
</AImagePreviewGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty :image="empty" :image-style="{
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
}">
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
暂无照片,快去上传吧
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ImageUpload/>
|
<ImageUpload/>
|
||||||
@@ -82,28 +40,27 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import Vue3JustifiedLayout from "vue3-justified-layout";
|
|
||||||
import 'vue3-justified-layout/dist/style.css';
|
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import ImageUpload from "@/views/Photograph/ImageUpload/ImageUpload.vue";
|
import ImageUpload from "@/components/ImageUpload/ImageUpload.vue";
|
||||||
import {createAlbumApi, queryRecentImagesApi} from "@/api/storage";
|
import {createAlbumApi, queryRecentImagesApi} from "@/api/storage";
|
||||||
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
|
import ImageToolbar from "@/components/ImageToolbar/ImageToolbar.vue";
|
||||||
import empty from "@/assets/svgs/empty.svg";
|
|
||||||
import {message} from "ant-design-vue";
|
import {message} from "ant-design-vue";
|
||||||
import router from "@/router/router.ts";
|
import router from "@/router/router.ts";
|
||||||
|
import ImageWaterfallList from "@/components/ImageWaterfallList/ImageWaterfallList.vue";
|
||||||
|
|
||||||
const imageStore = useStore().image;
|
const imageStore = useStore().image;
|
||||||
const upload = useStore().upload;
|
const upload = useStore().upload;
|
||||||
const images = ref<any[]>([]);
|
const imageList = ref<any[]>([]);
|
||||||
const options = reactive({
|
|
||||||
targetRowHeight: 200 // 高度
|
|
||||||
});
|
|
||||||
|
|
||||||
const getRecentImages = async () => {
|
const getRecentImages = async () => {
|
||||||
|
imageStore.imageListLoading = true;
|
||||||
const res: any = await queryRecentImagesApi(upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
const res: any = await queryRecentImagesApi(upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
images.value = res.data.records;
|
imageList.value = res.data.records;
|
||||||
}
|
}
|
||||||
|
imageStore.imageListLoading = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const albumNameValue = ref<string>("未命名相册");
|
const albumNameValue = ref<string>("未命名相册");
|
||||||
@@ -154,12 +111,21 @@ onMounted(() => {
|
|||||||
|
|
||||||
.photo-list {
|
.photo-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - 65px);
|
height: calc(100% - 65px);
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
.photo-list-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -5,74 +5,28 @@
|
|||||||
<span class="recycling-bin-desc">保存最近10天从云端删除的内容</span>
|
<span class="recycling-bin-desc">保存最近10天从云端删除的内容</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="photo-list">
|
<div class="photo-list">
|
||||||
<div style="width:100%;height:100%;" v-if="images.length !== 0">
|
<ImageWaterfallList :image-list="imageList"/>
|
||||||
<div v-for="(itemList, index) in images" :key="index">
|
|
||||||
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
|
|
||||||
<AImagePreviewGroup>
|
|
||||||
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options">
|
|
||||||
<template #default="{ item }">
|
|
||||||
<CheckCard :key="index"
|
|
||||||
class="photo-item"
|
|
||||||
margin="0"
|
|
||||||
border-radius="0"
|
|
||||||
v-model="selected"
|
|
||||||
:showHoverCircle="true"
|
|
||||||
:iconSize="20"
|
|
||||||
:showSelectedEffect="true"
|
|
||||||
:value="item.id">
|
|
||||||
<AImage :src="item.thumbnail"
|
|
||||||
:alt="item.file_name"
|
|
||||||
:key="index"
|
|
||||||
style="height: 200px"
|
|
||||||
:preview="{
|
|
||||||
src: item.url,
|
|
||||||
}"
|
|
||||||
loading="lazy">
|
|
||||||
<template #previewMask>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</AImage>
|
|
||||||
</CheckCard>
|
|
||||||
</template>
|
|
||||||
</Vue3JustifiedLayout>
|
|
||||||
</AImagePreviewGroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="empty-content">
|
|
||||||
<AEmpty :image="empty" :image-style="{ width: '100%', height: '100%' }">
|
|
||||||
<template #description>
|
|
||||||
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
|
|
||||||
暂无照片,快去上传吧
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</AEmpty>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Vue3JustifiedLayout from "vue3-justified-layout";
|
|
||||||
import 'vue3-justified-layout/dist/style.css';
|
|
||||||
import {getDeletedRecordApi} from "@/api/storage";
|
import {getDeletedRecordApi} from "@/api/storage";
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import empty from "@/assets/svgs/empty.svg";
|
import ImageWaterfallList from "@/components/ImageWaterfallList/ImageWaterfallList.vue";
|
||||||
|
|
||||||
|
const imageList = ref<any[]>([]);
|
||||||
const selected = ref<(string | number)[]>([]);
|
|
||||||
const images = ref<any[]>([]);
|
|
||||||
const options = reactive({
|
|
||||||
targetRowHeight: 200 // 高度
|
|
||||||
});
|
|
||||||
const upload = useStore().upload;
|
const upload = useStore().upload;
|
||||||
|
const imageStore = useStore().image;
|
||||||
/**
|
/**
|
||||||
* 查询回收站
|
* 查询回收站
|
||||||
*/
|
*/
|
||||||
async function queryRecyclingBin() {
|
async function queryRecyclingBin() {
|
||||||
|
imageStore.imageListLoading = true;
|
||||||
const res: any = await getDeletedRecordApi(upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
const res: any = await getDeletedRecordApi(upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
images.value = res.data.records;
|
imageList.value = res.data.records;
|
||||||
}
|
}
|
||||||
|
imageStore.imageListLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@@ -10,42 +10,11 @@
|
|||||||
<div class="share-content-header">
|
<div class="share-content-header">
|
||||||
<AButton type="link" size="large" class="share-content-header-button">图片列表</AButton>
|
<AButton type="link" size="large" class="share-content-header-button">图片列表</AButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="share-content-verify" v-if="images && images.length <= 0">
|
<div class="share-content-verify" v-if="imageList && imageList.length <= 0">
|
||||||
<AInputPassword size="large" placeholder="请输入访问密码" style="width: 20%" @pressEnter="getShareImages"/>
|
<AInputPassword size="large" placeholder="请输入访问密码" style="width: 20%" @pressEnter="getShareImages"/>
|
||||||
<p style="font-size: 12px;color: #999;">回车后可查看图片列表</p>
|
<p style="font-size: 12px;color: #999;">回车后可查看图片列表</p>
|
||||||
</div>
|
</div>
|
||||||
<ASpin :spinning="loading" size="large">
|
<ImageWaterfallList :image-list="imageList"/>
|
||||||
<div v-if="images && images.length !== 0">
|
|
||||||
<AImagePreviewGroup>
|
|
||||||
<Vue3JustifiedLayout v-model:list="images" :options="options">
|
|
||||||
<template #default="{ item }">
|
|
||||||
<CheckCard
|
|
||||||
class="photo-item"
|
|
||||||
margin="0"
|
|
||||||
border-radius="0"
|
|
||||||
v-model="selected"
|
|
||||||
:showHoverCircle="true"
|
|
||||||
:iconSize="20"
|
|
||||||
:showSelectedEffect="true"
|
|
||||||
:value="item.id">
|
|
||||||
<AImage :src="item.thumbnail"
|
|
||||||
:alt="item.file_name"
|
|
||||||
style="height: 200px"
|
|
||||||
:preview="{
|
|
||||||
src: item.url,
|
|
||||||
}"
|
|
||||||
loading="lazy">
|
|
||||||
<template #previewMask>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</AImage>
|
|
||||||
</CheckCard>
|
|
||||||
</template>
|
|
||||||
</Vue3JustifiedLayout>
|
|
||||||
</AImagePreviewGroup>
|
|
||||||
</div>
|
|
||||||
</ASpin>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,31 +23,30 @@
|
|||||||
|
|
||||||
import Header from "@/layout/default/Header/Header.vue";
|
import Header from "@/layout/default/Header/Header.vue";
|
||||||
import ShareSidebar from "@/views/Share/ShareViewList/ShareSidebar.vue";
|
import ShareSidebar from "@/views/Share/ShareViewList/ShareSidebar.vue";
|
||||||
import Vue3JustifiedLayout from "vue3-justified-layout";
|
|
||||||
import 'vue3-justified-layout/dist/style.css';
|
|
||||||
import {queryShareImageApi} from "@/api/share";
|
|
||||||
|
|
||||||
const selected = ref<(string | number)[]>([]);
|
import {queryShareImageApi} from "@/api/share";
|
||||||
const images = ref<any[]>([]);
|
import ImageWaterfallList from "@/components/ImageWaterfallList/ImageWaterfallList.vue";
|
||||||
const options = reactive({
|
import useStore from "@/store";
|
||||||
targetRowHeight: 200 // 高度
|
|
||||||
});
|
const imageList = ref<any[]>([]);
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const loading = ref<boolean>(false);
|
|
||||||
|
const imageStore = useStore().image;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取分享图片列表
|
* 获取分享图片列表
|
||||||
* @param e
|
* @param e
|
||||||
*/
|
*/
|
||||||
async function getShareImages(e) {
|
async function getShareImages(e) {
|
||||||
loading.value = true;
|
imageStore.imageListLoading = true;
|
||||||
const invite_code = route.params.id;
|
const invite_code = route.params.id;
|
||||||
const code = Array.isArray(invite_code) ? invite_code[0] : invite_code;
|
const code = Array.isArray(invite_code) ? invite_code[0] : invite_code;
|
||||||
const res: any = await queryShareImageApi(code, e.target.value);
|
const res: any = await queryShareImageApi(code, e.target.value);
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
images.value = res.data.records;
|
imageList.value = res.data.records;
|
||||||
}
|
}
|
||||||
loading.value = false;
|
imageStore.imageListLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
11
src/views/User/AccountSetting/AccountSetting.vue
Normal file
11
src/views/User/AccountSetting/AccountSetting.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
179
src/views/User/PersonalCenter/PersonalCenter.vue
Normal file
179
src/views/User/PersonalCenter/PersonalCenter.vue
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
<template>
|
||||||
|
<div class="personal-center">
|
||||||
|
<div class="personal-center-header">
|
||||||
|
<Header style="background: transparent;box-shadow: none;backdrop-filter: none;"/>
|
||||||
|
<div class="personal-center-header-info">
|
||||||
|
<div class="personal-center-header-info-container">
|
||||||
|
<div class="personal-center-header-info-container-avatar">
|
||||||
|
<AAvatar :size="80" :text-size="1.5" :round="true" :src="userStore.user.avatar"/>
|
||||||
|
</div>
|
||||||
|
<div class="personal-center-header-info-container-description">
|
||||||
|
<div class="personal-center-header-info-container-description-name">
|
||||||
|
<span>{{ userStore.user.nickname }}</span>
|
||||||
|
<img src="/level_icon/icon/lv1.png" class="personal-level" alt="level">
|
||||||
|
</div>
|
||||||
|
<div class="personal-center-header-info-container-description-introduce">
|
||||||
|
<span>描述信息</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="personal-center-content">
|
||||||
|
<AMenu :selectedKeys="[menuStore.userCenterMenu]" mode="horizontal" :selectable="true" :multiple="false"
|
||||||
|
@select="handleClick">
|
||||||
|
<AMenuItem key="home" :style="menuCSSStyle" title="主页">
|
||||||
|
<template #icon>
|
||||||
|
<AAvatar shape="square" size="small" :src="home"/>
|
||||||
|
</template>
|
||||||
|
<span class="ant-menu-item-title">主页</span>
|
||||||
|
</AMenuItem>
|
||||||
|
<AMenuItem key="dynamic" :style="menuCSSStyle" title="动态">
|
||||||
|
<template #icon>
|
||||||
|
<AAvatar shape="square" size="small" :src="dynamic"/>
|
||||||
|
</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"/>
|
||||||
|
</template>
|
||||||
|
<span class="ant-menu-item-title">设置</span>
|
||||||
|
</AMenuItem>
|
||||||
|
</AMenu>
|
||||||
|
<div class="personal-center-content-container">
|
||||||
|
<router-view/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
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;
|
||||||
|
const menuStore = useStore().menu;
|
||||||
|
const router = useRouter();
|
||||||
|
const menuCSSStyle: any = reactive({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleClick({key}) {
|
||||||
|
menuStore.userCenterMenu = key;
|
||||||
|
router.push(`/main/user/center/${key}`);
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.personal-center {
|
||||||
|
background-color: #eaeef6;
|
||||||
|
|
||||||
|
.personal-center-header {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.2)), url("@/assets/images/bg.webp");
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.personal-center-header-info {
|
||||||
|
width: 100%;
|
||||||
|
height: 130px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.personal-center-header-info-container {
|
||||||
|
width: 95%;
|
||||||
|
height: 110px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.personal-center-header-info-container-avatar {
|
||||||
|
height: 80px;
|
||||||
|
width: 80px;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-center-header-info-container-description {
|
||||||
|
height: 70px;
|
||||||
|
width: 80%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.personal-center-header-info-container-description-name {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0px 1px 2px rgba(0, 0, 0, .4);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.personal-level {
|
||||||
|
width: auto;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-center-header-info-container-description-introduce {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(255, 255, 255, .2);
|
||||||
|
box-shadow: 0 0 0 1px rgba(255, 255, 255, .5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-center-content {
|
||||||
|
|
||||||
|
.ant-menu-item-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.personal-center-content-container {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - 250px);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
6666
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
Reference in New Issue
Block a user