Explorar el Código

feat: 添加插件检测

小莫唐尼 hace 7 meses
padre
commit
6a05f664ba

+ 283 - 234
api/v2/all.api.js

@@ -1,246 +1,295 @@
 /**
  * 所有的接口
  */
-import {getPersonalToken} from '@/utils/token.js'
+import {
+	getPersonalToken
+} from '@/utils/token.js'
 import HttpHandler from '@/common/http/request.js'
 import qs from 'qs'
 
-import {getAppConfigs} from '@/config/index.js'
+import {
+	getAppConfigs
+} from '@/config/index.js'
 
 export default {
-    /**
-     * 获取文章列表
-     * @param {Object} params 参数
-     */
-    getPostList: (params) => {
-        return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/posts`, params)
-    },
-
-    /**
-     * 根据名称获取文章
-     * @param {String} name 分类名称
-     */
-    getPostByName: (name) => {
-        return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/posts/${name}`, {}, {
-            header: {
-                'Wechat-Session-Id': uni.getStorageSync('openid'),
-            }
-        })
-    },
-
-    /**
-     * 搜索文章
-     * @param {Object} params 数据
-     */
-    getPostListByKeyword: (params) => {
-        // return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/indices/post`, params)
-        return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/indices/-/search`, params)
-    },
-
-    /**
-     * 查询分类列表
-     * @param {Object} params  查询参数
-     */
-    getCategoryList: (params) => {
-        const param = qs.stringify(params, {
-            allowDots: true,
-            encodeValuesOnly: true,
-            skipNulls: true,
-            encode: false,
-            arrayFormat: 'repeat'
-        })
-        return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/categories?${param}`, {})
-    },
-    /**
-     * 查询分类下的文章
-     * @param {String} name  分类名称
-     * @param {Object} params  查询参数
-     */
-    getCategoryPostList: (name, params) => {
-        return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/categories/${name}/posts`, params)
-    },
-
-
-    /**
-     * 获取评论列表接口(列表数据)
-     * @param {Object} params  查询参数
-     */
-    getPostCommentList: (params) => {
-        return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/comments`, params)
-    },
-
-    /**
-     * 获取回复列表
-     * @param {String} commentName  名称
-     * @param {Object} params  查询参数
-     */
-    getPostCommentReplyList: (commentName, params) => {
-        return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/comments/${commentName}/reply`, params)
-    },
-
-    // 提交评论
-    addPostComment: (data) => {
-        return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/comments`, data)
-    },
-    // 提交回复
-    addPostCommentReply: (commentName, data) => {
-        return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/comments/${commentName}/reply`, data)
-    },
-
-    /**
-     * 获取标签列表
-     * @param {Object} params  查询参数
-     */
-    getTagList: (params) => {
-        return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/tags`, params)
-    },
-
-    /**
-     * 根据标签获取文章列表
-     * @param {String} tagName  参数
-     * @param {Object} params  查询参数
-     */
-    getPostByTagName: (tagName, params) => {
-        return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/tags/${tagName}/posts`, params)
-    },
-
-    /**
-     * 获取瞬间列表
-     */
-    getMomentList: (params) => {
-        return HttpHandler.Get(`/apis/moment.halo.run/v1alpha1/moments`, params, {
-            custom: {
-                personalToken: getPersonalToken()
-            }
-        })
-    },
-	
+	/**
+	 * 获取文章列表
+	 * @param {Object} params 参数
+	 */
+	getPostList: (params) => {
+		return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/posts`, params)
+	},
+
+	/**
+	 * 根据名称获取文章
+	 * @param {String} name 分类名称
+	 */
+	getPostByName: (name) => {
+		return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/posts/${name}`, {}, {
+			header: {
+				'Wechat-Session-Id': uni.getStorageSync('openid'),
+			}
+		})
+	},
+
+	/**
+	 * 搜索文章
+	 * @param {Object} params 数据
+	 */
+	getPostListByKeyword: (params) => {
+		// return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/indices/post`, params)
+		return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/indices/-/search`, params)
+	},
+
+	/**
+	 * 查询分类列表
+	 * @param {Object} params  查询参数
+	 */
+	getCategoryList: (params) => {
+		const param = qs.stringify(params, {
+			allowDots: true,
+			encodeValuesOnly: true,
+			skipNulls: true,
+			encode: false,
+			arrayFormat: 'repeat'
+		})
+		return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/categories?${param}`, {})
+	},
+	/**
+	 * 查询分类下的文章
+	 * @param {String} name  分类名称
+	 * @param {Object} params  查询参数
+	 */
+	getCategoryPostList: (name, params) => {
+		return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/categories/${name}/posts`, params)
+	},
+
+
+	/**
+	 * 获取评论列表接口(列表数据)
+	 * @param {Object} params  查询参数
+	 */
+	getPostCommentList: (params) => {
+		return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/comments`, params)
+	},
+
+	/**
+	 * 获取回复列表
+	 * @param {String} commentName  名称
+	 * @param {Object} params  查询参数
+	 */
+	getPostCommentReplyList: (commentName, params) => {
+		return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/comments/${commentName}/reply`, params)
+	},
+
+	// 提交评论
+	addPostComment: (data) => {
+		return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/comments`, data)
+	},
+	// 提交回复
+	addPostCommentReply: (commentName, data) => {
+		return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/comments/${commentName}/reply`, data)
+	},
+
+	/**
+	 * 获取标签列表
+	 * @param {Object} params  查询参数
+	 */
+	getTagList: (params) => {
+		return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/tags`, params)
+	},
+
+	/**
+	 * 根据标签获取文章列表
+	 * @param {String} tagName  参数
+	 * @param {Object} params  查询参数
+	 */
+	getPostByTagName: (tagName, params) => {
+		return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/tags/${tagName}/posts`, params)
+	},
+
+	/**
+	 * 获取瞬间列表
+	 */
+	getMomentList: (params) => {
+		return HttpHandler.Get(`/apis/moment.halo.run/v1alpha1/moments`, params, {
+			custom: {
+				personalToken: getPersonalToken()
+			}
+		})
+	},
+
 	/**
 	 * 获取瞬间详情
-     * @param {String} name 瞬间id
+	 * @param {String} name 瞬间id
 	 */
 	getMomentByName: (name) => {
-	    return HttpHandler.Get(`/apis/moment.halo.run/v1alpha1/moments/${name}`, {}, {
-	        custom: {
-	            personalToken: getPersonalToken()
-	        }
-	    })
-	},
-
-    /**
-     * 查询站点统计信息
-     */
-    getBlogStatistics: () => {
-        return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/stats/-`, {})
-    },
-
-
-    /**
-     * 获取相册分组
-     */
-    getPhotoGroupList: (params) => {
-        return HttpHandler.Get(`/apis/core.halo.run/v1alpha1/photogroups`, params, {
-            custom: {
-                personalToken: getPersonalToken()
-            }
-        })
-    },
-
-
-    /**
-     * 根据分组获取相册
-     */
-    getPhotoListByGroupName: (params) => {
-        return HttpHandler.Get(`/apis/console.api.photo.halo.run/v1alpha1/photos`, params, {
-            custom: {
-                personalToken: getPersonalToken()
-            }
-        })
-    },
-
-    /**
-     * 获取友链分组
-     */
-    getFriendLinkGroupList: (params) => {
-        return HttpHandler.Get(`/apis/core.halo.run/v1alpha1/linkgroups`, params, {
-            custom: {
-                personalToken: getPersonalToken()
-            }
-        })
-    },
-
-    /**
-     * 获取友链
-     */
-    getFriendLinkList: (params) => {
-        return HttpHandler.Get(`/apis/api.plugin.halo.run/v1alpha1/plugins/PluginLinks/links`, params)
-    },
-
-    /**
-     * 限制阅读校验
-     * @param restrictType
-     * @param code
-     * @param keyId
-     * @returns {HttpPromise<any>}
-     */
-    requestRestrictReadCheck: (restrictType, code, keyId) => {
-        const params = {
-            code: code,
-            templateType: 'post',
-            restrictType: restrictType,
-            keyId: keyId
-        }
-        return HttpHandler.Post(`/apis/tools.muyin.site/v1alpha1/restrict-read/check`, params, {
-            header: {
-                'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
-                'Wechat-Session-Id': uni.getStorageSync('openid'),
-            }
-        })
-    },
-
-    /**
-     * 获取文章验证码
-     */
-    createVerificationCode: () => {
-        return HttpHandler.Get(`/apis/tools.muyin.site/v1alpha1/restrict-read/create`, null, {
-            header: {
-                'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
-                'Wechat-Session-Id': uni.getStorageSync('openid'),
-            }
-        })
-    },
-
-    /**
-     * 提交友情链接
-     */
-    submitLink(form) {
-        return HttpHandler.Post(`/apis/linksSubmit.muyin.site/v1alpha1/submit`, form, {
-            header: {
-                'Authorization': getAppConfigs().pluginConfig.linksSubmitPlugin?.Authorization,
-                'Wechat-Session-Id': uni.getStorageSync('openid'),
-            }
-        })
-    },
-    /**
-     * 获取二维码信息
-     */
-    getQRCodeInfo: (key) => {
-        return HttpHandler.Get(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getQRCodeInfo/${key}`,
-            null, {})
-    },
-    /**
-     * 获取二维码图片
-     */
-    getQRCodeImg: (postId) => {
-        return HttpHandler.Get(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getQRCodeImg/${postId}`,
-            null, {})
-    },
-    /**
-     * 点赞
-     * @param {*} data ={group, plural, name}
-     */
-    submitUpvote(data) {
-        return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/trackers/upvote`, data, {})
-    }
-
-}
+		return HttpHandler.Get(`/apis/moment.halo.run/v1alpha1/moments/${name}`, {}, {
+			custom: {
+				personalToken: getPersonalToken()
+			}
+		})
+	},
+
+	/**
+	 * 查询站点统计信息
+	 */
+	getBlogStatistics: () => {
+		return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/stats/-`, {})
+	},
+
+
+	/**
+	 * 获取相册分组
+	 */
+	getPhotoGroupList: (params) => {
+		return HttpHandler.Get(`/apis/core.halo.run/v1alpha1/photogroups`, params, {
+			custom: {
+				personalToken: getPersonalToken()
+			}
+		})
+	},
+
+
+	/**
+	 * 根据分组获取相册
+	 */
+	getPhotoListByGroupName: (params) => {
+		return HttpHandler.Get(`/apis/console.api.photo.halo.run/v1alpha1/photos`, params, {
+			custom: {
+				personalToken: getPersonalToken()
+			}
+		})
+	},
+
+	/**
+	 * 获取友链分组
+	 */
+	getFriendLinkGroupList: (params) => {
+		return HttpHandler.Get(`/apis/core.halo.run/v1alpha1/linkgroups`, params, {
+			custom: {
+				personalToken: getPersonalToken()
+			}
+		})
+	},
+
+	/**
+	 * 获取友链
+	 */
+	getFriendLinkList: (params) => {
+		return HttpHandler.Get(`/apis/api.plugin.halo.run/v1alpha1/plugins/PluginLinks/links`, params)
+	},
+
+	/**
+	 * 限制阅读校验
+	 * @param restrictType
+	 * @param code
+	 * @param keyId
+	 * @returns {HttpPromise<any>}
+	 */
+	requestRestrictReadCheck: (restrictType, code, keyId) => {
+		const params = {
+			code: code,
+			templateType: 'post',
+			restrictType: restrictType,
+			keyId: keyId
+		}
+		return HttpHandler.Post(`/apis/tools.muyin.site/v1alpha1/restrict-read/check`, params, {
+			header: {
+				'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
+				'Wechat-Session-Id': uni.getStorageSync('openid'),
+			}
+		})
+	},
+
+	/**
+	 * 获取文章验证码
+	 */
+	createVerificationCode: () => {
+		return HttpHandler.Get(`/apis/tools.muyin.site/v1alpha1/restrict-read/create`, null, {
+			header: {
+				'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
+				'Wechat-Session-Id': uni.getStorageSync('openid'),
+			}
+		})
+	},
+
+	/**
+	 * 提交友情链接
+	 */
+	submitLink(form) {
+		return HttpHandler.Post(`/apis/linksSubmit.muyin.site/v1alpha1/submit`, form, {
+			header: {
+				'Authorization': getAppConfigs().pluginConfig.linksSubmitPlugin?.Authorization,
+				'Wechat-Session-Id': uni.getStorageSync('openid'),
+			}
+		})
+	},
+	/**
+	 * 获取二维码信息
+	 */
+	getQRCodeInfo: (key) => {
+		return HttpHandler.Get(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getQRCodeInfo/${key}`,
+			null, {})
+	},
+	/**
+	 * 获取二维码图片
+	 */
+	getQRCodeImg: (postId) => {
+		return HttpHandler.Get(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getQRCodeImg/${postId}`,
+			null, {})
+	},
+	/**
+	 * 点赞
+	 * @param {*} data ={group, plural, name}
+	 */
+	submitUpvote(data) {
+		return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/trackers/upvote`, data, {})
+	},
+
+	//----------- 投票 -----------------
+	/**
+	 * 获取投票列表
+	 */
+	getVoteList: (params) => {
+		return HttpHandler.Get(`/apis/console.api.vote.kunkunyu.com/v1alpha1/votes`, params, {
+			custom: {
+				personalToken: getPersonalToken()
+			}
+		})
+	},
+	/**
+	 * 获取投票详情
+	 * @param {String} name id 
+	 */
+	getVoteDetail: (name) => {
+		return HttpHandler.Get(`/apis/api.vote.kunkunyu.com/v1alpha1/votes/${name}/detail`, {})
+	},
+	/**
+	 * 获取投票用户列表
+	 * @param {String} name id 
+	 */
+	getVoteUserList: (name) => {
+		return HttpHandler.Get(`/apis/api.vote.kunkunyu.com/v1alpha1/votes/${name}/user-list`, {})
+	},
+	/**
+	 * 提交投票
+	 * @param {String} name id
+	 * @param {Object} { voteData:["选项ID"] } 提交的数据
+	 * @param {Boolean} canAnonymously 是否匿名 默认匿名
+	 */
+	submitVote: (name, data, canAnonymously = true) => {
+		return HttpHandler.Post(`/apis/api.vote.kunkunyu.com/v1alpha1/votes/${name}/submit`, data, {
+			custom: {
+				personalToken: canAnonymously ? undefined : getPersonalToken()
+			}
+		})
+	},
+	/**
+	 * 检查是否安装启用插件
+	 * @param {String} name 插件id 
+	 */
+	checkPluginAvailable: (name) => {
+		return HttpHandler.Get(`/apis/api.plugin.halo.run/v1alpha1/plugins/${name}/available`, {})
+	},
+}

+ 47 - 0
common/mixins/pluginAvailable.js

@@ -0,0 +1,47 @@
+/**
+ *  功能:插件检查
+ */
+import {
+	NeedPluginIds,
+	NeedPlugins,
+	checkNeedPluginAvailable
+} from "@/utils/plugin.js"
+import PluginUnavailable from '@/components/plugin-unavailable/plugin-unavailable.vue'
+
+
+const HaloPluginAvailableMixin = {
+	components: {
+		PluginUnavailable
+	},
+	data() {
+		return {
+			NeedPluginIds,
+			NeedPlugins,
+			uniHaloPluginAvailableError: "",
+			uniHaloPluginAvailable: true,
+			uniHaloPluginId: "", // 当前需要的插件
+			uniHaloPluginInfo: "" // 当前插件信息
+		};
+	},
+	methods: {
+		/** 设置插件ID */
+		setPluginId(pluginId) {
+			this.uniHaloPluginId = pluginId
+			this.uniHaloPluginInfo = NeedPlugins.get(pluginId)
+		},
+		/** 检查插件状态 */
+		async checkPluginAvailable(pluginId) {
+			pluginId = pluginId ?? this.uniHaloPluginId
+			if (!pluginId) return false;
+			const available = await checkNeedPluginAvailable(pluginId)
+			this.uniHaloPluginAvailable = available
+			return available
+		},
+		/** 设置错误信息 */
+		setPluginError(text) {
+			this.uniHaloPluginAvailableError = text
+		}
+	},
+}
+
+export default HaloPluginAvailableMixin;

+ 167 - 0
common/styles/app.base.scss

@@ -172,3 +172,170 @@
 }
 
 /* 文本省略样式 结束 */
+
+
+// 定义尺寸变量(单位:rpx)
+$spacing-sizes: (
+        0: 0,
+        2: 4rpx,
+        4: 8rpx,
+        8: 16rpx,
+        12: 24rpx,
+        24: 48rpx,
+        48: 96rpx
+);
+
+// 内边距类
+@each $name, $size in $spacing-sizes {
+  // 全方向内边距:p-{size}
+  .p-#{$name} {
+    padding: $size !important;
+  }
+
+  // 水平方向内边距:px-{size}
+  .px-#{$name} {
+    padding-left: $size !important;
+    padding-right: $size !important;
+  }
+
+  // 垂直方向内边距:py-{size}
+  .py-#{$name} {
+    padding-top: $size !important;
+    padding-bottom: $size !important;
+  }
+
+  // 上内边距:pt-{size}
+  .pt-#{$name} {
+    padding-top: $size !important;
+  }
+
+  // 右内边距:pr-{size}
+  .pr-#{$name} {
+    padding-right: $size !important;
+  }
+
+  // 下内边距:pb-{size}
+  .pb-#{$name} {
+    padding-bottom: $size !important;
+  }
+
+  // 左内边距:pl-{size}
+  .pl-#{$name} {
+    padding-left: $size !important;
+  }
+}
+
+//外边距工具类
+@each $name, $size in $spacing-sizes {
+  // 全方向外边距:m-{size}
+  .m-#{$name} {
+    margin: $size !important;
+  }
+
+  // 水平方向外边距:mx-{size}
+  .mx-#{$name} {
+    margin-left: $size !important;
+    margin-right: $size !important;
+  }
+
+  // 垂直方向外边距:my-{size}
+  .my-#{$name} {
+    margin-top: $size !important;
+    margin-bottom: $size !important;
+  }
+
+  // 上外边距:mt-{size}
+  .mt-#{$name} {
+    margin-top: $size !important;
+  }
+
+  // 右外边距:mr-{size}
+  .mr-#{$name} {
+    margin-right: $size !important;
+  }
+
+  // 下外边距:mb-{size}
+  .mb-#{$name} {
+    margin-bottom: $size !important;
+  }
+
+  // 左外边距:ml-{size}
+  .ml-#{$name} {
+    margin-left: $size !important;
+  }
+}
+
+// gap 类
+@each $name, $size in $spacing-sizes {
+  // 全方向gap:gap-{size}
+  .gap-#{$name} {
+    gap: $size !important;
+  }
+
+  // 水平方向gap:gap-x-{size}
+  .gap-x-#{$name} {
+    column-gap: $size !important;
+  }
+
+  // 垂直方向gap:gap-y-{size}
+  .gap-y-#{$name} {
+    row-gap: $size !important;
+  }
+}
+
+
+.w-full {
+  width: 100%;
+}
+
+.h-full {
+  height: 100%;
+}
+
+.flex {
+  display: flex;
+}
+
+.flex-col {
+  flex-direction: column;
+}
+
+.flex-1 {
+  flex: 1;
+}
+
+.flex-shrink-0 {
+  flex-shrink: 0;
+}
+
+.items-center {
+  align-items: center;
+}
+
+.items-start {
+  align-items: flex-start;
+}
+
+.items-end {
+  align-items: flex-end;
+}
+
+.justify-between {
+  justify-content: space-between;
+}
+
+.justify-around {
+  justify-content: space-around;
+}
+
+.justify-end {
+  justify-content: flex-end;
+}
+
+.justify-start {
+  justify-content: flex-start;
+}
+
+.justify-center {
+  justify-content: center;
+}

+ 364 - 342
pages/tabbar/gallery/gallery.vue

@@ -1,366 +1,388 @@
 <template>
-    <view class="app-page">
-        <!-- 顶部切换 -->
-        <view class="e-fixed" v-if="category.list.length > 0">
-            <tm-tabs color="light-blue" :shadow="0" v-model="category.activeIndex" range-key="displayName" :list="category.list"
-                     align="left" @change="fnOnCategoryChange($event, false)"></tm-tabs>
-        </view>
-        <!-- 占位区域 -->
-        <view v-if="category.list.length > 0" style="width: 100vw;height: 90rpx;"></view>
-        <!-- 加载区域 -->
-        <view v-if="loading == 'loading'" class="loading-wrap">
-            <tm-skeleton model="card"></tm-skeleton>
-            <tm-skeleton model="card"></tm-skeleton>
-            <tm-skeleton model="card"></tm-skeleton>
-            <tm-skeleton model="card"></tm-skeleton>
-        </view>
-		<view v-else-if="loading == 'error'" class="flex flex-col flex-center" style="width:100%;height:60vh;">
-			<tm-empty icon="icon-wind-cry" label="阿偶,似乎获取数据失败了~">
-				<tm-button theme="light-blue" size="m" :shadow="0" @click="fnGetData(true)">刷新试试</tm-button>
-			</tm-empty>
-		</view>	
-        <!-- 内容区域 -->
-        <view v-else class="content">
-			<k-touch-listen class="touch-listen-content" @touchLeft="touchLeft" @touchRight="touchRight">
-				<view v-if="dataList.length === 0" class="content-empty">
-					<!-- 空布局 -->
-					<tm-empty icon="icon-shiliangzhinengduixiang-" label="博主还没有分享图片~"></tm-empty>
-				</view>
-				<block v-else>
-					<block v-if="galleryConfig.useWaterfall">
-						<!--瀑布流-->
-						<tm-flowLayout-custom ref="wafll" style="width: 100%;" @click="fnOnFlowClick"></tm-flowLayout-custom>
-					</block>
-					<!--   列表   -->
+	<view class="app-page">
+		<PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId"
+			:error-text="uniHaloPluginAvailableError" />
+		<template v-else>
+			<!-- 顶部切换 -->
+			<view class="e-fixed" v-if="category.list.length > 0">
+				<tm-tabs color="light-blue" :shadow="0" v-model="category.activeIndex" range-key="displayName"
+					:list="category.list" align="left" @change="fnOnCategoryChange($event, false)"></tm-tabs>
+			</view>
+			<!-- 占位区域 -->
+			<view v-if="category.list.length > 0" style="width: 100vw;height: 90rpx;"></view>
+			<!-- 加载区域 -->
+			<view v-if="loading == 'loading'" class="loading-wrap">
+				<tm-skeleton model="card"></tm-skeleton>
+				<tm-skeleton model="card"></tm-skeleton>
+				<tm-skeleton model="card"></tm-skeleton>
+				<tm-skeleton model="card"></tm-skeleton>
+			</view>
+			<view v-else-if="loading == 'error'" class="flex flex-col flex-center" style="width:100%;height:60vh;">
+				<tm-empty icon="icon-wind-cry" label="阿偶,似乎获取数据失败了~">
+					<tm-button theme="light-blue" size="m" :shadow="0" @click="fnGetData(true)">刷新试试</tm-button>
+				</tm-empty>
+			</view>
+			<!-- 内容区域 -->
+			<view v-else class="content">
+				<k-touch-listen class="touch-listen-content" @touchLeft="touchLeft" @touchRight="touchRight">
+					<view v-if="dataList.length === 0" class="content-empty">
+						<!-- 空布局 -->
+						<tm-empty icon="icon-shiliangzhinengduixiang-" label="博主还没有分享图片~"></tm-empty>
+					</view>
 					<block v-else>
-						<tm-translate v-for="(item, index) in dataList" :key="index"
-									  style="box-sizing: border-box;padding: 6rpx;width: 50%;height: 250rpx;"
-									  animation-name="fadeUp" :wait="calcAniWait(index)">
-							<view style="border-radius: 12rpx;overflow: hidden;width: 100%;height: 250rpx;">
-								<image style="width: 100%;height: 100%;" mode="aspectFill" :src="item.spec.url"
-									   @click="fnPreview(item)"/>
-							</view>
-						</tm-translate>
+						<block v-if="galleryConfig.useWaterfall">
+							<!--瀑布流-->
+							<tm-flowLayout-custom ref="wafll" style="width: 100%;"
+								@click="fnOnFlowClick"></tm-flowLayout-custom>
+						</block>
+						<!--   列表   -->
+						<block v-else>
+							<tm-translate v-for="(item, index) in dataList" :key="index"
+								style="box-sizing: border-box;padding: 6rpx;width: 50%;height: 250rpx;"
+								animation-name="fadeUp" :wait="calcAniWait(index)">
+								<view style="border-radius: 12rpx;overflow: hidden;width: 100%;height: 250rpx;">
+									<image style="width: 100%;height: 100%;" mode="aspectFill" :src="item.spec.url"
+										@click="fnPreview(item)" />
+								</view>
+							</tm-translate>
+						</block>
+
+						<view class="load-text">{{ loadMoreText }}</view>
 					</block>
+				</k-touch-listen>
+			</view>
 
-					<view class="load-text">{{ loadMoreText }}</view>
-				</block>
-			</k-touch-listen>
-        </view>
-    
-		<view v-if="!calcAuditModeEnabled" class="flot-buttons">
-			<tm-button v-if="loading == 'error'" @click="fnGetCategory" size="m" :fab="true" theme="light-blue"
-			           icon="icon-sync-alt"></tm-button>
-		    <tm-button @click="fnToTopPage" size="m" :fab="true" theme="light-blue"
-		               icon="icon-angle-up"></tm-button>
-		</view>
+			<view v-if="!calcAuditModeEnabled" class="flot-buttons">
+				<tm-button v-if="loading == 'error'" @click="fnGetCategory" size="m" :fab="true" theme="light-blue"
+					icon="icon-sync-alt"></tm-button>
+				<tm-button @click="fnToTopPage" size="m" :fab="true" theme="light-blue"
+					icon="icon-angle-up"></tm-button>
+			</view>
+		</template>
 	</view>
 </template>
 
 <script>
-import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
-import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
-import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
-import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
-import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
-import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
-import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue';
-import tmFlowLayoutCustom from '@/tm-vuetify/components/tm-flowLayout-custom/tm-flowLayout-custom.vue';
-import tmTabs from '@/tm-vuetify/components/tm-tabs/tm-tabs.vue';
-import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
+	import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
+	import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
+	import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
+	import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
+	import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
+	import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
+	import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue';
+	import tmFlowLayoutCustom from '@/tm-vuetify/components/tm-flowLayout-custom/tm-flowLayout-custom.vue';
+	import tmTabs from '@/tm-vuetify/components/tm-tabs/tm-tabs.vue';
+	import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
+
+	import pluginAvailable from "@/common/mixins/pluginAvailable.js"
 
-export default {
-    options: {
-        multipleSlots: true
-    },
-    components: {
-        tmSkeleton,
-        tmTranslate,
-        tmFlotbutton,
-        tmTags,
-        tmEmpty,
-        tmIcons,
-        tmImages,
-        tmFlowLayoutCustom,
-        tmTabs,
-		tmButton
-    },
-    data() {
-        return {
-            isBlackTheme: false,
-            loading: 'loading',
-            category: {
-                activeIndex: 0,
-                activeValue: '',
-                list: []
-            },
-            queryParams: {
-                size: 10,
-                page: 1,
-                group: ""
-            },
-            isLoadMore: false,
-            loadMoreText: '',
-            hasNext: false,
-            dataList: [],
-			lock:false
-        };
-    },
-    computed: {
-        galleryConfig() {
-            return this.$tm.vx.getters().getConfigs.pageConfig.galleryConfig;
-        },
-        haloConfigs() {
-            return this.$tm.vx.getters().getConfigs;
-        },
-        mockJson() {
-            return this.$tm.vx.getters().getMockJson;
-        },
-        calcAuditModeEnabled(){
-            return this.haloConfigs.auditConfig.auditModeEnabled
-        },
-    },
-    watch: {
-        galleryConfig: {
-            handler(newValue, oldValue) {
-                if (!newValue) return;
-                this.fnSetPageTitle(newValue.pageTitle);
-                this.fnGetCategory();
-            },
-            deep: true,
-            immediate: true
-        }
-    },
-    onPullDownRefresh() {
-        this.dataList = []
-        this.isLoadMore = false;
-        this.queryParams.page = 1;
-        this.fnGetData(true);
-    },
-    onReachBottom(e) {
-        if (this.calcAuditModeEnabled) {
-            uni.showToast({
-                icon: 'none',
-                title: '没有更多数据了'
-            });
-            return;
-        }
-        if (this.hasNext) {
-            this.queryParams.page += 1;
-            this.isLoadMore = true;
-            this.fnGetData(false);
-        } else {
-            uni.showToast({
-                icon: 'none',
-                title: '没有更多数据了'
-            });
-        }
-    },
-    methods: {
-		fnGetDataByCategory(index){
-			this.fnResetSetAniWaitIndex();
-			this.queryParams.group = this.category.list[index].name;
+	export default {
+		options: {
+			multipleSlots: true
+		},
+		mixins: [pluginAvailable],
+		components: {
+			tmSkeleton,
+			tmTranslate,
+			tmFlotbutton,
+			tmTags,
+			tmEmpty,
+			tmIcons,
+			tmImages,
+			tmFlowLayoutCustom,
+			tmTabs,
+			tmButton
+		},
+		data() {
+			return {
+				isBlackTheme: false,
+				loading: 'loading',
+				category: {
+					activeIndex: 0,
+					activeValue: '',
+					list: []
+				},
+				queryParams: {
+					size: 10,
+					page: 1,
+					group: ""
+				},
+				isLoadMore: false,
+				loadMoreText: '',
+				hasNext: false,
+				dataList: [],
+				lock: false
+			};
+		},
+		computed: {
+			galleryConfig() {
+				return this.$tm.vx.getters().getConfigs.pageConfig.galleryConfig;
+			},
+			haloConfigs() {
+				return this.$tm.vx.getters().getConfigs;
+			},
+			mockJson() {
+				return this.$tm.vx.getters().getMockJson;
+			},
+			calcAuditModeEnabled() {
+				return this.haloConfigs.auditConfig.auditModeEnabled
+			},
+		},
+		watch: {
+			galleryConfig: {
+				  async handler(newValue, oldValue) {
+					if (!newValue) return;
+					this.fnSetPageTitle(newValue.pageTitle);
+					this.fnGetCategory();
+				},
+				deep: true,
+				immediate: true
+			}
+		},
+		async onLoad() {
+			// 检查插件
+			this.setPluginId(this.NeedPluginIds.PluginPhotos)
+			this.setPluginError("阿偶,检测到当前插件没有安装或者启用,无法使用图库功能哦,请联系管理员")
+			await this.checkPluginAvailable()
+		},
+		onPullDownRefresh() {
+			if (!this.uniHaloPluginAvailable) return;
+			this.dataList = []
+			this.isLoadMore = false;
 			this.queryParams.page = 1;
-			this.fnToTopPage();
-			this.dataList = [];
 			this.fnGetData(true);
 		},
-        fnOnCategoryChange(index) {
-			if(this.lock) {
-				// uni.showToast({
-				// 	title: "上一个请求进行中...",
-				// 	icon: "none"
-				// })
+		onReachBottom(e) {
+			if (!this.uniHaloPluginAvailable) return;
+			if (this.calcAuditModeEnabled) {
+				uni.showToast({
+					icon: 'none',
+					title: '没有更多数据了'
+				});
 				return;
 			}
-			this.fnGetDataByCategory(index)
-        },
-        fnGetCategory() {
-            if (this.calcAuditModeEnabled) {
-                this.fnGetData(true);
-                return
-            }
-            this.$httpApi.v2.getPhotoGroupList({
-                page: 1,
-                size: 0
-            }).then(res => {
-                this.category.list = res.items.map(item => {
-                    return {
-                        name: item.metadata.name,
-                        displayName: item.spec.displayName,
-						priority: item.spec.priority
-                    }
-                }).sort((a,b) => a.priority - b.priority);
-				
-                if (this.category.list.length !== 0) {
-                    this.queryParams.group = this.category.list[0].name;
-                    this.fnGetData(true);
-                }
-            }).catch(e=>{
-				this.loading = 'error'
-				this.category.list = []
-				this.category.activeIndex = 0
-				this.category.activeValue = ""
-			});
-        },
-        fnGetData(isClearWaterfall = false) {
-            if (this.calcAuditModeEnabled) {
-                this.dataList = this.mockJson.gallery.list.map(item => {
-                    return {
-                        metadata: {
-                            name: Date.now() * Math.random(),
-                        },
-                        spec: {
-                            url: this.$utils.checkImageUrl(item)
-                        }
-                    }
-                })
 
-                this.loading = 'success';
-
-                if (this.galleryConfig.useWaterfall) {
-                    this.$nextTick(() => {
-                        if (isClearWaterfall) {
-                            this.$refs.wafll.clear()
-                        }
-                        setTimeout(() => {
-                            this.$refs.wafll.pushData(this.dataList)
-                        }, 50)
-                    })
-                }
-                this.loadMoreText = '呜呜,没有更多数据啦~';
-                uni.hideLoading();
-                uni.stopPullDownRefresh();
-				this.lock = false;
-                return;
-            }
-
-            // 设置状态为加载中
-            if (!this.isLoadMore) {
-                this.loading = 'loading';
-            }
-            this.loadMoreText = '';
-            this.$httpApi.v2
-                .getPhotoListByGroupName(this.queryParams)
-                .then(res => {
-                    this.hasNext = res.hasNext;
-                    this.loading = 'success';
-                    if (res.items.length !== 0) {
-                        const _list = res.items.map((item, index) => {
-                            item.spec.url = this.$utils.checkImageUrl(item.spec.url || item.spec.cover);
-                            return item;
-                        });
-                        if (this.isLoadMore) {
-                            this.dataList = this.dataList.concat(_list);
-                        } else {
-                            this.dataList = _list;
-                        }
-                        if (this.galleryConfig.useWaterfall) {
-                            this.$nextTick(() => {
-                                if (isClearWaterfall) {
-                                    this.$refs.wafll.clear()
-                                }
-                                this.$refs.wafll.pushData(_list)
-                            })
-                        }
-                    }
-                    this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
-                })
-                .catch(err => {
-                    console.error(err);
-                    this.loading = 'error';
-                    this.loadMoreText = '加载失败,请下拉刷新!';
-                })
-                .finally(() => {
-                    setTimeout(() => {
-                        uni.hideLoading();
-                        uni.stopPullDownRefresh();
-						this.lock = false;
-                    }, 500);
-                });
-        },
-        fnOnFlowClick({item}) {
-            this.fnPreview(item)
-        },
-        // 预览
-        fnPreview(data) {
-            uni.previewImage({
-                current: this.dataList.findIndex(x => x.metadata.name === data.metadata.name),
-                urls: this.dataList.map(x => x.spec.url),
-                indicator: 'number',
-                loop: true
-            });
-        },
-		touchLeft(){
-			if(this.loading != "success") return; 
-			this.category.activeIndex += 1
-			if(this.category.activeIndex >= this.category.list.length){
-				this.category.activeIndex = 0
+			if (this.hasNext) {
+				this.queryParams.page += 1;
+				this.isLoadMore = true;
+				this.fnGetData(false);
+			} else {
+				uni.showToast({
+					icon: 'none',
+					title: '没有更多数据了'
+				});
 			}
-			this.lock = true
-			this.fnGetDataByCategory(this.category.activeIndex)
 		},
-		touchRight(){
-			if(this.loading != "success") return;
-			this.category.activeIndex -= 1
-			if(this.category.activeIndex < 0){
-				this.category.activeIndex = 0
+		methods: {
+			fnGetDataByCategory(index) {
+				this.fnResetSetAniWaitIndex();
+				this.queryParams.group = this.category.list[index].name;
+				this.queryParams.page = 1;
+				this.fnToTopPage();
+				this.dataList = [];
+				this.fnGetData(true);
+			},
+			fnOnCategoryChange(index) {
+				if (this.lock) {
+					// uni.showToast({
+					// 	title: "上一个请求进行中...",
+					// 	icon: "none"
+					// })
+					return;
+				}
+				this.fnGetDataByCategory(index)
+			},
+			fnGetCategory() {
+				if (this.calcAuditModeEnabled) {
+					this.fnGetData(true);
+					return
+				}
+				this.$httpApi.v2.getPhotoGroupList({
+					page: 1,
+					size: 0
+				}).then(res => {
+					this.category.list = res.items.map(item => {
+						return {
+							name: item.metadata.name,
+							displayName: item.spec.displayName,
+							priority: item.spec.priority
+						}
+					}).sort((a, b) => a.priority - b.priority);
+
+					if (this.category.list.length !== 0) {
+						this.queryParams.group = this.category.list[0].name;
+						this.fnGetData(true);
+					}
+				}).catch(e => {
+					this.loading = 'error'
+					this.category.list = []
+					this.category.activeIndex = 0
+					this.category.activeValue = ""
+				});
+			},
+			fnGetData(isClearWaterfall = false) {
+				if (this.calcAuditModeEnabled) {
+					this.dataList = this.mockJson.gallery.list.map(item => {
+						return {
+							metadata: {
+								name: Date.now() * Math.random(),
+							},
+							spec: {
+								url: this.$utils.checkImageUrl(item)
+							}
+						}
+					})
+
+					this.loading = 'success';
+
+					if (this.galleryConfig.useWaterfall) {
+						this.$nextTick(() => {
+							if (isClearWaterfall) {
+								this.$refs.wafll.clear()
+							}
+							setTimeout(() => {
+								this.$refs.wafll.pushData(this.dataList)
+							}, 50)
+						})
+					}
+					this.loadMoreText = '呜呜,没有更多数据啦~';
+					uni.hideLoading();
+					uni.stopPullDownRefresh();
+					this.lock = false;
+					return;
+				}
+
+				// 设置状态为加载中
+				if (!this.isLoadMore) {
+					this.loading = 'loading';
+				}
+				this.loadMoreText = '';
+				this.$httpApi.v2
+					.getPhotoListByGroupName(this.queryParams)
+					.then(res => {
+						this.hasNext = res.hasNext;
+						this.loading = 'success';
+						if (res.items.length !== 0) {
+							const _list = res.items.map((item, index) => {
+								item.spec.url = this.$utils.checkImageUrl(item.spec.url || item.spec.cover);
+								return item;
+							});
+							if (this.isLoadMore) {
+								this.dataList = this.dataList.concat(_list);
+							} else {
+								this.dataList = _list;
+							}
+							if (this.galleryConfig.useWaterfall) {
+								this.$nextTick(() => {
+									if (isClearWaterfall) {
+										this.$refs.wafll.clear()
+									}
+									this.$refs.wafll.pushData(_list)
+								})
+							}
+						}
+						this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
+					})
+					.catch(err => {
+						console.error(err);
+						this.loading = 'error';
+						this.loadMoreText = '加载失败,请下拉刷新!';
+					})
+					.finally(() => {
+						setTimeout(() => {
+							uni.hideLoading();
+							uni.stopPullDownRefresh();
+							this.lock = false;
+						}, 500);
+					});
+			},
+			fnOnFlowClick({
+				item
+			}) {
+				this.fnPreview(item)
+			},
+			// 预览
+			fnPreview(data) {
+				uni.previewImage({
+					current: this.dataList.findIndex(x => x.metadata.name === data.metadata.name),
+					urls: this.dataList.map(x => x.spec.url),
+					indicator: 'number',
+					loop: true
+				});
+			},
+			touchLeft() {
+				if (this.loading != "success") return;
+				this.category.activeIndex += 1
+				if (this.category.activeIndex >= this.category.list.length) {
+					this.category.activeIndex = 0
+				}
+				this.lock = true
+				this.fnGetDataByCategory(this.category.activeIndex)
+			},
+			touchRight() {
+				if (this.loading != "success") return;
+				this.category.activeIndex -= 1
+				if (this.category.activeIndex < 0) {
+					this.category.activeIndex = 0
+				}
+				this.lock = true
+				this.fnGetDataByCategory(this.category.activeIndex)
 			}
-			this.lock = true
-			this.fnGetDataByCategory(this.category.activeIndex)
-		} 
-    }
-};
+		}
+	};
 </script>
 
 <style lang="scss" scoped>
-.app-page {
-    width: 100vw;
-    min-height: 100vh;
-    display: flex;
-    flex-direction: column;
-    padding-bottom: 24rpx;
-    background-color: #fafafa;
-}
-.content {
-	width:100%;
-    display: flex;
-    flex-wrap: wrap;
-    box-sizing: border-box;
+	.app-page {
+		width: 100vw;
+		min-height: 100vh;
+		display: flex;
+		flex-direction: column;
+		padding-bottom: 24rpx;
+		background-color: #fafafa;
+	}
+
+	.content {
+		width: 100%;
+		display: flex;
+		flex-wrap: wrap;
+		box-sizing: border-box;
+
+		.content-empty {
+			width: 100%;
+			height: 70vh;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+		}
+	}
+
+	.touch-listen-content {
+		width: 100%;
+		display: flex;
+		flex-wrap: wrap;
+		box-sizing: border-box;
+		padding: 24rpx 24rpx 0;
+		gap: 12rpx 0;
+	}
 
-    .content-empty {
-        width: 100%;
-        height: 70vh;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-    }
-}
-.touch-listen-content {
-	width:100%;
-	display: flex;
-	flex-wrap: wrap;
-	box-sizing: border-box;
-	padding: 24rpx 24rpx 0;
-	gap: 12rpx 0;
-}
-.loading-wrap {
-    box-sizing: border-box;
-    padding: 24rpx;
-}
+	.loading-wrap {
+		box-sizing: border-box;
+		padding: 24rpx;
+	}
 
-.load-text {
-    width: 100%;
-    text-align: center;
-}
+	.load-text {
+		width: 100%;
+		text-align: center;
+	}
 
-.flot-buttons {
-    position: fixed;
-    bottom: 100rpx;
-    right: 32rpx;
-    flex-direction: column;
-    display: flex;
-    gap: 6rpx;
-    z-index: 999;
-}
-</style>
+	.flot-buttons {
+		position: fixed;
+		bottom: 100rpx;
+		right: 32rpx;
+		flex-direction: column;
+		display: flex;
+		gap: 6rpx;
+		z-index: 999;
+	}
+</style>

+ 87 - 69
pages/tabbar/moments/moments.vue

@@ -1,79 +1,84 @@
 <template>
 	<view class="app-page">
-		<view v-if="loading !== 'success'" class="loading-wrap">
-			<tm-skeleton model="listAvatr"></tm-skeleton>
-			<tm-skeleton model="listAvatr"></tm-skeleton>
-			<tm-skeleton model="listAvatr"></tm-skeleton>
-		</view>
-		<!-- 内容区域 -->
-		<view v-else class="app-page-content">
-			<view v-if="dataList.length === 0" class="content-empty flex flex-center" style="min-height: 70vh;">
-				<!-- 空布局 -->
-				<tm-empty icon="icon-shiliangzhinengduixiang-" label="暂无数据"></tm-empty>
+		<PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId"
+			:error-text="uniHaloPluginAvailableError" />
+		<template v-else>
+			<view v-if="loading !== 'success'" class="loading-wrap">
+				<tm-skeleton model="listAvatr"></tm-skeleton>
+				<tm-skeleton model="listAvatr"></tm-skeleton>
+				<tm-skeleton model="listAvatr"></tm-skeleton>
 			</view>
-			<block v-else>
-				<!-- 卡片 -->
-				<tm-translate v-for="(moment, index) in dataList" :key="moment.metadata.name" animation-name="fadeUp"
-					:wait="calcAniWait(index)">
-					<view class="moment-card">
-						<view class="head" style="display: flex;align-items: center;">
-							<view class="avatar" style="flex-shrink: 0;">
-								<image style="width: 66rpx;height: 66rpx;border-radius: 50%;"
-									:src="moment.spec.user.avatar" />
-							</view>
-							<view class="nickname" style="margin-left: 12rpx;">
-								<view style="font-size: 30rpx;font-weight: bold;color: #333333;">
-									{{ moment.spec.user.displayName }}
+			<!-- 内容区域 -->
+			<view v-else class="app-page-content">
+				<view v-if="dataList.length === 0" class="content-empty flex flex-center" style="min-height: 70vh;">
+					<!-- 空布局 -->
+					<tm-empty icon="icon-shiliangzhinengduixiang-" label="暂无数据"></tm-empty>
+				</view>
+				<block v-else>
+					<!-- 卡片 -->
+					<tm-translate v-for="(moment, index) in dataList" :key="moment.metadata.name"
+						animation-name="fadeUp" :wait="calcAniWait(index)">
+						<view class="moment-card">
+							<view class="head" style="display: flex;align-items: center;">
+								<view class="avatar" style="flex-shrink: 0;">
+									<image style="width: 66rpx;height: 66rpx;border-radius: 50%;"
+										:src="moment.spec.user.avatar" />
 								</view>
-								<view style="margin-top: 6rpx;font-size: 24rpx;color: #666;">
-									{{ {d: moment.spec.releaseTime, f: 'yyyy年MM月dd日 星期w'} | formatTime }}
+								<view class="nickname" style="margin-left: 12rpx;">
+									<view style="font-size: 30rpx;font-weight: bold;color: #333333;">
+										{{ moment.spec.user.displayName }}
+									</view>
+									<view style="margin-top: 6rpx;font-size: 24rpx;color: #666;">
+										{{ {d: moment.spec.releaseTime, f: 'yyyy年MM月dd日 星期w'} | formatTime }}
+									</view>
 								</view>
 							</view>
-						</view>
-						<view class="content" @click.stop="handleToMomentDetail(moment)">
-							<mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain"
-								:loading-img="markdownConfig.loadingGif" :scroll-table="true" :selectable="true"
-								:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
-								:content="moment.spec.newHtml" :markdown="true" :showLineNumber="true"
-								:showLanguageName="true" :copyByLongPress="true" />
-						</view>
-						<view v-if="moment.images && moment.images.length!==0" class="images"
-							:class="['images-'+moment.images.length]">
-							<view class="image-item" v-for="(image,mediumIndex) in moment.images" :key="mediumIndex">
-								<image mode="aspectFill" style="width: 100%;height: 100%;border-radius: 6rpx;"
-									:src="image.url" @click="handlePreview(mediumIndex,moment.images)" />
+							<view class="content" @click.stop="handleToMomentDetail(moment)">
+								<mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain"
+									:loading-img="markdownConfig.loadingGif" :scroll-table="true" :selectable="true"
+									:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
+									:content="moment.spec.newHtml" :markdown="true" :showLineNumber="true"
+									:showLanguageName="true" :copyByLongPress="true" />
+							</view>
+							<view v-if="moment.images && moment.images.length!==0" class="images"
+								:class="['images-'+moment.images.length]">
+								<view class="image-item" v-for="(image,mediumIndex) in moment.images"
+									:key="mediumIndex">
+									<image mode="aspectFill" style="width: 100%;height: 100%;border-radius: 6rpx;"
+										:src="image.url" @click="handlePreview(mediumIndex,moment.images)" />
+								</view>
+							</view>
+							<view v-if="moment.audios && moment.audios.length!==0" class="mb-12"
+								style="display: flex; flex-direction: column; gap: 12rpx 0;padding: 0 24rpx;padding-right:28rpx;">
+								<audio v-for="(audio,index) in moment.audios" :controls="true" :key="index"
+									:id="audio.url" :poster="bloggerInfo.avatar"
+									:name="'来自' + (startConfig.title||bloggerInfo.nickname) + '的声音'"
+									:author="bloggerInfo.nickname" :src="audio.url"></audio>
+							</view>
+							<view v-if="moment.videos && moment.videos.length!==0" class="mb-12"
+								style="display: flex; flex-direction: column; gap: 12rpx 0;padding: 0 24rpx; ">
+								<video style="width:100%;height: 400rpx;border-radius: 12rpx;"
+									v-for="(video,index) in moment.videos" :key="index" :src="video.url"
+									:id="'video_' + video.id" :show-mute-btn="true" :controls="true"
+									:show-center-play-btn="true" :enable-progress-gesture="true"
+									@play="onVideoPlay(video.id)" @pause="onVideoPause(video.id)"
+									@ended="onVideoEnded(video.id)"></video>
+							</view>
+							<view v-if="moment.spec.tags && moment.spec.tags.length!==0"
+								class="mt-12 px-16 pb-24 flex flex-wrap">
+								<tm-tags v-for="(tag,tagIndex) in moment.spec.tags" :key="tagIndex"
+									:color="randomTagColor()" size="m" model="text">
+									{{ tag }}
+								</tm-tags>
 							</view>
 						</view>
-						<view v-if="moment.audios && moment.audios.length!==0" class="mb-12"
-							style="display: flex; flex-direction: column; gap: 12rpx 0;padding: 0 24rpx;padding-right:28rpx;">
-							<audio v-for="(audio,index) in moment.audios" :controls="true" :key="index" :id="audio.url"
-								:poster="bloggerInfo.avatar"
-								:name="'来自' + (startConfig.title||bloggerInfo.nickname) + '的声音'"
-								:author="bloggerInfo.nickname" :src="audio.url"></audio>
-						</view>
-						<view v-if="moment.videos && moment.videos.length!==0" class="mb-12"
-							style="display: flex; flex-direction: column; gap: 12rpx 0;padding: 0 24rpx; ">
-							<video style="width:100%;height: 400rpx;border-radius: 12rpx;"
-								v-for="(video,index) in moment.videos" :key="index" :src="video.url"
-								:id="'video_' + video.id" :show-mute-btn="true" :controls="true"
-								:show-center-play-btn="true" :enable-progress-gesture="true"
-								@play="onVideoPlay(video.id)" @pause="onVideoPause(video.id)"
-								@ended="onVideoEnded(video.id)"></video>
-						</view>
-						<view v-if="moment.spec.tags && moment.spec.tags.length!==0"
-							class="mt-12 px-16 pb-24 flex flex-wrap">
-							<tm-tags v-for="(tag,tagIndex) in moment.spec.tags" :key="tagIndex"
-								:color="randomTagColor()" size="m" model="text">
-								{{ tag }}
-							</tm-tags>
-						</view>
-					</view>
-				</tm-translate>
-				<tm-flotbutton @click="fnToTopPage" :width="90" size="xs" color="light-blue" :icon-size="24"
-					icon="icon-angle-up"></tm-flotbutton>
-				<view class="load-text">{{ loadMoreText }}</view>
-			</block>
-		</view>
+					</tm-translate>
+					<tm-flotbutton @click="fnToTopPage" :width="90" size="xs" color="light-blue" :icon-size="24"
+						icon="icon-angle-up"></tm-flotbutton>
+					<view class="load-text">{{ loadMoreText }}</view>
+				</block>
+			</view>
+		</template>
 	</view>
 </template>
 
@@ -92,7 +97,11 @@
 	import {
 		generateUUID
 	} from '@/utils/uuid.js';
+
+	import pluginAvailable from "@/common/mixins/pluginAvailable.js"
+
 	export default {
+		mixins: [pluginAvailable],
 		components: {
 			tmSkeleton,
 			tmFlotbutton,
@@ -141,10 +150,16 @@
 				return this.haloConfigs.appConfig.startConfig;
 			}
 		},
-		onLoad() {
+		async onLoad() { 
+			// 检查插件
+			this.setPluginId(this.NeedPluginIds.PluginMoments)
+			this.setPluginError("阿偶,检测到当前插件没有安装或者启用,无法使用瞬间功能哦,请联系管理员")
+			if (!await this.checkPluginAvailable()) return
+			
 			this.fnGetData();
 		},
 		onPullDownRefresh() {
+			if (!this.uniHaloPluginAvailable) return;
 			this.isLoadMore = false;
 			this.queryParams.page = 0;
 			this.videoContexts = {};
@@ -152,6 +167,7 @@
 			this.fnGetData();
 		},
 		onReachBottom(e) {
+			if (!this.uniHaloPluginAvailable) return;
 			if (this.calcAuditModeEnabled) {
 				uni.showToast({
 					icon: 'none',
@@ -322,7 +338,9 @@
 
 <style lang="scss" scoped>
 	.app-page {
+		box-sizing: border-box;
 		width: 100vw;
+		min-height: 100vh;
 		display: flex;
 		flex-direction: column;
 		padding: 24rpx 0;

+ 436 - 419
pagesA/friend-links/friend-links.vue

@@ -1,431 +1,448 @@
 <template>
-    <view class="app-page card-shadow">
-        <view v-if="loading != 'success'" class="loading-wrap">
-            <tm-skeleton model="listAvatr"></tm-skeleton>
-            <tm-skeleton model="listAvatr"></tm-skeleton>
-            <tm-skeleton model="listAvatr"></tm-skeleton>
-            <tm-skeleton model="listAvatr"></tm-skeleton>
-            <tm-skeleton model="listAvatr"></tm-skeleton>
-        </view>
-        <view v-else class="content" :class="{ 'bg-white': dataList.length !== 0 }">
-            <!-- 空数据 -->
-            <view v-if="dataList.length == 0" class="content-empty flex flex-center">
-                <tm-empty icon="icon-shiliangzhinengduixiang-" label="啊偶,博主还没有朋友呢~"></tm-empty>
-            </view>
-
-            <!-- 如果只有一个分组:使用列表的形式 dataList.length == 1 -->
-            <view v-else class="flex flex-col pb-24">
-                <block v-for="(link, index) in dataList" :key="index">
-                    <tm-translate animation-name="fadeUp" :wait="calcAniWait(index)">
-                        <!-- 色彩版本 -->
-                        <view v-if="!globalAppSettings.links.useSimple" class="info flex pt-24 pb-24 pl-12 pr-12"
-                              :class="{ 'border-b-1': index !== dataList.length - 1 }" @click="fnOnLinkEvent(link)">
-                            <view class="link-logo">
-                                <cache-image class="link-logo_img" radius="12rpx" :url="link.spec.logo"
-                                             :fileMd5="link.spec.logo" mode="aspectFill"></cache-image>
-                            </view>
-                            <view class="flex flex-col pl-30 info-detail">
-                                <view class="link-card_name text-size-l text-weight-b text-red">
-                                    <tm-tags style="margin-right: 12rpx;margin-left: -2rpx;"
-                                             color="bg-gradient-light-blue-lighten" :shadow="0" size="s" model="fill">
-                                        {{ link.spec.groupName || '暂未分组' }}
-                                    </tm-tags>
-                                    {{ link.spec.displayName }}
-                                </view>
-                                <view class="poup-tag mt-6" style="font-size: 28rpx;">
-                                    站点地址:{{ link.spec.url }}
-                                    <!-- <tm-tags color="bg-gradient-amber-accent" :shadow="0" size="s" model="fill">
+	<view class="app-page card-shadow">
+		<PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId"
+			:error-text="uniHaloPluginAvailableError" />
+		<template v-else>
+			<view v-if="loading != 'success'" class="loading-wrap">
+				<tm-skeleton model="listAvatr"></tm-skeleton>
+				<tm-skeleton model="listAvatr"></tm-skeleton>
+				<tm-skeleton model="listAvatr"></tm-skeleton>
+				<tm-skeleton model="listAvatr"></tm-skeleton>
+				<tm-skeleton model="listAvatr"></tm-skeleton>
+			</view>
+			<view v-else class="content" :class="{ 'bg-white': dataList.length !== 0 }">
+				<!-- 空数据 -->
+				<view v-if="dataList.length == 0" class="content-empty flex flex-center">
+					<tm-empty icon="icon-shiliangzhinengduixiang-" label="啊偶,博主还没有朋友呢~"></tm-empty>
+				</view>
+
+				<!-- 如果只有一个分组:使用列表的形式 dataList.length == 1 -->
+				<view v-else class="flex flex-col pb-24">
+					<block v-for="(link, index) in dataList" :key="index">
+						<tm-translate animation-name="fadeUp" :wait="calcAniWait(index)">
+							<!-- 色彩版本 -->
+							<view v-if="!globalAppSettings.links.useSimple" class="info flex pt-24 pb-24 pl-12 pr-12"
+								:class="{ 'border-b-1': index !== dataList.length - 1 }" @click="fnOnLinkEvent(link)">
+								<view class="link-logo">
+									<cache-image class="link-logo_img" radius="12rpx" :url="link.spec.logo"
+										:fileMd5="link.spec.logo" mode="aspectFill"></cache-image>
+								</view>
+								<view class="flex flex-col pl-30 info-detail">
+									<view class="link-card_name text-size-l text-weight-b text-red">
+										<tm-tags style="margin-right: 12rpx;margin-left: -2rpx;"
+											color="bg-gradient-light-blue-lighten" :shadow="0" size="s" model="fill">
+											{{ link.spec.groupName || '暂未分组' }}
+										</tm-tags>
+										{{ link.spec.displayName }}
+									</view>
+									<view class="poup-tag mt-6" style="font-size: 28rpx;">
+										站点地址:{{ link.spec.url }}
+										<!-- <tm-tags color="bg-gradient-amber-accent" :shadow="0" size="s" model="fill">
                                         URL:{{ link.spec.url }}
                                     </tm-tags>
                                     <tm-tags color=" bg-gradient-light-blue-lighten" :shadow="0" size="s" model="fill">
                                         {{ link.spec.groupName || '暂未分组' }}
                                     </tm-tags> -->
-                                </view>
-                                <view class="link-card_desc text-overflow mt-4" style="font-size: 28rpx;">
-                                    博客简介:{{ link.spec.description || '这个博主很懒,没写简介~' }}
-                                </view>
-                            </view>
-                        </view>
-                        <!-- 简洁版本 -->
-                        <view v-else class="link-card flex ml-24 mr-24 pt-24 pb-24" @click="fnOnLinkEvent(link)">
-                            <image class="logo shadow-6" :src="link.spec.logo" mode="aspectFill"></image>
-                            <view class="info pl-24">
-                                <view class="name text-size-g">{{ link.spec.displayName }}</view>
-                                <view class="desc mt-12 text-size-s text-grey-darken-1">{{ link.spec.description }}
-                                </view>
-                                <view v-if="false" class="link mt-12 text-size-m text-grey-darken-1">
-                                    <text class="iconfont icon-link mr-6 text-size-s"></text>
-                                    {{ link.spec.url }}
-                                </view>
-                            </view>
-                        </view>
-                    </tm-translate>
-                </block>
-            </view>
-
-            <!-- 返回顶部 -->
-            <tm-flotbutton color="light-blue" @click="fnToTopPage" size="m" icon="icon-angle-up"></tm-flotbutton>
-            <tm-flotbutton v-if="haloPluginConfigs.linksSubmitPlugin.enabled" :offset="[16,80]" label="申请"
-                           actions-pos="left" :show-text="true" color="bg-gradient-orange-accent"
-                           @click="toSubmitLinkPage"></tm-flotbutton>
-            <!-- 详情弹窗 -->
-            <tm-poup v-model="detail.show" :width="640" height="auto" position="center" :round="6">
-                <view class="poup pa-36" v-if="detail.data">
-                    <view class="info flex">
-                        <view class="poup-logo bg-gradient-amber-accent pa-4 shadow-24">
-                            <image class="poup-logo_img" :src="$utils.checkImageUrl(detail.data.spec.logo)" mode="aspectFill"></image>
-                        </view>
-                        <view class="pl-24 info-detail">
-                            <view class="poup-name text-size-lg text-weight-b">{{ detail.data.spec.displayName }}</view>
-                            <view class="poup-tag ml--10">
-                                <tm-tags color="bg-gradient-light-blue-lighten" size="n" model="fill">
-                                    {{ detail.data.spec.groupName }}
-                                </tm-tags>
-                            </view>
-                            <view class="poup-link text-size-m" @click="fnCopyLink(detail.data)">
-                                <text class="text-orange">{{ detail.data.spec.url }}</text>
-                                <text class="iconfont icon-copy text-size-s ml-6 text-grey"></text>
-                            </view>
-                        </view>
-                    </view>
-
-                    <view class="poup-desc mt-20">
-                        博客简介:{{ detail.data.spec.description || '这个博主很懒,没写简介~' }}
-                    </view>
-
-                    <!-- 博客预览图 -->
-                    <view class="mt-24">
-                        <tm-images :width="568" :round="2" :src="calcSiteThumbnail(detail.data.spec.url)"
-                                   mode="aspectFill"></tm-images>
-                    </view>
-                </view>
-            </tm-poup>
-
-            <view class="load-text">{{ loadMoreText }}</view>
-        </view>
-    </view>
+									</view>
+									<view class="link-card_desc text-overflow mt-4" style="font-size: 28rpx;">
+										博客简介:{{ link.spec.description || '这个博主很懒,没写简介~' }}
+									</view>
+								</view>
+							</view>
+							<!-- 简洁版本 -->
+							<view v-else class="link-card flex ml-24 mr-24 pt-24 pb-24" @click="fnOnLinkEvent(link)">
+								<image class="logo shadow-6" :src="link.spec.logo" mode="aspectFill"></image>
+								<view class="info pl-24">
+									<view class="name text-size-g">{{ link.spec.displayName }}</view>
+									<view class="desc mt-12 text-size-s text-grey-darken-1">{{ link.spec.description }}
+									</view>
+									<view v-if="false" class="link mt-12 text-size-m text-grey-darken-1">
+										<text class="iconfont icon-link mr-6 text-size-s"></text>
+										{{ link.spec.url }}
+									</view>
+								</view>
+							</view>
+						</tm-translate>
+					</block>
+				</view>
+
+				<!-- 返回顶部 -->
+				<tm-flotbutton color="light-blue" @click="fnToTopPage" size="m" icon="icon-angle-up"></tm-flotbutton>
+				<tm-flotbutton v-if="haloPluginConfigs.linksSubmitPlugin.enabled" :offset="[16,80]" label="申请"
+					actions-pos="left" :show-text="true" color="bg-gradient-orange-accent"
+					@click="toSubmitLinkPage"></tm-flotbutton>
+				<!-- 详情弹窗 -->
+				<tm-poup v-model="detail.show" :width="640" height="auto" position="center" :round="6">
+					<view class="poup pa-36" v-if="detail.data">
+						<view class="info flex">
+							<view class="poup-logo bg-gradient-amber-accent pa-4 shadow-24">
+								<image class="poup-logo_img" :src="$utils.checkImageUrl(detail.data.spec.logo)"
+									mode="aspectFill"></image>
+							</view>
+							<view class="pl-24 info-detail">
+								<view class="poup-name text-size-lg text-weight-b">{{ detail.data.spec.displayName }}
+								</view>
+								<view class="poup-tag ml--10">
+									<tm-tags color="bg-gradient-light-blue-lighten" size="n" model="fill">
+										{{ detail.data.spec.groupName }}
+									</tm-tags>
+								</view>
+								<view class="poup-link text-size-m" @click="fnCopyLink(detail.data)">
+									<text class="text-orange">{{ detail.data.spec.url }}</text>
+									<text class="iconfont icon-copy text-size-s ml-6 text-grey"></text>
+								</view>
+							</view>
+						</view>
+
+						<view class="poup-desc mt-20">
+							博客简介:{{ detail.data.spec.description || '这个博主很懒,没写简介~' }}
+						</view>
+
+						<!-- 博客预览图 -->
+						<view class="mt-24">
+							<tm-images :width="568" :round="2" :src="calcSiteThumbnail(detail.data.spec.url)"
+								mode="aspectFill"></tm-images>
+						</view>
+					</view>
+				</tm-poup>
+
+				<view class="load-text">{{ loadMoreText }}</view>
+			</view>
+		</template>
+	</view>
 </template>
 
 <script>
-import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
-import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
-import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
-import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
-import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
-import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue';
-import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
-
-export default {
-    components: {
-        tmSkeleton,
-        tmTranslate,
-        tmFlotbutton,
-        tmTags,
-        tmEmpty,
-        tmImages,
-        tmPoup
-    },
-    data() {
-        return {
-            loading: 'loading',
-            queryParams: {
-                size: 10,
-                page: 1
-            },
-            detail: {
-                show: false,
-                data: null
-            },
-            hasNext: false,
-            isLoadMore: false,
-            loadMoreText: '',
-            linkGroupList: [],
-            dataList: [],
-            colors: [
-                '#39B449',
-                '#E44C41',
-                '#8698A2',
-                '#0080FE',
-                '#1CBCB4',
-                '#6638B5'
-            ]
-        };
-    },
-    computed: {
-        haloConfigs() {
-            return this.$tm.vx.getters().getConfigs;
-        },
-        haloPluginConfigs() {
-            return this.$tm.vx.getters().getConfigs.pluginConfig;
-        },
-        calcSiteThumbnail(val) {
-            return val => {
-                if (!val) return '';
-                if (val.charAt(val.length - 1) !== '/') {
-                    val = val + '/';
-                }
-                return 'https://image.thum.io/get/width/1000/crop/800/' + val;
-            };
-        },
-        calcAuditModeEnabled() {
-            return this.haloConfigs.auditConfig.auditModeEnabled
-        },
-    },
-    onLoad() {
-        this.fnSetPageTitle('友情链接');
-        this.fnGetLinkGroupData();
-    },
-    onPullDownRefresh() {
-        this.isLoadMore = false;
-        this.queryParams.page = 1;
-        this.dataList = []
-        this.fnGetData();
-    },
-    onReachBottom(e) {
-        if (this.hasNext) {
-            this.queryParams.page += 1;
-            this.isLoadMore = true;
-            this.fnGetData();
-        } else {
-            uni.showToast({
-                icon: 'none',
-                title: '没有更多数据了'
-            });
-        }
-    },
-    methods: {
-        fnGetLinkGroupData() {
-            this.$httpApi.v2
-                .getFriendLinkGroupList({
-                    page: 1,
-                    size: 0
-                })
-                .then(res => {
-                    this.linkGroupList = res.items;
-                    this.fnGetData()
-                })
-                .catch(err => {
-                    console.error(err);
-                });
-        },
-        findLinkGroupDisplayNameByGroupMetadataName(groupName) {
-            if (this.linkGroupList.length === 0) return groupName || "未分组"
-            return this.linkGroupList.find(item => item.metadata.name === groupName)?.spec?.displayName || groupName || "未分组"
-        },
-        fnGetData() {
-            if (this.calcAuditModeEnabled) {
-                return;
-            }
-            if (!this.isLoadMore) {
-                this.loading = 'loading';
-            }
-            this.loadMoreText = '';
-            this.$httpApi.v2
-                .getFriendLinkList(this.queryParams)
-                .then(res => {
-                    console.log('请求结果:');
-                    console.log(res);
-                    this.hasNext = res.hasNext;
-                    const list = res.items.map(item => {
-                        item.spec.logo = this.$utils.checkAvatarUrl(item.spec?.logo)
-                        item.spec.groupName = this.findLinkGroupDisplayNameByGroupMetadataName(item.spec?.groupName)
-                        return item;
-                    })
-                    this.dataList = this.dataList.concat(list);
-                    setTimeout(() => {
-                        this.loading = 'success';
-                        this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
-                    }, 500);
-
-                })
-                .catch(err => {
-                    console.error(err);
-                    this.loading = 'error';
-                    this.loadMoreText = '加载失败,请下拉刷新!';
-                })
-                .finally(() => {
-                    setTimeout(() => {
-                        uni.hideLoading();
-                        uni.stopPullDownRefresh();
-                    }, 500);
-                });
-        },
-
-        handleGroup(list) {
-            const group = {}
-            list.forEach(item => {
-                if (group[item.spec.groupName]) {
-                    group[item.spec.groupName].children.push(item)
-                } else {
-                    group[item.spec.groupName] = {
-                        title: item.spec.groupName,
-                        children: [item]
-                    }
-                }
-            })
-
-            return Object.keys(group).map(key => {
-                const {
-                    title,
-                    children = []
-                } = group[key]
-                return {
-                    title,
-                    children
-                }
-            })
-        },
-        fnOnLinkEvent(link) {
-            this.detail.data = link;
-            this.detail.show = true;
-        },
-
-        fnCopyLink(link) {
-            uni.setClipboardData({
-                data: `${link.spec.displayName}:${link.spec.url}`,
-                showToast: false,
-                success: () => {
-                    uni.showToast({
-                        icon: 'none',
-                        title: '链接复制成功!'
-                    });
-                },
-                fail: () => {
-                    uni.showToast({
-                        icon: 'none',
-                        title: '复制失败!'
-                    });
-                }
-            });
-        },
-        toSubmitLinkPage() {
-            uni.navigateTo({
-                url: '/pagesA/submit-link/submit-link'
-            })
-        }
-    }
-};
+	import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
+	import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
+	import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
+	import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
+	import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
+	import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue';
+	import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
+
+	import pluginAvailable from "@/common/mixins/pluginAvailable.js"
+
+	export default {
+		mixins: [pluginAvailable],
+		components: {
+			tmSkeleton,
+			tmTranslate,
+			tmFlotbutton,
+			tmTags,
+			tmEmpty,
+			tmImages,
+			tmPoup
+		},
+		data() {
+			return {
+				loading: 'loading',
+				queryParams: {
+					size: 10,
+					page: 1
+				},
+				detail: {
+					show: false,
+					data: null
+				},
+				hasNext: false,
+				isLoadMore: false,
+				loadMoreText: '',
+				linkGroupList: [],
+				dataList: [],
+				colors: [
+					'#39B449',
+					'#E44C41',
+					'#8698A2',
+					'#0080FE',
+					'#1CBCB4',
+					'#6638B5'
+				]
+			};
+		},
+		computed: {
+			haloConfigs() {
+				return this.$tm.vx.getters().getConfigs;
+			},
+			haloPluginConfigs() {
+				return this.$tm.vx.getters().getConfigs.pluginConfig;
+			},
+			calcSiteThumbnail(val) {
+				return val => {
+					if (!val) return '';
+					if (val.charAt(val.length - 1) !== '/') {
+						val = val + '/';
+					}
+					return 'https://image.thum.io/get/width/1000/crop/800/' + val;
+				};
+			},
+			calcAuditModeEnabled() {
+				return this.haloConfigs.auditConfig.auditModeEnabled
+			},
+		},
+		async onLoad() {
+			this.fnSetPageTitle('友情链接');
+			// 检查插件
+			this.setPluginId(this.NeedPluginIds.PluginLinks)
+			this.setPluginError("阿偶,检测到当前插件没有安装或者启用,无法使用友情链接功能哦,请联系管理员")
+			if (!await this.checkPluginAvailable()) return
+			this.fnGetLinkGroupData();
+		},
+		onPullDownRefresh() {
+			if (!this.uniHaloPluginAvailable) return;
+			this.isLoadMore = false;
+			this.queryParams.page = 1;
+			this.dataList = []
+			this.fnGetData();
+		},
+		onReachBottom(e) {
+			if (!this.uniHaloPluginAvailable) return;
+			if (this.hasNext) {
+				this.queryParams.page += 1;
+				this.isLoadMore = true;
+				this.fnGetData();
+			} else {
+				uni.showToast({
+					icon: 'none',
+					title: '没有更多数据了'
+				});
+			}
+		},
+		methods: {
+			fnGetLinkGroupData() {
+				this.$httpApi.v2
+					.getFriendLinkGroupList({
+						page: 1,
+						size: 0
+					})
+					.then(res => {
+						this.linkGroupList = res.items;
+						this.fnGetData()
+					})
+					.catch(err => {
+						console.error(err);
+					});
+			},
+			findLinkGroupDisplayNameByGroupMetadataName(groupName) {
+				if (this.linkGroupList.length === 0) return groupName || "未分组"
+				return this.linkGroupList.find(item => item.metadata.name === groupName)?.spec?.displayName || groupName ||
+					"未分组"
+			},
+			fnGetData() {
+				if (this.calcAuditModeEnabled) {
+					return;
+				}
+				if (!this.isLoadMore) {
+					this.loading = 'loading';
+				}
+				this.loadMoreText = '';
+				this.$httpApi.v2
+					.getFriendLinkList(this.queryParams)
+					.then(res => {
+						console.log('请求结果:');
+						console.log(res);
+						this.hasNext = res.hasNext;
+						const list = res.items.map(item => {
+							item.spec.logo = this.$utils.checkAvatarUrl(item.spec?.logo)
+							item.spec.groupName = this.findLinkGroupDisplayNameByGroupMetadataName(item.spec
+								?.groupName)
+							return item;
+						})
+						this.dataList = this.dataList.concat(list);
+						setTimeout(() => {
+							this.loading = 'success';
+							this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
+						}, 500);
+
+					})
+					.catch(err => {
+						console.error(err);
+						this.loading = 'error';
+						this.loadMoreText = '加载失败,请下拉刷新!';
+					})
+					.finally(() => {
+						setTimeout(() => {
+							uni.hideLoading();
+							uni.stopPullDownRefresh();
+						}, 500);
+					});
+			},
+
+			handleGroup(list) {
+				const group = {}
+				list.forEach(item => {
+					if (group[item.spec.groupName]) {
+						group[item.spec.groupName].children.push(item)
+					} else {
+						group[item.spec.groupName] = {
+							title: item.spec.groupName,
+							children: [item]
+						}
+					}
+				})
+
+				return Object.keys(group).map(key => {
+					const {
+						title,
+						children = []
+					} = group[key]
+					return {
+						title,
+						children
+					}
+				})
+			},
+			fnOnLinkEvent(link) {
+				this.detail.data = link;
+				this.detail.show = true;
+			},
+
+			fnCopyLink(link) {
+				uni.setClipboardData({
+					data: `${link.spec.displayName}:${link.spec.url}`,
+					showToast: false,
+					success: () => {
+						uni.showToast({
+							icon: 'none',
+							title: '链接复制成功!'
+						});
+					},
+					fail: () => {
+						uni.showToast({
+							icon: 'none',
+							title: '复制失败!'
+						});
+					}
+				});
+			},
+			toSubmitLinkPage() {
+				uni.navigateTo({
+					url: '/pagesA/submit-link/submit-link'
+				})
+			}
+		}
+	};
 </script>
 
 <style lang="scss" scoped>
-.app-page {
-    width: 100vw;
-    min-height: 100vh;
-    display: flex;
-    flex-direction: column;
-    background-color: #fafafd;
-}
-
-.loading-wrap {
-    padding: 24rpx;
-    min-height: 100vh;
-}
-
-.content {
-    padding: 0 24rpx;
-    padding-top: 24rpx;
-
-    .content-empty {
-        height: 60vh;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-    }
-}
-
-.link-card {
-    border-bottom: 2rpx solid #f5f5f5;
-    background-color: #ffffff;
-
-    &.one {
-        border: 0;
-        box-shadow: 0rpx 2rpx 24rpx 0rpx rgba(0, 0, 0, 0.03);
-
-        .logo {
-            box-shadow: 0rpx 2rpx 12rpx rgba(0, 0, 0, 0.1);
-        }
-    }
-
-    .logo {
-        // width: 126rpx;
-        // height: 126rpx;
-        width: 80rpx;
-        height: 80rpx;
-        border-radius: 12rpx;
-        border: 6rpx solid #ffffff;
-        box-shadow: none;
-    }
-
-    .info {
-        width: 0;
-        flex-grow: 1;
-
-        .name {
-            white-space: nowrap;
-            overflow: hidden;
-            text-overflow: ellipsis;
-            color: #303133;
-            font-size: 30rpx;
-            font-weight: bold;
-        }
-
-        .desc {
-            white-space: nowrap;
-            overflow: hidden;
-            text-overflow: ellipsis;
-            color: #303133;
-            font-size: 28rpx;
-        }
-    }
-}
-
-.link-card_name {
-    // color: #303133;
-    // color: #0080fe;
-}
-
-.link-card_desc {
-    font-size: 24rpx;
-    line-height: 1.6;
-    color: #303133;
-}
-
-.link-logo {
-    width: 140rpx;
-    height: 140rpx;
-
-    &_img {
-        width: 100%;
-        height: 100%;
-    }
-}
-
-.poup-logo {
-    width: 140rpx;
-    height: 140rpx;
-    border-radius: 50%;
-
-    &_img {
-        width: 100%;
-        height: 100%;
-        border-radius: 50%;
-    }
-}
-
-.info-detail {
-    width: 0;
-    flex-grow: 1;
-    justify-content: center;
-}
-
-.poup-desc {
-    font-size: 28rpx;
-    line-height: 1.6;
-    color: #555 !important;
-}
-
-.preview-site {
-    width: 100%;
-    height: 300rpx;
-}
-</style>
+	.app-page {
+		width: 100vw;
+		min-height: 100vh;
+		display: flex;
+		flex-direction: column;
+		background-color: #fafafd;
+	}
+
+	.loading-wrap {
+		padding: 24rpx;
+		min-height: 100vh;
+	}
+
+	.content {
+		padding: 0 24rpx;
+		padding-top: 24rpx;
+
+		.content-empty {
+			height: 60vh;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+		}
+	}
+
+	.link-card {
+		border-bottom: 2rpx solid #f5f5f5;
+		background-color: #ffffff;
+
+		&.one {
+			border: 0;
+			box-shadow: 0rpx 2rpx 24rpx 0rpx rgba(0, 0, 0, 0.03);
+
+			.logo {
+				box-shadow: 0rpx 2rpx 12rpx rgba(0, 0, 0, 0.1);
+			}
+		}
+
+		.logo {
+			// width: 126rpx;
+			// height: 126rpx;
+			width: 80rpx;
+			height: 80rpx;
+			border-radius: 12rpx;
+			border: 6rpx solid #ffffff;
+			box-shadow: none;
+		}
+
+		.info {
+			width: 0;
+			flex-grow: 1;
+
+			.name {
+				white-space: nowrap;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				color: #303133;
+				font-size: 30rpx;
+				font-weight: bold;
+			}
+
+			.desc {
+				white-space: nowrap;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				color: #303133;
+				font-size: 28rpx;
+			}
+		}
+	}
+
+	.link-card_name {
+		// color: #303133;
+		// color: #0080fe;
+	}
+
+	.link-card_desc {
+		font-size: 24rpx;
+		line-height: 1.6;
+		color: #303133;
+	}
+
+	.link-logo {
+		width: 140rpx;
+		height: 140rpx;
+
+		&_img {
+			width: 100%;
+			height: 100%;
+		}
+	}
+
+	.poup-logo {
+		width: 140rpx;
+		height: 140rpx;
+		border-radius: 50%;
+
+		&_img {
+			width: 100%;
+			height: 100%;
+			border-radius: 50%;
+		}
+	}
+
+	.info-detail {
+		width: 0;
+		flex-grow: 1;
+		justify-content: center;
+	}
+
+	.poup-desc {
+		font-size: 28rpx;
+		line-height: 1.6;
+		color: #555 !important;
+	}
+
+	.preview-site {
+		width: 100%;
+		height: 300rpx;
+	}
+</style>

+ 94 - 0
utils/plugin.js

@@ -0,0 +1,94 @@
+import utils from '@/utils/index.js'
+import v2Apis from "@/api/v2/all.api.js"
+
+export const NeedPluginIds = Object.freeze({
+	PluginUniHalo: "plugin-uni-halo",
+	PluginPhotos: "PluginPhotos",
+	PluginLinks: "PluginLinks",
+	PluginMoments: "PluginMoments",
+	PluginSearchWidget: "PluginSearchWidget",
+	PluginCommentWidget: "PluginCommentWidget",
+	PluginVote: "vote",
+})
+
+export const NeedPlugins = new Map([
+	[
+		NeedPluginIds.PluginUniHalo, {
+			id: "plugin-uni-halo",
+			name: "UniHalo配置",
+			desc: "uni-halo 核心插件,未安装和启用的情况下,将无法使用 uni-halo,请检查是否已安装和启用。",
+			logo: utils.checkUrl("/plugins/plugin-uni-halo/assets/logo.png"),
+			url: "https://www.halo.run/store/apps/app-ryemX"
+		}
+	],
+	[
+		NeedPluginIds.PluginPhotos, {
+			id: "PluginPhotos",
+			name: "图库管理",
+			desc: "图库功能模块所需要的插件,用于展示",
+			logo: utils.checkUrl("/plugins/PluginPhotos/assets/logo.svg"),
+			url: "https://www.halo.run/store/apps/app-BmQJW"
+		}
+	],
+	[
+		NeedPluginIds.PluginLinks, {
+			id: "PluginLinks",
+			name: "链接管理",
+			desc: "链接管理模块,用于网站友情链接功能模块。",
+			logo: utils.checkUrl("/plugins/PluginLinks/assets/logo.svg"),
+			url: "https://www.halo.run/store/apps/app-hfbQg"
+		}
+	],
+	[
+		NeedPluginIds.PluginMoments, {
+			id: "PluginMoments",
+			name: "瞬间",
+			desc: "提供一个轻量级的内容图文、视频、音频等内容展示。",
+			logo: utils.checkUrl("/plugins/PluginMoments/assets/logo.svg"),
+			url: "https://www.halo.run/store/apps/app-SnwWD"
+		}
+	],
+	[
+		NeedPluginIds.PluginSearchWidget, {
+			id: "PluginSearchWidget",
+			name: "搜索组件",
+			desc: "为应用提供统一的搜索组件。",
+			logo: utils.checkUrl("/plugins/PluginSearchWidget/assets/logo.svg"),
+			url: "https://www.halo.run/store/apps/app-DlacW"
+		}
+	],
+	[
+		NeedPluginIds.PluginCommentWidget, {
+			id: "PluginCommentWidget",
+			name: "评论组件",
+			desc: "为用户前台提供完整的评论解决方案",
+			logo: utils.checkUrl("/plugins/PluginCommentWidget/assets/logo.svg"),
+			url: "https://www.halo.run/store/apps/app-YXyaD"
+		}
+	],
+	[
+		NeedPluginIds.PluginVote, {
+			id: "vote",
+			name: "投票管理",
+			desc: "投票模块所需要的插件,用于展示投票和提交投票",
+			logo: utils.checkUrl("/plugins/vote/assets/logo.png"),
+			url: "https://www.halo.run/store/apps/app-veyvzyhv"
+		}
+	]
+])
+
+/**
+ * 检查插件是否启用、安装
+ * @param {String} pluginId 插件id
+ * @return {Boolean} true = 安装、启用  false= 未安装启用
+ */
+export const checkNeedPluginAvailable = (pluginId) => {
+	return new Promise(async (resolve) => {
+		try {
+			const available = await v2Apis.checkPluginAvailable(pluginId)
+			resolve(available)
+		} catch (err) {
+			resolve(false)
+		}
+	})
+}