diff --git a/App.vue b/App.vue
index b7dbcde..7710b6a 100644
--- a/App.vue
+++ b/App.vue
@@ -20,7 +20,11 @@ export default {
// #endif
uni.$tm.vx.actions('config/fetchConfigs').then((res) => {
+ console.log('fetchConfigs', res);
})
+ uni.$tm.vx.actions('config/fetchHaloConfigs').then((res) => {
+ console.log('fetchHaloConfigs', res);
+ })
// 启动检查app的配置是否已经就绪,若未就绪则设置默认的
uni.$tm.vx.actions('setting/checkAndSetDefaultAppSettings');
},
diff --git a/api/v2/all.api.js b/api/v2/all.api.js
index 1cdf1d8..ab184b9 100644
--- a/api/v2/all.api.js
+++ b/api/v2/all.api.js
@@ -3,12 +3,14 @@ import {
} from '@/utils/token.js'
import HttpHandler from '@/common/http/request.js'
import qs from 'qs'
-
+import { getCache } from "@/utils/storage";
+import { getAppConfigs } from '@/config/index.js'
import {
- getAppConfigs
-} from '@/config/index.js'
-import { getNologinEmail, getOpenid } from "@/utils/auth";
-
+ getNologinEmail,
+ getOpenid
+} from "@/utils/auth";
+import { CommentWidgetCaptchaCookies } from '@/common/constant/cookies.js'
+
export default {
getPostList: (params) => {
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/posts`, params)
@@ -52,11 +54,27 @@ export default {
},
addPostComment: (data) => {
- return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/comments`, data)
+ const captchaCode = data.captchaCode;
+ delete data.captchaCode;
+ return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/comments`, data, {
+ header: {
+ "Accept": "application/json",
+ "X-Captcha-Code": captchaCode,
+ "Cookie": getCache(CommentWidgetCaptchaCookies) ?? undefined
+ }
+ })
},
addPostCommentReply: (commentName, data) => {
- return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/comments/${commentName}/reply`, data)
+ const captchaCode = data.captchaCode;
+ delete data.captchaCode;
+ return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/comments/${commentName}/reply`, data, {
+ header: {
+ "Accept": "application/json",
+ "X-Captcha-Code": captchaCode,
+ "Cookie": getCache(CommentWidgetCaptchaCookies) ?? undefined
+ }
+ })
},
getTagList: (params) => {
@@ -68,19 +86,11 @@ export default {
},
getMomentList: (params) => {
- return HttpHandler.Get(`/apis/api.moment.halo.run/v1alpha1/moments`, params, {
- custom: {
- personalToken: getPersonalToken()
- }
- })
+ return HttpHandler.Get(`/apis/api.moment.halo.run/v1alpha1/moments`, params)
},
getMomentByName: (name) => {
- return HttpHandler.Get(`/apis/api.moment.halo.run/v1alpha1/moments/${name}`, {}, {
- custom: {
- personalToken: getPersonalToken()
- }
- })
+ return HttpHandler.Get(`/apis/api.moment.halo.run/v1alpha1/moments/${name}`, {})
},
getBlogStatistics: () => {
@@ -176,10 +186,17 @@ export default {
},
getDoubanDetail: (url) => {
- return HttpHandler.Get(`/apis/api.douban.moony.la/v1alpha1/doubanmovies/-/getDoubanDetail`, { url })
+ return HttpHandler.Get(`/apis/api.douban.moony.la/v1alpha1/doubanmovies/-/getDoubanDetail`, {
+ url
+ })
},
-
+
postTrackersCounter: (data) => {
return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/trackers/counter`, data)
},
+
+ /** 获取评论验证码 */
+ getCommentWidgetCaptcha: () => {
+ return HttpHandler.Get(`/apis/api.commentwidget.halo.run/v1alpha1/captcha/-/generate`, {})
+ }
}
\ No newline at end of file
diff --git a/api/v2/all.config.js b/api/v2/all.config.js
index 15df665..2ad74e3 100644
--- a/api/v2/all.config.js
+++ b/api/v2/all.config.js
@@ -7,4 +7,14 @@ export default {
getAppConfigs: () => {
return HttpHandler.Get(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getConfigs`, {})
},
+
+ /** 获取全局配置信息 */
+ getHaloGlobalInfo: () => {
+ return HttpHandler.Get(`/actuator/globalinfo`, {})
+ },
+
+ /** 获取评论组件配置 */
+ getCommentWidgetConfig: () => {
+ return HttpHandler.Get(`/apis/api.commentwidget.halo.run/v1alpha1/config`, {})
+ },
}
diff --git a/common/constant/cookies.js b/common/constant/cookies.js
new file mode 100644
index 0000000..b84ff05
--- /dev/null
+++ b/common/constant/cookies.js
@@ -0,0 +1,2 @@
+/** 评论验证码 */
+export const CommentWidgetCaptchaCookies = 'comment-widget-captcha';
\ No newline at end of file
diff --git a/common/http/index.js b/common/http/index.js
index 3624813..22f0ab2 100644
--- a/common/http/index.js
+++ b/common/http/index.js
@@ -16,7 +16,9 @@ import {
} from "./interceptors.js";
import Request from "@/js_sdk/luch-request/luch-request";
-const http = new Request()
+const http = new Request({
+ withCredentials: true, // 是否携带cookie
+})
/* 设置全局配置 */
http.setConfig((config) => {
diff --git a/common/http/interceptors.js b/common/http/interceptors.js
index 0f50ce5..7258d98 100644
--- a/common/http/interceptors.js
+++ b/common/http/interceptors.js
@@ -9,133 +9,169 @@
* 修改人员:
* 修改时间:
*/
-import {delCache, setCache} from "@/utils/storage";
+import {
+ delCache,
+ setCache
+} from "@/utils/storage";
+import {
+ CommentWidgetCaptchaCookies
+} from '@/common/constant/cookies.js'
+import {
+ extractCookieItem,
+ getHeaderCaseInsensitive
+} from '@/utils/cookies.js'
const getCategoryNameByUrl = (url) => {
- const reg = '(?<=/api/content/categories/).+(?=/posts)'
- return url.match(reg)[0] || '无分类名'
+ const reg = '(?<=/api/content/categories/).+(?=/posts)'
+ return url.match(reg)[0] || '无分类名'
}
+
const showCategoryInputPasswordModal = (response, category) => {
- uni.showModal({
- title: `[ ${category} ] 分类已加密`, // TODO 这里应该获取分类的名字,可以在弹窗之前请求后台拿到所有分类根据分类code拿到名称,但是不会在这之前发送请求
- content: '',
- editable: true,
- placeholderText: '请输入分类密码后访问',
- confirmText: '验证密码',
- cancelText: '暂不访问',
- showCancel: true,
- cancelColor: '#999999',
- confirmColor: '#03a9f4',
- success: (res) => {
- if (res.confirm) {
- // TODO 这里如果没有输入密码点击确认应该阻止窗口关闭,但是没找到方法
- if (!res.content) {
- uni.showToast({
- title: '提示:请输入密码', icon: 'none', success: () => {
- setTimeout(() => {
- showCategoryInputPasswordModal(response, category);
- }, 800)
- }
- })
- return;
- }
- // 根据请求URL正则匹配分类code,然后把输入的密码根据分类code放入缓存,然后在category.getCategoryPostList中获取,解决多个分类加密输入密码后点错的问题
- // 目前存在一个问题,比如前两个都需要密码,如果先输入第二个的密码之后,重新进来默认打开第一个还会弹窗,所以想在弹窗标题上增加分类名字
- // 另外有以下两种方式科技解决
- // TODO 1.其实这里获取到密码之后可以直接发送一个请求追加上password参数,因为后台会缓存权限,后续不输入密码也可以访问,可惜不会
- // TODO 2.另外也可以拿到密码之后,直接选中该分类追加password参数,重新请求,可惜也不会
- setCache('APP_CATEGORY_PWD_' + category, res.content)
- uni.reLaunch({
- url: '/pages/tabbar/category/category'
- });
- } else if (res.cancel) {
- }
- },
- })
+ uni.showModal({
+ title: `[ ${category} ] 分类已加密`, // TODO 这里应该获取分类的名字,可以在弹窗之前请求后台拿到所有分类根据分类code拿到名称,但是不会在这之前发送请求
+ content: '',
+ editable: true,
+ placeholderText: '请输入分类密码后访问',
+ confirmText: '验证密码',
+ cancelText: '暂不访问',
+ showCancel: true,
+ cancelColor: '#999999',
+ confirmColor: '#03a9f4',
+ success: (res) => {
+ if (res.confirm) {
+ // TODO 这里如果没有输入密码点击确认应该阻止窗口关闭,但是没找到方法
+ if (!res.content) {
+ uni.showToast({
+ title: '提示:请输入密码',
+ icon: 'none',
+ success: () => {
+ setTimeout(() => {
+ showCategoryInputPasswordModal(response, category);
+ }, 800)
+ }
+ })
+ return;
+ }
+ // 根据请求URL正则匹配分类code,然后把输入的密码根据分类code放入缓存,然后在category.getCategoryPostList中获取,解决多个分类加密输入密码后点错的问题
+ // 目前存在一个问题,比如前两个都需要密码,如果先输入第二个的密码之后,重新进来默认打开第一个还会弹窗,所以想在弹窗标题上增加分类名字
+ // 另外有以下两种方式科技解决
+ // TODO 1.其实这里获取到密码之后可以直接发送一个请求追加上password参数,因为后台会缓存权限,后续不输入密码也可以访问,可惜不会
+ // TODO 2.另外也可以拿到密码之后,直接选中该分类追加password参数,重新请求,可惜也不会
+ setCache('APP_CATEGORY_PWD_' + category, res.content)
+ uni.reLaunch({
+ url: '/pages/tabbar/category/category'
+ });
+ } else if (res.cancel) {}
+ },
+ })
+}
+
+const saveCookies = (response) => {
+ const cookies = getHeaderCaseInsensitive(response.header, 'set-cookie');
+
+ // app/小程序将 cookies 存储起来
+ // #ifdef MP-WEIXIN || APP-PLUS
+ // 存储评论验证码cookies
+ const MatchCommentApis = [
+ '/apis/api.commentwidget.halo.run/v1alpha1/captcha/-/generate',
+ '/apis/api.halo.run/v1alpha1/comments'
+ ]
+ if (MatchCommentApis.includes(response.config.url)) {
+ setCache(CommentWidgetCaptchaCookies, extractCookieItem(cookies, CommentWidgetCaptchaCookies))
+ }
+ // #endif
}
export const setInterceptors = (http) => {
- http.interceptors.request.use((config) => {
- console.log("config", config)
+ http.interceptors.request.use((config) => {
+ console.log("config", config)
- // 可使用async await 做异步操作
- config.header = {
- ...config.header
- // ... 可以直接加参数
- };
- if (config.custom.personalToken) {
- config.header['Authorization'] = `Bearer ${config.custom.personalToken}`
- }
- return config;
- }, (config) => {
- // 可使用async await 做异步操作
- return Promise.reject(config);
- });
+ // 可使用async await 做异步操作
+ config.header = {
+ ...config.header
+ // ... 可以直接加参数
+ };
+ if (config.custom.personalToken) {
+ config.header['Authorization'] = `Bearer ${config.custom.personalToken}`
+ }
+ return config;
+ }, (config) => {
+ // 可使用async await 做异步操作
+ return Promise.reject(config);
+ });
- http.interceptors.response.use((response) => {
- /* 对响应成功做点什么 可使用async await 做异步操作*/
- // if (response.data.code !== 200) { // 服务端返回的状态码不等于200,则reject()
- // return Promise.reject(response) // return Promise.reject 可使promise状态进入catch
- // if (response.config.custom.verification) { // 演示自定义参数的作用
- // return response.data
- // }
+ http.interceptors.response.use((response) => {
+ /* 对响应成功做点什么 可使用async await 做异步操作*/
+ // if (response.data.code !== 200) { // 服务端返回的状态码不等于200,则reject()
+ // return Promise.reject(response) // return Promise.reject 可使promise状态进入catch
+ // if (response.config.custom.verification) { // 演示自定义参数的作用
+ // return response.data
+ // }
- if (response.statusCode == 200) {
- return response.data;
- } else {
- return Promise.reject(response);
- }
- }, (response) => {
- /* 对响应错误做点什么 (statusCode !== 200)*/
- if (!response.data) {
- return Promise.reject({
- status: 500, message: 'API接口服务异常!'
- })
- } else if (response.data.status == 401) {
- uni.$tm.toast(response.data.message);
- // 如果是请求分类之后报401说明密码错误,那么清除该密码,下次点击会报403弹窗再次输入密码
- if (response.config.url.indexOf('/api/content/categories') >= 0) {
- const category = getCategoryNameByUrl(response.config.url)
- delCache('APP_CATEGORY_PWD_' + category);
- uni.showToast({
- title: '提示:密码不正确', icon: 'none', success: () => {
- setTimeout(() => {
- showCategoryInputPasswordModal(response, category);
- }, 800)
- }
- })
- } else {
- // 其他情况维持原来的逻辑
- delCache('APP_ADMIN_LOGIN_TOKEN');
- uni.$eShowModal({
- title: '提示',
- content: '您未登录超管账号或登录已过期,是否重新登录?',
- showCancel: true,
- cancelText: '否',
- cancelColor: '#999999',
- confirmText: '是',
- confirmColor: '#03a9f4'
- }).then(res => {
- uni.navigateTo({
- url: '/pagesB/login/login'
- })
- }).catch(err => {
- uni.switchTab({
- url: '/pages/tabbar/about/about'
- })
- })
- }
- return Promise.reject(response.data);
- } else if (response.data.status == 403) {
- // 如果报403是请求分类文章接口(您没有该分类的访问权限)的话说明是私密分类,需要输入密码请求
- if (response.config.url.indexOf('/api/content/categories') >= 0) {
- const category = getCategoryNameByUrl(response.config.url);
- showCategoryInputPasswordModal(response, category);
- }
- return Promise.reject(response.data);
- } else {
- return Promise.reject(response.data);
- }
+ saveCookies(response);
- });
-};
+ if (response.statusCode == 200) {
+ return response.data;
+ } else {
+ return Promise.reject(response);
+ }
+ }, (response) => {
+
+ saveCookies(response);
+
+ /* 对响应错误做点什么 (statusCode !== 200)*/
+ if (!response.data) {
+ return Promise.reject({
+ status: 500,
+ message: 'API接口服务异常!'
+ })
+ } else if (response.data.status == 401) {
+ uni.$tm.toast(response.data.message);
+ // 如果是请求分类之后报401说明密码错误,那么清除该密码,下次点击会报403弹窗再次输入密码
+ if (response.config.url.indexOf('/api/content/categories') >= 0) {
+ const category = getCategoryNameByUrl(response.config.url)
+ delCache('APP_CATEGORY_PWD_' + category);
+ uni.showToast({
+ title: '提示:密码不正确',
+ icon: 'none',
+ success: () => {
+ setTimeout(() => {
+ showCategoryInputPasswordModal(response, category);
+ }, 800)
+ }
+ })
+ } else {
+ // 其他情况维持原来的逻辑
+ delCache('APP_ADMIN_LOGIN_TOKEN');
+ uni.$eShowModal({
+ title: '提示',
+ content: '您未登录超管账号或登录已过期,是否重新登录?',
+ showCancel: true,
+ cancelText: '否',
+ cancelColor: '#999999',
+ confirmText: '是',
+ confirmColor: '#03a9f4'
+ }).then(res => {
+ uni.navigateTo({
+ url: '/pagesB/login/login'
+ })
+ }).catch(err => {
+ uni.switchTab({
+ url: '/pages/tabbar/about/about'
+ })
+ })
+ }
+ return Promise.reject(response.data);
+ } else if (response.data.status == 403) {
+ // 如果报403是请求分类文章接口(您没有该分类的访问权限)的话说明是私密分类,需要输入密码请求
+ if (response.config.url.indexOf('/api/content/categories') >= 0) {
+ const category = getCategoryNameByUrl(response.config.url);
+ showCategoryInputPasswordModal(response, category);
+ }
+ return Promise.reject(response.data);
+ } else {
+ return Promise.reject(response.data);
+ }
+
+ });
+};
\ No newline at end of file
diff --git a/components/comment-item/comment-item.vue b/components/comment-item/comment-item.vue
index d550b1c..3129117 100644
--- a/components/comment-item/comment-item.vue
+++ b/components/comment-item/comment-item.vue
@@ -91,9 +91,6 @@
}
}
}
- },
- created() {
- console.log("comment", this.comment)
}
};
diff --git a/components/comment-modal/comment-modal.vue b/components/comment-modal/comment-modal.vue
index 40ce546..1323892 100644
--- a/components/comment-modal/comment-modal.vue
+++ b/components/comment-modal/comment-modal.vue
@@ -1,233 +1,341 @@
-
- {{ calcTitle }}
-
-
-
-
-
-
-
- 提交
-
-
-
-
+
+ {{ calcTitle }}
+
+
+
+
+
+
+
+
+
+
+ 获取中...
+
+ 请重试
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+ .title {
+ margin: 24rpx 0;
+ }
+
+ .captch-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 200rpx;
+ height: 68rpx;
+ }
+
\ No newline at end of file
diff --git a/config/halo.config.js b/config/halo.config.js
new file mode 100644
index 0000000..a841129
--- /dev/null
+++ b/config/halo.config.js
@@ -0,0 +1,30 @@
+// 应用设置存储key值
+export const _HaloConfigKey = 'HALO_GLOBAL_CONFIGS';
+
+// 默认的应用设置
+export const DefaultHaloGlobalConfigs = {
+ allowAnonymousComments: true,
+ allowRegistration: true,
+ mustVerifyEmailOnRegistration: true,
+ mustVerifyPhoneOnRegistration: false,
+ restrictRegistrationDomain: false,
+ shopDisabled: true,
+}
+
+/**
+ * 获取应用设置
+ */
+export const getHaloGlobalConfigs = () => {
+ let configs = uni.getStorageSync(_HaloConfigKey)
+ if (configs) return JSON.parse(configs)
+
+ uni.setStorageSync(_HaloConfigKey, JSON.stringify(DefaultHaloGlobalConfigs))
+ return DefaultHaloConfigs;
+}
+
+/**
+ * 保存应用设置
+ */
+export const setHaloGlobalConfigs = (configs) => {
+ uni.setStorageSync(_HaloConfigKey, JSON.stringify(configs))
+}
\ No newline at end of file
diff --git a/store/config.js b/store/config.js
index 303f7df..2429a9c 100644
--- a/store/config.js
+++ b/store/config.js
@@ -6,6 +6,7 @@
* 版本:v0.1.0
*/
import {DefaultAppConfigs, getAppConfigs, getAppMockJson, setAppConfigs, setAppMockJson} from '@/config/index.js'
+import {DefaultHaloGlobalConfigs, getHaloGlobalConfigs, setHaloGlobalConfigs} from '@/config/halo.config.js'
import v2Config from '@/api/v2/all.config.js'
import utils from '@/utils/index.js'
import {setTokens} from "@/utils/token";
@@ -13,10 +14,15 @@ import {setTokens} from "@/utils/token";
export default {
namespaced: true,
state: {
+ haloConfig: getHaloGlobalConfigs(),
configs: getAppConfigs(),
mockJson: getAppMockJson(),
},
getters: {
+ getHaloConfig(state) {
+ if (state.haloConfig) return state.haloConfig;
+ return getHaloGlobalConfigs();
+ },
getConfigs(state) {
if (state.configs) return state.configs;
return getAppConfigs()
@@ -27,6 +33,10 @@ export default {
}
},
mutations: {
+ setHaloConfigs(state, data) {
+ state.haloConfig = data;
+ setHaloGlobalConfigs(data);
+ },
setConfigs(state, data) {
state.configs = data;
setAppConfigs(data)
@@ -37,6 +47,23 @@ export default {
}
},
actions: {
+ fetchHaloConfigs({state, commit, dispatch}) {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const res = await v2Config.getHaloGlobalInfo()
+ if (res) {
+ commit('setHaloConfigs', utils.deepMerge(DefaultHaloGlobalConfigs, res))
+ resolve(res)
+ } else {
+ dispatch("setDefaultHaloGlobalConfigs");
+ reject()
+ }
+ } catch (e) {
+ dispatch("setDefaultHaloGlobalConfigs");
+ reject()
+ }
+ })
+ },
fetchConfigs({state, commit, dispatch}) {
return new Promise(async (resolve, reject) => {
try {
@@ -59,6 +86,10 @@ export default {
}
})
},
+ // 设置默认的数据
+ setDefaultHaloGlobalConfigs({commit}) {
+ commit('setHaloConfigs', JSON.parse(JSON.stringify(DefaultHaloGlobalConfigs)))
+ },
// 设置默认的数据
setDefaultAppSettings({commit}) {
commit('setConfigs', JSON.parse(JSON.stringify(DefaultAppConfigs)))
diff --git a/utils/cookies.js b/utils/cookies.js
new file mode 100644
index 0000000..42ca9b2
--- /dev/null
+++ b/utils/cookies.js
@@ -0,0 +1,32 @@
+/**
+ * 从带换行的cookie原始串提取某一条cookie(带;结尾)
+ * @param {string} cookieRaw set‑cookie原始字符串
+ * @param {string} cookieKey cookie名称,例如 "comment-widget-captcha"
+ * @returns {string} 清理换行后的cookie片段,没匹配返回''
+ */
+export function extractCookieItem(cookieRaw, cookieKey) {
+ if (!cookieRaw || !cookieKey) return ''
+
+ // 转义正则特殊字符
+ function escapeRegExp(str) {
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ }
+
+ const keyEscaped = escapeRegExp(cookieKey)
+ const reg = new RegExp(`${keyEscaped}=[\\s\\S]*?;`)
+ const m = cookieRaw.match(reg)
+ return m ? m[0].replace(/\r?\n/g, '') : ''
+}
+
+/**
+ * 获取header中指定key的值,忽略大小写
+ * @param {object} headers HTTP header对象
+ * @param {string} name HTTP header键名
+ * @returns {string} HTTP header值,没找到返回''
+ */
+export function getHeaderCaseInsensitive(headers, name) {
+ if (!headers || typeof headers !== 'object') return '';
+ const lowerName = name.toLowerCase();
+ const key = Object.keys(headers).find(k => k.toLowerCase() === lowerName);
+ return key ? headers[key] : '';
+}
\ No newline at end of file