1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-07-28 05:00:41 +08:00

117 Commits

Author SHA1 Message Date
小莫唐尼 7c15c84dba update: 调整获取模拟参数请求策略 2024-10-28 18:38:29 +08:00
小莫唐尼 e8edc03526 update: 修改我的页面文章归档的显示策略 2024-10-28 18:28:56 +08:00
小莫唐尼 2b717ad449 update: 修改模拟数据请求地址为动态配置 2024-10-28 18:25:40 +08:00
小莫唐尼 5efc877a89 feat: 添加模拟数据模板 2024-10-28 18:00:19 +08:00
小莫唐尼 75a133415a fix: 修复README内容链接丢失 2024-10-28 17:57:37 +08:00
小莫唐尼 bf41b08b35 update: 优化审核模式,新增审核使用模拟数据 2024-10-28 17:53:43 +08:00
小莫唐尼 74a5efb596 Merge branch 'main' of https://github.com/ialley-workshop-open/uni-halo 2024-10-22 18:26:29 +08:00
小莫唐尼 40dd4f4b7f update: 调整审核模式隐藏部分内容,修复分类显示控制不生效 2024-10-22 18:19:23 +08:00
小莫唐尼 341670adcf fix: 修复恋爱日记图标丢失问题 2024-10-22 18:06:34 +08:00
小莫唐尼 115aea2580 fix: 修复文章分享功能 2024-10-22 17:03:06 +08:00
小莫唐尼 58f1e7f131 fix: 修复文章分享功能 2024-10-22 16:30:30 +08:00
小莫唐尼 26ce84b950 fix: 修复瞬间视频不显示bug 2024-10-17 18:49:28 +08:00
小莫唐尼 32b3bbaa74 fix: 修复文章归档 2024-10-17 18:07:15 +08:00
小莫唐尼 76c00f31f6 update: 恋爱配置导航内图标支持后台配置 2024-10-17 17:29:15 +08:00
小莫唐尼 e28ea6ca08 fix: 修复文章归档功能 2024-10-17 17:19:39 +08:00
小莫唐尼 a98254e6cc fix: 修复编译到app时,启动页按钮样式丢失问题 2024-10-17 11:28:13 +08:00
小莫唐尼 82062d9465 fix: 修复首页轮播图不显示问题 2024-08-27 20:41:37 +08:00
小莫唐尼 706cee2240 fix: 修复图库瀑布流模式无数据下,切换其他分类有数据不显示数据bug 2024-08-18 20:41:03 +08:00
liuyiwuqing da60eb2daf 1. 新增小程序审核模式 2024-08-05 19:23:16 +08:00
liuyiwuqing a764c3dbfe Merge remote-tracking branch 'origin/v2.0-beta' into v2.0-beta 2024-08-05 19:17:50 +08:00
小莫唐尼 88fb74a795 fix: 修复Halo最新版本搜索接口字段不存在问题 2024-08-05 19:04:55 +08:00
liuyiwuqing 52d4b7927b 1. 优化密码访问,实现联动插件动态启用关闭;
2. 文章海报分享支持动态小程序码;
3. 支持扫描文章海报跳转指定文章详情;
2024-08-02 15:28:50 +08:00
小莫唐尼 ff31e0d8e8 feat: 首页左上角图标支持跳转到关于页面 2024-07-16 11:20:14 +08:00
小莫唐尼 a65ea2617b update: 调整部分显示的内容,如文章相关文字进行替换 2024-07-16 11:13:52 +08:00
小莫唐尼 bb228c6705 fix: 修复首页分页失败问题,重新添加node依赖 2024-07-16 10:54:55 +08:00
小莫唐尼 62992b2688 fix: 修复loading图片地址异常问题 2024-07-15 18:40:27 +08:00
小莫唐尼 da141ae10a update: README.md 2024-07-15 09:09:17 +08:00
小莫唐尼 d22224e2fc update: 移除node_modules依赖 2024-07-11 11:28:27 +08:00
小莫唐尼 d0b3a42c1d fix: 修复启动页面图片背景不生效问题、取消logo阴影 2024-07-10 23:00:00 +08:00
小莫唐尼 c3349ae070 fix: 修复启动页面视频背景下文字遮挡问题、新增更多参数配置 2024-07-10 17:17:24 +08:00
小莫唐尼 ad6719e201 feat: 启动页面支持视频背景 2024-07-10 11:20:25 +08:00
小莫唐尼 0e342d5fa3 fix: 修复恋爱相册无封面取值问题 2024-07-09 16:11:28 +08:00
小莫唐尼 daf1318a90 update: 更新README文档 2024-07-05 19:04:44 +08:00
小莫唐尼 d4ccc9b7d6 fix: 修复图库瀑布流bug 2024-07-05 17:41:39 +08:00
小莫唐尼 ce60c334ec fix: 修复图库配置不生效问题,取消默认配置 2024-07-04 11:00:25 +08:00
小莫唐尼 ca723bae80 update: 增加图库可进行配置是否使用瀑布流还是列表、调整文章详情和关于页面图片配置 2024-07-04 10:42:32 +08:00
小莫唐尼 c312fab4b1 fix: 修复图库图片下拉和加载更多的时候清空且随机显示问题 2024-07-03 18:31:43 +08:00
小莫唐尼 d9db03959a update: 调整联系博页面,可在后台配置显示隐藏 2024-07-02 18:04:18 +08:00
小莫唐尼 add84f6e4b update: 文章详情广告字段调整 2024-07-02 17:43:18 +08:00
小莫唐尼 850ee73fa2 feat: 图库支持瀑布流 2024-07-02 15:40:43 +08:00
小莫唐尼 77da85f7d4 fix: 修复文章评论回复功能 2024-06-27 00:21:26 +08:00
小莫唐尼 4d0901b650 update: 删除分享配置,使用微信默认的分享机制 2024-06-26 23:41:00 +08:00
小莫唐尼 66a39dd723 fix: 修复文章海报摘要过长,并且显示html标签的问题 2024-06-26 15:52:17 +08:00
小莫唐尼 4cdcbb1958 fix: 修复默认进入文章详情页面自动弹出键盘问题 2024-06-26 14:56:38 +08:00
小莫唐尼 e0569e287f fix: 修复分享混入代码错误问题 2024-06-26 14:43:48 +08:00
小莫唐尼 b42932ddad fix: 修复分享图片失效问题 2024-06-26 11:54:25 +08:00
小莫唐尼 cc05b64dec update: 调整首页顶部头像为应用LOGO、调整启动页面,增加是否每次都显示判断 2024-06-26 09:47:04 +08:00
小莫唐尼 aec54e8c5b fix: 修复友情链接复制问题 2024-06-26 09:19:54 +08:00
小莫唐尼 15e95a2f3e update: 取消应用设置入口 2024-06-26 00:33:07 +08:00
小莫唐尼 74e5c9c3cf fix: 友链分组名称 2024-06-26 00:32:19 +08:00
小莫唐尼 85227a8aa4 refactor: 重构评论功能,评论页面改为弹窗方式 2024-06-26 00:19:35 +08:00
小莫唐尼 e7a00c5b69 update: 调整友情链接页面,显示分组、显示弹窗详情 2024-06-25 19:15:42 +08:00
小莫唐尼 85e558f1a0 fix: 修复文章详情评论区按钮显示隐藏问题 2024-06-25 12:19:21 +08:00
小莫唐尼 fade65397d fix: 修复启动页面无法正常启动问题 2024-06-25 12:10:53 +08:00
小莫唐尼 a2bd2cc8ba fix: 修复免责声明页面显示异常问题 2024-06-25 12:07:29 +08:00
小莫唐尼 bdaeb98432 fix: 修复关于页面背景图不显示和免责声明页面内容不显示问题 2024-06-25 11:26:10 +08:00
小莫唐尼 ccefbcee02 fix: 修复关于页面图片配置路径问题 2024-06-25 10:52:36 +08:00
小莫唐尼 e5590c0a22 fix: 修复启动页面图片配置路径问题 2024-06-25 10:50:20 +08:00
小莫唐尼 a85131308c update: 修改说明文档 2024-06-24 19:46:06 +08:00
小莫唐尼 e17edade28 refactor: 将页面配置通过插件动态化配置(plugin-uni-halo) 2024-06-24 18:59:05 +08:00
liuyiwuqing 959acab9c7 1. 修复文章密码访问失效问题 2024-06-15 18:28:58 +08:00
liuyiwuqing 0b59f69fa0 1. 提取tools工具箱插件配置 2024-06-15 17:33:06 +08:00
liuyiwuqing ab755a8623 1. 文章验证码相关接口更新;
2. 友链提交相关接口更新;
2024-06-09 15:33:26 +08:00
小莫唐尼 a67ad88c3f fix: 修复评论头像字段错误问题 2024-06-04 17:42:51 +08:00
小莫唐尼 6684f80f02 update: 调整插件配置为全局混入 2024-06-03 10:28:00 +08:00
小莫唐尼 620354b77a update: 优化友链提交配置 2024-06-02 19:38:27 +08:00
小莫唐尼 19c4e12a5b update: 删除后台管理相关页面代码 2024-06-02 18:46:36 +08:00
liuyiwuqing 3b0ef59ca7 新增友链提交功能 2024-05-31 18:47:19 +08:00
liuyiwuqing 0386c66b70 修复广告id变量引用问题 2024-05-28 17:34:34 +08:00
小莫唐尼 9721932c34 update: 优化代码格式 2024-05-27 23:10:23 +08:00
小莫唐尼 f9fce7d0ed !16 新增文章访问验证码功能
Merge pull request !16 from liuyiwuqing/v2.0-beta
2024-05-27 15:01:26 +00:00
liuyiwuqing 4747d593be 新增文章访问验证码功能
使用教程:https://blog.muyin.site/archives/3gysn5O9
2024-05-27 22:29:15 +08:00
小莫唐尼 7d8e4ad559 docs: 更新文档 2024-05-25 17:05:09 +08:00
小莫唐尼 3d02b8cc44 fix: 修复文章详情评论头像问题 2024-05-25 16:13:37 +08:00
小莫唐尼 595c2d7cb7 fix: 修复文章搜索内容不显示问题 2024-05-25 15:40:38 +08:00
小莫唐尼 2154daea57 fix: 修复首页文章请求地址默认使用代理问题 2024-05-25 15:00:48 +08:00
小莫唐尼 85f166e7d0 fix: 修复首页文章排序问题、修复文章详情广告问题 2024-05-25 14:58:28 +08:00
小莫唐尼 0d618e21ce fix: 修复瞬间图片预览索引不正确 2024-05-25 14:02:26 +08:00
小莫唐尼 8d610473b3 fix: 修复个人图库分类少于2条不显示问题,修复图片预览问题 2024-05-22 22:29:13 +08:00
小莫唐尼 512a4013b9 fix: 修复首页加载异常问题 2024-05-22 00:43:24 +08:00
小莫唐尼 f6069cad95 update: 修复首次运行跳转页面非首页问题 2024-05-21 10:00:07 +08:00
小莫唐尼 90ef890766 update: 添加忽略文件 2024-05-20 11:23:52 +08:00
小莫唐尼 5727efa820 update: 正式支持 Halo 2.x 版本(uni-halo v2.0-beta ) 2024-05-20 01:32:31 +08:00
小莫唐尼 057a7cf6f0 release: 发布 uni-halo beta-v2.0 版本 2024-05-20 01:27:15 +08:00
小莫唐尼 a81c506a14 修改:优化文章分类添加密码验证功能 2023-08-03 02:31:22 +08:00
小莫唐尼 70dc7cd0a7 修改:优化文章卡片样式 2023-08-03 01:55:57 +08:00
小莫唐尼 ee28753094 修改:取消博客页面的loading 2023-08-03 01:44:24 +08:00
小莫唐尼 91923c0b42 同步代码 2023-08-03 01:37:20 +08:00
cuolv f6feecb324 新增:私密分类的访问 2023-08-02 05:30:13 +00:00
小莫唐尼 6fdaeed792 update:修改README.md 2023-06-25 09:28:49 +08:00
小莫唐尼 f838da43d8 修改:优化列表动画效果 2023-06-08 21:23:21 +08:00
小莫唐尼 21d723d260 新增:文章详情原文链接、访问密码功能 2023-06-08 20:48:15 +08:00
小莫唐尼 d701867b83 修改:更新README 2023-05-14 11:12:48 +08:00
小莫唐尼 66f4ccffc0 修改:简单兼容QQ小程序(未测试) 2023-05-14 11:08:20 +08:00
小莫唐尼 9720743c80 修改:修复友情链接未分组并且使用色彩版本无法展示BUG 2023-03-18 00:04:25 +08:00
小莫唐尼 6ba87b1972 修改:修复文章详情页面不兼容其他pc端主题导致文章内容不显示的BUG 2023-02-07 15:39:58 +08:00
小莫唐尼 a0f3d1b220 修改:修复文章分享好友和朋友圈功能 2023-02-05 18:40:40 +08:00
小莫唐尼 6e80fa14c8 修改:修复文章和分类无封面图使用静态默认图片无法显示的BUG 2023-01-13 01:17:57 +08:00
小莫唐尼 22a8e92dc2 修改:修复tm-vuetify 在 HBuilderX3.6.14版本中 dayjs 找不到的问题 2022-12-26 20:09:30 +08:00
小莫唐尼 6b1e0e5cc0 新增:文章详情自定义广告 2022-12-15 17:34:26 +08:00
小莫唐尼 261617fc12 修改:修复恋爱日记在微信小程序ios下的日期转换错误bug;
修改:修复海报分享因为无图使用随机图在微信小程序下异常bug;
新增:halo.config.template.js 新增一个配置项 defaultStaticThumbnailUrl 默认静态图片用于海报在无图且使用了随机图时候的封面图片地址
2022-12-15 14:25:54 +08:00
小莫唐尼 5000da9a64 新增:文章详情评论弹幕设置功能 2022-12-13 10:51:21 +08:00
小莫唐尼 9573b303e2 新增:文章详情页面添加弹幕功能;
修改:修复友链列表单分组显示问题;
优化;对恋爱日记清单列表进行优化
2022-12-12 23:03:48 +08:00
小莫唐尼 f12262f4df 修改:调整恋爱日记相关UI 2022-12-12 00:30:47 +08:00
小莫唐尼 1305b4a555 修改:优化恋爱日记配置信息 2022-12-11 17:41:31 +08:00
小莫唐尼 b7e2025ebb 新增:新增恋爱日记功能 2022-12-11 16:52:32 +08:00
小莫唐尼 0da5feb2ee 修复:修复封面图片,如果是随机图api不生效的BUG 2022-12-10 14:30:32 +08:00
小莫唐尼 e46c5c2b6f 新增:文章版权说明;
新增:文章详情点击标签跳转查询标签下的文章列表;
优化:调整markdown文字大小
2022-12-10 14:24:36 +08:00
小莫唐尼 70c5b34de0 新增:留言评论头像形状设置;
修复:修复文章管理设置文章标签在小程序端不显示的bug;
修复:修复后台管理工作台因为无法读取用户信息页面不显示bug;
优化:优化友情链接动画效果
2022-12-10 12:26:47 +08:00
小莫唐尼 9d3ebac076 新增:更新halo.config.js 配置参数;
更新:更新项目UI框架;
修复:修复友链列表丢失白色背景色BUG;
2022-12-09 18:10:47 +08:00
小莫唐尼 3fd3b2247a 修复:后台管理文章设置BUG;
修复:友链列表不显示数据BUG;
修复:应用设置点击返回,触发提示BUG;
2022-12-09 10:48:00 +08:00
小莫唐尼 3702cb1618 新增:联系博主页面顶部返回按钮;
修复:修复 默认封面图、默认图片、默认头像在使用随机api时候无法显示的BUG;
修复:后台管理新增文章发布失败BUG;
删除:去除联系博主页面中的 联系博主 按钮;
优化:对友链页面进行重写;
优化:对部分页面和功能进行优化。
2022-12-09 00:24:39 +08:00
小莫唐尼 473655171b 修改:修正项目中的开源协议信息 2022-12-07 00:39:18 +08:00
小莫唐尼 9e2f090747 修改:更正开源协议 2022-12-07 00:33:26 +08:00
小莫唐尼 e05d0d7ce5 优化:重写友链列表,兼容微信小程序;
优化:解决分类功能滚动回弹问题;
2022-12-06 23:18:44 +08:00
小莫唐尼 3ba10fa105 修复:修复小程序文章图片不显示BUG 2022-12-06 18:36:06 +08:00
小莫唐尼 acc9070134 新增:首次启动页面配置 2022-12-06 18:02:36 +08:00
198 changed files with 12688 additions and 23731 deletions
+6 -2
View File
@@ -1,5 +1,9 @@
node_modules/
unpackage/
config/halo.config.js
config/ad.config.js
package-lock.json
.idea
config/halo.config.js
config/love.config.js
config/token.config.js
config/plugins.config.js
+27 -18
View File
@@ -1,20 +1,29 @@
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
"version": "0.0",
"configurations": [{
"app-plus" :
{
"launchtype" : "local"
},
"default" :
{
"launchtype" : "local"
},
"mp-weixin" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
{
// launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
"version" : "0.0",
"configurations" : [
{
"app-plus" : {
"launchtype" : "local"
},
"default" : {
"launchtype" : "local"
},
"h5" : {
"launchtype" : "local"
},
"mp-qq" : {
"launchtype" : "local"
},
"mp-weixin" : {
"launchtype" : "local"
},
"type" : "uniCloud"
},
{
"playground" : "standard",
"type" : "uni-app:app-android"
}
]
}
+26 -43
View File
@@ -1,48 +1,35 @@
<script>
import HaloConfig from '@/config/halo.config.js';
import HaloAdConfig from '@/config/ad.config.js';
import HaloTokenConfig from '@/config/token.config.js';
// app升级检测(搭配:https://ext.dcloud.net.cn/plugin?id=4470 升级中心)
import CheckAppUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update';
import { CheckWxUpdate } from '@/utils/update.js';
import {CheckWxUpdate} from '@/utils/update.js';
export default {
globalData: {
baseApiUrl: HaloConfig.apiUrl,
...HaloConfig,
haloAdConfig: HaloAdConfig
},
onLaunch: function() {
console.log('App Launch');
globalData: {
baseApiUrl: HaloTokenConfig.BASE_API
},
onLaunch: function () {
// #ifdef APP-PLUS
CheckAppUpdate();
// #endif
// #ifdef APP-PLUS
CheckAppUpdate();
// #endif
// #ifdef MP-WEIXIN
CheckWxUpdate();
uni.$tm.vx.commit('setWxShare', HaloConfig.wxShareConfig);
// #endif
// #ifdef MP-WEIXIN
CheckWxUpdate();
// #endif
// 监听中间按钮(暂时没有使用)
uni.onTabBarMidButtonTap(() => {
console.log('点击中间按钮');
});
// 初始化博主信息
uni.$tm.vx.actions('blogger/fnGetBlogger');
// 临时:检查是否有用户,没有的话添加一个默认的用户
uni.$tm.vx.actions('user/checkAndSetDefaultUser');
// 启动检查app的配置是否已经就绪,若未就绪则设置默认的
uni.$tm.vx.actions('setting/checkAndSetDefaultAppSettings');
},
onShow: function() {
console.log('App Show');
},
onHide: function() {
console.log('App Hide');
}
uni.$tm.vx.actions('config/fetchConfigs').then((res) => {
})
// 启动检查app的配置是否已经就绪,若未就绪则设置默认的
uni.$tm.vx.actions('setting/checkAndSetDefaultAppSettings');
},
onShow: function () {
console.log('App Show');
},
onHide: function () {
console.log('App Hide');
}
};
</script>
@@ -64,14 +51,10 @@ export default {
@import './common/icons/mphtmliconfont.css';
/* #ifndef MP-WEIXIN */
@import './common/markdown/markdown.scss'; //引入markdown呈现
@import './common/markdown/markdown.scss';
/* #endif */
page {
// background-color: #f3f5f7;
// background-color: #f7f7f7;
// background-color: #ffffff;
background-color: #fafafa;
// background-color: #f4f5f5;
background-color: #fafafa;
}
</style>
+657 -16
View File
@@ -1,20 +1,661 @@
The MIT License (MIT)
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (c) 2018 RuoYi
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
Preamble
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.
+96 -81
View File
@@ -1,110 +1,125 @@
<p align="center">
<a href="https://uni-halo.925i.cn" target="_blank" rel="noopener noreferrer">
<img width="100" src="https://b.925i.cn/uni_halo/uni_halo_logo.png" alt="uni-halo logo" />
<img width="100" src="https://uni-halo.925i.cn/logo.png" alt="uni-halo logo" />
</a>
</p>
<p align="center"><b>uni-halo</b> 基于Halo一款现代化的开源博客/CMS系统API开发的多端系统,值得一试。</p>
<p align="center"><b>uni-halo v2.0】</b> 基于 Halo2.X API 多端项目,值得一试。</p>
<p align="center"><b style="font-size: 20px;color: #10B5FB">正式支持 Halo2.X 版本,免费开源</b></p>
<br />
<p align="center">
<a href="https://b.925i.cn">作者博客</a>
<a href="https://uni-halo.925i.cn">文档地址</a>
<a href="https://gitee.com/ialley-workshop-open/uni-halo">Gitee仓库</a>
<a href="https://github.com/ialley-workshop-open/uni-halo">Github仓库</a>
<a href="https://www.xiaoxiaomo.cn">作者主页</a>
<a href="https://uni-halo.925i.cn">官方文档</a>
<a href="https://github.com/ialley-workshop-open/uni-halo">仓库地址</a>
<a href="https://github.com/ialley-workshop-open/plugin-uni-halo">插件仓库</a>
</p>
---
如果您觉得这个项目对您有帮助,可以帮作者买杯饮料鼓励鼓励!
如果您觉得这个项目对您有帮助,可以帮作者买杯饮料鼓励鼓励,同时为了项目能够持续发展,可以根据您的喜好支持一下本项目哦,非常感谢您的支持,作者也会更有动力持续维护和更新新的功能哦~
<table rules="none" align="center" border="0">
<tr>
<td>
<center>
<img src="https://uni-halo.925i.cn/qrcode/zfb.png" width="100%" />
<br/>
<font color="AAAAAA">支付宝打赏</font>
</center>
</td>
<td>
<center>
<img src="https://uni-halo.925i.cn/qrcode/wx.png" width="100%" />
<br/>
<font color="AAAAAA">微信打赏</font>
</center>
</td>
<td>
<center>
<img src="https://uni-halo.925i.cn/qrcode/qq.png" width="100%" />
<br/>
<font color="AAAAAA">QQ打赏</font>
</center>
</td>
</tr>
</table>
| 支付宝 | 微信 | QQ |
|:----------------------------------------------:|:---------------------------------------------:|:---------------------------------------------:|
| ![支付宝赞赏](https://blog.925i.cn/upload/zf_zfb_skm.png) | ![微信赞赏](https://blog.925i.cn/upload/zf_wx_zsm.png) | ![QQ赞赏](https://blog.925i.cn/upload/zf_qq_skm.png) |
## 一、项目介绍
基于 Halo 2.x 提供的 API 接口,为微信小程序提供的一套开源的博客应用。
- 完全免费开源,包括程序源码、插件源码
- 页面支持插件配置
- 使用最新流行的技术栈
- 支持特色功能,恋爱日记
- 理论支持编译为 小程序(推荐)、APP
## 🌸 快速开始
### 在线预览
详细部署文档请查阅 [uni-halo-doc](https://uni-halo.925i.cn/)
微信内容扫描下方二维码,即可在线预览小程序。
- 1、拉取或下载项目<https://gitee.com/ialley-workshop-open/uni-halo>
- 2、通过hbuilderx 导入项目;
- 3、命令行执行 npm i 安装依赖;
- 4、配置运行信息,找到项目根目录config目录,将`halo.config.template.js`修改为 `halo.config.js` 并设置相关信息;
- 5、点击hbuilderx 工具 右上角预览、或者点击工具栏 运行-内置浏览器运行;
- 6、项目发行:
- 发行小程序:点击工具栏 发行 -> 小程序-微信
- 发行APP: 点击工具栏 发行 -> 原生App-云打包
![QQ交流群](https://blog.925i.cn/upload/xiaochengxu.gif)
## ⭐️ 功能清单
### 部分截图
### 用户部分
1. - [x] 文章分类
2. - [x] 文章详情&海报分享
3. - [x] 文章归档
4. - [x] 文章列表和搜索
5. - [x] 数据统计展示
6. - [x] 图库
7. - [x] 友链
8. - [x] 留言板
9. - [x] 个人日记
|首页|分类|图库|
|:--:|:--:|:--:|
|![首页](https://blog.925i.cn/upload/39789CF4434C9CD6A6289D7209AF6EEF.jpg)|![分类](https://blog.925i.cn/upload/19CB6B66F40200045B6F572A9C28C5E8.jpg)|![图库](https://blog.925i.cn/upload/464F22FDB216CE802653A5F03BE34351.jpg)|
### 管理部分
1. - [x] 个人信息修改
2. - [x] 个人密码修改
3. - [x] 日记管理
4. - [x] 文章管理
5. - [x] 分类管理
6. - [x] 标签管理
7. - [x] 附件管理
8. - [x] 友链管理
9. - [x] 图库管理
10. - [x] 评论管理
11. - [x] 日志管理
12. - [ ] 菜单设置
13. - [ ] 系统设置
## 📃 TODO
1. - [ ] 暗黑模式
2. - [ ] 国际化
|瞬间|我的|
|:--:|:--:|
|![瞬间](https://blog.925i.cn/upload/41EE8ADBFAE709A483A6E5F814C6A6E4.jpg)|![我的](https://blog.925i.cn/upload/9AEFE8DA4671A3C7F20F76FF3F9D15C9.jpg)|
## 🌈 在线体验
### 恋爱日记
- 敬请期待...
|主页|恋爱清单|
|:--:|:--:|
|![主页](https://uni-halo.925i.cn/assets/love_001.6bf8b4e9.jpg)|![恋爱清单](https://uni-halo.925i.cn/assets/love_002.a08bd8d6.jpg)|
<br/>
## 🔍 许可证
<a href="https://gitee.com/ialley-workshop-open/uni-halo/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
uni-halo 使用 MIT 协议开源,请遵守开源协议。
### 功能介绍
## 🏭 贡献
- 基础功能:文章列表、文章分类、文章详情、个人图库、友情链接、我的瞬间
贡献代码请查看 [代码贡献规范](https://uni-halo.925i.cn/standard/category.html)
- 特色功能:恋爱日记,精心为您设计了秀恩爱的模块,秀出关于你们的故事、情侣甜蜜相册、恋爱记录清单,为您的恋爱保驾护航。
## 🙆‍♂ Halo
- 接口文档地址(内容端):<https://api.halo.run/content-api.html>
- 接口文档地址(管理端):<https://api.halo.run/admin-api.html>
## 二、配套插件
### 下载地址
- 代码仓库:[https://github.com/ialley-workshop-open/plugin-uni-halo](https://github.com/ialley-workshop-open/plugin-uni-halo)
- 仓库版本:[https://github.com/ialley-workshop-open/plugin-uni-halo/releases](https://github.com/ialley-workshop-open/plugin-uni-halo/releases)
- 应用市场:[https://www.halo.run/store/apps/app-ryemX](https://www.halo.run/store/apps/app-ryemX)
### 使用方式
直接下载安装插件,在插件市场安装即可,安装完成后,需要配置插件,配置完成后,即可使用插件提供的功能。
## 三、相关文档
- 作者主页:[https://www.xiaoxiaomo.cn/](https://www.xiaoxiaomo.cn/)
- 作者博客:[https://blog.925i.cn/](https://blog.925i.cn/)
- 官方文档:[https://uni-halo.925i.cn/](https://uni-halo.925i.cn/)
- 代码仓库:[https://github.com/ialley-workshop-open/uni-halo](https://github.com/ialley-workshop-open/uni-halo)
- 插件地址:[https://github.com/ialley-workshop-open/plugin-uni-halo](https://github.com/ialley-workshop-open/uni-halo)
- 应用市场:[https://www.halo.run/store/apps/app-ryemX](https://www.halo.run/store/apps/app-ryemX)
## 四、使用交流
- 提交 issues[Github Issues](https://github.com/ialley-workshop-open/uni-halo/issues)
丨 [Gitee Issues](https://gitee.com/ialley-workshop-open/uni-halo/issues)
- 微信交流群:由于微信交流群时效性太短,所以有需要的话可以先加入QQ群,再邀请进微信群
- QQ 交流群:632969367
![QQ交流群](https://blog.925i.cn/upload/qun.png)
<br/>
## 🎉 加入我们
uni-halo 还在持续更新中,本项目欢迎您的参与,共同维护,逐步完善,将项目做得更强。项目采用 AGPL-3.0 开源协议,本着一切免费的原则,原则上不会收取任何费用及版权,可以放心使用。
如果你想加入我们,可以多提供一些好的建议或者提交 pr,我们将会非常乐意接受您的建议和意见。
<br/>
## 🔍 开源许可
uni-halo 使用 AGPL-3.0 协议开源,请遵守开源协议。
<br/>
## 🙋 致谢
uni-halo 得益于以下优秀的技术支撑,我只是站在巨人的肩膀上捡了几根头发丝,但是非常感谢这些优秀的项目,带给了我们其他开发者和用户非常友好的体验!
- [Halo:一款好用又强大的开源建站工具](https://halo.run/)
- [uni-app:是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码可编译到多个平台](https://uniapp.dcloud.net.cn/)
- [tm-vuetify:是一个为 uni-app 平台定制的颜值也非常高的 UI 框架](https://www.jx2d.cn/)
- [mp-html:一个强大的小程序富文本组件](https://jin-yufeng.gitee.io/mp-html/#/)
-86
View File
@@ -1,86 +0,0 @@
/**
* 附件管理
* @see https://api.halo.run/admin-api.html#tag/attachment-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 分页获取附件列表
* {
* "attachmentType": "ALIOSS" "BAIDUBOS" "HUAWEIOBS" "LOCAL" "MINIO" "QINIUOSS" "SMMS" "TENCENTCOS" "UPOSS",
* "keyword": "string"
* "mediaType": "string"
* "page": "string"
* "size": "string"
* "sort": "string"
* }
*/
getAttachmentsByPage: (params) => {
return HttpHandler.Get('/api/admin/attachments', params, {})
},
/**
* 获取所有的附件类型
*/
getAttachmentsMediaTypes: () => {
return HttpHandler.Get('/api/admin/attachments/media_types')
},
/**
* 根据附件类型获取所有的附件列表
*/
getAttachmentsTypes: () => {
return HttpHandler.Get('/api/admin/attachments/types')
},
/**
* 根据附件Id获取附件详情
*/
getAttachmentsById: (attachmentId) => {
return HttpHandler.Get(`/api/admin/attachments/${attachmentId}`)
},
/**
* 上传附件-单文件(file)
* {
* file:文件对象
* }
*/
uploadAttachment: (data) => {
return HttpHandler.Upload(`/api/admin/attachments/upload`, data)
},
/**
* 上传附件-多文件(files)
* {
* files:文件对象集合
* }
*/
uploadAttachments: (data) => {
return HttpHandler.Upload(`/api/admin/attachments/uploads`, data)
},
/**
* 修改一个附件信息
*/
updateAttachmentById: (attachmentId, name) => {
return HttpHandler.Put(`/api/admin/attachments/${attachmentId}`, {
name: name
})
},
/**
* 批量删除附件(id集合)
*/
deleteAttachmentByIds: (attachmentIds = []) => {
return HttpHandler.Delete(`/api/admin/attachments`, attachmentIds)
},
/**
* 删除单个附件
*/
deleteAttachmentById: (attachmentId) => {
return HttpHandler.Delete(`/api/admin/attachments/${attachmentId}`)
},
}
-70
View File
@@ -1,70 +0,0 @@
/**
* 文章分类管理
* @see https://api.halo.run/admin-api.html#tag/category-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 查询所有的文章分类
* {
* "sort": ["",""], // 排序
* "more": "Boolean" ,// 更多参数(回调)
* }
*/
getCategoryList: (params) => {
return HttpHandler.Get('/api/admin/categories', params)
},
/**
* 查询所有的文章分类(树形数据)
* {
* "sort": ["",""], // 排序
* }
*/
getCategoryListTree: (params) => {
return HttpHandler.Get('/api/admin/categories/tree_view', params)
},
/**
* 查询文章分类详情
* @param {Number} categoryId 分类ID
*/
getCategoryDetail: (categoryId) => {
return HttpHandler.Get(`/api/admin/categories/${categoryId}`)
},
/**
* 新增文章分类
* {
* "description": "string",
* "id": 0,
* "name": "string",
* "parentId": 0,
* "password": "string",
* "priority": 0,
* "slug": "string",
* "thumbnail": "string"
* }
*/
createCategory: (data) => {
return HttpHandler.Post(`/api/admin/categories`, data)
},
/**
* 修改文章分类信息
* @param {Number} categoryId 分类id
* @param {Object} data 同新增
*/
updateCategoryById: (categoryId, data) => {
return HttpHandler.Put(`/api/admin/categories/${categoryId}`, data)
},
/**
* 删除单个文章分类
* @param {Number} categoryId 文章分类id
*/
deleteCategoryById: (categoryId) => {
return HttpHandler.Delete(`/api/admin/categories/${categoryId}`)
},
}
-142
View File
@@ -1,142 +0,0 @@
/**
* 文章评论管理
* @see https://api.halo.run/admin-api.html#tag/journal-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 查询文章评论
* {
* "keyword":"", // 关键字
* "page": 0, // 分页索引
* "size": 10, // 分页大小
* "sort": ["",""], // 排序
* "status": "" , // 类型 "AUDITING" "PUBLISHED" "RECYCLE"
* }
*/
getPostsComments: (params) => {
return HttpHandler.Get('/api/admin/posts/comments', params)
},
/**
* 回复文章评论
* {
* "allowNotification": true,
* "author": "string",
* "authorUrl": "string",
* "content": "string",
* "email": "string",
* "parentId": 0,
* "postId": 0
* }
*/
postPostsComments: (data) => {
return HttpHandler.Post('/api/admin/posts/comments', data)
},
/**
* 更新文章评论状态
* @param {Number} commentId id
* @param {String} status "AUDITING" "PUBLISHED" "RECYCLE"
*/
updatePostsCommentsStatus: (commentId, status) => {
return HttpHandler.Put(`/api/admin/posts/comments/${commentId}/status/${status}`)
},
/**
* 删除文章评论
* @param {Number} commentId id
*/
deletePostsCommentsById: (commentId) => {
return HttpHandler.Delete(`/api/admin/posts/comments/${commentId}`)
},
/**
* 查询页面评论
* {
* "keyword":"", // 关键字
* "page": 0, // 分页索引
* "size": 10, // 分页大小
* "sort": ["",""], // 排序
* "status": "" , // 类型 "AUDITING" "PUBLISHED" "RECYCLE"
* }
*/
getSheetsComments: (params) => {
return HttpHandler.Get('/api/admin/sheets/comments', params)
},
/**
* 回复页面评论
* {
* "allowNotification": true,
* "author": "string",
* "authorUrl": "string",
* "content": "string",
* "email": "string",
* "parentId": 0,
* "postId": 0
* }
*/
postSheetsComments: (data) => {
return HttpHandler.Post('/api/admin/sheets/comments', data)
},
/**
* 更新页面评论状态
* @param {Number} commentId id
* @param {String} status "AUDITING" "PUBLISHED" "RECYCLE"
*/
updateSheetsCommentsStatus: (commentId, status) => {
return HttpHandler.Put(`/api/admin/sheets/comments/${commentId}/status/${status}`)
},
/**
* 删除页面评论
* @param {Number} commentId id
*/
deleteSheetsCommentsById: (commentId) => {
return HttpHandler.Delete(`/api/admin/sheets/comments/${commentId}`)
},
/**
* 查询日记评论
* {
* "keyword":"", // 关键字
* "page": 0, // 分页索引
* "size": 10, // 分页大小
* "sort": ["",""], // 排序
* "status": "" , // 类型 "AUDITING" "PUBLISHED" "RECYCLE"
* }
*/
getJournalsComments: (params) => {
return HttpHandler.Get('/api/admin/journals/comments', params)
},
/**
* 回复日记评论
* {
* "allowNotification": true,
* "author": "string",
* "authorUrl": "string",
* "content": "string",
* "email": "string",
* "parentId": 0,
* "postId": 0
* }
*/
postJournalsComments: (data) => {
return HttpHandler.Post('/api/admin/journals/comments', data)
},
/**
* 更新日记评论状态
* @param {Number} commentId id
* @param {String} status "AUDITING" "PUBLISHED" "RECYCLE"
*/
updateJournalsCommentsStatus: (commentId, status) => {
return HttpHandler.Put(`/api/admin/journals/comments/${commentId}/status/${status}`)
},
/**
* 删除日记评论
* @param {Number} commentId id
*/
deleteJournalsCommentsById: (commentId) => {
return HttpHandler.Delete(`/api/admin/journals/comments/${commentId}`)
},
}
-61
View File
@@ -1,61 +0,0 @@
/**
* 个人日记管理
* @see https://api.halo.run/admin-api.html#tag/journal-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 查询所有的日记列表
* {
* "keyword":"", // 关键字
* "page": 0, // 分页索引
* "size": 10, // 分页大小
* "sort": ["",""], // 排序
* "type": "" , // 类型 "INTIMATE" "PUBLIC"
* }
*/
getJournals: (params) => {
return HttpHandler.Get('/api/admin/journals', params)
},
/**
* 查询最近的所有的日记列表
* {
* "top":number, // 数量
* }
*/
getLatestJournals: (params) => {
return HttpHandler.Get('/api/admin/journals/latest', params)
},
/**
* 新增个人日记
* {
* "content": "string",
* "keepRaw": true,
* "sourceContent": "string",
* "type": "INTIMATE",
* }
*/
createJournals: (data) => {
return HttpHandler.Post(`/api/admin/journals`, data)
},
/**
* 修改个人日记信息
* @param {Number} journalsId id
* @param {Object} data 同新增
*/
updateJournalsById: (journalsId, data) => {
return HttpHandler.Put(`/api/admin/journals/${journalsId}`, data)
},
/**
* 删除个人日记
* @param {Number} journalsId id
*/
deleteJournalsById: (journalsId) => {
return HttpHandler.Delete(`/api/admin/journals/${journalsId}`)
},
}
-59
View File
@@ -1,59 +0,0 @@
/**
* 友链管理
* @see https://api.halo.run/admin-api.html#tag/link-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
// 获取友链列表
getLinkList: () => {
return HttpHandler.Get('/api/admin/links')
},
/**
* 获取友链详情
* @params { Number } linkId 友链Id
*/
getLinkDetail: (linkId) => {
return HttpHandler.Get(`/api/admin/links/${linkId}`)
},
/**
* 新增友链
* {
* "description": "string",
* "logo": "string",
* "name": "string",
* "priority": 0,
* "team": "string",
* "url": "string"
* }
*/
addLink: (data) => {
return HttpHandler.Post('/api/admin/links', data, {})
},
/**
* 修改友链
* {
* "description": "string",
* "logo": "string",
* "name": "string",
* "priority": 0,
* "team": "string",
* "url": "string"
* }
*/
updateLink: (linkId, data) => {
return HttpHandler.Put(`/api/admin/links/${linkId}`, data, {})
},
/**
* 删除友链
* @params { Number } linkId 友链Id
*/
deleteLink: (linkId) => {
return HttpHandler.Delete(`/api/admin/links/${linkId}`)
},
// 获取友链分组
getLinkTeamList: (data) => {
return HttpHandler.Get('/api/admin/links/teams')
},
}
-28
View File
@@ -1,28 +0,0 @@
/**
* 日志管理
* @see https://api.halo.run/admin-api.html#tag/link-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取日志列表列表
* params:{ top: Number}
*/
getLogsLatestList: (params) => {
return HttpHandler.Get('/api/admin/logs/latest', params)
},
/**
* 获取日志列表列表
* params:{ page:Number,size:Number, sort:String }
*/
getLogsListByPage: (params) => {
return HttpHandler.Get('/api/admin/logs', params)
},
/**
* 清空日志
*/
deleteAllLogs: () => {
return HttpHandler.Get(`/api/admin/logs/clear`)
},
}
-101
View File
@@ -1,101 +0,0 @@
/**
* 图库管理
* @see https://api.halo.run/admin-api.html#tag/photo-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 查询图片列表
* {
* "sort": ["",""], // 排序
* "more": "Boolean" ,// 更多参数(回调)
* }
*/
getPhotos: (params) => {
return HttpHandler.Get('/api/admin/photos', params)
},
/**
* 查询最近的图库列表(树形数据)
* {
* "sort": ["",""], // 排序
* }
*/
getLatestPhotos: (params) => {
return HttpHandler.Get('/api/admin/photos/latest', params)
},
/**
* 查询所有的图片分组
*/
getPhotosTeams: () => {
return HttpHandler.Get('/api/admin/photos/teams')
},
/**
* 查询图片详情
* @param {Number} photoId id
*/
getPhotosDetail: (photoId) => {
return HttpHandler.Get(`/api/admin/photos/${photoId}`)
},
/**
* 新增图片(单图)
*{
* "description": "string",
* "id": 0,
* "location": "string",
* "name": "string",
* "takeTime": "2019-08-24T14:15:22Z",
* "team": "string",
* "thumbnail": "string",
* "url": "string"
*}
*/
createPhotos: (data) => {
return HttpHandler.Post(`/api/admin/photos`, data)
},
/**
* 新增图片(批量)
* {
* "description": "string",
* "id": 0,
* "location": "string",
* "name": "string",
* "takeTime": "2019-08-24T14:15:22Z",
* "team": "string",
* "thumbnail": "string",
* "url": "string"
* }
*/
createPhotosBatch: (data) => {
return HttpHandler.Post(`/api/admin/photos/batch`, data)
},
/**
* 修改图片信息
* @param {Number} photoId id
* @param {Object} data 同新增
*/
updatePhotosById: (photoId, data) => {
return HttpHandler.Put(`/api/admin/photos/${photoId}`, data)
},
/**
* 删除单张图片
* @param {Number} photoId id
*/
deletePhotosById: (photoId) => {
return HttpHandler.Delete(`/api/admin/photos/${photoId}`)
},
/**
* 批量删除图片
* @param {Number} photoIds id数组
*/
deletePhotosBatchById: (photoIds) => {
return HttpHandler.Delete(`/api/admin/photos/batch`, photoIds)
},
}
-121
View File
@@ -1,121 +0,0 @@
/**
* 文章管理
* @see https://api.halo.run/admin-api.html#tag/post-controller
*/
import HttpHandler from '@/common/http/request.js'
/**
* 新建和编辑文章字段
*/
const createOrEditModel = {
"categoryIds": [
0
],
"content": "string",
"createTime": "2019-08-24T14:15:22Z",
"disallowComment": true,
"editorType": "MARKDOWN",
"keepRaw": true,
"metaDescription": "string",
"metaKeywords": "string",
"metas": [{
"key": "string",
"postId": 0,
"value": "string"
}],
"originalContent": "string",
"password": "string",
"slug": "string",
"status": "DRAFT",
"summary": "string",
"tagIds": [
0
],
"template": "string",
"thumbnail": "string",
"title": "string",
"topPriority": 0
}
export default {
/**
* 查询文章列表
* @param {Object} params {
* categoryId,keyword,page,size,sort,
* status:"DRAFT" "INTIMATE" "PUBLISHED" "RECYCLE",statuses,more
* }
*/
getPostsByPage: (params) => {
return HttpHandler.Get('/api/admin/posts', params)
},
/**
* 查询最近的文章列表
* @param {Object} params {top:Number}
*/
getLatestPosts: (params) => {
return HttpHandler.Get('/api/admin/posts/latest', params)
},
/**
* 根据状态查询文章列表
* @param {String} status:"DRAFT" "INTIMATE" "PUBLISHED" "RECYCLE"
* @param {Object} params:{ page,size,sort,more }
*/
getPostsPageByStatus: (status, params) => {
return HttpHandler.Get(`/api/admin/posts/status/${status}`, params)
},
/**
* 根据文章id获取文章
* @param {Number} postsId 文章id
*/
getPostsById: (postsId) => {
return HttpHandler.Get(`/api/admin/posts/${postsId}`)
},
/**
* 新增文章
* @param {Object} data 同新增
* @param {Boolean} isAutoSave 是否来源于自动保存
*/
createPosts: (data, isAutoSave = false) => {
return HttpHandler.Post(`/api/admin/posts?autoSave=${isAutoSave}`, data)
},
/**
* 修改文章
* @param {Number} postsId id
* @param {Object} data 同新增
* @param {Boolean} isAutoSave 是否来源于自动保存
*/
updatePostsById: (postsId, data, isAutoSave = false) => {
return HttpHandler.Put(`/api/admin/posts/${postsId}?autoSave=${isAutoSave}`, data)
},
/**
* 修改文章(草稿)
* @param {Number} postsId id
* @param {Object} data { content,keepRaw,originalContent }
*/
updatePostsDraftById: (postsId, data) => {
return HttpHandler.Put(`/api/admin/posts/${postsId}/status/draft/content`, data)
},
/**
* 修改文章状态
* @param {Number} postsId id
* @param {String} status "DRAFT" "INTIMATE" "PUBLISHED" "RECYCLE"
*/
updatePostsDraftById: (postsId, status) => {
return HttpHandler.Put(`/api/admin/posts/${postsId}/status/${status}`)
},
/**
* 删除文章(批量)
* @param {Array} postsIds ids
*/
deletePostsByIds: (postsIds) => {
return HttpHandler.Delete(`/api/admin/posts`, postsIds)
},
}
-56
View File
@@ -1,56 +0,0 @@
/**
* 标签管理
* @see https://api.halo.run/admin-api.html#tag/tag-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 查询文章标签列表
* {
* "sort": ["",""], // 排序
* "more": "Boolean" ,// 更多参数(回调)
* }
*/
getTagsList: (params) => {
return HttpHandler.Get('/api/admin/tags', params)
},
/**
* 查询文章标签详情
* @param {Number} tagId id
*/
getTagsDetail: (tagId) => {
return HttpHandler.Get(`/api/admin/tags/${tagId}`)
},
/**
* 新增文章标签
* {
* "color": "#e23d66", // 颜色选择器
* "name": "string",
* "slug": "string",
* "thumbnail": "string"
* }
*/
createTags: (data) => {
return HttpHandler.Post(`/api/admin/tags`, data)
},
/**
* 修改文章标签信息
* @param {Number} tagId id
* @param {Object} data 同新增
*/
updateTagsById: (tagId, data) => {
return HttpHandler.Put(`/api/admin/tags/${tagId}`, data)
},
/**
* 删除文章标签
* @param {Number} tagId id
*/
deleteTagsById: (tagId) => {
return HttpHandler.Delete(`/api/admin/tags/${tagId}`)
},
}
-83
View File
@@ -1,83 +0,0 @@
/**
* 登录管理
* @see https://api.halo.run/admin-api.html#tag/admin-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
// 登录前检查
loginPreCheck: (data) => {
return HttpHandler.Post('/api/admin/login/precheck', data, {})
},
// 登录
login: (data) => {
return HttpHandler.Post('/api/admin/login', data, {})
},
// 刷新token
refreshToken: (refreshToken) => {
return HttpHandler.Post($`/api/admin/refresh/${refreshToken}`, {}, {})
},
// 退出登录
logout: () => {
return HttpHandler.Post('/api/admin/logout')
},
/**
* 获取修改密码的验证码
* {
* "email": "string",
* "username": "string"
* }
*/
getResetPasswordCode: () => {
return HttpHandler.Post('/api/admin/password/code')
},
/**
* 重置密码
* {
* "code": "string",
* "email": "string",
* "password": "stringst",
* "username": "string"
* }
*/
resetPasswordByCode: (data) => {
return HttpHandler.Put('/api/admin/password/reset', data)
},
/**
* 获取个人信息(当前登录的管理员)
*/
getAdminProfile: () => {
return HttpHandler.Get('/api/admin/users/profiles')
},
/**
* 修改个人信息(当前登录的管理员)
* {
* "avatar": "string",
* "description": "string",
* "email": "string",
* "nickname": "string",
* "password": "stringst",
* "username": "string"
* }
*/
updateAdminProfile: (data) => {
return HttpHandler.Put('/api/admin/users/profiles', data)
},
/**
* 修改密码
* {
* "confirmPassword": "string",
* "newPassword": "string",
* "oldPassword": "strings"
* }
*/
modifyAdminPassword: (data) => {
return HttpHandler.Put('/api/admin/users/profiles/password', data)
},
}
-22
View File
@@ -1,22 +0,0 @@
/**
* 归档接口
* @see https://api.halo.run/content-api.html#tag/archive-controlle
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取归档列表(按月)
*/
getMonthArchives: () => {
return HttpHandler.Get(`/api/content/archives/months`)
},
/**
* 获取归档列表(按年)
*/
getYearArchives: () => {
return HttpHandler.Get(`/api/content/archives/years`)
},
}
-25
View File
@@ -1,25 +0,0 @@
/**
* 文章接口
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取文章列表
* @param {Object} params 查询参数
*/
getArticleList: (params) => {
return HttpHandler.Get('/api/content/posts', params)
},
/**
* 获取文章详情
* @param {String} articleId 文章id
*/
getArticleDetail: (articleId) => {
return HttpHandler.Get(`/api/content/posts/${articleId}`, {
formatDisabled: false,
sourceDisabled: true
})
},
}
-15
View File
@@ -1,15 +0,0 @@
/**
* 博主信息
* @see https://api.halo.run/content-api.html#tag/user-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取博主信息
*/
getBloggerInfo: () => {
return HttpHandler.Get(`/api/content/users/profile`)
},
}
-24
View File
@@ -1,24 +0,0 @@
/**
* 分类接口
* @see https://api.halo.run/content-api.html#tag/category-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 查询分类列表
* @param {Object} params 查询参数
*/
getCategoryList: (params) => {
return HttpHandler.Get('/api/content/categories', params)
},
/**
* 查询分类下的文章
* @param {String} slug 分类名称
* @param {Object} params 查询参数
*/
getCategoryPostList: (slug, params) => {
return HttpHandler.Get(`/api/content/categories/${slug}/posts`, params)
},
}
-45
View File
@@ -1,45 +0,0 @@
/**
* 评论接口
* @see https://api.halo.run/content-api.html#tag/post-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取评论列表接口(树形数据)
* @param {String} postId 文章id
* @param {Object} params 查询参数
*/
getPostCommentTree: (postId, params) => {
return HttpHandler.Get(`/api/content/posts/${postId}/comments/tree_view`, params)
},
/**
* 获取评论列表接口(列表数据)
* @param {String} postId 文章id
* @param {Object} params 查询参数
*/
getPostCommentList: (postId, params) => {
return HttpHandler.Get(`/api/content/posts/${postId}/comments/list_view`, params)
},
/**
* 获取置顶评论
* @param {String} postId 文章id
* @param {Object} params 查询参数
*/
getPostTopCommentList: (postId, params) => {
return HttpHandler.Get(`/api/content/posts/${postId}/comments/top_view`, params)
},
/**
* 获取评论的子评论列表
* @param {String} postId 文章id
* @param {String} commentParentId 要获取的评论id
* @param {Object} params 查询参数
*/
getPostChildrenCommentList: (postId, commentParentId, params) => {
return HttpHandler.Get(`/api/content/posts/${postId}/comments/${commentParentId}/children`, params)
},
}
+7 -56
View File
@@ -10,68 +10,19 @@
* 修改时间:
*/
import HttpHandler from '@/common/http/request.js'
import archive from './archive.js'
import article from './article.js'
import blogger from './blogger.js'
import category from './category.js'
import comment from './comment.js'
import journal from './journal.js'
import link from './link.js'
import menu from './menu.js'
import option from './option.js'
import photo from './photo.js'
import post from './post.js'
import sheet from './sheet.js'
import statistics from './statistics.js'
import theme from './theme.js'
// 管理端
import admin_login from './admin/user.js'
import admin_links from './admin/links.js'
import admin_attachment from './admin/attachment.js'
import admin_category from './admin/category.js'
import admin_journal from './admin/journal.js'
import admin_photos from './admin/photos.js'
import admin_tags from './admin/tags.js'
import admin_comments from './admin/comments.js'
import admin_posts from './admin/posts.js'
import admin_logs from './admin/logs.js'
// 2.0接口
import v2 from './v2/all.api.js'
import v2Config from './v2/all.config.js'
const ApiManager = {
...archive,
...article,
...blogger,
...category,
...comment,
...journal,
...link,
...option,
...photo,
...post,
...sheet,
...statistics,
...theme,
// 管理端的api
admin: {
...admin_login,
...admin_links,
...admin_attachment,
...admin_category,
...admin_journal,
...admin_photos,
...admin_tags,
...admin_comments,
...admin_posts,
...admin_logs
}
v2,
v2Config,
};
const install = (Vue) => {
Vue.prototype.$httpApi = ApiManager
Vue.prototype.$httpApi = ApiManager
}
export default {
install
install
}
-74
View File
@@ -1,74 +0,0 @@
/**
* 日记接口
* @see https://api.halo.run/content-api.html#tag/journal-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取日记列表
* @param {String} journalId 日记id
*/
getJournals: () => {
return HttpHandler.Get(`/api/content/journals`)
},
/**
* 获取日记详情
* @param {String} journalId 日记id
*/
getJournalDetail: (journalId) => {
return HttpHandler.Get(`/api/content/journals/${journalId}`)
},
/**
* 获取日记置顶评论列表
* @param {String} journalId 日记id
*/
getJournalTopComments: (journalId) => {
return HttpHandler.Get(`/api/content/journals/${journalId}/comments/top_view`)
},
/**
* 获取日记评论列表(列表形式)
* @param {String} journalId 日记id
*/
getJournalCommentList: (journalId) => {
return HttpHandler.Get(`/api/content/journals/${journalId}/comments/list_view`)
},
/**
* 获取日记评论列表(树形式)
* @param {String} journalId 日记id
*/
getJournalCommentTree: (journalId) => {
return HttpHandler.Get(`/api/content/journals/${journalId}/comments/tree_view`)
},
/**
* 获取日记评论列表(树形式)
* @param {String} journalId 日记id
* @param {String} commentParentId 评论id
*/
getJournalCommentChildren: (journalId, commentParentId) => {
return HttpHandler.Get(
`/api/content/journals/${journalId}/comments/${commentParentId}/children`)
},
/**
* 发表日记评论
* @param {Object} data 评论数据
*/
postJournalComments: (data) => {
return HttpHandler.Post(`/api/content/journals/comments`, data)
},
/**
* 给日记点赞
* @param {String} journalId 日记id
*/
postJournalLikes: (journalId) => {
return HttpHandler.Post(`/api/content/journals/${journalId}/likes`)
},
}
-24
View File
@@ -1,24 +0,0 @@
/**
* 友链接口
* @see https://api.halo.run/content-api.html#tag/link-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取友链列表
* @param {Object} params 参数
*/
getLinkList: (params) => {
return HttpHandler.Get(`/api/content/links`, params)
},
/**
* 获取分组的友链列表
* @param {Object} params 参数
*/
getLinkListByTeam: (params) => {
return HttpHandler.Get(`/api/content/links/team_view`, params)
},
}
-112
View File
@@ -1,112 +0,0 @@
/**
* 普通用户登录
*/
// 获取用户信息
export function getUserInfo() {
return new Promise((resolve, reject) => {
uni.getUserProfile({
lang: 'zh_CN',
desc: '用户登录', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,
success: (res) => {
console.log(res, 'resss')
resolve(res.userInfo)
},
fail: (err) => {
reject(err)
}
})
})
}
export function getLogin() {
return new Promise((resolve, reject) => {
uni.login({
success(res) {
console.log('----------getLogin ---------')
console.log(res)
resolve(res)
},
fail: (err) => {
console.log(err, 'logoer')
reject(err)
}
})
})
}
export function wxLogin() {
uni.getProvider({
service: 'oauth',
success: function(res) {
//支持微信、qq和微博等
if (~res.provider.indexOf('weixin')) {
console.log(res, 'ress')
let _userInfo = getUserInfo();
let _loginRes = getLogin();
Promise.all([_userInfo, _loginRes]).then((res) => {
let userInfo = res[0];
let loginRes = res[1];
if (loginRes.errMsg == 'login:ok') {
uni.$tm.vx.commit('user/setWxLoginInfo', {
avatarUrl: userInfo.avatarUrl,
nickName: userInfo.nickName,
email: '',
url: ''
});
uni.showToast({
icon: 'none',
title: '登录成功!'
})
} else {
uni.showToast({
icon: 'none',
title: '登录失败,请重试!'
})
}
}).catch(err => {
uni.showToast({
icon: 'none',
title: '登录失败,请重试!'
})
})
}
},
fail: function(err) {
uni.showToast({
icon: 'none',
title: '登录失败,请重试!'
})
}
})
}
export function appWxLogin() {
uni.login({
provider: 'weixin',
success: function(loginRes) {
// 获取用户信息
uni.getUserInfo({
provider: 'weixin',
success: function(infoRes) {
uni.$tm.vx.commit('user/setWxLoginInfo', {
avatarUrl: infoRes.userInfo.avatarUrl,
nickName: infoRes.userInfo.nickName,
email: '',
url: ''
});
uni.showToast({
icon: 'none',
title: '登录成功!'
})
},
fail: function(err) {
uni.showToast({
icon: 'none',
title: '登录失败,请重试!'
})
}
});
}
});
}
-24
View File
@@ -1,24 +0,0 @@
/**
* 菜单接口
* @see https://api.halo.run/content-api.html#tag/menu-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取菜单列表(列表)
* @param {Object} params 参数
*/
getMenuList: (params) => {
return HttpHandler.Get(`/api/content/menus`, params)
},
/**
* 获取菜单列表(树形)
* @param {Object} params 参数
*/
getMenuTree: (params) => {
return HttpHandler.Get(`/api/content/menus/tree_view`, params)
},
}
-30
View File
@@ -1,30 +0,0 @@
/**
* 配置接口
* @see https://api.halo.run/content-api.html#tag/option-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 根据key获取配置
* @param {String} key 配置的key
*/
getOptionByKey: (key) => {
return HttpHandler.Get(`/api/content/options/keys/${key}`)
},
/**
* 获取配置列表(列表)
*/
getOptionList: () => {
return HttpHandler.Get(`/api/content/options/list_view`)
},
/**
* 获取配置列表(键值对)
*/
getOptionMap: () => {
return HttpHandler.Get(`/api/content/options/map_view`)
},
}
-31
View File
@@ -1,31 +0,0 @@
/**
* 图库接口
* @see https://api.halo.run/content-api.html#tag/photo-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取图库列表(分页查询)
* @param {Object} params 参数
*/
getPhotoListByPage: (params) => {
return HttpHandler.Get(`/api/content/photos`, params)
},
/**
* 获取图库列表(最新)
* @param {Object} params 参数
*/
getPhotoList: (params) => {
return HttpHandler.Get(`/api/content/photos/latest`, params)
},
/**
* 获取图库分组
*/
getPhotoTeams: () => {
return HttpHandler.Get(`/api/content/photos/teams`)
},
}
-82
View File
@@ -1,82 +0,0 @@
/**
* 文章接口
* @see https://api.halo.run/content-api.html#tag/post-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取文章列表
* @param {Object} params 参数
*/
getPostList: (params) => {
return HttpHandler.Get(`/api/content/posts`, params)
},
/**
* 评论文章
* @param {Object} data 数据
* {
* "allowNotification": true,
* "author": "string",
* "authorUrl": "string",
* "content": "string",
* "email": "string",
* "parentId": 0,
* "postId": 0
* }
*/
postCommentPost: (data) => {
return HttpHandler.Post(`/api/content/posts/comments`, data)
},
/**
* 搜索文章
* @param {Object} data 数据
*/
getPostListByKeyword: (data) => {
return HttpHandler.Post(`/api/content/posts/search`, data)
},
/**
* 根据分类获取文章
* @param {Object} params 参数
*/
getPostDetailBySlug: (params) => {
return HttpHandler.Get(`/api/content/posts/slug`, params)
},
/**
* 根据文章id获取文章
* @param {Object} params 参数
*/
getPostDetailByPostId: (postId, params) => {
return HttpHandler.Get(`/api/content/posts/${postId}`, params)
},
/**
* 给文章点赞
* @param {Object} postId 文章id
*/
postLikePost: (postId) => {
return HttpHandler.Post(`/api/content/posts/${postId}/likes`)
},
/**
* 根据当前文章id获取前一篇文章
* @param {Object} postId 文章id
*/
getPrevByCurrentPostId: (postId) => {
return HttpHandler.Get(`/api/content/posts/${postId}/prev`)
},
/**
* 根据当前文章id获取下一篇文章
* @param {Object} postId 文章id
*/
getNextByCurrentPostId: (postId) => {
return HttpHandler.Get(`/api/content/posts/${postId}/next`)
},
}
-67
View File
@@ -1,67 +0,0 @@
/**
* 自定义页面模板
* @see https://api.halo.run/content-api.html#tag/sheet-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取页面列表
* {
* page:
* size:
* sort:
* }
*/
getSheetsList: (params) => {
return HttpHandler.Get(`/api/content/sheets`, params)
},
/**
* 根据分类获取页面数据
*/
getSheetsListBySlug: (params) => {
return HttpHandler.Get(`/api/content/sheets/slug`, params)
},
/**
* 获取页面评论(列表)
*/
getSheetsCommentsListBySheetId: (sheetId, params) => {
return HttpHandler.Get(`/api/content/sheets/${sheetId}/comments/list_view`, params)
},
/**
* 获取页面评论(树形)
*/
getSheetsCommentsTreeBySheetId: (sheetId, params) => {
return HttpHandler.Get(`/api/content/sheets/${sheetId}/comments/tree_view`, params)
},
/**
* 获取评论的子评论列表
* @param {String} sheetId 页面id
* @param {String} commentParentId 要获取的评论id
* @param {Object} params 查询参数
*/
getSheetsChildrenCommentList: (sheetId, commentParentId, params) => {
return HttpHandler.Get(`/api/content/sheets/${sheetId}/comments/${commentParentId}/children`, params)
},
/**
* 给页面添加一个评论
* {
* "allowNotification": true,
* "author": "string",
* "authorUrl": "string",
* "content": "string",
* "email": "string",
* "parentId": 0,
* "postId": 0
* }
*/
postSheetsComments: (data) => {
return HttpHandler.Post(`/api/content/sheets/comments`, data)
},
}
-22
View File
@@ -1,22 +0,0 @@
/**
* 博客统计信息
* @see https://api.halo.run/content-api.html#tag/statistic-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取博客统计信息
*/
getBlogStatistics: () => {
return HttpHandler.Get(`/api/content/statistics`)
},
/**
* 获取博客统计信息和用户信息
*/
getBlogStatisticsWithUser: () => {
return HttpHandler.Get(`/api/content/statistics/user`)
},
}
-40
View File
@@ -1,40 +0,0 @@
/**
* 主题设置
* @see https://api.halo.run/content-api.html#tag/theme-controller
*/
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取激活主题的信息
* @param {Object} params 参数
*/
geActivationThemeList: (params) => {
return HttpHandler.Get(`/api/content/themes/activation`, params)
},
/**
* 获取激活的主题设置
* @param {Object} params 参数
*/
getActivationThemeSettings: (params) => {
return HttpHandler.Get(`/api/content/themes/activation/settings`, params)
},
/**
* 根据主题ID列出主题设置
* @param {Object} params 参数
*/
getThemeSettingsByThemeId: (themeId) => {
return HttpHandler.Get(`/api/content/themes/${themeId}/settings`)
},
/**
* 通过主题ID获取主题属性
* @param {Object} params 参数
*/
getThemePropertyByThemeId: (themeId) => {
return HttpHandler.Get(`/api/content/themes/${themeId}`)
},
}
-1
View File
@@ -1 +0,0 @@
+216
View File
@@ -0,0 +1,216 @@
/**
* 所有的接口
*/
import HaloTokenConfig from '@/config/token.config.js'
import HttpHandler from '@/common/http/request.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)
},
/**
* 查询分类列表
* @param {Object} params 查询参数
*/
getCategoryList: (params) => {
return HttpHandler.Get('/apis/api.content.halo.run/v1alpha1/categories', params)
},
/**
* 查询分类下的文章
* @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: {
systemToken: HaloTokenConfig.systemToken
}
})
},
/**
* 查询站点统计信息
*/
getBlogStatistics: () => {
return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/stats/-`, {})
},
/**
* 获取相册分组
*/
getPhotoGroupList: (params) => {
return HttpHandler.Get(`/apis/core.halo.run/v1alpha1/photogroups`, params, {
custom: {
systemToken: HaloTokenConfig.systemToken
}
})
},
/**
* 根据分组获取相册
*/
getPhotoListByGroupName: (params) => {
return HttpHandler.Get(`/apis/console.api.photo.halo.run/v1alpha1/photos`, params, {
custom: {
systemToken: HaloTokenConfig.systemToken
}
})
},
/**
* 获取友链分组
*/
getFriendLinkGroupList: (params) => {
return HttpHandler.Get(`/apis/core.halo.run/v1alpha1/linkgroups`, params,{
custom: {
systemToken: HaloTokenConfig.systemToken
}
})
},
/**
* 获取友链
*/
getFriendLinkList: (params) => {
return HttpHandler.Get(`/apis/api.plugin.halo.run/v1alpha1/plugins/PluginLinks/links`, params)
},
/**
* 校验文章访问密码
*/
checkPostVerifyCode: (verifyCode, postId) => {
return HttpHandler.Get(`/apis/tools.muyin.site/v1alpha1/verificationCode/check?code=${verifyCode}`, null, {
header: {
'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
'Wechat-Session-Id': uni.getStorageSync('openid'),
'Post-Id': postId
}
})
},
/**
* 校验文章访问密码
*/
checkPostPasswordAccess: (password, postId) => {
return HttpHandler.Get(`/apis/tools.muyin.site/v1alpha1/visitPassword/checkPost?password=${password}`,
null, {
header: {
'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
'Wechat-Session-Id': uni.getStorageSync('openid'),
'Post-Id': postId
}
})
},
/**
* 获取文章验证码
*/
getPostVerifyCode: () => {
return HttpHandler.Get(`/apis/tools.muyin.site/v1alpha1/verificationCode/create`, null, {
header: {
'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
}
})
},
/**
* 提交友情链接
*/
submitLink(form) {
return HttpHandler.Post(`/apis/linksSubmit.muyin.site/v1alpha1/submit`, form, null)
},
/**
* 获取二维码信息
*/
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,)
}
}
+10
View File
@@ -0,0 +1,10 @@
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取应用基本配置
*/
getAppConfigs: () => {
return HttpHandler.Get(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getConfigs`, {})
},
}
+3 -3
View File
@@ -10,7 +10,7 @@
* 修改时间:
*/
import HaloConfig from '@/config/halo.config.js'
import HaloTokenConfig from '@/config/token.config.js'
import {
setInterceptors
} from "./interceptors.js";
@@ -21,16 +21,16 @@ const http = new Request()
http.setConfig((config) => {
// 如果是在外部浏览器调试或者编译为h5,请注释该行代码
config.baseURL = HaloConfig.apiUrl;
config.baseURL = HaloTokenConfig.BASE_API;
config.header = {
...config.header,
'api-authorization': HaloConfig.apiAuthorization,
ContentType: 'application/json',
dataType: 'json'
}
return config
})
setInterceptors(http)
export {
+92 -19
View File
@@ -13,18 +13,66 @@ import {
getAdminAccessToken
} from "@/utils/auth.js";
import {
delCache
delCache,
setCache
} from "@/utils/storage";
const getCategoryNameByUrl = (url) => {
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) {}
},
})
}
export const setInterceptors = (http) => {
http.interceptors.request.use(
(config) => {
console.log("config", config)
// 可使用async await 做异步操作
config.header = {
...config.header
// ... 可以直接加参数
};
if (getAdminAccessToken()) {
config.header['admin-authorization'] = getAdminAccessToken()
if (config.custom.systemToken) {
config.header['Authorization'] = `Bearer ${config.custom.systemToken}`
}
return config;
},
@@ -57,24 +105,49 @@ export const setInterceptors = (http) => {
message: 'API接口服务异常!'
})
} else if (response.data.status == 401) {
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'
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)
}
})
}).catch(err => {
uni.switchTab({
url: '/pages/tabbar/about/about'
} 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);
}
-2
View File
@@ -1,8 +1,6 @@
/**
* 封装各种请求方式
*/
import {
http
} from '@/common/http/index.js'
+34 -31
View File
@@ -2,11 +2,14 @@
* markdown配置
*/
import HaloConfig from '@/config/halo.config.js'
import {getAppConfigs} from '@/config/index.js'
import HaloTokenConfig from '@/config/token.config.js'
import utils from '@/utils/index.js'
export default {
domain: HaloConfig.apiUrl,
tagStyle: {
table: `
domain: HaloTokenConfig.BASE_API,
tagStyle: {
table: `
table-layout: fixed;
border-collapse:collapse;
margin-bottom: 18px;
@@ -17,17 +20,17 @@ export default {
border: 1px solid #dcdcdc;
border-radius: 4px;
`,
th: `
th: `
padding: 8px;
border-right: 1px solid var(--classE);
border-bottom: 1px solid var(--classE);
`,
td: `
td: `
padding: 8px;
border-right: 1px solid var(--classE);
border-bottom: 1px solid var(--classE);
`,
blockquote: `
blockquote: `
padding: 8px 15px;
color: #606266;
background: #f2f6fc;
@@ -36,62 +39,62 @@ export default {
line-height: 26px;
margin-bottom: 18px;
`,
ul: 'padding-left: 15px;line-height: 1.85;',
ol: 'padding-left: 15px;line-height: 1.85;',
li: 'margin-bottom: 12px;line-height: 1.85;',
h1: `
ul: 'padding-left: 15px;line-height: 1.85;',
ol: 'padding-left: 15px;line-height: 1.85;',
li: 'margin-bottom: 12px;line-height: 1.85;',
h1: `
margin: 30px 0 20px;
color: var(--main);
line-height: 24px;
position: relative;
font-size:1.25em;
font-size:1.2em;
`,
h2: `
h2: `
color: var(--main);
line-height: 24px;
position: relative;
margin: 22px 0 16px;
font-size: 1.2em;
font-size: 1.16em;
`,
h3: `
h3: `
color: var(--main);
line-height: 24px;
position: relative;
margin: 26px 0 18px;
font-size: 1.3em;
font-size: 1.14em;
`,
h4: `
h4: `
color: var(--main);
line-height: 24px;
margin-bottom: 18px;
position: relative;
font-size: 1.18em;
font-size: 1.12em;
`,
h5: `
h5: `
color: var(--main);
line-height: 24px;
margin-bottom: 14px;
position: relative;
font-size: 1em;
font-size: 1.1em;
`,
h6: `
h6: `
color: #303133;
line-height: 24px;
margin-bottom: 14px;
position: relative;
font-size: 1em;
font-size: 14px;
`,
p: `
p: `
line-height: 1.65;
margin-bottom: 14px;
font-size: 15px;
font-size: 14px;
`,
'code': ` `,
strong: 'font-weight: 700;color: rgb(248, 57, 41);',
video: 'width: 100%',
},
containStyle: 'font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;padding:12px;font-size: 16px;color: #606266;word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 6px;background-color:#FFFFFF;',
'code': ` `,
strong: 'font-weight: 700;color: rgb(248, 57, 41);',
video: 'width: 100%',
},
containStyle: 'font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;padding:12px;font-size: 14px;color: #606266;word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 6px;background-color:#FFFFFF;',
loadingGif: HaloConfig.loadingGifUrl,
emptyGif: HaloConfig.loadingEmptyUrl,
loadingGif: utils.checkImageUrl(getAppConfigs().imagesConfig.loadingGifUrl),
emptyGif: utils.checkImageUrl(getAppConfigs().imagesConfig.loadingEmptyUrl),
}
+71 -53
View File
@@ -9,59 +9,77 @@
* 修改人员:
* 修改时间:
*/
import HaloConfig from '@/config/halo.config.js';
import HaloAdConfig from '@/config/ad.config.js';
export default {
install(Vue) {
Vue.mixin({
data() {
return {
author: HaloConfig.author,
_isWechat: true,
haloAdConfig: HaloAdConfig
};
},
computed: {
// 获取全局应用设置
globalAppSettings() {
return uni.$tm.vx.getters().setting.getSettings;
}
},
created() {
// #ifdef MP-WEIXIN
this._isWechat = true;
uni.$tm.vx.commit('setWxShare', HaloConfig.wxShareConfig);
// #endif
// #ifndef MP-WEIXIN
this._isWechat = false;
// #endif
},
install(Vue) {
Vue.mixin({
data() {
return {
_isWechat: true,
_aniWaitIndex: 0
};
},
computed: {
haloConfig() {
return this.$tm.vx.getters().getConfigs;
},
haloPluginsConfig() {
return this.$tm.vx.getters().getConfigs?.pluginConfig;
},
// 获取全局应用设置
globalAppSettings() {
return uni.$tm.vx.getters().getSettings;
},
// 计算动画索引
calcAniWait() {
return (index) => {
if ((index + 1) % 10 === 0) {
this._aniWaitIndex = 1;
} else {
this._aniWaitIndex += 1;
}
return this._aniWaitIndex * 50
}
}
},
onLoad() {
this.fnResetSetAniWaitIndex()
},
created() {
// #ifdef MP-WEIXIN
this._isWechat = true;
// #endif
// #ifndef MP-WEIXIN
this._isWechat = false;
// #endif
},
onShow() {
this.fnResetSetAniWaitIndex()
},
methods: {
// 设置页面标题
fnSetPageTitle(title) {
uni.setNavigationBarTitle({
title: title || this.haloConfig?.appConfig?.startConfig?.title || "uni-halo",
})
},
methods: {
/**
* 设置页面标题
* @param {Object} title 标题
*/
fnSetPageTitle(title) {
uni.setNavigationBarTitle({
title: title || HaloConfig.title
})
},
/**
* 页面返回顶部
*/
fnToTopPage(duration = 500) {
duration = isNaN(duration) ? 500 : duration
uni.pageScrollTo({
scrollTop: 0,
duration: duration,
fail: (err) => {
console.log('err', err);
},
});
}
},
});
},
// 页面返回顶部
fnToTopPage(duration = 500) {
duration = isNaN(duration) ? 500 : duration
uni.pageScrollTo({
scrollTop: 0,
duration: duration,
fail: (err) => {
console.log('err', err);
},
});
},
// 初始化动画索引值(需要在每个页面调用)
fnResetSetAniWaitIndex() {
this._aniWaitIndex = 0
}
},
});
},
};
+136
View File
@@ -36,3 +36,139 @@
transform: rotateZ(360deg);
}
}
// 自定义广告
.ad-card {
position: relative;
box-sizing: border-box;
padding: 24rpx;
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.03);
background-color: #ffffff;
border-radius: 12rpx;
&.is-lr {
display: flex;
.ad-card_tip {
top: initial;
left: 288rpx;
bottom: 24rpx;
// right: 160rpx;
padding: 4rpx 20rpx;
background-color: transparent;
border: 2rpx solid #607d8b;
color: #607d8b;
}
.ad-card_cover {
width: 240rpx;
height: 180rpx;
animation: adLinkAni 1s ease-in-out infinite;
}
.ad-card_info {
width: 0;
flex-grow: 1;
padding-left: 24rpx;
&-title {
margin-top: 0;
width: 100%;
font-size: 30rpx;
}
&-link {
top: initial;
// right: 150rpx;
bottom: 0;
transform: scale(1);
}
}
}
&_tip {
position: absolute;
left: 36rpx;
top: 36rpx;
display: inline-flex;
box-sizing: border-box;
border-radius: 6rpx;
padding: 4rpx 10rpx;
background-color: rgba(0, 0, 0, 0.3);
color: #ffffff;
font-size: 24rpx;
z-index: 2;
}
&_cover {
width: 100%;
height: 300rpx;
border-radius: 6rpx;
}
&_info {
display: flex;
flex-direction: column;
box-sizing: border-box;
position: relative;
&-title {
width: calc(100% - 120rpx);
font-size: 28rpx;
font-weight: bold;
margin-top: 8rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #333;
}
&-desc {
margin-top: 6rpx;
font-size: 24rpx;
color: #616161;
line-height: 1.8;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
&-link {
position: absolute;
right: 0;
top: 6rpx;
display: inline-flex;
box-sizing: border-box;
border-radius: 6rpx;
padding: 4rpx 10rpx;
border: 2rpx solid #03a9f4;
color: #03a9f4;
font-size: 24rpx;
transform: scale(0.93);
// animation: adLinkAni 0.8s ease-in-out infinite;
}
}
}
@keyframes adLinkAni {
0% {
transform: scale(1);
}
50% {
transform: scale(1.02);
}
100% {
transform: scale(1);
}
}
/* 文本省略样式 开始 */
.text-sub-one {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 多行文本省略 */
@for $i from 2 through 30 {
.text-sub-more-#{$i} {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: $i;
overflow: hidden;
}
}
/* 文本省略样式 结束 */
+292 -244
View File
@@ -1,30 +1,23 @@
<template>
<view class="article-card " :class="cardType" @click="fnClickEvent('card')">
<view class="left">
<cache-image
class="thumbnail"
radius="12rpx"
:url="$utils.checkThumbnailUrl(article.thumbnail)"
:fileMd5="$utils.checkThumbnailUrl(article.thumbnail)"
mode="aspectFill"
></cache-image>
<!-- <image class="thumbnail" lazy-load :src="$utils.checkThumbnailUrl(article.thumbnail)" mode="aspectFill"></image> -->
<cache-image class="thumbnail" radius="6rpx" :url="$utils.checkThumbnailUrl(article.spec.cover)" :fileMd5="article.spec.cover" mode="aspectFill"></cache-image>
<!-- <image class="thumbnail" lazy-load :src="$utils.checkThumbnailUrl(article.spec.cover)" mode="aspectFill"></image> -->
</view>
<view class="right">
<view class="title">
<text class="is-top" v-if="article.topped">置顶</text>
<text class="title-text text-overflow">{{ article.title }}</text>
<text class="is-top bg-gradient-blue-accent" v-if="article.spec.pinned">置顶</text>
<text class="title-text text-overflow">{{ article.spec.title }}</text>
</view>
<view class="content text-overflow-2">{{ article.summary }}</view>
<view class="content text-overflow-2">{{ article.status.excerpt }}</view>
<view class="foot">
<view class="create-time">
<text class="time-label">发布时间</text>
{{ { d: article.createTime, f: 'yyyy-MM-dd' } | formatTime }}
{{ { d: article.spec.publishTime, f: 'yyyy-MM-dd' } | formatTime }}
</view>
<view class="visits">
<!-- <tm-icons :size="24" name="icon-filter-fill"></tm-icons> -->
浏览
<text class="number">{{ article.visits }}</text>
<text class="number">{{ article.stats.visit }}</text>
</view>
</view>
@@ -33,272 +26,327 @@
</template>
<script>
export default {
name: 'article-card',
props: {
from: {
type: String,
default: ''
},
article: {
type: Object,
default: () => {}
}
},
computed: {
cardType() {
// tb_image_text=上图下文
// tb_text_image=上文下图
if (this.from == 'home' && this.globalAppSettings.layout.home == 'h_row_col2') {
if (!['tb_image_text', 'tb_text_image', 'only_text'].some(x => x == this.globalAppSettings.layout.cardType)) {
return [this.from, this.globalAppSettings.layout.home, 'tb_image_text'];
}
return [this.from, this.globalAppSettings.layout.home, this.globalAppSettings.layout.cardType];
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
export default {
name: 'article-card',
components: { tmTags },
props: {
from: {
type: String,
default: ''
},
article: {
type: Object,
default: () => {}
}
},
computed: {
cardType() {
// tb_image_text=上图下文
// tb_text_image=上文下图
if (this.from == 'home' && this.globalAppSettings.layout.home == 'h_row_col2') {
if (!['tb_image_text', 'tb_text_image', 'only_text'].some(x => x == this.globalAppSettings.layout.cardType)) {
return [this.from, this.globalAppSettings.layout.home, 'tb_image_text'];
}
return [this.from, this.globalAppSettings.layout.home, this.globalAppSettings.layout.cardType];
}
return [this.globalAppSettings.layout.home, this.globalAppSettings.layout.cardType];
}
},
methods: {
fnClickEvent() {
this.$emit('on-click', this.article);
}
return [this.globalAppSettings.layout.home, this.globalAppSettings.layout.cardType];
}
},
methods: {
fnClickEvent() {
this.$emit('on-click', this.article);
}
}
};
};
</script>
<style scoped lang="scss">
.article-card {
display: flex;
box-sizing: border-box;
margin: 0 24rpx;
padding: 32rpx;
border-radius: 12rpx;
background-color: #ffff;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
overflow: hidden;
margin-bottom: 24rpx;
&.home {
&.h_row_col2 {
margin: 12rpx;
.article-card {
display: flex;
box-sizing: border-box;
margin: 0 24rpx;
padding: 24rpx;
border-radius: 12rpx;
background-color: #ffff;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
overflow: hidden;
margin-bottom: 24rpx;
&.h_row_col1 {
display: flex;
align-items: center;
}
&.home {
&.h_row_col2 {
margin: 12rpx;
.left {
width: 100%;
height: 200rpx;
.thumbnail {
::v-deep uni-image {
border-radius: 6rpx 6rpx 0 0 !important;
}
}
}
.right {
.title {
display: flex;
align-items: center;
font-size: 26rpx;
font-weight: bold;
.is-top {
height: 36rpx;
margin-right: 10rpx;
line-height: 36rpx;
vertical-align: 4rpx;
transform: scale(0.9);
}
}
.foot {
justify-content: space-between;
.create-time {
font-size: 24rpx;
.time-label {
display: none;
}
}
.visits {
font-size: 24rpx;
margin-left: 0;
}
}
}
&.tb_text_image {
padding: 12rpx;
.left .thumbnail {
::v-deep {
uni-image {
border-radius: 6rpx !important;
}
}
}
}
&.only_text {
padding: 24rpx;
.right .foot {
.create-time {
.time-label {
display: none;
}
}
.visits {
font-size: 24rpx;
}
}
}
}
}
&.lr_image_text {}
&.lr_text_image {
.left {
order: 2;
padding-left: 30rpx;
}
.right {
order: 1;
padding-left: 0;
}
}
&.tb_image_text {
flex-direction: column;
padding: 24rpx;
.left {
width: 100%;
height: 200rpx;
height: 340rpx;
.thumbnail {
::v-deep uni-image {
border-radius: 6rpx 6rpx 0 0 !important;
}
}
}
.right {
.title {
display: flex;
align-items: center;
font-size: 26rpx;
font-weight: bold;
.is-top {
height: 36rpx;
margin-right: 10rpx;
line-height: 36rpx;
vertical-align: 4rpx;
transform: scale(0.9);
}
}
padding-left: 0;
padding: 24rpx 0;
padding-bottom: 0;
width: 100%;
.foot {
justify-content: space-between;
.create-time {
font-size: 24rpx;
.time-label {
display: none;
}
}
.visits {
font-size: 24rpx;
margin-left: 0;
}
}
}
&.tb_text_image {
padding: 12rpx;
.left .thumbnail {
::v-deep {
uni-image {
border-radius: 6rpx !important;
}
}
}
}
&.only_text {
padding: 24rpx;
.right .foot {
.create-time {
.time-label {
display: none;
}
}
.visits {
font-size: 24rpx;
}
}
}
}
}
&.lr_image_text {
}
justify-content: flex-start;
&.lr_text_image {
.left {
order: 2;
padding-left: 30rpx;
.create-time {
.time-label {
display: inline-block;
}
}
.visits {
margin-left: 24rpx;
}
}
}
}
.right {
order: 1;
padding-left: 0;
&.tb_text_image {
flex-direction: column;
.left {
width: 100%;
height: 340rpx;
order: 2;
margin-top: 24rpx;
}
.right {
padding-left: 0;
width: 100%;
order: 1;
.foot {
justify-content: flex-start;
.create-time {
.time-label {
display: inline-block;
}
}
.visits {
margin-left: 24rpx;
}
}
}
}
}
&.tb_image_text {
flex-direction: column;
padding: 0;
&.only_text {
padding: 36rpx;
.left {
display: none;
}
.right {
padding-left: 0;
.content {
margin-top: 24rpx;
}
.foot {
justify-content: flex-start;
margin-top: 24rpx;
.create-time {
.time-label {
display: inline-block;
}
}
.visits {
margin-left: 24rpx;
}
}
}
}
.left {
width: 100%;
height: 300rpx;
width: 240rpx;
height: 180rpx;
.thumbnail {
::v-deep uni-image {
border-radius: 6rpx 6rpx 0 0 !important;
}
width: 100%;
height: 100%;
border-radius: 12rpx;
}
}
.right {
padding-left: 0;
padding: 24rpx;
width: 100%;
.foot {
justify-content: flex-start;
.create-time {
.time-label {
display: inline-block;
}
width: 0;
flex-grow: 1;
display: flex;
flex-direction: column;
padding-left: 30rpx;
box-sizing: border-box;
.title {
display: flex;
align-items: center;
font-size: 30rpx;
color: var(--main-text-color);
.is-top {
height: 40rpx;
padding: 0 12rpx;
margin-right: 10rpx;
line-height: 40rpx;
font-size: 24rpx;
white-space: nowrap;
vertical-align: 4rpx;
color: #fff;
// background-image: -webkit-linear-gradient(0deg, #3ca5f6 0, #a86af9 100%);
border-radius: 6rpx 12rpx;
box-shadow: none !important;
}
.visits {
margin-left: 24rpx;
&-text {
color: #303133;
}
}
}
}
&.tb_text_image {
flex-direction: column;
.left {
width: 100%;
height: 260rpx;
order: 2;
margin-top: 24rpx;
}
.right {
padding-left: 0;
width: 100%;
order: 1;
.foot {
justify-content: flex-start;
.create-time {
.time-label {
display: inline-block;
}
}
.visits {
margin-left: 24rpx;
}
}
}
}
&.only_text {
padding: 36rpx;
.left {
display: none;
}
.right {
padding-left: 0;
.content {
margin-top: 24rpx;
display: -webkit-box;
font-size: 26rpx;
color: #909399;
height: 80rpx;
margin-top: 14rpx;
line-height: 42rpx;
}
.foot {
justify-content: flex-start;
margin-top: 24rpx;
display: flex;
font-size: 24rpx;
justify-content: space-between;
align-items: center;
color: #909399;
margin-top: 18rpx;
.create-time {
font-size: 26rpx;
.time-label {
display: inline-block;
display: none;
}
}
.visits {
margin-left: 24rpx;
.number {
padding: 0 6rpx;
font-size: 26rpx;
}
}
}
}
}
.left {
width: 240rpx;
height: 180rpx;
.thumbnail {
width: 100%;
height: 100%;
border-radius: 12rpx;
}
}
.right {
width: 0;
flex-grow: 1;
display: flex;
flex-direction: column;
padding-left: 30rpx;
box-sizing: border-box;
.title {
display: flex;
font-size: 30rpx;
color: var(--main-text-color);
.is-top {
height: 40rpx;
padding: 0 12rpx;
margin-right: 10rpx;
line-height: 40rpx;
font-size: 24rpx;
white-space: nowrap;
vertical-align: 4rpx;
color: #fff;
background-image: -webkit-linear-gradient(0deg, #3ca5f6 0, #a86af9 100%);
border-radius: 4rpx 12rpx;
}
&-text {
color: #303133;
}
}
.content {
display: -webkit-box;
font-size: 26rpx;
color: #909399;
height: 80rpx;
margin-top: 14rpx;
line-height: 42rpx;
}
.foot {
display: flex;
font-size: 24rpx;
justify-content: space-between;
align-items: center;
color: #909399;
margin-top: 18rpx;
.create-time {
font-size: 26rpx;
.time-label {
display: none;
}
}
.visits {
.number {
padding: 0 6rpx;
font-size: 26rpx;
}
}
}
}
}
</style>
+179 -156
View File
@@ -1,13 +1,7 @@
<template>
<view class="article-min-card" :class="[globalAppSettings.layout.cardType]" @click="fnClickEvent('card')">
<view class="left">
<cache-image
class="thumbnail"
radius="12rpx"
:url="$utils.checkThumbnailUrl(article.thumbnail)"
:fileMd5="$utils.checkThumbnailUrl(article.thumbnail)"
mode="aspectFill"
></cache-image>
<cache-image class="thumbnail" radius="6rpx" :url="$utils.checkThumbnailUrl(article.thumbnail)" :fileMd5="$utils.checkThumbnailUrl(article.thumbnail)" mode="aspectFill"></cache-image>
</view>
<view class="right">
<view class="title text-overflow">{{ article.title }}</view>
@@ -30,168 +24,197 @@
</template>
<script>
export default {
name: 'article-min-card',
props: {
article: {
type: Object,
default: () => {}
export default {
name: 'article-min-card',
props: {
article: {
type: Object,
default: () => {}
}
},
methods: {
fnClickEvent() {
this.$emit('on-click', this.article);
}
}
},
methods: {
fnClickEvent() {
this.$emit('on-click', this.article);
}
}
};
};
</script>
<style scoped lang="scss">
.article-min-card {
display: flex;
box-sizing: border-box;
border-radius: 12rpx;
background-color: #ffff;
overflow: hidden;
margin: 12rpx 24rpx;
margin-bottom: 24rpx;
padding: 16rpx;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.05);
&.lr_image_text {
}
&.lr_text_image {
.left {
order: 2;
padding-left: 30rpx;
}
.right {
order: 1;
padding-left: 0;
}
}
&.tb_image_text {
flex-direction: column;
.left {
width: 100%;
height: 220rpx;
}
.right {
padding-left: 0;
width: 100%;
.title {
margin-top: 24rpx;
}
.foot {
justify-content: flex-start;
.create-time {
.time-label {
display: inline-block;
}
}
.visits {
margin-left: 24rpx;
}
}
}
}
&.tb_text_image {
flex-direction: column;
.left {
width: 100%;
height: 220rpx;
order: 2;
margin-top: 20rpx;
}
.right {
padding-left: 0;
width: 100%;
order: 1;
.foot {
justify-content: flex-start;
.create-time {
.time-label {
display: inline-block;
}
}
.visits {
margin-left: 24rpx;
}
}
}
}
&.only_text {
.left {
display: none;
}
.right {
padding-left: 0;
.foot {
justify-content: flex-start;
.create-time {
.time-label {
display: inline-block;
}
}
.visits {
margin-left: 24rpx;
}
}
}
}
.left {
width: 180rpx;
height: 130rpx;
.thumbnail {
width: 100%;
height: 100%;
border-radius: 12rpx;
}
}
.right {
width: 0;
flex-grow: 1;
.article-min-card {
display: flex;
flex-direction: column;
padding-left: 24rpx;
.title {
font-size: 28rpx;
font-weight: 600;
color: var(--main-text-color);
box-sizing: border-box;
border-radius: 12rpx;
background-color: #ffff;
overflow: hidden;
margin: 12rpx 24rpx;
margin-bottom: 24rpx;
padding: 16rpx;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
&.lr_image_text {}
&.lr_text_image {
.left {
order: 2;
padding-left: 30rpx;
}
.right {
order: 1;
padding-left: 0;
}
}
.content {
font-size: 26rpx;
color: #909399;
margin-top: 14rpx;
}
.foot {
display: flex;
font-size: 24rpx;
justify-content: space-between;
align-items: center;
color: #909399;
margin-top: 14rpx;
.create-time {
font-size: 24rpx;
.time-label {
display: none;
&.tb_image_text {
flex-direction: column;
.left {
width: 100%;
height: 220rpx;
}
.right {
padding-left: 0;
width: 100%;
.title {
margin-top: 24rpx;
}
.icon {
font-size: 24rpx;
padding-right: 4rpx;
.foot {
justify-content: flex-start;
.create-time {
.time-label {
display: inline-block;
}
}
.visits {
margin-left: 24rpx;
}
}
}
.visits {
.icon {
font-size: 28rpx;
}
&.tb_text_image {
flex-direction: column;
.left {
width: 100%;
height: 220rpx;
order: 2;
margin-top: 20rpx;
}
.right {
padding-left: 0;
width: 100%;
order: 1;
.foot {
justify-content: flex-start;
.create-time {
.time-label {
display: inline-block;
}
}
.visits {
margin-left: 24rpx;
}
}
.number {
padding: 0 6rpx;
}
}
&.only_text {
.left {
display: none;
}
.right {
padding-left: 0;
.foot {
justify-content: flex-start;
.create-time {
.time-label {
display: inline-block;
}
}
.visits {
margin-left: 24rpx;
}
}
}
}
.left {
width: 180rpx;
height: 130rpx;
.thumbnail {
width: 100%;
height: 100%;
border-radius: 12rpx;
}
}
.right {
width: 0;
flex-grow: 1;
display: flex;
flex-direction: column;
padding-left: 20rpx;
.title {
font-size: 28rpx;
font-weight: 600;
color: var(--main-text-color);
}
.content {
font-size: 26rpx;
color: #909399;
margin-top: 14rpx;
}
.foot {
display: flex;
font-size: 24rpx;
justify-content: space-between;
align-items: center;
color: #909399;
margin-top: 14rpx;
.create-time {
font-size: 24rpx;
.time-label {
display: none;
}
.icon {
font-size: 24rpx;
padding-right: 4rpx;
}
}
.visits {
.icon {
font-size: 28rpx;
}
.number {
padding: 0 6rpx;
font-size: 24rpx;
}
}
}
}
}
}
</style>
@@ -1,207 +0,0 @@
<template>
<tm-poup v-model="show" position="bottom" height="auto" @change="fnClose">
<view class="poup-head pa-24 text-align-center text-weight-b ">{{ title }}</view>
<view class="poup-body pa-24 pt-0 pb-0">
<view v-if="loading != 'success'" class="loading-wrap flex flex-center">
<view v-if="loading == 'loading'" class="loading">加载中...</view>
<view v-else class="error" @click="fnGetData()">加载失败点击刷新</view>
</view>
<block v-else>
<view v-if="total == 0" class="empty">无附件</view>
<scroll-view v-else class="poup-content" :enable-flex="true" :scroll-y="true" @touchmove.stop>
<view class="card-content">
<view class="card pa-12" v-for="(file, index) in dataList" :key="index" @click="fnOnSelect(file, index)">
<view class="card-inner round-3" :class="{ 'is-select': selectIndex == index }">
<cache-image v-if="file.isImage" class="cover" height="160rpx" :url="file.thumbPath" :fileMd5="file.thumbPath" mode="aspectFill"></cache-image>
<view v-else class="cover flex pl-46 pr-46 flex-center bg-gradient-blue-grey-accent text-align-center text-size-m">{{ file.mediaType }}</view>
<view class="name text-overflow text-size-m pa-12">{{ file.name }}</view>
</view>
</view>
</view>
</scroll-view>
</block>
</view>
<view class="poup-foot pa-30 pb-12 pt-0">
<!-- 分页 -->
<view v-if="total > queryParams.size" class="mt-36 pl-24 pr-24">
<tm-pagination color="bg-gradient-blue-accent" :page.sync="queryParams.page" :total="total" :totalVisible="5" @change="fnGetPagingData"></tm-pagination>
</view>
<view class=" flex flex-center mt-12">
<tm-button size="m" theme="bg-gradient-blue-accent" @click="fnSava()">确定选用</tm-button>
<tm-button size="m" theme="bg-gradient-orange-accent" @click="fnUpload()">上传</tm-button>
<tm-button size="m" theme="bg-gradient-blue-grey-accent" @click="fnClose(false)"> </tm-button>
</view>
</view>
</tm-poup>
</template>
<script>
import { getAdminAccessToken } from '@/utils/auth.js';
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
import tmPagination from '@/tm-vuetify/components/tm-pagination/tm-pagination.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
export default {
name: 'attachment-select',
components: { tmPoup, tmPagination, tmButton },
props: {
title: {
type: String,
default: '附件列表'
},
selectType: {
type: String,
default: ''
}
},
data() {
return {
show: true,
loading: 'loading',
total: 0,
queryParams: {
size: 6,
page: 1
},
dataList: [],
select: null,
selectIndex: -1
};
},
created() {
this.fnGetData();
},
methods: {
fnGetData() {
this.queryParams.page = 1;
this.fnGetPagingData(1);
},
fnGetPagingData(page) {
this.loading = 'loading';
const _params = {
...this.queryParams
};
_params.page = page - 1;
this.$httpApi.admin
.getAttachmentsByPage(_params)
.then(res => {
if (res.status == 200) {
this.total = res.data.total;
this.dataList = res.data.content.map(file => {
if (this.$utils.fnCheckIsFileType('image', file) && file.size / 1024 / 1024 > 2) {
file.isImage = false;
file.desc = '图片过大无法显示缩略图';
} else {
file.isImage = this.$utils.fnCheckIsFileType('image', file);
}
file.thumbPath = this.$utils.checkThumbnailUrl(file.thumbPath);
return file;
});
this.loading = 'success';
} else {
uni.$tm.toast('加载失败,请重试!');
this.loading = 'error';
}
})
.catch(err => {
console.error(err);
uni.$tm.toast('加载失败,请重试!');
this.loading = 'error';
});
},
fnOnSelect(file, index) {
this.select = file;
this.selectIndex = index;
},
fnSava() {
if (this.selectType) {
if (this.$utils.fnCheckIsFileType(this.selectType, this.select)) {
this.$emit('on-select', this.select);
} else {
uni.$tm.toast('该附件类型不符合!');
}
} else {
this.$emit('on-select', this.select);
}
},
fnClose(e) {
if (!e) {
this.$emit('on-close');
}
},
fnUpload() {
uni.chooseImage({
count: 1,
success: res => {
uni.uploadFile({
filePath: res.tempFilePaths[0],
header: {
'admin-authorization': getAdminAccessToken()
},
url: this.$baseApiUrl + '/api/admin/attachments/upload',
name: 'file',
success: upladRes => {
const _uploadRes = JSON.parse(upladRes.data);
if (_uploadRes.status == 200) {
uni.$tm.toast('上传成功!');
this.fnGetData(1);
} else {
uni.$tm.toast(_uploadRes.message);
}
},
fail: err => {
uni.$tm.toast(err.message);
}
});
}
});
}
}
};
</script>
<style scoped lang="scss">
.poup-head {
}
.poup-body {
height: 50vh;
}
.loading-wrap {
height: 50vh;
background-color: #fafafa;
}
.poup-content {
height: inherit;
box-sizing: border-box;
.card-content {
height: inherit;
display: flex;
flex-wrap: wrap;
}
}
.card {
width: 50%;
box-sizing: border-box;
&-inner {
box-sizing: border-box;
overflow: hidden;
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.05);
border: 4rpx solid transparent;
&.is-select {
border-color: rgb(13, 141, 242);
}
}
.cover {
width: 100%;
height: 160rpx;
flex-wrap: wrap;
box-sizing: border-box;
}
.name {
color: #303133;
box-sizing: border-box;
text-align: center;
}
}
</style>
@@ -1,15 +1,11 @@
<template>
<view style="overflow: hidden;position: fixed;width: 100%;height: 100%;pointer-events: none; top: 0;">
<view class="danmu-li" v-for="(item, index) in listData" :class="item.type" :style="item.style" :key="index">
<view class="barrage-wrap" :class="[type]">
<view class="danmu-li" v-for="(item, index) in listData" :class="[type, item.type]" :style="[item.style]" :key="index">
<view class="danmu-inner">
<view class="user-box">
<view class="user-img">
<view class="img-box">
<image :src="item.avatar || 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=317894666,3379114684&fm=26&gp=0.jpg'"></image>
</view>
</view>
<view class="user-text cl1">{{ item.nickName }}</view>
<view class="user-status cl1">{{ item.text }}</view>
<view class="user-img"><image class="avatar" :src="item.item.avatar"></image></view>
<view class="user-text cl1">{{ item.item.author }}</view>
<view class="user-status cl1">{{ item.item.content }}</view>
</view>
</view>
</view>
@@ -18,11 +14,17 @@
<script>
export default {
props: {
//rightToLeft leftToRight leftBottom
// rightToLeft leftToRight leftBottom
type: {
type: String,
default: 'rightToLeft'
},
list: {
type: Array,
default() {
return [];
}
},
minTime: {
type: Number,
default: 4
@@ -43,34 +45,34 @@ export default {
//
type: Number,
default: 40
},
noStacked: {
//()
type: Array,
default() {
return [];
}
}
},
data() {
return {
closeTimer: null,
listData: []
};
},
mounted() {
this.hrackNum = Math.floor((this.maxTop - this.minTop) / this.hrackH);
//leftBottom 使
if (this.type === 'leftBottom') {
this.hrackNum = Math.floor(this.maxTop / this.hrackH);
}
},
methods: {
add(obj) {
this.isShow = true;
this.showFlag = true;
let data = {
item: obj.item,
...obj,
id: Date.parse(new Date()),
time: Math.ceil(Math.floor(Math.random() * (this.maxTime - this.minTime + 1) + this.minTime)),
type: this.type
};
if (this.type === 'leftBottom') {
let objData = {
item: data.item,
item: data,
type: 'leftBottomEnter',
style: {
transition: `all 0.5s`,
@@ -88,7 +90,7 @@ export default {
} else if (this.listData[i].status === 'reset') {
//
this.listData[i].style.transition = 'none';
this.listData[i].style.bottom = 0;
this.listData[i].style.bottom = '6px';
this.listData[i].status = 'reuse';
} else if (this.listData[i].status === 'recycle') {
//
@@ -105,8 +107,16 @@ export default {
if (listLen < hrackNum + 2) {
this.listData.push(objData);
}
} else if (this.type === 'rightToLeft' || this.type === 'leftToRight') {
let objData = this.horStacked(data);
} else if (this.type === 'rightToLeft') {
let objData = {
item: data,
type: 'rightToLeft',
style: {
animationDuration: `${data.time}s`,
top: `${Math.ceil(Math.random() * (this.maxTop - this.minTop + 1) + this.minTop)}px`
},
delTime: Date.parse(new Date()) + data.time * 1200
};
for (let i in this.listData) {
if (this.listData[i].delTime <= Date.parse(new Date())) {
this.repaint(i, objData.type);
@@ -118,16 +128,50 @@ export default {
this.listData.push(objData);
}
},
horStacked(data) {
return {
item: data.item,
type: this.type,
style: {
animationDuration: `${data.time}s`,
top: `${Math.ceil(Math.random() * (this.maxTop - this.minTop + 1) + this.minTop)}px`
async remove(options = {}) {
if (this.type == 'rightToLeft') {
if (this.listData.length != 0) {
const last = this.listData[this.listData.length - 1];
setTimeout(() => {
this.listData = [];
}, last.item.time * 1200 + 1000);
}
return;
}
options = Object.assign(
{},
{
duration: 5000, //
speed: 1000 //
},
delTime: Date.parse(new Date()) + data.time * 1200
options
);
const _fnHandleRemove = item => {
return new Promise(resolve => {
setTimeout(() => {
// item['type'] = 'leftBottomExitLeft';
item['type'] = 'is-hide';
resolve();
}, options.speed);
});
};
const _fnHandleLoop = item => {
return new Promise(resolve => {
setTimeout(async () => {
for (var i = 0; i < this.listData.length; i++) {
await _fnHandleRemove(this.listData[i]);
}
resolve();
}, options.duration);
});
};
await _fnHandleLoop();
setTimeout(() => {
this.listData = [];
}, options.duration + 200);
},
repaint(index, type) {
setTimeout(() => {
@@ -137,8 +181,8 @@ export default {
}
};
</script>
<style></style>
<style lang="scss">
<style lang="scss" scoped>
@keyframes leftBottomEnter {
0% {
transform: translateY(100%);
@@ -163,6 +207,20 @@ export default {
}
}
@keyframes leftBottomExitLeft {
0% {
transform: translateX(0%);
opacity: 1;
}
50% {
transform: translateX(-50%);
opacity: 1;
}
100% {
transform: translateX(-100%);
opacity: 0;
}
}
@keyframes leftToRight {
0% {
transform: translateX(-100%);
@@ -182,13 +240,38 @@ export default {
transform: translateX(-100%);
}
}
.barrage-wrap {
position: fixed;
width: 100%;
height: 100%;
pointer-events: none;
top: 0;
z-index: 99;
transition: all 1s ease-in-out;
&.leftBottom {
top: initial;
/* #ifdef H5 */
bottom: 130rpx;
/* #endif */
/* #ifndef H5 */
bottom: 36rpx;
/* #endif */
}
}
.danmu-li {
position: absolute;
width: 100%;
transform: translateX(100%);
animation-timing-function: linear;
transition: all 0.5s ease-in-out;
opacity: 1;
&.is-hide {
opacity: 0;
// transform: translateX(-100%) !important;
}
&.leftBottom {
left: 24rpx;
}
&.leftBottomEnter {
animation-name: leftBottomEnter;
}
@@ -196,53 +279,59 @@ export default {
animation-name: leftBottomExit;
animation-fill-mode: forwards;
}
&.leftBottomExitLeft {
animation-name: leftBottomExitLeft;
animation-fill-mode: forwards;
}
&.rightToLeft {
animation-name: rightToLeft;
}
&.leftToRight {
animation-name: leftToRight;
animation-name: rightToLeft;
}
.danmu-inner {
display: inline-block;
font-size: 24rpx;
.user-box {
display: flex;
padding: 3rpx 40rpx 3rpx 10rpx;
padding: 6rpx 40rpx 6rpx 10rpx;
background: rgba(0, 0, 0, 0.3);
border-radius: 32rpx;
align-items: center;
.user-img {
.img-box {
display: flex;
image {
width: 58rpx;
height: 58rpx;
background: rgba(55, 55, 55, 1);
border-radius: 50%;
}
display: flex;
align-items: center;
justify-content: center;
.avatar {
width: 42rpx;
height: 42rpx;
background: rgba(55, 55, 55, 1);
border-radius: 50%;
}
}
.user-status {
margin-left: 10rpx;
white-space: nowrap;
font-size: 28rpx;
font-weight: 400;
color: rgba(255, 255, 255, 1);
max-width: 320rpx;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.user-text {
max-width: 100rpx;
margin-left: 10rpx;
// white-space: nowrap;
font-size: 28rpx;
font-weight: 400;
width: 80rpx;
color: rgba(255, 255, 255, 1);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
}
@@ -1,194 +0,0 @@
<template>
<view class="bottom-tool-bar">
<tm-translate :auto="true" animation-name="fadeUp">
<view class="content flex">
<view class="input" @click="fnToComment()">
<text class="icon iconfont icon-edit"></text>
<text class="text">(*^^*)说点啥吧~</text>
</view>
<view class="right flex">
<!-- 点赞 -->
<view class="item likes" @click="fnDoLikes()">
<view class="iconfont icon-like"></view>
<view class="text">{{ tempPost.likes }}</view>
</view>
<!-- 评论 -->
<view class="item comment">
<view class="iconfont icon-comment-dots"></view>
<view class="text">{{ tempPost.commentCount }}</view>
</view>
<!-- 分享 -->
<view class="item share" @click="fnOnShare()"><text class="iconfont icon-share1"></text></view>
</view>
</view>
</tm-translate>
<tm-shareSheet @change="fnOnShareChange" :actions="share.list" title="分享文章" v-model="share.show"></tm-shareSheet>
</view>
</template>
<script>
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmShareSheet from '@/tm-vuetify/components/tm-shareSheet/tm-shareSheet.vue';
export default {
name: 'bottom-tool-bar',
components: {
tmTranslate,
tmShareSheet
},
props: {
// 文章数据
post: {
type: Object,
default: () => {}
},
// 其他参数
params: {
type: Object,
default: () => {}
}
},
data() {
return {
share: {
show: false,
list: [
[
{ name: '微信好友', bgcolor: '#07c160', icon: 'icon-weixin', color: 'white' },
{ name: '朋友圈', bgcolor: '#04c887', icon: 'icon-pengyouquan', color: 'white' },
{ name: '生成海报', bgcolor: '#1dc0fd', icon: 'icon-QQ', color: 'white' }
]
]
},
tempPost: {}
};
},
watch: {
post: {
deep: true,
handler(val) {
console.log('watch', val);
this.tempPost = this.$utils.deepClone(val);
}
}
},
created() {
console.log(this.post);
this.tempPost = this.$utils.deepClone(this.post);
console.log(this.tempPost);
},
methods: {
fnToComment() {
this.$Router.push({
path: '/pagesA/comment/comment',
query: {
postId: this.post.id,
parentId: 0,
title: this.post.title,
formPage: 'comment_list',
type: 'post'
}
});
},
fnDoLikes() {
this.$httpApi
.postLikePost(this.post.id)
.then(res => {
if (res.status == 200) {
uni.showToast({
icon: 'none',
title: '点赞成功'
});
this.tempPost.likes += 1;
} else {
uni.showToast({
icon: 'none',
title: res.message
});
}
})
.catch(err => {
console.log(err);
uni.showToast({
icon: 'none',
title: err.message
});
});
},
fnOnShare() {
// this.$emit('on-share');
this.share.show = true;
},
fnOnShareChange(e) {
console.log(e);
}
}
};
</script>
<style scoped lang="scss">
.bottom-tool-bar {
width: 100vw;
position: fixed;
left: 0;
bottom: 0;
z-index: 401;
::v-deep {
.tm-shareSheet-wk .uni-scroll-view-content {
display: flex;
align-items: center;
justify-content: center;
}
}
.content {
width: 100%;
justify-content: space-between;
box-sizing: border-box;
padding: 24rpx;
background-color: #ffffff;
box-shadow: 0rpx -4rpx 24rpx rgba(0, 0, 0, 0.07);
border-radius: 24rpx 24rpx 0 0;
.input {
width: 280rpx;
padding: 12rpx 24rpx;
background-color: #f5f5f5;
border-radius: 60rpx;
font-size: 24rpx;
color: #666;
.icon {
}
.text {
padding-left: 8rpx;
}
}
.right {
width: 0;
flex-grow: 1;
align-items: center;
justify-content: space-between;
padding-left: 24rpx;
.item {
margin-left: 24rpx;
text-align: center;
display: flex;
align-items: center;
&.share {
.iconfont {
font-size: 36rpx;
}
}
.iconfont {
font-size: 36rpx;
color: #333;
}
.text {
padding-left: 6rpx;
font-size: 32rpx;
}
}
}
}
}
</style>
+182 -164
View File
@@ -1,180 +1,198 @@
<template>
<view class="">
<view v-if="loadStatus == 'loading'" class="img-loading" :style="[imgStyle, loadStyle]">
<!-- <text class="img-load-icon iconfont icon-loading"></text>
<text class="img-load-text">{{ loadText }}</text> -->
<image :src="loadingImgSrc" :style="[imgStyle]" mode="aspectFit"></image>
</view>
<view v-if="loadStatus == 'error'" class="img-error" :style="[imgStyle, loadErrStyle]">
<text class="img-err-icon iconfont icon-exclamation-circle"></text>
<text class="img-load-text">{{ loadErrText }}</text>
</view>
<image
v-show="loadStatus == 'success'"
:src="src"
@load="fnOnLoad"
@error="fnOnError"
:lazy-load="lazyLoad"
:style="[imgStyle]"
:mode="mode"
@click="$emit('on-click', url)"
></image>
</view>
<view class="cache-image-wrap">
<view v-if="loadStatus == 'loading'" class="img-loading" :style="[imgStyle, loadStyle]">
<!-- <text class="img-load-icon iconfont icon-loading"></text>
<text class="img-load-text">{{ loadText }}</text> -->
<image :src="loadingImgSrc" :style="[loadingImgStyle]" mode="aspectFit"></image>
</view>
<view v-if="loadStatus == 'error'" class="img-error" :style="[imgStyle, loadErrStyle]">
<!-- <text class="img-err-icon iconfont icon-exclamation-circle"></text>
<text class="img-load-text">{{ loadErrText }}</text> -->
<image class="img-error-img" :src="loadingErrorImageSrc" mode="scaleToFill"></image>
</view>
<image v-show="loadStatus == 'success'" :src="src" @load="fnOnLoad" @error="fnOnError" :lazy-load="lazyLoad"
:style="[imgStyle]" :mode="mode" @click="$emit('on-click', url)"></image>
</view>
</template>
<script>
import imageCache from '@/utils/imageCache.js';
export default {
name: 'cache-image',
props: {
url: {
type: String,
default: ''
},
lazyLoad: {
type: Boolean,
default: true
},
loadStyle: {
type: Object,
default() {
return {
backgroundColor: '#ffffff',
color: '#333'
};
}
},
loadErrStyle: {
type: Object,
default() {
return {
color: 'rgba(244, 67, 54,1)'
// backgroundColor: 'rgba(244, 67, 54,0.2)'
};
}
},
mode: {
type: String,
default: 'aspectFill'
},
loadText: {
type: String,
default: '加载中...'
},
loadErrText: {
type: String,
default: '加载失败'
},
fileMd5: {
type: String,
default: ''
},
styles: {
type: Object,
default() {
return {};
}
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '100%'
},
radius: {
type: String,
default: ''
}
},
data() {
return {
imgStyle: {},
src: '', // 图片地址
loadStatus: 'loading'
};
},
computed: {
loadingImgSrc() {
return getApp().globalData.loadingGifUrl;
}
},
watch: {
// 监听图片md5值的变化
fileMd5(val) {
// 查找获取图片缓存
this.fnGetImageCache();
}
},
created() {
this.imgStyle = {
width: this.width,
height: this.height,
borderRadius: this.radius,
...this.styles
};
// 查找获取图片缓存
this.fnGetImageCache();
},
methods: {
// 查找获取图片缓存
async fnGetImageCache() {
// #ifdef APP-PLUS
var result = await imageCache.getImageCache(this.url, this.fileMd5);
if (result) {
this.src = result;
} else {
this.src = this.url;
}
// #endif
// #ifndef APP-PLUS
this.src = this.url;
// #endif
},
fnOnLoad() {
this.loadStatus = 'success';
},
fnOnError() {
this.loadStatus = 'error';
}
}
export default {
name: 'cache-image',
props: {
url: {
type: String,
default: ''
},
lazyLoad: {
type: Boolean,
default: true
},
loadStyle: {
type: Object,
default() {
return {
backgroundColor: '#ffffff',
color: '#333'
};
}
},
loadErrStyle: {
type: Object,
default() {
return {
color: 'rgba(244, 67, 54,1)'
// backgroundColor: 'rgba(244, 67, 54,0.2)'
};
}
},
mode: {
type: String,
default: 'aspectFill'
},
loadText: {
type: String,
default: '加载中...'
},
loadErrText: {
type: String,
default: '加载失败'
},
fileMd5: {
type: String,
default: ''
},
styles: {
type: Object,
default() {
return {};
}
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '100%'
},
radius: {
type: String,
default: ''
}
},
data() {
return {
imgStyle: {},
loadingImgStyle: {
width: '120rpx',
},
src: '', // 图片地址
loadStatus: 'loading'
};
},
computed: {
imagesConfig() {
return this.$tm.vx.getters().getConfigs.imagesConfig
},
loadingImgSrc() {
return this.$utils.checkImageUrl(this.imagesConfig.loadingGifUrl);
},
loadingErrorImageSrc() {
return this.$utils.checkImageUrl(this.imagesConfig.loadingErrUrl)
}
},
watch: {
// 监听图片md5值的变化
fileMd5(val) {
// 查找获取图片缓存
this.fnGetImageCache();
}
},
created() {
this.imgStyle = {
width: this.width,
height: this.height,
borderRadius: this.radius,
...this.styles
};
// 查找获取图片缓存
this.fnGetImageCache();
},
methods: {
// 查找获取图片缓存
async fnGetImageCache() {
// #ifdef APP-PLUS
var result = await imageCache.getImageCache(this.url, this.fileMd5);
if (result) {
this.src = result;
} else {
this.src = this.url;
}
// #endif
// #ifndef APP-PLUS
this.src = this.url;
// #endif
},
fnOnLoad() {
this.loadStatus = 'success';
},
fnOnError(e) {
this.loadStatus = 'error';
}
}
};
</script>
<style scoped lang="scss">
.cache-image-wrap {
width: 100%;
height: 100%;
}
.img-loading,
.img-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
background-color: #f2f2f2;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
background-color: #f2f2f2;
}
.img-load-icon {
font-size: 36rpx;
animation: xhRote 0.8s infinite linear;
}
.img-load-text {
font-size: 28rpx;
margin-top: 8rpx;
color: inherit;
}
.img-error {
font-size: 28rpx;
}
.img-err-icon {
font-size: 36rpx;
}
@keyframes xhRote {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
.img-load-icon {
font-size: 36rpx;
animation: xhRote 0.8s infinite linear;
}
.img-load-text {
font-size: 28rpx;
margin-top: 8rpx;
color: inherit;
}
.img-error {
font-size: 28rpx;
&-img {
width: 100%;
border-radius: 12rpx;
}
}
.img-err-icon {
font-size: 36rpx;
}
@keyframes xhRote {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
@@ -1,63 +1,79 @@
<template>
<view class="category-mini-card">
<!-- <image class="img" lazy-load :src="$utils.checkThumbnailUrl(category.thumbnail)" mode="aspectFill"></image> -->
<cache-image
class="img"
height="120rpx"
:url="$utils.checkThumbnailUrl(category.thumbnail)"
:fileMd5="$utils.checkThumbnailUrl(category.thumbnail)"
mode="aspectFill"
></cache-image>
<text class="label">{{ category.postCount }}&nbsp;</text>
<view class="name">{{ category.name }}</view>
<cache-image class="img" height="180rpx" :url="$utils.checkThumbnailUrl(category.spec.cover,false)"
:fileMd5="$utils.checkThumbnailUrl(category.spec.cover)" mode="aspectFill"></cache-image>
<view class="content">
<view class="name">{{ category.spec.displayName }}</view>
<text class="label"> {{ category.postCount }} </text>
</view>
</view>
</template>
<script>
export default {
name: 'category-mini-card',
props: {
category: {
type: Object,
default: () => {}
export default {
name: 'category-mini-card',
props: {
category: {
type: Object,
default: () => {}
}
}
}
};
};
</script>
<style scoped lang="scss">
.category-mini-card {
display: inline-block;
width: 260rpx;
height: 180rpx;
position: relative;
border-radius: 12rpx;
background-color: #fff;
overflow: hidden;
// border: 2rpx solid #f7f7f7;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
.img {
width: 100%;
height: 120rpx;
border: 6rpx 6rpx 0 0;
}
.label {
position: absolute;
left: 0;
top: 86rpx;
color: #03a9f4;
font-size: 24rpx;
background-color: rgba(255, 255, 255, 1);
border-radius: 0rpx 24rpx 0 0;
display: flex;
padding: 2rpx 12rpx;
padding-right: 24rpx;
}
.name {
font-size: 24rpx;
.category-mini-card {
display: inline-block;
width: 260rpx;
height: 180rpx;
position: relative;
border-radius: 12rpx;
background-color: #fff;
overflow: hidden;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
position: relative;
text-align: center;
color: var(--main-text-color);
color: #ffffff;
&:before {
content: '';
position: absolute;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(3rpx);
}
.content {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 3;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.img {
width: 100%;
height: 100%;
}
.name {
font-weight: bold;
font-size: 30rpx;
}
.label {
font-size: 24rpx;
margin-top: 6rpx;
}
}
}
</style>
+135 -117
View File
@@ -1,147 +1,165 @@
<template>
<view class=" comment-item flex flex-col mt-30 pt-24" :class="{ 'child-comment-item': isChild, 'no-solid': !useSolid, classItem }">
<view v-if="comment" class=" comment-item flex flex-col mt-30 pt-24"
:class="{ 'child-comment-item': isChild, 'no-solid': !useSolid, classItem }">
<view class="comment-item_user flex">
<image v-if="comment.isAdmin" class="user-avatar" :src="bloggerInfo.avatar" mode="aspectFill" @error="fnOnImageError(comment)"></image>
<image v-else class="user-avatar" :src="comment.avatar" mode="aspectFill" @error="fnOnImageError(comment)"></image>
<image class="user-avatar" :class="{ 'is-radius': globalAppSettings.isAvatarRadius }"
:src="$utils.checkAvatarUrl(comment.owner.avatar, false)" mode="aspectFill"
@error="fnOnImageError(comment)"></image>
<view class="user-info pl-14">
<view class="author">
<text class="mr-6 text-grey-darken-1 text-size-m">{{ comment.author }}</text>
<tm-tags v-if="comment.isAdmin" :dense="true" color="bg-gradient-amber-accent" size="xs" model="fill">博主</tm-tags>
<tm-tags v-else :dense="true" color="bg-gradient-light-blue-lighten " size="xs" model="fill">游客</tm-tags>
<text class="mr-6 text-grey-darken-1 text-size-m">{{ comment.owner.displayName }}</text>
</view>
<view class="flex mt-4">
<view v-if="false" class="text-size-s text-grey mr-12">IP属地浙江省杭州市</view>
<view class="time text-size-xs text-grey">
<text class="">{{ $tm.dayjs(comment.createTime).format('YYYY年MM月DD日') }}</text>
<text class="ml-12">{{ $tm.dayjs(comment.createTime).fromNow(true) }}</text>
<text class="">{{ $tm.dayjs(comment.spec.creationTime).format('YYYY年MM月DD日') }}</text>
<text class="ml-12">{{ $tm.dayjs(comment.spec.creationTime).fromNow(true) }}</text>
</view>
</view>
</view>
<view v-if="useActions" class="">
<tm-button size="s" text theme="blue" @click="$emit('on-comment', { type: 'user', comment: comment })">回复</tm-button>
<tm-button size="s" text theme="grey" @click="$emit('on-copy', comment.content)"></tm-button>
<tm-button v-if="!disallowComment" size="s" text theme="blue"
@click="$emit('on-comment', { type: 'user', comment: comment })"></tm-button>
<tm-button size="s" text theme="grey" @click="$emit('on-copy', comment.spec.raw)">复制</tm-button>
</view>
</view>
<view class="comment-item_content mt-12" :class="{ 'has-bg': useContentBg, 'not-ml': isChild }" @click="$emit('on-detail', comment)" v-html="comment.content"></view>
<!-- <view v-if="useActions" class="comment-item_info text-size-s text-grey">
<text v-if="false" @click="$emit('on-todo')">点赞</text>
<text @click="$emit('on-comment', { type: 'user', comment: comment })">回复</text>
<text v-if="false" class="ml-24" @click="$emit('on-todo')">举报</text>
<text class="ml-24" @click="$emit('on-copy', comment.content)">复制内容</text>
</view> -->
<view class="comment-item_content mt-12" :class="{ 'has-bg': useContentBg, 'not-ml': isChild }"
@click="$emit('on-detail', comment)" v-html="comment.spec.raw"></view>
</view>
</template>
<script>
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
export default {
name: 'comment-item',
components: { tmTags, tmButton },
props: {
classItem: {
type: Array,
default: () => []
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
export default {
name: 'comment-item',
components: {
tmTags,
tmButton
},
useActions: {
type: Boolean,
default: true
},
useSolid: {
type: Boolean,
default: true
},
useContentBg: {
type: Boolean,
default: true
},
isChild: {
type: Boolean,
default: false
},
postId: {
type: Number,
default: null
},
comment: {
type: Object,
default: () => {}
}
},
computed: {
// 获取博主信息
bloggerInfo() {
let blogger = this.$tm.vx.getters().blogger.getBlogger;
blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
return blogger;
}
},
methods: {
fnOnImageError(data) {
if (data.isAdmin) {
data.avatar = this.$haloConfig.author.avatar;
} else {
data.avatar = `${this.$haloConfig.defaultAvatarUrl}&rt=${new Date().getTime()}`;
props: {
classItem: {
type: Array,
default: () => []
},
disallowComment: {
type: Boolean,
default: false
},
useActions: {
type: Boolean,
default: true
},
useSolid: {
type: Boolean,
default: true
},
useContentBg: {
type: Boolean,
default: true
},
isChild: {
type: Boolean,
default: false
},
postName: {
type: String,
default: ""
},
comment: {
type: Object,
default: () => null
}
},
computed: {
imageConfigs() {
return this.$tm.vx.getters().getConfigs.imagesConfig;
},
bloggerInfo() {
let blogger = this.$tm.vx.getters().getConfigs.authorConfig.blogger;
blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
return blogger;
}
},
methods: {
fnOnImageError(data) {
if (data && data.owner) {
if (this.imageConfigs.defaultAvatarUrl.indexOf('?') == -1) {
data.owner.avatar = `${this.imageConfigs.defaultAvatarUrl}?next-v=${new Date().getTime()}`
} else {
data.owner.avatar = `${this.imageConfigs.defaultAvatarUrl}&next-v=${new Date().getTime()}`
}
}
}
},
created() {
console.log("comment", this.comment)
}
}
};
};
</script>
<style scoped lang="scss">
.comment-item {
box-sizing: border-box;
border-top: 2rpx solid #f5f5f5;
&.child-comment-item {
padding-top: 0;
margin-left: 80rpx;
border: 0;
}
&.no-solid {
border: 0;
margin-top: 0 !important;
}
&_user {
display: flex;
align-items: center;
.user-avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
box-sizing: border-box;
border: 4rpx solid #ffffff;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.05);
}
.user-info {
width: 0;
flex-grow: 1;
}
}
&_content {
font-size: 28rpx;
margin-left: 80rpx;
.comment-item {
box-sizing: border-box;
border-radius: 10rpx;
line-height: 1.8;
color: var(--main-text-color);
&.has-bg {
background-color: #fafafa;
padding: 6rpx 24rpx;
border-top: 2rpx solid #f5f5f5;
&.child-comment-item {
padding-top: 0;
margin-left: 80rpx;
border: 0;
}
&.not-ml {
&.no-solid {
border: 0;
margin-top: 0 !important;
}
&_user {
display: flex;
align-items: center;
.user-avatar {
width: 70rpx;
height: 70rpx;
box-sizing: border-box;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.05);
border: 4rpx solid #ffffff;
border-radius: 12rpx;
&.is-radius {
border-radius: 50%;
}
}
.user-info {
width: 0;
flex-grow: 1;
}
}
&_content {
font-size: 28rpx;
margin-left: 80rpx;
box-sizing: border-box;
border-radius: 10rpx;
line-height: 1.8;
color: var(--main-text-color);
&.has-bg {
background-color: #fafafa;
padding: 6rpx 24rpx;
}
&.not-ml {
margin-left: 80rpx;
}
}
&_info {
margin-top: 6rpx;
display: flex;
align-items: center;
margin-left: 80rpx;
}
}
&_info {
margin-top: 6rpx;
display: flex;
align-items: center;
margin-left: 80rpx;
}
}
</style>
+244 -242
View File
@@ -1,266 +1,268 @@
<template>
<view class="comment-list">
<!-- 顶部区域 -->
<view class="comment-list_head">
<view class="title">
评论列表
<text class="count">{{ result.total || 0 }}</text>
</view>
<view class="filter">
<text class="filter-item " :class="{ active: sort == 0 }" @click="fnOnSort(0)">默认</text>
<text class="filter-item " :class="{ active: sort == 1 }" @click="fnOnSort(1)">热评</text>
<!-- <text class="filter-item">全部</text> -->
</view>
</view>
<!-- <view v-if="disallowComment" class="disallow-comment"><tm-empty icon="icon-shiliangzhinengduixiang-" label="文章已开启禁止评论"></tm-empty></view> -->
<!-- 内容区域 -->
<view class="comment-list_content">
<view v-if="loading != 'success'" class="loading-wrap flex">
<view v-if="loading == 'loading'" class="loading flex flex-center flex-col">
<text class="e-loading-icon iconfont icon-loading text-blue"></text>
<view class="text-size-n text-grey-lighten-1 py-12 mt-12">加载中请稍等...</view>
</view>
<view v-else-if="loading == 'error'" class="error">
<tm-empty icon="icon-wind-cry" label="加载失败">
<tm-button theme="bg-gradient-light-blue-accent" size="m" v-if="!disallowComment" @click="fnToComment()">刷新试试</tm-button>
</tm-empty>
</view>
</view>
<block v-else>
<view class="empty pt-50" v-if="dataList.length == 0">
<tm-empty icon="icon-shiliangzhinengduixiang-" label="暂无评论">
<tm-button theme="bg-gradient-light-blue-accent" size="m" v-if="!disallowComment" @click="fnToComment(null)">抢沙发</tm-button>
</tm-empty>
</view>
<block v-else>
<!-- 评论内容 : 目前仅支持二级评论 -->
<block v-for="(comment, index) in dataList" :key="comment.id">
<comment-item
:useContentBg="false"
:isChild="false"
:comment="comment"
:postId="postId"
@on-copy="fnCopyContent"
@on-comment="fnToComment"
@on-todo="fnToDo"
@on-detail="fnShowCommetnDetail"
></comment-item>
<view class="comment-list">
<!-- 顶部区域 -->
<view class="comment-list_head">
<view class="title">
<text>评论列表</text>
<text class="count">{{ (result && result.total) || 0 }}</text>
</view>
</view>
<!-- 内容区域 -->
<view class="comment-list_content">
<view v-if="loading !== 'success'" class="loading-wrap flex">
<view v-if="loading === 'loading'" class="loading flex flex-center flex-col">
<text class="e-loading-icon iconfont icon-loading text-blue"></text>
<view class="text-size-n text-grey-lighten-1 py-12 mt-12">加载中请稍等...</view>
</view>
<view v-else-if="loading === 'error'" class="error">
<tm-empty icon="icon-wind-cry" label="加载失败">
<tm-button theme="bg-gradient-light-blue-accent" size="m" v-if="!disallowComment"
@click="fnToComment()">刷新试试
</tm-button>
</tm-empty>
</view>
</view>
<block v-else>
<tm-alerts v-if="disallowComment && dataList.length !== 0" color="red" text :margin="[0, 0]" :shadow="0"
label="Ծ‸Ծ博主已设置该文章禁止评论!" :round="3"></tm-alerts>
<view class="empty pt-50" v-if="dataList.length === 0">
<tm-empty v-if="disallowComment" icon="icon-shiliangzhinengduixiang-" label="暂无评论">
<text class="text-red text-size-s">- 文章已开启禁止评论 -</text>
</tm-empty>
<tm-empty v-else icon="icon-shiliangzhinengduixiang-" label="暂无评论">
<tm-button theme="light-blue" :dense="true" :shadow="0" size="m" @click="fnToComment(null)">抢沙发
</tm-button>
</tm-empty>
</view>
<block v-else>
<!-- 评论内容 : 目前仅支持二级评论 -->
<block v-for="(comment, index) in dataList" :key="comment.metadata.name">
<comment-item :useContentBg="false" :isChild="false" :comment="comment" :postName="postName"
:disallowComment="disallowComment" @on-copy="fnCopyContent" @on-comment="fnToComment"
@on-todo="fnToDo" @on-detail="fnShowCommentDetail"></comment-item>
<!-- 二级评论 -->
<block v-if="comment.children && comment.children.length != 0">
<block v-for="(childComment, childIndex) in comment.children" :key="childComment.id">
<comment-item
:useContentBg="false"
:isChild="true"
:comment="childComment"
:postId="postId"
@on-copy="fnCopyContent"
@on-comment="fnToComment"
@on-todo="fnToDo"
@on-detail="fnShowCommetnDetail"
></comment-item>
</block>
</block>
</block>
<view v-if="false" class="to-more-comment">
<tm-button item-class="btn" :block="true" width="90vw" theme="bg-gradient-light-blue-lighten" size="m">点击查看全部评论</tm-button>
</view>
</block>
</block>
</view>
</view>
<!-- 二级评论 -->
<block v-if="comment.replies && comment.replies.items.length !== 0">
<block v-for="(childComment, childIndex) in comment.replies.items"
:key="childComment.metadata.name">
<comment-item :useContentBg="false" :isChild="true" :comment="childComment"
:postName="postName" :disallowComment="disallowComment" @on-copy="fnCopyContent"
@on-comment="fnToComment" @on-todo="fnToDo"
@on-detail="fnShowCommentDetail"></comment-item>
</block>
</block>
</block>
<view v-if="false" class="to-more-comment">
<tm-button item-class="btn" :block="true" width="90vw" theme="bg-gradient-light-blue-lighten"
size="m">点击查看全部评论
</tm-button>
</view>
</block>
</block>
</view>
</view>
</template>
<script>
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmAlerts from '@/tm-vuetify/components/tm-alerts/tm-alerts.vue';
export default {
name: 'comment-list',
components: { tmEmpty, tmButton },
props: {
// 是否禁用评论
disallowComment: {
type: Boolean,
default: false
},
postId: {
type: Number,
default: null
},
post: {
type: Object,
default: () => {}
}
},
data() {
return {
loading: 'loading',
sort: 0,
queryParams: {
sort: '',
more: true
},
api: 'getPostCommentTree',
result: {},
dataList: []
};
},
created() {
this.fnGetData();
uni.$on('comment_list_refresh', () => {
this.fnOnSort(this.sort, true);
});
},
methods: {
fnOnSort(type, refresh = false) {
if (this.sort == type && refresh == false) return;
const _api = ['getPostCommentTree', 'getPostTopCommentList'];
this.sort = type;
this.api = _api[type];
this.fnGetData();
},
fnGetData() {
this.loading = 'loading';
this.$httpApi[this.api](this.postId, {})
.then(res => {
if (res.status == 200) {
this.result = res.data;
this.dataList = res.data.content;
this.loading = 'success';
} else {
this.loading = 'error';
}
})
.catch(err => {
this.loading = 'error';
})
.finally(() => {
uni.hideLoading();
});
},
fnToDo() {
uni.$tm.toast('Halo暂未支持!');
},
fnToComment(data) {
if (this.disallowComment) {
return uni.$tm.toast('文章已禁止评论!');
}
console.log(data);
let _comment = {};
if (data) {
let { type, comment } = data;
// 来自用户
_comment = {
id: this.post.id,
parentId: comment.id,
title: comment.author,
from: 'posts',
formPage: 'comment_list',
type: 'user'
};
} else {
// 来自文章
_comment = {
id: this.post.id,
parentId: 0,
title: '评论文章:' + this.post.title,
formPage: 'comment_list',
from: 'posts',
type: 'post'
};
}
name: 'comment-list',
components: {
tmEmpty,
tmButton,
tmAlerts
},
props: {
// 是否禁用评论
disallowComment: {
type: Boolean,
default: false
},
postName: {
type: String,
default: ""
},
post: {
type: Object,
default: () => {
}
}
},
data() {
return {
loading: 'loading',
queryParams: {
group: "content.halo.run",
kind: "Post",
version: "v1alpha1",
name: "",
page: 1,
size: 50,
withReplies: true,
replySize: 10
},
result: null,
dataList: []
};
},
created() {
this.queryParams.name = this.postName;
this.fnGetData();
uni.$on('comment_list_refresh', () => {
this.fnOnSort(true);
});
},
methods: {
fnOnSort(refresh = false) {
if (refresh === false) return;
this.fnGetData();
},
fnGetData() {
this.loading = 'loading';
this.$httpApi.v2.getPostCommentList(this.queryParams)
.then(res => {
console.log("日志:", res)
this.result = res;
this.dataList = res.items;
this.loading = 'success';
this.$emit('on-loaded', this.dataList);
})
.catch(err => {
this.loading = 'error';
})
.finally(() => {
uni.hideLoading();
});
},
fnToDo() {
uni.$tm.toast('Halo暂未支持!');
},
fnToComment(data) {
if (this.disallowComment) {
return uni.$tm.toast('文章已禁止评论!');
}
let _comment = {};
console.log('data', data)
if (data) {
let {type, comment} = data;
// 来自用户
_comment = {
isComment: false,
postName: comment.metadata.name,
title: comment.owner.displayName,
from: 'posts',
formPage: 'comment_list',
type: 'user'
};
} else {
// 来自文章
_comment = {
isComment: true,
postName: this.post.metadata.name,
title: '新增评论',
formPage: 'comment_list',
from: 'posts',
type: 'post'
};
}
uni.$tm.vx.commit('comment/setCommentInfo', _comment);
this.$Router.push({
path: '/pagesA/comment/comment',
query: _comment
});
},
fnCopyContent(content) {
uni.$tm.u.setClipboardData(content);
uni.$tm.toast('内容已复制成功!');
},
this.$emit("on-comment", _comment)
},
fnCopyContent(content) {
uni.$tm.u.setClipboardData(content);
uni.$tm.toast('内容已复制成功!');
},
fnShowCommetnDetail(comment) {
this.$emit('on-comment-detail', {
postId: this.postId,
comment: comment
});
}
}
fnShowCommentDetail(comment) {
this.$emit('on-comment-detail', {
postName: this.postName,
comment: comment
});
}
}
};
</script>
<style lang="scss" scoped>
.comment-list {
&_head {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
box-sizing: border-box;
padding-left: 24rpx;
font-size: 34rpx;
font-weight: bold;
&_head {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
box-sizing: border-box;
padding-left: 24rpx;
font-size: 30rpx;
font-weight: bold;
&:before {
content: '';
position: absolute;
left: 0rpx;
top: 8rpx;
width: 8rpx;
height: 30rpx;
background-color: rgb(3, 174, 252);
border-radius: 6rpx;
}
.title {
.count {
font-size: 28rpx;
font-weight: normal;
}
}
.filter {
font-size: 26rpx;
font-weight: normal;
&:before {
content: '';
position: absolute;
left: 0rpx;
top: 8rpx;
width: 8rpx;
height: 26rpx;
background-color: rgb(3, 174, 252);
border-radius: 6rpx;
}
&-item {
margin-left: 20rpx;
color: #666;
&.active {
font-weight: bold;
color: rgb(255, 152, 0);
font-size: 26rpx;
}
}
}
}
&_content {
margin-top: 24rpx;
padding-bottom: 36rpx;
}
.title {
.count {
font-size: 28rpx;
font-weight: normal;
}
}
.filter {
font-size: 26rpx;
font-weight: normal;
&-item {
margin-left: 20rpx;
color: #666;
&.active {
font-weight: bold;
color: rgb(255, 152, 0);
font-size: 26rpx;
}
}
}
}
&_content {
margin-top: 24rpx;
padding-bottom: 36rpx;
}
}
.loading-wrap {
width: 100%;
height: 506rpx;
.loading {
width: 100%;
}
.e-loading-icon {
font-size: 100rpx;
}
.loading-wrap {
width: 100%;
height: 506rpx;
.loading {
width: 100%;
}
.e-loading-icon {
font-size: 100rpx;
}
}
.to-more-comment {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-top: 80rpx;
::v-deep {
.tm-button .tm-button-btn uni-button {
height: 70rpx;
}
}
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-top: 80rpx;
::v-deep {
.tm-button .tm-button-btn uni-button {
height: 70rpx;
}
}
}
</style>
+231
View File
@@ -0,0 +1,231 @@
<template>
<tm-poup v-model="isShow" position="bottom" height="71vh" @change="handleOnChange">
<view class="title text-align-center">{{ calcTitle }}</view>
<tm-form @submit="fnOnSubmit">
<tm-input name="content" :vertical="true" required :height="220" input-type="textarea"
bg-color="grey-lighten-5" :maxlength="200" :borderBottom="false" placeholder="请输入内容,不超过200字符..."
v-model="form.content"></tm-input>
<tm-input name="author" align="right" required title="我的昵称" placeholder="请输入您的昵称..."
v-model="form.author"></tm-input>
<tm-input name="avatar" align="right" title="我的头像" placeholder="请输入您的头像..."
v-model="form.avatar"></tm-input>
<tm-input name="email" align="right" title="邮箱地址" placeholder="请输入您的邮箱..." v-model="form.email"></tm-input>
<tm-input name="authorUrl" align="right" title="我的网站" placeholder="请输入您的网址..."
v-model="form.authorUrl"></tm-input>
<view class="pa-24 pl-30 pr-30">
<tm-button navtie-type="form" theme="bg-gradient-blue-accent" block>提交
</tm-button>
</view>
</tm-form>
</tm-poup>
</template>
<script>
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
import tmForm from '@/tm-vuetify/components/tm-form/tm-form.vue';
import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
import tmSwitch from '@/tm-vuetify/components/tm-switch/tm-switch.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
export default {
name: 'CommentModal',
components: {
tmPoup,
tmForm,
tmInput,
tmSwitch,
tmButton
},
props: {
show: {
type: Boolean,
default: false
},
isComment: {
type: Boolean,
default: false
},
title: {
type: String,
default: ""
},
postName: {
type: String,
default: ""
}
},
data() {
return {
isShow: false,
params: {
postName: '',
title: '', // 被回复的标题 type=user =用户名 否则为文章标题
form: '',
formPage: '', // 来自哪个页面
type: 'post' // 来源文章/页面 还是用户 user=用户
},
form: {
allowNotification: true,
author: '', // 作者
avatar: '',
authorUrl: '', // 作者主页
content: '', // 评论内容
email: '', // 邮件
postName: ""
}
}
},
computed: {
calcTitle() {
if (this.isComment) {
return this.title
}
return `回复用户:${this.title}`
}
},
watch: {
show: {
immediate: true,
handler: function (newVal) {
if (!newVal) return;
this.isShow = true;
this.handleResetForm();
this.form.postName = this.postName;
let visitor = uni.getStorageSync('Visitor')
if (!visitor) return;
visitor = JSON.parse(visitor)
this.form.author = visitor.author;
this.form.avatar = visitor.avatar;
this.form.email = visitor.email;
this.form.authorUrl = visitor.authorUrl;
}
}
},
methods: {
handleResetForm() {
this.params = {
postName: '',
title: '',
form: '',
formPage: '',
type: 'post'
}
this.form = {
allowNotification: true,
author: '',
avatar: '',
authorUrl: '',
content: '',
email: '',
postName: ""
}
},
fnOnSubmit(e) {
if (e === false) {
return uni.$tm.toast('请检查所有的必填项是否填写完整!');
}
// if (!this.form.email) {
// return uni.$tm.toast('未填写邮箱地址,将无法接收提醒!');
// }
// if (this.form.email && !uni.$tm.test.email(this.form.email)) {
// return uni.$tm.toast('请填写正确的邮箱地址!');
// }
// if (this.form.authorUrl && !uni.$tm.test.url(this.form.authorUrl)) {
// return uni.$tm.toast('请输入正确的Url地址!');
// }
this.fnHandle();
},
handleSetVisitor() {
uni.setStorageSync('Visitor', JSON.stringify({
author: this.form.author,
avatar: this.form.avatar,
email: this.form.email,
authorUrl: this.form.authorUrl,
}))
},
fnHandle() {
uni.showLoading({
title: '正在提交...'
});
// 评论
if (this.isComment) {
const commentForm = {
allowNotification: true,
raw: this.form.content,
content: this.form.content,
owner: {
avatar: this.form.avatar,
displayName: this.form.author,
email: this.form.email,
website: this.form.authorUrl,
},
subjectRef: {
group: "content.halo.run",
kind: "Post",
name: this.form.postName,
version: "v1alpha1",
}
}
this.$httpApi.v2.addPostComment(commentForm)
.then(res => {
uni.$tm.toast('评论成功,可能需要审核!');
// 更新评论者信息
this.handleSetVisitor();
this.handleClose(true)
this.handleResetForm()
})
.catch(err => {
uni.$tm.toast("评论失败");
});
return;
}
// 回复
const replyForm = {
allowNotification: true,
raw: this.form.content,
content: this.form.content,
owner: {
avatar: this.form.avatar,
displayName: this.form.author,
email: this.form.email,
website: this.form.authorUrl,
},
quoteReply: this.form.postName
}
this.$httpApi.v2.addPostCommentReply(this.form.postName, replyForm)
.then(res => {
uni.$tm.toast('回复成功,可能需要审核!');
// 更新评论者信息
this.handleSetVisitor();
this.handleClose(true)
this.handleResetForm()
})
.catch(err => {
uni.$tm.toast("回复失败");
});
},
handleOnChange(isOpen) {
this.isShow = isOpen;
if (!isOpen) {
this.$emit("on-close", {
refresh: false
})
}
},
handleClose(refresh = false) {
this.isShow = false;
this.$emit("on-close", {
refresh: refresh
})
}
}
}
</script>
<style lang="scss" scoped>
.title {
margin: 24rpx 0;
}
</style>
+119 -132
View File
@@ -2,47 +2,31 @@
<!-- 轮播图 -->
<view class="Swiper-mfw-index-box">
<view class="Swiper-mfw-index Swiper-box" :class="[dotPosition]">
<swiper
class="Swiper-mfw"
:style="{ height: height }"
:circular="true"
:indicator-dots="false"
:autoplay="autoplay"
:interval="3000"
:duration="1000"
:current="currentIndex"
:disable-touch="disable_touch"
@change="change"
>
<swiper class="Swiper-mfw" :style="{ height: height }" :circular="true" :indicator-dots="false"
:autoplay="autoplay" :interval="3000" :duration="1000" :current="currentIndex"
:disable-touch="disable_touch" @change="change">
<!-- 只需要前5条数据 -->
<swiper-item class="swiper-mfw-item" v-if="index <= (dotPosition == 'right' ? 3 : 4)" v-for="(item, index) in list" :key="index">
<!-- /*
<block v-for="(item, index) in list" :key="index">
<swiper-item class="swiper-mfw-item" v-if="index <= 4">
<!-- /*
1. 这里不需要用api控制暂停视频
2. 因为video标签上加了v-if="current==index"
3. 当current == index时才会创建视频组件
4. 否current != index则就销毁视频
*/ -->
<!-- 如果有视频则显示视频-->
<template v-if="item.mp4 && current == index">
<video
class="ImageVideo"
:id="'ImageVideo' + index"
:ref="'ImageVideo' + index"
:src="item.mp4"
:loop="true"
:muted="false"
:autoplay="current == index ? true : false"
:controls="false"
:show-fullscreen-btn="false"
:show-play-btn="false"
:enable-progress-gesture="false"
:play-strategy="0"
:poster="item.image || item.src"
></video>
</template>
<!-- 否则显示图片 -->
<image v-else :src="item.image || item.src" class="Image" mode="aspectFill" @click.stop="$emit('on-click', item)"></image>
</swiper-item>
<!-- 如果有视频则显示视频-->
<template v-if="item.mp4 && current == index">
<video class="ImageVideo" :id="'ImageVideo' + index" :ref="'ImageVideo' + index"
:src="item.mp4" :loop="true" :muted="false" :autoplay="current == index ? true : false"
:controls="false" :show-fullscreen-btn="false" :show-play-btn="false"
:enable-progress-gesture="false" :play-strategy="0"
:poster="item.image || item.src"></video>
</template>
<!-- 否则显示图片 -->
<image v-else :src="item.image || item.src" class="Image" mode="aspectFill"
@click.stop="$emit('on-click', item)"></image>
</swiper-item>
</block>
</swiper>
<!-- 指示器 [Top] -->
<view v-if="useTop" class="Swiper-indicator-box indicator-Top-box">
@@ -84,12 +68,15 @@
<!-- 用户信息 -->
<view class="Bottom-UserInfo">
<!-- 头像 -->
<view class="UserImage-box"><image :src="item.avatar" class="Image" mode="aspectFill"></image></view>
<view class="UserImage-box">
<image :src="item.avatar" class="Image" mode="aspectFill"></image>
</view>
<!-- 用户名 -->
<view class="textbox UserName-box">
<text class="text UserInfo">{{ item.nickname }}</text>
</view>
<view v-if="item.createTime" class="jiange-box"><text class="text jiange-text"></text></view>
<view v-if="item.createTime" class="jiange-box"><text class="text jiange-text"></text>
</view>
<view v-if="item.createTime" class="textbox UserGPS-box">
<text class="text UserInfo">发布于 {{ item.createTime }}</text>
</view>
@@ -106,18 +93,14 @@
<!-- 左边 -->
<view class="Bottom-left-Imagelist">
<block v-for="(item, index) in list" :key="index">
<view
class="Bottom-item"
v-if="Number(index) <= (dotPosition == 'right' ? 3 : 4)"
:class="currentIndex == index ? 'current' : 'no'"
@click="SwiperIndTap(index)"
>
<view class="Bottom-item" v-if="Number(index) <= 4"
:class="currentIndex == index ? 'current' : 'no'" @click="SwiperIndTap(index)">
<image :src="item.image || item.src" class="Image" mode="aspectFill"></image>
</view>
</block>
</view>
<!-- 右边 -->
<view class="Bottom-right-lili-btn">
<view v-if="false" class="Bottom-right-lili-btn">
<view class="Bottom-item is-more">
<view class="more" @click.stop="$emit('on-more')">
MORE
@@ -132,102 +115,106 @@
</template>
<script>
export default {
name: 'e-swiper',
props: {
title: {
type: String,
default: ''
},
height: {
type: String,
default: '450rpx'
},
dotPosition: {
type: String,
default: 'bottom'
},
useTop: {
type: Boolean,
default: true
},
useDot: {
type: Boolean,
default: true
},
useTitle: {
type: Boolean,
default: true
},
useUser: {
type: Boolean,
default: true
},
// 轮播图 数据列表
list: {
type: Array,
default: () => []
},
// 当前选中的项(指示器坐标位置)
current: {
type: Number,
default: 0
},
// 是否自动轮播
autoplay: {
type: Boolean,
default: false
}
},
data() {
return {
// 是否禁止用户 touch 操作
currentIndex: 0,
disable_touch: false, //touch 用户划动引起swiper变化。
date: {
year: '-',
monthEn: '-',
month: '-'
export default {
name: 'e-swiper',
props: {
title: {
type: String,
default: ''
},
height: {
type: String,
default: '450rpx'
},
dotPosition: {
type: String,
default: 'bottom'
},
useTop: {
type: Boolean,
default: true
},
useDot: {
type: Boolean,
default: true
},
useTitle: {
type: Boolean,
default: true
},
useUser: {
type: Boolean,
default: true
},
// 轮播图 数据列表
list: {
type: Array,
default: () => []
},
// 当前选中的项(指示器坐标位置)
current: {
type: Number,
default: 0
},
// 是否自动轮播
autoplay: {
type: Boolean,
default: false
}
};
},
created() {
this.currentIndex = this.current;
const date = new Date();
//将月份名称存储在数组中
const monthArray = new Array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
},
data() {
return {
// 是否禁止用户 touch 操作
currentIndex: 0,
disable_touch: false, //touch 用户划动引起swiper变化。
date: {
year: '-',
monthEn: '-',
month: '-'
}
};
},
created() {
this.currentIndex = this.current;
const date = new Date();
//将月份名称存储在数组中
const monthArray = new Array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov',
'Dec');
this.date.year = date.getFullYear();
let month = date.getMonth() + 1;
this.date.month = month < 10 ? '0' + month : month;
this.date.monthEn = monthArray[date.getMonth()].toUpperCase();
},
methods: {
// current 改变时会触发 change 事件,event.detail = {current: current, source: source}
change(e) {
let { current, source } = e.detail;
//只有页面自动切换,手动切换时才轮播,其他不允许
if (source === 'autoplay' || source === 'touch') {
this.date.year = date.getFullYear();
let month = date.getMonth() + 1;
this.date.month = month < 10 ? '0' + month : month;
this.date.monthEn = monthArray[date.getMonth()].toUpperCase();
},
methods: {
// current 改变时会触发 change 事件,event.detail = {current: current, source: source}
change(e) {
let {
current,
source
} = e.detail;
//只有页面自动切换,手动切换时才轮播,其他不允许
if (source === 'autoplay' || source === 'touch') {
let event = {
current: current
};
this.currentIndex = current;
this.$emit('change', event);
}
},
// 手动点击了指示器[小图模式]
SwiperIndTap(e) {
let index = e;
let event = {
current: current
current: index
};
this.currentIndex = current;
this.currentIndex = index;
this.$emit('change', event);
}
},
// 手动点击了指示器[小图模式]
SwiperIndTap(e) {
let index = e;
let event = {
current: index
};
this.currentIndex = index;
this.$emit('change', event);
}
}
};
};
</script>
<style lang="scss" scoped>
@import './e-swiper.scss';
@import './e-swiper.scss';
</style>
-177
View File
@@ -1,177 +0,0 @@
<template>
<view class="journal-card mb-24 round-3 bg-white ">
<view class="head pa-24 pb-0 flex flex-between">
<view class="left flex">
<cache-image
class="avatar rounded"
radius="50%"
width="70rpx"
height="70rpx"
:url="bloggerInfo.avatar"
:fileMd5="bloggerInfo.avatar"
mode="scaleToFill"
></cache-image>
<view class="info pl-16 flex flex-col">
<view class="nickname text-weight-b text-grey-darken-4">{{ bloggerInfo.nickname }}</view>
<view class="mt-3 time text-size-m ">{{ $tm.dayjs(journal.createTime).format('YYYY-MM-DD HH:mm:ss') }}</view>
</view>
</view>
<view class="right">
<tm-button v-if="useLike" :shadow="0" theme="light-blue" size="s" @click="fnLike(journal)">点赞({{ journal.likes }})</tm-button>
<tm-button v-if="useEdit" :shadow="0" theme="light-blue" size="s" @click="$emit('on-edit', journal)">编辑</tm-button>
<tm-button v-if="useDel" :shadow="0" theme="red" size="s" @click="fnDel(journal)">删除</tm-button>
</view>
</view>
<tm-more v-if="journal.content.length > 50" :maxHeight="100" label="查看全部内容" open-label="隐藏部分内容">
<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="journal.content"
:markdown="true"
:showLineNumber="true"
:showLanguageName="true"
:copyByLongPress="true"
/>
</tm-more>
<mp-html
v-else
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="journal.content"
:markdown="true"
:showLineNumber="true"
:showLanguageName="true"
:copyByLongPress="true"
/>
</view>
</template>
<script>
import MarkdownConfig from '@/common/markdown/markdown.config.js';
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmMore from '@/tm-vuetify/components/tm-more/tm-more.vue';
export default {
name: 'journal-card',
components: { mpHtml, tmButton, tmMore },
props: {
isAdmin: {
type: Boolean,
default: false
},
journal: {
type: Object,
default: () => {}
},
useLike: {
type: Boolean,
default: false
},
useEdit: {
type: Boolean,
default: false
},
useDel: {
type: Boolean,
default: false
}
},
data() {
return {
markdownConfig: MarkdownConfig
};
},
computed: {
// 获取博主信息
bloggerInfo() {
let blogger = this.$tm.vx.getters().blogger.getBlogger;
blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
return blogger;
}
},
methods: {
fnLike(journal) {
uni.showLoading({
mask: true,
title: '正在点赞中...'
});
this.$httpApi
.postJournalLikes(journal.id)
.then(res => {
if (res.status == 200) {
journal.likes += 1;
uni.$tm.toast('o( ̄▽ ̄)d点赞成功!');
} else {
uni.$tm.toast('Ծ‸Ծ点赞失败了~');
}
})
.catch(err => {
uni.$tm.toast('Ծ‸Ծ点赞失败了~');
});
},
fnDel(journal) {
uni.$eShowModal({
title: '提示',
content: '您确定要删除该日记吗?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
this.$httpApi.admin
.deleteJournalsById(journal.id)
.then(res => {
if (res.status == 200) {
this.$emit('on-del', journal);
uni.$tm.toast('删除成功!');
} else {
uni.$tm.toast('Ծ‸Ծ删除失败~');
}
})
.catch(err => {
uni.$tm.toast('Ծ‸Ծ删除失败~');
});
})
.catch(() => {});
}
}
};
</script>
<style scoped lang="scss">
.journal-card {
box-sizing: border-box;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.05);
overflow: hidden;
.avatar {
width: 70rpx;
height: 70rpx;
border: 6rpx solid #fff;
box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.05);
}
.info {
justify-content: center;
.nickname {
font-size: 30rpx;
}
.time {
font-size: 26rpx;
}
}
}
</style>
-15
View File
@@ -1,15 +0,0 @@
#如何使用
###js
```javascript
import lffBarrage from '@/components/lff-barrage/lff-barrage.vue'
components:{lffBarrage},
methods:{
colrdo(){ //插入一条弹幕
this.$refs.lffBarrage.add({item:'你好呀小伙子'});
}
}
```
###HTML
```html
<lff-barrage ref="lffBarrage"></lff-barrage>
```
+85
View File
@@ -0,0 +1,85 @@
<template>
<view class="scroll-btn" :style="{ bottom: bottom }" @click.stop="fnScroll()">
<text v-if="_scrollTop >= 180" class="iconfont icon-long-arrow-up"></text>
<text v-else class="iconfont icon-long-arrow-down"></text>
</view>
</template>
<script>
import throttle from '@/utils/throttle.js';
export default {
name: 'scroll-btn',
props: {
bottom: {
type: String,
default: '180rpx'
},
scrollTop: {
type: Number,
default: 0
}
},
data() {
return { timer: null, _scrollTop: 0 };
},
watch: {
scrollTop(val) {
this._scrollTop = val;
this.$forceUpdate();
}
},
created() {
this._scrollTop = this.scrollTop;
},
methods: {
fnScroll() {
throttle(() => {
this.$emit('on-status', false);
const isTop = this._scrollTop >= 180;
this._scrollTop = isTop ? 0 : 999999999999;
uni.pageScrollTo({
duration: 500,
scrollTop: this._scrollTop,
success: () => {
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.$emit('on-status', true);
}, 500);
}
});
this.$emit('update:scrollTop', this._scrollTop);
});
}
}
};
</script>
<style scoped lang="scss">
.scroll-btn {
position: fixed;
// bottom: 200rpx;
// right: -90rpx;
right: 52rpx;
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
border-radius: 50%;
border: 4rpx solid #ffffff;
background-color: #bfe9ef;
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.1);
z-index: 99;
transition: right 0.5s ease-in-out;
&.is-show {
right: 52rpx;
}
.iconfont {
font-size: 36rpx;
color: #555;
}
}
</style>
-20
View File
@@ -1,20 +0,0 @@
/**
* 广告配置
*/
export default {
adpid: '', // uni-AD App广告位id,在uni-AD官网申请广告位
unitId: '', // 广告单元id,可在小程序管理后台的流量主模块新建 (非个人资质,小程序后台广告主开通申请)
frequency: 8, // 列表中,广告出现的频率(8=每8条数据出现一次广告)
// 首页广告
home: {
use: false,
},
// 文章列表广告
articles: {
use: false,
},
// 文章详情广告
articleDetail: {
use: false, // 是否启用
}
}
-113
View File
@@ -1,113 +0,0 @@
/**
* 功能:基础配置
* 作者:小莫唐尼
* 邮箱:studio@925i.cn
* 时间:2022年08月23日 15:19:14
* 版本:v0.1.0
* 修改记录:
* 修改内容:
* 修改人员:
* 修改时间:
*/
export default {
showCopyright: true, // 显示开源版权信息
showAbout: true, // 显示关于项目入口
uni_halo_logo: 'https://b.925i.cn/uni_halo/uni_halo_logo.png', // uni-halo的logo
apiUrl: '', // Api基础域名(您的halo博客基础域名或者是Halo后台管理系统api地址)
apiAuthorization: '', // Halo中-系统-博客设置-切换到高级选项-API设置-Access key
title: '', // 博客标题
indexImageUrl: '', // 开屏首页图片
miniCodeImageUrl: '', // 小程序码地址
author: {
name: '', // 昵称
avatar: '', // 头像地址
motto: '', // 格言
},
social: {
qq: "", // qq号
wechat: "", // 微信号
weibo: "", // 微博地址
email: "", // 邮箱地址
blog: "", // 博客地址
juejin: "", // 掘金地址
bilibili: "", // b站地址
gitee: "", // gitee地址
github: "", // github地址
csdn: "" // CSDN地址
},
defaultThumbnailUrl: '', // 默认封面图地址
defaultImageUrl: '', // 默认图片地址
defaultAvatarUrl: '', // 默认头像地址
loadingGifUrl: '', // 图片加载中的地址
loadingErrUrl: '', // 图片加载失败的地址
loadingEmptyUrl: '', // 加载图片为空地址
waveImageUrl: '', // 关于页面波浪图片
banner: { // 轮播图配置
type: 'article', // 轮播图数据源 list=下方配置 article=热门文章封面
list: [],
},
quickNav: { // 快捷导航配置
use: true,
list: [{
icon: 'halocoloricon-classify',
text: '文章归档',
iconSize: 60,
color: 'blue',
type: 'page',
path: '/pagesA/archives/archives'
},
{
icon: 'halocoloricon-attent',
text: '恋爱日记',
iconSize: 60,
color: 'blue',
type: 'page',
path: '/pagesA/love/love'
},
{
icon: 'halocoloricon-calendar',
text: '个人日记',
iconSize: 60,
color: 'blue',
type: 'page',
path: '/pagesA/journal/journal'
},
{
icon: 'halocoloricon-message',
text: '留言板',
iconSize: 60,
color: 'blue',
type: 'page',
path: '/pagesA/leaving/leaving'
}
]
},
// 微信分享信息
wxShareConfig: {
title: '', // 小程序分享标题[非必填]
desc: '', // 小程序分享描述[非必填]
imageUrl: '', // 小程序分享时候图片地址[非必填]
path: '/pages/start/start', // 分享路径[非必填] - 基本不需要修改
copyLink: '/pages/start/start', // 复制链接[非必填] - 基本不需要修改
query: {}, // 分享参数[非必填] - 基本不需要填写
},
colors: [
'#39B449',
'#E44C41',
'#8698A2',
'#0080FE',
'#1CBCB4',
'#6638B5',
]
}
+50
View File
@@ -0,0 +1,50 @@
// 应用设置存储key值
export const _AppConfigKey = 'APP_GLOBAL_CONFIGS';
export const _AppMockJsonKey = 'APP_GLOBAL_MOCK_JSON';
// 默认的应用设置
export const DefaultAppConfigs = {
loveConfig: {},
imagesConfig: {},
authorConfig: {},
appConfig: {},
pluginConfig: {},
pageConfig: {}
}
/**
* 获取应用设置
*/
export const getAppConfigs = () => {
let configs = uni.getStorageSync(_AppConfigKey)
if (configs) return JSON.parse(configs)
uni.setStorageSync(_AppConfigKey, JSON.stringify(DefaultAppConfigs))
return DefaultAppConfigs;
}
/**
* 保存应用设置
*/
export const setAppConfigs = (configs) => {
uni.setStorageSync(_AppConfigKey, JSON.stringify(configs))
}
/**
* 获取应用模拟数据
*/
export const getAppMockJson = () => {
let json = uni.getStorageSync(_AppMockJsonKey)
if (json) return JSON.parse(json)
uni.setStorageSync(_AppMockJsonKey, JSON.stringify({}))
return {};
}
/**
* 保存应用模拟数据
*/
export const setAppMockJson = (json) => {
uni.setStorageSync(_AppMockJsonKey, JSON.stringify(json))
}
-7
View File
@@ -1,7 +0,0 @@
/**
* 配置key
*/
export default {
SHEET_LEAVING: 'leaving', // 留言板
}
-8
View File
@@ -1,8 +0,0 @@
/**
* 页面配置
*/
import AppKeys from './keys.js'
export default {
[AppKeys.SHEET_LEAVING]: 65, // 留言板页面ID
}
+16
View File
@@ -0,0 +1,16 @@
/** 配置后台管理员token */
const HaloTokenConfig = Object.freeze({
/** 基础请求域名:你的Halo博客基础域名 */
BASE_API: "https://demo.halo.run",
// BASE_API: "https://blog.925i.cn",
/** 管理员token */
systemToken: ``,
/** 匿名用户token */
anonymousToken: ``
})
export default HaloTokenConfig;
+177
View File
@@ -0,0 +1,177 @@
{
"home": {
"bannerList": [
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
},
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
},
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
},
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
},
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
}
],
"categoryList": [
{
"title": "uni-halo",
"desc": "",
"cover": "/upload/uni_halo_ad_cover.png",
"time": ""
},
{
"title": "uni-halo",
"desc": "",
"cover": "/upload/uni_halo_ad_cover.png",
"time": ""
},
{
"title": "uni-halo",
"desc": "",
"cover": "/upload/uni_halo_ad_cover.png",
"time": ""
},
{
"title": "uni-halo",
"desc": "",
"cover": "/upload/uni_halo_ad_cover.png",
"time": ""
}
],
"postList": [
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
},
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
},
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
},
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
},
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
}
]
},
"category": {
"list": [
{
"title": "uni-halo",
"desc": "",
"cover": "/upload/uni_halo_ad_cover.png",
"time": ""
},
{
"title": "uni-halo",
"desc": "",
"cover": "/upload/uni_halo_ad_cover.png",
"time": ""
},
{
"title": "uni-halo",
"desc": "",
"cover": "/upload/uni_halo_ad_cover.png",
"time": ""
},
{
"title": "uni-halo",
"desc": "",
"cover": "/upload/uni_halo_ad_cover.png",
"time": ""
}
]
},
"moments": {
"list": [
{
"content": "你好啊,这是我的一条生活记录",
"time": "2024-05-25 08:59:00",
"images": [
"/upload/uni_halo_ad_cover.png",
"/upload/uni_halo_ad_cover.png",
"/upload/uni_halo_ad_cover.png"
]
}
]
},
"gallery": {
"list": [
"/upload/uni_halo_ad_cover.png",
"/upload/uni_halo_ad_cover.png",
"/upload/uni_halo_ad_cover.png",
"/upload/uni_halo_ad_cover.png"
]
},
"archives": {
"list": [
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
},
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
},
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
},
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
},
{
"title": "uni-halo2.0)开源啦!!!",
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
"cover": "/upload/uni_halo_ad_cover.png",
"time": "2024-05-25 08:59:00"
}
]
}
}
+3 -18
View File
@@ -1,10 +1,5 @@
import App from "./App";
import Vue from "vue";
import {
router,
RouterMount
} from './router/router.js'
Vue.use(router)
// 挂载全局工具类
import utils from "./utils/index.js";
@@ -62,12 +57,9 @@ Vue.prototype.$http = http;
import ApiManager from '@/api/index.js'
Vue.use(ApiManager);
import HaloConfig from '@/config/halo.config.js'
Vue.prototype.$haloConfig = HaloConfig
Vue.prototype.$baseApiUrl = HaloConfig.apiUrl
import HaloTokenConfig from '@/config/token.config.js'
Vue.prototype.$baseApiUrl = HaloTokenConfig.BASE_API
import HaloAdConfig from '@/config/ad.config.js'
Vue.prototype.$haloAdConfig = HaloAdConfig
// 由于微信小程序的运行机制问题,需声明如下一行,H5和APP非必填
Vue.prototype._i18n = i18n;
@@ -77,12 +69,5 @@ const app = new Vue({
i18n,
...App,
});
// app.$mount();
// #ifdef H5
RouterMount(app, router, '#app')
// #endif
// #ifndef H5
app.$mount(); //为了兼容小程序及app端必须这样写才有效果
// #endif
app.$mount();
+215 -157
View File
@@ -1,159 +1,217 @@
{
"name": "uni-halo",
"appid": "__UNI__5748B6E",
"description": "uni-halo博客:基于halo开源博客系统API开发的多端博客。",
"versionName": "1.0.0",
"versionCode": 100,
"transformPx": false,
/* 5+App */
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
/* */
"modules": {
"OAuth": {},
"Share": {}
},
/* */
"distribute": {
/* android */
"android": {
"permissions": [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CAPTURE_VIDEO_OUTPUT\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios": {
"dSYMs": false,
"capabilities": {
"entitlements": {
"com.apple.developer.associated-domains": [
"applinks:static-699a26a5-a6cd-4b54-b603-5155b312a5f5.bspapp.com"
]
}
}
},
/* SDK */
"sdkConfigs": {
"ad": {
"gdt": {},
"csj": {},
"360": {},
"ks": {},
"sigmob": {},
"hw": {},
"bd": {}
},
"oauth": {
"weixin": {
"appid": "wxd971954762ec4744",
"UniversalLinks": "https://static-699a26a5-a6cd-4b54-b603-5155b312a5f5.bspapp.com/uni-universallinks/__UNI__5748B6E/"
}
},
"share": {
"weixin": {
"appid": "wxd971954762ec4744",
"UniversalLinks": "https://static-699a26a5-a6cd-4b54-b603-5155b312a5f5.bspapp.com/uni-universallinks/__UNI__5748B6E/"
}
}
},
"icons": {
"android": {
"hdpi": "unpackage/res/icons/72x72.png",
"xhdpi": "unpackage/res/icons/96x96.png",
"xxhdpi": "unpackage/res/icons/144x144.png",
"xxxhdpi": "unpackage/res/icons/192x192.png"
},
"ios": {
"appstore": "unpackage/res/icons/1024x1024.png",
"ipad": {
"app": "unpackage/res/icons/76x76.png",
"app@2x": "unpackage/res/icons/152x152.png",
"notification": "unpackage/res/icons/20x20.png",
"notification@2x": "unpackage/res/icons/40x40.png",
"proapp@2x": "unpackage/res/icons/167x167.png",
"settings": "unpackage/res/icons/29x29.png",
"settings@2x": "unpackage/res/icons/58x58.png",
"spotlight": "unpackage/res/icons/40x40.png",
"spotlight@2x": "unpackage/res/icons/80x80.png"
},
"iphone": {
"app@2x": "unpackage/res/icons/120x120.png",
"app@3x": "unpackage/res/icons/180x180.png",
"notification@2x": "unpackage/res/icons/40x40.png",
"notification@3x": "unpackage/res/icons/60x60.png",
"settings@2x": "unpackage/res/icons/58x58.png",
"settings@3x": "unpackage/res/icons/87x87.png",
"spotlight@2x": "unpackage/res/icons/80x80.png",
"spotlight@3x": "unpackage/res/icons/120x120.png"
}
}
}
}
},
/* */
"quickapp": {},
/* */
"mp-weixin": {
"appid": "wx1bb161867082c72f",
"setting": {
"urlCheck": false,
"minified": true
},
"usingComponents": true
},
"mp-alipay": {
"usingComponents": true
},
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao": {
"usingComponents": true
},
"uniStatistics": {
"enable": false
},
"vueVersion": "2",
"h5": {
"title": "uni-halo",
"devServer": {
"https": false,
"disableHostCheck": true,
"proxy": {
"/api": {
"target": "https://b.925i.cn",
"changeOrigin": true,
"secure": false,
"pathRewrite": {
"^/api": ""
}
}
}
}
},
"_spaceID": "699a26a5-a6cd-4b54-b603-5155b312a5f5"
"name" : "uni-halo",
"appid" : "__UNI__5748B6E",
"description" : "uni-halo博客:基于halo开源博客系统API开发的多端博客。",
"versionName" : "1.0.0",
"versionCode" : 100,
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {
"OAuth" : {},
"Share" : {}
},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CAPTURE_VIDEO_OUTPUT\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {
"dSYMs" : false,
"capabilities" : {
"entitlements" : {
"com.apple.developer.associated-domains" : [ "applinks:static-699a26a5-a6cd-4b54-b603-5155b312a5f5.bspapp.com" ]
}
}
},
/* SDK */
"sdkConfigs" : {
"ad" : {
"gdt" : {},
"csj" : {},
"360" : {},
"ks" : {},
"sigmob" : {},
"hw" : {},
"bd" : {}
},
"oauth" : {
"weixin" : {
"appid" : "wxd971954762ec4744",
"UniversalLinks" : "https://static-699a26a5-a6cd-4b54-b603-5155b312a5f5.bspapp.com/uni-universallinks/__UNI__5748B6E/"
}
},
"share" : {
"weixin" : {
"appid" : "wxd971954762ec4744",
"UniversalLinks" : "https://static-699a26a5-a6cd-4b54-b603-5155b312a5f5.bspapp.com/uni-universallinks/__UNI__5748B6E/"
}
}
},
"icons" : {
"android" : {
"hdpi" : "unpackage/res/icons/72x72.png",
"xhdpi" : "unpackage/res/icons/96x96.png",
"xxhdpi" : "unpackage/res/icons/144x144.png",
"xxxhdpi" : "unpackage/res/icons/192x192.png"
},
"ios" : {
"appstore" : "unpackage/res/icons/1024x1024.png",
"ipad" : {
"app" : "unpackage/res/icons/76x76.png",
"app@2x" : "unpackage/res/icons/152x152.png",
"notification" : "unpackage/res/icons/20x20.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"proapp@2x" : "unpackage/res/icons/167x167.png",
"settings" : "unpackage/res/icons/29x29.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"spotlight" : "unpackage/res/icons/40x40.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png"
},
"iphone" : {
"app@2x" : "unpackage/res/icons/120x120.png",
"app@3x" : "unpackage/res/icons/180x180.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"notification@3x" : "unpackage/res/icons/60x60.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"settings@3x" : "unpackage/res/icons/87x87.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png",
"spotlight@3x" : "unpackage/res/icons/120x120.png"
}
}
}
},
"uniStatistics" : {
"enable" : true
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wx1bb161867082c72f",
"setting" : {
"urlCheck" : false,
"minified" : true
},
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"mp-alipay" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"mp-baidu" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"mp-toutiao" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"uniStatistics" : {
"enable" : true,
"version" : "2"
},
"vueVersion" : "2",
"h5" : {
"title" : "uni-halo",
"devServer" : {
"disableHostCheck" : true,
"proxy" : {
"/apis" : {
"target" : "https://demo.halo.run",
"changeOrigin" : true,
"secure" : false,
"pathRewrite" : {
"^/apis" : "/apis"
}
}
}
},
"optimization" : {
"preload" : true,
"treeShaking" : {
"enable" : true
}
},
"uniStatistics" : {
"enable" : true
}
},
"_spaceID" : "699a26a5-a6cd-4b54-b603-5155b312a5f5",
"mp-qq" : {
"appid" : "1112285420",
"setting" : {
"minified" : true,
"postcss" : true,
"es6" : true
},
"uniStatistics" : {
"enable" : true
}
},
"mp-jd" : {
"uniStatistics" : {
"enable" : true
}
},
"mp-kuaishou" : {
"uniStatistics" : {
"enable" : true
}
},
"mp-lark" : {
"uniStatistics" : {
"enable" : true
}
},
"quickapp-webview-huawei" : {
"uniStatistics" : {
"enable" : true
}
},
"quickapp-webview-union" : {
"uniStatistics" : {
"enable" : true
}
}
}
+19 -2
View File
@@ -1,9 +1,26 @@
{
"name": "uni-halo",
"version": "1.0.0",
"description": "<p align=\"center\">\r <img alt=\"logo\" src=\"https://b.925i.cn/uni_halo/uni_halo_logo.png\" width=\"120\" height=\"120\" style=\"margin-bottom: 10px;\">\r </p>\r <h3 align=\"center\" style=\"margin: 30px 0 30px;font-weight: bold;font-size:40px;\">uni-halo</h3>\r <h3 align=\"center\">uni-halo 基于Halo一款现代化的开源博客/CMS系统API开发的可多端编译应用</h3>",
"repository": {
"type": "git",
"url": "git+https://gitee.com/ialley-workshop-open/uni-halo.git"
},
"keywords": [
"uni-halo",
"小莫唐尼",
"巷子工坊"
],
"author": "巷子工坊丨小莫唐尼",
"license": "AGPL-3.0",
"bugs": {
"url": "https://gitee.com/ialley-workshop-open/uni-halo/issues"
},
"homepage": "https://uni-halo.925i.cn",
"devDependencies": {
"vue-i18n": "^9.1.10"
},
"dependencies": {
"uni-read-pages": "^1.0.5",
"uni-simple-router": "^2.0.8-beta.4"
"qs": "^6.12.1"
}
}
+334 -540
View File
@@ -1,542 +1,336 @@
{
"pages": [{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-halo",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/tabbar/home/home",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true,
"navigationStyle": "custom",
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
}, {
"path": "pages/tabbar/category/category",
"style": {
"navigationBarTitleText": "分类",
"enablePullDownRefresh": false,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
}, {
"path": "pages/tabbar/gallery/gallery",
"style": {
"navigationBarTitleText": "图库",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
}, {
"path": "pages/tabbar/links/links",
"style": {
"navigationBarTitleText": "友链",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
}, {
"path": "pages/tabbar/about/about",
"style": {
"navigationBarTitleText": "关于",
"navigationStyle": "custom",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
}, {
"path": "uni_modules/uni-upgrade-center-app/pages/upgrade-popup",
"style": {
"disableScroll": true,
"app-plus": {
"backgroundColorTop": "transparent",
"background": "transparent",
"titleNView": false,
"scrollIndicator": false,
"popGesture": "none",
"animationType": "fade-in",
"animationDuration": 200
}
}
}
],
//此配置为小程序的分包加载机制。在5+App里始终为整包。
"subPackages": [{
"root": "pagesA",
"pages": [{
"path": "start/start",
"style": {
"navigationBarTitleText": "uni-halo",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
}, {
"path": "article-detail/article-detail",
"style": {
"navigationBarTitleText": "文章详情",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
}, {
"path": "love/love",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
}, {
"path": "archives/archives",
"style": {
"navigationBarTitleText": "文章归档",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
}, {
"path": "journal/journal",
"style": {
"navigationBarTitleText": "个人日记",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
}, {
"path": "leaving/leaving",
"style": {
"navigationBarTitleText": "留言板",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
}, {
"path": "articles/articles",
"style": {
"navigationBarTitleText": "文章列表",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
}, {
"path": "category-detail/category-detail",
"style": {
"navigationBarTitleText": "分类详情",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
}, {
"path": "comment/comment",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
},
"meta": {
"auth": "login"
}
}, {
"path": "disclaimers/disclaimers",
"style": {
"navigationBarTitleText": "免责声明",
"enablePullDownRefresh": false
}
}, {
"path": "contact/contact",
"style": {
"navigationBarTitleText": "联系博主",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
}, {
"path": "about/about",
"style": {
"navigationBarTitleText": "关于博客",
"enablePullDownRefresh": false
}
}, {
"path": "setting/setting",
"style": {
"navigationBarTitleText": "设置",
"enablePullDownRefresh": false
}
}]
},
{
"root": "pagesB",
"pages": [{
"path": "login/login",
"style": {
"navigationBarTitleText": "登录",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
}, {
"path": "admin/admin",
"style": {
"navigationBarTitleText": "工作台",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "settings/settings",
"style": {
"navigationBarTitleText": "系统设置",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "journal/journal",
"style": {
"navigationBarTitleText": "日记管理",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "journal/journal-edit",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "articles/articles",
"style": {
"navigationBarTitleText": "文章管理",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "articles/article-edit",
"style": {
"navigationBarTitleText": "写文章",
"enablePullDownRefresh": false
},
"meta": {
"auth": "admin"
}
}, {
"path": "articles/article-setting",
"style": {
"navigationBarTitleText": "文章设置",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "category/category",
"style": {
"navigationBarTitleText": "分类管理",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "tags/tags",
"style": {
"navigationBarTitleText": "标签管理",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "attachment/attachment",
"style": {
"navigationBarTitleText": "附件管理",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "links/links",
"style": {
"navigationBarTitleText": "友链管理",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "photos/photos",
"style": {
"navigationBarTitleText": "图库管理",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "photos/photos-edit",
"style": {
"navigationBarTitleText": "新增/编辑图片",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "comments/comments",
"style": {
"navigationBarTitleText": "评论管理",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "menus/menus",
"style": {
"navigationBarTitleText": "菜单管理",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "password/password",
"style": {
"navigationBarTitleText": "修改密码",
"enablePullDownRefresh": false
},
"meta": {
"auth": "admin"
}
}, {
"path": "profile/profile",
"style": {
"navigationBarTitleText": "个人信息",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}, {
"path": "logs/logs",
"style": {
"navigationBarTitleText": "日志管理",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
},
"meta": {
"auth": "admin"
}
}]
}, {
"root": "pagesC",
"pages": [{
"path": "website/website",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}]
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-halo",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"backgroundColor": "white",
"borderStyle": "white",
"color": "#303133",
"selectedColor": "#03a9f4",
"list": [{
"iconPath": "static/tabbar/select_home.png",
"selectedIconPath": "static/tabbar/select_home_active.png",
"pagePath": "pages/tabbar/home/home",
"text": "首页"
},
{
"iconPath": "static/tabbar/select_category.png",
"selectedIconPath": "static/tabbar/select_category_active.png",
"pagePath": "pages/tabbar/category/category",
"text": "分类"
}, {
"iconPath": "static/tabbar/select_gallery.png",
"selectedIconPath": "static/tabbar/select_gallery_active.png",
"pagePath": "pages/tabbar/gallery/gallery",
"text": "图库"
}, {
"iconPath": "static/tabbar/select_links.png",
"selectedIconPath": "static/tabbar/select_links_active.png",
"pagePath": "pages/tabbar/links/links",
"text": "友链"
}, {
"iconPath": "static/tabbar/select_mine.png",
"selectedIconPath": "static/tabbar/select_mine_active.png",
"pagePath": "pages/tabbar/about/about",
"text": "关于"
}
]
}
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-halo",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/tabbar/home/home",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true,
"navigationStyle": "custom",
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "pages/tabbar/category/category",
"style": {
"navigationBarTitleText": "分类",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "pages/tabbar/gallery/gallery",
"style": {
"navigationBarTitleText": "图库",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "pages/tabbar/moments/moments",
"style": {
"navigationBarTitleText": "瞬间",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "pages/tabbar/about/about",
"style": {
"navigationBarTitleText": "关于",
"navigationStyle": "custom",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "uni_modules/uni-upgrade-center-app/pages/upgrade-popup",
"style": {
"disableScroll": true,
"app-plus": {
"backgroundColorTop": "transparent",
"background": "transparent",
"titleNView": false,
"scrollIndicator": false,
"popGesture": "none",
"animationType": "fade-in",
"animationDuration": 200
}
}
}
],
//此配置为小程序的分包加载机制。在5+App里始终为整包。
"subPackages": [
{
"root": "pagesA",
"pages": [
{
"path": "start/start",
"style": {
"navigationBarTitleText": "uni-halo",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "article-detail/article-detail",
"style": {
"navigationBarTitleText": "内容详情",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "love/love",
"style": {
"navigationBarTitleText": "恋爱日记",
"navigationStyle": "custom",
"enablePullDownRefresh": false,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "love/list",
"style": {
"navigationBarTitleText": "恋爱清单",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "love/album",
"style": {
"navigationBarTitleText": "恋爱相册",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "love/journey",
"style": {
"navigationBarTitleText": "我们的故事",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "archives/archives",
"style": {
"navigationBarTitleText": "归档",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "friend-links/friend-links",
"style": {
"navigationBarTitleText": "友情链接",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "articles/articles",
"style": {
"navigationBarTitleText": "内容搜索",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "category-detail/category-detail",
"style": {
"navigationBarTitleText": "分类详情",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "disclaimers/disclaimers",
"style": {
"navigationBarTitleText": "免责声明",
"enablePullDownRefresh": false
}
},
{
"path": "contact/contact",
"style": {
"navigationBarTitleText": "联系博主",
"enablePullDownRefresh": false
}
},
{
"path": "about/about",
"style": {
"navigationBarTitleText": "关于博客",
"enablePullDownRefresh": false
}
},
{
"path": "setting/setting",
"style": {
"navigationBarTitleText": "设置",
"enablePullDownRefresh": false
}
},
{
"path": "tags/tags",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},
{
"path": "tag-detail/tag-detail",
"style": {
"navigationBarTitleText": "标签详情",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"color": "#03a9f4",
"style": "circle"
}
}
}
},
{
"path": "advertise/advertise",
"style": {
"navigationBarTitleText": "广告页面",
"enablePullDownRefresh": false
}
},
{
"path": "submit-link/submit-link",
"style": {
"navigationBarTitleText": "友链提交",
"enablePullDownRefresh": false
}
}
]
},
{
"root": "pagesC",
"pages": [
{
"path": "website/website",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
]
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-halo",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"backgroundColor": "#fff",
"borderStyle": "white",
"color": "#303133",
"selectedColor": "#03a9f4",
"list": [
{
"iconPath": "static/tabbar/select_home.png",
"selectedIconPath": "static/tabbar/select_home_active.png",
"pagePath": "pages/tabbar/home/home",
"text": "首页"
},
{
"iconPath": "static/tabbar/select_category.png",
"selectedIconPath": "static/tabbar/select_category_active.png",
"pagePath": "pages/tabbar/category/category",
"text": "分类"
},
{
"iconPath": "static/tabbar/select_gallery.png",
"selectedIconPath": "static/tabbar/select_gallery_active.png",
"pagePath": "pages/tabbar/gallery/gallery",
"text": "图库"
},
{
"iconPath": "static/tabbar/select_links.png",
"selectedIconPath": "static/tabbar/select_links_active.png",
"pagePath": "pages/tabbar/moments/moments",
"text": "瞬间"
},
{
"iconPath": "static/tabbar/select_mine.png",
"selectedIconPath": "static/tabbar/select_mine_active.png",
"pagePath": "pages/tabbar/about/about",
"text": "关于"
}
]
}
}
+79 -18
View File
@@ -1,25 +1,86 @@
<template>
<view class="app-page"></view>
<view class="app-page"></view>
</template>
<script>
const homePagePath = '/pages/tabbar/home/home'
const startPagePath = '/pagesA/start/start'
const articleDetailPath = '/pagesA/article-detail/article-detail';
export default {
onLoad() {
this.fnCheckHadStarted();
},
methods: {
// 检查是否已经启动过
fnCheckHadStarted() {
if (uni.getStorageSync('APP_HAS_STARTED')) {
uni.switchTab({
url: '/pages/tabbar/home/home'
});
} else {
uni.redirectTo({
url: '/pagesA/start/start'
});
}
}
}
computed: {
configs() {
return this.$tm.vx.getters().getConfigs;
}
},
onLoad: function (options) {
uni.$tm.vx.actions('config/fetchConfigs').then(async (res) => {
if (options.scene) {
if ('' !== options.scene) {
const postId = await this.getPostIdByQRCode(options.scene);
if (postId) {
uni.redirectTo({
url: articleDetailPath + `?name=${postId}`,
animationType: 'slide-in-right'
});
}
}
}
// #ifdef MP-WEIXIN
// uni.$tm.vx.commit('setWxShare', res.shareConfig);
// #endif
// 获取mockjson
if (res.basicConfig.auditModeEnabled) {
await uni.$tm.vx.actions('config/fetchMockJson')
}
// 进入检查
this.fnCheckShowStarted();
}).catch((err) => {
uni.switchTab({
url: homePagePath
});
})
},
methods: {
fnCheckShowStarted() {
if (!this.configs.appConfig.startConfig.enabled) {
uni.switchTab({
url: homePagePath
});
return;
}
// 是否每次都显示启动页
if (this.configs.appConfig.startConfig.alwaysShow) {
uni.removeStorageSync('APP_HAS_STARTED')
uni.redirectTo({
url: startPagePath
});
return;
}
// 只显示一次启动页
if (uni.getStorageSync('APP_HAS_STARTED')) {
uni.switchTab({
url: homePagePath
});
} else {
uni.redirectTo({
url: startPagePath
});
}
},
async getPostIdByQRCode(key) {
const response = await this.$httpApi.v2.getQRCodeInfo(key);
if (response) {
if (response && response.postId) {
return response.postId;
}
}
return null;
}
}
};
</script>
File diff suppressed because it is too large Load Diff
+234 -205
View File
@@ -1,229 +1,258 @@
<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>
<tm-skeleton model="listAvatr"></tm-skeleton>
</view>
<view v-else class="content flex">
<view v-if="categoryList.length == 0" class="category-empty flex flex-center">
<tm-empty icon="icon-shiliangzhinengduixiang-" label="博主还没有文章分类~"></tm-empty>
</view>
<block v-else>
<tm-sliderNav :list="categoryList" color="light-blue" rang-key="name" @change="fnOnCategoryChange"></tm-sliderNav>
<scroll-view
class="right-content pt-12 pb-12"
:scroll-y="true"
:scroll-top="scrollTop"
:scroll-with-animation="true"
:refresher-enabled="true"
:refresher-triggered="triggered"
:refresher-threshold="60"
@refresherrefresh="fnGetData(true)"
@scrolltolower="fnGetData(false)"
@scroll="fnOnScroll"
>
<view v-if="dataList.length == 0" class="article-empty flex flex-center">
<tm-empty icon="icon-shiliangzhinengduixiang-" label="该分类下暂无文章~"></tm-empty>
</view>
<view class="app-page">
<block v-else>
<block v-for="(article, index) in dataList" :key="article.createTime">
<!-- 文章卡片 -->
<tm-translate animation-name="fadeUp" :wait="(index + 1) * 50">
<article-min-card :article="article" @on-click="fnToArticleDetail"></article-min-card>
</tm-translate>
</block>
<view class="load-text">{{ loadMoreText }}</view>
</block>
</scroll-view>
</block>
</view>
<tm-flotbutton v-if="dataList.length > 10" color="light-blue" @click="fnToTopScroll" size="m" icon="icon-angle-up"></tm-flotbutton>
</view>
<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="height: 70vh;">
<!-- 空布局 -->
<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;width: 50%;padding: 0 8rpx;" animation-name="fadeUp"
:wait="calcAniWait(index)">
<view class="catgory-card" :style="{backgroundImage:`url(${item.spec.cover})`}">
<view class="content" @click="handleToCategory(item)">
<view style="font-size: 32rpx;color: #ffffff;">{{ item.spec.displayName }}</view>
<view v-if="!haloConfigs.basicConfig.auditModeEnabled" style="font-size: 24rpx;color: #ffffff;margin-top: 6rpx;">
{{ item.postCount }} 篇文章
</view>
<view v-else style="font-size: 24rpx;color: #ffffff;margin-top: 6rpx;">
{{ item.postCount }} 篇内容
</view>
</view>
</view>
</tm-translate>
<tm-flotbutton @click="fnToTopPage" size="m" color="light-blue"
icon="icon-angle-up"></tm-flotbutton>
<view class="load-text">{{ loadMoreText }}</view>
</block>
</view>
</view>
</template>
<script>
import throttle from '@/utils/throttle.js';
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 tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import tmFlowLayout from '@/tm-vuetify/components/tm-flowLayout/tm-flowLayout.vue';
import tmSliderNav from '@/tm-vuetify/components/tm-sliderNav/tm-sliderNav.vue';
import ArticleMinCard from '@/components/article-min-card/article-min-card.vue';
import MarkdownConfig from '@/common/markdown/markdown.config.js';
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
export default {
components: {
tmSkeleton,
tmTranslate,
tmFlotbutton,
tmEmpty,
tmFlowLayout,
tmSliderNav,
ArticleMinCard
},
data() {
return {
loading: 'loading',
queryParams: {
size: 10,
page: 0,
slug: ''
},
categoryList: [],
result: null,
dataList: [],
isLoadMore: false,
loadMoreText: '',
scrollTop: 0,
triggered: false
};
},
components: {
tmSkeleton,
tmFlotbutton,
tmTranslate,
tmEmpty,
mpHtml
},
data() {
return {
markdownConfig: MarkdownConfig,
loading: 'loading',
queryParams: {
size: 20,
page: 1
},
hasNext: false,
dataList: [],
isLoadMore: false,
loadMoreText: '加载中...'
};
},
computed: {
haloConfigs() {
return this.$tm.vx.getters().getConfigs;
},
mockJson() {
return this.$tm.vx.getters().getMockJson;
}
},
onLoad() {
this.fnGetData();
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
},
onLoad() {
this.fnSetPageTitle('文章分类');
this.fnGetAllCategory();
},
onPullDownRefresh() {
this.queryParams.page = 0;
this.isLoadMore = false;
this.fnGetData();
},
onReachBottom(e) {
if (this.result.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData(false);
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnOnCategoryChange(e) {
this.queryParams.slug = this.categoryList[e].slug;
this.fnToTopScroll();
this.dataList = [];
this.fnGetData();
},
onReachBottom(e) {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
return
}
if (this.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnGetData() {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
this.dataList = this.mockJson.category.list.map((item) => {
return {
metadata: {
name: Date.now() * Math.random(),
},
spec: {
displayName: item.title,
cover: this.$utils.checkImageUrl(item.cover)
},
postCount: 0
}
});
this.loading = 'success';
this.loadMoreText = '呜呜,没有更多数据啦~';
uni.hideLoading();
uni.stopPullDownRefresh();
return;
}
uni.showLoading({
mask: true,
title: '加载中...'
});
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi.v2
.getCategoryList(this.queryParams)
.then(res => {
console.log('请求结果:');
console.log(res);
fnGetAllCategory() {
this.loading = 'loading';
uni.showLoading({
mask: true,
title: '加载中...'
});
this.$httpApi
.getCategoryList({ more: true })
.then(res => {
this.categoryList = res.data;
if (res.data.length != 0) {
this.queryParams.slug = res.data[0].slug;
this.triggered = false;
this.loading = 'success';
this.fnGetData(true, false);
}
})
.catch(err => {
console.error(err);
this.loading = 'error';
})
.finally(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
});
},
fnGetData(isPulldownRefresh = true, triggered = true) {
if (!isPulldownRefresh) {
if (this.result.hasNext) {
this.queryParams.page += 1;
} else {
return uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
} else {
this.queryParams.page = 0;
if (triggered) {
this.triggered = true;
}
}
this.loading = 'success';
this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
// 处理数据
this.hasNext = res.hasNext;
this.$httpApi
.getCategoryPostList(this.queryParams.slug, this.queryParams)
.then(res => {
this.result = res.data;
const tempItems = res.items.map(item => {
item.spec.cover = this.$utils.checkThumbnailUrl(item.spec.cover, true)
return item;
})
if (!isPulldownRefresh) {
this.dataList = this.dataList.concat(res.data.content);
} else {
this.dataList = res.data.content;
}
this.loadMoreText = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
})
.catch(err => {
this.loadMoreText = '加载失败!';
})
.finally(() => {
this.triggered = false;
});
},
//跳转文章详情
fnToArticleDetail(article) {
uni.navigateTo({
url: '/pagesA/article-detail/article-detail?articleId=' + article.id,
animationType: 'slide-in-right'
});
},
fnOnScroll(e) {
throttle(() => {
this.scrollTop = e.detail.scrollTop;
}, 1000);
},
fnToTopScroll() {
uni.pageScrollTo({
scrollTop: 0,
duration: 500
});
this.scrollTop = 0;
}
}
if (this.isLoadMore) {
this.dataList = this.dataList.concat(tempItems);
} else {
this.dataList = tempItems;
}
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
},
handlePreview(index, list) {
uni.previewImage({
current: index,
urls: list.map(item => item.url)
})
},
handleToCategory(data) {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
return;
}
uni.navigateTo({
url: `/pagesA/category-detail/category-detail?name=${data.metadata.name}&title=${data.spec.displayName}`
})
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #fff;
width: 100vw;
display: flex;
flex-direction: column;
padding: 24rpx 0;
}
.content {
height: 100vh;
background-color: #fafafa;
}
.category-empty {
width: 100%;
height: 70vh;
}
.article-empty {
width: 100%;
height: 70vh;
}
.right-content {
// width: calc(100vw - 144rpx);
width: calc(100vw - 190rpx);
height: 100vh;
background-color: #fff;
white-space: nowrap;
.auditModeEnabled {
width: 100%;
height: 80vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-wrap {
padding: 24rpx;
padding: 24rpx;
}
.app-page-content {
display: flex;
flex-wrap: wrap;
padding: 0 12rpx;
gap: 20rpx 0;
}
.catgory-card {
width: 100%;
height: 200rpx;
position: relative;
display: flex;
flex-direction: column;
box-sizing: border-box;
border-radius: 12rpx;
background-color: #ffff;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
overflow: hidden;
background-repeat: no-repeat;
background-size: cover;
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.15);
z-index: 1;
}
}
.content {
width: 100%;
height: 100%;
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.load-text {
width: 100%;
text-align: center;
}
</style>
+270 -291
View File
@@ -1,111 +1,48 @@
<template>
<view class="app-page" :class="{ 'is-balck grey-darken-6': isBlackTheme }">
<!-- 顶部切换 -->
<view class="e-fixed" v-if="category.list.length > 2">
<tm-tabs color="light-blue" v-model="category.activeIndex" :list="category.list" align="left" @change="fnOnCategoryChange"></tm-tabs>
</view>
<!-- 占位区域 -->
<view style="width: 100vw;height: 90rpx;"></view>
<!-- 加载区域 -->
<view v-if="loading != 'success'" class="loading-wrap">
<tm-skeleton model="card"></tm-skeleton>
<tm-skeleton model="cardActions"></tm-skeleton>
<tm-skeleton model="list"></tm-skeleton>
<tm-skeleton model="listAvatr"></tm-skeleton>
<tm-skeleton model="listAvatr"></tm-skeleton>
<tm-skeleton model="listAvatr"></tm-skeleton>
</view>
<!-- 内容区域 -->
<view class="content" v-else>
<view v-if="dataList.length == 0" class="content-empty">
<!-- 空布局 -->
<tm-empty icon="icon-shiliangzhinengduixiang-" label="该分类下暂无数据"></tm-empty>
</view>
<block v-else>
<tm-flowLayout v-if="globalAppSettings.gallery.useWaterfull" @click="fnOnClick" ref="wafll">
<template v-slot:left="{ hdata }">
<tm-translate animation-name="fadeUp">
<view class="round-3 shadow-2 overflow white">
<tm-images :previmage="true" :src="hdata.item.image"></tm-images>
<view class="pa-10 text-size-s">
<view class="text-overflow-2">
<tm-tags color="bg-gradient-amber-accent" :shadow="0" :dense="true" size="s" model="fill">{{ hdata.item.team }}</tm-tags>
<text class="pl-6">{{ hdata.item.name }}</text>
</view>
<view v-if="hdata.item.description" class="ma-10">{{ hdata.item.description }}</view>
<view v-if="hdata.item.location" class="mt-10 text-grey-lighten-1">
<tm-icons name="icon-position-fill"></tm-icons>
<text class="pl-5">{{ hdata.item.location }}</text>
</view>
<view class="flex-between mt-12 flex-center">
<view>
<text class="text-size-xs text-red ml-5">{{ hdata.item.likes }}</text>
<text class="text-size-xs text-red ml-5">喜欢</text>
</view>
<view class="pl-10 text-size-xs text-grey-lighten-1">{{ hdata.item.takeTime }}</view>
</view>
</view>
</view>
</tm-translate>
</template>
<template v-slot:right="{ hdata }">
<tm-translate animation-name="fadeUp">
<view class="round-3 shadow-2 overflow white">
<tm-images :previmage="true" :src="hdata.item.image"></tm-images>
<view class="pa-10 text-size-s">
<view class="text-overflow-2">
<tm-tags :shadow="0" :dense="true" color="bg-gradient-amber-accent" size="s" model="fill">{{ hdata.item.team }}</tm-tags>
<text class="pl-6">{{ hdata.item.name }}</text>
</view>
<view v-if="hdata.item.description" class="ma-10">{{ hdata.item.description }}</view>
<view v-if="hdata.item.location" class="mt-10 text-grey-lighten-1">
<tm-icons name="icon-position-fill"></tm-icons>
<text class="pl-5">{{ hdata.item.location }}</text>
</view>
<view class="flex-between mt-12 flex-center">
<view>
<text class="text-size-xs text-red ml-5">{{ hdata.item.likes }}</text>
<text class="text-size-xs text-red ml-5">喜欢</text>
</view>
<view class="pl-10 text-size-xs text-grey-lighten-1">{{ hdata.item.takeTime }}</view>
</view>
</view>
</view>
</tm-translate>
</template>
</tm-flowLayout>
<block v-else>
<block v-for="(item, index) in dataList" :key="index">
<tm-translate animation-name="fadeUp" :wait="(index + 1) * 50">
<view class="round-3 shadow-2 overflow white mb-24">
<tm-images :previmage="false" :src="item.image" @click="fnPreview(item, index)"></tm-images>
<view class="pa-24 text-size-m">
<view class="text-overflow-2">
<tm-tags :shadow="0" :dense="true" color="bg-gradient-amber-accent" size="s" model="fill">{{ item.team }}</tm-tags>
<text class="pl-6">{{ item.name }}</text>
</view>
<view v-if="item.description" class="ma-10">{{ item.description }}</view>
<view v-if="item.location" class="mt-10 text-grey-lighten-1">
<tm-icons name="icon-position-fill"></tm-icons>
<text class="pl-5">{{ item.location }}</text>
</view>
<view class="flex-between mt-12 flex-center">
<view>
<text class="text-size-m text-red ml-5">{{ item.likes }}</text>
<text class="text-size-m text-red ml-5">喜欢</text>
</view>
<view class="pl-10 text-size-m text-grey-lighten-1">{{ item.takeTime }}</view>
</view>
</view>
</view>
</tm-translate>
</block>
</block>
<tm-flotbutton @click="fnToTopPage" color="light-blue" size="m" icon="icon-angle-up"></tm-flotbutton>
<view class="load-text">{{ loadMoreText }}</view>
</block>
</view>
</view>
<view class="app-page">
<!-- 顶部切换 -->
<view class="e-fixed" v-if="category.list.length > 0">
<tm-tabs color="light-blue" v-model="category.activeIndex" range-key="displayName" :list="category.list"
align="left" @change="fnOnCategoryChange"></tm-tabs>
</view>
<!-- 占位区域 -->
<view v-if="category.list.length > 0" style="width: 100vw;height: 90rpx;"></view>
<!-- 加载区域 -->
<view v-if="loading !== 'success'" 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 class="content" v-else>
<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>
<!-- 列表 -->
<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>
<tm-flotbutton @click="fnToTopPage" color="light-blue" size="m" icon="icon-angle-up"></tm-flotbutton>
<view class="load-text">{{ loadMoreText }}</view>
</block>
</view>
</view>
</template>
<script>
@@ -116,202 +53,244 @@ 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 tmFlowLayout from '@/tm-vuetify/components/tm-flowLayout/tm-flowLayout.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';
export default {
components: {
tmSkeleton,
tmTranslate,
tmFlotbutton,
tmTags,
tmEmpty,
tmIcons,
tmImages,
tmFlowLayout,
tmTabs
},
data() {
return {
isBlackTheme: false,
loading: 'loading',
category: {
activeIndex: 0,
activeValue: '',
list: []
},
queryParams: {
size: 10,
page: 0,
team: null,
keyword: ''
},
cache: {
dataList: [],
total: 0
},
isLoadMore: false,
loadMoreText: '',
result: {},
dataList: []
};
},
filters: {
filterTakeTime(val) {
return uni.$tm.dayjs(val).format('DD/MM/YYYY');
}
},
watch: {
globalAppSettings: {
deep: true,
handler() {
// this.isLoadMore = false;
// this.queryParams.page = 0;
// this.dataList = [];
// this.cache.list = [];
// this.cache.total = 0;
}
}
},
onLoad() {
this.fnSetPageTitle('个人图库');
this.fnGetCategory();
},
created() {
this.fnGetData();
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
},
onReachBottom(e) {
if (this.result.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnOnCategoryChange(index) {
this.dataList = [];
if (index == 0) {
this.queryParams.team = null;
} else {
this.queryParams.team = this.category.list[index];
}
this.queryParams.page = 0;
this.fnToTopPage();
this.fnGetData();
},
fnGetCategory() {
this.$httpApi.getPhotoTeams().then(res => {
this.category.list = ['全部', ...res.data];
});
},
fnGetData() {
uni.showLoading({
mask: true,
title: '加载中...'
});
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '';
this.$httpApi
.getPhotoListByPage(this.queryParams)
.then(res => {
this.loading = 'success';
this.result = res.data;
if (res.data.content.length != 0) {
const _list = res.data.content.map((item, index) => {
item['image'] = this.$utils.checkImageUrl(item.thumbnail);
item['model'] = 'text';
item['takeTime'] = this.$tm.dayjs(item['takeTime']).format('DD/MM/YYYY');
return item;
});
this.fnCacheDataList(_list);
if (this.globalAppSettings.gallery.useWaterfull) {
this.dataList = _list;
this.$nextTick(() => {
this.$refs.wafll.pushData(_list);
});
} else {
this.dataList = this.dataList.concat(_list);
}
}
this.loadMoreText = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.waterfall.loading = 'finish';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
},
// 缓存数据
fnCacheDataList(dataList) {
if (this.queryParams.page == 0) {
this.cache.dataList = dataList;
} else {
this.cache.dataList = [...this.cache.dataList, ...dataList];
}
},
options: {
multipleSlots: true
},
components: {
tmSkeleton,
tmTranslate,
tmFlotbutton,
tmTags,
tmEmpty,
tmIcons,
tmImages,
tmFlowLayoutCustom,
tmTabs
},
data() {
return {
isBlackTheme: false,
loading: 'loading',
category: {
activeIndex: 0,
activeValue: '',
list: []
},
queryParams: {
size: 10,
page: 1,
group: ""
},
isLoadMore: false,
loadMoreText: '',
hasNext: false,
dataList: []
};
},
computed: {
galleryConfig() {
return this.$tm.vx.getters().getConfigs.pageConfig.galleryConfig;
},
haloConfigs() {
return this.$tm.vx.getters().getConfigs;
},
mockJson() {
return this.$tm.vx.getters().getMockJson;
}
},
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.haloConfigs.basicConfig.auditModeEnabled) {
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: {
fnOnCategoryChange(index) {
this.fnResetSetAniWaitIndex();
this.queryParams.group = this.category.list[index].name;
this.queryParams.page = 1;
this.fnToTopPage();
this.dataList = [];
this.fnGetData(true);
},
fnGetCategory() {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
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
}
});
if (this.category.list.length !== 0) {
this.queryParams.group = this.category.list[0].name;
this.fnGetData(true);
}
});
},
fnGetData(isClearWaterfall = false) {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
this.dataList = this.mockJson.gallery.list.map(item => {
return {
metadata: {
name: Date.now() * Math.random(),
},
spec: {
url: this.$utils.checkImageUrl(item)
}
}
})
// 瀑布流组件点击事件
fnOnClick(data) {
console.log('点击数据', data);
},
// 预览
fnPreview(item, index) {
uni.previewImage({
current: index,
urls: this.dataList.map(x => x.image),
indicator: 'number',
loop: true
});
}
}
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();
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();
}, 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
});
}
}
};
</script>
<style lang="scss">
<style lang="scss" scoped>
.app-page {
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
padding-bottom: 24rpx;
background-color: #fafafd;
&.is-balck {
background-color: #212121;
}
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
padding-bottom: 24rpx;
background-color: #fafafa;
}
.content {
padding: 0 24rpx;
padding-top: 24rpx;
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
padding: 24rpx 24rpx 0;
gap: 12rpx 0;
.content-empty {
height: 60vh;
display: flex;
align-items: center;
justify-content: center;
}
.content-empty {
width: 100%;
height: 70vh;
display: flex;
align-items: center;
justify-content: center;
}
}
.loading-wrap {
padding: 24rpx;
box-sizing: border-box;
padding: 24rpx;
}
.load-text {
width: 100%;
text-align: center;
}
</style>
+500 -415
View File
@@ -1,98 +1,87 @@
<template>
<view class="app-page">
<tm-menubars iconColor="white" color="white" :flat="true" :showback="false">
<image slot="left" class="logo ml-24 round-24" :src="bloggerInfo.avatar" mode="scaleToFill" @click="$tm.toast('以后会放一个彩蛋~')"></image>
<view class="search-input round-12 pt-12 pb-12 flex pl-24" @click="fnToSearch">
<text class="search-input_icon iconfont text-size-m icon-search text-grey"></text>
<view class="search-input_text pl-12 text-size-m text-grey">搜索文章...</view>
</view>
<!-- #ifdef APP-PLUS || H5 -->
<view slot="right" class="mr-24 text-size-m text-grey">uni-halo</view>
<!-- #endif -->
</tm-menubars>
<view v-if="loading != 'success'" class="loading-wrap">
<tm-skeleton model="card"></tm-skeleton>
<tm-skeleton model="cardActions"></tm-skeleton>
<tm-skeleton model="list"></tm-skeleton>
<tm-skeleton model="listAvatr"></tm-skeleton>
<tm-skeleton model="listAvatr"></tm-skeleton>
<tm-skeleton model="listAvatr"></tm-skeleton>
</view>
<block v-else>
<view class="bg-white pb-24">
<!-- 轮播图+广告 -->
<view class="banner bg-white ml-24 mr-24 mt-12 round-3" v-if="bannerList.length != 0">
<e-swiper
:dotPosition="globalAppSettings.banner.dotPosition"
:autoplay="true"
:useDot="globalAppSettings.banner.useDot"
:list="bannerList"
@on-click="fnOnBannerClick"
></e-swiper>
</view>
<!-- 快捷导航 -->
<!-- <view v-if="useQuickNav" class="quick-nav flex-between round-3 flex mt-12 ml-12 mr-12 pa-24">
<view class="quick-nav-item flex flex-col flex-center" v-for="(nav, index) in quickNavList" :key="index" @click="fnToNavPage(nav)">
<tm-icons :size="120" :name="nav.icon"></tm-icons>
<view class="name text-size-s mt-4">{{ nav.text }}</view>
</view>
</view> -->
</view>
<!-- 精品分类 -->
<view class="flex flex-between mt-16 mb-24 pl-24 pr-24">
<view class="page-item_title text-weight-b ">精品分类</view>
<view class="show-more flex flex-center bg-white round-3" @click="fnToCategoryPage">
<text class="iconfont icon-angle-right text-size-s text-grey-darken-1"></text>
</view>
<view v-if="false" class="flex flex-center text-size-s text-grey-darken-1" @click="fnToCategoryPage">
<text class=" text-size-m">查看更多</text>
<text class="iconfont icon-angle-right text-size-s "></text>
</view>
</view>
<scroll-view class="category" scroll-x="true">
<view v-if="categoryList.length == 0" class="cate-empty round-3 mr-5 flex flex-center text-grey">还没有任何文章分类~</view>
<block v-else>
<view class="content" v-for="(category, index) in categoryList" :key="category.createTime" @click="fnToCategoryBy(category)">
<category-mini-card :category="category"></category-mini-card>
</view>
</block>
</scroll-view>
<view class="app-page">
<tm-menubars iconColor="white" color="white" :flat="true" :showback="false">
<view slot="left">
<image @click="fnOnLogoToPage" class="logo ml-24 round-24" :src="appInfo.logo" mode="scaleToFill"/>
</view>
<view class="search-input round-12 pt-12 pb-12 flex pl-24" @click="fnToSearch">
<text class="search-input_icon iconfont text-size-m icon-search text-grey"></text>
<view class="search-input_text pl-12 text-size-m text-grey">搜索内容...</view>
</view>
<!-- #ifdef APP-PLUS || H5 -->
<view slot="right" class="mr-24 text-size-m text-grey text-overflow">{{ appInfo.name }}</view>
<!-- #endif -->
</tm-menubars>
<view v-if="loading !== 'success' && articleList.length===0" class="loading-wrap">
<tm-skeleton model="card"></tm-skeleton>
<tm-skeleton model="cardActions"></tm-skeleton>
<tm-skeleton model="list"></tm-skeleton>
<tm-skeleton model="listAvatr"></tm-skeleton>
<tm-skeleton model="listAvatr"></tm-skeleton>
<tm-skeleton model="listAvatr"></tm-skeleton>
</view>
<block v-else>
<view class="bg-white pb-24">
<view class="banner bg-white ml-24 mr-24 mt-12 round-3" v-if="bannerList.length !== 0">
<e-swiper :dotPosition="globalAppSettings.banner.dotPosition" :autoplay="true"
:useDot="globalAppSettings.banner.useDot" :list="bannerList"
@on-click="fnOnBannerClick"></e-swiper>
</view>
</view>
<!-- 精品分类 -->
<block v-if="calcIsShowCategory">
<view class="flex flex-between mt-16 mb-24 pl-24 pr-24">
<view class="page-item_title text-weight-b ">精品分类</view>
<view class="show-more flex flex-center bg-white round-3" @click="fnToCategoryPage">
<text class="iconfont icon-angle-right text-size-s text-grey-darken-1"></text>
</view>
<view v-if="false" class="flex flex-center text-size-s text-grey-darken-1"
@click="fnToCategoryPage">
<text class=" text-size-m">查看更多</text>
<text class="iconfont icon-angle-right text-size-s "></text>
</view>
</view>
<scroll-view class="category" scroll-x="true">
<view v-if="categoryList.length === 0" class="cate-empty round-3 mr-5 flex flex-center text-grey">
还没有任何分类~
</view>
<block v-else>
<view class="content" v-for="(category, index) in categoryList" :key="category.metadata.name"
@click="fnToCategoryBy(category)">
<category-mini-card :category="category"></category-mini-card>
</view>
</block>
</scroll-view>
</block>
<!-- 最新文章 -->
<view class="flex flex-between mt-24 mb-24 pl-24 pr-24">
<view class="page-item_title text-weight-b">最新文章</view>
<view class="show-more flex flex-center bg-white round-3" @click="fnToArticlesPage">
<text class="iconfont icon-angle-right text-size-s text-grey-darken-1"></text>
</view>
<view v-if="false" class="flex flex-center text-size-s text-grey-darken-1" @click="fnToArticlesPage">
<text class=" text-size-m ">查看更多</text>
<text class="iconfont icon-angle-right text-size-s "></text>
</view>
</view>
<view v-if="articleList.length == 0" class="article-empty"><tm-empty icon="icon-shiliangzhinengduixiang-" label="博主还没有发表任何文章~"></tm-empty></view>
<block v-else>
<view :class="globalAppSettings.layout.home">
<block v-for="(article, index) in articleList" :key="article.createTime">
<tm-translate class="ani-item" animation-name="fadeUp" :wait="(index + 1) * 50">
<article-card from="home" :article="article" @on-click="fnToArticleDetail"></article-card>
<!-- 广告区域 -->
<view v-if="haloAdConfig.home.use && (index + 1) % haloAdConfig.frequency == 0" class="ad-wrap ma-24">
<!-- #ifdef MP-WEIXIN -->
<ad v-if="haloAdConfig.unitId" :unit-id="haloAdConfig.unitId"></ad>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<ad v-if="haloAdConfig.adpid" :adpid="haloAdConfig.adpid"></ad>
<!-- #endif -->
</view>
</tm-translate>
</block>
</view>
<view class="load-text mt-12">{{ loadMoreText }}</view>
<tm-flotbutton v-if="articleList.length > 10" color="light-blue" @click="fnToTopPage" size="m" icon="icon-angle-up"></tm-flotbutton>
</block>
</block>
<!-- <tm-flotbutton @click="fnChangeMode" size="m" color="light-blue" :icon="$tm.vx.state().tmVuetify.black ? 'icon-lightbulb' : 'icon-lightbulb-fill'"></tm-flotbutton> -->
</view>
<!-- 最新文章 -->
<view class="flex flex-between mt-24 mb-24 pl-24 pr-24">
<view class="page-item_title text-weight-b">最新列表</view>
<view class="show-more flex flex-center bg-white round-3" @click="fnToArticlesPage">
<text class="iconfont icon-angle-right text-size-s text-grey-darken-1"></text>
</view>
<view v-if="false" class="flex flex-center text-size-s text-grey-darken-1" @click="fnToArticlesPage">
<text class=" text-size-m ">查看更多</text>
<text class="iconfont icon-angle-right text-size-s "></text>
</view>
</view>
<view v-if="articleList.length === 0" class="article-empty">
<tm-empty icon="icon-shiliangzhinengduixiang-" label="博主还没有发表任何内容~"></tm-empty>
</view>
<block v-else>
<view :class="globalAppSettings.layout.home">
<tm-translate v-for="(article, index) in articleList" :key="index" class="ani-item"
animation-name="fadeUp" :wait="calcAniWait(index)">
<article-card from="home" :article="article" :post="article"
@on-click="fnToArticleDetail"></article-card>
</tm-translate>
</view>
<view class="load-text mt-12">{{ loadMoreText }}</view>
<tm-flotbutton v-if="articleList.length > 10" color="light-blue" @click="fnToTopPage" size="m"
icon="icon-angle-up"></tm-flotbutton>
</block>
</block>
</view>
</template>
<script>
@@ -104,361 +93,457 @@ import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import eSwiper from '@/components/e-swiper/e-swiper.vue';
import qs from 'qs'
export default {
components: {
tmMenubars,
tmSkeleton,
tmTranslate,
tmFlotbutton,
tmIcons,
tmEmpty,
eSwiper
},
data() {
return {
loading: 'loading',
queryParams: {
size: 10,
page: 0
},
result: {},
isLoadMore: false,
loadMoreText: '加载中...',
bannerCurrent: 0,
bannerList: [],
noticeList: [],
articleList: [],
categoryList: [],
useQuickNav: false,
quickNavList: []
};
},
components: {
tmMenubars,
tmSkeleton,
tmTranslate,
tmFlotbutton,
tmIcons,
tmEmpty,
eSwiper
},
data() {
return {
loading: 'loading',
queryParams: {
size: 5,
page: 1,
sort: ['spec.pinned,desc', 'spec.publishTime,desc']
},
result: {},
isLoadMore: false,
loadMoreText: '加载中...',
bannerCurrent: 0,
bannerList: [],
noticeList: [],
articleList: [],
categoryList: [],
};
},
computed: {
haloConfigs() {
return this.$tm.vx.getters().getConfigs;
},
bloggerInfo() {
const blogger = this.$tm.vx.getters().getConfigs.authorConfig.blogger;
blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
return blogger;
},
appInfo() {
const appInfo = this.haloConfigs.appConfig.appInfo;
appInfo.logo = this.$utils.checkImageUrl(appInfo.logo)
return appInfo;
},
mockJson() {
return this.$tm.vx.getters().getMockJson;
},
calcIsShowCategory() {
if (this.haloConfigs.basicConfig.auditModeEnabled && this.categoryList.length !== 0) {
return false
}
if (this.haloConfigs.basicConfig.auditModeEnabled) {
return false
}
return this.haloConfigs.pageConfig.homeConfig.useCategory
}
},
onLoad() {
this.fnSetPageTitle();
},
created() {
this.fnQuery();
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 1;
this.fnQuery();
},
onReachBottom(e) {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
return
}
if (this.result.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetArticleList();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnQuery() {
console.log('this.mockJson', this.mockJson)
this.fnGetBanner();
this.fnGetArticleList();
this.fnGetCategoryList();
},
fnGetCategoryList() {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
this.categoryList = this.mockJson.home.categoryList.map((item) => {
return {
metadata: {
name: Date.now() * Math.random(),
},
spec: {
displayName: item.title,
cover: item.cover
},
postCount: 0
}
});
return;
}
computed: {
// 获取博主信息
bloggerInfo() {
let blogger = this.$tm.vx.getters().blogger.getBlogger;
blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
return blogger;
}
},
onLoad() {
this.fnSetPageTitle();
this.useQuickNav = this.$haloConfig.quickNav.use;
},
if (!this.calcIsShowCategory) {
return;
}
created() {
this.fnQuery();
this.$httpApi
.getOptionList()
.then(result => {
console.log('配置列表:');
console.log(result);
})
.catch(err => {});
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnQuery();
},
this.$httpApi.v2
.getCategoryList({})
.then(res => {
this.categoryList = res.items.sort((a, b) => {
return b.postCount - a.postCount;
});
onReachBottom(e) {
if (this.result.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetArticleList();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
// 查询数据
fnQuery() {
this.fnGetBanner();
this.fnGetArticleList();
this.fnGetCategoryList();
this.fnGetQuickNavList();
},
fnGetQuickNavList() {
this.useQuickNav = this.$haloConfig.quickNav.use;
if (!this.$haloConfig.quickNav.use) return;
const _quickNavList = this.$haloConfig.quickNav.list;
this.quickNavList = _quickNavList.map(item => {
return item;
});
},
fnGetCategoryList() {
this.$httpApi
.getCategoryList({ more: true })
.then(res => {
this.categoryList = res.data;
setTimeout(() => {
this.loading = 'success';
}, 500);
})
.catch(err => {
console.error(err);
this.loading = 'error';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
},
// 获取轮播图
fnGetBanner() {
const _this = this;
const _format = function(list, type) {
return list.map(item => {
switch (type) {
case 'list':
return {
id: '',
src: item
};
case 'article':
return {
mp4: '',
id: item.id,
nickname: _this.bloggerInfo.nickname,
avatar: _this.bloggerInfo.avatar,
address: '',
createTime: uni.$tm.dayjs(item.createTime).fromNow(),
title: item.title,
src: _this.$utils.checkImageUrl(item.thumbnail),
image: _this.$utils.checkImageUrl(item.thumbnail)
};
case 'banner':
return {
id: '',
src: item
};
}
});
};
switch (this.$haloConfig.banner.type) {
case 'list': // 手动配置的banner
this.bannerList = _format(this.$haloConfig.banner.list, 'list');
break;
case 'article': // 来自热门文章的封面
this.$httpApi.getPostList({ page: 0, size: 6, sort: 'topPriority,visits,desc' }).then(res => {
this.bannerList = _format(res.data.content, 'article');
if (this.bannerList.length == 0) {
this.bannerList = _format(this.$haloConfig.banner.list, 'list');
}
});
break;
case 'banner': // 来自后台配置的banner(暂未开放)
this.bannerList = _format([], 'banner');
break;
}
},
fnOnBannerChange(e) {
this.bannerCurrent = e.current;
},
fnOnBannerClick(item) {
if (item.id == '') return;
this.fnToArticleDetail(item);
},
// 文章列表
fnGetArticleList() {
uni.showLoading({
mask: true,
title: '加载中...'
});
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi
.getPostList(this.queryParams)
.then(res => {
this.loading = 'success';
this.loadMoreText = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
this.result = res.data;
if (this.isLoadMore) {
this.articleList = this.articleList.concat(res.data.content);
} else {
this.articleList = res.data.content;
}
this.loading = 'success';
})
.catch(err => {
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
uni.$tm.toast(err.message || '数据加载失败!');
})
.finally(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
});
},
setTimeout(() => {
this.loading = 'success';
}, 500);
})
.catch(err => {
console.error(err);
this.loading = 'error';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
},
// 获取轮播图
fnGetBanner() {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
this.bannerList = this.mockJson.home.bannerList.map((item) => {
return {
mp4: '',
id: Date.now() * Math.random(),
nickname: this.haloConfigs.authorConfig.blogger.nickname,
avatar: this.$utils.checkAvatarUrl(this.haloConfigs.authorConfig.blogger.avatar),
address: '',
createTime: item.time,
title: item.title,
src: this.$utils.checkThumbnailUrl(item.cover),
image: this.$utils.checkThumbnailUrl(item.cover)
}
});
return;
}
const _this = this;
const _format = function (list) {
return list.map((item, index) => {
return {
mp4: '',
id: item.metadata.name,
nickname: item.owner.displayName,
avatar: _this.$utils.checkAvatarUrl(item.owner.avatar),
address: '',
createTime: uni.$tm.dayjs(item.spec.publishTime).fromNow(),
title: item.spec.title,
src: _this.$utils.checkThumbnailUrl(item.spec.cover),
image: _this.$utils.checkThumbnailUrl(item.spec.cover)
};
});
};
//跳转文章详情
fnToArticleDetail(article) {
uni.navigateTo({
url: '/pagesA/article-detail/article-detail?articleId=' + article.id,
animationType: 'slide-in-right'
});
},
// 快捷导航页面跳转
fnToNavPage(item) {
switch (item.type) {
case 'tabbar':
uni.switchTab({
url: item.path
});
break;
case 'page':
uni.navigateTo({
url: item.path
});
break;
}
},
// 分类页面
fnToCategoryPage() {
uni.switchTab({
url: '/pages/tabbar/category/category'
});
},
// 所有的文章列表页面
fnToArticlesPage() {
uni.navigateTo({
url: '/pagesA/articles/articles'
});
},
const paramsStr = qs.stringify(this.queryParams, {
allowDots: true,
encodeValuesOnly: true,
skipNulls: true,
encode: true,
arrayFormat: 'repeat'
})
uni.request({
url: this.$baseApiUrl + '/apis/api.content.halo.run/v1alpha1/posts?' + paramsStr,
method: 'GET',
success: (res) => {
this.bannerList = _format(res.data.items);
},
fail: (err) => {
}
})
// 根据slug查询分类下的文章
fnToCategoryBy(category) {
uni.navigateTo({
url: `/pagesA/category-detail/category-detail?slug=${category.slug}&name=${category.name}`
});
},
},
fnOnBannerChange(e) {
this.bannerCurrent = e.current;
},
fnOnBannerClick(item) {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
return;
}
if (item.id === '') return;
this.fnToArticleDetail({
metadata: {
name: item.id
}
});
},
// 文章列表
fnGetArticleList() {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
this.articleList = this.mockJson.home.postList.map((item) => {
return {
metadata: {
name: Date.now() * Math.random(),
},
spec: {
pinned: false,
cover: item.cover,
title: item.title,
publishTime: item.time
},
status: {
excerpt: item.desc
},
stats: {
visit: 0
}
}
});
this.loading = 'success';
this.loadMoreText = '呜呜,没有更多数据啦~';
uni.hideLoading();
uni.stopPullDownRefresh();
return;
}
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
fnChangeMode() {
const isBlackTheme = this.$tm.vx.state().tmVuetify.black;
this.$tm.theme.setBlack(!isBlackTheme);
uni.setNavigationBarColor({
backgroundColor: !isBlackTheme ? '#0a0a0a' : '#ffffff',
frontColor: !isBlackTheme ? '#ffffff' : '#0a0a0a'
});
},
const paramsStr = qs.stringify(this.queryParams, {
allowDots: true,
encodeValuesOnly: true,
skipNulls: true,
encode: true,
arrayFormat: 'repeat'
})
uni.request({
url: this.$baseApiUrl + '/apis/api.content.halo.run/v1alpha1/posts?' + paramsStr,
method: 'GET',
success: (res) => {
const data = res.data;
this.result.hasNext = data.hasNext;
if (this.isLoadMore) {
this.articleList = this.articleList.concat(data.items);
} else {
this.articleList = data.items;
}
this.loading = 'success';
this.loadMoreText = data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
uni.hideLoading();
uni.stopPullDownRefresh();
},
fail: (err) => {
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
uni.$tm.toast(err.message || '数据加载失败!');
uni.stopPullDownRefresh();
}
})
},
//跳转文章详情
fnToArticleDetail(article) {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
return;
}
uni.navigateTo({
url: '/pagesA/article-detail/article-detail?name=' + article.metadata.name,
animationType: 'slide-in-right'
});
},
// 快捷导航页面跳转
fnToNavPage(item) {
switch (item.type) {
case 'tabbar':
uni.switchTab({
url: item.path
});
break;
case 'page':
uni.navigateTo({
url: item.path
});
break;
}
},
// 分类页面
fnToCategoryPage() {
uni.switchTab({
url: '/pages/tabbar/category/category'
});
},
// 所有的文章列表页面
fnToArticlesPage() {
uni.navigateTo({
url: '/pagesA/articles/articles'
});
},
fnToSearch() {
uni.navigateTo({
url: '/pagesA/articles/articles'
});
}
}
// 根据slug查询分类下的文章
fnToCategoryBy(category) {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
return;
}
uni.navigateTo({
url: `/pagesA/category-detail/category-detail?name=${category.metadata.name}&title=${category.spec.displayName}`
});
},
fnChangeMode() {
const isBlackTheme = this.$tm.vx.state().tmVuetify.black;
this.$tm.theme.setBlack(!isBlackTheme);
uni.setNavigationBarColor({
backgroundColor: !isBlackTheme ? '#0a0a0a' : '#ffffff',
frontColor: !isBlackTheme ? '#ffffff' : '#0a0a0a'
});
},
fnToSearch() {
uni.navigateTo({
url: '/pagesA/articles/articles'
});
},
fnOnLogoToPage() {
uni.switchTab({
url: '/pages/tabbar/about/about'
})
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
// background-color: #ffffff;
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
// background-color: #ffffff;
.logo {
width: 60rpx;
height: 60rpx;
box-sizing: border-box;
}
.logo {
width: 60rpx;
height: 60rpx;
box-sizing: border-box;
}
::v-deep {
.tm-menubars .body .body_wk .left {
min-width: initial;
}
}
::v-deep {
.tm-menubars .body .body_wk .left {
min-width: initial;
}
}
}
.loading-wrap {
padding: 24rpx;
padding: 24rpx;
}
.search-input {
background-color: #f5f5f5;
align-items: center;
/* #ifdef MP-WEIXIN */
margin-right: 24rpx;
/* #endif */
&_icon {
}
&_text {
}
background-color: #f5f5f5;
align-items: center;
/* #ifdef MP-WEIXIN */
margin-right: 24rpx;
/* #endif */
&_icon {
}
&_text {
}
}
.show-more {
width: 42rpx;
height: 42rpx;
box-sizing: border-box;
box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.03);
width: 42rpx;
height: 42rpx;
box-sizing: border-box;
box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.03);
}
.banner {
overflow: hidden;
overflow: hidden;
}
.quick-nav {
background-color: #fff;
box-sizing: border-box;
// box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
.name {
color: var(--main-text-color);
}
background-color: #fff;
box-sizing: border-box;
// box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
.name {
color: var(--main-text-color);
}
}
.category {
width: 94vw;
display: flex;
height: 200rpx;
white-space: nowrap;
margin: 0 24rpx;
.content {
display: inline-block;
padding-left: 24rpx;
&:first-child {
padding-left: 0;
}
}
.cate-empty {
height: inherit;
}
width: 94vw;
display: flex;
height: 200rpx;
white-space: nowrap;
margin: 0 24rpx;
.content {
display: inline-block;
padding-left: 24rpx;
&:first-child {
padding-left: 0;
}
}
.cate-empty {
height: inherit;
}
}
.page-item {
&_title {
position: relative;
padding-left: 24rpx;
font-size: 32rpx;
z-index: 1;
color: var(--main-text-color);
&:before {
content: '';
position: absolute;
left: 0rpx;
top: 8rpx;
width: 8rpx;
height: 30rpx;
background-color: rgba(33, 150, 243, 1);
border-radius: 6rpx;
z-index: 0;
}
}
&_title {
position: relative;
padding-left: 24rpx;
font-size: 32rpx;
z-index: 1;
color: var(--main-text-color);
&:before {
content: '';
position: absolute;
left: 0rpx;
top: 8rpx;
width: 8rpx;
height: 30rpx;
background-color: rgba(33, 150, 243, 1);
border-radius: 6rpx;
z-index: 0;
}
}
}
.h_row_col2 {
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
padding: 0 12rpx;
.ani-item {
width: 50%;
}
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
padding: 0 12rpx;
.ani-item {
width: 50%;
}
}
</style>
-335
View File
@@ -1,335 +0,0 @@
<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>
<block v-else>
<!-- 空数据 -->
<view v-if="result.length == 0" class="empty flex flex-center"><tm-empty icon="icon-shiliangzhinengduixiang-" label="啊偶,博主还没有朋友呢~"></tm-empty></view>
<!-- 如果只有一个分组使用列表的形式 result.length == 1 -->
<view v-else-if="result.length == 1" class="flex flex-col pb-24">
<block v-for="(item, index) in result[0].children" :key="index">
<tm-translate animation-name="fadeUp" :wait="(index + 1) * 50">
<view class="link-card one round-a-6 flex ma-24 mb-0 pa-24" @click="fnOnLinkEvent(item)">
<cache-image class="logo shadow-2" radius="12rpx" width="80rpx" height="80rpx" :url="item.logo" :fileMd5="item.logo" mode="aspectFill"></cache-image>
<view class="info pl-24">
<view class="name text-size-g">{{ item.name }}</view>
<view class="desc mt-12 text-size-s text-grey-darken-1">{{ item.description }}</view>
<view class="link mt-12 text-size-m text-grey-darken-1">
<text class="iconfont icon-link mr-6 text-size-s"></text>
{{ item.url }}
</view>
</view>
</view>
</tm-translate>
</block>
<!-- 返回顶部 -->
<tm-flotbutton @click="fnToTopPage" size="m" icon="icon-angle-up"></tm-flotbutton>
</view>
<!-- 如果大于一个分组使用联系人的索引形式 result.length > 1 -->
<block v-else-if="result.length > 1">
<tm-quickIndex :list="result">
<template #cell="{ data }">
<!-- 艳丽版本 -->
<view v-if="!globalAppSettings.links.useSimple" class="info flex pa-36 border-b-1" @click="fnOnLinkEvent(data.item)">
<view class="link-logo">
<cache-image class="link-logo_img" radius="12rpx" :url="data.item.logo" :fileMd5="data.item.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">{{ data.item.name }}</view>
<view class="poup-tag ml--10 mt-6">
<tm-tags color="bg-gradient-amber-accent" :shadow="0" size="s" model="fill">ID{{ data.item.id }}</tm-tags>
<tm-tags color=" bg-gradient-light-blue-lighten" :shadow="0" size="s" model="fill">{{ data.item.team }}</tm-tags>
</view>
<view class="link-card_desc text-overflow text-size-s mt-4">博客简介{{ data.item.description || '这个博主很懒,没写简介~' }}</view>
</view>
</view>
<!-- 简洁版本 -->
<view v-else class="link-card flex ml-24 mr-24 pt-24 pb-24" @click="fnOnLinkEvent(data.item)">
<image class="logo shadow-6" :src="data.item.logo" mode="aspectFill"></image>
<view class="info pl-24">
<view class="name text-size-g">{{ data.item.name }}</view>
<view class="desc mt-12 text-size-s text-grey-darken-1">{{ data.item.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>
{{ data.item.url }}
</view>
</view>
</view>
</template>
</tm-quickIndex>
</block>
<!-- 详情弹窗 -->
<tm-poup v-model="detail.show" :width="640" height="auto" position="center" :round="6">
<view class="poup pa-36">
<view class="info flex">
<view class="poup-logo bg-gradient-amber-accent pa-4 shadow-24"><image class="poup-logo_img" :src="detail.data.logo" mode="aspectFill"></image></view>
<view class="pl-24 info-detail">
<view class="poup-name text-size-lg text-weight-b">{{ detail.data.name }}</view>
<view class="poup-tag ml--10">
<tm-tags color="bg-gradient-amber-accent" size="n" model="fill">ID{{ detail.data.id }}</tm-tags>
<tm-tags color="bg-gradient-light-blue-lighten" size="n" model="fill">{{ detail.data.team }}</tm-tags>
</view>
<view class="poup-link text-size-m" @click="fnCopyLink(detail.data)">
<text class="text-orange">{{ detail.data.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.description || '这个博主很懒,没写简介~' }}</view>
<!-- 博客预览图 -->
<view class="mt-24"><tm-images :width="568" :round="2" :src="caclSiteThumbnail(detail.data.url)" mode="aspectFill"></tm-images></view>
</view>
</tm-poup>
</block>
</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 tmQuickIndex from '@/tm-vuetify/components/tm-quickIndex/tm-quickIndex.vue';
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
import { GetRandomNumberByRange } from '@/utils/random.js';
import findCnChart from '@/tm-vuetify/tool/function/findCnChart.js';
export default {
components: {
tmSkeleton,
tmTranslate,
tmFlotbutton,
tmTags,
tmEmpty,
tmImages,
tmQuickIndex,
tmPoup
},
data() {
return {
loading: 'loading',
queryParams: {
size: 10,
page: 0,
sort: ''
},
result: [],
detail: {
show: false,
data: {}
}
};
},
computed: {
caclSiteThumbnail(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;
};
}
},
onLoad() {
this.fnSetPageTitle('朋友圈');
},
created() {
this.fnGetData();
},
onPullDownRefresh() {
this.fnGetData();
},
methods: {
fnRandomColor() {
const _r = GetRandomNumberByRange(0, this.$haloConfig.colors.length - 1);
return this.$haloConfig.colors[_r];
},
fnGetData() {
this.loading = 'loading';
uni.showLoading({
mask: true,
title: '加载中...'
});
this.$httpApi
.getLinkListByTeam()
.then(res => {
if (res.status == 200) {
console.log('请求结果:');
console.log(res);
let resData = res.data;
// 处理数据
const _result = resData.map(item => {
const _team = item.team || '未分组';
const _firstChart = _team ? _team.substring(0, 1) : '';
const _links = item.links.map(link => {
link.logo = this.$utils.checkAvatarUrl(link.logo);
return link;
});
return {
title: _team,
index: findCnChart(_firstChart),
children: _links
};
});
this.result = _result.reverse();
setTimeout(() => {
this.loading = 'success';
}, 500);
} else {
setTimeout(() => {
this.loading = 'error';
}, 500);
}
})
.catch(err => {
console.error(err);
this.loading = 'error';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
},
fnOnLinkEvent(link) {
this.detail.data = link;
this.detail.show = true;
},
fnCopyLink(link) {
uni.setClipboardData({
data: `${link.name}:${link.url}`,
showToast: false,
success: () => {
uni.showToast({
icon: 'none',
title: '链接复制成功!'
});
},
fail: () => {
uni.showToast({
icon: 'none',
title: '复制失败!'
});
}
});
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.loading-wrap {
padding: 24rpx;
min-height: 100vh;
}
.empty {
width: 100vw;
height: 60vh;
}
.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: 50%;
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>
+293
View File
@@ -0,0 +1,293 @@
<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>
</view>
<block v-else>
<block v-for="(moment, index) in dataList" :key="index">
<!-- 卡片 -->
<tm-translate animation-name="fadeUp" :wait="calcAniWait()">
<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>
<view style="margin-top: 6rpx;font-size: 24rpx;color: #666;">
{{ {d: moment.spec.releaseTime, f: 'yyyy年MM月dd日 星期w'} | formatTime }}
</view>
</view>
</view>
<view class="content">
<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.content.html" :markdown="true" :showLineNumber="true"
:showLanguageName="true" :copyByLongPress="true"/>
</view>
<view class="mb-12 mt--12" v-if="moment.videos.length!==0"
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"></video>
</view>
<view v-if="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>
</tm-translate>
</block>
<tm-flotbutton @click="fnToTopPage" size="m" color="light-blue" icon="icon-angle-up"></tm-flotbutton>
<view class="load-text">{{ loadMoreText }}</view>
</block>
</view>
</view>
</template>
<script>
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import MarkdownConfig from '@/common/markdown/markdown.config.js';
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
export default {
components: {
tmSkeleton,
tmFlotbutton,
tmTranslate,
tmEmpty,
mpHtml
},
data() {
return {
markdownConfig: MarkdownConfig,
loading: 'loading',
queryParams: {
size: 10,
page: 1
},
hasNext: false,
dataList: [],
isLoadMore: false,
loadMoreText: '加载中...'
};
},
computed: {
bloggerInfo() {
let blogger = this.$tm.vx.getters().getConfigs.authorConfig.blogger;
blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
return blogger;
},
haloConfigs() {
return this.$tm.vx.getters().getConfigs;
},
mockJson() {
return this.$tm.vx.getters().getMockJson;
}
},
onLoad() {
this.fnGetData();
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
},
onReachBottom(e) {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
return
}
if (this.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnGetData() {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
this.dataList = this.mockJson.moments.list.map((item) => {
return {
metadata: {
name: Date.now() * Math.random(),
},
spec: {
user: {
displayName: this.bloggerInfo.nickname,
avatar: this.$utils.checkAvatarUrl(this.bloggerInfo.avatar),
},
content: {
html: item.content
},
releaseTime: item.time
},
images: item.images.map((img) => {
return {
type: "PHOTO",
url: this.$utils.checkThumbnailUrl(img),
}
}),
videos: []
}
});
this.loading = 'success';
this.loadMoreText = '呜呜,没有更多数据啦~';
uni.hideLoading();
uni.stopPullDownRefresh();
return;
}
uni.showLoading({
mask: true,
title: '加载中...'
});
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi.v2
.getMomentList(this.queryParams)
.then(res => {
this.loading = 'success';
this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
this.hasNext = res.hasNext;
const tempItems = res.items.map(item => {
item.spec.user = {
displayName: this.bloggerInfo.nickname,
avatar: this.$utils.checkAvatarUrl(this.bloggerInfo.avatar)
}
item.spec.content.medium.map(medium => {
medium.url = this.$utils.checkThumbnailUrl(medium.url, true)
})
item['images'] = item.spec.content.medium
.filter(x => x.type === 'PHOTO')
item['videos'] = item.spec.content.medium
.filter(x => x.type === 'VIDEO')
return item;
})
if (this.isLoadMore) {
this.dataList = this.dataList.concat(tempItems);
} else {
this.dataList = tempItems;
}
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
},
handlePreview(index, list) {
uni.previewImage({
current: index,
urls: list.map(item => item.url)
})
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
display: flex;
flex-direction: column;
padding: 24rpx 0;
}
.loading-wrap {
padding: 24rpx;
}
.moment-card {
display: flex;
flex-direction: column;
box-sizing: border-box;
margin: 0 24rpx;
border-radius: 12rpx;
background-color: #ffff;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
overflow: hidden;
margin-bottom: 24rpx;
}
.head {
padding: 24rpx;
padding-bottom: 0;
}
.images {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
padding: 24rpx;
padding-top: 0;
.image-item {
box-sizing: border-box;
border-radius: 24rpx;
padding: 6rpx;
width: 33%;
height: 200rpx
}
&-1 {
> .image-item {
width: 100%;
height: 350rpx
}
}
&-2 {
> .image-item {
width: 50%;
height: 250rpx
}
}
}
</style>
-134
View File
@@ -1,134 +0,0 @@
<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">
<!-- 空布局 -->
<tm-empty icon="icon-shiliangzhinengduixiang-" label="该分类下暂无数据"></tm-empty>
</view>
<block v-else>
<block v-for="(item, index) in dataList" :key="index">
<!-- 卡片 -->
<tm-translate animation-name="fadeUp" :wait="(index + 1) * 50">
<!-- 列表项 -->
{{ item }}
</tm-translate>
</block>
<tm-flotbutton @click="fnToTopPage" size="m" color="light-blue" icon="icon-angle-up"></tm-flotbutton>
<view class="load-text">{{ loadMoreText }}</view>
</block>
</view>
</view>
</template>
<script>
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
export default {
components: {
tmSkeleton,
tmFlotbutton,
tmTranslate,
tmEmpty
},
data() {
return {
loading: 'loading',
queryParams: {
size: 10,
page: 0
},
result: null,
dataList: [],
isLoadMore: false,
loadMoreText: '加载中...'
};
},
onLoad() {
this.fnSetPageTitle();
},
created() {
this.fnGetData();
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
},
onReachBottom(e) {
if (this.result.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnGetData() {
return;
uni.showLoading({
mask: true,
title: '加载中...'
});
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi
.getXXX(this.queryParams)
.then(res => {
console.log('请求结果:');
console.log(res);
this.loading = 'success';
this.loadMoreText = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
// 处理数据
this.result = res.data;
if (this.isLoadMore) {
this.dataList = this.dataList.concat(res.data.content);
} else {
this.dataList = res.data.content;
}
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
display: flex;
flex-direction: column;
padding: 24rpx 0;
padding-bottom: 144rpx;
}
.loading-wrap {
padding: 24rpx;
}
</style>
+80 -75
View File
@@ -1,94 +1,99 @@
<template>
<view class="app-page bg-white flex flex-col">
<view v-if="uni_halo_logo" class="logo mt-40 pt-40"><image class="logo-img round-3" :src="uni_halo_logo" mode="aspectFill"></image></view>
<view class="mt-12 text-weight-b text-size-lg" :class="{ 'mt-40 pt-40': !uni_halo_logo }">uni-halo</view>
<view class="mt-12 text-size-s text-grey-darken-3"></view>
<view class="list-group mt-48" style="width: 100%;">
<tm-grouplist :shadow="0" :margin="0" :borderBottom="true">
<tm-listitem title="开源组织" :font-size="28" @click="$utils.copyText('https://www.ialley.cn', '巷子工坊官网已复制成功!')">
<text class="text-size-s" slot="rightValue">巷子工坊</text>
</tm-listitem>
<tm-listitem title="开源作者" :font-size="28" @click="$utils.copyText('https://b.925i.cn', '作者博客地址已复制')">
<text class="text-size-s" slot="rightValue">小莫唐尼</text>
</tm-listitem>
<tm-listitem title="作者博客" :font-size="28" @click="$utils.copyText('https://b.925i.cn', '作者博客地址已复制')">
<text class="text-size-s text-overflow" slot="rightValue">https://b.925i.cn</text>
</tm-listitem>
<tm-listitem title="文档地址" :font-size="28" @click="$utils.copyText('https://uni-halo.925i.cn', '项目码云仓库已复制')">
<text class="text-size-s text-overflow" slot="rightValue">https://uni-halo.925i.cn</text>
</tm-listitem>
<tm-listitem title="码云仓库" :font-size="28" @click="$utils.copyText('https://gitee.com/ialley-workshop-open/uni-halo', '码云仓库地址已复制')">
<text class="text-size-s text-overflow" slot="rightValue">https://gitee.com/ialley-workshop-open/uni-halo</text>
</tm-listitem>
<tm-listitem title="Github" :font-size="28" @click="$utils.copyText('https://github.com/ialley-workshop-open/uni-halo', 'Github地址已复制')">
<text class="text-size-s text-overflow" slot="rightValue">https://github.com/ialley-workshop-open/uni-halo</text>
</tm-listitem>
</tm-grouplist>
</view>
<view class="copyright bg-white text-size-xs text-align-center text-grey-darken-1 pa-36">
<view class="">根据 MIT 许可证发布</view>
<view class="mt-8"> 2022 uni-halo 开源项目丨巷子工坊@小莫唐尼 </view>
</view>
</view>
<view class="app-page bg-white flex flex-col">
<view class="logo mt-40 pt-40">
<image class="logo-img round-3" src="https://uni-halo.925i.cn/logo.png" mode="aspectFill"></image>
</view>
<view class="mt-12 text-weight-b text-size-lg">uni-halo</view>
<view class="mt-12 text-size-s text-grey-darken-3"></view>
<view class="list-group mt-48" style="width: 100%;">
<tm-grouplist :shadow="0" :margin="0" :borderBottom="true">
<tm-listitem title="开源组织" :font-size="28" @click="$utils.copyText('https://www.ialley.cn', '巷子工坊官网已复制成功!')">
<text class="text-size-s" slot="rightValue">巷子工坊</text>
</tm-listitem>
<tm-listitem title="开源作者" :font-size="28" @click="$utils.copyText('https://b.925i.cn', '作者博客地址已复制')">
<text class="text-size-s" slot="rightValue">小莫唐尼</text>
</tm-listitem>
<tm-listitem title="作者博客" :font-size="28" @click="$utils.copyText('https://b.925i.cn', '作者博客地址已复制')">
<text class="text-size-s text-overflow" slot="rightValue">https://b.925i.cn</text>
</tm-listitem>
<tm-listitem title="文档地址" :font-size="28" @click="$utils.copyText('https://uni-halo.925i.cn', '项目码云仓库已复制')">
<text class="text-size-s text-overflow" slot="rightValue">https://uni-halo.925i.cn</text>
</tm-listitem>
<tm-listitem title="码云仓库" :font-size="28"
@click="$utils.copyText('https://gitee.com/ialley-workshop-open/uni-halo', '码云仓库地址已复制')">
<text class="text-size-s text-overflow" slot="rightValue">https://gitee.com/ialley-workshop-open/uni-halo</text>
</tm-listitem>
<tm-listitem title="Github" :font-size="28"
@click="$utils.copyText('https://github.com/ialley-workshop-open/uni-halo', 'Github地址已复制')">
<text class="text-size-s text-overflow" slot="rightValue">https://github.com/ialley-workshop-open/uni-halo</text>
</tm-listitem>
</tm-grouplist>
</view>
<view class="copyright bg-white text-size-xs text-align-center text-grey-darken-1 pa-36">
<view class="">根据 AGPL-3.0 协议开源</view>
<view class="mt-8"> 2022 uni-halo 开源项目丨巷子工坊@小莫唐尼 </view>
</view>
</view>
</template>
<script>
import tmGrouplist from '@/tm-vuetify/components/tm-grouplist/tm-grouplist.vue';
import tmListitem from '@/tm-vuetify/components/tm-listitem/tm-listitem.vue';
export default {
components: {
tmGrouplist,
tmListitem
},
computed: {
uni_halo_logo() {
return getApp().globalData.uni_halo_logo;
}
},
onLoad() {
this.fnSetPageTitle('关于项目');
},
methods: {}
components: {
tmGrouplist,
tmListitem
},
onLoad() {
this.fnSetPageTitle('关于项目');
},
methods: {}
};
</script>
<style scoped lang="scss">
.app-page {
width: 100vw;
height: 100vh;
box-sizing: border-box;
align-items: center;
.logo-img {
width: 160rpx;
height: 160rpx;
}
.info {
margin-left: 90rpx;
margin-right: 90rpx;
}
.copyright {
width: 100vw;
position: fixed;
bottom: 0;
left: 0;
box-sizing: border-box;
}
width: 100vw;
height: 100vh;
box-sizing: border-box;
align-items: center;
.label {
display: inline-block;
min-width: 140rpx;
}
.logo-img {
width: 160rpx;
height: 160rpx;
}
.info {
margin-left: 90rpx;
margin-right: 90rpx;
}
.copyright {
width: 100vw;
position: fixed;
bottom: 0;
left: 0;
box-sizing: border-box;
}
.label {
display: inline-block;
min-width: 140rpx;
}
}
.text-overflow {
width: calc(100vw - 300rpx);
text-align: right;
width: calc(100vw - 300rpx);
text-align: right;
}
.list-group {
::v-deep {
.left {
width: 160rpx;
min-width: 160rpx;
}
}
::v-deep {
.left {
width: 160rpx;
min-width: 160rpx;
}
}
}
</style>
+145
View File
@@ -0,0 +1,145 @@
<template>
<tm-fullView>
<tm-sheet :shadow="24">
<tm-alerts label="观看视频即可获取注册码" close></tm-alerts>
<tm-divider color="red" model="dashed" :text="codeDataShow?'请复制下方注册码':'请点击“获取注册码”'"></tm-divider>
<view class="ma-20" v-show="!codeDataShow">
<tm-button theme="bg-gradient-orange-accent" :round="24" block @click="openVideoAd">获取注册码</tm-button>
</view>
<view class="ma-20" v-show="codeDataShow">
<tm-coupon :hdata="codeData" color="orange" @click="fnCopyCode"></tm-coupon>
</view>
</tm-sheet>
<!-- <tm-sheet :shadow="24">
<tm-images @load="loadimg" src="https://picsum.photos/300?id=7"></tm-images>
</tm-sheet> -->
</tm-fullView>
</template>
<script>
let videoAd = null;
import tmFullView from "@/tm-vuetify/components/tm-fullView/tm-fullView.vue"
import tmMenubars from "@/tm-vuetify/components/tm-menubars/tm-menubars.vue"
import tmSheet from "@/tm-vuetify/components/tm-sheet/tm-sheet.vue"
import tmAlerts from "@/tm-vuetify/components/tm-alerts/tm-alerts.vue"
import tmDivider from "@/tm-vuetify/components/tm-divider/tm-divider.vue"
import tmCoupon from '@/tm-vuetify/components/tm-coupon/tm-coupon.vue'
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
import tmImages from "@/tm-vuetify/components/tm-images/tm-images.vue"
export default {
components: {
tmFullView,
tmMenubars,
tmSheet,
tmAlerts,
tmDivider,
tmCoupon,
tmButton,
tmImages
},
data() {
return {
adUnitId: '',
codeDataShow: false,
codeData: {
// img: 'https://lywq.muyin.site/logo.png',
title: "请获取",
btnText: '复制',
time: '有效期5分钟',
sale: '',
saleLable: '注册码',
saleSplit: ''
}
}
},
computed: {
toolsPluginConfigs() {
return this.$tm.vx.getters().getConfigs?.pluginConfig?.toolsPlugin || {};
}
},
onLoad(options) {
// #ifdef MP-WEIXIN
wx.hideShareMenu();
this.adLoad();
// #endif
uni.onCopyUrl((result) => {
setTimeout(() => {
uni.setClipboardData({
data: "禁止复制哦",
})
}, 1000);
})
},
methods: {
adLoad() {
if (wx.createRewardedVideoAd) {
videoAd = wx.createRewardedVideoAd({
adUnitId: this.toolsPluginConfigs.rewardedVideoAdId //你的广告key
})
videoAd.onError(err => {
})
videoAd.onClose((status) => {
if (status && status.isEnded || status === undefined) {
//这里写广告播放完成后的事件
this.getRegistrationCode();
} else {
// 广告播放未完成
}
})
}
},
openVideoAd: function () {
if (videoAd && this.toolsPluginConfigs.rewardedVideoAdId !== '') {
videoAd.show().catch(err => {
// 失败重试
console.log("广告拉取失败")
videoAd.load().then(() => videoAd.show())
})
} else {
this.getRegistrationCode();
}
},
getRegistrationCode() {
uni.showLoading({
title: '正在获取...'
});
this.$httpApi.v2.getPostVerifyCode()
.then(res => {
if (res.code === 200) {
uni.$tm.toast('获取成功!');
this.codeData.title = res.data;
this.codeDataShow = true;
} else {
uni.$tm.toast('操作失败,请重试!');
}
})
.catch(err => {
uni.$tm.toast(err.message);
});
},
fnCopyCode() {
uni.setClipboardData({
data: this.codeData.title,
showToast: false,
success: () => {
uni.showToast({
icon: 'none',
title: '复制成功!'
});
setTimeout(() => {
uni.navigateBack()
}, 500);
},
fail: () => {
uni.showToast({
icon: 'none',
title: '复制失败!'
});
}
});
}
}
}
</script>
+556 -299
View File
@@ -1,69 +1,85 @@
<template>
<view class="app-page">
<view class="e-fixed"><tm-tabs color="light-blue" v-model="tab.activeIndex" :list="tab.list" align="center" @change="fnOnTabChange"></tm-tabs></view>
<!-- 占位区域 -->
<view style="width: 100vw;height: 90rpx;"></view>
<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 class="e-fixed">
<tm-tabs color="light-blue" v-model="tab.activeIndex" :list="tab.list" align="center"
@change="fnOnTabChange"></tm-tabs>
</view>
<!-- 占位区域 -->
<view style="width: 100vw;height: 90rpx;"></view>
<!-- 加载完成区域 -->
<block v-else>
<view v-if="dataList.length == 0" class="list-empty flex flex-center"><tm-empty icon="icon-shiliangzhinengduixiang-" label="暂无归档的文章"></tm-empty></view>
<view v-else class="e-timeline tm-timeline mt-24">
<block v-for="(item, index) in dataList" :key="index">
<view class="tm-timeline-item tm-timeline-item--leftDir">
<view style="width: 160rpx;">
<view
:style="{ width: '24rpx', height: '24rpx' }"
:class="[black_tmeme ? 'bk' : '']"
class="flex-center rounded tm-timeline-jidian border-white-a-2 grey-lighten-2 light-blue shadow-primary-4"
></view>
<view
:style="{ marginTop: '-24rpx' }"
:class="[index !== dataList.length - 1 ? 'tm-timeline-item-boder' : '', black_tmeme ? 'bk' : '']"
class="grey-lighten-2"
></view>
</view>
<view class="tm-timeline-item-content relative">
<view class="tm-timeline-item-left">
<view class="time text-weight-b mb-24">
{{ item.year }}
<block v-if="tab.activeIndex == 0">{{ item.month }}</block>
<text class="text-size-s text-grey-darken-1 ml-24">共发布 {{ item.posts.length }} 篇文章</text>
</view>
<block v-if="item.posts.length != 0">
<block v-for="(post, postIndex) in item.posts" :key="post.id">
<tm-translate animation-name="fadeUp" :wait="(postIndex + 1) * 50">
<view class="flex post shadow-3 pa-24 mb-24" :class="[globalAppSettings.layout.cardType]" @click="fnToArticleDetail(post)">
<image class="post-thumbnail" :src="$utils.checkThumbnailUrl(post.thumbnail)" mode="aspectFill"></image>
<view class="post-info pl-20">
<view class="post-info_title text-overflow">{{ post.title }}</view>
<view class="post-info_summary text-overflow-2 mt-12 text-size-s text-grey-darken-1">{{ post.summary }}</view>
<view class="post-info_time mt-12 text-size-s text-grey-darken-1">
<text class="iconfont icon-clock text-size-s mr-6"></text>
<text class="time-label">发布时间</text>
{{ { d: post.createTime, f: 'yyyy年MM月dd日 星期w' } | formatTime }}
</view>
</view>
</view>
</tm-translate>
</block>
</block>
<view v-else class="post-empty text-size-m text-grey-darken-1">该日期下暂无归档文章</view>
</view>
</view>
</view>
</block>
</view>
<!-- 返回顶部 -->
<tm-flotbutton @click="fnToTopPage" size="m" color="bg-gradient-light-blue-accent" icon="icon-angle-up"></tm-flotbutton>
</block>
</view>
<!-- 骨架屏加载区域 -->
<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>
<view v-if="dataList.length === 0" class="list-empty flex flex-center">
<tm-empty v-if="haloConfigs.basicConfig.auditModeEnabled" icon="icon-shiliangzhinengduixiang-" label="暂无归档的内容"></tm-empty>
<tm-empty v-else icon="icon-shiliangzhinengduixiang-" label="暂无归档的文章"></tm-empty>
</view>
<view v-else class="e-timeline tm-timeline mt-24">
<block v-for="(item, index) in dataList" :key="index">
<view class="tm-timeline-item tm-timeline-item--leftDir">
<view style="width: 160rpx;">
<view :style="{ width: '24rpx', height: '24rpx' }" :class="[black_tmeme ? 'bk' : '']"
class="flex-center rounded tm-timeline-jidian border-white-a-2 grey-lighten-2 light-blue shadow-primary-4">
</view>
<view :style="{ marginTop: '-24rpx' }"
:class="[index !== dataList.length - 1 ? 'tm-timeline-item-boder' : '', black_tmeme ? 'bk' : '']"
class="grey-lighten-2"></view>
</view>
<view class="tm-timeline-item-content relative">
<view class="tm-timeline-item-left">
<view class="flex time text-weight-b mb-24">
<text>{{ item.year }}</text>
<text v-if="tab.activeIndex === 0">{{ item.month }}</text>
<view v-if="haloConfigs.basicConfig.auditModeEnabled" class="text-size-s text-grey-darken-1 ml-12">
{{ item.posts.length }} 篇内容
</view>
<view v-else class="text-size-s text-grey-darken-1 ml-12">
{{ item.posts.length }} 篇文章
</view>
</view>
<block v-if="item.posts.length !== 0">
<block v-for="(post, postIndex) in item.posts" :key="post.metadata.name">
<view class="flex post shadow-3 pa-24 mb-24"
:class="[globalAppSettings.layout.cardType]"
@click="fnToArticleDetail(post)">
<image class="post-thumbnail"
:src="$utils.checkThumbnailUrl(post.spec.cover)" mode="aspectFill">
</image>
<view class="post-info pl-20">
<view class="post-info_title text-overflow">{{ post.spec.title }}
</view>
<view
class="post-info_summary text-overflow-2 mt-12 text-size-s text-grey-darken-1">
{{ post.status.excerpt }}
</view>
<view class="post-info_time mt-12 text-size-s text-grey-darken-1">
<text class="iconfont icon-clock text-size-s mr-6"></text>
<text class="time-label">发布时间</text>
{{ {d: post.spec.publishTime, f: 'yyyy年MM月dd日 星期w'} | formatTime }}
</view>
</view>
</view>
</block>
</block>
<view v-else class="post-empty text-size-m text-grey-darken-1">该日期下暂无归档文章</view>
</view>
</view>
</view>
</block>
</view>
<view class="load-text mt-12">{{ loadMoreText }}</view>
<!-- 返回顶部 -->
<tm-flotbutton @click="fnToTopPage" size="m" color="bg-gradient-light-blue-accent"
icon="icon-angle-up"></tm-flotbutton>
</block>
</view>
</template>
<script>
@@ -72,269 +88,510 @@ import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
import tmTabs from '@/tm-vuetify/components/tm-tabs/tm-tabs.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import qs from 'qs'
export default {
components: {
tmSkeleton,
tmTranslate,
tmFlotbutton,
tmTabs,
tmEmpty
},
data() {
return {
loading: 'loading',
tab: {
activeIndex: 0,
list: ['按月份查看', '按年份查看']
},
queryParams: {
size: 10,
page: 0
},
result: null,
dataList: [],
api: 'getMonthArchives'
};
},
computed: {
black_tmeme: function() {
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme: function() {
return this.$tm.vx.state().tmVuetify.color;
}
},
created() {
this.fnGetData();
},
onPullDownRefresh() {
this.fnGetData();
this.queryParams.page = 0;
},
components: {
tmSkeleton,
tmTranslate,
tmFlotbutton,
tmTabs,
tmEmpty
},
data() {
return {
loading: 'loading',
tab: {
activeIndex: 0,
list: ['按月份查看', '按年份查看']
},
queryParams: {
size: 10,
page: 1
},
result: {},
cacheDataList: [], // 所有请求的缓存数据
dataList: [], // 显示的数据
isLoadMore: false,
loadMoreText: "加载中..."
};
},
computed: {
black_tmeme: function () {
return this.$tm.vx.state().tmVuetify.black;
},
color_tmeme: function () {
return this.$tm.vx.state().tmVuetify.color;
},
haloConfigs() {
return this.$tm.vx.getters().getConfigs;
},
mockJson() {
return this.$tm.vx.getters().getMockJson;
}
},
created() {
this.fnGetData();
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 1;
this.fnGetData();
},
onReachBottom(e) {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
return
}
if (this.result.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnOnTabChange(index) {
this.fnResetSetAniWaitIndex();
this.queryParams.page = 0;
this.dataList = this.handleGetShowDataList(this.handleGetPosts(this.cacheDataList))
this.fnToTopPage();
},
fnGetData() {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
const dataList = this.mockJson.archives.list.map(item => {
const date = new Date(item.time)
const year = date.getFullYear()
const month = date.getMonth() + 1
return {
metadata: {
name: Date.now() * Math.random(),
labels: {
"content.halo.run/archive-year": year,
"content.halo.run/archive-month": month
}
},
spec: {
pinned: false,
cover: item.cover,
title: item.title,
publishTime: item.time
},
status: {
excerpt: item.desc
},
stats: {
visit: 0
}
}
});
const posts = this.handleGetPosts(dataList)
this.dataList = []
this.cacheDataList = dataList;
this.dataList = this.handleGetShowDataList(posts)
this.loading = 'success';
this.loadMoreText = '呜呜,没有更多数据啦~';
uni.hideLoading();
uni.stopPullDownRefresh();
return;
}
if (this.isLoadMore) {
uni.showLoading({
title: "加载中..."
})
} else {
this.loading = 'loading';
}
this.loadMoreText = "加载中...";
const paramsStr = qs.stringify(this.queryParams, {
allowDots: true,
encodeValuesOnly: true,
skipNulls: true,
encode: true,
arrayFormat: 'repeat'
})
uni.request({
url: this.$baseApiUrl + '/apis/api.content.halo.run/v1alpha1/posts?' + paramsStr,
method: 'GET',
success: (res) => {
const data = res.data;
this.result = data;
const posts = this.handleGetPosts(data.items)
const showDataList = this.handleGetShowDataList(posts)
if (this.isLoadMore) {
this.cacheDataList = this.handleUniqueCacheDatalist([
...this.cacheDataList, ...data.items
]);
this.handleMergeDataList2(showDataList)
} else {
this.dataList = []
this.cacheDataList = data.items;
this.dataList = showDataList
}
methods: {
fnOnTabChange(index) {
// todo:
const _api = ['getMonthArchives', 'getYearArchives'];
this.api = _api[index];
this.queryParams.page = 0;
this.fnGetData();
},
fnGetData() {
this.loading = 'loading';
uni.showLoading({
mask: true,
title: '加载中...'
});
this.$httpApi[this.api](this.queryParams)
.then(res => {
this.result = res.data;
this.dataList = res.data;
setTimeout(() => {
this.loading = 'success';
}, 500);
})
.catch(err => {
console.error(err);
this.loading = 'error';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
},
//跳转文章详情
fnToArticleDetail(article) {
uni.navigateTo({
url: '/pagesA/article-detail/article-detail?articleId=' + article.id,
animationType: 'slide-in-right'
});
}
}
this.loading = 'success';
this.loadMoreText = data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
uni.hideLoading();
uni.stopPullDownRefresh();
},
fail: (err) => {
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
uni.$tm.toast(err.message || '数据加载失败!');
uni.hideLoading();
uni.stopPullDownRefresh();
}
})
},
// 处理数据分类
handleGetPosts(dataList) {
const posts = {}
const postLabelYearKey = "content.halo.run/archive-year"
const postLabelMonthKey = "content.halo.run/archive-month"
dataList.forEach(item => {
let postItemKey = ""
if (this.tab.activeIndex === 0) {
postItemKey =
`${item.metadata.labels[postLabelYearKey]}-${item.metadata.labels[postLabelMonthKey]}`
} else {
postItemKey = `${item.metadata.labels[postLabelYearKey]}`
}
if (posts[postItemKey]) {
posts[postItemKey].push(item)
} else {
posts[postItemKey] = [item]
}
})
return posts;
},
// 根据分类的数据,处理成显示的数据
handleGetShowDataList(posts) {
const dataListResult = []
Object.keys(posts).forEach((key) => {
const postData = {
sort: 0,
key: key,
year: key,
month: "",
posts: posts[key]
}
if (this.tab.activeIndex == 0) {
const splitDate = key.split("-")
postData.year = splitDate[0]
postData.month = splitDate[1]
postData.sort = Number(key.replace("-", ""))
} else {
postData.sort = Number(key)
}
dataListResult.push(postData)
})
dataListResult.sort((a, b) => {
return Number(b.sort) - Number(a.sort)
})
return dataListResult;
},
handleMergeDataList(list1, list2) {
// 将list1转换为以key为键的对象
let merged = list1.reduce((acc, item) => {
acc[item.key] = {
...item
};
return acc;
}, {});
// 遍历list2,合并posts数组或添加新对象
list2.forEach(item => {
if (merged[item.key]) {
// 如果key已存在,合并posts数组
merged[item.key].posts = [...merged[item.key].posts, ...item.posts];
} else {
// 如果key不存在,添加新对象
merged[item.key] = {
...item
};
}
});
// 将对象转换回数组
return Object.values(merged);
},
handleMergeDataList2(list) {
list.forEach((item, index) => {
const find = this.dataList.find(x => x.key == item.key)
if (find) {
item.posts.forEach(post => {
if (!find.posts.find(x => x.metadata.name == post.metadata.name)) {
find.posts.push(post)
}
})
}
})
list.forEach(post => {
if (!this.dataList.find(x => x.key === post.key)) {
this.dataList.push(post)
}
})
this.dataList.sort((a, b) => {
return Number(b.sort) - Number(a.sort)
})
},
handleUniqueCacheDatalist(dataList) {
const seen = new Set();
return dataList.filter(item => {
return seen.has(item.metadata.name) ? false : seen.add(item.metadata.name);
});
},
fnToArticleDetail(article) {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
return;
}
uni.navigateTo({
url: '/pagesA/article-detail/article-detail?name=' + article.metadata.name,
animationType: 'slide-in-right'
});
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #fafafd;
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #fafafd;
}
.auditModeEnabled {
width: 100%;
height: 80vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-wrap {
padding: 24rpx;
padding: 24rpx;
}
.list-empty {
width: 100vw;
height: 100vh;
width: 100vw;
height: 100vh;
}
.statistics {
background-color: #ffffff;
background-color: #ffffff;
}
.e-timeline {
::v-deep {
.tm-timeline-item > view:first-child {
width: 110rpx !important;
}
.tm-timeline-item-left {
max-width: 580rpx !important;
width: 100% !important;
}
}
::v-deep {
.tm-timeline-item > view:first-child {
width: 110rpx !important;
}
.tm-timeline-item-left {
max-width: 580rpx !important;
width: 100% !important;
}
}
}
.tm-timeline {
.tm-timeline-item {
.tm-timeline-item-left,
.tm-timeline-item-right {
width: 200rpx;
flex-shrink: 0;
}
.tm-timeline-item {
.tm-timeline-item-content {
display: flex;
justify-content: center;
align-items: flex-start;
align-content: flex-start;
}
.tm-timeline-jidian {
margin: auto;
}
&.tm-timeline-item--leftDir {
display: flex;
flex-flow: row;
&.endright {
justify-content: flex-end;
}
.tm-timeline-item-left,
.tm-timeline-item-right {
width: auto;
max-width: 400rpx;
}
.tm-timeline-item-boder {
height: 100%;
width: 1px;
margin: auto;
}
.tm-timeline-jidian {
position: relative;
margin: auto;
z-index: 2;
}
.tm-timeline-item-content {
display: flex;
justify-content: flex-start;
align-items: flex-start;
align-content: flex-start;
}
}
}
.tm-timeline-item-left,
.tm-timeline-item-right {
width: 200rpx;
flex-shrink: 0;
}
.tm-timeline-item-content {
display: flex;
justify-content: center;
align-items: flex-start;
align-content: flex-start;
}
.tm-timeline-jidian {
margin: auto;
}
&.tm-timeline-item--leftDir {
display: flex;
flex-flow: row;
&.endright {
justify-content: flex-end;
}
.tm-timeline-item-left,
.tm-timeline-item-right {
width: auto;
max-width: 400rpx;
}
.tm-timeline-item-boder {
height: 100%;
width: 1px;
margin: auto;
}
.tm-timeline-jidian {
position: relative;
margin: auto;
z-index: 2;
}
.tm-timeline-item-content {
display: flex;
justify-content: flex-start;
align-items: flex-start;
align-content: flex-start;
}
}
}
}
.post {
width: 560rpx;
border-radius: 12rpx;
background-color: #fff;
&.lr_image_text {
}
&.lr_text_image {
.post-thumbnail {
order: 2;
}
.post-info {
order: 1;
padding-left: 0;
padding-right: 24rpx;
}
}
&.tb_image_text {
flex-direction: column;
.post-thumbnail {
width: 100%;
height: 220rpx;
}
.post-info {
width: 100%;
padding-left: 0;
&_title {
margin-top: 12rpx;
}
&_time {
.iconfont {
display: none;
}
.time-label {
display: inline-block;
}
}
}
}
&.tb_text_image {
flex-direction: column;
.post-thumbnail {
order: 2;
width: 100%;
height: 220rpx;
margin-top: 12rpx;
}
.post-info {
order: 1;
width: 100%;
padding-left: 0;
width: 560rpx;
border-radius: 12rpx;
background-color: #fff;
&_time {
.iconfont {
display: none;
}
.time-label {
display: inline-block;
}
}
}
}
&.only_text {
.post-info {
padding: 6rpx;
&_time {
margin-top: 20rpx;
.iconfont {
display: none;
}
.time-label {
display: inline-block;
}
}
}
.post-thumbnail {
display: none;
}
}
&.lr_image_text {
}
&.lr_text_image {
.post-thumbnail {
order: 2;
}
.post-info {
order: 1;
padding-left: 0;
padding-right: 24rpx;
}
}
&.tb_image_text {
flex-direction: column;
.post-thumbnail {
width: 100%;
height: 220rpx;
}
.post-info {
width: 100%;
padding-left: 0;
&_title {
margin-top: 12rpx;
}
&_time {
.iconfont {
display: none;
}
.time-label {
display: inline-block;
}
}
}
}
&.tb_text_image {
flex-direction: column;
.post-thumbnail {
order: 2;
width: 100%;
height: 220rpx;
margin-top: 12rpx;
}
.post-info {
order: 1;
width: 100%;
padding-left: 0;
&_time {
.iconfont {
display: none;
}
.time-label {
display: inline-block;
}
}
}
}
&.only_text {
.post-info {
padding: 6rpx;
&_time {
margin-top: 20rpx;
.iconfont {
display: none;
}
.time-label {
display: inline-block;
}
}
}
.post-thumbnail {
display: none;
}
}
}
.post-thumbnail {
border-radius: 6rpx;
width: 200rpx;
height: 170rpx;
border-radius: 6rpx;
width: 200rpx;
height: 170rpx;
}
.post-info {
width: 0;
flex-grow: 1;
&_title {
color: #303133;
font-size: 28rpx;
font-weight: bold;
}
&_summary {
display: -webkit-box;
line-height: 1.6;
}
&_time {
.time-label {
display: none;
}
}
width: 0;
flex-grow: 1;
&_title {
color: #303133;
font-size: 28rpx;
font-weight: bold;
}
&_summary {
display: -webkit-box;
line-height: 1.6;
}
&_time {
.time-label {
display: none;
}
}
}
.time {
align-items: center;
}
</style>
File diff suppressed because it is too large Load Diff
+175 -177
View File
@@ -1,47 +1,49 @@
<template>
<view class="app-page" :class="{ 'is-balck grey-darken-6': isBlackTheme }">
<!-- 顶部切换 -->
<view class="e-fixed shadow-2">
<tm-search v-model="queryParams.keyword" :round="24" :shadow="0" color="light-blue" insert-color="light-blue" :clear="true" @confirm="fnOnSearch"></tm-search>
<tm-tabs color="light-blue" :shadow="0" v-model="tab.activeIndex" :list="tab.list" align="center" @change="fnOnTabChange"></tm-tabs>
</view>
<!-- 占位区域 -->
<view style="width: 100vw;height: 184rpx;"></view>
<!-- 加载区域 -->
<view v-if="loading != 'success'" class="loading-wrap pa-24">
<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">
<view v-if="dataList.length == 0" class="content-empty flex flex-center">
<!-- 空布局 -->
<tm-empty icon="icon-shiliangzhinengduixiang-" label="该分类下暂无数据"></tm-empty>
</view>
<block v-else>
<block v-for="(article, index) in dataList" :key="article.id">
<!-- 文章卡片 -->
<tm-translate animation-name="fadeUp" :wait="(index + 1) * 50">
<article-card :article="article" @on-click="fnToArticleDetail"></article-card>
<!-- 广告区域 -->
<view v-if="haloAdConfig.articles.use && (index + 1) % haloAdConfig.frequency == 0" class="ad-wrap ma-24">
<!-- #ifdef MP-WEIXIN -->
<ad v-if="haloAdConfig.unitId" :unit-id="haloAdConfig.unitId"></ad>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<ad v-if="haloAdConfig.adpid" :adpid="haloAdConfig.adpid"></ad>
<!-- #endif -->
</view>
</tm-translate>
</block>
<view class="app-page" :class="{ 'is-balck grey-darken-6': isBlackTheme }">
<!-- 顶部切换 -->
<view class="e-fixed shadow-2">
<tm-search v-model="queryParams.keyword" :round="24" :shadow="0" color="light-blue"
insert-color="light-blue" :clear="true" @input="fnOnSearch" @confirm="fnOnSearch"></tm-search>
<tm-tabs v-if="false" color="light-blue" :shadow="0" v-model="tab.activeIndex" :list="tab.list"
align="center" @change="fnOnTabChange"></tm-tabs>
</view>
<!-- 占位区域 -->
<view style="width: 100vw;height: 100rpx;"></view>
<!-- 加载区域 -->
<view v-if="loading != 'success'" class="loading-wrap pa-24">
<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">
<view v-if="dataList.length == 0" class="content-empty flex flex-center">
<!-- 空布局 -->
<tm-empty v-if="!queryParams.keyword" icon="icon-shiliangzhinengduixiang-" label="请输入关键词搜索"></tm-empty>
<tm-empty v-else icon="icon-shiliangzhinengduixiang-"
:label="`未搜到 ${queryParams.keyword} 相关内容`"></tm-empty>
</view>
<block v-else>
<!-- 文章卡片 -->
<tm-translate v-for="(article, index) in dataList" :key="article.metadataName" animation-name="fadeUp"
:wait="calcAniWait(index)">
<view class="article-card" @click="fnToArticleDetail(article)">
<rich-text style="font-size: 32rpx;font-weight: bold;color: #333;"
:nodes="article.title"></rich-text>
<rich-text style="font-size: 28rpx;margin-top: 16rpx;color: #555;"
:nodes="article.content"></rich-text>
<text style="font-size: 24rpx;margin-top: 24rpx;color:#888">
最近更新{{ {d: article.updateTimestamp, f: 'yyyy年MM月dd日 HH点mm分ss秒'} | formatTime }}
</text>
</view>
</tm-translate>
<tm-flotbutton @click="fnToTopPage" size="m" color="light-blue" icon="icon-angle-up"></tm-flotbutton>
<view class="load-text">{{ loadMoreText }}</view>
</block>
</view>
</view>
<tm-flotbutton @click="fnToTopPage" size="m" color="light-blue" icon="icon-angle-up"></tm-flotbutton>
</block>
</view>
</view>
</template>
<script>
@@ -53,151 +55,147 @@ import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vu
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
export default {
components: {
tmSkeleton,
tmSearch,
tmTranslate,
tmTabs,
tmFlotbutton,
tmEmpty
},
data() {
return {
isBlackTheme: false,
loading: 'loading',
tab: {
activeIndex: 0,
list: ['全部', '最新文章', '热门文章', '最近更新', '最多点赞']
},
queryParams: {
size: 10,
page: 0,
sort: 'topPriority,createTime,desc',
keyword: ''
},
cache: {
dataList: [],
total: 0
},
isLoadMore: false,
loadMoreText: '加载中...',
result: {},
dataList: []
};
},
onLoad() {
this.fnSetPageTitle('文章列表');
},
created() {
this.fnGetData();
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
},
components: {
tmSkeleton,
tmSearch,
tmTranslate,
tmTabs,
tmFlotbutton,
tmEmpty
},
data() {
return {
isBlackTheme: false,
loading: 'loading',
tab: {
activeIndex: 0,
list: ['全部', '最新文章', '热门文章', '最近更新', '最多点赞']
},
queryParams: {
keyword: "",
limit: 5,
highlightPreTag: "<text>",
highlightPostTag: "</text>"
},
cache: {
dataList: [],
total: 0
},
dataList: []
};
},
computed: {
haloConfigs() {
return this.$tm.vx.getters().getConfigs;
},
},
onLoad() {
this.fnSetPageTitle('内容搜索');
},
created() {
if (!this.queryParams.keyword) {
this.loading = 'success'
} else {
this.fnGetData();
}
},
onPullDownRefresh() {
this.fnResetSetAniWaitIndex();
this.fnGetData();
},
onReachBottom(e) {
if (this.result.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnOnTabChange(index) {
this.dataList = [];
const _sorts = {
0: '',
1: 'topPriority,createTime,desc',
2: 'topPriority,visits,desc',
3: 'topPriority,updateTime,desc',
4: 'topPriority,likes,desc'
};
this.queryParams.sort = _sorts[index];
this.queryParams.page = 0;
this.dataList = [];
this.fnToTopPage();
this.fnGetData();
},
fnOnSearch() {
this.queryParams.page = 0;
this.isLoadMore = false;
this.fnGetData();
},
fnGetData() {
uni.showLoading({
mask: true,
title: '加载中...'
});
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi
.getPostList(this.queryParams)
.then(res => {
console.log('请求结果:');
console.log(res);
methods: {
fnOnTabChange(index) {
this.fnResetSetAniWaitIndex();
this.dataList = [];
const _sorts = {
0: '',
1: 'topPriority,createTime,desc',
2: 'topPriority,visits,desc',
3: 'topPriority,updateTime,desc',
4: 'topPriority,likes,desc'
};
this.queryParams.sort = _sorts[index];
this.dataList = [];
this.fnToTopPage();
this.fnGetData();
},
fnOnSearch() {
this.fnResetSetAniWaitIndex();
this.loading = 'success';
this.loadMoreText = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
// 处理数据
this.result = res.data;
if (this.isLoadMore) {
this.dataList = this.dataList.concat(res.data.content);
} else {
this.dataList = res.data.content;
}
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 800);
});
},
if (!this.queryParams.keyword) {
this.dataList = []
} else {
this.fnGetData();
}
},
fnGetData() {
if (this.haloConfigs.basicConfig.auditModeEnabled) {
return;
}
// 设置状态为加载中
this.loading = 'loading';
this.$httpApi.v2
.getPostListByKeyword(this.queryParams)
.then(res => {
this.loading = 'success';
this.dataList = res.hits;
})
.catch(err => {
console.error(err);
this.loading = 'error';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 800);
});
},
//跳转文章详情
fnToArticleDetail(article) {
uni.navigateTo({
url: '/pagesA/article-detail/article-detail?articleId=' + article.id,
animationType: 'slide-in-right'
});
}
}
//跳转文章详情
fnToArticleDetail(article) {
uni.navigateTo({
url: '/pagesA/article-detail/article-detail?name=' + article.metadataName,
animationType: 'slide-in-right'
});
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
padding-bottom: 24rpx;
background-color: #fafafd;
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
padding-bottom: 24rpx;
background-color: #fafafd;
&.is-balck {
background-color: #212121;
}
&.is-balck {
background-color: #212121;
}
}
.content {
padding-top: 24rpx;
.content-empty {
height: 60vh;
}
.content {
padding-top: 24rpx;
.content-empty {
height: 60vh;
}
}
.article-card {
display: flex;
flex-direction: column;
box-sizing: border-box;
margin: 0 24rpx;
padding: 24rpx;
border-radius: 12rpx;
background-color: #ffff;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
overflow: hidden;
margin-bottom: 24rpx;
}
</style>
+132 -122
View File
@@ -1,24 +1,30 @@
<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>
<tm-skeleton model="listAvatr"></tm-skeleton>
</view>
<block v-else>
<view class="empty" v-if="dataList.length == 0"><tm-empty icon="icon-shiliangzhinengduixiang-" label="该分类下暂无文章"></tm-empty></view>
<block v-else>
<block v-for="(article, index) in dataList" :key="article.createTime">
<!-- 文章卡片 -->
<tm-translate animation-name="fadeUp" :wait="(index + 1) * 50"><article-card :article="article" @on-click="fnToArticleDetail"></article-card></tm-translate>
</block>
<view class="load-text">{{ loadMoreText }}</view>
</block>
<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>
<tm-skeleton model="listAvatr"></tm-skeleton>
</view>
<block v-else>
<view class="empty" v-if="dataList.length == 0">
<tm-empty icon="icon-shiliangzhinengduixiang-"
label="该分类下暂无文章"></tm-empty>
</view>
<block v-else>
<block v-for="(article, index) in dataList" :key="article.createTime">
<!-- 文章卡片 -->
<tm-translate animation-name="fadeUp" :wait="calcAniWait(index)">
<article-card :article="article"
@on-click="fnToArticleDetail"></article-card>
</tm-translate>
</block>
<view class="load-text">{{ loadMoreText }}</view>
</block>
<tm-flotbutton @click="fnToTopPage" size="m" icon="icon-angle-up"></tm-flotbutton>
</block>
</view>
<tm-flotbutton @click="fnToTopPage" color="light-blue" size="m" icon="icon-angle-up"></tm-flotbutton>
</block>
</view>
</template>
<script>
@@ -27,118 +33,122 @@ import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import ArticleCard from '@/components/article-card/article-card.vue';
export default {
components: {
tmSkeleton,
tmTranslate,
tmFlotbutton,
tmEmpty,
ArticleCard
},
data() {
return {
loading: 'loading',
queryParams: {
size: 10,
page: 0
},
slug: '',
pageTitle: '加载中...',
result: null,
dataList: [],
isLoadMore: false,
loadMoreText: ''
};
},
onLoad(e) {
this.slug = e.slug;
this.pageTitle = e.name;
this.fnGetData();
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
},
onReachBottom(e) {
if (this.result.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnGetData() {
uni.showLoading({
mask: true,
title: '加载中...'
});
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi
.getCategoryPostList(this.slug, this.queryParams)
.then(res => {
this.fnSetPageTitle(this.pageTitle + ` (共${res.data.total}篇)`);
this.result = res.data;
if (this.isLoadMore) {
this.dataList = this.dataList.concat(res.data.content);
} else {
this.dataList = res.data.content;
}
this.loadMoreText = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
setTimeout(() => {
this.loading = 'success';
}, 500);
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
},
//跳转文章详情
fnToArticleDetail(article) {
uni.navigateTo({
url: '/pagesA/article-detail/article-detail?articleId=' + article.id,
animationType: 'slide-in-right'
});
}
}
export default {
components: {
tmSkeleton,
tmTranslate,
tmFlotbutton,
tmEmpty,
ArticleCard
},
data() {
return {
loading: 'loading',
queryParams: {
size: 10,
page: 0
},
name: '',
pageTitle: '加载中...',
hasNext: false,
dataList: [],
isLoadMore: false,
loadMoreText: ''
};
},
onLoad(e) {
this.name = e.name;
this.pageTitle = e.title;
this.fnGetData();
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
},
onReachBottom(e) {
if (this.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnGetData() {
// uni.showLoading({
// mask: true,
// title: '加载中...'
// });
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi.v2
.getCategoryPostList(this.name, this.queryParams)
.then(res => {
console.log("请求成功:", res)
this.fnSetPageTitle(`${this.pageTitle} (共${res.total}篇)`);
this.hasNext = res.hasNext;
if (this.isLoadMore) {
this.dataList = this.dataList.concat(res.items);
} else {
this.dataList = res.items;
}
this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
setTimeout(() => {
this.loading = 'success';
}, 500);
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
},
//跳转文章详情
fnToArticleDetail(article) {
uni.navigateTo({
url: '/pagesA/article-detail/article-detail?name=' + article.metadata.name,
animationType: 'slide-in-right'
});
}
}
};
</script>
<style lang="scss">
.app-page {
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
padding: 24rpx 0;
background-color: #fafafd;
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
padding: 24rpx 0;
background-color: #fafafd;
}
.loading-wrap {
padding: 0 24rpx;
min-height: 100vh;
padding: 0 24rpx;
min-height: 100vh;
}
.empty {
height: 60vh;
display: flex;
align-items: center;
justify-content: center;
height: 60vh;
display: flex;
align-items: center;
justify-content: center;
}
</style>
-163
View File
@@ -1,163 +0,0 @@
<template>
<view class="app-page pa-6">
<view class="content pt-24 pb-24 round-4">
<!-- 表单区域 -->
<tm-form @submit="fnOnSubmit">
<tm-input
:auto-focus="true"
name="content"
:vertical="true"
required
:height="220"
input-type="textarea"
bg-color="grey-lighten-5"
:maxlength="200"
:borderBottom="false"
placeholder="请输入内容,不超过200字符..."
v-model="form.content"
></tm-input>
<tm-input name="author" align="right" required title="我的昵称" placeholder="请输入您的昵称..." v-model="form.author"></tm-input>
<tm-input name="email" align="right" title="邮箱地址" placeholder="请输入您的邮箱..." v-model="form.email"></tm-input>
<tm-input name="authorUrl" align="right" title="我的网站" placeholder="请输入您的网址..." v-model="form.authorUrl"></tm-input>
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-n ">接收提醒</text>
<view><tm-switch :text="['是', '否']" v-model="form.allowNotification" color="bg-gradient-blue-accent"></tm-switch></view>
</view>
<view class="pa-24 pl-30 pr-30"><tm-button navtie-type="form" theme="bg-gradient-blue-accent" block>提交</tm-button></view>
</tm-form>
</view>
</view>
</template>
<script>
import tmForm from '@/tm-vuetify/components/tm-form/tm-form.vue';
import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
import tmSwitch from '@/tm-vuetify/components/tm-switch/tm-switch.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
export default {
components: {
tmForm,
tmInput,
tmSwitch,
tmButton
},
data() {
return {
params: {
postId: '',
parentId: '',
title: '', // 被回复的标题 type=user =用户名 否则为文章标题
form: '',
formPage: '', // 来自哪个页面
type: 'post' // 来源文章/页面 还是用户 user=用户
},
form: {
allowNotification: true,
author: '', // 作者
avatar: '',
authorUrl: '', // 作者主页
content: '', // 评论内容
email: '', // 邮件
parentId: 0,
postId: 0
}
};
},
computed: {
// 评论游客信息
wxLoginVisitorUser() {
return uni.$tm.vx.getters().user.getWxLoginInfo;
}
},
onLoad() {
this.params = this.$Route.query;
this.form.postId = this.params.id;
if (this.params.type == 'user') {
this.form.parentId = this.params.parentId;
this.fnSetPageTitle('回复用户:' + this.params.title);
} else {
this.form.parentId = 0;
this.fnSetPageTitle(this.params.title);
}
this.form.author = this.wxLoginVisitorUser.nickName;
this.form.avatar = this.wxLoginVisitorUser.avatarUrl;
this.form.email = this.wxLoginVisitorUser.email;
this.form.authorUrl = this.wxLoginVisitorUser.url;
},
methods: {
fnOnSubmit(e) {
if (e === false) {
return uni.$tm.toast('请检查所有的必填项是否填写完整!');
}
if (this.form.allowNotification && !this.form.email) {
return uni.$tm.toast('未填写邮箱地址,将无法接收提醒!');
}
if (this.form.email && !uni.$tm.test.email(this.form.email)) {
return uni.$tm.toast('请填写正确的邮箱地址!');
}
if (this.form.authorUrl && !uni.$tm.test.url(this.form.authorUrl)) {
return uni.$tm.toast('请输入正确的Url地址!');
}
this.fnHandle();
},
fnHandle() {
uni.showLoading({
title: '正在提交...'
});
const _api = {
sheets: this.$httpApi.postSheetsComments,
posts: this.$httpApi.postCommentPost
};
_api[this.params.from](this.form)
.then(res => {
if (res.status == 200) {
uni.$tm.toast('提交成功,待博主审核通过后即可展示!');
// 更新评论者信息
uni.$tm.vx.commit('user/setWxLoginInfo', {
avatarUrl: this.wxLoginVisitorUser.avatarUrl,
nickName: this.form.author,
email: this.form.email,
url: this.form.authorUrl
});
// 清空评论内容
this.form.content = '';
// 触发刷新评论(可能需要评论审核不会有改变)
// uni.$emit(this.params.formPage + '_refresh');
} else {
uni.$tm.toast('操作失败,请重试!');
}
})
.catch(err => {
uni.$tm.toast(err.message);
});
}
}
};
</script>
<style scoped lang="scss">
.app-page {
width: 100vw;
min-height: 100vh;
box-sizing: border-box;
// background-color: #fafafd;
background-color: #ffffff;
.content {
overflow: hidden;
background-color: #fff;
// box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.05);
}
}
.poup-content {
width: 500rpx;
::v-deep {
.slider_id {
width: 100% !important;
}
}
}
</style>
+261 -250
View File
@@ -1,268 +1,279 @@
<template>
<view class="app-page flex flex-col felx-center bg-white">
<!-- 信息 -->
<view class="profile flex flex-col flex-center pa-36">
<view class="avatar">
<text v-if="false" class="icon flex flex-center"><text class="iconfont icon-check text-size-s"></text></text>
<image class="avatar-img" :src="bloggerInfo.avatar" mode="aspectFill"></image>
</view>
<view class="nickname mt-24 text-weight-b text-size-g">{{ bloggerInfo.nickname }}</view>
<view class="desc mt-24 text-size-m text-grey-darken-3">{{ bloggerInfo.description }}</view>
<!-- 联系图标 -->
<view class="mt-24 contact-icons">
<!-- 放全部似乎显得有点拥挤 -->
<!-- <block v-for="(item, index) in result" :key="index"><text v-if="item.value" class="halocoloricon" :class="[item.icon, { 'ml-12': index != 0 }]"></text></block> -->
<!-- 考虑放几个常用的就行 -->
<text class="halocoloricon halocoloricon-qq"></text>
<text class="ml-12 halocoloricon halocoloricon-wechat"></text>
<text class="ml-12 halocoloricon halocoloricon-GitHub"></text>
<text class="ml-12 halocoloricon halocoloricon-gitee"></text>
<text class="ml-12 halocoloricon halocoloricon-ic_email_round"></text>
</view>
<view class="mt-24 ">
<tm-button theme="bg-gradient-light-blue-accent" size="m" openType="contact">在线客服联系</tm-button>
<tm-button theme="bg-gradient-orange-accent" size="m" @click="fnOnToWeb">访问PC端博客</tm-button>
</view>
</view>
<!-- 社交联系方式列表 -->
<view class="contact ma-50 mt-0 pt-12">
<block v-for="(item, index) in result" :key="index">
<view v-if="item.value" class="item mt-24 flex" @click="fnOnClick(item)">
<view class="left">
<text class="icon halocoloricon" :class="[item.icon]"></text>
<text class="name">{{ item.name }}</text>
</view>
<view class="right flex-1">{{ item.value }}</view>
</view>
</block>
</view>
</view>
<view class="app-page flex flex-col felx-center bg-white">
<!-- 信息 -->
<view class="profile flex flex-col flex-center pa-36">
<view class="avatar">
<image class="avatar-img" :src="bloggerInfo.avatar" mode="aspectFill"></image>
</view>
<view class="nickname mt-24 text-weight-b text-size-g">{{ bloggerInfo.nickname }}</view>
<view class="desc mt-24 text-size-m text-grey-darken-3">
{{ bloggerInfo.description || '这个博主很懒,竟然没写介绍~' }}
</view>
<!-- 联系图标 -->
<view class="mt-24 contact-icons">
<!-- 放全部似乎显得有点拥挤 -->
<!-- <block v-for="(item, index) in result" :key="index"><text v-if="item.value" class="halocoloricon" :class="[item.icon, { 'ml-12': index != 0 }]"></text></block> -->
<!-- 考虑放几个常用的就行 -->
<text class="halocoloricon halocoloricon-qq"></text>
<!-- #ifndef MP-QQ -->
<text class="ml-12 halocoloricon halocoloricon-wechat"></text>
<!-- #endif -->
<text class="ml-12 halocoloricon halocoloricon-GitHub"></text>
<text class="ml-12 halocoloricon halocoloricon-gitee"></text>
<text class="ml-12 halocoloricon halocoloricon-ic_email_round"></text>
</view>
</view>
<!-- 社交联系方式列表 -->
<view class="contact ma-50 mt-0 pt-12">
<block v-if="calcIsNotEmpty">
<block v-for="(item, index) in result" :key="index">
<view v-if="item.value" class="item mt-24 flex" @click="fnOnClick(item)">
<view class="left">
<text class="icon halocoloricon" :class="[item.icon]"></text>
<text class="name">{{ item.name }}</text>
</view>
<view class="right flex-1">{{ item.value }}</view>
</view>
</block>
</block>
<view v-else class="mt-12 pt-12">
<tm-empty icon="icon-shiliangzhinengduixiang-" label="暂无联系方式"/>
</view>
</view>
</view>
</template>
<script>
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
export default {
components: {
tmSkeleton,
tmButton
},
data() {
return {
loading: 'loading',
queryParams: {
size: 10,
page: 0
},
result: [
{
key: 'qq',
icon: 'halocoloricon-qq',
name: '企鹅号',
value: ''
},
{
key: 'wechat',
icon: 'halocoloricon-wechat',
name: '微信号',
value: ''
},
{
key: 'github',
icon: 'halocoloricon-GitHub',
name: 'Github',
value: ''
},
{
key: 'gitee',
icon: 'halocoloricon-gitee',
name: 'Gitee',
value: ''
},
{
key: 'bilibili',
icon: 'halocoloricon-bilibili',
name: 'Bilibili',
value: ''
},
{
key: 'csdn',
icon: 'halocoloricon-csdn1',
name: 'CSDN',
value: ''
},
{
key: 'blog',
icon: 'halocoloricon-url-monitor',
name: '博客地址',
value: ''
},
{
key: 'juejin',
icon: 'halocoloricon-juejin',
name: '掘金地址',
value: ''
},
components: {
tmSkeleton,
tmButton,
tmEmpty
},
data() {
return {
loading: 'loading',
queryParams: {
size: 10,
page: 0
},
result: [
{
key: 'qq',
icon: 'halocoloricon-qq',
name: '企鹅号',
value: ''
},
{
key: 'wechat',
icon: 'halocoloricon-wechat',
name: '微信号',
value: ''
},
{
key: 'github',
icon: 'halocoloricon-GitHub',
name: 'Github',
value: ''
},
{
key: 'gitee',
icon: 'halocoloricon-gitee',
name: 'Gitee',
value: ''
},
{
key: 'bilibili',
icon: 'halocoloricon-bilibili',
name: 'Bilibili',
value: ''
},
{
key: 'csdn',
icon: 'halocoloricon-csdn1',
name: 'CSDN',
value: ''
},
{
key: 'blog',
icon: 'halocoloricon-url-monitor',
name: '博客地址',
value: ''
},
{
key: 'juejin',
icon: 'halocoloricon-juejin',
name: '掘金地址',
value: ''
},
{
key: 'weibo',
icon: 'halocoloricon-social-weibo',
name: '微博地址',
value: ''
},
{
key: 'email',
icon: 'halocoloricon-ic_email_round',
name: '邮箱地址',
value: ''
}
]
};
},
computed: {
// 获取博主信息
bloggerInfo() {
let blogger = this.$tm.vx.getters().blogger.getBlogger;
blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
return blogger;
}
},
onLoad() {
this.fnSetPageTitle('❤');
},
created() {
this.fnGetData();
},
methods: {
fnGetData() {
for (let key in this.$haloConfig.social) {
this.result.find(x => x.key == key).value = this.$haloConfig.social[key];
}
},
fnOnToWeb() {
uni.navigateTo({
url:
'/pagesC/website/website?data=' +
JSON.stringify({
title: this.$haloConfig.title,
url: this.$haloConfig.social.blog
})
});
},
fnOnClick(item) {
if (this.globalAppSettings.contact.isLinkCopy && this.$utils.checkIsUrl(item.value)) {
uni.navigateTo({
url:
'/pagesC/website/website?data=' +
JSON.stringify({
title: item.name,
url: item.value
})
});
} else {
this.$utils.copyText(item.value, item.name + ' 已复制!');
}
}
}
{
key: 'weibo',
icon: 'halocoloricon-social-weibo',
name: '微博地址',
value: ''
},
{
key: 'email',
icon: 'halocoloricon-ic_email_round',
name: '邮箱地址',
value: ''
}
]
};
},
computed: {
authorConfig() {
return this.$tm.vx.getters().getConfigs.authorConfig;
},
bloggerInfo() {
let blogger = this.authorConfig.blogger;
blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
return blogger;
},
socialConfig() {
return this.authorConfig.social;
},
calcIsNotEmpty() {
return this.result.some((item) => item.value !== "");
}
},
watch: {
socialConfig: {
handler(newVal) {
if (!newVal) return;
this.fnGetData();
},
deep: true,
immediate: true
}
},
onLoad() {
this.fnSetPageTitle('联系博主');
},
methods: {
fnGetData() {
for (let key in this.socialConfig) {
if (key === 'enabled') {
continue;
}
this.result.find(x => x.key === key).value = this.socialConfig[key];
}
},
fnOnClick(item) {
this.$utils.copyText(item.value, item.name + ' 已复制!');
// if (this.globalAppSettings.contact.isLinkCopy && this.$utils.checkIsUrl(item.value)) {
// uni.navigateTo({
// url: '/pagesC/website/website?data=' +
// JSON.stringify({
// title: item.name,
// url: item.value
// })
// });
// } else {
// this.$utils.copyText(item.value, item.name + ' 已复制!');
// }
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
min-height: 100vh;
box-sizing: border-box;
padding-top: 160rpx;
width: 100vw;
min-height: 100vh;
box-sizing: border-box;
padding-top: 160rpx;
}
.profile {
.avatar {
width: 170rpx;
height: 170rpx;
position: relative;
box-sizing: border-box;
border-radius: 50%;
border: 6rpx solid #fff;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.07);
overflow: hidden;
.icon {
width: 42rpx;
height: 42rpx;
position: absolute;
right: 6rpx;
bottom: 0rpx;
border-radius: 50%;
color: #fff;
box-sizing: border-box;
background-color: #03b0fd;
border: 4rpx solid #fff;
z-index: 2;
}
&-img {
width: 100%;
height: 100%;
}
}
.nickname {
font-size: 38rpx;
}
}
.contact-icons {
.halocoloricon {
font-size: 42rpx;
}
}
.contact {
box-sizing: border-box;
border-top: 2rpx solid #f2f2f2;
.item {
box-sizing: border-box;
border-radius: 12rpx;
background-color: #fafafa;
// background-color: rgba(0, 0, 0, 0.05);
// padding-bottom: 12rpx;
// border-bottom: 2rpx solid #eee;
padding: 16rpx;
.left {
position: relative;
width: 140rpx;
display: flex;
align-items: center;
box-sizing: border-box;
// background-color: rgba(0, 0, 0, 0.03);
border-radius: 12rpx 0 0 12rpx;
&::after {
display: none;
content: ':';
position: absolute;
right: 18rpx;
top: 50%;
transform: translateY(-50%);
font-size: 24rpx;
color: #666;
}
.icon {
font-size: 24rpx;
}
.name {
margin-left: 10rpx;
color: #555;
font-size: 24rpx;
}
}
.right {
width: 0;
display: flex;
align-items: center;
flex-wrap: wrap;
box-sizing: border-box;
word-wrap: break-word;
word-break: break-all;
font-size: 24rpx;
box-sizing: border-box;
padding-left: 12rpx;
}
}
.profile {
.avatar {
width: 170rpx;
height: 170rpx;
position: relative;
box-sizing: border-box;
border-radius: 50%;
border: 6rpx solid #fff;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.07);
overflow: hidden;
&-img {
width: 100%;
height: 100%;
}
}
.nickname {
font-size: 38rpx;
}
}
.contact-icons {
.halocoloricon {
font-size: 42rpx;
}
}
.contact {
box-sizing: border-box;
border-top: 2rpx solid #f2f2f2;
.item {
box-sizing: border-box;
border-radius: 12rpx;
background-color: #fafafa;
// background-color: rgba(0, 0, 0, 0.05);
// padding-bottom: 12rpx;
// border-bottom: 2rpx solid #eee;
padding: 16rpx;
.left {
position: relative;
width: 140rpx;
display: flex;
align-items: center;
box-sizing: border-box;
// background-color: rgba(0, 0, 0, 0.03);
border-radius: 12rpx 0 0 12rpx;
&::after {
display: none;
content: ':';
position: absolute;
right: 18rpx;
top: 50%;
transform: translateY(-50%);
font-size: 24rpx;
color: #666;
}
.icon {
font-size: 24rpx;
}
.name {
margin-left: 10rpx;
color: #555;
font-size: 24rpx;
}
}
.right {
width: 0;
display: flex;
align-items: center;
flex-wrap: wrap;
box-sizing: border-box;
word-wrap: break-word;
word-break: break-all;
font-size: 24rpx;
box-sizing: border-box;
padding-left: 12rpx;
}
}
}
</style>
+47 -30
View File
@@ -1,42 +1,59 @@
<template>
<view class="app-page bg-white round-3 pa-46">
<view class="text-weight-b text-size-lg text-align-center"> 本博客免责声明 </view>
<view v-if="false" class="text-size-s text-align-center mt-12 text-grey-darken-1"> 博主{{ bloggerInfo.nickname }} </view>
<view class="mt-32">1本博客属于个人非赢利性质的网站所有转载的文章都以遵循原作者的版权声明注明了文章来源</view>
<view class="mt-24">2如果原文没有版权声明按照目前互联网开放的原则本博客将在不通知作者的情况下转载文章</view>
<view class="mt-24">3如果原文明确注明禁止转载本博客将不会转载</view>
<view class="mt-24">
4如果本博客转载的文章不符合作者的版权声明或者作者不想让本博客转载您的文章请邮件告知
<text class="text-bg-gradient-light-blue-accent ml-12 mr-12 " @click="$utils.copyText(bloggerInfo.email, '邮箱复制成功!')">
<text class="iconfont icon-mail-fill text-size-m mr-6"></text>
{{ bloggerInfo.email }}
</text>
博主将会在第一时间删除相关信息
</view>
<view class="mt-24">5本博客转载文章仅为留作备份和知识点分享的目的</view>
<view class="mt-24">6本博客将尽力确保所提供信息的准确性及可靠性但不保证信息的正确性和完整性且不对因信息的不正确或遗漏导致的任何损失或损害承担相关责任</view>
<view class="mt-24">
7本博客所发布转载的文章其版权均归原作者所有如其他自媒体网站或个人从本博客下载使用请在转载有关文章时务必尊重该文章的著作权保留本博客注明的原文来源或者自行去原文处复制版权声明并自负版权等法律责任
</view>
<view class="mt-24">8本博客的所有原创文章皆可以任意转载但转载时务必请注明出处</view>
<view class="mt-24">9尊重原创知识共享</view>
</view>
<view class="app-page bg-white round-3 pa-46">
<!-- 通过配置 -->
<view v-if="disclaimersContent" style="min-height: 100%;" v-html="disclaimersContent"></view>
<!-- 静态写法 -->
<block v-else>
<view class="text-weight-b text-size-lg text-align-center"> 本博客免责声明 </view>
<view v-if="false" class="text-size-s text-align-center mt-12 text-grey-darken-1"> 博主{{ bloggerInfo.nickname }} </view>
<view class="mt-32">1本博客属于个人非赢利性质的网站所有转载的文章都以遵循原作者的版权声明注明了文章来源</view>
<view class="mt-24">2如果原文没有版权声明按照目前互联网开放的原则本博客将在不通知作者的情况下转载文章</view>
<view class="mt-24">3如果原文明确注明禁止转载本博客将不会转载</view>
<view class="mt-24">
4如果本博客转载的文章不符合作者的版权声明或者作者不想让本博客转载您的文章请邮件告知
<text class="text-bg-gradient-light-blue-accent ml-12 mr-12 ">
<text class="iconfont icon-mail-fill text-size-m mr-6"></text>
{{ bloggerInfo.email }}
</text>
博主将会在第一时间删除相关信息
</view>
<view class="mt-24">5本博客转载文章仅为留作备份和知识点分享的目的</view>
<view class="mt-24">
6本博客将尽力确保所提供信息的准确性及可靠性但不保证信息的正确性和完整性且不对因信息的不正确或遗漏导致的任何损失或损害承担相关责任
</view>
<view class="mt-24">
7本博客所发布转载的文章其版权均归原作者所有如其他自媒体网站或个人从本博客下载使用请在转载有关文章时务必尊重该文章的著作权保留本博客注明的原文来源或者自行去原文处复制版权声明并自负版权等法律责任
</view>
<view class="mt-24">8本博客的所有原创文章皆可以任意转载但转载时务必请注明出处</view>
<view class="mt-24">9尊重原创知识共享</view>
</block>
</view>
</template>
<script>
export default {
computed: {
bloggerInfo() {
return this.$tm.vx.getters().blogger.getBlogger;
}
}
computed: {
haloConfigs() {
return this.$tm.vx.getters().getConfigs
},
disclaimersContent() {
const basicConfig = this.haloConfigs.basicConfig;
return basicConfig?.disclaimers.content
},
bloggerInfo() {
const blogger = this.haloConfigs.authorConfig.blogger;
blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar)
return blogger;
},
}
};
</script>
<style scoped lang="scss">
.app-page {
line-height: 1.65;
font-size: 30rpx;
color: var(--main-text-color);
line-height: 1.65;
font-size: 30rpx;
color: var(--main-text-color);
}
</style>
+428
View File
@@ -0,0 +1,428 @@
<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">
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.submitLink.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>
</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;
};
}
},
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.haloConfigs.basicConfig.auditModeEnabled) {
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>
-133
View File
@@ -1,133 +0,0 @@
<template>
<view class="app-page">
<view v-if="loading != 'success'" class="loading-wrap pa-24">
<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 pa-24">
<view v-if="dataList.length == 0" class="content-empty flex flex-center">
<!-- 空布局 -->
<tm-empty icon="icon-shiliangzhinengduixiang-" label="博主还没有写任何日记"></tm-empty>
</view>
<block v-else>
<block v-for="(journal, index) in dataList" :key="index">
<tm-translate animation-name="fadeUp" :wait="(index + 1) * 50">
<!-- 日记卡片 -->
<journal-card :journal="journal" :useLike="true"></journal-card>
</tm-translate>
</block>
<tm-flotbutton @click="fnToTopPage" color="light-blue" size="m" icon="icon-angle-up"></tm-flotbutton>
<view class="load-text">{{ loadMoreText }}</view>
</block>
</view>
</view>
</template>
<script>
import MarkdownConfig from '@/common/markdown/markdown.config.js';
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
import journalCard from '@/components/journal-card/journal-card.vue';
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
export default {
components: { mpHtml, tmSkeleton, tmEmpty, tmTranslate, tmFlotbutton, journalCard },
data() {
return {
loading: 'loading',
markdownConfig: MarkdownConfig,
queryParams: {
size: 10,
page: 0
},
result: null,
dataList: [],
isLoadMore: false,
loadMoreText: '加载中...'
};
},
onLoad() {
this.fnSetPageTitle('个人日记');
},
created() {
this.fnGetData();
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
},
onReachBottom(e) {
if (this.result.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnGetData() {
uni.showLoading({
mask: true,
title: '加载中...'
});
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi
.getJournals(this.queryParams)
.then(res => {
this.loading = 'success';
this.loadMoreText = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
// 处理数据
this.result = res.data;
if (this.isLoadMore) {
this.dataList = this.dataList.concat(res.data.content);
} else {
this.dataList = res.data.content;
}
this.loading = 'success';
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
display: flex;
flex-direction: column;
}
.loading-wrap {
}
.app-page-content {
}
.content-empty {
height: 60vh;
}
</style>
-263
View File
@@ -1,263 +0,0 @@
<template>
<view class="app-page">
<view v-if="loading != 'success'" class="loading-wrap pa-24">
<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 bg-white pa-24">
<view v-if="dataList.length == 0" class="content-empty flex flex-center">
<!-- 空布局 -->
<tm-empty icon="icon-shiliangzhinengduixiang-" label="该分类下暂无数据"></tm-empty>
</view>
<block v-else>
<block v-for="(item, index) in dataList" :key="index">
<tm-translate animation-name="fadeUp" :wait="(index + 1) * 50">
<!-- 列表项 -->
<comment-item
class="mb-12"
:isChild="false"
:comment="item"
:postId="sheetId"
:useSolid="false"
@on-copy="fnCopyContent"
@on-comment="fnToComment"
@on-detail="fnOnShowCommentDetail"
></comment-item>
</tm-translate>
</block>
<tm-flotbutton :offset="[16, 80]" @click="fnToTopPage" size="m" color="light-blue" icon="icon-angle-up"></tm-flotbutton>
<tm-flotbutton actions-pos="left" :show-text="true" color="bg-gradient-orange-accent" @click="fnToComment(null)"></tm-flotbutton>
<view class="load-text">{{ loadMoreText }}</view>
</block>
</view>
<!-- 评论详情 -->
<tm-poup v-model="commentDetail.show" height="auto" :round="6" :over-close="true" position="bottom">
<view class="pa-24">
<view class="poup-head pb-24">
<view class="poup-title text-align-center text-size-g text-weight-b mb-32">留言详情</view>
<comment-item :useActions="false" :isChild="false" :comment="commentDetail.comment" :postId="sheetId"></comment-item>
</view>
<scroll-view :scroll-y="true" class="poup-body">
<view v-if="commentDetail.loading != 'success'" class="poup-loading-wrap flex flex-center">
<view v-if="commentDetail.loading == 'loading'" class="loading flex flex-center flex-col">
<text class="e-loading-icon iconfont icon-loading text-blue"></text>
<view class="text-size-n text-grey-lighten-1 py-12 mt-12">加载中请稍等...</view>
</view>
<view v-else-if="commentDetail.loading == 'error'" class="error">
<tm-empty icon="icon-wind-cry" label="加载失败">
<tm-button theme="bg-gradient-light-blue-accent" size="m" @click="fnGetChildComments()">刷新试试</tm-button>
</tm-empty>
</view>
</view>
<block v-else>
<view v-if="commentDetail.list.length == 0" class="poup-empty flex flex-center">
<tm-empty icon="icon-shiliangzhinengduixiang-" label="没有更多评论啦~"></tm-empty>
</view>
<block v-else>
<comment-item
v-for="(comment, index) in commentDetail.list"
:useSolid="false"
:useActions="false"
:key="index"
:isChild="false"
:comment="comment"
:postId="sheetId"
></comment-item>
</block>
</block>
</scroll-view>
</view>
</tm-poup>
</view>
</template>
<script>
import AppKeys from '@/config//keys.js';
import SheetConfig from '@/config/sheets.config.js';
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import commentItem from '@/components/comment-item/comment-item.vue';
export default {
components: {
tmSkeleton,
tmFlotbutton,
tmTranslate,
tmEmpty,
tmPoup,
tmButton,
commentItem
},
data() {
return {
loading: 'loading',
queryParams: {
size: 10,
page: 0
},
result: null,
dataList: [],
isLoadMore: false,
loadMoreText: '加载中...',
sheetId: SheetConfig[AppKeys.SHEET_LEAVING],
commentDetail: {
loading: 'loading',
show: false,
comment: {},
postId: undefined,
list: []
}
};
},
onLoad() {
this.fnSetPageTitle('留言板');
},
created() {
this.fnGetData();
uni.$on('leaving_refresh', () => {
this.fnGetData();
});
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
},
onReachBottom(e) {
if (this.result.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnGetData() {
uni.showLoading({
mask: true,
title: '加载中...'
});
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi
.getSheetsCommentsTreeBySheetId(this.sheetId, this.queryParams)
.then(res => {
if (res.status == 200) {
this.loading = 'success';
this.loadMoreText = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
// 处理数据
this.result = res.data;
if (this.isLoadMore) {
this.dataList = this.dataList.concat(res.data.content);
} else {
this.dataList = res.data.content;
}
} else {
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
}
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
},
fnToComment(data) {
let _comment = {};
if (data) {
_comment = {
id: this.sheetId,
parentId: data.comment.id,
title: data.comment.author,
from: 'sheets',
formPage: 'leaving',
type: 'user'
};
} else {
_comment = {
id: this.sheetId,
parentId: 0,
title: '留言板留言',
from: 'sheets',
formPage: 'leaving',
type: 'post'
};
}
uni.$tm.vx.commit('comment/setCommentInfo', _comment);
this.$Router.push({
path: '/pagesA/comment/comment',
query: _comment
});
},
fnCopyContent(content) {
uni.$tm.u.setClipboardData(content);
uni.$tm.toast('内容已复制成功!');
},
fnOnShowCommentDetail(comment) {
this.commentDetail.comment = comment;
this.commentDetail.list = [];
this.commentDetail.show = true;
this.fnGetChildComments();
},
fnGetChildComments() {
this.commentDetail.loading = 'loading';
this.$httpApi
.getSheetsChildrenCommentList(this.sheetId, this.commentDetail.comment.id, {})
.then(res => {
if (res.status == 200) {
this.commentDetail.loading = 'success';
this.commentDetail.list = res.data;
} else {
this.commentDetail.loading = 'error';
}
})
.catch(err => {
this.commentDetail.loading = 'error';
});
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
display: flex;
flex-direction: column;
}
.app-page-content {
box-sizing: border-box;
box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.05);
}
.loading-wrap {
padding: 24rpx;
}
</style>
+383
View File
@@ -0,0 +1,383 @@
<template>
<view class="app-page">
<view v-if="loading !== 'success'" class="loading-wrap">
<view v-if="loading === 'loading'" class="loading">
<view class="loading-icon flex flex-center">
<text class="e-loading-icon iconfont icon-loading"></text>
</view>
<view class="loadig-text ">相册正在努力加载中啦~</view>
</view>
<tm-empty v-else icon="icon-shiliangzhinengduixiang-" color="red" label="啊偶,加载失败了呢~">
<tm-button theme="red" :shadow="0" size="m" @click="fnRefresh()">刷新试试</tm-button>
</tm-empty>
</view>
<!-- 内容区域 -->
<view v-else class="app-page-content">
<view v-if="dataList.length === 0" color="light-blue" class="content-empty flex flex-center">
<tm-empty icon="icon-shiliangzhinengduixiang-" label="相册暂时还没有数据~">
<tm-button :shadow="0" size="m" theme="light-blue" @click="fnRefresh()">刷新试试</tm-button>
</tm-empty>
</view>
<block v-else>
<swiper class="swiper-album" :current="swiperIndex" :acceleration="true" :circular="true"
:vertical="false" :indicator-dots="false" :autoplay="false" @change="fnOnChange">
<block v-for="(item, index) in dataList" :key="index">
<swiper-item class="swiper-album-item">
<view class="scroll-wrap">
<view class="card">
<cache-image class="card-image" width="100%" height="46vh" radius="12rpx"
:url="item.image" :fileMd5="item.image" mode="aspectFill"
@on-click="fnOnPreview(item)"></cache-image>
<view v-if="item.spec.description" class="card-desc">{{ item.spec.description }}
</view>
<view v-else class="card-desc is-empty flex flex-col">
<view class="text-grey-darken-1">该照片没有记录任何信息</view>
<view class="text-size-m mt-24 text-grey-darken-1">记录一下拍照的瞬间会更精彩哟</view>
</view>
</view>
</view>
</swiper-item>
</block>
</swiper>
<view class="tabbar">
<view class="pre" @click="fnChange(false)">
<text class="icon">
<text class="iconfont icon-arrow-left"></text>
</text>
<text class="text">上一张</text>
</view>
<view class="refresh" @click="fnRefresh()">
<text class="refresh-text">点击</text>
<text class="refresh-heart iconfont icon-diagnose"></text>
<text class="refresh-text">刷新</text>
</view>
<view class="next" @click="fnChange(true)">
<text class="text">下一张</text>
<text class="icon">
<text class="iconfont icon-arrow-right"></text>
</text>
</view>
</view>
</block>
</view>
</view>
</template>
<script>
import throttle from '@/utils/throttle.js';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
export default {
components: {
tmButton,
tmFlotbutton,
tmTranslate,
tmEmpty
},
data() {
return {
loading: 'loading',
queryParams: {
size: 100,
page: 1,
group: ""
},
result: {},
dataList: [],
swiperIndex: 0,
tabbar: {
list: []
}
};
},
computed: {
loveConfig() {
return this.$tm.vx.getters().getConfigs.loveConfig;
}
},
watch: {
loveConfig: {
deep: true,
immediate: true,
handler(newVal, oldVal) {
this.fnGetData()
}
}
},
onLoad() {
this.fnSetPageTitle('恋爱相册');
},
onPullDownRefresh() {
this.fnRefresh();
},
methods: {
fnRefresh() {
this.queryParams.page = 0;
this.fnGetData();
},
fnGetData() {
this.loading = 'loading';
this.queryParams.group = this.loveConfig.lovePhoto.groupName
this.$httpApi.v2
.getPhotoListByGroupName(this.queryParams)
.then(res => {
console.log("相册 res", res)
this.loading = 'success';
this.dataList = res.items.map((item, index) => {
item['image'] = this.$utils.checkImageUrl(item.spec.cover || item.spec.url);
item['takeTime'] = this.$tm.dayjs(item.metadata.creationTimestamp).format(
'DD/MM/YYYY');
return item;
});
this.swiperIndex = 0;
})
.catch(err => {
console.error(err);
this.loading = 'error';
uni.$tm.toast('加载失败,请下拉刷新重试!');
})
.finally(() => {
setTimeout(() => {
uni.stopPullDownRefresh();
}, 200);
});
},
fnOnPreview(item) {
uni.previewImage({
current: item.image,
urls: this.dataList.map(x => x.image)
});
},
fnOnChange(e) {
this.swiperIndex = e.detail.current;
},
fnChange(isNext) {
throttle(() => {
if (isNext) {
if (this.swiperIndex === this.dataList.length - 1) {
this.swiperIndex = 0;
} else {
this.swiperIndex += 1;
}
} else {
if (this.swiperIndex === 0) {
this.swiperIndex = this.dataList.length - 1;
} else {
this.swiperIndex -= 1;
}
}
});
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 24rpx 0;
padding-bottom: 144rpx;
background: linear-gradient(-135deg,
rgba(247, 149, 51, 0.1),
rgba(243, 112, 85, 0.1) 15%,
rgba(239, 78, 123, 0.1) 30%,
rgba(161, 102, 171, 0.1) 44%,
rgba(80, 115, 184, 0.1) 58%,
rgba(16, 152, 173, 0.1) 72%,
rgba(7, 179, 155, 0.1) 86%,
rgba(109, 186, 130, 0.1));
}
.app-page-content {
}
.loading-wrap {
width: 100vw;
height: 60vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 36rpx;
::v-deep {
.tm-icons {
margin-right: -20rpx;
}
}
}
.e-loading-icon {
font-size: 120rpx;
// color: #f88ca2;
color: #56bbf9;
}
.loadig-text {
margin-top: 28rpx;
font-size: 28rpx;
// color: #f88ca2;
color: #56bbf9;
}
.content-empty {
width: 100%;
height: 60vh;
}
.swiper-album {
width: 100vw;
height: calc(100vh - 24rpx - 144rpx);
}
.swiper-album-item {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 36rpx;
/* #ifdef H5 */
padding-bottom: 110rpx;
/* #endif */
/* #ifndef H5 */
padding-bottom: 60rpx;
/* #endif */
}
.scroll-wrap {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 36rpx;
background-color: #ffffff;
border-radius: 12rpx;
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.03);
}
.card {
display: flex;
flex-direction: column;
width: 100%;
// height: 65vh;
height: 100%;
max-height: 100%;
border-radius: 12rpx;
background-color: #ffffff;
box-sizing: border-box;
// box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.03);
overflow: hidden;
overflow-y: auto;
::v-deep {
.cache-image {
height: initial !important;
}
}
&-image {
width: 100%;
height: initial !important;
border-radius: 12rpx;
}
&-desc {
margin-top: 24rpx;
line-height: 1.6;
font-size: 30rpx;
color: rgba(26, 26, 26, 0.9);
&.is-empty {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
font-size: 32rpx;
}
}
}
.tabbar {
width: 90vw;
position: fixed;
left: 50%;
bottom: 40rpx;
transform: translateX(-50%);
border-radius: 25rpx;
box-sizing: border-box;
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
// background-color: rgba(0, 0, 0, 0.5);
background-color: #ffffff;
color: #333;
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.05);
.refresh {
animation: refreshAni 6s ease-in-out infinite;
color: #56bbf9;
&-heart {
font-size: 42rpx;
color: inherit;
margin: 0 6rpx;
}
&-text {
font-size: 24rpx;
}
}
.pre {
color: #56bbf9;
transition: transform 0.1s ease-in-out;
&:hover {
transform: scale(1.05);
}
.text {
padding-left: 12rpx;
}
}
.next {
color: #f88ca2;
transition: transform 0.1s ease-in-out;
&:hover {
transform: scale(1.03);
}
.text {
padding-right: 12rpx;
}
}
}
@keyframes refreshAni {
0% {
color: #f88ca2;
}
50% {
color: #56bbf9;
}
100% {
color: #f88ca2;
}
}
</style>
+106
View File
@@ -0,0 +1,106 @@
<template>
<view class="app-page">
<view class="page-title">我们的故事</view>
<view class="html-typed" v-html="html">内容渲染</view>
<scroll-btn bottom="160rpx" :scrollTop.sync="scrollTop"></scroll-btn>
</view>
</template>
<script>
import ScrollBtn from '@/components/scroll-btn/scroll-btn.vue';
export default {
components: {
ScrollBtn
},
data() {
return {
scrollTop: 0,
html: '',
timer: null
};
},
computed: {
loveConfig() {
return this.$tm.vx.getters().getConfigs.loveConfig;
}
},
watch: {
loveConfig: {
deep: true,
immediate: true,
handler(newVal, oldVal) {
if (!newVal) return;
this.fnInit();
}
}
},
onPageScroll(e) {
this.scrollTop = e.scrollTop;
},
onBackPress() {
clearTimeout(this.timer);
},
methods: {
fnInit() {
clearTimeout(this.timer);
const _html = this.loveConfig.ourStory.content;
let _index = 0;
const _typing = () => {
this.timer = setTimeout(() => {
if (_index >= _html.length) {
clearTimeout(this.timer);
} else {
this.html += _html.substring(_index, _index + 1);
_index += 1;
_typing();
}
}, 100);
};
_typing();
}
}
};
</script>
<style scoped lang="scss">
.app-page {
width: 100vw;
min-height: 100vh;
box-sizing: border-box;
padding: 36rpx;
/* #ifdef APP-PLUS */
padding-top: 100rpx;
/* #endif */
/* #ifdef H5 */
padding-top: 80rpx;
/* #endif */
/* #ifdef MP-WEIXIN */
padding-top: 120rpx;
/* #endif */
background: linear-gradient(-45deg,
rgba(247, 149, 51, 0.1),
rgba(243, 112, 85, 0.1) 15%,
rgba(239, 78, 123, 0.1) 30%,
rgba(161, 102, 171, 0.1) 44%,
rgba(80, 115, 184, 0.1) 58%,
rgba(16, 152, 173, 0.1) 72%,
rgba(7, 179, 155, 0.1) 86%,
rgba(109, 186, 130, 0.1));
color: rgba(26, 26, 26, 1);
}
.page-title {
font-size: 42rpx;
font-weight: bold;
text-align: center;
text-shadow: 0rpx 4rpx 24rpx #bfe9ef;
}
.html-typed {
margin-top: 52rpx;
line-height: 1.8;
font-size: 30rpx;
}
</style>
+430
View File
@@ -0,0 +1,430 @@
<template>
<view class="app-page" @touchstart="fnOnTouchstart" @touchend="fnOnTouchend" @touchcancel="fnOnTouchend">
<view class="love-card" :class="{ ani: isDoAni }">
<view class="head">
<image class="avatar" :src="$utils.checkAvatarUrl(loveConfig.loveInfo.boyAvatar)" mode="scaleToFill"></image>
<view class="love-days">
<view class="tip-text">相恋</view>
<view class="number">
<text class="boy">-</text>
<text class="days">{{ calcLoveDays }}</text>
<text class="girl">-</text>
</view>
<view class="tip-text"></view>
</view>
<image class="avatar" :src="$utils.checkAvatarUrl(loveConfig.loveInfo.girlAvatar)" mode="scaleToFill"></image>
</view>
<view class="foot">
<view class="text" v-if="false">
我们已经相恋
<text class="number">- {{ calcLoveDays }} -</text>
天啦
</view>
看看我们的恋爱清单都完成了哪些吧
</view>
</view>
<view v-if="list.length == 0" class="list empty">
<view class="card">
<image class="empty-image" :src="$utils.checkImageUrl(loveConfig.pageImages.heartImageUrl)" mode="scaleToFill"></image>
<view class="empty-text">暂时还没有恋爱清单快去制定你们的恋爱清单吧~</view>
</view>
</view>
<view v-else class="list">
<block v-for="(item, index) in list" :key="index">
<view class="card" :class="{ ani: isDoAni }" :style="{ '--delay': calcCardDelay(index) }">
<view class="head">
<view class="status">
<view v-if="item.status==='wait'" class="text">未开始</view>
<view v-else-if="item.status==='doing'" class="text">进行中</view>
<view v-else-if="item.status==='complete'" class="text finish">已完成</view>
</view>
<view class="title">
<view class="title-name">{{ item.title }}</view>
</view>
<view class="actions" @click="fnOnItemOpen(item)">
<text class="icon">{{ item.open ? '-' : '+' }}</text>
</view>
</view>
<view v-if="item.open && item.content" class="body">
<view v-if="item.content" class="desc">
<view class="desc-label">事件内容</view>
<view class="desc-value">{{ item.content || "-" }}</view>
</view>
<view v-if="item.status==='complete' && item.completeDate" class="desc">
<view class="desc-label">完成时间</view>
<view class="desc-value">{{ item.completeDate || '-' }}</view>
</view>
</view>
</view>
</block>
</view>
<scroll-btn :scrollTop.sync="scrollTop" @on-status="fnOnScrollStatus"></scroll-btn>
</view>
</template>
<script>
import ScrollBtn from '@/components/scroll-btn/scroll-btn.vue';
export default {
components: {
ScrollBtn
},
data() {
return {
isDoAni: true,
scrollTop: 0,
list: []
};
},
computed: {
loveConfig() {
return this.$tm.vx.getters().getConfigs.loveConfig;
},
calcLoveDays() {
const formatStartDate = this.loveConfig.loveDate.replace(/-/g, '/');
const start = new Date(formatStartDate),
now = new Date();
const T = now.getTime() - start.getTime();
const i = 24 * 60 * 60 * 1000;
const d = T / i;
const D = Math.floor(d);
return D;
},
calcCardDelay() {
return index => {
return Math.random() * index + 1 + 's';
};
}
},
watch: {
loveConfig: {
deep: true,
immediate: true,
handler(newVal) {
if (!newVal) return;
this.fnGetList();
}
}
},
onPageScroll(e) {
this.scrollTop = e.scrollTop;
},
methods: {
fnGetList() {
this.list = this.loveConfig.loveDaily.list.map(item => {
item['open'] = false;
return item;
});
},
fnOnItemOpen(item) {
item.open = !item.open;
this.$forceUpdate();
},
fnOnScrollStatus(isEnd) {
this.isDoAni = isEnd;
},
fnOnTouchstart() {
this.isDoAni = false;
},
fnOnTouchend() {
this.isDoAni = true;
}
}
};
</script>
<style scoped lang="scss">
.app-page {
width: 100vw;
min-height: 100vh;
box-sizing: border-box;
padding: 36rpx;
/* #ifdef H5 */
padding-top: 60rpx;
/* #endif */
/* #ifndef H5 */
padding-top: 180rpx;
/* #endif */
background: linear-gradient(135deg,
rgba(247, 149, 51, 0.1),
rgba(243, 112, 85, 0.1) 15%,
rgba(239, 78, 123, 0.1) 30%,
rgba(161, 102, 171, 0.1) 44%,
rgba(80, 115, 184, 0.1) 58%,
rgba(16, 152, 173, 0.1) 72%,
rgba(7, 179, 155, 0.1) 86%,
rgba(109, 186, 130, 0.1));
}
.love-card {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
padding: 0 24rpx;
padding-top: 66rpx;
padding-bottom: 52rpx;
border-radius: 50rpx;
border: 4rpx solid rgba(96, 77, 68, 0.9);
border-color: #faf8eb;
background-color: rgba(255, 199, 184, 0.9);
margin-bottom: 52rpx;
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.1);
&.ani {
animation: loveCardAni 3s ease-in-out infinite;
}
.head {
display: flex;
.avatar {
width: 150rpx;
height: 150rpx;
box-sizing: border-box;
border-radius: 50%;
border: 6rpx solid rgba(255, 255, 255, 0.7);
&.boy {
border-color: #56bbf9;
}
&.girl {
border-color: #f88ca2;
}
}
.love-days {
margin: 0 12rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 26rpx;
.tip-text {
color: #333;
}
.number {
font-size: 46rpx;
padding: 12rpx 0;
> .boy {
color: #56bbf9;
margin-right: 12rpx;
}
> .girl {
color: #f88ca2;
margin-left: 12rpx;
}
}
.days {
animation: daysAni 6s ease-in-out infinite;
font-weight: bold;
}
}
}
.foot {
display: none;
margin-top: 36rpx;
font-size: 24rpx;
}
}
@keyframes daysAni {
0% {
color: #f88ca2;
}
50% {
color: #56bbf9;
}
100% {
color: #f88ca2;
}
}
@keyframes loveCardAni {
0% {
transform: scale(1);
}
50% {
transform: scale(1.03);
}
100% {
transform: scale(1);
}
}
.list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.empty {
height: calc(100vh - 180rpx - 280rpx - 36rpx);
.card {
height: 100%;
padding: 100rpx;
margin-bottom: 0;
text-align: center;
justify-content: center;
color: rgba(96, 77, 68, 0.9);
}
&-image {
width: 300rpx;
height: 300rpx;
}
&-text {
margin-top: 36rpx;
line-height: 50rpx;
}
}
.card {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
padding: 24rpx;
border-radius: 50rpx;
border: 4rpx solid rgba(96, 77, 68, 0.9);
border-color: #fff;
// background-color: #faf8eb;
background-color: #ffffff;
margin-bottom: 36rpx;
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.05);
animation-delay: var(--delay);
&.ani {
// animation: cardAni 3s ease-in-out infinite;
}
.head {
width: 100%;
display: flex;
align-items: center;
box-sizing: border-box;
.status {
width: 100rpx;
display: flex;
.text {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background-color: #ffc6ba;
font-size: 24rpx;
line-height: 100rpx;
text-align: center;
color: #55423b;
&.finish {
background-color: #bfe9ef;
}
}
}
.title {
width: 0;
flex-grow: 1;
box-sizing: border-box;
padding-left: 30rpx;
padding-right: 24rpx;
&-name {
font-weight: bold;
font-size: 30rpx;
color: #333;
}
&-desc {
margin-top: 8rpx;
font-size: 26rpx;
color: #555;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
.actions {
width: 50rpx;
.icon {
display: inline-block;
width: 45rpx;
height: 45rpx;
background-color: rgba(96, 77, 68, 0.2);
border-radius: 50%;
text-align: center;
line-height: 45rpx;
font-size: 32rpx;
font-weight: bold;
}
}
}
.body {
margin-top: 24rpx;
width: 100%;
background-color: #ffffff;
// background-color: #faf8eb;
background-color: rgba(96, 77, 68, 0.05);
border-radius: 24rpx;
box-sizing: border-box;
padding: 24rpx;
padding-bottom: 12rpx;
font-size: 26rpx;
}
}
@keyframes cardAni {
0% {
transform: translateY(0rpx);
}
50% {
transform: translateY(-10rpx);
}
100% {
transform: translateY(0rpx);
}
}
.desc {
display: flex;
margin-bottom: 12rpx;
&-label {
color: #333;
width: 140rpx;
// font-weight: bold;
}
&-value {
width: 0;
flex-grow: 1;
line-height: 1.5;
color: #333;
}
}
</style>
+406 -10
View File
@@ -1,22 +1,418 @@
<template>
<view class="app-page bg-white flex flex-center"><text class="text-bg-gradient-red-accent-b">祝有情人👩👨终成眷属</text></view>
<view class="app-page bg-white">
<!-- 情侣信息 -->
<view class="lover-wrap" :style="[loveWrapStyle]">
<view class="lover-card">
<view class="boy">
<image class="avatar" :src="$utils.checkAvatarUrl(loveConfig.loveInfo.boyAvatar)" mode="aspectFit"></image>
<view class="name">{{ loveConfig.loveInfo.boyNickname }}</view>
</view>
<image class="like" :src="$utils.checkImageUrl(loveConfig.pageImages.heartImageUrl)" mode="scaleToFill"></image>
<view class="girl">
<image class="avatar" :src="$utils.checkAvatarUrl(loveConfig.loveInfo.girlAvatar)" mode="aspectFit"></image>
<view class="name">{{ loveConfig.loveInfo.girlNickname }}</view>
</view>
</view>
<image class="wave-image" :src="$utils.checkImageUrl(loveConfig.pageImages.waveImageUrl)" mode="scaleToFill"></image>
</view>
<!-- 恋爱记时 -->
<view class="love-time-wrap">
<view class="title">{{ loveConfig.loveDateTitle }}</view>
<view class="content">
<text class="text">
<text class="number">{{ loveDayCount.d }}</text>
</text>
<text class="text">
<text class="number">{{ loveDayCount.h }}</text>
小时
</text>
<text class="text">
<text class="number">{{ loveDayCount.m }}</text>
分钟
</text>
<text class="text">
<text class="number">{{ loveDayCount.s }}</text>
</text>
</view>
</view>
<!-- 功能导航 -->
<view class="list-wrap">
<block v-for="(nav, index) in navList" :key="index">
<view v-if="nav.use" class="list-item" @click="fnToPage(nav.key)">
<view class="left">
<image class="icon" :src="$utils.checkImageUrl(nav.iconImageUrl)" mode="aspectFit"></image>
</view>
<view class="right">
<view class="name">{{ nav.title }}</view>
<view class="desc">{{ nav.desc }}</view>
</view>
</view>
</block>
</view>
</view>
</template>
<script>
export default {
data() {
return {};
},
onLoad() {
this.fnSetPageTitle('恋爱日记');
},
methods: {}
data() {
return {
loveDayTimer: null,
loveDayCount: {
d: 0,
h: 0,
m: 0,
s: 0
},
navList: []
};
},
computed: {
loveConfig() {
return this.$tm.vx.getters().getConfigs.loveConfig;
},
loveWrapStyle() {
return {
backgroundImage: `url(${this.$utils.checkImageUrl(this.loveConfig.pageImages.bgImageUrl)})`
};
}
},
watch: {
loveConfig: {
deep: true,
immediate: true,
handler(newVal, oldVal) {
if (!newVal) return;
this.initList(newVal)
this.fnInitLoveDayCount();
}
}
},
onLoad() {
this.fnSetPageTitle('恋爱日记');
},
methods: {
initList(configs) {
this.navList = [
{
key: 'journey',
use: configs.ourStory.enabled,
iconImageUrl: configs.ourStory.iconUrl,
title: '关于我们',
desc: '我们一起度过的那些经历'
},
{
key: 'album',
use: configs.lovePhoto.enabled,
iconImageUrl: configs.lovePhoto.iconUrl,
title: '恋爱相册',
desc: '定格了我们的那些小美好'
},
{
key: 'list',
use: configs.loveDaily.enabled,
iconImageUrl: configs.loveDaily.iconUrl,
title: '恋爱清单',
desc: '你我之间的约定我们都在努力实现'
},
]
},
fnInitLoveDayCount() {
clearTimeout(this.loveDayTimer);
const _countDownFn = () => {
this.loveDayTimer = setTimeout(_countDownFn, 1000);
const formatStartDate = this.loveConfig.loveDate.replace(/-/g, '/');
const start = new Date(formatStartDate),
now = new Date();
const T = now.getTime() - start.getTime();
const i = 24 * 60 * 60 * 1000;
const d = T / i;
const D = Math.floor(d);
const h = (d - D) * 24;
const H = Math.floor(h);
const m = (h - H) * 60;
const M = Math.floor(m);
const s = (m - M) * 60;
const S = Math.floor(s);
this.loveDayCount = {
d: D,
h: H,
m: M,
s: S
};
};
_countDownFn();
},
fnToPage(pageName) {
uni.navigateTo({
url: `/pagesA/love/${pageName}`
});
}
}
};
</script>
<style scoped lang="scss">
.app-page {
width: 100vw;
height: 100vh;
width: 100vw;
min-height: 100vh;
background: linear-gradient(
-45deg,
rgba(247, 149, 51, 0.1),
rgba(243, 112, 85, 0.1) 15%,
rgba(239, 78, 123, 0.1) 30%,
rgba(161, 102, 171, 0.1) 44%,
rgba(80, 115, 184, 0.1) 58%,
rgba(16, 152, 173, 0.1) 72%,
rgba(7, 179, 155, 0.1) 86%,
rgba(109, 186, 130, 0.1)
);
}
.lover-wrap {
position: relative;
width: 100vw;
height: 50vh;
display: flex;
align-items: center;
justify-content: center;
background-size: cover;
background-repeat: no-repeat;
background-position: 50% 50%;
&::before {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
content: '';
background-color: rgba(255, 255, 255, 0.1);
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKUlEQVQImU3IMREAIAgAwJfNkQCEsH8cijjpMf6vnXlQaIiJFx+omEBfmqIEZLe2jzcAAAAASUVORK5CYII=);
z-index: 0;
backdrop-filter: blur(4rpx);
overflow: hidden;
}
&::after {
content: '';
position: absolute;
left: 0;
bottom: -60rpx;
width: 100vw;
height: 60rpx;
background-image: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
}
.lover-card {
position: absolute;
left: 50%;
top: 58%;
transform: translate(-50%, -50%);
width: 90vw;
display: flex;
align-items: center;
justify-content: space-around;
border-radius: 12rpx;
z-index: 2;
.avatar {
width: 180rpx;
height: 180rpx;
border-radius: 50%;
box-sizing: border-box;
// border: 8rpx solid transparent;
// border: 8rpx solid rgba(255, 255, 255, 0.7) !important;
border: 8rpx solid rgba(255, 255, 255, 1) !important;
}
.name {
font-size: 32rpx;
font-weight: bold;
color: #ffffff;
text-align: center;
letter-spacing: 2rpx;
}
.boy {
color: #3ab8e4;
.avatar {
border-color: rgba(58, 184, 228, 0.7);
}
}
.girl {
color: #f57ab3;
.avatar {
border-color: rgba(245, 122, 179, 0.7);
}
}
.like {
width: 120rpx;
height: 120rpx;
animation: likeani 1s ease-in-out infinite;
}
}
.wave-image {
width: 100%;
height: 120rpx;
position: absolute;
left: 0;
bottom: 0;
mix-blend-mode: screen;
}
}
.love-time-wrap {
margin-top: 80rpx;
width: 100vw;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.title {
font-size: 46rpx;
letter-spacing: 4rpx;
// background-image: linear-gradient(270deg, #ff4500, #ffa500, #ffd700, #90ee90, #00ffff, #1e90ff, #9370db, #ff69b4, #ff4500);
// -webkit-background-clip: text;
// color: #000;
color: #333;
font-size: 42rpx;
font-weight: bold;
// animation: loveTimeTitleAni 80s linear infinite;
}
.content {
margin-top: 24rpx;
display: flex;
align-items: center;
justify-content: center;
.text {
font-size: 28rpx;
}
.number {
margin: 0 8rpx;
font-size: 46rpx;
// color: #ff69b4;
color: #f83856;
font-weight: bold;
}
}
}
.list-wrap {
margin-top: 75rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 0 36rpx;
.list-item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
box-sizing: border-box;
padding: 28rpx 32rpx;
background-color: #ffffff;
border-radius: 50rpx;
margin-bottom: 32rpx;
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.03);
&:nth-child(1) {
animation: listItemAni1 3s ease-in-out infinite;
}
&:nth-child(2) {
animation: listItemAni1 3s ease-in-out infinite;
animation-delay: 1.5s;
}
&:nth-child(3) {
animation: listItemAni1 3s ease-in-out infinite;
animation-delay: 2s;
}
.left {
width: 120rpx;
height: 120rpx;
.icon {
width: 100%;
height: 100%;
}
}
.right {
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
box-sizing: border-box;
padding-left: 40rpx;
.name {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.desc {
margin-top: 8px;
font-size: 26rpx;
color: #777777;
}
}
}
}
@keyframes likeani {
0% {
transform: scale(1);
}
25% {
transform: scale(1.2);
}
50% {
transform: scale(1.1);
}
75% {
transform: scale(1.3);
}
100% {
transform: scale(1);
}
}
@keyframes loveTimeTitleAni {
to {
background-position: -200rem;
}
}
@keyframes listItemAni1 {
0% {
transform: translateY(0rpx);
}
50% {
transform: translateY(-10rpx);
}
100% {
transform: translateY(0rpx);
}
}
</style>
+313 -242
View File
@@ -9,52 +9,27 @@
<view class="sheet-content">内容区域</view>
</tm-sheet> -->
<tm-sheet :shadow="6" :padding="[12, 24]" :margin="[24, 24]">
<view class="py-12 px-24 mx-12 round-3 border-b-1 grey text">
<text class="text-size-n text-weight-b ">布局</text>
<view class="py-12 px-24 mx-12 round-3 grey text">
<text class="text-size-n text-weight-b text-grey-darken-3">布局</text>
<text class="text-grey text-size-xs px-10 ml-12">应用以及文章列表布局设置</text>
</view>
<view class="sheet-content">
<tm-pickers
title="请选择首页布局"
btn-color="light-blue"
:default-value.sync="homeLayout.selectDefault"
rang-key="name"
:list="homeLayout.list"
@confirm="fnOnHomeLayoutConfirm"
>
<tm-input
name="status"
required
title="首页布局"
placeholder="请选择首页布局"
disabled
:value="homeLayout.selectLabel"
right-icon="icon-angle-right"
></tm-input>
<tm-pickers title="请选择首页布局" btn-color="light-blue" :default-value.sync="homeLayout.selectDefault"
rang-key="name" :list="homeLayout.list" @confirm="fnOnHomeLayoutConfirm">
<tm-input name="status" required title="首页文章布局" placeholder="请选择首页文章布局" disabled align="right"
:value="homeLayout.selectLabel" right-icon="icon-angle-right"></tm-input>
</tm-pickers>
<tm-pickers
title="请选择文章卡片样式"
btn-color="light-blue"
:default-value.sync="articleCardStyle.selectDefault"
rang-key="name"
:list="articleCardStyle.list"
@confirm="fnOnArticleCardStyleConfirm"
>
<tm-input
name="status"
required
title="卡片样式"
placeholder="请选择文章卡片样式"
disabled
:value="articleCardStyle.selectLabel"
right-icon="icon-angle-right"
></tm-input>
<tm-pickers title="请选择文章卡片样式" btn-color="light-blue"
:default-value.sync="articleCardStyle.selectDefault" rang-key="name"
:list="articleCardStyle.list" @confirm="fnOnArticleCardStyleConfirm">
<tm-input name="status" required title="文章卡片样式" placeholder="请选择文章卡片样式" disabled align="right"
:value="articleCardStyle.selectLabel" right-icon="icon-angle-right"></tm-input>
</tm-pickers>
</view>
</tm-sheet>
<tm-sheet v-if="false" :shadow="24" :padding="[12, 24]" :margin="[24, 24]">
<view class="py-12 px-24 mx-12 round-3 border-b-1 grey text">
<view class="py-12 px-24 mx-12 round-3 grey text">
<text class="text-size-n text-weight-b ">外观</text>
<text class="text-grey text-size-xs px-10">设置应用主题色/暗黑模式等</text>
</view>
@@ -65,12 +40,64 @@
</tm-sheet>
<tm-sheet :shadow="6" :padding="[12, 24]" :margin="[24, 24]">
<view class="py-12 px-24 mx-12 round-3 border-b-1 grey text">
<text class="text-size-n text-weight-b ">功能</text>
<view class="py-12 px-24 mx-12 round-3 grey text">
<text class="text-size-n text-weight-b text-grey-darken-3">功能</text>
<text class="text-grey text-size-xs px-10 ml-12">一些常用的功能性设置</text>
</view>
<view class="sheet-content">
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m">图库瀑布流模式</text>
<tm-switch v-model="appSettings.gallery.useWaterfull" color="light-blue"
:text="['是', '否']"></tm-switch>
</view>
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m">友链简洁模式</text>
<tm-switch v-model="appSettings.links.useSimple" color="light-blue"
:text="['是', '否']"></tm-switch>
</view>
<!-- <view class="mx-32 mt-24 mb-0 border-b-1 pb-24 flex-between">
<text class="text-size-m">启用评论弹幕</text>
<tm-switch v-model="appSettings.barrage.use" color="light-blue" :text="['是', '否']"></tm-switch>
</view>
<tm-pickers
v-if="appSettings.barrage.use"
title="评论弹幕位置"
btn-color="light-blue"
:default-value.sync="barrage.selectDefault"
rang-key="name"
:list="barrage.list"
@confirm="fnOnBarrageConfirm"
>
<tm-input
name="status"
title="评论弹幕位置"
placeholder="请选择评论弹幕位置"
disabled
align="right"
:value="barrage.selectLabel"
right-icon="icon-angle-right"
></tm-input>
</tm-pickers> -->
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m">是否圆形头像</text>
<tm-switch v-model="appSettings.isAvatarRadius" color="light-blue"
:text="['是', '否']"></tm-switch>
</view>
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m ">轮播图指示器</text>
<tm-switch v-model="appSettings.banner.useDot" color="light-blue"
:text="['是', '否']"></tm-switch>
</view>
<view v-if="appSettings.banner.useDot" class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m ">轮播图指示器位置</text>
<tm-groupradio name="dotPosition" @change="fnOnBannerDotChange">
<tm-radio :name="item.name" :size="28" color="light-blue"
v-for="(item, index) in dotPositionList" :key="index" v-model="item.checked"
:label="item.name"></tm-radio>
</tm-groupradio>
</view>
<!-- <view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m">显示完整统计</text>
<tm-switch v-model="appSettings.about.showAllCount" color="light-blue" :text="['是', '否']"></tm-switch>
</view>
@@ -78,36 +105,10 @@
<text class="text-size-m ">链接直接打开</text>
<tm-switch v-model="appSettings.contact.isLinkCopy" color="light-blue" :text="['是', '否']"></tm-switch>
</view>
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m ">图库瀑布流模式</text>
<tm-switch v-model="appSettings.gallery.useWaterfull" color="light-blue" :text="['是', '否']"></tm-switch>
</view>
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m ">友链简洁模式</text>
<tm-switch v-model="appSettings.links.useSimple" color="light-blue" :text="['是', '否']"></tm-switch>
</view>
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m ">轮播图指示器</text>
<tm-switch v-model="appSettings.banner.useDot" color="light-blue" :text="['是', '否']"></tm-switch>
</view>
<view v-if="appSettings.banner.useDot" class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m ">轮播图指示器位置</text>
<tm-groupradio name="dotPosition" @change="fnOnBannerDotChange">
<tm-radio
:name="item.name"
:size="28"
color="light-blue"
v-for="(item, index) in dotPositionList"
:key="index"
v-model="item.checked"
:label="item.name"
></tm-radio>
</tm-groupradio>
</view>
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<view v-if="false" class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m ">显示后台入口</text>
<tm-switch v-model="appSettings.about.showAdmin" color="light-blue" :text="['是', '否']"></tm-switch>
</view>
</view> -->
</view>
</tm-sheet>
</tm-form>
@@ -122,191 +123,261 @@
</template>
<script>
import { _DefaultAppSettings } from '@/utils/app.js';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmForm from '@/tm-vuetify/components/tm-form/tm-form.vue';
import tmPickers from '@/tm-vuetify/components/tm-pickers/tm-pickers.vue';
import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
import tmSwitch from '@/tm-vuetify/components/tm-switch/tm-switch.vue';
import tmSheet from '@/tm-vuetify/components/tm-sheet/tm-sheet.vue';
import tmGroupradio from '@/tm-vuetify/components/tm-groupradio/tm-groupradio.vue';
import tmRadio from '@/tm-vuetify/components/tm-radio/tm-radio.vue';
import {
_DefaultAppSettings
} from '@/utils/app.js';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmForm from '@/tm-vuetify/components/tm-form/tm-form.vue';
import tmPickers from '@/tm-vuetify/components/tm-pickers/tm-pickers.vue';
import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
import tmSwitch from '@/tm-vuetify/components/tm-switch/tm-switch.vue';
import tmSheet from '@/tm-vuetify/components/tm-sheet/tm-sheet.vue';
import tmGroupradio from '@/tm-vuetify/components/tm-groupradio/tm-groupradio.vue';
import tmRadio from '@/tm-vuetify/components/tm-radio/tm-radio.vue';
export default {
components: {
tmButton,
tmForm,
tmPickers,
tmInput,
tmSwitch,
tmSheet,
tmGroupradio,
tmRadio
},
data() {
return {
isBlackTheme: false,
loading: true,
appSettings: {},
homeLayout: {
list: [{ name: '一行一列', value: 'h_row_col1' }, { name: '一行两列', value: 'h_row_col2' }],
selectDefault: ['一行一列'],
selectLabel: '一行一列',
selectValue: 'h_row_col1'
export default {
components: {
tmButton,
tmForm,
tmPickers,
tmInput,
tmSwitch,
tmSheet,
tmGroupradio,
tmRadio
},
data() {
return {
isBlackTheme: false,
loading: true,
appSettings: {},
isSaved: true,
firstLoad: true,
homeLayout: {
list: [{
name: '一行一列',
value: 'h_row_col1'
}, {
name: '一行两列',
value: 'h_row_col2'
}],
selectDefault: ['一行一列'],
selectLabel: '一行一列',
selectValue: 'h_row_col1'
},
articleCardStyle: {
list: [{
name: '左图右文',
value: 'lr_image_text'
},
{
name: '左文右图',
value: 'lr_text_image'
},
{
name: '上图下文',
value: 'tb_image_text'
},
{
name: '上文下图',
value: 'tb_text_image'
},
{
name: '只有文字',
value: 'only_text'
}
],
selectDefault: ['左图右文'],
selectLabel: '左图右文',
selectValue: 'lr_image_text'
},
dotPositionList: [{
name: '右边',
value: 'right',
checked: true
}, {
name: '下边',
value: 'bottom',
checked: false
}],
barrage: {
list: [{
name: '顶部',
value: 'rightToLeft'
}, {
name: '左下',
value: 'leftBottom'
}],
selectDefault: ['顶部'],
selectLabel: '顶部',
selectValue: 'rightToLeft'
}
};
},
watch: {
appSettings: {
deep: true,
handler() {
if (this.firstLoad) {
this.firstLoad = false;
} else {
this.isSaved = false;
}
}
}
},
onLoad(e) {
this.fnSetPageTitle('应用设置');
},
created() {
this.appSettings = Object.assign({}, _DefaultAppSettings, uni.$tm.vx.getters().getSettings);
this.fnHandleFormatSelect();
uni.showLoading({
title: '加载中...',
mask: true
});
setTimeout(() => {
this.loading = false;
uni.hideLoading();
}, 500);
},
methods: {
// 统一处理选择框的内容
fnHandleFormatSelect() {
// 首页布局
const _homeLayout = this.fnFindObjInList(this.homeLayout.list, 'value', this.appSettings.layout.home);
this.homeLayout.selectDefault = [_homeLayout.name];
this.homeLayout.selectLabel = _homeLayout.name;
this.homeLayout.selectValue = _homeLayout.value;
const _articleCardStyle = this.fnFindObjInList(this.articleCardStyle.list, 'value', this.appSettings.layout
.cardType);
this.articleCardStyle.selectDefault = [_articleCardStyle.name];
this.articleCardStyle.selectLabel = _articleCardStyle.name;
this.articleCardStyle.selectValue = _articleCardStyle.value;
const _barrage = this.fnFindObjInList(this.barrage.list, 'value', this.appSettings.barrage.type);
this.barrage.selectDefault = [_barrage.name];
this.barrage.selectLabel = _barrage.name;
this.barrage.selectValue = _barrage.value;
},
articleCardStyle: {
list: [
{ name: '左图右文', value: 'lr_image_text' },
{ name: '左文右图', value: 'lr_text_image' },
{ name: '上图下文', value: 'tb_image_text' },
{ name: '上文下图', value: 'tb_text_image' },
{ name: '只有文字', value: 'only_text' }
],
selectDefault: ['左图右文'],
selectLabel: '左图右文',
selectValue: 'lr_image_text'
// 在集合中找匹配的对象
fnFindObjInList(list, key, value) {
return list.find(x => x[key] == value);
},
fnOnBannerDotChange(e) {
const _select = e[0];
if (_select.index == 0 && _select.checked) {
this.appSettings.banner.dotPosition = 'right';
}
if (_select.index == 1 && _select.checked) {
this.appSettings.banner.dotPosition = 'bottom';
}
},
dotPositionList: [{ name: '右边', value: 'right', checked: true }, { name: '下边', value: 'bottom', checked: false }]
};
},
computed: {
// 获取设置
_appSettings() {
return uni.$tm.vx.getters().setting.getSettings;
}
},
watch: {
_appSettings(val) {
this.appSettings = val;
}
},
onLoad(e) {
this.fnSetPageTitle('应用设置');
},
created() {
this.appSettings = this._appSettings;
this.fnHandleFormatSelect();
uni.showLoading({
title: '加载中...',
mask: true
});
setTimeout(() => {
this.loading = false;
uni.hideLoading();
}, 500);
},
methods: {
// 统一处理选择框的内容
fnHandleFormatSelect() {
// 首页布局
const _homeLayout = this.fnFindObjInList(this.homeLayout.list, 'value', this.appSettings.layout.home);
this.homeLayout.selectDefault = [_homeLayout.name];
this.homeLayout.selectLabel = _homeLayout.name;
this.homeLayout.selectValue = _homeLayout.value;
const _articleCardStyle = this.fnFindObjInList(this.articleCardStyle.list, 'value', this.appSettings.layout.cardType);
this.articleCardStyle.selectDefault = [_articleCardStyle.name];
this.articleCardStyle.selectLabel = _articleCardStyle.name;
this.articleCardStyle.selectValue = _articleCardStyle.value;
},
// 在集合中找匹配的对象
fnFindObjInList(list, key, value) {
return list.find(x => x[key] == value);
},
fnOnBannerDotChange(e) {
const _select = e[0];
if (_select.index == 0 && _select.checked) {
this.appSettings.banner.dotPosition = 'right';
}
if (_select.index == 1 && _select.checked) {
this.appSettings.banner.dotPosition = 'bottom';
}
},
// 首页布局
fnOnHomeLayoutConfirm(e) {
const _select = e[0].data;
this.homeLayout.selectDefault = [_select.name];
this.homeLayout.selectLabel = _select.name;
this.homeLayout.selectValue = _select.value;
this.appSettings.layout.home = _select.value;
},
// 文章卡片样式
fnOnArticleCardStyleConfirm(e) {
const _select = e[0].data;
this.articleCardStyle.selectDefault = [_select.name];
this.articleCardStyle.selectLabel = _select.name;
this.articleCardStyle.selectValue = _select.value;
this.appSettings.layout.cardType = _select.value;
},
// 保存
fnOnSave() {
this.$tm.vx.commit('setting/setSettings', this.appSettings);
uni.$tm.toast('保存成功,部分设置在重启后生效!');
},
// 使用默认配置
fnOnSaveDefault() {
uni.$eShowModal({
title: '提示',
content: '您确定要恢复为默认的设置吗?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
uni.$tm.vx.actions('setting/updateDefaultAppSettings');
uni.$tm.toast('系统设置已恢复为默认配置,部分设置在重启后生效!');
})
.catch(err => {});
},
fnOnBack() {
uni.$eShowModal({
title: '提示',
content: '您当前可能有未保存的数据,确定返回吗?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
fnOnHomeLayoutConfirm(e) {
const _select = e[0].data;
this.homeLayout.selectDefault = [_select.name];
this.homeLayout.selectLabel = _select.name;
this.homeLayout.selectValue = _select.value;
this.appSettings.layout.home = _select.value;
},
// 文章卡片样式
fnOnArticleCardStyleConfirm(e) {
const _select = e[0].data;
this.articleCardStyle.selectDefault = [_select.name];
this.articleCardStyle.selectLabel = _select.name;
this.articleCardStyle.selectValue = _select.value;
this.appSettings.layout.cardType = _select.value;
},
// 弹幕设置
fnOnBarrageConfirm(e) {
const _select = e[0].data;
this.barrage.selectDefault = [_select.name];
this.barrage.selectLabel = _select.name;
this.barrage.selectValue = _select.value;
this.appSettings.barrage.type = _select.value;
},
// 保存
fnOnSave() {
this.isSaved = true;
this.$tm.vx.commit('setting/setSettings', this.appSettings);
uni.$tm.toast('保存成功,部分设置在重启后生效!');
},
// 使用默认配置
fnOnSaveDefault() {
uni.$eShowModal({
title: '提示',
content: '您确定要恢复为默认的设置吗?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
this.isSaved = true;
uni.$tm.vx.actions('setting/updateDefaultAppSettings');
uni.$tm.toast('系统设置已恢复为默认配置,部分设置在重启后生效!');
})
.catch(err => {});
},
fnOnBack() {
if (this.isSaved) {
uni.navigateBack();
})
.catch(err => {});
return;
}
uni.$eShowModal({
title: '提示',
content: '您当前可能有未保存的数据,确定返回吗?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
uni.navigateBack();
this.isSaved = true;
})
.catch(err => {});
}
}
}
};
};
</script>
<style scoped lang="scss">
.app-page {
box-sizing: border-box;
padding-bottom: 120rpx;
.btn-bar {
width: 100vw;
position: fixed;
left: 0;
bottom: 0;
.app-page {
box-sizing: border-box;
padding: 24rpx;
gap: 0 24rpx;
box-shadow: 0rpx -6rpx 24rpx rgba(0, 0, 0, 0.03);
padding-bottom: 120rpx;
.btn-bar {
width: 100vw;
position: fixed;
left: 0;
bottom: 0;
box-sizing: border-box;
padding: 24rpx;
gap: 0 24rpx;
box-shadow: 0rpx -6rpx 24rpx rgba(0, 0, 0, 0.03);
}
}
}
.required {
position: relative;
padding-left: 18rpx;
&:before {
content: '*';
position: absolute;
left: -4rpx;
top: 50%;
transform: translateY(-50%);
font-size: 34rpx;
color: rgba(244, 67, 54, 1);
.required {
position: relative;
padding-left: 18rpx;
&:before {
content: '*';
position: absolute;
left: -4rpx;
top: 50%;
transform: translateY(-50%);
font-size: 34rpx;
color: rgba(244, 67, 54, 1);
}
}
}
</style>
+449 -304
View File
@@ -1,7 +1,6 @@
<template>
<view class="app-page bg-gradient-blue-lighten-b">
<!-- 流星-->
<view class="tn-satr">
<view class="app-page bg-gradient-blue-lighten-b" :class="calcPageClass" :style="[calcPageStyle]">
<view v-if="calcBackgroundType!=='video'" class="tn-satr">
<view class="sky"></view>
<view class="stars">
<view class="falling-stars">
@@ -41,346 +40,492 @@
</view>
</view>
<!-- 头像用户信息 -->
<view class="user-info__container flex flex-col flex-center">
<image class="user-info__avatar" src="https://blog-oss.925i.cn/blog-files/d500acd35e8b6d24bbfa2dabd2a605e6.png" mode="aspectFill"></image>
<view class="user-info__nick-name"> uni-halo </view>
</view>
<video v-else class="video-bg" :object-fit="startConfig.bgVideoFit" :src="startConfig.bgVideo" :loop="true"
:autoplay="true" :muted="true" :controls="false" :show-fullscreen-btn="false" :show-play-btn="false"
:show-center-play-btn="false" :show-loading="false" :enable-progress-gesture="false"
:show-progress="false"></video>
<view class="text-align-center text-white" style="padding: 60vh 0 0 0;">
<view class="" style="font-size: 44rpx;">全新UI准备出发</view>
<view class="mt-30 text-size-m">新触动 新感受 新体验</view>
</view>
<cover-view v-if="startConfig.title || startConfig.logo" class="title-container">
<view class="" style="padding: 120rpx 200rpx;z-index: 999;position: relative;"><view class="start-btn" @click="fnStart()">全新出发</view></view>
<cover-view v-if="startConfig.logo" class="app-logo">
<cover-view class="app-logo-border">
<cover-image class="app-logo-image" :src="$utils.checkImageUrl(startConfig.logo)"
mode="aspectFill"></cover-image>
</cover-view>
</cover-view>
<cover-view v-if="startConfig.title" class="app-title" :style="startConfig.titleStyle">
{{ startConfig.title }}
</cover-view>
</cover-view>
<cover-view class="bottom-container">
<cover-view class="text-align-center" :style="startConfig.descStyle"
style="color:white;padding: 60vh 0 0 0;">
<cover-view style="font-size: 44rpx;" v-show="startConfig.desc1">
{{ startConfig.desc1 }}
</cover-view>
<cover-view class="mt-30 text-size-m" v-show="startConfig.desc2">
{{ startConfig.desc2 }}
</cover-view>
</cover-view>
<!-- #ifdef APP-PLUS -->
<cover-view class="start-btn-app" :style="[startConfig.btnStyle]" @click="fnStart()">
{{ startConfig.btnText || '开始体验' }}
</cover-view>
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<cover-view class="start-btn" :class="[startConfig.btnClass]" @click="fnStart()"
:style="[startConfig.btnStyle]">{{ startConfig.btnText || '开始体验' }}
</cover-view>
<!-- #endif -->
</cover-view>
<!-- 波浪效果 -->
<wave></wave>
<wave v-if="startConfig.useWave"></wave>
</view>
</template>
<script>
import wave from '@/components/wave/wave.vue';
export default {
components: { wave },
methods: {
fnStart() {
uni.switchTab({
url: '/pages/tabbar/home/home',
success: () => {
uni.setStorageSync('APP_HAS_STARTED', true);
import wave from '@/components/wave/wave.vue';
export default {
components: {
wave
},
computed: {
startConfig() {
return this.$tm.vx.getters().getConfigs.appConfig.startConfig;
},
calcBackgroundType() {
return this.startConfig.backgroundType;
},
calcPageClass() {
if (this.calcBackgroundType === 'color') {
return [this.startConfig.bg]
}
});
return []
},
calcPageStyle() {
if (this.calcBackgroundType === 'color') {
return {}
}
if (this.calcBackgroundType === 'image') {
return {
backgroundImage: `url(${this.$utils.checkImageUrl(this.startConfig.bgImage)})!important`,
backgroundSize: this.startConfig.bgImageFit
}
}
if (this.calcBackgroundType === 'video') {
return {
background: '#ffffff'
}
}
return {};
}
},
methods: {
fnStart() {
uni.switchTab({
url: '/pages/tabbar/home/home',
success: () => {
uni.setStorageSync('APP_HAS_STARTED', true);
}
});
}
}
}
};
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
height: 100vh;
}
.start-btn {
box-sizing: border-box;
background-color: transparent;
padding: 16rpx 50rpx;
border-radius: 50rpx;
border: 2rpx solid #ffffff;
color: #ffffff;
text-align: center;
font-size: 28rpx;
}
/* 用户信息 start */
.user-info {
&__container {
position: absolute;
top: 25vh;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
.app-page {
position: relative;
width: 100vw;
height: 100vh;
background-size: cover;
background-repeat: no-repeat;
}
&__avatar {
.start-btn {
box-sizing: border-box;
background-color: transparent;
padding: 16rpx 50rpx;
border-radius: 50rpx;
border: 2rpx solid #ffffff;
color: #ffffff;
text-align: center;
font-size: 28rpx;
margin-top: 60rpx;
margin-bottom: 120rpx;
}
.start-btn-app {
border-radius: 50rpx;
color: #ffffff;
text-align: center;
font-size: 28rpx;
margin: 60rpx auto 120rpx auto;
width: 40vw;
height: 60rpx;
line-height: 60rpx;
border: 2rpx solid #ffffff;
}
.bottom-container {
display: flex;
flex-direction: column;
position: absolute;
bottom: 50rpx;
left: 50%;
transform: translateX(-50%);
z-index: 999;
}
.video-bg {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
z-index: 0;
}
.title-container {
width: 100vw;
position: absolute;
top: 20vh;
left: 0;
z-index: 999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.app-logo {
width: 200rpx;
height: 200rpx;
border: 8rpx solid rgba(255, 255, 255, 0.05);
border-radius: 50%;
overflow: hidden;
box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.15);
&-border {
width: 100%;
height: 100%;
box-sizing: border-box;
border: 8rpx solid rgba(255, 255, 255, 0.35);
border-radius: 50%;
overflow: hidden;
}
&-image {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
&__nick-name {
.app-title {
color: #ffffff;
margin-top: 26rpx;
font-size: 36rpx;
font-weight: 600;
text-align: center;
}
}
/* 用户信息 end */
/* 流星*/
.tn-satr {
position: fixed;
width: 100%;
height: 600px;
overflow: hidden;
flex-shrink: 0;
z-index: 998;
}
.stars {
position: absolute;
z-index: 1;
width: 100%;
height: 400px;
}
.tn-satr {
position: fixed;
width: 100%;
height: 600px;
overflow: hidden;
flex-shrink: 0;
z-index: 998;
}
.star {
border-radius: 50%;
background: #ffffff;
box-shadow: 0px 0px 6px 0px rgba(255, 255, 255, 0.8);
}
.stars {
position: absolute;
z-index: 1;
width: 100%;
height: 400px;
}
.small-stars .star {
position: absolute;
width: 3px;
height: 3px;
}
.small-stars .star:nth-child(2n) {
opacity: 0;
-webkit-animation: star-blink 1.2s linear infinite alternate;
animation: star-blink 1.2s linear infinite alternate;
}
.small-stars .star:nth-child(1) {
left: 40px;
bottom: 50px;
}
.small-stars .star:nth-child(2) {
left: 200px;
bottom: 40px;
}
.small-stars .star:nth-child(3) {
left: 60px;
bottom: 120px;
}
.small-stars .star:nth-child(4) {
left: 140px;
bottom: 250px;
}
.small-stars .star:nth-child(5) {
left: 400px;
bottom: 300px;
}
.small-stars .star:nth-child(6) {
left: 170px;
bottom: 80px;
}
.small-stars .star:nth-child(7) {
left: 200px;
bottom: 360px;
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.small-stars .star:nth-child(8) {
left: 250px;
bottom: 320px;
}
.small-stars .star:nth-child(9) {
left: 300px;
bottom: 340px;
}
.small-stars .star:nth-child(10) {
left: 130px;
bottom: 320px;
-webkit-animation-delay: 0.5s;
animation-delay: 0.5s;
}
.small-stars .star:nth-child(11) {
left: 230px;
bottom: 330px;
-webkit-animation-delay: 7s;
animation-delay: 7s;
}
.small-stars .star:nth-child(12) {
left: 300px;
bottom: 360px;
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
@-webkit-keyframes star-blink {
50% {
.star {
border-radius: 50%;
background: #ffffff;
box-shadow: 0 0 6px 0 rgba(255, 255, 255, 0.8);
}
.small-stars .star {
position: absolute;
width: 3px;
height: 3px;
opacity: 1;
}
}
@keyframes star-blink {
50% {
.small-stars .star:nth-child(2n) {
opacity: 0;
-webkit-animation: star-blink 1.2s linear infinite alternate;
animation: star-blink 1.2s linear infinite alternate;
}
.small-stars .star:nth-child(1) {
left: 40px;
bottom: 50px;
}
.small-stars .star:nth-child(2) {
left: 200px;
bottom: 40px;
}
.small-stars .star:nth-child(3) {
left: 60px;
bottom: 120px;
}
.small-stars .star:nth-child(4) {
left: 140px;
bottom: 250px;
}
.small-stars .star:nth-child(5) {
left: 400px;
bottom: 300px;
}
.small-stars .star:nth-child(6) {
left: 170px;
bottom: 80px;
}
.small-stars .star:nth-child(7) {
left: 200px;
bottom: 360px;
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.small-stars .star:nth-child(8) {
left: 250px;
bottom: 320px;
}
.small-stars .star:nth-child(9) {
left: 300px;
bottom: 340px;
}
.small-stars .star:nth-child(10) {
left: 130px;
bottom: 320px;
-webkit-animation-delay: 0.5s;
animation-delay: 0.5s;
}
.small-stars .star:nth-child(11) {
left: 230px;
bottom: 330px;
-webkit-animation-delay: 7s;
animation-delay: 7s;
}
.small-stars .star:nth-child(12) {
left: 300px;
bottom: 360px;
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
@-webkit-keyframes star-blink {
50% {
width: 3px;
height: 3px;
opacity: 1;
}
}
@keyframes star-blink {
50% {
width: 3px;
height: 3px;
opacity: 1;
}
}
.medium-stars .star {
position: absolute;
width: 3px;
height: 3px;
opacity: 1;
opacity: 0;
-webkit-animation: star-blink 1.2s ease-in infinite alternate;
animation: star-blink 1.2s ease-in infinite alternate;
}
}
.medium-stars .star {
position: absolute;
width: 3px;
height: 3px;
opacity: 0;
-webkit-animation: star-blink 1.2s ease-in infinite alternate;
animation: star-blink 1.2s ease-in infinite alternate;
}
.medium-stars .star:nth-child(1) {
left: 300px;
bottom: 50px;
}
.medium-stars .star:nth-child(2) {
left: 400px;
bottom: 40px;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.medium-stars .star:nth-child(3) {
left: 330px;
bottom: 300px;
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.medium-stars .star:nth-child(4) {
left: 460px;
bottom: 300px;
-webkit-animation-delay: 0.9s;
animation-delay: 0.9s;
}
.medium-stars .star:nth-child(5) {
left: 300px;
bottom: 150px;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.medium-stars .star:nth-child(6) {
left: 440px;
bottom: 120px;
-webkit-animation-delay: 1s;
animation-delay: 1s;
}
.medium-stars .star:nth-child(7) {
left: 200px;
bottom: 140px;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.medium-stars .star:nth-child(8) {
left: 30px;
bottom: 480px;
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.medium-stars .star:nth-child(9) {
left: 460px;
bottom: 400px;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.medium-stars .star:nth-child(10) {
left: 150px;
bottom: 10px;
-webkit-animation-delay: 1s;
animation-delay: 1s;
}
.medium-stars .star:nth-child(11) {
left: 420px;
bottom: 450px;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.medium-stars .star:nth-child(12) {
left: 340px;
bottom: 180px;
-webkit-animation-delay: 1.1s;
animation-delay: 1.1s;
}
@keyframes star-blink {
50% {
width: 4px;
height: 4px;
opacity: 1;
.medium-stars .star:nth-child(1) {
left: 300px;
bottom: 50px;
}
}
.star-fall {
position: relative;
border-radius: 2px;
width: 80px;
height: 2px;
overflow: hidden;
-webkit-transform: rotate(-20deg);
transform: rotate(-20deg);
}
.star-fall:after {
content: '';
position: absolute;
width: 50px;
height: 2px;
background: -webkit-gradient(linear, right top, left top, from(rgba(0, 0, 0, 0)), to(rgba(255, 255, 255, 0.4)));
background: linear-gradient(to left, rgba(0, 0, 0, 0) 0%, rgba(255, 255, 255, 0.4) 100%);
left: 100%;
-webkit-animation: star-fall 3.6s linear infinite;
animation: star-fall 3.6s linear infinite;
}
.star-fall:nth-child(1) {
left: 80px;
bottom: -100px;
}
.star-fall:nth-child(1):after {
-webkit-animation-delay: 2.4s;
animation-delay: 2.4s;
}
.star-fall:nth-child(2) {
left: 200px;
bottom: -200px;
}
.star-fall:nth-child(2):after {
-webkit-animation-delay: 2s;
animation-delay: 2s;
}
.star-fall:nth-child(3) {
left: 430px;
bottom: -50px;
}
.star-fall:nth-child(3):after {
-webkit-animation-delay: 3.6s;
animation-delay: 3.6s;
}
.star-fall:nth-child(4) {
left: 400px;
bottom: 100px;
}
.star-fall:nth-child(4):after {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
@-webkit-keyframes star-fall {
20% {
left: -100%;
.medium-stars .star:nth-child(2) {
left: 400px;
bottom: 40px;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
100% {
left: -100%;
}
}
@keyframes star-fall {
20% {
left: -100%;
.medium-stars .star:nth-child(3) {
left: 330px;
bottom: 300px;
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
100% {
left: -100%;
.medium-stars .star:nth-child(4) {
left: 460px;
bottom: 300px;
-webkit-animation-delay: 0.9s;
animation-delay: 0.9s;
}
.medium-stars .star:nth-child(5) {
left: 300px;
bottom: 150px;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.medium-stars .star:nth-child(6) {
left: 440px;
bottom: 120px;
-webkit-animation-delay: 1s;
animation-delay: 1s;
}
.medium-stars .star:nth-child(7) {
left: 200px;
bottom: 140px;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.medium-stars .star:nth-child(8) {
left: 30px;
bottom: 480px;
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.medium-stars .star:nth-child(9) {
left: 460px;
bottom: 400px;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.medium-stars .star:nth-child(10) {
left: 150px;
bottom: 10px;
-webkit-animation-delay: 1s;
animation-delay: 1s;
}
.medium-stars .star:nth-child(11) {
left: 420px;
bottom: 450px;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.medium-stars .star:nth-child(12) {
left: 340px;
bottom: 180px;
-webkit-animation-delay: 1.1s;
animation-delay: 1.1s;
}
@keyframes star-blink {
50% {
width: 4px;
height: 4px;
opacity: 1;
}
}
.star-fall {
position: relative;
border-radius: 2px;
width: 80px;
height: 2px;
overflow: hidden;
-webkit-transform: rotate(-20deg);
transform: rotate(-20deg);
}
.star-fall:after {
content: '';
position: absolute;
width: 50px;
height: 2px;
background: -webkit-gradient(linear, right top, left top, from(rgba(0, 0, 0, 0)), to(rgba(255, 255, 255, 0.4)));
background: linear-gradient(to left, rgba(0, 0, 0, 0) 0%, rgba(255, 255, 255, 0.4) 100%);
left: 100%;
-webkit-animation: star-fall 3.6s linear infinite;
animation: star-fall 3.6s linear infinite;
}
.star-fall:nth-child(1) {
left: 80px;
bottom: -100px;
}
.star-fall:nth-child(1):after {
-webkit-animation-delay: 2.4s;
animation-delay: 2.4s;
}
.star-fall:nth-child(2) {
left: 200px;
bottom: -200px;
}
.star-fall:nth-child(2):after {
-webkit-animation-delay: 2s;
animation-delay: 2s;
}
.star-fall:nth-child(3) {
left: 430px;
bottom: -50px;
}
.star-fall:nth-child(3):after {
-webkit-animation-delay: 3.6s;
animation-delay: 3.6s;
}
.star-fall:nth-child(4) {
left: 400px;
bottom: 100px;
}
.star-fall:nth-child(4):after {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
@-webkit-keyframes star-fall {
20% {
left: -100%;
}
100% {
left: -100%;
}
}
@keyframes star-fall {
20% {
left: -100%;
}
100% {
left: -100%;
}
}
}
</style>
+268
View File
@@ -0,0 +1,268 @@
<template>
<tm-fullView>
<view class="ma-32">
<tm-coupon :hdata="calcBlogCoupon" color="blue" @click="blogDetailPoupShow=true"></tm-coupon>
</view>
<view class="pb-50">
<tm-form @submit="fnOnSubmit">
<tm-sheet :shadow="24" color="blue">
<view class="text-size-s text-weight-b mb-24">友链信息提交</view>
<tm-sheet :margin="[0,0]" :padding="[0,0]" :shadow="24" classname="overflow">
<tm-input v-model="form.displayName" align="right" name="displayName" placeholder="请输入网站名称"
required title="名称"></tm-input>
<tm-input v-model="form.url" align="right" name="url" placeholder="请输入网站地址" required
title="网址"></tm-input>
<tm-input v-model="form.logo" align="right" name="logo" placeholder="请输入网站Logo" required
title="Logo"></tm-input>
<tm-input v-model="form.email" align="right" name="email" placeholder="请输入邮箱" required
title="邮箱"></tm-input>
<tm-input v-model="form.linkPageUrl" align="right" name="linkPageUrl" placeholder="请输入友链页面地址"
title="友链页面"></tm-input>
<view class="py-12 px-24 mx-12 round-3 border-b-1 grey text">
<text class="text-grey text-size-xs px-10">
<tm-helpTips :show="true" color="bg-gradient-blue-lighten-b" direction="bottom"
label="展示本站链接的页面地址,用于校验是否添加本站友链" ment-direction="left" tip-direction="left">
<tm-icons :fllowTheme="true" name="icon-question-circle"></tm-icons>
</tm-helpTips>
贵站友情链接页面地址即包含本站链接也页面
</text>
</view>
<tm-input v-model="form.rssUrl" align="right" name="rssUrl" placeholder="请输入RSS地址"
title="RSS地址"></tm-input>
<view class="py-12 px-24 mx-12 round-3 border-b-1 grey text">
<text class="text-grey text-size-xs px-10">
<tm-helpTips :show="true" color="bg-gradient-blue-lighten-b" direction="bottom"
label="用于抓取文章" ment-direction="left" tip-direction="left">
<tm-icons :fllowTheme="true" name="icon-question-circle"></tm-icons>
</tm-helpTips>
用于抓取文章
</text>
</view>
<tm-input v-model="form.description" :border-bottom="false" :height="100" :maxlength="30"
:vertical="true" bg-color="grey-lighten-5" input-type="textarea" name="description"
placeholder="请输入网站描述,不超过30字符" required title="网站描述"></tm-input>
<view class="px-24">
<tm-button block navtie-type="form" theme="bg-gradient-blue-accent">提交数据</tm-button>
<view class="py-32 text-size-s text-grey text-align-center">友链申请</view>
</view>
</tm-sheet>
</tm-sheet>
</tm-form>
<tm-poup v-model="blogDetailPoupShow" :round="6" :width="640" height="auto" position="center">
<view class="poup pa-36" scroll-y="auto">
<view class="info flex">
<view class="poup-logo pa-4 shadow-24">
<image :src="blogDetail.blogLogo" class="poup-logo_img" mode="aspectFill"></image>
</view>
<view class="pl-24 info-detail">
<view class="poup-name text-size-lg text-weight-b">{{ blogDetail.blogName }}</view>
<view class="poup-tag ml--10 text-size-n mt-10 text-grey">
{{ blogDetail.blogDesc }}
</view>
</view>
</view>
<view class="poup-desc">
<text>{{ calcBlogContent }}</text>
</view>
<!-- 博客预览图 -->
<view class="mt-24">
<tm-images :round="2" :src="calcSiteThumbnail(blogDetail.blogUrl)" :width="568"
mode="aspectFill"></tm-images>
</view>
</view>
<view class="poup-link flex flex-center mb-24">
<tm-button theme="light-blue" size="n" @click="fnCopyLink(blogDetail.blogUrl)">复制友链交换信息</tm-button>
<tm-button text theme="white" @click="blogDetailPoupShow = false">关闭</tm-button>
</view>
</tm-poup>
</view>
</tm-fullView>
</template>
<script>
import tmFullView from "@/tm-vuetify/components/tm-fullView/tm-fullView.vue"
import tmMenubars from "@/tm-vuetify/components/tm-menubars/tm-menubars.vue"
import tmSheet from "@/tm-vuetify/components/tm-sheet/tm-sheet.vue"
import tmForm from "@/tm-vuetify/components/tm-form/tm-form.vue"
import tmActionSheet from "@/tm-vuetify/components/tm-actionSheet/tm-actionSheet.vue"
import tmButton from "@/tm-vuetify/components/tm-button/tm-button.vue"
import tmInput from "@/tm-vuetify/components/tm-input/tm-input.vue"
import tmHelpTips from "@/tm-vuetify/components/tm-helpTips/tm-helpTips.vue"
import tmIcons from "@/tm-vuetify/components/tm-icons/tm-icons.vue"
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue'
import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue'
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue'
import tmCoupon from '@/tm-vuetify/components/tm-coupon/tm-coupon.vue'
export default {
components: {
tmFullView,
tmSheet,
tmMenubars,
tmForm,
tmActionSheet,
tmButton,
tmInput,
tmHelpTips,
tmIcons,
tmPoup,
tmImages,
tmTags,
tmCoupon
},
data() {
return {
blogDetailPoupShow: false,
form: {
url: '', // 网址
name: '', // 名称
logo: '', // Logo
linkPageUrl: '', // 友链页面
team: '自助提交', // 分组
description: '' // 描述
}
};
},
computed: {
haloPluginConfigs(){
return this.$tm.vx.getters().getConfigs.pluginConfig;
},
blogDetail() {
return this.haloPluginConfigs.submitLink;
},
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;
};
},
calcBlogContent() {
return `
博客名称:${this.blogDetail.blogName}
博客地址:${this.blogDetail.blogUrl}
博客logo${this.blogDetail.blogLogo}
博客简介:${this.blogDetail.blogDesc}
`
},
calcBlogCoupon() {
return {
img: this.blogDetail.blogLogo,
title: this.blogDetail.blogName,
time: this.blogDetail.blogDesc,
btnText: '友链详情'
}
}
},
onLoad() {
this.fnSetPageTitle('友链申请');
},
methods: {
fnOnSubmit(e) {
if (e === false) {
return uni.$tm.toast('请检查所有的必填项是否填写完整!');
}
if (this.form.url && !uni.$tm.test.url(this.form.url)) {
return uni.$tm.toast('请输入正确的网站地址!');
}
if (this.form.logo && !uni.$tm.test.url(this.form.logo)) {
return uni.$tm.toast('请输入正确的Logo地址!');
}
if (this.form.email && !uni.$tm.test.email(this.form.email)) {
return uni.$tm.toast('请输入正确的邮箱地址!');
}
if (this.form.linkPageUrl && !uni.$tm.test.url(this.form.linkPageUrl)) {
return uni.$tm.toast('请输入正确的友链页面地址!');
}
if (this.form.rssUrl && !uni.$tm.test.url(this.form.rssUrl)) {
return uni.$tm.toast('请输入正确的RSS地址!');
}
this.fnHandle();
},
fnHandle() {
uni.showLoading({
title: '正在提交...'
});
this.$httpApi.v2.submitLink(this.form)
.then(res => {
if (res.code === 200) {
uni.$tm.toast('友链提交成功!');
setTimeout(() => {
uni.navigateTo({
url: '/pagesA/friend-links/friend-links',
success() {
let page = getCurrentPages().pop(); //跳转页面成功之后
if (!page) return;
page.onLoad(); //如果页面存在,则重新刷新页面
}
});
}, 1000);
} else {
uni.$tm.toast('操作失败,请重试!');
}
})
.catch(err => {
uni.$tm.toast(err.msg);
});
},
fnCopyLink() {
uni.setClipboardData({
data: this.calcBlogContent,
showToast: false,
success: () => {
uni.showToast({
icon: 'none',
title: '复制成功!'
});
},
fail: () => {
uni.showToast({
icon: 'none',
title: '复制失败!'
});
}
});
}
}
}
</script>
<style lang="scss" scoped>
.poup-logo {
width: 140rpx;
height: 140rpx;
border-radius: 24rpx;
overflow: hidden;
&_img {
width: 100%;
height: 100%;
}
}
.poup-desc {
margin-top: -20rpx;
font-size: 28rpx;
line-height: 1.8;
color: #555 !important;
}
.info-detail {
width: 0;
flex-grow: 1;
justify-content: center;
}
pre {
//方法一:保留空白符序列,但是正常地进行换行。
white-space: pre-wrap;
//方法二:添加横向滚动条
overflow-x: auto;
}
</style>
+155
View File
@@ -0,0 +1,155 @@
<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>
<tm-skeleton model="listAvatr"></tm-skeleton>
</view>
<block v-else>
<view class="empty" v-if="dataList.length == 0">
<tm-empty icon="icon-shiliangzhinengduixiang-"
label="该标签下暂无文章"></tm-empty>
</view>
<block v-else>
<block v-for="(article, index) in dataList" :key="article.metadata.name">
<!-- 文章卡片 -->
<tm-translate animation-name="fadeUp" :wait="calcAniWait(index)">
<article-card :article="article" @on-click="fnToArticleDetail"></article-card>
</tm-translate>
</block>
<view class="load-text">{{ loadMoreText }}</view>
</block>
<tm-flotbutton @click="fnToTopPage" color="light-blue" size="m" icon="icon-angle-up"></tm-flotbutton>
</block>
</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 tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import ArticleCard from '@/components/article-card/article-card.vue';
export default {
components: {
tmSkeleton,
tmTranslate,
tmFlotbutton,
tmEmpty,
ArticleCard
},
data() {
return {
loading: 'loading',
queryParams: {
name: "",
size: 10,
page: 0
},
name: '',
pageTitle: '加载中...',
dataList: [],
hasNext: false,
isLoadMore: false,
loadMoreText: ''
};
},
onLoad(e) {
this.name = e.name;
this.queryParams.name = this.name;
this.pageTitle = e.title;
this.fnGetData();
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
},
onReachBottom(e) {
if (this.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnGetData() {
// uni.showLoading({
// mask: true,
// title: '加载中...'
// });
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi.v2
.getPostByTagName(this.name, this.queryParams)
.then(res => {
this.fnSetPageTitle(`${this.pageTitle} (共${res.total}篇)`);
this.hasNext = res.hasNext;
if (this.isLoadMore) {
this.dataList = this.dataList.concat(res.items);
} else {
this.dataList = res.items;
}
this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
setTimeout(() => {
this.loading = 'success';
}, 500);
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 500);
});
},
//跳转文章详情
fnToArticleDetail(article) {
uni.navigateTo({
url: '/pagesA/article-detail/article-detail?name=' + article.metadata.name,
animationType: 'slide-in-right'
});
}
}
};
</script>
<style lang="scss">
.app-page {
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
padding: 24rpx 0;
background-color: #fafafd;
}
.loading-wrap {
padding: 0 24rpx;
min-height: 100vh;
}
.empty {
height: 60vh;
display: flex;
align-items: center;
justify-content: center;
}
</style>
+14
View File
@@ -0,0 +1,14 @@
<template>
<view>文章标签页面</view>
</template>
<script>
export default {
data() {
return {};
},
methods: {}
};
</script>
<style></style>
-286
View File
@@ -1,286 +0,0 @@
<template>
<view class="app-page">
<!-- 博主信息 -->
<view class="blogger bg-white flex flex-center ma-24 mt-30 pa-24 round-3" @click="fnToModifyProfile">
<image class="avatar" :src="$utils.checkAvatarUrl(bloggerInfo.avatar, true)" mode="aspectFill"></image>
<view class="info pl-24">
<view class="name text-weight-b text-grey-darken-5">{{ bloggerInfo.nickname }} 欢迎回来</view>
<view class="mt-12 text-size-s text-grey-darken-1">{{ bloggerInfo.description || '这个人真懒,没填写描述~' }}</view>
</view>
<view class="modify"><text class="text-grey-darken-1 text-size-m iconfont icon-angle-right"></text></view>
</view>
<!-- 工作台导航 -->
<view class="works bg-white ma-24 mt-36 pt-24 pb-16 flex flex-wrap round-3">
<view class="works-item pt-24 pb-24 flex flex-col flex-center " v-for="(item, index) in workList" :key="index">
<view class="icon flex flex-center round-4" :style="{ backgroundColor: item.bgColor }" @click="fnOnNav(item)">
<text class="haloicon" :class="[item.icon]"></text>
</view>
<view class="mt-12 title text-size-s text-grey-darken-1">{{ item.name }}</view>
</view>
</view>
<tm-bottomnavigation
class="shadow-6"
position="bottom"
:bottom="24"
:safe="false"
:round="3"
:offset-left="12"
border=""
icon-color="blue"
icon-color-grey="blue"
:list="quickBarList"
@change="fnOnNavBar"
></tm-bottomnavigation>
</view>
</template>
<script>
import tmBottomnavigation from '@/tm-vuetify/components/tm-bottomnavigation/tm-bottomnavigation.vue';
export default {
components: { tmBottomnavigation },
data() {
return {
workList: [
{
icon: 'haloicon-shuxie1',
name: '日记管理',
bgColor: 'rgba(80,167,255,1)',
path: '/pagesB/journal/journal',
todo: false
},
{
icon: 'haloicon-wenzhang1',
name: '文章管理',
bgColor: 'rgba(27,187,179,1)',
path: '/pagesB/articles/articles',
todo: false
},
{
icon: 'haloicon-fenlei2',
name: '分类管理',
bgColor: 'rgba(253,103,132,1)',
path: '/pagesB/category/category',
todo: false
},
{
icon: 'haloicon-biaoqian2',
name: '标签管理',
bgColor: '#6638B5',
path: '/pagesB/tags/tags',
todo: false
},
{
icon: 'haloicon-fujian1',
name: '附件管理',
bgColor: 'rgba(253,103,132,1)',
path: '/pagesB/attachment/attachment',
todo: false
},
{
icon: 'haloicon-links',
name: '友链管理',
bgColor: 'rgba(80,167,255,1)',
path: '/pagesB/links/links',
todo: false
},
{
icon: 'haloicon-tukuguanli',
name: '图库管理',
bgColor: 'rgba(254,178,0,1) ',
path: '/pagesB/photos/photos',
todo: false
},
{
icon: 'haloicon-pinglun1',
name: '评论管理',
bgColor: 'rgba(27,187,179,1)',
path: '/pagesB/comments/comments',
todo: false
},
{
icon: 'haloicon-xitongrizhi',
name: '日志管理',
bgColor: '#6638B5',
path: '/pagesB/logs/logs',
todo: false
},
{
icon: 'haloicon-caidan',
name: '菜单设置',
bgColor: 'rgba(254,178,0,1)',
path: '/pagesB/menus/menus',
todo: true
},
{
icon: 'haloicon-password',
name: '修改密码',
bgColor: 'rgba(27,187,179,1)',
path: '/pagesB/password/password',
todo: false
},
{
icon: 'haloicon-shezhi',
name: '系统设置',
bgColor: '#8698A2',
path: '/pagesB/settings/settings',
todo: true
}
],
quickBarList: [
{ key: 'logout', iconSize: 36, icon: 'icon-poweroff', fontSize: 26, value: '退出登录' },
{
key: 'article-edit',
iconSize: 36,
icon: 'icon-plus',
fontSize: 26,
value: '写文章',
custom: true,
customColor: 'blue',
customFontColor: 'white'
},
{ key: 'back-home', iconSize: 36, icon: 'icon-home', fontSize: 26, value: '返回首页' }
]
};
},
computed: {
bloggerInfo() {
return this.$tm.vx.getters().blogger.getBlogger;
}
},
created() {
// this.fnGetData();
this.fnSetPageTitle('欢迎登录工作台');
console.log(this.bloggerInfo);
},
onPullDownRefresh() {
uni.stopPullDownRefresh();
},
methods: {
fnGetData() {
this.$httpApi.admin
.getLinkList()
.then(res => {
console.log('getLinkListsuccess', res);
})
.catch(err => {
console.log('getLinkListerror', err);
});
},
fnToModifyProfile() {
this.$Router.push({
path: '/pagesB/profile/profile'
});
},
fnOnNav(item) {
if (item.todo) return uni.$tm.toast('功能开发中...');
this.$Router.push({
path: item.path
});
},
fnOnNavBar(e) {
if (e.item.key == 'logout') {
uni.$eShowModal({
title: '提示',
content: '是否要退出当前后台管理登录?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
uni.showLoading({
title: '正在退出登录...'
});
uni.$tm.vx.actions('user/adminLogout');
setTimeout(() => {
uni.hideLoading();
uni.switchTab({
url: '/pages/tabbar/about/about'
});
}, 1000);
})
.catch(err => {});
} else if (e.item.key == 'article-edit') {
uni.$tm.toast(e.item.value);
this.$Router.push({
path: '/pagesB/articles/article-edit'
});
} else if (e.item.key == 'back-home') {
uni.$eShowModal({
title: '提示',
content: '是否返回首页?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
uni.switchTab({
url: '/pages/tabbar/home/home'
});
})
.catch(err => {});
}
}
}
};
</script>
<style scoped lang="scss">
.app-page {
width: 100vw;
::v-deep {
.tm-bottomnavigation {
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.05);
}
}
}
.blogger {
box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.05);
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
border: 6rpx solid #ffffff;
box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.07);
}
.info {
width: 0;
flex-grow: 1;
.name {
font-size: 30rpx;
}
}
.modify {
width: 40rpx;
}
}
.works {
box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.05);
&-item {
width: 25%;
box-sizing: border-box;
.icon {
width: 100rpx;
height: 100rpx;
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.1);
.haloicon {
color: #ffffff;
font-size: 66rpx;
}
}
.title {
color: #55647a;
}
}
}
</style>
-687
View File
@@ -1,687 +0,0 @@
<template>
<view class="app-page bg-white pa-12">
<!-- 工具栏区域 -->
<view class="tool-bar-wrap e-fixed bg-white pt-12 pb-16 border-b-1">
<view class="tool-bar flex flex-center text-grey-darken-2">
<text class="halohtmlicon icon-undo" data-method="undo" @click="fnOnToolBarEdit"></text>
<text class="halohtmlicon icon-redo" data-method="redo" @click="fnOnToolBarEdit"></text>
<text class="halohtmlicon icon-img" data-method="insertImg" @click="fnOnToolBarEdit"></text>
<text class="halohtmlicon icon-video" data-method="insertVideo" @click="fnOnToolBarEdit"></text>
<text class="halohtmlicon icon-link" data-method="insertLink" @click="fnOnToolBarEdit"></text>
<text class="halohtmlicon icon-text" data-method="insertText" @click="fnOnToolBarEdit"></text>
<text class="halohtmlicon icon-line" data-method="insertHtml" data-param="<hr style='margin:10px 0;'/>" @click="fnOnToolBarEdit"></text>
</view>
<view class="tool-bar flex flex-center text-grey-darken-2 mt-16">
<text class="halohtmlicon icon-heading" @click="fnOnInsertHead"></text>
<text
class="halohtmlicon icon-quote"
data-method="insertHtml"
data-param="<blockquote style='padding:0 1em;color:#6a737d;border-left:.25em solid #dfe2e5'>引用</blockquote>"
@click="fnOnToolBarEdit"
></text>
<text class="halohtmlicon icon-table" @click="fnOnInsertTable"></text>
<text class="halohtmlicon icon-code" @click="fnOnInsertCode"></text>
<text class="halohtmlicon icon-emoji" data-type="emoji" data-title="插入表情" @click="fnOnOpenDialog"></text>
<text class="halohtmlicon icon-template" data-type="template" data-title="插入模板" @click="fnOnOpenDialog"></text>
<text v-if="editable" class="flex-1 text-align-center iconfont icon-eye" style="font-size: 44rpx;" @click="fnOnPreview()"></text>
<text v-else class="halohtmlicon icon-edit" @click="fnOnPreview()"></text>
</view>
</view>
<!-- 编辑区域 -->
<view class="edit-wrap bg-white round-3">
<mp-html
class="evan-markdown"
lazy-load
ref="markdown"
:editable="editable"
:domain="markdownConfig.domain"
:loading-img="markdownConfig.loadingGif"
:scroll-table="true"
selectable="force"
:tag-style="markdownConfig.tagStyle"
:container-style="markdownConfig.containStyle"
:content="form.content"
:markdown="true"
:showLineNumber="true"
:showLanguageName="true"
:copyByLongPress="true"
/>
</view>
<view class="fixed-bottom bg-white pa-24 ">
<view class="btn-wrap flex flex-center">
<block v-if="queryParams.postsId">
<tm-button :width="200" :height="75" block theme="light-blue" @click="fnSaveAtPublish()">我要发布</tm-button>
<tm-button class="ml-24" :width="200" :height="75" block theme="blue-grey" @click="fnRefreshData()">重新加载</tm-button>
<tm-button class="ml-24" :width="200" :height="75" block theme="red" @click="fnClear()">清空内容</tm-button>
</block>
<block v-else>
<tm-button :width="300" :height="75" block theme="light-blue" @click="fnSaveAtPublish()">我要发布</tm-button>
<tm-button class="ml-24" theme="red" :width="300" :height="75" block @click="fnClear()">清空内容</tm-button>
</block>
</view>
</view>
<!-- 附件选择文件 -->
<attachment-select
v-if="attachmentsSelect.show"
:title="attachmentsSelect.title"
@on-select="fnOnAttachmentsSelect"
@on-close="attachmentsSelect.show = false"
></attachment-select>
<!-- 弹窗 -->
<block v-if="modal">
<view class="mask" />
<view class="modal">
<view class="modal_title">{{ modal.title }}</view>
<view class="flex flex-col flex-center" v-if="modal.type == 'table'">
<tm-input required title="输入行数" input-type="number" v-model="modal.rows"></tm-input>
<tm-input required title="输入列数" input-type="number" v-model="modal.cols"></tm-input>
</view>
<block v-else><input class="modal_input" :value="modal.value" maxlength="-1" auto-focus @input="modalInput" /></block>
<view class="modal_foot">
<view class="modal_button" @tap="modalCancel">取消</view>
<view class="modal_button" style="color:#576b95;border-left:1px solid rgba(0,0,0,.1)" @tap="modalConfirm">确定</view>
</view>
</view>
</block>
<!-- 底部弹窗 -->
<tm-poup v-model="dialog.show" position="bottom" height="auto" @change="fnOnCloseDialog()">
<text class="poup-close" @click="fnOnCloseDialog(false)">×</text>
<view class="poup-head pa-24 pb-0 text-align-center text-weight-b ">{{ dialog.title }}</view>
<view class="poup-body pa-36">
<!-- 表情区域 -->
<block v-if="dialog.type == 'emoji'">
<view class="flex" v-for="(item, index) in emojis" :key="index">
<view class="flex-1 mt-6 mb-6 text-size-xl" v-for="(emoji, index) in item" :key="emoji" :data-emoji="emoji" @click="fnOnInsertEmoji">{{ emoji }}</view>
</view>
</block>
<!-- 模板区域 -->
<block v-else-if="dialog.type == 'template'">
<block v-for="(template, index) in templates" :key="index">
<rich-text :nodes="template" data-method="insertHtml" :data-param="template" @click="fnOnToolBarEdit"></rich-text>
</block>
</block>
</view>
</tm-poup>
<!-- 发布设置弹窗 -->
</view>
</template>
<script>
import { getAdminAccessToken } from '@/utils/auth.js';
import MarkdownConfig from '@/common/markdown/markdown.config.js';
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
import tmSwitch from '@/tm-vuetify/components/tm-switch/tm-switch.vue';
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
import attachmentSelect from '@/components/attachment-select/attachment-select.vue';
export default {
components: {
mpHtml,
tmSkeleton,
tmButton,
tmEmpty,
tmTranslate,
tmFlotbutton,
tmSwitch,
tmPoup,
tmInput,
attachmentSelect
},
data() {
return {
loading: 'loading',
markdownConfig: MarkdownConfig,
queryParams: {
postsId: undefined
},
status: true,
form: {
content: '',
keepRaw: true,
sourceContent: '',
type: 'PUBLIC'
},
modal: null,
editable: true,
attachmentsSelect: {
title: '选择附件',
show: false
},
dialog: {
show: false,
type: ''
},
// 用于插入的 emoji 表情
emojis: [
['😄', '😷', '😂', '😝', '😳', '😱', '😔', '😒', '😉'],
['😎', '😭', '😍', '😘', '🤔', '😕', '🙃', '🤑', '😲'],
['🙄', '😤', '😴', '🤓', '😡', '😑', '😮', '🤒', '🤮']
],
// 用于插入的 html 模板
templates: [
'<section style="text-align: center; margin: 0px auto;"><section style="border-radius: 4px; border: 1px solid #757576; display: inline-block; padding: 5px 20px;"><span style="font-size: 18px; color: #595959;">标题</span></section></section>',
'<div style="width: 100%; box-sizing: border-box; border-radius: 5px; background-color: #f6f6f6; padding: 10px; margin: 10px 0"><div>卡片</div><div style="font-size: 12px; color: gray">正文</div></div>',
'<div style="border: 1px solid gray; box-shadow: 3px 3px 0px #cfcfce; padding: 10px; margin: 10px 0">段落</div>'
]
};
},
onLoad() {
const postsId = this.$Route.query.postsId;
if (postsId == undefined) {
this.fnSetPageTitle('新增文章');
this.form.content = uni.getStorageSync('posts-content');
} else {
this.fnSetPageTitle('编辑文章');
this.queryParams.postsId = postsId;
this.fnGetData();
}
},
onReady() {
/**
* @description 设置获取链接的方法
* @param {String} type 链接的类型(img/video/audio/link
* @param {String} value 修改链接时,这里会传入旧值
* @returns {Promise} 返回线上地址
* type 为音视频时可以返回一个数组作为源地址
* type 为 audio 时,可以返回一个 object,包含 src、name、author、poster 等字段
*/
this.$refs.markdown.getSrc = (type, value) => {
return new Promise((resolve, reject) => {
this.checkEditable()
.then(res => {
if (type === 'img' || type === 'video') {
uni.showActionSheet({
itemList: ['本地选取', '附件选取'],
success: res => {
if (res.tapIndex === 0) {
// 本地选取
if (type === 'img') {
uni.chooseImage({
count: value === undefined ? 9 : 1,
success: res => {
// #ifdef MP-WEIXIN
if (res.tempFilePaths.length == 1 && wx.editImage) {
// 单张图片时进行编辑
wx.editImage({
src: res.tempFilePaths[0],
complete: res2 => {
uni.showLoading({
title: '上传中'
});
this.fnFileUpload(res2.tempFilePath || res.tempFilePaths[0], type).then(res => {
uni.hideLoading();
resolve(res);
});
}
});
} else {
// #endif
uni.showLoading({
title: '上传中'
});
(async () => {
const arr = [];
for (let item of res.tempFilePaths) {
// 依次上传
const src = await this.fnFileUpload(item, type);
arr.push(src);
}
return arr;
})().then(res => {
uni.hideLoading();
resolve(res);
});
// #ifdef MP-WEIXIN
}
// #endif
},
fail: reject
});
} else {
uni.chooseVideo({
success: res => {
uni.showLoading({
title: '上传中'
});
this.fnFileUpload(res.tempFilePath, type).then(res => {
uni.hideLoading();
resolve(res);
});
},
fail: reject
});
}
} else {
// 远程链接
this.callback = {
resolve,
reject
};
this.attachmentsSelect.title = type === 'img' ? '选取图片' : '选取视频';
this.attachmentsSelect.show = true;
}
}
});
} else {
this.callback = {
resolve,
reject
};
let title;
if (type === 'audio') {
title = '音频链接';
} else if (type === 'link') {
title = '链接地址';
}
this.$set(this, 'modal', {
title,
type: 'link',
value
});
}
})
.catch(err => {});
});
};
},
methods: {
// 加载文章数据
fnGetData(refresh = false) {
this.loading = 'loading';
uni.showLoading({
mask: true,
title: '加载中...'
});
this.$httpApi.admin
.getPostsById(this.queryParams.postsId)
.then(res => {
if (res.status == 200) {
this.fnSetPageTitle(`正在编辑 ${res.data.title}`);
this.$set(this, 'form', res.data);
if (refresh) {
this.$nextTick(() => {
this.$refs.markdown.setContent(res.data.content);
});
}
this.loading = 'success';
uni.hideLoading();
} else {
this.loading = 'error';
this.fnSetPageTitle(`文章加载失败`);
uni.$tm.toast('文章加载失败,请点击下方重新加载!');
}
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.fnSetPageTitle(`文章加载失败`);
uni.$tm.toast('文章加载失败,请点击下方重新加载!');
});
},
fnRefreshData(showConfirm = true) {
if (showConfirm) {
uni.$eShowModal({
title: '提示',
content: '您当前的编辑可能未保存,确定要重新加载吗?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
this.fnGetData(true);
})
.catch(err => {});
} else {
this.fnGetData(true);
}
},
// 检查是否可编辑
checkEditable() {
return new Promise((resolve, reject) => {
if (this.editable) {
resolve();
} else {
uni.showModal({
title: '提示',
content: '需要继续编辑吗?',
success: res => {
if (res.confirm) {
this.editable = true;
resolve();
} else {
reject();
}
}
});
}
});
},
// 调用编辑操作
fnOnToolBarEdit(e) {
this.$refs.markdown[e.currentTarget.dataset.method](e.currentTarget.dataset.param);
},
// 监听附件选择
fnOnAttachmentsSelect(file) {
this.attachmentsSelect.show = false;
this.callback.resolve(file.path);
},
// 处理模态框
modalInput(e) {
this.value = e.detail.value;
},
modalConfirm() {
if (this.modal.type == 'table') {
if (this.modal.rows <= 0) {
return uni.$tm.toast('行数必须大于0');
}
if (this.modal.cols <= 0) {
return uni.$tm.toast('列数必须大于0');
}
}
this.callback.resolve(this.value || this.modal.value || '');
this.$set(this, 'modal', null);
},
modalCancel() {
this.callback.reject();
this.$set(this, 'modal', null);
},
// 上传图片方法
fnFileUpload(src, type) {
return new Promise((resolve, reject) => {
uni.uploadFile({
filePath: src,
header: {
'admin-authorization': getAdminAccessToken()
},
url: this.$baseApiUrl + '/api/admin/attachments/upload',
name: 'file',
success: upladRes => {
const _uploadRes = JSON.parse(upladRes.data);
if (_uploadRes.status == 200) {
resolve(_uploadRes.data.path);
} else {
uni.$tm.toast(_uploadRes.message);
reject();
}
},
fail: err => {
uni.$tm.toast(err.message);
reject();
}
});
});
},
// 插入 head 系列标签
fnOnInsertHead() {
this.checkEditable()
.then(() => {
const _hList = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
wx.showActionSheet({
itemList: _hList,
success: res => {
let tagName = _hList[res.tapIndex];
this.$refs.markdown.insertHtml(`<${tagName}>标题</${tagName}>`);
}
});
})
.catch(() => {});
},
// 插入表格
fnOnInsertTable() {
this.checkEditable()
.then(() => {
this.$set(this, 'modal', {
title: '插入表格',
type: 'table',
rows: 1,
cols: 1,
value: this.value
});
this.callback = {
resolve: () => {
this.$refs.markdown.insertTable(this.modal.rows, this.modal.cols);
},
reject: () => {}
};
})
.catch(() => {});
},
// 保存插入表格
fnOnSaveInsertTable() {
this.callback.resolve(this.value || this.modal.value || '');
},
// 插入代码
fnOnInsertCode() {
this.checkEditable()
.then(() => {
uni.showActionSheet({
itemList: ['html', 'css', 'javascript', 'json'],
success: res => {
const lan = ['html', 'css', 'javascript', 'json'][res.tapIndex];
this.$refs.markdown.insertHtml(`<pre><code class="language-${lan}">${lan} code</code></pre>`);
}
});
})
.catch(() => {});
},
// 插入 emoji
fnOnInsertEmoji(e) {
this.$refs.markdown.insertHtml(e.currentTarget.dataset.emoji);
this.fnOnCloseDialog();
},
// 处理底部弹窗
fnOnOpenDialog(e) {
this.checkEditable()
.then(() => {
this.dialog.type = e.currentTarget.dataset.type;
this.dialog.title = e.currentTarget.dataset.title;
this.dialog.show = true;
})
.catch(() => {});
},
fnOnCloseDialog(e) {
if (e == false) {
this.dialog.show = false;
this.dialog.type = '';
}
},
fnOnPreview() {
this.editable = !this.editable;
if (this.editable) {
uni.$tm.toast('您已进入编辑模式!');
} else {
uni.$tm.toast('您已进入预览模式!');
let _content = this.$refs.markdown.getContent();
if (_content === '<p></p>') {
_content = '';
}
this.form.content = _content;
uni.setStorageSync('posts-content', _content);
}
},
// 发布
fnSaveAtPublish() {
uni.showLoading({
mask: true,
title: '保存中...'
});
const _content = this.$refs.markdown.getContent();
if (!_content.trim()) {
return uni.$tm.toast('请输入内容!');
}
this.form.content = _content;
this.form.sourceContent = this.$refs.markdown.getText();
uni.setStorageSync('posts-content', _content);
uni.setStorageSync('posts-content-source', this.form.sourceContent);
if (this.form.id) {
this.$Router.push({
path: '/pagesB/articles/article-setting',
query: { postsId: this.form.id, postTitle: this.form.title, isEdit: true, from: 'edit' }
});
} else {
this.$Router.push({ path: '/pagesB/articles/article-setting', query: { postsId: '', postTitle: '', isEdit: false, from: 'edit' } });
}
return;
if (this.form) {
this.$httpApi.admin
.updatePostsById(this.form.id, this.form)
.then(res => {
if (res.status == 200) {
uni.$tm.toast('保存成功!');
uni.setStorageSync('posts-content', '');
setTimeout(() => {
uni.$emit('refresh-article-list');
}, 500);
} else {
uni.$tm.toast('保存失败,请重试!');
}
})
.catch(err => {
uni.$tm.toast('保存失败,请重试!');
});
} else {
this.$httpApi.admin
.createPosts(this.form)
.then(res => {
if (res.status == 200) {
uni.$tm.toast('保存成功!');
uni.setStorageSync('posts-content', '');
setTimeout(() => {
uni.$emit('refresh-article-list');
}, 500);
} else {
uni.$tm.toast('保存失败,请重试!');
}
})
.catch(err => {
uni.$tm.toast('保存失败,请重试!');
});
}
},
fnClear() {
uni.$eShowModal({
title: '提示',
content: '确定清空当前内容吗?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
this.$refs.markdown.clear();
uni.setStorageSync('posts-content', '');
uni.setStorageSync('posts-content-source', '');
this.fnToTopPage();
})
.catch(() => {});
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
min-height: 100vh;
box-sizing: border-box;
padding-bottom: 236rpx;
}
.fixed-bottom {
position: fixed;
left: 0;
bottom: 0;
right: 0;
box-sizing: border-box;
box-shadow: 0rpx -4rpx 24rpx rgba(0, 0, 0, 0.05);
border-radius: 24rpx 24rpx 0 0;
}
.edit-wrap {
box-sizing: border-box;
padding-top: 150rpx;
}
.modal {
width: 80vw;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
border-radius: 24rpx;
}
.modal_title {
padding-top: 42rpx;
padding-bottom: 32rpx;
font-size: 34rpx;
font-weight: 700;
text-align: center;
}
.modal_input {
display: block;
padding: 12rpx 12rpx;
margin: 24rpx;
margin-top: 0;
font-size: 30rpx;
border: 2rpx solid #dfe2e5;
border-radius: 6rpx;
}
.modal_foot {
display: flex;
line-height: 100rpx;
font-weight: bold;
border-top: 2rpx solid rgba(0, 0, 0, 0.1);
}
.modal_button {
flex: 1;
text-align: center;
}
/* 蒙版 */
.mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: black;
opacity: 0.5;
}
.poup-close {
position: absolute;
right: 30rpx;
top: 8rpx;
font-size: 50rpx;
}
</style>
-760
View File
@@ -1,760 +0,0 @@
<template>
<view class="app-page" :class="{ 'is-balck grey-darken-6': isBlackTheme }">
<!-- 文章设置页面 -->
<view class="e-fixed shadow-2"><tm-tabs color="light-blue" :shadow="0" v-model="tab.activeIndex" :list="tab.list" align="center"></tm-tabs></view>
<!-- 占位区域 -->
<view style="width: 100vw;height: 90rpx;"></view>
<view class="pa-24" v-if="loading != 'success'">
<block v-if="loading == 'loading'">
<tm-skeleton model="list"></tm-skeleton>
<tm-skeleton model="list"></tm-skeleton>
<tm-skeleton model="list"></tm-skeleton>
<tm-skeleton model="list"></tm-skeleton>
</block>
<view v-else class="flex flex-center" style="height:60vh;">
<tm-empty color="blue"><tm-button theme="blue" @click="fnGetSettings()">刷新试试</tm-button></tm-empty>
</view>
</view>
<view v-else class="app-page-content ma-24 bg-white round-3 ">
<!-- 表单区域 -->
<tm-form ref="formData">
<!-- 基本设置 -->
<view v-show="tab.activeIndex == 0">
<tm-input name="title" required title="文章标题" placeholder="请输入文章标题..." v-model="article.title"></tm-input>
<tm-input name="slug" required title="文章别名" v-model="article.slug" placeholder="请输入文章别名..."></tm-input>
<tm-pickers
title="选择文章的状态"
btn-color="light-blue"
:default-value.sync="articleStatus.status"
rang-key="name"
:list="articleStatus.list"
@confirm="fnOnArticleStatusConfirm"
>
<tm-input
name="status"
required
title="文章状态"
placeholder="请选择文章状态"
disabled
:value="articleStatus.selectText"
right-icon="icon-angle-right"
></tm-input>
</tm-pickers>
<tm-input
v-if="articleStatus.selectValue == 'INTIMATE'"
name="password"
:password="true"
required
title="文章密码"
placeholder="请输入密码..."
v-model="article.password"
></tm-input>
<view v-if="articleStatus.selectValue == 'INTIMATE'" class="pl-32 mt-12 mb-24 input-tips text-grey text-size-s">
填写提示文章状态为私有的时候需要填写密码
</view>
<view class="mx-32 my-24 pb-24 border-b-1">
<view class="text-size-m required flex-between">
<text>选择分类</text>
<tm-button size="xs" theme="light-blue" :fab="true" :shadow="0" icon="icon-plus" @click="catePoupShow = true">新增</tm-button>
</view>
<view>
<tm-groupcheckbox name="categoryIds">
<tm-checkbox :dense="true" :name="item.id" v-for="(item, index) in categories" :key="index" v-model="item.checked">
<template v-slot:default="{ checkData }">
<tm-button :flat="true" size="s" :theme="checkData.checked ? 'light-blue' : 'grey-lighten-4'" :plan="true">{{ item.name }}</tm-button>
</template>
</tm-checkbox>
</tm-groupcheckbox>
</view>
</view>
<view class="mx-32 my-24 pb-24">
<view class="text-size-m required flex-between">
<text>选择标签</text>
<tm-button size="xs" theme="light-blue" :fab="true" :shadow="0" icon="icon-plus" @click="tagPoupShow = true">新增</tm-button>
</view>
<view>
<tm-groupcheckbox name="tagIds">
<tm-checkbox :dense="true" :name="item.id" v-for="(item, index) in tags" :key="index" v-model="item.checked">
<template v-slot:default="{ checkData }">
<tm-button :flat="true" size="s" :theme="checkData.checked ? 'light-blue' : 'grey-lighten-4'" :plan="true">{{ item.name }}</tm-button>
</template>
</tm-checkbox>
</tm-groupcheckbox>
</view>
</view>
</view>
<!-- 高级设置 -->
<view v-show="tab.activeIndex == 1" class="pt-6">
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m ">禁止评论</text>
<tm-switch v-model="article.disallowComment" color="light-blue" :text="['是', '否']"></tm-switch>
</view>
<view class="mx-32 my-24 border-b-1 pb-24 flex-between">
<text class="text-size-m ">是否置顶</text>
<tm-switch v-model="topPriority" color="light-blue" :text="['是', '否']"></tm-switch>
</view>
<tm-pickersDate
@confirm="fnOnConfirmPublishTime"
:default-value="createTime"
:show-detail="{ year: true, month: true, day: true, hour: false, min: false, sec: false }"
>
<tm-input name="createTime" title="发表时间" placeholder="请选择发表时间" disabled :value="createTime" right-icon="icon-angle-right"></tm-input>
</tm-pickersDate>
<tm-input
:vertical="true"
:height="150"
input-type="textarea"
bg-color="grey-lighten-5"
:maxlength="200"
title=" 文章摘要"
placeholder="如不填写,会从文章中自动截取"
v-model="article.summary"
></tm-input>
<view class="ma-30 mt-12 pb-12">
<view class="mb-12">
<text class="text-size-m">封面图</text>
<text class="text-grey text-size-s">点击下方图片区域选择封面</text>
</view>
<image v-if="article.showThumbnail" class="thumbnail round-3" :src="article.showThumbnail" mode="aspectFill" @click="attachmentsSelectShow = true"></image>
<view v-else class="thumbnail round-3 text-grey grey-lighten-5 flex flex-col flex-center " @click="attachmentsSelectShow = true">
<text class="iconfont icon-picture" style="font-size: 46rpx;"></text>
<text class="mt-12 text-size-m">选择文章封面图</text>
</view>
</view>
</view>
<view v-show="tab.activeIndex == 2" class="pt-12">
<tm-input
:vertical="true"
:height="150"
input-type="textarea"
bg-color="grey-lighten-5"
:maxlength="200"
title="自定义关键字"
placeholder="多个关键词以英文逗号隔开,如不填写,将自动使用标签作为关键词"
v-model="article.metaKeywords"
></tm-input>
<tm-input
:vertical="true"
:height="200"
:borderBottom="false"
input-type="textarea"
bg-color="grey-lighten-5"
:maxlength="200"
title="自定义描述"
placeholder="如不填写,会从文章中自动截取"
v-model="article.metaDescription"
></tm-input>
</view>
</tm-form>
<!-- 操作区域 -->
<view class="btn-bar flex flex-center bg-white">
<tm-button theme="blue" :shadow="0" block :height="70" @click="fnOnSave(false, false)">立即保存</tm-button>
<tm-button theme="light-blue" :shadow="0" block :height="70" @click="fnOnSave(false, true)">保存并返回</tm-button>
<!-- <block v-if="from == 'edit' && isEdit">
<tm-button
v-if="article.status == 'DRAFT' || article.status == 'INTIMATE' || article.status == 'RECYCLE'"
theme="light-blue"
:shadow="0"
block
:height="60"
size="m"
@click="fnOnChangeStatus('PUBLISHED')"
>
转为发布
</tm-button>
<tm-button
v-if="article.status == 'PUBLISHED' || article.status == 'RECYCLE'"
theme="red"
:shadow="0"
block
:height="60"
size="m"
@click="fnOnChangeStatus('DRAFT')"
>
转为草稿
</tm-button>
</block>
<block v-else-if="from == 'edit' && isEdit == false">
<tm-button theme="light-blue" :shadow="0" block :height="60" size="m" @click="fnOnChangeCreateStatus('PUBLISHED')">立即发布</tm-button>
<tm-button theme="red" :shadow="0" block :height="60" size="m" @click="fnOnChangeCreateStatus('DRAFT')">存为草稿</tm-button>
</block>
<block v-else>
<tm-button
v-if="article.status == 'DRAFT' || article.status == 'RECYCLE'"
theme="light-blue"
:shadow="0"
block
:height="60"
size="m"
@click="fnOnChangeStatus('PUBLISHED')"
>
转为发布
</tm-button>
<tm-button
v-if="article.status == 'PUBLISHED' || article.status == 'RECYCLE'"
theme="red"
:shadow="0"
block
:height="60"
size="m"
@click="fnOnChangeStatus('DRAFT')"
>
转为草稿
</tm-button>
</block> -->
<tm-button theme="blue-grey" :shadow="0" block :height="70" @click="fnOnBack()">{{ from == 'list' ? '返回列表' : '返回编辑' }}</tm-button>
</view>
<!-- 附件选择文件 -->
<attachment-select selectType="image" v-if="attachmentsSelectShow" @on-select="fnOnAttachmentsSelect" @on-close="attachmentsSelectShow = false"></attachment-select>
<!-- 新增分类 -->
<tm-poup v-model="catePoupShow" position="bottom" @change="fnOnCatePoupChange" height="45vh">
<view class="poup-content">
<view class="poup-head text-align-center text-weight-b text-size-g ma-24">新增分类</view>
<scroll-view class="poup-body pa-24 pt-0" style="height: 28vh;" :scroll-y="true" @touchmove.stop>
<tm-input
required
:adjust-position="true"
:round="3"
:borderBottom="false"
title="名称"
bg-color="grey-lighten-5"
v-model="cateForm.name"
placeholder="请输入分类名称"
></tm-input>
<view class="pl-32 mb-24 input-tips text-grey text-size-s">填写提示页面上所显示的名称</view>
<tm-input
:borderBottom="false"
:adjust-position="true"
:round="3"
title="别名"
bg-color="grey-lighten-5"
v-model="cateForm.slug"
placeholder="请输入分类别名"
></tm-input>
<view class="pl-32 mb-24 input-tips text-grey text-size-s">填写提示一般为单个分类页面的标识最好为英文</view>
<!-- <tm-input
:borderBottom="false"
:adjust-position="true"
:round="3"
title="密码"
bg-color="grey-lighten-5"
v-model="cateForm.password"
placeholder="请输入分类密码"
></tm-input>
<view class="pl-32 mb-24 input-tips text-grey text-size-s">填写提示分类的访问密码</view>
<tm-input
:borderBottom="false"
:vertical="true"
:adjust-position="true"
inputType="textarea"
:round="3"
title="描述"
:height="160"
bg-color="grey-lighten-5"
v-model="cateForm.description"
placeholder="请输入分类描述"
></tm-input>
<view class="pl-32 mb-24 input-tips text-grey text-size-s">填写提示分类的描述信息</view> -->
</scroll-view>
<view class="btn-wrap flex flex-center">
<tm-button size="m" theme="bg-gradient-blue-accent" @click="fnOnCateSave()"> </tm-button>
<tm-button size="m" theme="bg-gradient-blue-grey-accent" @click="fnOnCatePoupHide()"> </tm-button>
</view>
</view>
</tm-poup>
<!-- 新增标签 -->
<tm-poup v-model="tagPoupShow" position="bottom" @change="fnOnTagPoupChange" height="45vh">
<view class="poup-content">
<view class="poup-head text-align-center text-weight-b text-size-g ma-24">新增标签</view>
<scroll-view class="poup-body pa-24 pt-0" style="height: 28vh;" :scroll-y="true" @touchmove.stop>
<tm-input
required
:adjust-position="true"
:round="3"
:borderBottom="false"
title="标签名称"
bg-color="grey-lighten-5"
v-model="tagForm.name"
placeholder="请输入标签名称"
></tm-input>
<view class="pl-32 mb-24 input-tips text-grey text-size-s">填写提示页面上所显示的名称</view>
<tm-input
:borderBottom="false"
:adjust-position="true"
:round="3"
title="标签别名"
bg-color="grey-lighten-5"
v-model="tagForm.slug"
placeholder="请输入分类别名"
></tm-input>
<view class="pl-32 mb-24 input-tips text-grey text-size-s">填写提示一般为单个标签页面的标识最好为英文</view>
</scroll-view>
<view class="btn-wrap flex flex-center">
<tm-button size="m" theme="bg-gradient-blue-accent" @click="fnOnTagSave()"> </tm-button>
<tm-button size="m" theme="bg-gradient-blue-grey-accent" @click="fnOnTagPoupHide()"> </tm-button>
</view>
</view>
</tm-poup>
</view>
</view>
</template>
<script>
import { getAdminAccessToken } from '@/utils/auth.js';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmTabs from '@/tm-vuetify/components/tm-tabs/tm-tabs.vue';
import tmForm from '@/tm-vuetify/components/tm-form/tm-form.vue';
import tmGroupcheckbox from '@/tm-vuetify/components/tm-groupcheckbox/tm-groupcheckbox.vue';
import tmCheckbox from '@/tm-vuetify/components/tm-checkbox/tm-checkbox.vue';
import tmPickers from '@/tm-vuetify/components/tm-pickers/tm-pickers.vue';
import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
import tmPickersDate from '@/tm-vuetify/components/tm-pickersDate/tm-pickersDate.vue';
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import tmSwitch from '@/tm-vuetify/components/tm-switch/tm-switch.vue';
import AttachmentSelect from '@/components/attachment-select/attachment-select.vue';
export default {
components: {
tmButton,
tmTabs,
tmForm,
tmGroupcheckbox,
tmCheckbox,
tmPickers,
tmInput,
tmPickersDate,
tmPoup,
tmSkeleton,
tmEmpty,
tmSwitch,
AttachmentSelect
},
data() {
return {
isBlackTheme: false,
loading: 'loading',
uploadAvatarShow: false,
attachmentsSelectShow: false,
postsId: undefined,
postTitle: undefined,
createTime: undefined,
isEdit: true,
from: 'list',
topPriority: false,
sfList: [{ title: '是', checked: null }, { title: '否', checked: null }],
tab: {
activeIndex: 0,
list: ['常规', '高级', 'SEO']
},
article: {
content: '',
keepRaw: true,
sourceContent: '',
type: 'PUBLIC'
},
articleStatus: {
list: [{ name: '发布', value: 'PUBLISHED' }, { name: '私有', value: 'INTIMATE' }, { name: '草稿', value: 'DRAFT' }, { name: '回收站', value: 'RECYCLE' }],
status: ['PUBLISHED'],
selectText: '发布',
selectValue: 'PUBLISHED'
},
// 分类
categories: [],
catePoupShow: false,
cateForm: {
name: undefined,
slug: undefined,
password: undefined,
description: undefined
},
// 标签
tags: [],
tagPoupShow: false,
tagForm: {
name: undefined,
slug: undefined
}
};
},
onLoad(e) {
const postsId = this.$Route.query.postsId;
const postTitle = this.$Route.query.postTitle;
const isEdit = this.$Route.query.isEdit;
const from = this.$Route.query.from;
this.postsId = postsId;
this.postTitle = postTitle || '';
this.isEdit = isEdit;
this.from = from;
this.createTime = uni.$tm.dayjs(new Date().getTime()).format('YYYY-MM-DD HH:mm:ss');
this.fnGetSettings();
},
onPullDownRefresh() {
if (this.isEdit == false) {
uni.stopPullDownRefresh();
return;
}
this.fnGetSettings();
},
methods: {
fnOnCatePoupChange(e) {
if (!e) {
this.fnResetCateForm();
}
},
fnResetCateForm() {
this.cateForm = {
color: undefined,
createTime: undefined,
fullPath: undefined,
id: undefined,
name: '',
slug: undefined,
thumbnail: undefined,
showThumbnail: undefined
};
},
fnOnCatePoupHide() {
this.catePoupShow = false;
this.fnResetCateForm();
},
fnOnCateSave() {
if (this.cateForm.name.trim() == '') {
return uni.$tm.toast('分类名称未填写!');
}
this.$httpApi.admin
.createCategory(this.cateForm)
.then(res => {
if (res.status == 200) {
uni.$tm.toast(`保存成功!`);
this.fnResetCateForm();
// 将数据填充到列表
this.categories.push({
id: res.data.id,
name: res.data.name,
checked: false
});
} else {
uni.$tm.toast('添加成功!');
}
})
.catch(err => {
uni.$tm.toast('添加失败,请重试!');
});
},
fnOnTagPoupChange(e) {
if (!e) {
this.fnResetTagForm();
}
},
fnResetTagForm() {
this.tagForm = {
color: undefined,
createTime: undefined,
fullPath: undefined,
id: undefined,
name: '',
slug: undefined,
thumbnail: undefined,
showThumbnail: undefined
};
},
fnOnTagPoupHide() {
this.tagPoupShow = false;
this.fnResetTagForm();
},
fnOnTagSave() {
if (this.tagForm.name.trim() == '') {
return uni.$tm.toast('标签名称未填写!');
}
this.$httpApi.admin
.createTags(this.tagForm)
.then(res => {
if (res.status == 200) {
uni.$tm.toast(`保存成功!`);
this.fnResetTagForm();
// 将数据填充到列表
this.tags.push({
id: res.data.id,
name: res.data.name,
checked: false
});
} else {
uni.$tm.toast('添加成功!');
}
})
.catch(err => {
uni.$tm.toast('添加失败,请重试!');
});
},
fnOnArticleStatusConfirm(e) {
const status = e[0].data;
this.articleStatus.status = [status.value];
this.articleStatus.selectText = status.name;
this.articleStatus.selectValue = status.value;
},
// 获取文章的设置
fnGetSettings() {
// this.loading = 'loading';
this.loading = 'success';
const _post = this.isEdit ? this.$httpApi.admin.getPostsById(this.postsId) : Promise.resolve();
const _cate = this.$httpApi.admin.getCategoryList({ more: true });
const _tags = this.$httpApi.admin.getTagsList({ more: true });
const _apis = Promise.all([_post, _cate, _tags]);
uni.showLoading({
title: '加载中,请稍等...',
mask: true
});
_apis
.then(res => {
if (this.isEdit) {
const _postRes = res[0];
this.article = _postRes.data;
// 设置文章状态
let _articleStatus = this.articleStatus.list.find(x => x.value == this.article.status);
if (_articleStatus) {
this.articleStatus.status = [_articleStatus.value];
this.articleStatus.selectText = _articleStatus.name;
this.articleStatus.selectValue = _articleStatus.value;
}
this.topPriority = this.article.topPriority == 0 ? false : true;
if (this.article.thumbnail) {
this.$set(this.article, 'showThumbnail', this.$utils.checkThumbnailUrl(this.article.thumbnail));
}
this.createTime = uni.$tm.dayjs(new Date(this.article.createTime).getTime()).format('YYYY-MM-DD HH:mm:ss');
if (this.postTitle) {
this.article.title = this.postTitle;
}
}
const _cateRes = res[1];
let _tempCategories = _cateRes.data.map((item, index) => {
let _isCateCheck = false;
if (this.isEdit) {
_isCateCheck = this.article.categoryIds.some(x => x == item.id);
}
return {
index: _isCateCheck ? 0 : index + 1,
id: item.id,
name: item.name,
checked: _isCateCheck
};
});
if (this.isEdit) {
this.categories = _tempCategories.sort((a, b) => a.index - b.index);
} else {
this.categories = _tempCategories;
}
const _tagRes = res[2];
let _tempTags = _tagRes.data.map((item, index) => {
let _isTagCheck = false;
if (this.isEdit) {
_isTagCheck = this.article.tagIds.some(x => x == item.id);
}
return {
index: _isTagCheck ? 0 : index + 1,
id: item.id,
name: item.name,
checked: _isTagCheck
};
});
if (this.isEdit) {
this.tags = _tempTags.sort((a, b) => a.index - b.index);
} else {
this.tags = _tempTags;
}
this.loading = 'success';
uni.hideLoading();
})
.catch(err => {
this.$tm.toast('数据加载失败,请重试!');
this.loading = 'error';
})
.finally(() => {
uni.stopPullDownRefresh();
});
},
// 监听附件选择
fnOnAttachmentsSelect(file) {
this.article.thumbnail = file.path;
this.$set(this.article, 'showThumbnail', this.$utils.checkThumbnailUrl(file.path));
this.attachmentsSelectShow = false;
},
fnOnConfirmPublishTime(e) {
this.createTime = `${e.year}-${e.month}-${e.day} 00:00:00`;
this.article.createTime = new Date(`${e.year}-${e.month}-${e.day}`).getTime();
},
// 修改文章的状态
fnOnChangeStatus(status) {
if (this.article.status == 'INTIMATE') {
this.article['password'] = '';
}
this.article.status = status;
this.fnOnSave(true);
},
fnOnChangeCreateStatus(status) {
this.article.status = status;
this.fnOnSave();
},
// 保存文章设置
fnOnSave(isChangeStatus = false, isBack = true) {
// 校验数据
if (!this.article.title) {
return uni.$tm.toast('请输入文章标题!');
}
if (!this.article.slug) {
return uni.$tm.toast('请输入文章别名!');
}
if (this.articleStatus.selectValue == 'INTIMATE' && !this.article.password) {
return uni.$tm.toast('文章私有状态,缺少密码!');
}
uni.showLoading({
title: '保存中...',
mask: true
});
// 设置文章内容
if (this.from == 'edit') {
this.article.content = uni.getStorageSync('posts-content');
this.article.sourceContent = uni.getStorageSync('posts-content-source');
}
if (this.articleStatus.selectValue != 'INTIMATE') {
this.article.password = '';
}
this.article.status = this.articleStatus.selectValue;
this.article.topPriority = this.topPriority ? 1 : 0;
this.article.tagIds = this.tags.filter(item => item.checked == true).map(item => item.id);
this.article.categoryIds = this.categories.filter(item => item.checked == true).map(item => item.id);
if (this.isEdit) {
this.$httpApi.admin
.updatePostsById(this.postsId, this.article)
.then(res => {
if (res.status == 200) {
this.$tm.toast('保存成功!');
uni.setStorageSync('posts-html-edit', '');
uni.setStorageSync('posts-content', '');
uni.setStorageSync('posts-content-source', '');
setTimeout(() => {
if (isBack) {
uni.navigateBack({
delta: 1
});
}
uni.$emit('refresh-article-list');
}, 1000);
} else {
uni.$tm.toast('保存失败,请重试!');
if (isChangeStatus) {
this.article.status = this.article.status == 'DRAFT' ? 'PUBLISHED' : 'DRAFT';
}
}
})
.catch(err => {
uni.$tm.toast('保存失败,请重试!');
if (isChangeStatus) {
this.article.status = this.article.status == 'DRAFT' ? 'PUBLISHED' : 'DRAFT';
}
});
} else {
this.article.content = this.article.sourceContent = uni.getStorageInfoSync();
this.$httpApi.admin
.createPosts(this.article)
.then(res => {
if (res.status == 200) {
uni.$tm.toast('发布成功!');
uni.setStorageSync('posts-html-edit', '');
uni.setStorageSync('posts-content', '');
uni.setStorageSync('posts-content-source', '');
setTimeout(() => {
if (isBack) {
uni.navigateBack({
delta: 1
});
}
uni.$emit('refresh-article-list');
}, 1000);
} else {
uni.$tm.toast('发布失败,请重试!');
}
})
.catch(err => {
uni.$tm.toast('发布失败,请重试!');
});
}
},
fnOnBack() {
uni.$eShowModal({
title: '提示',
content: '您当前可能有未保存的数据,确定返回吗?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
uni.navigateBack();
})
.catch(err => {});
}
}
};
</script>
<style scoped lang="scss">
.app-page {
padding-bottom: 110rpx;
.app-page-content {
min-height: calc(100vh - 340rpx);
box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.03);
}
.btn-bar {
width: 100vw;
position: fixed;
left: 0;
bottom: 0;
box-sizing: border-box;
padding: 24rpx;
gap: 0 24rpx;
box-shadow: 0rpx -6rpx 24rpx rgba(0, 0, 0, 0.03);
}
.thumbnail {
width: 100%;
height: 260rpx;
}
}
.required {
position: relative;
padding-left: 18rpx;
&:before {
content: '*';
position: absolute;
left: -4rpx;
top: 50%;
transform: translateY(-50%);
font-size: 34rpx;
color: rgba(244, 67, 54, 1);
}
}
.poup-content {
overflow: hidden;
}
.poup-body {
height: 60vh;
box-sizing: border-box;
touch-action: none;
}
</style>
-404
View File
@@ -1,404 +0,0 @@
<template>
<view class="app-page" :class="{ 'is-balck grey-darken-6': isBlackTheme }">
<!-- 顶部切换 -->
<view class="e-fixed shadow-2">
<tm-search v-model="queryParams.keyword" :round="24" :shadow="0" color="light-blue" insert-color="light-blue" :clear="true">
<view slot="right" class="flex flex-center">
<tm-pickers title="请选择分类" :default-value="category.selected" :list="category.list" rang-key="name" btn-color="blue" @confirm="fnOnCategoryConfirm">
<text class="iconfont icon-filter text-size-lg text-grey-darken-2"></text>
</tm-pickers>
<text class="ml-16 text-size-g" style="min-width: 70rpx;" @click="fnOnSearch">搜索</text>
</view>
</tm-search>
<tm-tabs color="light-blue" :shadow="0" v-model="tab.activeIndex" :list="tab.list" align="center" @change="fnOnTabChange"></tm-tabs>
</view>
<!-- 占位区域 -->
<view style="width: 100vw;height: 184rpx;"></view>
<!-- 加载区域 -->
<view v-if="loading != 'success'" class="loading-wrap pa-24">
<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="app-page-content pl-24 pr-24">
<view v-if="dataList.length == 0" class="content-empty flex flex-center">
<!-- 空布局 -->
<tm-empty icon="icon-shiliangzhinengduixiang-" label="该分类下暂无数据"></tm-empty>
</view>
<block v-else>
<block v-for="(article, index) in dataList" :key="index">
<tm-translate animation-name="fadeUp" :wait="(index + 1) * 50">
<!-- 文章卡片 -->
<view class="article-card mt-24 bg-white pa-24 round-3">
<view class="thumbnail round-2" @click="fnToArticleDetail(article)">
<image v-if="article.hasThumbnail" class="thumbnail-img round-2" :src="article.thumbnail" mode="aspectFill"></image>
<view v-else class="thumbnail-not round-2 flex flex-center text-grey-darken-2">无封面图</view>
</view>
<view class="title mt-16 text-bg-gradient-blue-accent">{{ article.title }}</view>
<view class="summary mt-16 text-grey-darken-2 text-size-m">{{ article.summary }}</view>
<view class="flex mt-12 flex-start desc-box text-size-m">
<view class="label text-grey-darken-2">分类</view>
<view class="value">
<block v-if="article.categories.length != 0">
<tm-tags v-for="(category, categoryIndex) in article.categories" :key="category.slug" color="light-blue" size="s" model="text">
{{ category.name }}
</tm-tags>
</block>
<text v-else class="ml-12 text-grey-darken-2">未选择分类</text>
</view>
</view>
<view class="flex flex-start desc-box text-size-m" :class="{ 'mt-12': article.tags.length == 0 }">
<view class="label text-grey-darken-2">标签</view>
<view class="value">
<block v-if="article.tags.length != 0">
<tm-tags v-for="(tag, tagIndex) in article.tags" :key="tag.slug" color="orange" size="s" model="text">{{ tag.name }}</tm-tags>
</block>
<text v-else class="ml-12 text-grey-darken-2 ">未选择标签</text>
</view>
</view>
<view class="flex flex-start desc-box text-size-m" :class="{ 'mt-12': article.tags.length == 0 }">
<view class="label text-grey-darken-2">状态</view>
<view class="value">
<tm-tags v-if="article.status == 'PUBLISHED'" color="green" size="s" model="text">已发布访客可见</tm-tags>
<tm-tags v-else-if="article.status == 'INTIMATE'" color="light-blue" size="s" model="text">私密访客不可见</tm-tags>
<tm-tags v-else-if="article.status == 'DRAFT'" color="blue-grey" size="s" model="text">草稿未发布</tm-tags>
<tm-tags v-else-if="article.status == 'RECYCLE'" color="deep-orange" size="s" model="text">回收站</tm-tags>
</view>
</view>
<view class="flex mt-12 flex-start desc-box text-size-m">
<view class="label text-grey-darken-2">日期</view>
<view class="value ml-12 text-grey-darken-2">{{ $tm.dayjs(article.createTime).format('YYYY年MM月DD日 HH点mm分ss秒') }}</view>
</view>
<view class="flex mt-12 flex-start desc-box text-size-m ">
<view class="label text-grey-darken-2">统计</view>
<view class="value ml-12 text-grey-darken-2">
<text class="">
<text class="text-size-m text-weight-b text-bg-gradient-blue-accent">{{ article.wordCount }}</text>
<text class="ml-6 text-size-s"></text>
</text>
<text class="ml-20">
<text class="text-size-m text-weight-b text-bg-gradient-light-blue-accent">{{ article.visits }}</text>
<text class="ml-6 text-size-s">浏览</text>
</text>
<text class="ml-20">
<text class="text-size-m text-weight-b text-bg-gradient-cyan-accent">{{ article.likes }}</text>
<text class="ml-6 text-size-s">点赞</text>
</text>
<text class="ml-20">
<text class="text-size-m text-weight-b text-bg-gradient-blue-grey-accent">{{ article.commentCount }}</text>
<text class="ml-6 text-size-s">评论</text>
</text>
</view>
</view>
<view class="foot flex flex-between mt-20 pt-24 desc-box text-size-m">
<tm-button theme="light-blue" :shadow="0" block :width="200" :height="60" size="m" @click="fnOnEditArticle(article)">编辑</tm-button>
<tm-button theme="red" :shadow="0" block :width="200" :height="60" size="m" @click="fnOnDelArticle(article, index)">删除</tm-button>
<tm-button theme="blue-grey" :shadow="0" block :width="200" :height="60" size="m" @click="fnOnSetArticle(article, index)">设置</tm-button>
</view>
</view>
</tm-translate>
</block>
<tm-flotbutton @click="fnToTopPage" :offset="[16, 80]" color="bg-gradient-light-blue-accent" size="m" icon="icon-angle-up"></tm-flotbutton>
<tm-flotbutton :show-text="true" color="bg-gradient-orange-accent" @click="fnOnAddArticle()"></tm-flotbutton>
<view class="load-text">{{ loadMoreText }}</view>
</block>
</view>
</view>
</template>
<script>
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
import tmSearch from '@/tm-vuetify/components/tm-search/tm-search.vue';
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmTabs from '@/tm-vuetify/components/tm-tabs/tm-tabs.vue';
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmPickers from '@/tm-vuetify/components/tm-pickers/tm-pickers.vue';
export default {
components: {
tmSkeleton,
tmSearch,
tmTranslate,
tmTabs,
tmFlotbutton,
tmEmpty,
tmTags,
tmButton,
tmPickers
},
data() {
return {
isBlackTheme: false,
loading: 'loading',
tab: {
activeIndex: 0,
list: ['全部', '已发布', '私密', '草稿', '回收站']
},
queryParams: {
size: 10,
page: 0,
status: '',
keyword: '',
categoryId: undefined
},
cache: {
dataList: [],
total: 0
},
isLoadMore: false,
loadMoreText: '加载中...',
result: {},
dataList: [],
category: {
loading: 'loading',
show: false,
list: [],
selected: [0]
}
};
},
onLoad() {
this.fnSetPageTitle('文章管理');
this.fnGetCategoryList();
},
created() {
this.fnGetData();
uni.$on('refresh-article-list', () => {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
});
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
},
onReachBottom(e) {
if (this.result.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
fnOnTabChange(index) {
this.dataList = [];
const _status = {
0: '',
1: 'PUBLISHED',
2: 'INTIMATE',
3: 'DRAFT',
4: 'RECYCLE'
};
this.queryParams.status = _status[index];
this.queryParams.page = 0;
this.fnToTopPage();
this.fnGetData();
},
// 获取分类列表
fnGetCategoryList() {
this.category.loading = 'loading';
this.$httpApi.admin
.getCategoryList()
.then(res => {
if (res.status == 200) {
let _list = res.data;
_list.unshift({ id: undefined, name: '全部' });
this.category.list = _list;
this.category.loading = 'success';
} else {
this.category.loading = 'error';
}
})
.catch(err => {
this.category.loading = 'error';
});
},
// 显示分类选择
fnOnCategoryConfirm(e) {
this.category.selected = [e[0].index];
this.queryParams.categoryId = e[0].data.id;
},
fnOnSearch() {
this.queryParams.page = 0;
this.isLoadMore = false;
this.fnToTopPage();
this.fnGetData();
},
fnGetData() {
uni.showLoading({
mask: true,
title: '加载中...'
});
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi.admin
.getPostsByPage(this.queryParams)
.then(res => {
console.log('请求结果:');
console.log(res);
if (res.status == 200) {
// 处理数据
this.result = res.data;
const _dataList = res.data.content.map(item => {
item.hasThumbnail = item.thumbnail != '';
if (item.thumbnail) {
item.thumbnail = this.$utils.checkThumbnailUrl(item.thumbnail);
}
return item;
});
if (this.isLoadMore) {
this.dataList = this.dataList.concat(_dataList);
} else {
this.dataList = _dataList;
}
this.loading = 'success';
this.loadMoreText = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
} else {
this.loading = 'error';
this.loadMoreText = '呜呜,加载失败了~';
}
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 800);
});
},
// 跳转文章详情(预览)
fnToArticleDetail(article) {
uni.navigateTo({
url: '/pagesA/article-detail/article-detail?articleId=' + article.id,
animationType: 'slide-in-right'
});
},
// 新增
fnOnAddArticle() {
this.$Router.push({ path: '/pagesB/articles/article-edit', query: {} });
},
// 文章编辑
fnOnEditArticle(article) {
this.$Router.push({ path: '/pagesB/articles/article-edit', query: { postsId: article.id } });
},
// 设置文章信息
fnOnSetArticle(article, index) {
this.$Router.push({ path: '/pagesB/articles/article-setting', query: { postsId: article.id, postTitle: article.title, isEdit: true, from: 'list' } });
},
// 删除文章
fnOnDelArticle(article, index) {
uni.$eShowModal({
title: '提示',
content: '是否确定要删除该文章?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
uni.showLoading({
mask: true,
title: '删除中...'
});
this.$httpApi.admin
.deletePostsByIds([article.id])
.then(res => {
if (res.status == 200) {
uni.$tm.toast('文章已删除成功!');
this.dataList.splice(index, 1);
} else {
uni.$tm.toast('操作失败,请重试!');
}
})
.catch(err => {
uni.$tm.toast('操作失败,请重试!');
});
})
.catch(err => {});
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
min-height: 100vh;
display: flex;
flex-direction: column;
padding-bottom: 24rpx;
background-color: #fafafd;
&.is-balck {
background-color: #212121;
}
}
.app-page-content {
box-sizing: border-box;
.content-empty {
height: 60vh;
}
}
.article-card {
box-sizing: border-box;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.05);
.thumbnail {
width: 100%;
height: 280rpx;
&-img {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.03);
}
&-not {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.03);
}
}
.title {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
word-wrap: break-word;
word-break: break-all;
}
.summary {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
word-wrap: break-word;
word-break: break-all;
}
.foot {
box-sizing: border-box;
border-top: 2rpx solid rgba(0, 0, 0, 0.03);
}
}
</style>
-767
View File
@@ -1,767 +0,0 @@
<template>
<view class="app-page flex flex-col">
<!-- 搜索区域 -->
<view class="filter-wrap bg-white shadow-3">
<tm-search v-model="queryParams.keyword" :round="24" :shadow="0" color="light-blue" insert-color="light-blue" :clear="true" @confirm="fnOnSearch"></tm-search>
<tm-dropDownMenu :shadow="0" color="light-blue" @confirm="fnOnConfirm" :list="filterList"></tm-dropDownMenu>
</view>
<!-- 占位区域 -->
<view style="width: 100vw;height: 184rpx;"></view>
<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>
</view>
<view class="page-content pa-24 flex flex-col" v-else>
<view v-if="dataList.length == 0" class="content-empty flex flex-center">
<!-- 空布局 -->
<tm-empty icon="icon-shiliangzhinengduixiang-" label="暂无任何附件"></tm-empty>
</view>
<block v-else>
<!-- 内容区域 -->
<block v-for="(item, index) in dataList" :key="index">
<tm-translate animation-name="fadeUp" :wait="(index + 1) * 50">
<view class="attachment-card mb-24 flex bg-white pa-24 round-3" @click="fnShowDetail(item)">
<view class="cover round-3 flex flex-center" :class="[fnGetIconClass(item.mediaType)]">
<text class="icon iconfont">{{ item.suffix }}</text>
</view>
<!-- 内容区域 -->
<view class="info pl-36">
<view class="file-name text-size-m text-weight-b">{{ item.name }}</view>
<view class="file-media mt-12 text-size-s text-grey-darken-1">附件类型{{ item.mediaType }}</view>
<view class="file-media mt-8 text-size-s text-grey-darken-1">附件大小{{ item.sizeText }}</view>
<view class="file-path mt-8 text-size-s text-grey-darken-1">上传日期{{ { d: item.createTime, f: 'yyyy年MM月dd日 星期w' } | formatTime }}</view>
</view>
</view>
</tm-translate>
</block>
<tm-flotbutton @click="fnToTopPage" :offset="[16, 80]" color="bg-gradient-light-blue-accent" size="m" icon="icon-angle-up"></tm-flotbutton>
<view class="load-text ">{{ loadMoreText }}</view>
</block>
<!-- 弹窗详情 -->
<tm-poup v-model="detail.show" position="bottom" height="80vh">
<view class="poup-content">
<view class="poup-head text-align-center text-weight-b text-size-g ma-24 ">附件详情</view>
<scroll-view class="poup-body pa-24 pt-0" :scroll-y="true" @touchmove.stop>
<view class="preview-file pl-24 pr-24">
<!-- 图片预览 -->
<tm-images
v-if="$utils.fnCheckIsFileType('image', detail.form)"
:width="700"
:height="350"
:round="3"
:src="$utils.checkThumbnailUrl(detail.form.path)"
model="aspectFill"
></tm-images>
<!-- 视频 -->
<video
v-else-if="$utils.fnCheckIsFileType('video', detail.form)"
class="round-3"
style="width: 100%;height: 350rpx;"
:src="$utils.checkUrl(detail.form.path)"
controls
></video>
<view v-else class="flex flex-center border-a-1 round-3 text-align-center text-grey-darken-1" style="height: 280rpx;background-color: #f2f2f2;">
该文件暂不支持预览
</view>
</view>
<tm-input title="附件名称" v-model="detail.form.name" :clear="true" placeholder="请输入附件名称..."></tm-input>
<tm-input :disabled="true" color="grey" title="附件类型" v-model="detail.form.mediaType"></tm-input>
<tm-input :disabled="true" color="grey" title="附件位置" v-model="detail.form.type"></tm-input>
<tm-input :disabled="true" color="grey" title="附件大小" v-model="detail.form.sizeText"></tm-input>
<tm-input :disabled="true" color="grey" title="上传日期" v-model="detail.form.createTime"></tm-input>
<tm-input
:disabled="true"
:vertical="true"
:borderBottom="false"
input-type="textarea"
title="文件地址"
color="grey"
bg-color="grey-lighten-5"
:height="120"
v-model="detail.form.path"
>
<template v-slot:default="{ title }">
{{ title }}
<tm-button text theme="light-blue" size="xs" @click="$utils.copyText(detail.form.path, '复制成功!')">复制</tm-button>
</template>
</tm-input>
</scroll-view>
<view class="btn-wrap flex flex-center">
<tm-button size="m" navtie-type="form" theme="bg-gradient-blue-accent" @click="fnOnUpdate()">保存</tm-button>
<tm-button size="m" theme="bg-gradient-red-accent" @click="fnOnDelete()">删除</tm-button>
<tm-button size="m" theme="bg-gradient-blue-grey-accent" @click="detail.show = false">关闭</tm-button>
</view>
</view>
</tm-poup>
</view>
<tm-flotbutton :show-text="true" color="bg-gradient-orange-accent" @click="fnOnFlotButton"></tm-flotbutton>
<tm-actionSheetMenu title="文件上传操作" @change="fnOnFileSelectActionChange" v-model="fileSelectAction.show" :actions="fileSelectAction.list"></tm-actionSheetMenu>
</view>
</template>
<script>
// #ifdef APP-PLUS
const AfDocument = uni.requireNativePlugin('Aq-ChooseFile'); // 临时替代
// #endif
import { getAdminAccessToken } from '@/utils/auth.js';
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
import tmSearch from '@/tm-vuetify/components/tm-search/tm-search.vue';
import tmDropDownMenu from '@/tm-vuetify/components/tm-dropDownMenu/tm-dropDownMenu.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmCheckbox from '@/tm-vuetify/components/tm-checkbox/tm-checkbox.vue';
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
import tmForm from '@/tm-vuetify/components/tm-form/tm-form.vue';
import tmUpload from '@/tm-vuetify/components/tm-upload/tm-upload.vue';
import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
import tmImages from '@/tm-vuetify/components/tm-images/tm-images.vue';
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
import tmActionSheetMenu from '@/tm-vuetify/components/tm-actionSheetMenu/tm-actionSheetMenu.vue';
export default {
components: {
tmSkeleton,
tmSearch,
tmDropDownMenu,
tmEmpty,
tmButton,
tmCheckbox,
tmPoup,
tmForm,
tmUpload,
tmInput,
tmImages,
tmFlotbutton,
tmTranslate,
tmActionSheetMenu
},
data() {
return {
loading: 'loading',
filterLoading: 'loading',
btnOption: {
actions: [
{
icon: 'icon-like',
color: 'bg-gradient-orange-accent'
},
{
icon: 'icon-commentdots-fill',
color: 'bg-gradient-green-accent'
},
{
icon: 'icon-share1',
color: 'bg-gradient-blue-accent'
}
]
},
filterList: [
{
title: '存储位置',
children: [
{
title: '',
name: 'attachmentType',
model: 'radio',
children: []
}
]
},
{
title: '文件类型',
children: [
{
title: '',
name: 'mediaType',
model: 'radio',
children: []
}
]
}
],
queryParams: {
size: 8,
page: 0,
attachmentType: undefined,
keyword: undefined,
mediaType: undefined,
sort: undefined
},
result: {},
dataList: [],
total: 0,
detail: {
params: {},
form: {},
show: false
},
isLoadMore: false,
loadMoreText: '加载中...',
fileSelectAction: {
show: false,
list: []
}
};
},
onLoad() {
this.fnSetPageTitle('附件管理');
},
created() {
this.fnGetData();
this.fnGetFilters();
},
onPullDownRefresh() {
this.queryParams.page = 0;
this.dataList = [];
this.fnGetData();
if (this.filterLoading != 'success') {
this.fnGetFilters();
}
},
onReachBottom(e) {
if (this.result.hasNext) {
this.queryParams.page += 1;
this.isLoadMore = true;
this.fnGetData();
} else {
uni.showToast({
icon: 'none',
title: '没有更多数据了'
});
}
},
methods: {
/**
* 文件大小 字节转换单位
* @param size
* @returns {string|*}
*/
fnTransFileSize(size) {
if (!size) return '';
if (size < this.fnPow1024(1)) return size + ' B';
if (size < this.fnPow1024(2)) return (size / this.fnPow1024(1)).toFixed(2) + ' KB';
if (size < this.fnPow1024(3)) return (size / this.fnPow1024(2)).toFixed(2) + ' MB';
if (size < this.fnPow1024(4)) return (size / this.fnPow1024(3)).toFixed(2) + ' GB';
return (size / this.fnPow1024(4)).toFixed(2) + ' TB';
},
// 求次幂
fnPow1024(num) {
return Math.pow(1024, num);
},
fnGetIconClass(mediaType) {
let _class = '';
if (mediaType.indexOf('video/') != -1) {
_class = 'is-video';
} else if (mediaType.indexOf('image/') != -1) {
_class = 'is-image';
}
return _class;
},
fnCheckIsFileType(type, attachment) {
if (type == 'video') return attachment.mediaType.indexOf('video/') != -1;
else if (type == 'image') return attachment.mediaType.indexOf('image/') != -1;
else return false;
},
fnGetFilters() {
this.filterLoading = 'loading';
Promise.all([this.$httpApi.admin.getAttachmentsTypes(), this.$httpApi.admin.getAttachmentsMediaTypes()])
.then(res => {
const res1 = res[0];
this.filterList[0].children[0].children = res1.data.map(item => {
return {
title: item,
id: item
};
});
const res2 = res[1];
this.filterList[1].children[0].children = res2.data.map(item => {
return {
title: item,
id: item
};
});
this.filterLoading = 'success';
})
.catch(err => {
this.filterLoading = 'error';
uni.$tm.toast('搜索条件数据加载失败!');
});
},
// 搜索区域确定
fnOnConfirm(e) {
this.queryParams.attachmentType = e[0].children.map(x => x.id).join('') || undefined;
this.queryParams.mediaType = e[1].children.map(x => x.id).join('') || undefined;
this.fnOnSearch();
},
fnOnSearch() {
this.queryParams.page = 0;
this.dataList = [];
this.fnGetData();
},
fnGetData() {
uni.showLoading({
mask: true,
title: '加载中...'
});
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi.admin
.getAttachmentsByPage(this.queryParams)
.then(res => {
if (res.status == 200) {
this.loadMoreText = res.data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
this.result = res.data;
this.total = res.data.total;
if (this.isLoadMore) {
let _list = this.dataList.concat(res.data.content);
this.dataList = _list.map(item => {
item['sizeText'] = this.fnTransFileSize(item.size);
return item;
});
} else {
this.dataList = res.data.content.map(item => {
item['sizeText'] = this.fnTransFileSize(item.size);
return item;
});
}
this.loading = 'success';
} else {
uni.$tm.toast('加载失败,请重试!');
thi.loading = 'error';
}
})
.catch(err => {
console.error(err);
uni.$tm.toast('加载失败,请重试!');
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 300);
});
},
// 详情
fnShowDetail(item) {
this.detail.params = { ...item };
this.detail.form = { ...item };
this.detail.show = true;
},
// 删除
fnOnDelete() {
uni.$eShowModal({
title: '提示',
content: `您确定要删除该附件吗?`,
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
uni.showLoading({
mask: true,
title: '正在删除...'
});
this.$httpApi.admin
.deleteAttachmentById(this.detail.params.id)
.then(res => {
if (res.status == 200) {
uni.$tm.toast('删除成功!');
setTimeout(() => {
this.detail.show = false;
this.detail.params = {};
this.detail.form = {};
this.fnGetData();
}, 1000);
} else {
uni.$tm.toast('删除失败,请重试!');
}
})
.catch(err => {
console.error(err);
uni.$tm.toast('删除失败,请重试!');
});
})
.catch(err => {});
},
fnOnUpdate() {
if (this.detail.form.name == '') {
return uni.$tm.toast('附件名称不能为空!');
}
uni.showLoading({
mask: true,
title: '正在保存...'
});
this.$httpApi.admin
.updateAttachmentById(this.detail.params.id, this.detail.form.name)
.then(res => {
if (res.status == 200) {
uni.$tm.toast('保存成功!');
// setTimeout(() => {
// this.detail.show = false;
// this.detail.params = {};
// this.detail.form = {};
// this.fnGetData();
// }, 1000);
} else {
uni.$tm.toast('保存失败,请重试!');
}
})
.catch(err => {
uni.$tm.toast('保存失败,请重试!');
});
},
// 浮动按钮点击
fnOnFlotButton() {
// 微信小程序:提供从消息列表和媒体选择
// #ifdef MP-WEIXIN
this.fileSelectAction.list = ['上传照片', '上传视频', '上传文件'];
// #endif
// 安卓app:使用第三方上传
// #ifdef APP-PLUS
this.fileSelectAction.list = ['上传照片', '上传视频', '上传文件'];
// #endif
// h5
// #ifdef H5
this.fileSelectAction.list = ['上传照片', '上传视频', '上传文件'];
// #endif
// #ifndef H5||MP-WEIXIN||APP-PLUS
this.fileSelectAction.list = ['上传照片', '上传视频'];
// #endif
this.fileSelectAction.show = true;
},
fnOnFileSelectActionChange(e) {
// 微信小程序:提供从消息列表和媒体选择
// #ifdef MP-WEIXIN
switch (e.index) {
case 0:
// 上传照片
uni.chooseImage({
count: 1,
success: res => {
console.log(res);
this.fnOnUploadFileByPath(res.tempFilePaths[0]);
}
});
break;
case 1:
// 上传视频
uni.chooseVideo({
count: 1,
success: res => {
console.log(res);
this.fnOnUploadFileByPath(res.tempFilePath);
}
});
break;
case 2:
// 会话选择
uni.chooseMessageFile({
count: 1,
success: res => {
this.fnOnUploadFileByPath(res.tempFiles[0].path);
}
});
break;
}
// #endif
// 安卓app
// #ifdef APP-PLUS
switch (e.index) {
case 0:
// 上传照片
uni.chooseImage({
count: 1,
success: res => {
console.log(res);
this.fnOnUploadFileByPath(res.tempFilePaths[0]);
}
});
break;
case 1:
// 上传视频
uni.chooseVideo({
count: 1,
success: res => {
console.log(res);
this.fnOnUploadFileByPath(res.tempFilePath);
}
});
break;
case 2:
// 上传文件:
// todo:使用第三方控件
AfDocument.openMode(
{
size: 1,
isDown: true,
types: [
{
name: '文档',
value: ['doc', 'wps', 'docx', 'xls', 'xlsx', 'pdf']
},
{
name: '视频',
value: ['mp4']
},
{
name: '音乐',
value: ['mp3', 'flac']
},
{
name: '图片',
value: ['jpg', 'png']
}
]
},
res => {
// this.data = JSON.stringify(res);
this.fnOnUploadFileByFile(res[0].path);
}
);
break;
}
// #endif
// h5
// #ifdef H5
switch (e.index) {
case 0:
// 上传照片
uni.chooseImage({
count: 1,
success: res => {
console.log(res);
this.fnOnUploadFileByPath(res.tempFilePaths[0]);
}
});
break;
case 1:
// 上传视频
uni.chooseVideo({
count: 1,
success: res => {
console.log(res);
this.fnOnUploadFileByPath(res.tempFilePath);
}
});
break;
case 2:
// 上传文件
uni.chooseFile({
count: 1,
success: res => {
console.log(res);
this.fnOnUploadFileByPath(res.tempFilePaths[0]);
}
});
break;
}
// #endif
// #ifndef H5||MP-WEIXIN||APP-PLUS
switch (e.index) {
case 0:
// 上传照片
uni.chooseImage({
count: 1,
success: res => {
this.fnOnUploadFileByFile(res.tempFiles[0]);
}
});
break;
case 1:
// 上传视频
uni.chooseVideo({
count: 1,
success: res => {
this.fnOnUploadFileByFile(res.tempFile);
}
});
break;
}
// #endif
},
// 使用文件路径上传文件
fnOnUploadFileByPath(path) {
uni.showLoading({
title: '文件上传中...',
mask: true
});
uni.uploadFile({
filePath: path,
header: {
'admin-authorization': getAdminAccessToken()
},
url: this.$baseApiUrl + '/api/admin/attachments/upload',
name: 'file',
success: upladRes => {
const _uploadRes = JSON.parse(upladRes.data);
if (_uploadRes.status == 200) {
uni.$tm.toast('文件上传成功!');
setTimeout(() => {
this.fnGetData();
}, 1000);
} else {
uni.$tm.toast(_uploadRes.message);
}
},
fail: err => {
uni.$tm.toast(err.message);
}
});
},
// 使用文件对象上传文件
fnOnUploadFileByFile(file) {
uni.showLoading({
title: '文件上传中...',
mask: true
});
uni.uploadFile({
// #ifdef H5
file: file,
// #endif
// #ifndef H5
files: [file],
// #endif
header: {
'admin-authorization': getAdminAccessToken()
},
url: this.$baseApiUrl + '/api/admin/attachments/upload',
name: 'file',
success: upladRes => {
const _uploadRes = JSON.parse(upladRes.data);
if (_uploadRes.status == 200) {
uni.$tm.toast('文件上传成功!');
setTimeout(() => {
this.fnGetData();
}, 1000);
} else {
uni.$tm.toast(_uploadRes.message);
}
},
fail: err => {
uni.$tm.toast(err.message);
}
});
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
min-height: 100vh;
}
.loading-wrap {
padding: 24rpx;
}
.filter-wrap {
position: fixed;
left: 0;
right: 0;
/* #ifdef H5 */
top: 88rpx;
/* #endif */
/* #ifndef H5 */
top: 0;
/* #endif */
width: 100vw;
z-index: 6;
}
.page-content {
}
.content-empty {
width: 100vw;
height: 65vh;
}
.attachment-card {
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.05);
.cover {
width: 170rpx;
height: 160rpx;
background-color: rgb(104, 136, 151);
// box-shadow: 0 6rpx 10rpx rgba(51, 154, 204, 0.2);
&.is-video {
background-color: rgb(3, 174, 252);
}
&.is-image {
background-color: #1cbcb4;
}
.icon {
font-size: 40rpx;
color: #fff;
}
.thumbnai {
width: 100%;
height: 100%;
box-sizing: border-box;
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.03);
border: 6rpx solid #fff;
}
}
.info {
width: 0;
flex-grow: 1;
.time-icon {
font-size: 24rpx;
}
}
}
.poup-body {
box-sizing: border-box;
height: 66vh;
}
.poup-content {
width: 100%;
overflow: hidden;
}
.btn-wrap {
width: 100%;
}
.file-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
word-break: break-all;
}
</style>
-420
View File
@@ -1,420 +0,0 @@
<template>
<view class="app-page flex flex-col">
<view v-if="loading != 'success'" class="loading-wrap pa-24 pt-36">
<block v-if="loading == 'loading'">
<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>
</block>
<view v-else-if="loading == 'error'" class="loading-error flex flex-col flex-center">
<tm-empty icon="icon-wind-cry" label="加载失败"><tm-button theme="light-blue" size="m" @click="fnGetData()">重新加载</tm-button></tm-empty>
</view>
</view>
<view v-else class="app-page-content pa-24">
<view v-if="dataList.length == 0" class="content-empty flex flex-center"><tm-empty icon="icon-shiliangzhinengduixiang-" label="无数据"></tm-empty></view>
<block v-else>
<block v-for="(category, index) in dataList" :key="index">
<tm-translate animation-name="fadeUp" :wait="(index + 1) * 50">
<view class="category-card flex round-3 bg-white pa-24 mb-24" @click="fnShowFormModal(category)">
<text class="del-icon flex flex-center" @click.stop="fnDelete(category, index)">×</text>
<view class="left">
<image v-if="category.showThumbnail" class="cover round-3" :src="category.showThumbnail" mode="aspectFill"></image>
<view v-else class="cover round-3 flex flex-center text-weight-b text-white text-size-m">无封面</view>
</view>
<view class="right pl-24 text-size-m">
<view class="text-weight-b">名称{{ category.name }}</view>
<view class="mt-12">
<text class="text-grey-darken-1">别名</text>
{{ category.slug || '暂无别名' }}
</view>
<view class="mt-12 flex desc-box">
<view class="label text-grey-darken-1">路径</view>
<view class="value text-size-s">{{ category.fullPath }}</view>
</view>
<view class="mt-12 flex desc-box">
<view class="label text-grey-darken-1">描述</view>
<view class="value text-size-s">{{ category.description || '暂无描述' }}</view>
</view>
</view>
</view>
</tm-translate>
</block>
<tm-flotbutton @click="fnToTopPage" :offset="[16, 80]" size="m" color="light-blue" icon="icon-angle-up"></tm-flotbutton>
<tm-flotbutton @click="fnShowFormModal()" size="m" color="orange" icon="icon-plus"></tm-flotbutton>
</block>
</view>
<!-- 编辑或新增 -->
<tm-poup v-model="poupShow" position="bottom" height="85vh" @change="fnOnPoupChange">
<view class="poup-content">
<view class="poup-head text-align-center text-weight-b text-size-g ma-24">{{ form.id != undefined ? '编辑分类' : '新增分类' }}</view>
<scroll-view class="poup-body pa-24 pt-0" :scroll-y="true" @touchmove.stop>
<tm-input
required
:adjust-position="true"
:round="3"
:borderBottom="false"
title="名称"
bg-color="grey-lighten-5"
v-model="form.name"
placeholder="请输入分类名称"
></tm-input>
<view class="pl-32 mb-24 input-tips text-grey text-size-s">填写提示页面上所显示的名称</view>
<tm-input
:borderBottom="false"
:adjust-position="true"
:round="3"
title="别名"
bg-color="grey-lighten-5"
v-model="form.slug"
placeholder="请输入分类别名"
></tm-input>
<view class="pl-32 mb-24 input-tips text-grey text-size-s">填写提示一般为单个分类页面的标识最好为英文</view>
<tm-input
:borderBottom="false"
:adjust-position="true"
:round="3"
title="密码"
bg-color="grey-lighten-5"
v-model="form.password"
placeholder="请输入分类密码"
></tm-input>
<view class="pl-32 mb-24 input-tips text-grey text-size-s">填写提示分类的访问密码</view>
<tm-input
:borderBottom="false"
:vertical="true"
:adjust-position="true"
inputType="textarea"
:round="3"
title="描述"
:height="160"
bg-color="grey-lighten-5"
v-model="form.description"
placeholder="请输入分类描述"
></tm-input>
<view class="pl-32 mb-24 input-tips text-grey text-size-s">填写提示分类的描述信息</view>
<view class="ma-30 mt-12 pb-12">
<view class="mb-12">
<text class="text-size-m">封面图</text>
<text class="text-grey text-size-s">点击下方图片区域选择封面</text>
</view>
<image v-if="form.showThumbnail" class="thumbnail round-3" :src="form.showThumbnail" mode="aspectFill" @click="attachmentsSelectShow = true"></image>
<view v-else class="thumbnail round-3 text-grey grey-lighten-5 flex flex-col flex-center " @click="attachmentsSelectShow = true">
<text class="iconfont icon-picture" style="font-size: 46rpx;"></text>
<text class="mt-12 text-size-m">选择封面图</text>
</view>
</view>
</scroll-view>
<view class="btn-wrap flex flex-center">
<tm-button size="m" theme="bg-gradient-blue-accent" @click="fnSubmit()">保存</tm-button>
<tm-button v-if="form.id != undefined" size="m" theme="bg-gradient-red-accent" @click="fnDelete()">删除</tm-button>
<tm-button size="m" theme="bg-gradient-blue-grey-accent" @click="poupShow = false"> </tm-button>
</view>
</view>
</tm-poup>
<!-- 附件选择文件 -->
<attachment-select selectType="image" v-if="attachmentsSelectShow" @on-select="fnOnAttachmentsSelect" @on-close="fnOnAttachmentsSelectClose"></attachment-select>
</view>
</template>
<script>
import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
import tmInput from '@/tm-vuetify/components/tm-input/tm-input.vue';
import AttachmentSelect from '@/components/attachment-select/attachment-select.vue';
export default {
components: {
tmSkeleton,
tmButton,
tmEmpty,
tmTranslate,
tmFlotbutton,
tmPoup,
tmInput,
AttachmentSelect
},
data() {
return {
loading: 'loading',
queryParams: {
size: 10,
page: 0,
more: true
},
dataList: [],
poupShow: false,
attachmentsSelectShow: false,
form: {
description: undefined,
password: undefined,
createTime: undefined,
fullPath: undefined,
id: undefined,
name: '',
slug: undefined,
thumbnail: undefined,
showThumbnail: undefined
}
};
},
onLoad() {
this.fnSetPageTitle('分类管理');
},
created() {
this.fnGetData();
},
onPullDownRefresh() {
this.isLoadMore = false;
this.queryParams.page = 0;
this.fnGetData();
},
methods: {
fnGetData() {
uni.showLoading({
mask: true,
title: '加载中...'
});
// 设置状态为加载中
if (!this.isLoadMore) {
this.loading = 'loading';
}
this.loadMoreText = '加载中...';
this.$httpApi.admin
.getCategoryList(this.queryParams)
.then(res => {
console.log('getCategoryList');
console.log(res);
if (res.status == 200) {
this.loading = 'success';
this.dataList = res.data.map(item => {
if (item.thumbnail) {
item['showThumbnail'] = this.$utils.checkThumbnailUrl(item.thumbnail);
} else {
item['showThumbnail'] = '';
}
return item;
});
} else {
this.loading = 'error';
}
console.log(this.dataList);
})
.catch(err => {
console.error(err);
this.loading = 'error';
this.loadMoreText = '加载失败,请下拉刷新!';
})
.finally(() => {
setTimeout(() => {
uni.hideLoading();
uni.stopPullDownRefresh();
}, 800);
});
},
fnSelectColor() {
this.$refs.colorPicker.open();
},
fnOnConfirmSelectColor(e) {
this.selectColor = e.rgba;
this.$set(this.form, 'color', e.hex);
},
fnOnAttachmentsSelectClose() {
this.attachmentsSelectShow = false;
},
fnOnPoupChange(e) {
if (!e) {
this.fnResetForm();
}
},
// 监听附件选择
fnOnAttachmentsSelect(file) {
this.form.thumbnail = file.path;
this.form.showThumbnail = this.$utils.checkThumbnailUrl(file.path);
this.attachmentsSelectShow = false;
},
fnShowFormModal(category) {
if (category) {
this.form = Object.assign({}, {}, category);
if (category.thumbnail) {
this.form.showThumbnail = this.$utils.checkThumbnailUrl(category.thumbnail);
}
} else {
this.fnResetForm();
}
this.poupShow = true;
},
fnResetForm() {
this.form = {
color: undefined,
createTime: undefined,
fullPath: undefined,
id: undefined,
name: '',
slug: undefined,
thumbnail: undefined,
showThumbnail: undefined
};
},
fnSubmit() {
if (this.form.name.trim() == '') {
return uni.$tm.toast('分类名称未填写!');
}
if (this.form.id == undefined) {
this.$httpApi.admin
.createCategory(this.form)
.then(res => {
if (res.status == 200) {
uni.$tm.toast(`保存成功!`);
uni.startPullDownRefresh();
} else {
uni.$tm.toast('操作失败,请重试!');
}
})
.catch(err => {
uni.$tm.toast('操作失败,请重试!');
});
} else {
this.$httpApi.admin
.updateCategoryById(this.form.id, this.form)
.then(res => {
if (res.status == 200) {
uni.$tm.toast(`保存成功!`);
let updateIndex = this.dataList.findIndex(x => x.id == this.form.id);
this.$set(this.dataList, updateIndex, this.form);
} else {
uni.$tm.toast('操作失败,请重试!');
}
})
.catch(err => {
uni.$tm.toast('操作失败,请重试!');
});
}
},
// 删除
fnDelete(category) {
const _category = category || this.form;
uni.$eShowModal({
title: '提示',
content: `您是否要将 ${_category.name} 的分类删除?`,
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
this.$httpApi.admin
.deleteCategoryById(_category.id)
.then(res => {
if (res.status == 200) {
uni.$tm.toast(`${_category.name} 分类已删除!`);
const delIndex = this.dataList.findIndex(x => x.id == _category.id);
this.dataList.splice(delIndex, 1);
} else {
uni.$tm.toast('操作失败,请重试!');
}
})
.catch(err => {
uni.$tm.toast('操作失败,请重试!');
});
})
.catch(err => {});
}
}
};
</script>
<style lang="scss" scoped>
.app-page {
width: 100vw;
min-height: 100vh;
}
.loading-wrap {
.loading-error {
height: 65vh;
}
}
.content-empty {
width: 100%;
height: 60vh;
}
.poup-content {
overflow: hidden;
}
.poup-body {
height: 71vh;
box-sizing: border-box;
touch-action: none;
}
.thumbnail {
width: 100%;
height: 260rpx;
}
.category-card {
position: relative;
box-sizing: border-box;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
.del-icon {
width: 46rpx;
height: 46rpx;
position: absolute;
right: 24rpx;
top: 24rpx;
border-radius: 50%;
box-sizing: border-box;
border: 4rpx solid #fff;
background-color: #ff8177;
box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.05);
font-size: 32rpx;
color: #fff;
}
.left {
width: 200rpx;
height: 190rpx;
.cover {
width: 100%;
height: inherit;
background-color: #607d8b;
}
}
.right {
width: 0;
flex-grow: 1;
box-sizing: border-box;
.color {
color: #fff;
}
}
}
.desc-box {
box-sizing: border-box;
.label {
width: 86rpx;
}
.value {
width: 0;
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
word-break: break-all;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More