1
0
mirror of https://github.com/ialley-workshop-open/uni-halo.git synced 2026-07-27 04:20:43 +08:00

94 Commits

Author SHA1 Message Date
小莫唐尼 af87beebff style: 统一项目内文字尺寸为xs替换11px
1. 调整微信登录按钮图标颜色格式为小写
2. 统一帖子卡片、文章详情页的文字大小为xs替代11px
2026-06-16 01:56:54 +08:00
小莫唐尼 9dc5aae164 style: 调整markdown列表内边距和分享按钮样式
1. 将ul和ol的左侧内边距从15px改为24px,统一列表缩进样式
2. 简化文章详情页上下导航的padding类,移除重复的pt-0
3. 优化分享按钮的样式类,合并border相关样式并移除冗余样式
2026-06-16 01:53:58 +08:00
小莫唐尼 a510875d38 style: 调整多处页面UI细节与默认状态
1. 将动态页面默认视图从时间线模式改为非时间线模式
2. 修复分享按钮的默认样式问题,移除多余边框和背景
3. 调整登录页分隔线上下间距与微信登录按钮高度
4. 隐藏文章卡片的分类信息展示
2026-06-16 01:44:17 +08:00
小莫唐尼 02c803f0c1 style(login,guest-panel): 优化表单UI和代码结构
1. 移除访客信息面板的冗余标签文本
2. 调整登录页样式顺序、类名和分隔线排版
3. 拆分长条件语句,新增返回按钮和自定义导航栏
4. 统一输入框背景色类名
2026-06-16 01:28:25 +08:00
小莫唐尼 a9e649e110 feat: 新增点赞、游客评论、分享等核心功能,重构页面逻辑
1. 新增全局点赞状态管理,使用metadata.name做key并持久化存储
2. 新增游客信息存储与评论面板,支持游客评论互动
3. 新增全局分享hook,为多个页面配置分享功能
4. 重构登录页面,支持登录/注册切换与游客访问模式
5. 合并注册页到登录页,简化路由结构
6. 优化剪贴板工具函数,支持自定义提示文本
7. 重构个人中心页面,支持游客信息管理
8. 修复相册页面标题与样式问题
9. 移除无用的备份文件与测试代码
2026-06-16 00:43:48 +08:00
小莫唐尼 1ba71ff43f feat(comment-panel): 新增回复目标原内容展示并优化回复提示样式
1. 扩展replyTarget类型新增rawContent字段用于存储原评论内容
2. 传递评论和回复的原始内容到回复处理函数
3. 在回复提示区展示被回复的原评论内容
4. 优化回复提示的排版布局,调整文字样式和间距
2026-06-15 23:03:34 +08:00
小莫唐尼 700eb10517 feat(comment-panel): 优化评论回复展示与交互细节
1. 新增查找被引用回复的工具函数
2. 优化加载状态的展示样式与文案
3. 调整回复列表间距与引用回复的渲染逻辑
4. 更新输入框占位文案与回复提示区域的展示
5. 优化回复内容的文字颜色与排版
2026-06-15 22:40:39 +08:00
小莫唐尼 9c028bdc1b style(comment-panel): 调整加载组件样式并添加加载文案
优化评论列表加载状态的视觉表现,增加黑色加载图标与加载中文案提示
2026-06-15 22:09:59 +08:00
小莫唐尼 1ad951913e refactor: 优化多篇页面细节与评论展示效果
1. 隐藏两个页面的测试调试模块
2. 将图库默认样式改为card模式
3. 优化评论项间距与图标、内容展示
4. 调整代码块圆角样式与文章详情页逻辑
5. 新增文章导航封面处理与加载逻辑优化
2026-06-15 22:02:59 +08:00
小莫唐尼 e191eb8358 style: 调整背景模糊值与布局样式,优化评论与图片功能
1.  调整全局背景模糊参数,统一并优化页面毛玻璃效果
2.  调整home页布局类名顺序,为post-card添加全屏宽度
3.  新增头像处理工具函数,补充随机图片源
4.  优化markdown内容容器的内边距样式
5.  重构文章详情页评论面板,实现完整评论回复功能
6.  优化文章详情页布局与样式细节
2026-06-15 21:29:14 +08:00
小莫唐尼 a86baa9641 feat(blog,comment): add post comment panel and optimize tips
1. 新增文章评论弹窗组件uh-post-comment-panel
2. 给文章详情页评论按钮绑定打开评论面板事件
3. 优化图片画廊和评论加载失败的提示文案与样式
4. 修复评论列表接口返回类型适配问题
2026-06-15 18:53:48 +08:00
小莫唐尼 c4fc9c930e style: 调整主题色为深灰并优化文章页样式
1.  将全局主题色替换为#1A1A1A
2.  重构markdown样式,优化间距与排版
3.  新增文章页返回顶部功能
4.  优化文章详情页的布局与交互细节
2026-06-15 18:26:56 +08:00
小莫唐尼 01c929c4b5 refactor(blog-post-detail): 重构文章详情页布局与交互栏
1.  将分类和标签整合到文章标题下方统一展示
2.  移除独立的标签区域,将互动操作栏改为底部浮动栏
3.  新增分类标签样式优化,调整整体视觉风格
4.  添加阴影和毛玻璃背景工具类
2026-06-15 17:14:48 +08:00
小莫唐尼 2cc78dd329 feat: 新增博客token配置,完善文章详情页与markdown样式
1.  新增IUniHaloConfig的token配置项并关联环境变量
2.  调整文章跳转路径到subpkg分包目录
3.  重构markdown配置使用主题色替换硬编码值
4.  升级代码块样式为深色主题并添加圆角
5.  新增完整的文章详情页面实现
6.  删除冗余的markdown样式scss文件
7.  添加项目claude开发指南文档
2026-06-15 16:36:23 +08:00
小莫唐尼 b56de87856 refactor(home&post-card): 调整点击事件绑定位置并优化空状态样式
1. 将作者卡片的点击事件从子组件移到父容器上,简化子组件依赖
2. 给空状态提示容器添加box-border类统一样式规范
3. 为文章卡片添加点击跳转详情页的逻辑
2026-06-15 13:32:56 +08:00
小莫唐尼 21eeb912fd feat: 完成uni-halo小程序基础功能迭代
本次提交完成多项核心功能升级与优化:
1. 新增页面背景色变量page3并替换部分页面背景样式
2. 新增通用列表分页请求基础接口类型
3. 调整首页默认跳转路径为首页
4. 重构配置结构,新增插件配置模块
5. 新增瞬间、友链、相册相关API接口与类型定义
6. 重构useRequest导出方式,修复hook导出问题
7. 移除部分调试日志代码
8. 新增padStart兼容polyfill工具函数
9. 新增文章卡片多种布局样式支持
10. 优化友链页面UI展示与交互逻辑
11. 新增瞬间页面时间轴展示模式与时间解析工具
12. 新增相册页面多种浏览模式切换功能
2026-06-14 22:00:54 +08:00
小莫唐尼 a60def91a0 feat(gallery): 实现相册功能对接后端API,替换静态模拟数据
1.  重构相册页面,使用真实的Halo图片插件API获取分组与图片数据
2.  新增图片预览工具函数,支持大图预览
3.  更新滚动加载hook,补充响应式原始数据与加载状态控制
4.  调整相册面板组件参数与样式,适配真实数据展示
5.  修复开发环境接口地址配置
2026-06-14 17:36:13 +08:00
小莫唐尼 759373e327 refactor: 项目结构与代码优化调整
1. 重构tabbar路由与多语言key,调整首页默认跳转
2. 拆分博客页面到subpkg目录,调整vite分包配置
3. 新增markdown、图片组件、滚动钩子等工具模块
4. 优化api类型定义与工具函数,修复图片缓存逻辑
5. 调整国际化文案与个人页面路径
2026-06-14 01:25:15 +08:00
小莫唐尼 7d74a40f51 style: 调整多处UI样式与布局细节
1. 修改项目概览文档标题
2. 将详情按钮文字大小从11px改为text-xs
3. 移除分类标签右侧的多余内边距
4. 关闭首页列表加载loading弹窗
5. 调整友链项的间距与头像尺寸
2026-06-13 00:37:17 +08:00
小莫唐尼 50cad268d3 feat: 新增全局搜索功能,优化页面默认跳转逻辑
1. 新增全局搜索弹窗组件及相关事件常量
2. 修复首页默认跳转路径,改回首页而非动态页
3. 注释无用的单例运行代码
4. 添加背景模糊样式类
5. 更新搜索API类型定义
6. 底部导航栏搜索按钮触发全局搜索弹窗
2026-06-13 00:29:49 +08:00
小莫唐尼 8510b71ff6 feat: 新增瞬间页面、通用阴影样式,优化页面配置
1. 新增通用阴影样式工具类并引入全局样式
2. 新增moments瞬间页面并修改首页跳转指向
3. 为页面模板和瞬间页面添加下拉刷新、页面滚动等配置
4. 为halo瞬间api添加isHalo请求元信息
5. 优化links页面布局样式
2026-06-12 22:30:52 +08:00
小莫唐尼 a012a57cf0 feat: 新增友链页面,添加友链tabbar项并调整配置
1. 新增友链页面组件,实现分组展示、滚动导航功能
2. 多语言中添加友链相关翻译
3. 修改默认首页跳转至友链页
4. 调整API请求配置,添加halo请求标记
5. 优化剪贴板提示、布局样式和工具函数
6. 更新开发环境请求地址
2026-06-12 21:07:16 +08:00
小莫唐尼 d8ad0772bf refactor: 项目重构适配uni-halo,拆分首页组件
1.  统一环境变量前缀为VITE_UNI_HALO_BASEURL,替换原有VITE_SERVER_BASEURL
2.  新增首页home页面,将原index页面作为启动页跳转首页
3.  新增多个业务组件:模板组件、个人资料卡片、分类推荐卡片、文章卡片
4.  新增图片处理工具、数字格式化工具、URL验证工具
5.  重构tabbar配置,修正首页跳转路径
6.  更新站点统计API方法命名,优化请求逻辑
2026-06-12 15:48:58 +08:00
小莫唐尼 f105dedc23 refactor: 重构项目并新增多项功能
1. 新增全局配置管理store,拉取uni-halo插件配置
2. 新增post-card文章卡片组件,简化首页文章列表代码
3. 优化tabbar样式与切换逻辑,调整图标库类型
4. 新增随机图片工具函数,重构首页横幅与分类模块
5. 调整openapi生成配置,新增工具类与样式类
6. 移除废弃的tabbar关于页面配置项
2026-06-12 14:32:21 +08:00
小莫唐尼 c847dfb1c4 feat: 新增返回顶部功能与优化首页横幅样式
1.  给useListData的下拉刷新添加调试日志
2.  首页横幅替换固定图片为随机资源图,调整渐变背景层级
3.  新增tabbar滚动监听与返回顶部按钮功能
2026-06-12 00:12:43 +08:00
小莫唐尼 862d176593 refactor: 项目整体重构与功能完善
1. 配置开发环境接口地址
2. 重构工具函数目录结构,迁移防抖节流等工具到base目录
3. 统一页面布局样式为全屏居中布局
4. 新增大量通用工具函数:深克隆、深合并、uuid、时间格式化、随机数、数组打乱、剪贴板操作等
5. 优化自定义tabbar实现,修复交互逻辑
6. 新增列表数据管理hook和请求loading封装
7. 移除无用测试文件和冗余代码
8. 完善首页内容,增加轮播图、作者卡片、金刚区等模块
2026-06-11 23:33:54 +08:00
小莫唐尼 3945ee611d chore: 清理旧文件并重构项目基础结构
- 删除大量废弃的旧代码、依赖和配置文件
- 新增基础项目配置、工具函数、页面模板和请求库
- 重构目录结构,统一项目规范
- 添加commitlint、husky等代码规范工具
2026-06-11 02:13:47 +08:00
小莫唐尼 de142c4729 feat: 添加文章访问统计功能
1. 新增getDomainOnly工具函数用于提取URL域名
2. 新增postTrackersCounter接口调用方法
3. 在文章详情页添加访问数据上报 #31 #41
2026-06-05 13:20:25 +08:00
小莫唐尼 0b4090f9e9 chore: 向gitignore添加.trae目录
忽略.trae临时配置目录,避免将其提交到代码仓库
2026-05-25 00:33:24 +08:00
小莫唐尼 3ccd4f7ccf chore: 删除无用的 git 提交消息规则文件
移除了项目中未实际使用的自定义提交规则配置文件
2026-05-25 00:32:16 +08:00
小莫唐尼 6f01501fd1 chore: 发布版本2.0.10,修复并优化多项功能
1.  更新manifest.json和package.json版本号至2.0.10
2.  修复友情链接api #40
3.  修复图库api #39
4.  统一home.vue页面的代码缩进格式
5.  清理api文件冗余注释,修复接口域名路径
6.  优化图库页面空状态提示文案
2026-05-25 00:27:42 +08:00
yhliu e426abf336 modify home.vue 2026-04-27 22:43:37 +08:00
yhliu a767397e23 feat 链接管理、投票管理控制 2026-04-27 22:43:37 +08:00
yhliu 17f5b4b917 feat 首页是否显示快捷导航 2026-04-27 22:43:37 +08:00
小莫唐尼 94ff37f77f fix: 修复文档图片不显示问题 2026-04-24 18:53:56 +08:00
小莫唐尼 73bc88bad8 fix: 修复域名 后缀带 / 问题,修改doc文档 2026-04-24 18:06:23 +08:00
小莫唐尼 72dc278a2b feat: 修复数据看板无法滚动问题 2025-11-28 01:19:52 +08:00
小莫唐尼 7ba35ffd5d feat: 新增数据可视化看板、删除多余的文件 2025-11-28 00:01:00 +08:00
小莫唐尼 3643bdf6f6 fix: 修复海报保存问题 2025-11-26 15:19:04 +08:00
liuyiwuqing 21fee2dc9a feat(auth): 限制阅读支持评论后访问
- 引入 getOpenid 和 getNologinEmail 方法统一管理认证信息
- 替换原有直接读取 storage 方式为封装方法调用
- 优化文章详情页按钮文案展示逻辑
- 新增评论后自动刷新限制阅读内容功能
- 调整评论权限提示逻辑,跳转至评论弹窗而非 toast 提示
2025-11-25 21:32:07 +08:00
小莫唐尼 90f6516f45 fix: 修复文章详情的豆瓣资源名称重复显示 2025-11-13 18:43:43 +08:00
小莫唐尼 a15621d55e feat: 优化文章详情的豆瓣资源显示 2025-11-13 18:33:40 +08:00
小莫唐尼 a717beb0e7 feat: 文章详情支持豆瓣插件 2025-11-13 17:37:33 +08:00
liuyiwuqing b9ad94ddb5 !26 fix(api):修复友情链接提交接口地址
* fix(api):修复友情链接提交接口地址
2025-10-20 08:51:09 +00:00
小莫唐尼 9ab013562d fix: 修复投票状态显示问题 2025-08-29 17:52:53 +08:00
小莫唐尼 a8bd121deb feat: 文章详情有封面图的时候支持点击预览大图 2025-08-22 11:16:33 +08:00
小莫唐尼 476a1ec48e fix: 修复投票详情页面分享BUG 2025-08-22 11:15:56 +08:00
小莫唐尼 31ade9908a feat: 优化投票功能,调整API 2025-08-21 19:32:22 +08:00
小莫唐尼 5606e8c59f fix: 修复投票多选BUG 2025-08-21 11:18:08 +08:00
小莫唐尼 61d3b22fdb fix: 修复插件状态检测功能 2025-08-21 11:17:36 +08:00
小莫唐尼 c05aa5253c feat: 优化投票功能,在审核模式下设置不显示 2025-08-20 23:47:05 +08:00
小莫唐尼 0b599dcced feat: 优化首页快捷导航 2025-08-20 23:29:05 +08:00
小莫唐尼 623e3fa59a feat: 文章详情支持投票功能 2025-08-20 23:19:41 +08:00
小莫唐尼 1075f588fa fix: 修复全局预定义css影响问题 2025-08-20 22:00:47 +08:00
小莫唐尼 c0d8cb2c3b feat: 新增投票功能 2025-08-20 21:56:34 +08:00
小莫唐尼 9c6a054b12 feat: 补充遗漏插件检查组件 2025-08-20 21:55:11 +08:00
小莫唐尼 6a05f664ba feat: 添加插件检测 2025-08-20 21:53:24 +08:00
小莫唐尼 9c4d2242ae fix: 修复瞬间详情显示bug 2025-07-22 00:40:36 +08:00
小莫唐尼 eac027c116 feat: 内容搜索支持显示文章和瞬间、瞬间支持跳转详情 2025-07-22 00:07:57 +08:00
小莫唐尼 3a08008192 fix: 修复审核模式瞬间无法正确加载数据问题 (#33) 2025-07-21 22:19:06 +08:00
小莫唐尼 7daf93d51f fix: 修复瞬间视频可同时播放多个视频bug、优化列表加载 2025-07-21 17:53:36 +08:00
小莫唐尼 409be28deb fix: 修复图库添加左右手势滑动后布局显示bug 2025-07-20 00:11:41 +08:00
小莫唐尼 97e358e41f fix: 修复图库功能loading 成功状态 error 改为 success 2025-07-20 00:04:53 +08:00
小莫唐尼 c17e668afe feat: 优化返回顶部按钮大小 2025-07-19 23:59:56 +08:00
小莫唐尼 4611932ad8 feat: 图库支持左右滑动 2025-07-19 23:46:40 +08:00
小莫唐尼 452d021816 fix: 修复图库分类排序问题 2025-07-19 14:08:44 +08:00
小莫唐尼 d71022c224 fix: 修复分类详情分享bug 2025-07-01 20:23:06 +08:00
小莫唐尼 d521a641de refactor: 对token配置进行重构、同时修复token获取失败的问题 2025-07-01 20:09:05 +08:00
小莫唐尼 4246d8cdcb refactor: 调整默认的配置文件 2025-06-29 11:29:06 +08:00
小莫唐尼 6989fd8ddc fix: 修复自定义轮播图弹窗链接跳转网页问题 2025-06-28 15:09:56 +08:00
小莫唐尼 742b51a23e feat: 新增瞬间支持音频 2025-06-25 18:51:44 +08:00
小莫唐尼 bc541a3ef6 fix: 修复瞬间内容中的图片视频无法展示问题 2025-06-25 18:33:44 +08:00
小莫唐尼 67fc682164 feat: 瞬间卡片增加标签显示功能 2025-06-25 17:28:57 +08:00
小莫唐尼 b43dd7e5a7 fix: 修复启动页面视频背景url兼容问题 2025-06-25 16:22:07 +08:00
小莫唐尼 e29ccf65c5 fix: 修复搜索功能 2025-06-22 12:02:43 +08:00
小莫唐尼 91553fc053 feat: 优化分类显示按钮根据审核模式状态显示因此 2025-06-22 11:15:20 +08:00
小莫唐尼 22f57b7860 feat: 优化UI 2025-06-22 11:06:41 +08:00
小莫唐尼 2a25727c0b feat: 分类页面支持切换不同的展示模式 2025-06-16 19:42:45 +08:00
小莫唐尼 eb69ae4af6 feat: 更新版本 2.0.8 2025-06-15 18:42:00 +08:00
小莫唐尼 2fd83a7814 feat: 新增首页轮播支持配置 2025-06-15 18:38:52 +08:00
小莫唐尼 8963ba1921 fix: 修复首页和分类页面显示已经隐藏的分类数据问题 2025-06-15 14:58:07 +08:00
liuyiwuqing 051c72b584 !25 支持文章自定义验证码获取方式、优化友链提交页面
* fix(提交链接): 修复博客 logo 显示问题
* feat(plugin): 重构友链提交插件配置
* feat(api): 为 restrict-read 请求添加微信会话 ID 并支持自定义验证码类型
2025-06-15 06:37:50 +00:00
小莫唐尼 552827eaac update: 调整token配置参数 2025-06-13 12:47:12 +08:00
liuyiwuqing e8d13c674f !24 feat(restrict-read): 实现文章限制阅读功能
* feat(restrict-read): 实现文章限制阅读功能
* Merge remote-tracking branch 'origin/v2.0-beta' into v2.0-beta
* feat(restrict-read): 实现文章限制阅读功能
2025-06-13 03:38:44 +00:00
小莫唐尼 256bc0e00f update: 首页分类查询过滤 2025-06-11 15:57:35 +08:00
小莫唐尼 9aa0e310f2 fix: 修复文章详情海报二维码在安卓下显示异常问题 2025-02-14 16:44:43 +08:00
小莫唐尼 6a0bb0e07f update: 文章海报取消渐变背景 2025-02-07 21:21:51 +08:00
小莫唐尼 044f737016 update: 瞬间过滤非公开的内容 2025-02-07 21:11:48 +08:00
小莫唐尼 27b3d31828 update: 优化文章详情海报图片在安卓下显示不完整问题 2025-02-07 21:03:43 +08:00
小莫唐尼 64c9a15d51 update: 修改README内容 2024-12-04 17:28:13 +08:00
小莫唐尼 df4c58d3e4 feat: 文章详情新增评论自动滚动到评论区域 2024-12-03 10:04:08 +08:00
小莫唐尼 93f23696ef update: 修改作者相关网页地址、免责声明页面邮箱点击可复制 2024-12-03 09:25:50 +08:00
小莫唐尼 2ed7e1a260 update: 修改作者博客地址 2024-11-29 16:16:20 +08:00
小莫唐尼 e19b107ed0 feat: 新增文章点赞功能 2024-11-06 19:23:13 +08:00
601 changed files with 41505 additions and 101258 deletions
+8
View File
@@ -0,0 +1,8 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets).
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md).
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.3/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "base",
"updateInternalDependencies": "patch",
"ignore": []
}
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
}
+51
View File
@@ -0,0 +1,51 @@
# API 和 HTTP 请求规范
## HTTP 请求封装
- 可以使用 `简单http` 或者 `alova` 或者 `@tanstack/vue-query` 进行请求管理
- HTTP 配置在 [src/http/](mdc:src/http/) 目录下
- `简单http` - [src/http/http.ts](mdc:src/http/http.ts)
- `alova` - [src/http/alova.ts](mdc:src/http/alova.ts)
- `vue-query` - [src/http/vue-query.ts](mdc:src/http/vue-query.ts)
- 请求拦截器在 [src/http/interceptor.ts](mdc:src/http/interceptor.ts)
- 支持请求重试、缓存、错误处理
## API 接口规范
- API 接口定义在 [src/api/](mdc:src/api/) 目录下
- 按功能模块组织 API 文件
- 使用 TypeScript 定义请求和响应类型
- 支持 `简单http`、`alova` 和 `vue-query` 三种请求方式
## 示例代码结构
```typescript
// API 接口定义
export interface LoginParams {
username: string
password: string
}
export interface LoginResponse {
token: string
userInfo: UserInfo
}
// alova 方式
export const login = (params: LoginParams) =>
http.Post<LoginResponse>('/api/login', params)
// vue-query 方式
export const useLogin = () => {
return useMutation({
mutationFn: (params: LoginParams) =>
http.post<LoginResponse>('/api/login', params)
})
}
```
## 错误处理
- 统一错误处理在拦截器中配置
- 支持网络错误、业务错误、认证错误等
- 自动处理 token 过期和刷新
---
globs: src/api/*.ts,src/http/*.ts
---
+43
View File
@@ -0,0 +1,43 @@
# 开发工作流程
## 项目启动
1. 安装依赖:`pnpm install`
2. 开发环境:
- H5: `pnpm dev` 或 `pnpm dev:h5`
- 微信小程序: `pnpm dev:mp`
- 支付宝小程序: `pnpm dev:mp-alipay`
- APP: `pnpm dev:app`
## 代码规范
- 使用 ESLint 进行代码检查:`pnpm lint`
- 自动修复代码格式:`pnpm lint:fix`
- 使用 eslint 格式化代码
- 遵循 TypeScript 严格模式
## 构建和部署
- H5 构建:`pnpm build:h5`
- 微信小程序构建:`pnpm build:mp`
- 支付宝小程序构建:`pnpm build:mp-alipay`
- APP 构建:`pnpm build:app`
- 类型检查:`pnpm type-check`
## 开发工具
- 推荐使用 VSCode 编辑器
- 安装 Vue 和 TypeScript 相关插件
- 使用 uni-app 开发者工具调试小程序
- 使用 HBuilderX 调试 APP
## 调试技巧
- 使用 console.log 和 uni.showToast 调试
- 利用 Vue DevTools 调试组件状态
- 使用网络面板调试 API 请求
- 平台差异测试和兼容性检查
## 性能优化
- 使用懒加载和代码分割
- 优化图片和静态资源
- 减少不必要的重渲染
- 合理使用缓存策略
---
description: 开发工作流程和最佳实践指南
---
+36
View File
@@ -0,0 +1,36 @@
---
alwaysApply: true
---
# 项目概览
这是一个基于 uniapp + Vue3 + TypeScript + Vite5 + UnoCSS 的跨平台开发框架。
## 项目特点
- 支持 H5、小程序、APP 多平台开发
- 使用最新的前端技术栈
- 内置约定式路由、layout布局、请求封装、登录拦截、自定义tabbar等功能
- 无需依赖 HBuilderX,支持命令行开发
## 核心配置文件
- [package.json](mdc:package.json) - 项目依赖和脚本配置
- [vite.config.ts](mdc:vite.config.ts) - Vite 构建配置
- [pages.config.ts](mdc:pages.config.ts) - 页面路由配置
- [manifest.config.ts](mdc:manifest.config.ts) - 应用清单配置
- [uno.config.ts](mdc:uno.config.ts) - UnoCSS 配置
## 主要目录结构
- `src/pages/` - 页面文件
- `src/components/` - 组件文件
- `src/layouts/` - 布局文件
- `src/api/` - API 接口
- `src/http/` - HTTP 请求封装
- `src/store/` - 状态管理
- `src/tabbar/` - 底部导航栏
- `src/App.ku.vue` - 全局根组件(类似 App.vue 里面的 template作用)
## 开发命令
- `pnpm dev` - 开发 H5 版本
- `pnpm dev:mp` - 开发微信小程序
- `pnpm dev:mp-alipay` - 开发支付宝小程序(含钉钉)
- `pnpm dev:app` - 开发 APP 版本
- `pnpm build` - 构建生产版本
+54
View File
@@ -0,0 +1,54 @@
# 样式和 CSS 开发规范
## UnoCSS 原子化 CSS
- 项目使用 UnoCSS 作为原子化 CSS 框架
- 配置在 [uno.config.ts](mdc:uno.config.ts)
- 支持预设和自定义规则
- 优先使用原子化类名,减少自定义 CSS
## SCSS 规范
- 使用 SCSS 预处理器
- 样式文件使用 `lang="scss"` 和 `scoped` 属性
- 遵循 BEM 命名规范
- 使用变量和混入提高复用性
## 样式组织
- 全局样式在 [src/style/](mdc:src/style/) 目录下
- 组件样式使用 scoped 作用域
- 图标字体在 [src/style/iconfont.css](mdc:src/style/iconfont.css)
- 主题变量在 [src/uni_modules/uni-scss/](mdc:src/uni_modules/uni-scss/) 目录下
## 示例代码结构
```vue
<template>
<view class="container flex flex-col items-center p-4">
<text class="title text-lg font-bold mb-2">标题</text>
<view class="content bg-gray-100 rounded-lg p-3">
<!-- 内容 -->
</view>
</view>
</template>
<style lang="scss" scoped>
.container {
min-height: 100vh;
.title {
color: var(--primary-color);
}
.content {
width: 100%;
max-width: 600rpx;
}
}
</style>
## 响应式设计
- 使用 rpx 单位适配不同屏幕
- 支持横屏和竖屏布局
- 使用 flexbox 和 grid 布局
- 考虑不同平台的样式差异
---
globs: *.vue,*.scss,*.css
---
+63
View File
@@ -0,0 +1,63 @@
# uni-app 开发规范
## 页面开发
- 页面文件放在 [src/pages/](mdc:src/pages/) 目录下
- 使用约定式路由,文件名即路由路径
- 页面配置在仅需要在 宏`definePage` 中配置标题等内容即可,会自动生成到 `pages.json` 中
- definePage的顺序在最上面
## 组件开发
- 组件文件放在 [src/components/](mdc:src/components/) 或者 [src/pages/xx/components/](mdc:src/pages/xx/components/) 目录下
- 使用 uni-app 内置组件和第三方组件库
- 支持 wot-ui\uview-pro\uv-ui\sard-ui\uview-plus 等多种第三方组件库 和 z-paging 组件
- 自定义组件遵循 uni-app 组件规范
## 平台适配
- 使用条件编译处理平台差异
- 支持 H5、小程序、APP 多平台
- 注意各平台的 API 差异
- 使用 uni.xxx API 替代原生 API
## 示例代码结构
```vue
<script setup lang="ts">
// #ifdef H5
import { h5Api } from '@/utils/h5'
// #endif
// #ifdef MP-WEIXIN
import { mpApi } from '@/utils/mp'
// #endif
const handleClick = () => {
// #ifdef H5
h5Api.showToast('H5 平台')
// #endif
// #ifdef MP-WEIXIN
mpApi.showToast('微信小程序')
// #endif
}
</script>
<template>
<view class="page">
<!-- uni-app 组件 -->
<button @click="handleClick">点击</button>
<!-- 条件渲染 -->
<!-- #ifdef H5 -->
<view>H5 特有内容</view>
<!-- #endif -->
</view>
</template>
```
## 生命周期
- 使用 uni-app 页面生命周期
- onLoad、onShow、onReady、onHide、onUnload
- 组件生命周期遵循 Vue3 规范
- 注意页面栈和导航管理
---
globs: src/pages/*.vue,src/components/*.vue
---
+53
View File
@@ -0,0 +1,53 @@
# Vue3 + TypeScript 开发规范
## Vue 组件规范
- 使用 Composition API 和 `<script setup>` 语法
- 组件文件使用 PascalCase 命名
- 页面文件放在 `src/pages/` 目录下
- 全局组件文件放在 `src/components/` 目录下
- 局部组件文件放在页面的 `/components/` 目录下
## Vue SFC 组件规范
- `<script setup lang="ts">` 标签必须是第一个子元素
- `<template>` 标签必须是第二个子元素
- `<style scoped>` 标签必须是最后一个子元素(因为推荐使用原子化类名,所以很可能没有)
## TypeScript 规范
- 严格使用 TypeScript,避免使用 `any` 类型
- 为 API 响应数据定义接口类型
- 使用 `interface` 定义对象类型,`type` 定义联合类型
- 导入类型时使用 `import type` 语法
## 状态管理
- 使用 Pinia 进行状态管理
- Store 文件放在 `src/store/` 目录下
- 使用 `defineStore` 定义 store
- 支持持久化存储
## 示例代码结构
```vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { UserInfo } from '@/types/user'
const userInfo = ref<UserInfo | null>(null)
onMounted(() => {
// 初始化逻辑
})
</script>
<template>
<view class="container">
<!-- 模板内容 -->
</view>
</template>
<style lang="scss" scoped>
.container {
// 样式
}
</style>
---
globs: *.vue,*.ts,*.tsx
---
+13
View File
@@ -0,0 +1,13 @@
root = true
[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off # 关闭最大行长度限制
trim_trailing_whitespace = false # 关闭末尾空格修剪
+47 -8
View File
@@ -1,9 +1,48 @@
node_modules/
unpackage/
package-lock.json
.idea
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
config/halo.config.js
config/love.config.js
config/token.config.js
config/plugins.config.js
node_modules
.DS_Store
dist
*.local
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.hbuilderx
.stylelintcache
.eslintcache
docs/.vitepress/dist
docs/.vitepress/cache
src/types
# 单独把这个文件排除掉,用以解决部分电脑生成的 auto-import.d.ts 的API不完整导致类型提示报错问题
!src/types/auto-import.d.ts
src/manifest.json
src/pages.json
# 2025-10-15 by 菲鸽: lock 文件还是需要加入版本管理,今天又遇到版本不一致导致无法运行的问题了。
# pnpm-lock.yaml
# package-lock.json
# TIPS:如果某些文件已经加入了版本管理,现在重新加入 .gitignore 是不生效的,需要执行下面的操作
# `git rm -r --cached .` 然后提交 commit 即可。
# git rm -r --cached file1 file2 ## 针对某些文件
# git rm -r --cached dir1 dir2 ## 针对某些文件夹
# git rm -r --cached . ## 针对所有文件
# 更新 uni-app 官方版本
# npx @dcloudio/uvm@latest
-29
View File
@@ -1,29 +0,0 @@
{
// 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"
}
]
}
+1
View File
@@ -0,0 +1 @@
npx --no-install commitlint --edit "$1"
+1
View File
@@ -0,0 +1 @@
npx lint-staged --allow-empty
+9
View File
@@ -0,0 +1,9 @@
# registry = https://registry.npmjs.org
registry = https://registry.npmmirror.com
strict-peer-dependencies=false
auto-install-peers=true
shamefully-hoist=true
ignore-workspace-root-check=true
install-workspace-root=true
node-options=--max-old-space-size=8192
+8
View File
@@ -0,0 +1,8 @@
---
alwaysApply: true
scene: git_message
---
在此处编写规则,自定义 AI 生成提交信息的风格。
使用中文编写提交信息
+123
View File
@@ -0,0 +1,123 @@
# 项目概览
这是一个基于 uniapp + Vue3 + TypeScript + Vite5 + UnoCSS 的跨平台开发框架。
## 项目特点
- 支持 H5、小程序、APP 多平台开发
- 使用最新的前端技术栈
- 内置约定式路由、layout布局、请求封装等功能
- 无需依赖 HBuilderX,支持命令行开发
## 核心配置文件
- [package.json](mdc:package.json) - 项目依赖和脚本配置
- [vite.config.ts](mdc:vite.config.ts) - Vite 构建配置
- [pages.config.ts](mdc:pages.config.ts) - 页面路由配置
- [manifest.config.ts](mdc:manifest.config.ts) - 应用清单配置
- [uno.config.ts](mdc:uno.config.ts) - UnoCSS 配置
## 主要目录结构
- `src/pages/` - 页面文件
- `src/components/` - 组件文件
- `src/layouts/` - 布局文件
- `src/api/` - API 接口
- `src/http/` - HTTP 请求封装
- `src/store/` - 状态管理
- `src/tabbar/` - 底部导航栏
- `src/App.ku.vue` - 全局根组件(类似 App.vue 里面的 template作用)
## 开发命令
- `pnpm dev` - 开发 H5 版本
- `pnpm dev:mp` - 开发微信小程序
- `pnpm dev:mp-alipay` - 开发支付宝小程序(含钉钉)
- `pnpm dev:app` - 开发 APP 版本
- `pnpm build` - 构建生产版本
## Vue 组件规范
- 使用 Composition API 和 `<script setup>` 语法
- 组件文件使用 PascalCase 命名
- 页面文件放在 `src/pages/` 目录下
- 全局组件文件放在 `src/components/` 目录下
- 局部组件文件放在页面的 `/components/` 目录下
## TypeScript 规范
- 严格使用 TypeScript,避免使用 `any` 类型
- 为 API 响应数据定义接口类型
- 使用 `interface` 定义对象类型,`type` 定义联合类型
- 导入类型时使用 `import type` 语法
## 状态管理
- 使用 Pinia 进行状态管理
- Store 文件放在 `src/store/` 目录下
- 使用 `defineStore` 定义 store
- 支持持久化存储
## UnoCSS 原子化 CSS
- 项目使用 UnoCSS 作为原子化 CSS 框架
- 配置在 [uno.config.ts]
- 支持预设和自定义规则
- 优先使用原子化类名,减少自定义 CSS
## Vue SFC 组件规范
- `<script setup lang="ts">` 标签必须是第一个子元素
- `<template>` 标签必须是第二个子元素
- `<style scoped>` 标签必须是最后一个子元素(因为推荐使用原子化类名,所以很可能没有)
## 页面开发
- 页面文件放在 [src/pages/]目录下
- 使用约定式路由,文件名即路由路径
- 页面配置在仅需要在 宏`definePage` 中配置标题等内容即可,会自动生成到 `pages.json`
- definePage的顺序在最上面
## 组件开发
- 全局组件文件放在 `src/components/` 目录下
- 局部组件文件放在页面的 `/components/` 目录下
- 使用 uni-app 内置组件和第三方组件库
- 支持 wot-ui\uview-pro\uv-ui\sard-ui\uview-plus 等多种第三方组件库 和 z-paging 组件
- 自定义组件遵循 uni-app 组件规范
## 平台适配
- 使用条件编译处理平台差异
- 支持 H5、小程序、APP 多平台
- 注意各平台的 API 差异
- 使用 uni.xxx API 替代原生 API
## 示例代码结构
```vue
<script setup lang="ts">
// #ifdef H5
import { h5Api } from '@/utils/h5'
// #endif
// #ifdef MP-WEIXIN
import { mpApi } from '@/utils/mp'
// #endif
const handleClick = () => {
// #ifdef H5
h5Api.showToast('H5 平台')
// #endif
// #ifdef MP-WEIXIN
mpApi.showToast('微信小程序')
// #endif
}
</script>
<template>
<view class="page">
<!-- uni-app 组件 -->
<button @click="handleClick">点击</button>
<!-- 条件渲染 -->
<!-- #ifdef H5 -->
<view>H5 特有内容</view>
<!-- #endif -->
</view>
</template>
```
## 生命周期
- 使用 uni-app 页面生命周期
- onLoad、onShow、onReady、onHide、onUnload
- 组件生命周期遵循 Vue3 规范
- 注意页面栈和导航管理
+15
View File
@@ -0,0 +1,15 @@
{
"recommendations": [
"vue.volar",
"dbaeumer.vscode-eslint",
"antfu.unocss",
"antfu.iconify",
"evils.uniapp-vscode",
"uni-helper.uni-helper-vscode",
"uni-helper.uni-app-schemas-vscode",
"uni-helper.uni-highlight-vscode",
"uni-helper.uni-ui-snippets-vscode",
"uni-helper.uni-app-snippets-vscode",
"streetsidesoftware.code-spell-checker"
]
}
+107
View File
@@ -0,0 +1,107 @@
{
// 配置语言的文件关联
"files.associations": {
"pages.json": "jsonc", // pages.json 可以写注释
"manifest.json": "jsonc" // manifest.json 可以写注释
},
"stylelint.enable": false, // 禁用 stylelint
"css.validate": false, // 禁用 CSS 内置验证
"scss.validate": false, // 禁用 SCSS 内置验证
"less.validate": false, // 禁用 LESS 内置验证
// 新版本 VsCode 中这个配置已失效
"typescript.tsdk": "node_modules/typescript/lib",
// 配置新版本 VsCode 工作区的 TypeScript 的版本
"js/ts.tsdk.path": "node_modules/typescript/lib",
"js/ts.tsdk.promptToUseWorkspaceVersion": true,
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
"explorer.fileNesting.patterns": {
"README.md": "index.html,favicon.ico,robots.txt,CHANGELOG.md",
"docker.md": "Dockerfile,docker*.md,nginx*,.dockerignore",
"pages.config.ts": "manifest.config.ts,openapi-ts-request.config.ts",
"package.json": "tsconfig.json,pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc",
"eslint.config.mjs": ".commitlintrc.*,.prettier*,.editorconfig,.commitlint.cjs,.eslint*"
},
// Disable the default formatter, use eslint instead
"prettier.enable": false,
"editor.formatOnSave": false,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off", "fixable": true },
{ "rule": "format/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true }
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml",
"xml",
"gql",
"graphql",
"astro",
"svelte",
"css",
"less",
"scss",
"pcss",
"postcss"
],
"cSpell.words": [
"alova",
"Aplipay",
"attributify",
"chooseavatar",
"climblee",
"commitlint",
"dcloudio",
"iconfont",
"oxlint",
"qrcode",
"refresherrefresh",
"scrolltolower",
"tabbar",
"Toutiao",
"uniapp",
"unibest",
"unocss",
"uview",
"uvui",
"Wechat",
"WechatMiniprogram",
"Weixin"
],
"i18n-ally.localesPaths": [
"src/locale",
"src/pages/i18n"
],
"i18n-ally.keystyle": "nested"
}
+80
View File
@@ -0,0 +1,80 @@
{
// Place your unibest 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
// "Print to console": {
// "scope": "javascript,typescript",
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"Print unibest Vue3 SFC": {
"scope": "vue",
"prefix": "v3",
"body": [
"<script lang=\"ts\" setup>",
"definePage({",
" style: {",
" navigationBarTitleText: '$1',",
" },",
"})",
"defineOptions({",
" name: '$2',",
" options: { ",
" virtualHost: true,",
" },",
"})",
"</script>\n",
"<template>",
" <view class=\"\">$3</view>",
"</template>\n",
"<style lang=\"scss\" scoped>",
"//$4",
"</style>\n",
],
},
"Print unibest style": {
"scope": "vue",
"prefix": "st",
"body": [
"<style lang=\"scss\" scoped>",
"//",
"</style>\n"
],
},
"Print unibest script with definePage": {
"scope": "vue",
"prefix": "sc",
"body": [
"<script lang=\"ts\" setup>",
"definePage({",
" style: {",
" navigationBarTitleText: '$1',",
" },",
"})",
"defineOptions({",
" name: '$2',",
" options: { ",
" virtualHost: true,",
" },",
"})",
"</script>\n"
],
},
"Print unibest template": {
"scope": "vue",
"prefix": "te",
"body": [
"<template>",
" <view class=\"\">$1</view>",
"</template>\n"
],
},
}
-60
View File
@@ -1,60 +0,0 @@
<script>
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';
export default {
globalData: {
baseApiUrl: HaloTokenConfig.BASE_API
},
onLaunch: function () {
// #ifdef APP-PLUS
CheckAppUpdate();
// #endif
// #ifdef MP-WEIXIN
CheckWxUpdate();
// #endif
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>
<style lang="scss">
// 基础样式
@import './common/styles/app.theme.scss';
@import './common/styles/app.base.scss';
// 引入tmUI2.x样式
@import './tm-vuetify/mian.min.css';
// 引入tmUI2.x主题包
@import './tm-vuetify/scss/theme.css';
// 引入tmUI2.x预置图标
@import './tm-vuetify/scss/fonts/fontawesome_base64.css';
// 自定义图标
@import './common/icons/halocoloriconfont.css';
@import './common/icons/haloiconfont.css';
@import './common/icons/mphtmliconfont.css';
/* #ifndef MP-WEIXIN */
@import './common/markdown/markdown.scss';
/* #endif */
page {
background-color: #fafafa;
}
</style>
-3
View File
@@ -1,3 +0,0 @@
# 更新日志
## 暂无更新记录
+118
View File
@@ -0,0 +1,118 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
uni-halo v3.0 is a multi-platform blog client built on the **Halo 2.x** headless CMS API. It targets WeChat mini-program (primary), H5, Alipay, and native apps. Built on the **unibest** scaffold with Vue 3 + TypeScript + Vite + UnoCSS.
## Commands
| Task | Command |
|------|---------|
| Install dependencies | `pnpm install` |
| Dev (H5) | `pnpm dev` or `pnpm dev:h5` |
| Dev (WeChat mini-program) | `pnpm dev:mp` or `pnpm dev:mp-weixin` |
| Dev (Alipay mini-program) | `pnpm dev:mp-alipay` |
| Dev (native app) | `pnpm dev:app` |
| Build (H5) | `pnpm build` or `pnpm build:h5` |
| Build (WeChat) | `pnpm build:mp` |
| Build (app) | `pnpm build:app` |
| Lint | `pnpm lint` |
| Lint + auto-fix | `pnpm lint:fix` |
| Type check | `pnpm type-check` |
| Run tests (watch) | `pnpm test` |
| Run tests (single) | `pnpm test:run` |
| Generate API client from OpenAPI | `pnpm openapi` |
| Upload to WeChat platform | `pnpm upload:mp` |
## Architecture
### Entry Flow
`src/main.ts` → creates Vue app → installs Pinia → route interceptor → request interceptor → i18n. On launch, `src/App.vue` fetches the uni-halo plugin configuration from the Halo server and stores it in the `uniHaloConfig` Pinia store.
### Routing (Convention-Based)
Pages in `src/pages/` are auto-registered by `@uni-helper/vite-plugin-uni-pages`. Use the `definePage()` macro at the top of page files to set metadata (navigation style, title, etc.). Sub-packages live in `src/subpkg-blog/` and `src/subpkg-demo/`.
Route guards in `src/router/interceptor.ts` intercept `navigateTo`, `reLaunch`, `redirectTo`, `switchTab`. Supports whitelist (default: login required) and blacklist strategies, configured in `src/router/config.ts`.
### HTTP Layer — Three Coexisting Strategies
1. **Custom `uni.request` wrapper** (`src/http/http.ts`) — GET/POST/PUT/DELETE helpers with transparent 401 token refresh (queues pending requests during refresh). Used by `src/api/login.ts`.
2. **Alova** (`src/http/alova.ts`) — Alova 3.3+ with `@alova/adapter-uniapp`, Halo-specific response handling. Used by `src/api/halo-base/*` and `src/api/halo-plugin/*`.
3. **vue-query** (`src/http/vue-query.ts`) — skeleton/option, not actively used.
Request interceptor (`src/http/interceptor.ts`) attaches `Authorization: Bearer <token>` to all requests.
### Authentication
Dual token mode support (single access token OR access+refresh pair), controlled by `VITE_AUTH_MODE` env var. Token store (`src/store/token.ts`) handles login (standard + WeChat mini-program), logout, and automatic refresh with computed expiry checks.
### API Layer
- `src/api/halo-base/` — Core Halo APIs: posts, categories, tags, comments, stats, trackers
- `src/api/halo-plugin/` — uni-halo plugin APIs: plugin config, moments, links, gallery, votes, douban, muyin, data stats
- `src/api/login.ts` — Auth APIs (login, register, WeChat login, token refresh)
- `src/api/types/` — TypeScript interfaces for API responses
- Types from `@halo-dev/api-client` (e.g., `ListedPostVo`, `PostVo`)
### State Management
Pinia with `pinia-plugin-persistedstate` using `uni.getStorageSync`/`uni.setStorageSync`. All stores use Composition API style (`defineStore` with setup function):
- `src/store/token.ts` — Authentication
- `src/store/user.ts` — User profile
- `src/store/config.ts` — uni-halo plugin configuration
### Custom Tabbar
Fully custom tabbar in `src/tabbar/` with its own Pinia store. Configurable strategy: no tabbar, native tabbar, or custom tabbar. Currently set to `CUSTOM_TABBAR` with 5 tabs.
## Coding Conventions
### Vue Components
- Use `<script setup lang="ts">` exclusively — script must be first, template second, style last
- Name components with `defineOptions({ name: '...' })`
- Define props with `interface` + `withDefaults(defineProps<IProps>(), {...})`
- Project-specific components prefixed with `uh-` (e.g., `uh-post-card`, `uh-image`)
- Global components in `src/components/`, page-local in `src/pages/xx/components/`
- UI library: wot-ui (`wd-*` components), auto-imported via easycom
### TypeScript
- Use `interface` for object types, `type` for union types
- Use `import type` for type-only imports
- Avoid `any`
### Styling
- Prefer UnoCSS atomic classes over custom CSS
- SCSS with `scoped` when custom styles are needed; follow BEM naming
- Use `rpx` units for cross-platform screen adaptation
- Safe-area utilities: `p-safe`, `pt-safe`, `pb-safe`
- UnoCSS shortcut `center` = `flex items-center justify-center`
- Theme color `primary` from wot-ui CSS variable `--wd-primary-color`
### Platform Conditional Compilation
Use uni-app directives for platform-specific code:
```vue
<!-- #ifdef H5 -->
<view>H5 only</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view>WeChat only</view>
<!-- #endif -->
```
Import boundaries must not be reordered by ESLint — `perfectionist/sort-imports` is disabled for this reason.
### Git
- Conventional commits enforced via commitlint (`@commitlint/config-conventional`)
- Write commit messages in Chinese
- Pre-commit runs lint-staged via Husky
### i18n
English and Simplified Chinese via vue-i18n. Locale files in `src/locale/` (en.json, zh-Hans.json).
## Path Aliases
- `@``./src`
- `@img``./src/static/images`
## Environment Configuration
Environment files in `env/` directory:
- `.env` — Base config (app title, port 9000, app IDs, auth mode)
- `.env.development` — Dev backend URL, console enabled
- `.env.test` — Test environment
- `.env.production` — Production (console stripped)
-19
View File
@@ -1,19 +0,0 @@
# Git管理同步配置
#### 同时推送Gitee和Github
找到项目目录下的`.git`文件夹,打开`config`文件,修改相关代码:
原始代码:
```bash
[remote "origin"]
url = https://gitee.com/ialley-workshop-open/uni-halo.git
fetch = +refs/heads/*:refs/remotes/origin/*
```
修改后的代码:
```bash
[remote "github"]
url = https://github.com/ialley-workshop-open/uni-halo.git
fetch = +refs/heads/*:refs/remotes/github/*
[remote "gitee"]
url = https://gitee.com/ialley-workshop-open/uni-halo.git
fetch = +refs/heads/*:refs/remotes/gitee/*
```
+21 -661
View File
@@ -1,661 +1,21 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
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.
Preamble
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 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/>.
MIT License
Copyright (c) 2025 菲鸽
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the 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.
+28 -31
View File
@@ -4,7 +4,7 @@
</a>
</p>
<p align="center"><b>【uni-halo v2.0】</b> 基于 Halo2.X API 多端项目,值得一试。</p>
<p align="center"><b>【uni-halo v3.0】</b> 基于 Halo2.X API 多端项目,值得一试。</p>
<p align="center"><b style="font-size: 20px;color: #10B5FB">正式支持 Halo2.X 版本,免费开源</b></p>
@@ -20,9 +20,9 @@
如果您觉得这个项目对您有帮助,可以帮作者买杯饮料鼓励鼓励,同时为了项目能够持续发展,可以根据您的喜好支持一下本项目哦,非常感谢您的支持,作者也会更有动力持续维护和更新新的功能哦~
| 支付宝 | 微信 | 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) |
| 支付宝 | 微信 | QQ |
|:---------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------:|
| ![支付宝赞](https://gcore.jsdelivr.net/gh/uni-halo/uni-halo-static-resources/author/ZFBRewardCode.png) | ![微信赞](https://gcore.jsdelivr.net/gh/uni-halo/uni-halo-static-resources/author/WXRewardCode.png) | ![QQ赞](https://gcore.jsdelivr.net/gh/uni-halo/uni-halo-static-resources/author/QQRewardCode.png) |
## 一、项目介绍
@@ -34,30 +34,20 @@
- 支持特色功能,恋爱日记
- 理论支持编译为 小程序(推荐)、APP
### 在线预览
微信内容扫描下方二维码,即可在线预览小程序。
![QQ交流群](https://blog.925i.cn/upload/xiaochengxu.gif)
### 部分截图
|首页|分类|图库|
|首页|分类|我的|
|:--:|:--:|:--:|
|![首页](https://blog.925i.cn/upload/39789CF4434C9CD6A6289D7209AF6EEF.jpg)|![分类](https://blog.925i.cn/upload/19CB6B66F40200045B6F572A9C28C5E8.jpg)|![图库](https://blog.925i.cn/upload/464F22FDB216CE802653A5F03BE34351.jpg)|
|瞬间|我的|
|:--:|:--:|
|![瞬间](https://blog.925i.cn/upload/41EE8ADBFAE709A483A6E5F814C6A6E4.jpg)|![我的](https://blog.925i.cn/upload/9AEFE8DA4671A3C7F20F76FF3F9D15C9.jpg)|
|![首页](https://gcore.jsdelivr.net/gh/uni-halo/uni-halo-static-resources/screenshots/v2/首页.png)|![分类](https://gcore.jsdelivr.net/gh/uni-halo/uni-halo-static-resources/screenshots/v2/分类.jpg)|![我的](https://gcore.jsdelivr.net/gh/uni-halo/uni-halo-static-resources/screenshots/v2/关于.jpg)|
### 恋爱日记
|主页|恋爱清单|
|:--:|:--:|
|![主页](https://uni-halo.925i.cn/assets/love_001.6bf8b4e9.jpg)|![恋爱清单](https://uni-halo.925i.cn/assets/love_002.a08bd8d6.jpg)|
### 恋爱日记
- 在 uni-halo 中,我们为您准备了一个恋爱日记的模块,您可以在其中记录您的恋爱故事,分享给您的朋友们,请扫示例小程序,在线体验恋爱日记的功能。
![恋爱日记](https://gcore.jsdelivr.net/gh/uni-halo/uni-halo-static-resources/screenshots/v2/恋爱日记.jpg)
<br/>
@@ -66,7 +56,7 @@
- 基础功能:文章列表、文章分类、文章详情、个人图库、友情链接、我的瞬间
- 特色功能:恋爱日记,精心为您设计了秀恩爱的模块,秀出关于你们的故事、情侣甜蜜相册、恋爱记录清单,为您的恋爱保驾护航。
## 二、配套插件
@@ -74,7 +64,7 @@
- 代码仓库:[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)
### 使用方式
直接下载安装插件,在插件市场安装即可,安装完成后,需要配置插件,配置完成后,即可使用插件提供的功能。
@@ -83,21 +73,18 @@
## 三、相关文档
- 作者主页:[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://blog.xiaoxiaomo.cn/](https://blog.xiaoxiaomo.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://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)
- 微信交流群:由于微信交流群时效性太短,所以有需要的话可以先加入QQ群,再邀请进微信群
<br/>
@@ -109,6 +96,16 @@ uni-halo 还在持续更新中,本项目欢迎您的参与,共同维护,
<br/>
## 💪 贡献者们
感谢以下所有给 uni-halo 贡献过代码的 [开发者](https://github.com/ialley-workshop-open/uni-halo/graphs/contributors)。
<a href="https://github.com/ialley-workshop-open/uni-halo/graphs/contributors">
<img src="https://contrib.rocks/image?repo=ialley-workshop-open/uni-halo" />
</a>
<br/>
## 🔍 开源许可
uni-halo 使用 AGPL-3.0 协议开源,请遵守开源协议。
-28
View File
@@ -1,28 +0,0 @@
/**
* 功能:全局API管理
* 作者:小莫唐尼
* 邮箱:studio@925i.cn
* 时间:2022年07月21日 19:14:44
* 版本:v0.1.0
* 修改记录:
* 修改内容:
* 修改人员:
* 修改时间:
*/
// 2.0接口
import v2 from './v2/all.api.js'
import v2Config from './v2/all.config.js'
const ApiManager = {
v2,
v2Config,
};
const install = (Vue) => {
Vue.prototype.$httpApi = ApiManager
}
export default {
install
}
-216
View File
@@ -1,216 +0,0 @@
/**
* 所有的接口
*/
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
@@ -1,10 +0,0 @@
import HttpHandler from '@/common/http/request.js'
export default {
/**
* 获取应用基本配置
*/
getAppConfigs: () => {
return HttpHandler.Get(`/apis/api.uni.uhalo.pro/v1alpha1/plugins/plugin-uni-halo/getConfigs`, {})
},
}
-74
View File
@@ -1,74 +0,0 @@
/**
* 功能:全局过滤器
* 作者:小莫唐尼
* 邮箱:studio@925i.cn
* 时间:2022年07月21日 17:39:04
* 版本:v0.1.0
* 修改记录:
* 修改内容:
* 修改人员:
* 修改时间:
*/
export default {
/**
* 功能描述:时间格式化,将指定的时间戳(或正常的日期)转换为带格式的日期
*
* 参数说明:
* 1.支持格式化 yyyy年MM月dd日 HH点mm分ss秒 星期w q季
* 2.对象形式传入 { d:'2021-06-04',f:'yyyy年' } d是必传项,f可不传(默认yyyy-MM-dd HH:mm:ss
* 使用示例:
* 1<view>{{ dateTimeParamName | formatTime }}</view>
* 2<view>{{ { d: '2021-06-04', f: 'yyyy' } | formatTime }}</view>
* 3<view>{{ { d: dateTimeParamName, f: 'yyyy年MM月dd日 HH点mm分ss秒 星期w q季' } | formatTime }}</view>
* 特别说明: 由于uniapp中的filter 不支持多参数,但是允许传入对象的形式,故以此方式实现!
*/
formatTime: function(data) {
let _dateTime = new Date(data);
let _fmt = 'yyyy-MM-dd HH:mm:ss';
if (_dateTime == 'Invalid Date') {
if (data.d == undefined || data.d == null || data.d == "") {
console.error('日期参数不正确,传入的参数列表:', data);
return ''
};
_dateTime = new Date(data.d);
if (_dateTime == 'Invalid Date') {
console.error('日期参数不正确,传入的参数列表:', data);
return '111'
}
if (data.hasOwnProperty('f')) {
_fmt = data.f
}
}
const _weekDays = ["日", "一", "二", "三", "四", "五", "六"];
const _seasons = ["冬", "春", "夏", "秋"];
const o = {
"M+": _dateTime.getMonth() + 1, //月份
"d+": _dateTime.getDate(), //日
"H+": _dateTime.getHours(), //小时
"m+": _dateTime.getMinutes(), //分
"s+": _dateTime.getSeconds(), //秒
"w+": _weekDays[_dateTime.getDay()], // 星期几
"q+": _seasons[Math.floor((_dateTime.getMonth() + 3) / 3)], //季度
S: _dateTime.getMilliseconds(), //毫秒
};
if (/(y+)/.test(_fmt)) {
_fmt = _fmt.replace(
RegExp.$1,
(_dateTime.getFullYear() + "").substr(4 - RegExp.$1.length)
);
}
for (let k in o) {
if (new RegExp("(" + k + ")").test(_fmt)) {
_fmt = _fmt.replace(
RegExp.$1,
RegExp.$1.length == 1 ?
o[k] :
("00" + o[k]).substr(("" + o[k]).length)
);
}
}
return _fmt;
},
};
-38
View File
@@ -1,38 +0,0 @@
/**
* 功能:请求工具
* 作者:小莫唐尼
* 邮箱:studio@925i.cn
* 时间:2022年07月21日 18:58:03
* 版本:v0.1.0
* 修改记录:
* 修改内容:
* 修改人员:
* 修改时间:
*/
import HaloTokenConfig from '@/config/token.config.js'
import {
setInterceptors
} from "./interceptors.js";
import Request from "@/js_sdk/luch-request/luch-request";
const http = new Request()
/* 设置全局配置 */
http.setConfig((config) => {
// 如果是在外部浏览器调试或者编译为h5,请注释该行代码
config.baseURL = HaloTokenConfig.BASE_API;
config.header = {
...config.header,
ContentType: 'application/json',
dataType: 'json'
}
return config
})
setInterceptors(http)
export {
http
}
-157
View File
@@ -1,157 +0,0 @@
/**
* 功能:http拦截
* 作者:小莫唐尼
* 邮箱:studio@925i.cn
* 时间:2022年07月21日 19:02:14
* 版本:v0.1.0
* 修改记录:
* 修改内容:
* 修改人员:
* 修改时间:
*/
import {
getAdminAccessToken
} from "@/utils/auth.js";
import {
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 (config.custom.systemToken) {
config.header['Authorization'] = `Bearer ${config.custom.systemToken}`
}
return config;
},
(config) => {
// 可使用async await 做异步操作
return Promise.reject(config);
}
);
http.interceptors.response.use(
(response) => {
/* 对响应成功做点什么 可使用async await 做异步操作*/
// if (response.data.code !== 200) { // 服务端返回的状态码不等于200,则reject()
// return Promise.reject(response) // return Promise.reject 可使promise状态进入catch
// if (response.config.custom.verification) { // 演示自定义参数的作用
// return response.data
// }
if (response.statusCode == 200) {
return response.data;
} else {
return Promise.reject(response);
}
},
(response) => {
/* 对响应错误做点什么 statusCode !== 200*/
if (!response.data) {
return Promise.reject({
status: 500,
message: 'API接口服务异常!'
})
} else if (response.data.status == 401) {
uni.$tm.toast(response.data.message);
// 如果是请求分类之后报401说明密码错误,那么清除该密码,下次点击会报403弹窗再次输入密码
if (response.config.url.indexOf('/api/content/categories') >= 0) {
const category = getCategoryNameByUrl(response.config.url)
delCache('APP_CATEGORY_PWD_' + category);
uni.showToast({
title: '提示:密码不正确',
icon: 'none',
success: () => {
setTimeout(() => {
showCategoryInputPasswordModal(response, category);
}, 800)
}
})
} else {
// 其他情况维持原来的逻辑
delCache('APP_ADMIN_LOGIN_TOKEN');
uni.$eShowModal({
title: '提示',
content: '您未登录超管账号或登录已过期,是否重新登录?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
}).then(res => {
uni.navigateTo({
url: '/pagesB/login/login'
})
}).catch(err => {
uni.switchTab({
url: '/pages/tabbar/about/about'
})
})
}
return Promise.reject(response.data);
} else if (response.data.status == 403) {
// 如果报403是请求分类文章接口(您没有该分类的访问权限)的话说明是私密分类,需要输入密码请求
if (response.config.url.indexOf('/api/content/categories') >= 0) {
const category = getCategoryNameByUrl(response.config.url);
showCategoryInputPasswordModal(response, category);
}
return Promise.reject(response.data);
} else {
return Promise.reject(response.data);
}
}
);
};
-27
View File
@@ -1,27 +0,0 @@
/**
* 封装各种请求方式
*/
import {
http
} from '@/common/http/index.js'
export default {
Get: (url, params, config = {}) => {
return http.get(url, {
params,
...config
})
},
Post: (url, data, config = {}) => {
return http.post(url, data, config)
},
Put: (url, data, config = {}) => {
return http.put(url, data, config)
},
Upload: (url, config = {}) => {
return http.upload(url, config)
},
Delete: (url, data, config = {}) => {
return http.delete(url, data, config)
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-23
View File
@@ -1,23 +0,0 @@
/**
*
* 可以以页面为单位来写,
* 比如首页的内容,写在index字段,个人中心写在center,共同部分写在common部分
* */
export default {
app: {
name: "alley studio",
author: "Evan Mo",
},
tabbar: {
home: "Home",
moments: "Moments",
publish: "Publish",
mall: "Mall",
mine: "Mine",
},
// 提示文本
tips: {
switchLang: "switch Lang",
},
};
-23
View File
@@ -1,23 +0,0 @@
/**
*
* 可以以页面为单位来写,
* 比如首页的内容,写在index字段,个人中心写在center,共同部分写在common部分
* */
export default {
app: {
name: "巷子工坊",
author: "小莫唐尼",
},
tabbar: {
home: "首页",
moments: "动态",
publish: "发布",
mall: "商店",
mine: "我的",
},
// 提示文本
tips: {
switchLang: "切换语言",
},
};
-100
View File
@@ -1,100 +0,0 @@
/**
* markdown配置
*/
import {getAppConfigs} from '@/config/index.js'
import HaloTokenConfig from '@/config/token.config.js'
import utils from '@/utils/index.js'
export default {
domain: HaloTokenConfig.BASE_API,
tagStyle: {
table: `
table-layout: fixed;
border-collapse:collapse;
margin-bottom: 18px;
overflow: hidden;
font-size: 13px;
color: var(--routine);
background: #f2f6fc;
border: 1px solid #dcdcdc;
border-radius: 4px;
`,
th: `
padding: 8px;
border-right: 1px solid var(--classE);
border-bottom: 1px solid var(--classE);
`,
td: `
padding: 8px;
border-right: 1px solid var(--classE);
border-bottom: 1px solid var(--classE);
`,
blockquote: `
padding: 8px 15px;
color: #606266;
background: #f2f6fc;
border-left: 5px solid #50bfff;
border-radius: 4px;
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: `
margin: 30px 0 20px;
color: var(--main);
line-height: 24px;
position: relative;
font-size:1.2em;
`,
h2: `
color: var(--main);
line-height: 24px;
position: relative;
margin: 22px 0 16px;
font-size: 1.16em;
`,
h3: `
color: var(--main);
line-height: 24px;
position: relative;
margin: 26px 0 18px;
font-size: 1.14em;
`,
h4: `
color: var(--main);
line-height: 24px;
margin-bottom: 18px;
position: relative;
font-size: 1.12em;
`,
h5: `
color: var(--main);
line-height: 24px;
margin-bottom: 14px;
position: relative;
font-size: 1.1em;
`,
h6: `
color: #303133;
line-height: 24px;
margin-bottom: 14px;
position: relative;
font-size: 14px;
`,
p: `
line-height: 1.65;
margin-bottom: 14px;
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: 14px;color: #606266;word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 6px;background-color:#FFFFFF;',
loadingGif: utils.checkImageUrl(getAppConfigs().imagesConfig.loadingGifUrl),
emptyGif: utils.checkImageUrl(getAppConfigs().imagesConfig.loadingEmptyUrl),
}
-155
View File
@@ -1,155 +0,0 @@
:root {
--main: #303133;
--theme: #fb6c28;
--code-background: #e8f3ff;
--radius-inner: 4px;
--classA: #dcdfe6;
--classB: #e4e7ed;
--classC: #ebeef5;
--classD: #f2f6fc;
--classE: #dcdcdc;
--classF: #333;
--classG: #dcdcdc;
--classH: #e9f2ff;
--classI: #5a3713;
--classJ: #f9e5fb;
--classK: #e4e7ed;
--classL: #666;
--classM: #2d2e37;
--quote: #50bfff;
--code: #409eff;
}
.evan-markdown {
::v-deep {
h1::before,
h2::before,
h3::before,
h4::before,
h5::before,
h6::before {
position: relative;
display: inline-block;
vertical-align: middle;
content: '';
margin-right: 6px;
background-position: center;
}
h1::before {
position: relative;
display: inline-block;
vertical-align: middle;
content: '';
top: -4px;
margin-right: 12px;
font-size: 24px;
color: var(--theme);
}
h2::before {
top: -2px;
left: 0;
width: 20px;
height: 20px;
background-size: auto 100%;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAVJJREFUWEftl7FKw1AUhv9rbmf1PRw6utkHEOoi6WPoE1ifQJ8gySKCOtRFRBKsDuIiDdZAURBFF50M2sFIzpGogVK03JA2cbh3S/i55zvfvYETgRyLj5erIKOO949FyKm5eHvvHEBV2jyruq1QDQ7n2DNXALEx+D7e2vl6lBYp76scHCzErtmCEPVhqEIAfus8BZk4wM+Zd/46tskDeI0mgLUyAdoAFsoDcM0XCDFdHoDX4FGfbRF3QANoA9qANqANaAPawD82EEWId1sAOJQWz6iO+5nGch41kDw9I3bbYOaTis21wgG4G4AuAzCwXrEoGV6V1ngMJPoPjoB+PzSIa8KBr1QdwFgA6KID7t1AgFYNC5uqxZNcbgB+eASdngHM+9LmpSzF8wFEEagbgHvXoQA3s3aegmYz8P1f4NNVME+39z5e3w4lkSMc3GXtPM1/AjYDFjDGddN5AAAAAElFTkSuQmCC);
}
h3::before {
top: -3px;
left: 0;
width: 20px;
height: 20px;
background-size: auto 100%;
background-repeat: none;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAH1JREFUWEft1qENgDAQheH/JANg60DAJIgmrMFCzEEYgTFgDyRFIClp0yBf9evl8pl3RsYbp7BjNLHoMptljPiMZH3WAhKQgAQkIIGXQAXUT79cniHaNMa5draliqqojIID86nRHEtvbSqlBSQgAQlIQAISkECRAA746SC5Ad6XpiGnnOGPAAAAAElFTkSuQmCC);
}
h4::before {
top: -2px;
width: 22px;
height: 22px;
color: var(--theme);
background-size: auto 100%;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAndJREFUWEftVkty2kAQfT1SBLtwg6AqwzbxCQIniG8QvAmwCp8kRqvgFa5KwHjlkI3NCeIbhJwgXptUgW9AdiDQdGoITqUsjcZyuYqNZ6WSXs88vel+3YQtL9ry+Xgk8KhApALNAe8x5PPbCUrgabdsD2/e+1edEpN4FsIRps7OwT9cXKKHCLw75QIL+V0XRFIUP1dptBx3ChKkxQlw8UnOG5mqLESgdsYZy5dTAE8jg5lH3Ypd5Mlxxl8ttDgGRulcq5iYgApofgn6ILzVBQsSu5/e0OXiqnMOotdatVjsOvkPl4muQIFrp5y1hJzEBA67ZavEk07WX5EexzxM5b1SYgJrFQbBOQDt3wVSuP0qTRfjowsAr3SHODa75HrqqiKX1gdMycjAYa9stU3JCPBhKue1ExNQAY3BakSgl1HBDMykI9z+Ps0WV0eXIITKdhM3c+yUS259FrVPrBM2TlclEnSmY8+S93tV+/yvH+hxxLzv5D11paFltOLmIFD3FzIbtRMzpr2K5arn+bgzJZAWl8631rjby0zAUJI3xnTfkjQTiFEAwHW3bGWNCoCv0zlvjUukgOoJgPymzQGg3itbfVMOMKiezh30ExOIqwIAvwNHZFUVzMcdbbUonGOnsomr4P1XfiFZ/tS6GOOkW7FqJh8gwomz06ol9oE7O6GhH9zLCbfeCxqDoE3AR61sm5nA/3XUZ47pmneYCSLnAeHLCQGZaAvmH72yXdjMA6oTanHpnFfQ5tDmQ+KJ6MZ+jckXY7//k4pWYB7sQVCUccxU3a9teHKcWS7ne0wI4Rhipqv7REZkku8hvhut+CEOidvjkcDWFfgD9RMzMKE7f80AAAAASUVORK5CYII=);
}
h5::before {
top: -1px;
left: 0;
width: 18px;
height: 18px;
background-size: 100% 100%;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAC8klEQVRYR+3WP2gTURwH8O/vKnVRRHKXP52cBO3g4p/BqYNIhy4muajUQRBFKjQV1En6ZxJBcmlRCoJDRe3FDiJVwamLS3FwqbgIgss1l2ZQF5XeT3I1Z3NJ7u5dLlAwN9699/t97vfe7/EIO/yhHe5DD9jpCv3fFVTu856+Xzi62Y/35hj9CFPNrlUwWeRJAJfBGADwBcBNI0/PRZFdAdo4xlQLjCqKjBzogat7hZCRAgPghJGBgbFnxglAOvS7b/fLb+q+qnv5BHBCyEBAWTdnANy2IxOtWSC1qsY+1jOFwAVG+gIVvXyHQbcaKuZCxmf5iMQogXFQtEsBeO5JT6BcMu+Bcb1lUhcyqfExACUAB6JEtgUqi+U5JrrmmcyFHCjwSYtQO+tSUSFbAmXdnAdwJVASdyWLPAS2kbFA8xsHNS13EzCmlx8R6KJQcBcypfFpho3cKxRna3ADsgEo6+ZjAKMhgjZ1d2KWR2gTSyD0h4jnIB2gXDIXwciFCPZvSnMl0wwshYrJGDImaMUGKovlLBPVOrDjh8APzFx8zDkjNa7FzYoGZsKT9XEarQOHmei1aJA246cqOWW6/i2l8VMGzoWIPW/k6eq2Ja6UwCz8pw2JGZ8sS8pUz8fWau/jGp+SgLchcGBgZD1Py41NUuoI+ZloV8ZU93+ogZKzfBiWfXAPCgMJU8Y42avQdMzI4ZBfmSizocqrUeJaAmsvBZGGBCtTziXeRY1rCxRAViEhXckqK93AeQJ9kYTvlmVlqmcTdhNEtefc+9X3utVmuX+CkK6oyqtu4nwrWP8bF5IZdGYjJ79wDuMCz4D+XmhFWnZbt7ab5ltBB6mbkyAaBPFCJassuwMmNb4L4EZgXwBc4AoGTZrUeA6A9x1yK6tzzvnFDlxBv0D176kCP2TCpbbjBXCRV9DZk0VeAONCE1IQ1zWg3dlF1sFQHWQIXFeBNrLAw5BwHBZWjQl6E3SbbB8X+R4Mg/Ca0wN2WtFeBTut4B84mFI4VpekyAAAAABJRU5ErkJggg==);
}
h6::before {
top: -1px;
left: 0;
width: 16px;
height: 16px;
background-size: auto 100%;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAEI0lEQVRYR+3Xb2wTZRwH8G/vL22RPy5GW8fK6rJpGeFFY9RO3TRRE1HfmEAammEyjZmOSEg1RrPZaTD6xjhU/Ndlf0CZgwmD+qcgZBRIETeqY0Vcnc7pGonhRY2l3m2t5upqjq693l2vcy+8N81zz/NcPvf75fd7rjos8ku3yH34HyjK0PqlDLPzD56vMNL0VHxmpgXAoUIZXKgI3sMSpK+cNTAbylaj/9IkprnLM3+mkg8XQi4EsJ4lSL+ZNbAfVd+JG/XLcSERg3M8IAtZauBtDEkeNdN6fX9NfRqXueQiSwm00wQRMDMGw94snBJkqYBrSYIIXs/ojftqGq6IXHZRFIpkKYC2JSw9SJE663W0nthlqcMawwrJYpVCag1M48pNy1YNvLWRffGNIYSCv6Cnog7rjCtVIbUEXoG7qeqaNMj51F58dWoKPZY62JeWyUJG+ctcIplcIizWCpgTl9EIyODJn9BtceDWq/6B57tO/H4R688fFaZ7AWzWAiiJEyMDx39Ed6UDdyy7Ni/w1ekxbP95VJi/F8CRYoGycGLksaEJdFU6cPdy0zxkR/RbtE6FhPvHATQUm2JFODHSfyyCLmsd7lth/hf53q/jcE8OC+NTAG7PTKiNoCqcGPnpF+PotDrwwNXl+OC3H9A8cVqYPgPgFnFo1QCLwomRg4cvoPMGBzZHTgq3zwKwZ+ddKVATXAZhf+gdnPvuojAUqmJdrspRAtQU93pXEM+8clgwhQHU5itruUBNcTt6TsP9sl8weQC0S/VFOUBNcW/2folt2z+XhZPTZh6kSKJ79aqVRuFszRxfUm8sNbdz9xlsfekz2bhCQBvDMMM8z+ubN92Mjrb71brS+97dM4wtnk8U4SSBBEGErVarzeVywePxoHVLA1pb6lUh3+8bwZMv+BTjpIAVACa9Xq+uqakJ7e3tqpGd/WfR3Jr+81awIJS0mY0A+sLhMGw2W3qfGmTXvhAef/6gapxUBDuqqqoejUQiBvFbKUH2DHyNx54bLAqXF0jT9HBjY6Pd6/XOi7oc5K7936Dp2QNF4/IBrTqdLuLxeIi2tracRSGF/PDgKB55er8muHzATQB2m81m+P1+1NbmPoVyIfsOnUOj+2PNcPmAO1iWbeE4TmexWODz+WQhayrL4No2oCkuJ5Bl2VGO49ZmcisXObdeVSuRaq7ZZ3G10KBTqRQl3pQPGY1GEQgE4HQ6heVDAO5S1cklNmUDXQRB9KZSqXkfESaTCW63G7FYDKFQKDEyMoJoNKqfe/bbAJ7QGpcrxa8xDLOV5/k0kGXZv2ZnZ5FMJtNjmqYvURR1IpFInAcwAeD7ud/pUuDmAY1G41g8Hl9DkmScoqggx3FHAIyJMMlSQfI9V5zKagDC93dsoRFKimQx2dIWOV/U/yn6bx0WyDj8vgLOAAAAAElFTkSuQmCC);
}
blockquote > p {
margin-bottom: 0 !important;
margin-top: 0 !important;
font-size: 0.9em !important;
}
code[class='md-code'],
code:not([class]) {
display: inline-block;
font-size: 13px;
color: #409eff;
margin: 2px 5px;
padding: 0 8px;
white-space: normal;
text-indent: 0;
-webkit-user-select: auto;
-moz-user-select: auto;
-ms-user-select: auto;
user-select: auto;
vertical-align: baseline;
word-break: break-word;
background: #e8f3ff;
border-radius: 4px;
}
code[class*='language-'] {
display: block;
overflow-x: auto;
// border-radius: 0 0 8px 8px;
white-space: pre-wrap;
word-break: break-all;
user-select: auto;
padding: 12px 12px 14px 18px;
margin-bottom: 16px;
background: #282c34;
color: #abb2bf;
border-radius: 4px;
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
font-family: 'Fira Code', 'Fira Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
line-height: 1.5;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
table {
td {
padding: 8px;
border-right: 1px solid var(--classE);
border-bottom: 1px solid var(--classE);
}
thead th {
font-weight: 500;
background: var(--classC);
}
tbody tr {
transition: background 0.35s;
}
}
}
}
-85
View File
@@ -1,85 +0,0 @@
/**
* 功能:全局混入函数
* 作者:小莫唐尼
* 邮箱:studio@925i.cn
* 时间:2022年07月21日 17:39:32
* 版本:v0.1.0
* 修改记录:
* 修改内容:
* 修改人员:
* 修改时间:
*/
export default {
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",
})
},
// 页面返回顶部
fnToTopPage(duration = 500) {
duration = isNaN(duration) ? 500 : duration
uni.pageScrollTo({
scrollTop: 0,
duration: duration,
fail: (err) => {
console.log('err', err);
},
});
},
// 初始化动画索引值(需要在每个页面调用)
fnResetSetAniWaitIndex() {
this._aniWaitIndex = 0
}
},
});
},
};
-174
View File
@@ -1,174 +0,0 @@
// 全局css基础样式
.card-shadow {
box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.08);
}
.bg-white {
background-color: #fff;
}
.load-text {
padding: 0 0 20rpx 0;
text-align: center;
color: #999;
font-size: 24rpx;
}
.e-fixed {
position: fixed;
left: 0;
/* #ifndef H5 */
top: 0;
/* #endif */
/* #ifdef H5 */
top: 88rpx;
/* #endif */
right: 0;
z-index: 6;
}
.e-loading-icon {
animation: eLoading 0.8s linear infinite;
}
@keyframes eLoading {
0% {
transform: rotateZ(0deg);
}
100% {
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;
}
}
/* 文本省略样式 结束 */
-8
View File
@@ -1,8 +0,0 @@
// 主题
:root {
--theme: #f79ea3;
// --theme: #ffaec3;
--main: #303133;
--main-text-color: rgba(12, 25, 50, 1);
}
File diff suppressed because one or more lines are too long
-352
View File
@@ -1,352 +0,0 @@
<template>
<view class="article-card " :class="cardType" @click="fnClickEvent('card')">
<view class="left">
<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 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.status.excerpt }}</view>
<view class="foot">
<view class="create-time">
<text class="time-label">发布时间</text>
{{ { d: article.spec.publishTime, f: 'yyyy-MM-dd' } | formatTime }}
</view>
<view class="visits">
浏览
<text class="number">{{ article.stats.visit }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
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);
}
}
};
</script>
<style scoped lang="scss">
.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: 340rpx;
.thumbnail {
::v-deep uni-image {
border-radius: 6rpx 6rpx 0 0 !important;
}
}
}
.right {
padding-left: 0;
padding: 24rpx 0;
padding-bottom: 0;
width: 100%;
.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: 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;
}
}
}
}
&.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: 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;
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;
}
&-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>
@@ -1,220 +0,0 @@
<template>
<view class="article-min-card" :class="[globalAppSettings.layout.cardType]" @click="fnClickEvent('card')">
<view class="left">
<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>
<view class="content text-overflow">{{ article.summary }}</view>
<view class="foot">
<view class="create-time">
<!-- <text class="icon iconfont icon-clock"></text> -->
<text class="time-label">发布时间</text>
{{ { d: article.createTime, f: 'yyyy-MM-dd' } | formatTime }}
</view>
<view class="visits">
<!-- <text class="icon iconfont icon-eye"></text> -->
浏览
<text class="number">{{ article.visits }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'article-min-card',
props: {
article: {
type: Object,
default: () => {}
}
},
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.03);
&.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;
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>
-339
View File
@@ -1,339 +0,0 @@
<template>
<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"><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>
</view>
</template>
<script>
export default {
props: {
// rightToLeft leftToRight leftBottom
type: {
type: String,
default: 'rightToLeft'
},
list: {
type: Array,
default() {
return [];
}
},
minTime: {
type: Number,
default: 4
},
maxTime: {
type: Number,
default: 9
},
minTop: {
type: Number,
default: 0
},
maxTop: {
type: Number,
default: 240
},
hrackH: {
//轨道高度
type: Number,
default: 40
}
},
data() {
return {
closeTimer: null,
listData: []
};
},
mounted() {
//leftBottom 使用参数
if (this.type === 'leftBottom') {
this.hrackNum = Math.floor(this.maxTop / this.hrackH);
}
},
methods: {
add(obj) {
this.isShow = true;
this.showFlag = true;
let data = {
...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,
type: 'leftBottomEnter',
style: {
transition: `all 0.5s`,
animationDuration: `0.5s`,
transform: `translateX(0%)`,
bottom: `${this.minTop}px`
}
};
let listLen = this.listData.length;
let hrackNum = this.hrackNum;
for (let i in this.listData) {
if (this.listData[i].status === 'reuse') {
//重用
this.$set(this.listData, i, objData);
} else if (this.listData[i].status === 'reset') {
//重置
this.listData[i].style.transition = 'none';
this.listData[i].style.bottom = '6px';
this.listData[i].status = 'reuse';
} else if (this.listData[i].status === 'recycle') {
//回收
this.listData[i].type = 'leftBottomExit';
this.listData[i].status = 'reset';
} else {
this.listData[i].style.bottom = parseInt(this.listData[i].style.bottom) + this.hrackH + 'px';
}
if (parseInt(this.listData[i].style.bottom) >= this.maxTop - this.hrackH && this.listData[i].status !== 'reset') {
//需要回收
this.listData[i].status = 'recycle';
}
}
if (listLen < hrackNum + 2) {
this.listData.push(objData);
}
} 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);
objData.type = '';
this.$set(this.listData, i, objData);
return;
}
}
this.listData.push(objData);
}
},
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 // 弹幕消失的速度
},
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(() => {
this.listData[index].type = type;
}, 100);
}
}
};
</script>
<style lang="scss" scoped>
@keyframes leftBottomEnter {
0% {
transform: translateY(100%);
opacity: 0;
}
100% {
transform: translateY(0%);
opacity: 1;
}
}
@keyframes leftBottomExit {
0% {
transform: translateY(0%);
opacity: 1;
}
100% {
transform: translateY(-200%);
opacity: 0;
}
}
@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%);
}
100% {
transform: translateX(100%);
}
}
@keyframes rightToLeft {
0% {
transform: translateX(100%);
}
100% {
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;
}
&.leftBottomExit {
animation-name: leftBottomExit;
animation-fill-mode: forwards;
}
&.leftBottomExitLeft {
animation-name: leftBottomExitLeft;
animation-fill-mode: forwards;
}
&.rightToLeft {
animation-name: rightToLeft;
}
&.leftToRight {
animation-name: rightToLeft;
}
.danmu-inner {
display: inline-block;
font-size: 24rpx;
.user-box {
display: flex;
padding: 6rpx 40rpx 6rpx 10rpx;
background: rgba(0, 0, 0, 0.3);
border-radius: 32rpx;
align-items: center;
.user-img {
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-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;
font-weight: 400;
color: rgba(255, 255, 255, 1);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
}
}
</style>
-198
View File
@@ -1,198 +0,0 @@
<template>
<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: {},
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;
}
.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,79 +0,0 @@
<template>
<view class="category-mini-card">
<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: () => {}
}
}
};
</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;
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
position: relative;
text-align: center;
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>
-165
View File
@@ -1,165 +0,0 @@
<template>
<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 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.owner.displayName }}</text>
</view>
<view class="flex mt-4">
<view class="time text-size-xs text-grey">
<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 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.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: () => []
},
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;
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;
}
}
</style>
-268
View File
@@ -1,268 +0,0 @@
<template>
<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.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,
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'
};
}
this.$emit("on-comment", _comment)
},
fnCopyContent(content) {
uni.$tm.u.setClipboardData(content);
uni.$tm.toast('内容已复制成功!');
},
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: 30rpx;
font-weight: bold;
&:before {
content: '';
position: absolute;
left: 0rpx;
top: 8rpx;
width: 8rpx;
height: 26rpx;
background-color: rgb(3, 174, 252);
border-radius: 6rpx;
}
.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;
}
}
.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;
}
}
}
</style>
-231
View File
@@ -1,231 +0,0 @@
<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>
-341
View File
@@ -1,341 +0,0 @@
// 轮播图
.Swiper-mfw-index-box {
display: flex;
width: 100%;
justify-content: center;
align-items: center;
view {
display: flex;
}
.Swiper-mfw-index {
// 轮播图
width: 100%;
.Swiper-mfw {
width: inherit;
height: 450rpx;
border-radius: 12rpx;
.swiper-mfw-item {
width: inherit;
height: inherit;
border-radius: 12rpx;
.Image,
.ImageVideo {
border-radius: 12rpx;
width: inherit;
height: inherit;
}
}
}
// 指示器
.Swiper-indicator-box {
width: inherit;
display: flex;
flex-direction: column;
align-items: center;
// Top顶部 [今日首推-盒子]
.Top-date-hot {
width: 100%;
box-sizing: border-box;
padding: 20rpx 24rpx;
display: flex;
align-items: center;
flex-wrap: nowrap;
.left-date-ri {
justify-content: center;
.date-ri-text {
color: #ffffff;
font-size: 60rpx;
font-weight: 700;
margin-top: -4rpx;
}
}
.conter-date-nianyue {
margin: 0 14rpx;
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
flex-wrap: nowrap;
.left-width-bgcolor {
width: 8rpx;
height: 45rpx;
border-radius: 12rpx;
background-color: #fafafa;
margin-right: 14rpx;
}
.right-date-nianyue {
flex-direction: column;
.Top-yue-usa,
.Bottom-nian,
.text {
color: #ffffff;
font-size: 24rpx;
font-weight: 700;
transform: scale(0.95);
}
.text {
margin-top: -4rpx;
}
}
}
.right-hot-ttf {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 6rpx;
.hot-text {
color: #ffffff;
font-size: 52rpx;
font-weight: 700;
}
}
}
}
// 指示器 [轮播信息 -> 标题,用户,头像,所在地]
.Swiper-indicator-Top {
position: absolute;
left: 0rpx;
bottom: 110rpx;
width: 100%;
box-sizing: border-box;
padding: 20rpx 24rpx;
&.no-dot {
bottom: 0;
}
.Top-item {
width: 100%;
display: flex;
flex-direction: column;
// 如果有视频,则显示“视频预览”
.Top-ImageVideo {
width: 150rpx;
box-sizing: border-box;
padding: 2rpx 6rpx;
margin-bottom: 10rpx;
background-color: #f0ad4e;
border-radius: 20rpx;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
.Icons {
color: #242629;
font-size: 26rpx;
transform: scale(0.8);
}
.ImageVideo-text {
color: #242629;
font-size: 24rpx;
}
}
// 标题
.Top-Title {
width: 100%;
display: flex;
align-items: flex-start;
.title-text {
width: 100%;
color: #ffffff;
font-size: 28rpx;
font-weight: 700;
}
}
// 用户信息盒子
.Bottom-UserInfo {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
margin-top: 16rpx;
align-items: center;
.UserImage-box {
width: 40rpx;
border-radius: 20rpx;
margin-right: 20rpx;
.Image {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
}
}
.textbox {
flex-direction: row;
flex-wrap: nowrap;
.wo-text,
.UserInfo {
font-size: 24rpx;
}
.wo-text {
color: #f1f2f6;
margin-right: 8rpx;
}
.UserInfo {
color: #ffffff;
}
}
.jiange-box {
margin: 0 8rpx;
.jiange-text {
color: #f1f2f6;
}
}
}
}
}
// 指示器 [左边图片列表+右边按钮]
.Swiper-indicator-Bottom {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
display: flex;
justify-content: space-between;
border-radius: 12rpx;
flex-direction: row;
flex-wrap: nowrap;
box-sizing: border-box;
padding: 14rpx;
background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), rgba(101, 101, 101, 0.7));
// 左边[图片列表]
.Bottom-left-Imagelist {
// width: 560rpx;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
// 指示图(小图模式)
.Bottom-item {
width: 98rpx;
height: 78rpx;
border-radius: 8rpx;
.Image {
width: 98rpx;
height: 78rpx;
border-radius: 8rpx;
box-sizing: border-box;
}
&.current {
.Image {
// border: 4rpx solid #ffda02;
border: 4rpx solid rgb(110, 186, 247);
}
}
}
.Bottom-item + .Bottom-item {
margin-left: 10rpx;
}
}
// 右边 [历历在目-按钮]
.Bottom-right-lili-btn {
width: 145rpx;
align-items: center;
justify-content: center;
border-radius: 10rpx;
// background-image: linear-gradient(45deg, rgb(110, 186, 247), rgb(13, 141, 242));
.Bottom-item {
width: 145rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #fff;
.indicator-text {
font-size: 24rpx;
font-weight: 700;
}
.more {
display: flex;
align-items: center;
font-weight: bold;
font-size: 26rpx;
.iconfont {
color: #fff;
font-size: 24rpx;
}
}
.text {
display: flex;
flex-direction: column;
align-items: center;
// font-weight: normal;
letter-spacing: 4rpx;
}
}
}
}
}
.Swiper-box {
border-radius: 12rpx;
overflow: hidden !important;
transform: translateY(0) !important;
transform: translateX(0) !important;
&.right {
.indicator-Top-box {
position: absolute;
}
.Swiper-indicator-Top {
bottom: 0;
.Top-item .Top-Title .title-text {
width: 540rpx;
}
}
.Swiper-indicator-Bottom {
width: 120rpx;
position: absolute;
top: 0rpx;
left: initial;
right: 0rpx;
flex-direction: column;
background-image: none;
background-color: rgba(0, 0, 0, 0.1);
// border-radius: 0rpx 12rpx 12rpx 0;
border-radius: 12rpx;
.Bottom-left-Imagelist {
flex-direction: column;
align-items: center;
.Bottom-item {
border-radius: 6rpx;
}
.Bottom-item + .Bottom-item {
margin-left: 0rpx;
margin-top: 10rpx;
}
}
.Bottom-right-lili-btn {
width: 100rpx;
margin-left: -4rpx;
.Bottom-item {
width: initial;
margin-top: 6rpx;
align-items: flex-start;
justify-content: flex-start;
.more {
font-size: 24rpx;
}
.text {
letter-spacing: 0;
font-size: 24rpx;
font-weight: normal;
}
}
}
}
}
}
}
.Swiper-mfw {
position: relative;
}
.indicator-Top-box {
position: absolute;
}
.indicator-Btoom-box {
position: absolute;
bottom: 0;
}
-220
View File
@@ -1,220 +0,0 @@
<template>
<!-- 轮播图 -->
<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">
<!-- 只需要前5条数据 -->
<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>
</block>
</swiper>
<!-- 指示器 [Top] -->
<view v-if="useTop" class="Swiper-indicator-box indicator-Top-box">
<!-- Top顶部 [今日首推-盒子] -->
<view class="Top-date-hot">
<view class="left-date-ri">
<text class="date-ri-text text">{{ date.month }}</text>
</view>
<view class="conter-date-nianyue">
<view class="left-width-bgcolor"></view>
<view class="right-date-nianyue">
<text class="Top-yue-usa text">{{ date.monthEn }}</text>
<text class="Bottom-nian text">{{ date.year }}</text>
</view>
</view>
<view class="right-hot-ttf">
<text class="text hot-text">{{ title }}</text>
</view>
</view>
</view>
<!-- 指示器 标题区域 -->
<view v-if="useTitle" class="Swiper-indicator-Top" :class="{ 'no-dot': !useDot }">
<block v-for="(item, index) in list" :key="index">
<view v-if="currentIndex == index" class="Top-item" :class="current == index ? 'current' : 'no'">
<!-- 如果存在视频则显示视频预览提示 -->
<view v-if="item.mp4" class="Top-ImageVideo">
<!-- icon图标 -->
<view class="Icons">
<!-- 播放按钮图标 -->
<text class="iconfont icon-caret-right"></text>
</view>
<text class="text ImageVideo-text app-ttf">视频预览</text>
</view>
<!-- 标题 -->
<view class="Top-Title">
<text class="text title-text">{{ item.title }}</text>
</view>
<!-- 用户信息 -->
<view class="Bottom-UserInfo">
<!-- 头像 -->
<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="textbox UserGPS-box">
<text class="text UserInfo">发布于 {{ item.createTime }}</text>
</view>
<view v-if="item.address" class="jiange-box"><text class="text jiange-text">·</text></view>
<view v-if="item.address" class="textbox UserGPS-box">
<text class="text UserInfo">{{ item.address }}</text>
</view>
</view>
</view>
</block>
</view>
<!-- 指示器 [左边图片列表+右边按钮] -->
<view v-if="useDot" class="Swiper-indicator-Bottom">
<!-- 左边 -->
<view class="Bottom-left-Imagelist">
<block v-for="(item, index) in list" :key="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 v-if="false" class="Bottom-right-lili-btn">
<view class="Bottom-item is-more">
<view class="more" @click.stop="$emit('on-more')">
MORE
<text class="iconfont icon-caret-right"></text>
</view>
<text class="left text indicator-text">更多推荐</text>
</view>
</view>
</view>
</view>
</view>
</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: '-'
}
};
},
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') {
let event = {
current: current
};
this.currentIndex = current;
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';
</style>
@@ -1 +0,0 @@
<style>.hl-code,.hl-pre{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}.hl-pre{padding:1em;margin:.5em 0;overflow:auto}.hl-pre{background:#2d2d2d}.hl-block-comment,.hl-cdata,.hl-comment,.hl-doctype,.hl-prolog{color:#999}.hl-punctuation{color:#ccc}.hl-attr-name,.hl-deleted,.hl-namespace,.hl-tag{color:#e2777a}.hl-function-name{color:#6196cc}.hl-boolean,.hl-function,.hl-number{color:#f08d49}.hl-class-name,.hl-constant,.hl-property,.hl-symbol{color:#f8c555}.hl-atrule,.hl-builtin,.hl-important,.hl-keyword,.hl-selector{color:#cc99cd}.hl-attr-value,.hl-char,.hl-regex,.hl-string,.hl-variable{color:#7ec699}.hl-entity,.hl-operator,.hl-url{color:#67cdcc}.hl-bold,.hl-important{font-weight:700}.hl-italic{font-style:italic}.hl-entity{cursor:help}.hl-inserted{color:green}</style><style>.md-p{margin-block-start:1em;margin-block-end:1em}.md-blockquote,.md-table{margin-bottom:16px}.md-table{box-sizing:border-box;width:100%;overflow:auto;border-spacing:0;border-collapse:collapse}.md-tr{background-color:#fff;border-top:1px solid #c6cbd1}.md-table .md-tr:nth-child(2n){background-color:#f6f8fa}.md-td,.md-th{padding:6px 13px!important;border:1px solid #dfe2e5}.md-th{font-weight:600}.md-blockquote{padding:0 1em;color:#6a737d;border-left:.25em solid #dfe2e5}.md-code{padding:.2em .4em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:85%;background-color:rgba(27,31,35,.05);border-radius:3px}.md-pre .md-code{padding:0;font-size:100%;background:0 0;border:0}.hl-pre{position:relative}.hl-code{overflow:auto;display:block}.hl-language{font-size:12px;font-weight:600;position:absolute;right:8px;text-align:right;top:3px}.hl-pre{padding-top:1.5em}.hl-pre{font-size:14px;padding-left:3.8em;counter-reset:linenumber}.line-numbers-rows{position:absolute;pointer-events:none;top:1.5em;font-size:100%;left:0;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows .span{display:block;counter-increment:linenumber}.line-numbers-rows .span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}._address,._article,._aside,._body,._caption,._center,._cite,._footer,._header,._html,._nav,._pre,._section{display:block}._video{width:300px;height:225px;display:inline-block;background-color:#000}</style><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>body,html{width:100%;height:100%;overflow-x:scroll;overflow-y:hidden}body{margin:0}video{width:300px;height:225px}img{max-width:100%;-webkit-touch-callout:none}</style></head><body><div id="content" style="overflow:hidden"></div><script type="text/javascript" src="./js/uni.webview.min.js"></script><script type="text/javascript" src="./js/handler.js"></script></body>
-735
View File
@@ -1,735 +0,0 @@
export default{
data(){
return{
system_info:{}, //system info
canvas_width:0, //canvas width px
canvas_height:0, //canvas height px
ctx:null, //canvas object
canvas_id:null, //canvas id
hidden:false,//Whether to hide canvas
scale:1,//canvas scale
r_canvas_scale:1,
if_ctx:true
}
},
methods:{
/**
* save r-canvas.vue object
* @param {Object} that
*/
// saveThis(that){
// rCanvasThis = that
// },
/**
* Draw round rect text
* @param {Object} config
* @param {Number} config.x x坐标
* @param {Number} config.y y坐标
* @param {Number} config.w 宽度
* @param {Number} config.h 高度
* @param {Number} config.radius 圆角弧度
* @param {String} config.fill_color 矩形颜色
*/
fillRoundRect(config) {
return new Promise((resolve,reject)=>{
let x = this.compatibilitySize(parseFloat(config.x)*this.scale)
let y = this.compatibilitySize(parseFloat(config.y)*this.scale)
let w = this.compatibilitySize(parseFloat(config.w)*this.scale)
let h = this.compatibilitySize(parseFloat(config.h)*this.scale)
let radius = config.radius?parseFloat(config.radius)*this.scale:10*this.scale
let fill_color = config.fill_color || "black"
// The diameter of the circle must be less than the width and height of the rectangle
if (2 * radius > w || 2 * radius > h) {
reject("The diameter of the circle must be less than the width and height of the rectangle")
return false;
}
this.ctx.save();
this.ctx.translate(x, y);
//
this.drawRoundRectPath({
w: w,
h: h,
radius: radius
});
this.ctx.fillStyle = fill_color
this.ctx.fill();
this.ctx.restore();
resolve()
})
},
/**
* Draws the sides of a rounded rectangle
* @param {Object} config
* @param {Number} config.w 宽度
* @param {Number} config.h 高度
* @param {Number} config.radius 圆角弧度
*/
drawRoundRectPath(config) {
this.ctx.beginPath(0);
this.ctx.arc(config.w - config.radius, config.h - config.radius, config.radius, 0, Math.PI / 2);
this.ctx.lineTo(config.radius, config.h);
this.ctx.arc(config.radius, config.h - config.radius, config.radius, Math.PI / 2, Math.PI);
this.ctx.lineTo(0, config.radius);
this.ctx.arc(config.radius, config.radius, config.radius, Math.PI, Math.PI * 3 / 2);
this.ctx.lineTo(config.w - config.radius, 0);
this.ctx.arc(config.w - config.radius, config.radius, config.radius, Math.PI * 3 / 2, Math.PI * 2);
this.ctx.lineTo(config.w, config.h - config.radius);
this.ctx.closePath();
},
/**
* Draw special Text,line wrapping is not supported
* @param {Object} config
* @param {String} config.text 文字
* @param {Number} config.x x坐标
* @param {Number} config.y y坐标
* @param {String} config.font_color 文字颜色
* @param {String} config.font_family 文字字体
* @param {Number} config.font_size 文字大小(px
*/
drawSpecialText(params){
let general = params.general
let list = params.list
return new Promise(async (resolve,reject)=>{
if(!general){
reject("general cannot be empty:101")
return;
}else if(list && list.length>0){
for(let i in list){
if(i != 0){
let font_size = list[i-1].font_size?parseFloat(list[i-1].font_size):20
this.ctx.setFontSize(font_size)
general.x = parseFloat(general.x) + this.ctx.measureText(list[i-1].text).width
}
list[i].x = general.x
list[i].y = general.y + (list[i].margin_top?parseFloat(list[i].margin_top):0)
await this.drawText(list[i])
}
resolve()
}else{
reject("The length of config arr is less than 0")
return;
}
})
},
/**
* array delete empty
* @param {Object} arr
*/
arrDeleteEmpty(arr){
let newArr = []
for(let i in arr){
if(arr[i]){
newArr.push(arr[i])
}
}
return newArr
},
/**
* Draw Text,support line
* @param {Object} config
* @param {String} config.text 文字
* @param {Number} config.max_width 文字最大宽度(大于宽度自动换行)
* @param {Number} config.line_height 文字上下行间距
* @param {Number} config.x x坐标
* @param {Number} config.y y坐标
* @param {String} config.font_color 文字颜色
* @param {String} config.font_family 文字字体 默认值:Arial
* @param {String} config.text_align 文字对齐方式(left/center/right
* @param {Number} config.font_size 文字大小(px
* @param {Boolean} config.line_through_height 中划线大小
* @param {Boolean} config.line_through_color 中划线颜色
* @param {String} config.font_style 规定文字样式
* @param {String} config.font_variant 规定字体变体
* @param {String} config.font_weight 规定字体粗细
* @param {String} config.line_through_cap 线末端类型
* @param {String} config.line_clamp 最大行数
* @param {String} config.line_clamp_hint 超过line_clamp后,尾部显示的自定义标识 如 ...
* @param {String} config.is_line_break 是否开启换行符换行
*
*/
drawText(config,configuration = {}){
configuration['line_num'] = configuration.line_num?configuration.line_num:0
configuration['text_width'] = configuration.text_width?configuration.text_width:0
return new Promise(async (resolve,reject)=>{
if(config.text){
let draw_width = 0,draw_height = 0,draw_x = config.x,draw_y = config.y
let font_size = config.font_size?(parseFloat(config.font_size)*this.scale):(20*this.scale)
let font_color = config.font_color || "#000"
let font_family = config.font_family || "Arial"
let line_height = config.line_height || config.font_size || 20
let text_align = config.text_align || "left"
let font_weight = config.font_weight || "normal"
let font_variant = config.font_variant || "normal"
let font_style = config.font_style || "normal"
let line_clamp_hint = config.line_clamp_hint || '...'
let lineBreakJoinText = ""
let max_width = config.max_width?parseFloat(config.max_width)*this.scale:0
// checkout is line break
if(config.is_line_break){
let splitTextArr = config.text.split(/[\n]/g)
if(splitTextArr && splitTextArr.length > 0){
let newSplitTextArr = this.arrDeleteEmpty(splitTextArr)
if(newSplitTextArr && newSplitTextArr.length > 0){
lineBreakJoinText = newSplitTextArr.slice(1).join("\n")
config.text = newSplitTextArr[0]
}else{
reject("Text cannot be empty:103")
return
}
}else{
reject("Text cannot be empty:102")
return
}
}
this.ctx.setFillStyle(font_color) // color
this.ctx.textAlign = text_align;
this.ctx.font = `${font_style} ${font_variant} ${font_weight} ${parseInt(font_size)}px ${font_family}`
if(configuration.text_width >= this.ctx.measureText(config.text).width){
draw_width = configuration.text_width
}else if(max_width > 0){
draw_width = max_width < this.ctx.measureText(config.text).width ? this.resetCompatibilitySize(max_width) : this.resetCompatibilitySize(this.ctx.measureText(config.text).width)
}else{
draw_width = this.ctx.measureText(config.text).width
}
configuration.text_width = draw_width / this.scale
if( max_width && this.compatibilitySize(this.ctx.measureText(config.text).width) > this.compatibilitySize(max_width)){
let current_text = ""
let text_arr = config.text.split("")
for(let i in text_arr){
if( this.compatibilitySize(this.ctx.measureText(current_text+text_arr[i]).width) > this.compatibilitySize(max_width) ){
// Hyphenation that is greater than the drawable width continues to draw
if(config.line_clamp && parseInt(config.line_clamp) == 1){
// Subtracting the current_text tail width from the line_clamp_hint width
let current_text_arr = current_text.split('')
let json_current_text = ''
while(true){
current_text_arr = current_text_arr.slice(1)
json_current_text = current_text_arr.join('')
if(this.compatibilitySize(this.ctx.measureText(json_current_text).width) <= this.compatibilitySize(this.ctx.measureText(line_clamp_hint).width)){
current_text = current_text.replace(json_current_text,'')
break;
}
}
configuration.line_num += 1
this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
this.ctx.fillText(current_text + line_clamp_hint, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
}else{
configuration.line_num += 1
this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
this.ctx.fillText(current_text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
config.text = text_arr.slice(i).join("")
config.y = config.y + line_height
if(config.line_clamp){
config.line_clamp = parseInt(config.line_clamp) - 1
}
await this.drawText(config,configuration)
}
break;
}else{
current_text = current_text+text_arr[i]
}
}
}else{
if(config.line_through_height){
let x = parseFloat(config.x)*this.scale
let w
let y = parseFloat(config.y)*this.scale - (font_size / 2.6)
if(text_align == "left"){
w = this.ctx.measureText(config.text).width/1.1 + parseFloat(config.x)*this.scale
}else if(text_align == "right"){
w = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width/1.1
}else if(text_align == "center"){
x = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width / 1.1 / 2
w = parseFloat(config.x)*this.scale + this.ctx.measureText(config.text).width / 1.1 / 2
}
this.drawLineTo({
x:x,
y:y,
w:w,
h:y,
line_width:config.line_through_height,
line_color:config.line_through_color,
line_cap:config.line_through_cap
})
}
configuration.line_num += 1
this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
this.ctx.fillText(config.text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
if(config.line_clamp){
config.line_clamp = parseInt(config.line_clamp) - 1
}
}
if(lineBreakJoinText){
await this.drawText({...config,text:lineBreakJoinText,y:config.y + line_height},configuration)
}
draw_height = config.font_size * configuration.line_num
draw_width = configuration.text_width
resolve({draw_width,draw_height,draw_x,draw_y})
}else{
reject("Text cannot be empty:101")
}
})
},
/**
* Draw Line
* @param {Object} config
* @param {Object} config.x x坐标
* @param {Object} config.y y坐标
* @param {Object} config.w 线的宽度
* @param {Object} config.h 线的高度
* @param {Object} config.line_width 线的宽度
* @param {Object} config.line_color 线条颜色
*/
drawLineTo(config){
let x = this.compatibilitySize(config.x)
let y = this.compatibilitySize(config.y)
let w = this.compatibilitySize(config.w)
let h = this.compatibilitySize(config.h)
let line_width = config.line_width?parseFloat(config.line_width)*this.scale:1*this.scale
let line_color = config.line_color || "black"
let line_cap = config.line_cap || "butt"
this.ctx.beginPath()
this.ctx.lineCap = line_cap
this.ctx.lineWidth = line_width
this.ctx.strokeStyle = line_color
this.ctx.moveTo(x,y)
this.ctx.lineTo(w,h)
this.ctx.stroke()
},
/**
* Compatibility px
* @param {Object} size
*/
compatibilitySize(size) {
let canvasSize = (parseFloat(size) / 750) * this.system_info.windowWidth
canvasSize = parseFloat(canvasSize * 2)
return canvasSize
},
/**
* Restore compatibility px
* @param {Object} size
*/
resetCompatibilitySize(size) {
let canvasSize = (parseFloat(size/2)/this.system_info.windowWidth) * 750
return canvasSize
},
/**
* Init canvas
*/
init(config){
return new Promise(async (resolve,reject)=>{
if(!config.canvas_id){
reject("Canvas ID cannot be empty, please refer to the usage example")
return;
}
this.hidden = config.hidden
this.canvas_id = config.canvas_id
let system_info = await uni.getSystemInfoSync()
this.system_info = system_info
this.scale = config.scale&&parseFloat(config.scale)>0?parseInt(config.scale):1
this.canvas_width = (config.canvas_width ? this.compatibilitySize(config.canvas_width) : system_info.windowWidth) * this.scale
this.canvas_height = (config.canvas_height ? this.compatibilitySize(config.canvas_height) : system_info.windowHeight) * this.scale,
this.r_canvas_scale = 1/this.scale
this.ctx = uni.createCanvasContext(this.canvas_id,this)
this.setCanvasConfig({
global_alpha:config.global_alpha?parseFloat(config.global_alpha):1,
backgroundColor:config.background_color?config.background_color:"#fff"
})
resolve()
})
},
/**
* clear canvas all path
*/
clearCanvas(){
return new Promise(async (resolve,reject)=>{
if(!this.ctx){
reject("canvas is not initialized:101")
return
}else{
this.ctx.clearRect(0,0,parseFloat(this.canvas_width)*this.scale,parseFloat(this.canvas_height)*this.scale)
await this.draw()
resolve()
}
})
},
/**
* Set canvas config
* @param {Object} config
*/
setCanvasConfig(config){
this.ctx.globalAlpha = config.global_alpha
this.ctx.fillStyle = config.backgroundColor
this.ctx.fillRect(0, 0, parseFloat(this.canvas_width)*this.scale, parseFloat(this.canvas_height)*this.scale)
},
/**
* set canvas width
* @param {Object} width
*/
setCanvasWidth(width){
if(!width){
uni.showToast({
title:'setCanvasWidthwidth error',
icon:'none'
})
}
this.canvas_width = this.compatibilitySize(parseFloat(width)) * this.scale
this.ctx.width = this.canvas_width
},
/**
* set canvas height
* @param {Object} height
*/
setCanvasHeight(height){
if(!height){
uni.showToast({
title:'setCanvasWidthheight error',
icon:'none'
})
}
this.canvas_height = this.compatibilitySize(parseFloat(height)) * this.scale
this.ctx.height = this.canvas_height
},
/**
* Draw to filepath
*/
draw(callback){
return new Promise((resolve,reject)=>{
let stop = setTimeout(()=>{
this.ctx.draw(false,setTimeout(()=>{
uni.canvasToTempFilePath({
canvasId: this.canvas_id,
quality: 1,
success: (res)=>{
console.log('res',res)
resolve(res)
callback && callback(res)
},
fail:(err)=>{
reject(JSON.stringify(err)|| "Failed to generate poster:101")
}
},this)
},300))
clearTimeout(stop)
},300)
})
},
/**
* draw rect
* @param {Number} config.x x坐标
* @param {Number} config.y y坐标
* @param {Number} config.w 图形宽度(px)
* @param {Number} config.h 图形高度(px)
* @param {Number} config.color 图形颜色
* @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius
* @param {Number} config.border_width 边框大小
* @param {Number} config.border_color 边框颜色
*
*/
drawRect(config){
return new Promise(async (resolve,reject)=>{
if(!config.border_width || config.border_width <=0){
config.border_width = 0
}else{
config.border_width = parseFloat(config.border_width)
}
if(parseFloat(config.border_width) > 0){
let sub_config = JSON.parse(JSON.stringify(config))
sub_config.border_width = 0
sub_config.w = config.w + config.border_width
sub_config.h = config.h + config.border_width
sub_config.color = config.border_color || 'black'
if(sub_config.border_radius){
sub_config.border_radius = parseFloat(sub_config.border_radius) + parseFloat(config.border_width) / 2
}
await this.drawRect(sub_config)
}
let color = config.color || 'white'
config.x = (parseFloat(config.x) + config.border_width / 2)
config.y = (parseFloat(config.y) + config.border_width / 2)
config['color'] = color
this.ctx.fillStyle = color;
if(config.is_radius || config.border_radius){
this.setNativeBorderRadius(config)
this.ctx.fill()
}else{
console.log('config.border_width',config.border_width)
this.ctx.fillRect(this.compatibilitySize(config.x*this.scale),this.compatibilitySize(config.y*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))
}
resolve()
})
},
/**
* Draw image
* @param {Object} config
* @param {String} config.url 图片链接
* @param {Number} config.x x坐标
* @param {Number} config.y y坐标
* @param {Number} config.w 图片宽度(px)
* @param {Number} config.h 图片高度(px)
* @param {Number} config.border_width 边大小
* @param {Number} config.border_color 边颜色
* @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius
* @param {Number} config.border_radius 圆角弧度
*/
drawImage(config){
return new Promise(async (resolve,reject)=>{
if(config.url){
let type = 0 // 1、network image 2、native image 3、base64 image
let image_url
let reg = /^https?/ig;
if(reg.test(config.url)){
type = 1
}else{
if((config.url.indexOf("data:image/png;base64") != -1) || config.url.indexOf("data:image/jpeg;base64") != -1 || config.url.indexOf("data:image/gif;base64") != -1){
type = 3
}else{
type = 2
}
}
if(type == 1){
// network image
await this.downLoadNetworkFile(config.url).then(res=>{ // two function
image_url = res
}).catch(err=>{
reject(err)
return;
})
}else if(type == 2){
// native image
const imageInfoResult = await uni.getImageInfo({
src: config.url
});
try{
if(imageInfoResult.length <= 1){
reject(imageInfoResult[0].errMsg + ':404')
return
}
}catch(e){
reject(e+':500')
return
}
let base64 = await this.urlToBase64({url:imageInfoResult[1].path})
// #ifdef MP-WEIXIN
await this.base64ToNative({url:base64}).then(res=>{
image_url = res
}).catch(err=>{
reject(JSON.stringify(err)+":501")
return;
})
// #endif
// #ifndef MP-WEIXIN
image_url = base64
// #endif
}else if(type == 3){
// #ifdef MP-WEIXIN
await this.base64ToNative({url:config.url}).then(res=>{
image_url = res
}).catch(err=>{
reject(JSON.stringify(err)+":500")
return;
})
// #endif
// #ifndef MP-WEIXIN
image_url = config.url
// #endif
}else{
reject("Other Type Errors:101")
return
}
if(config.border_width){
let border_radius = 0
if(config.border_radius){
let multiple = config.w / config.border_radius
border_radius = (parseFloat(config.w) + parseFloat(config.border_width)) / multiple
}
// drawRect
await this.drawRect({
x:parseFloat(config.x) - parseFloat(config.border_width)/2,
y:parseFloat(config.y) - parseFloat(config.border_width)/2,
w:parseFloat(config.w) + parseFloat(config.border_width),
h:parseFloat(config.h) + parseFloat(config.border_width),
color:config.border_color,
border_radius:border_radius,
border_width:config.border_width,
is_radius:config.is_radius
})
}
if(config.border_radius){
config.color = config.color?config.color:'rgba(0,0,0,0)'
// 圆角有白边,+0.5的误差
config.w = config.w + 0.3
config.h = config.h + 0.3
this.setNativeBorderRadius(config)
}else if(config.is_radius){
//已废弃 is_radius
this.ctx.setStrokeStyle("rgba(0,0,0,0)")
this.ctx.save()
this.ctx.beginPath()
this.ctx.arc(this.compatibilitySize(parseFloat(config.x)*this.scale+parseFloat(config.w)*this.scale/2), this.compatibilitySize(parseFloat(config.y)*this.scale+parseFloat(config.h)*this.scale/2), this.compatibilitySize(parseFloat(config.w)*this.scale/2), 0, 2 * Math.PI, false)
this.ctx.stroke();
this.ctx.clip()
}
await this.ctx.drawImage(image_url,this.compatibilitySize(parseFloat(config.x)*this.scale),this.compatibilitySize(parseFloat(config.y)*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))
this.ctx.restore() //Restore previously saved drawing context
resolve()
}else{
let err_msg = "Links cannot be empty:101"
reject(err_msg)
}
})
},
/**
* base64 to native available path
* @param {Object} config
*/
base64ToNative(config){
return new Promise((resolve,reject)=>{
let fileName = new Date().getTime()
var filePath = `${wx.env.USER_DATA_PATH}/${fileName}_rCanvas.png`
wx.getFileSystemManager().writeFile({
filePath: filePath,
data: config.url.replace(/^data:\S+\/\S+;base64,/, ''),
encoding: 'base64',
success: function() {
resolve(filePath)
},
fail: function(error) {
reject(error)
}
})
})
},
/**
* native url to base64
* @param {Object} config
*/
urlToBase64(config){
return new Promise(async (resolve,reject)=>{
if (typeof window != 'undefined') {
await this.downLoadNetworkFile(config.url).then(res=>{ // two function
resolve(res)
}).catch(err=>{
reject(err)
})
}else if (typeof plus != 'undefined') {
plus.io.resolveLocalFileSystemURL(config.url,(obj)=>{
obj.file((file)=>{
let fileReader = new plus.io.FileReader()
fileReader.onload = (res)=>{
resolve(res.target.result)
}
fileReader.onerror = (err)=>{
reject(err)
}
fileReader.readAsDataURL(file)
}, (err)=>{
reject(err)
})
},(err)=>{
reject(err)
})
}else if(typeof wx != 'undefined'){
wx.getFileSystemManager().readFile({
filePath: config.url,
encoding: 'base64',
success: function(res) {
resolve('data:image/png;base64,' + res.data)
},
fail: function(error) {
reject(error)
}
})
}
})
},
setNativeBorderRadius(config){
let border_radius = config.border_radius?(parseFloat(config.border_radius)*this.scale):(20*this.scale)
if ((parseFloat(config.w)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.w)*this.scale) / 2;
if ((parseFloat(config.h)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.h)*this.scale) / 2;
this.ctx.beginPath();
this.ctx.moveTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + border_radius), this.compatibilitySize((parseFloat(config.y)*this.scale)));
this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));
this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));
this.ctx.arcTo((this.compatibilitySize(parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));
this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));
this.ctx.closePath();
this.ctx.strokeStyle = config.color || config.border_color || 'rgba(0,0,0,0)'; // 设置绘制边框的颜色
this.ctx.stroke();
this.ctx.save()
this.ctx.clip();
},
/**
* Download network file
* @param {Object} url : download url
*/
downLoadNetworkFile(url){
return new Promise((resolve,reject)=>{
uni.downloadFile({
url,
success:(res)=>{
if(res.statusCode == 200){
resolve(res.tempFilePath)
}else{
reject("Download Image Fail:102")
}
},
fail:(err)=>{
reject("Download Image Fail:101")
}
})
})
},
/**
* Save image to natice
* @param {Object} filePath native imageUrl
*/
saveImage(filePath){
return new Promise((resolve,reject)=>{
if(!filePath){
reject("FilePath cannot be null:101")
return;
}
// #ifdef H5
var createA = document.createElement("a");
createA.download = filePath;
createA.href = filePath;
document.body.appendChild(createA);
createA.click();
createA.remove();
resolve()
// #endif
// #ifndef H5
uni.saveImageToPhotosAlbum({
filePath: filePath,
success:(res)=>{
resolve(res)
},
fail:(err)=>{
reject(err)
}
})
// #endif
})
}
}
}
-26
View File
@@ -1,26 +0,0 @@
<template>
<view>
<view class="r-canvas-component" :style="{width:canvas_width/scale+'px',height:canvas_height/scale+'px'}" :class="{'hidden':hidden}">
<canvas class="r-canvas" v-if="canvas_id" :canvas-id="canvas_id" :id="canvas_id" :style="{width:canvas_width+'px',height:canvas_height+'px','transform': `scale(${r_canvas_scale})`}"></canvas>
</view>
</view>
</template>
<script>
import rCanvasJS from "./r-canvas.js"
export default {
mixins:[rCanvasJS]
}
</script>
<style>
.r-canvas{
transform-origin: 0 0;
}
.r-canvas-component{
overflow: hidden;
}
.r-canvas-component.hidden{
position: fixed;
top:-5000upx;
}
</style>
-85
View File
@@ -1,85 +0,0 @@
<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>
@@ -1,784 +0,0 @@
<template>
<view v-show="show" class="t-wrapper" @touchmove.stop.prevent="moveHandle">
<view class="t-mask" :class="{active:active}" @click.stop="close"></view>
<view class="t-box" :class="{active:active}">
<view class="t-header">
<view class="t-header-button" @click="close">取消</view>
<view class="t-header-button" @click="confirm">确认</view>
</view>
<view class="t-color__box" :style="{ background: 'rgb(' + bgcolor.r + ',' + bgcolor.g + ',' + bgcolor.b + ')'}">
<view class="t-background boxs" @touchstart="touchstart($event, 0)" @touchmove="touchmove($event, 0)" @touchend="touchend($event, 0)">
<view class="t-color-mask"></view>
<view class="t-pointer" :style="{ top: site[0].top - 8 + 'px', left: site[0].left - 8 + 'px' }"></view>
</view>
</view>
<view class="t-control__box">
<view class="t-control__color">
<view class="t-control__color-content" :style="{ background: 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')' }"></view>
</view>
<view class="t-control-box__item">
<view class="t-controller boxs" @touchstart="touchstart($event, 1)" @touchmove="touchmove($event, 1)" @touchend="touchend($event, 1)">
<view class="t-hue">
<view class="t-circle" :style="{ left: site[1].left - 12 + 'px' }"></view>
</view>
</view>
<view class="t-controller boxs" @touchstart="touchstart($event, 2)" @touchmove="touchmove($event, 2)" @touchend="touchend($event, 2)">
<view class="t-transparency">
<view class="t-circle" :style="{ left: site[2].left - 12 + 'px' }"></view>
</view>
</view>
</view>
</view>
<view class="t-result__box">
<view v-if="mode" class="t-result__item">
<view class="t-result__box-input">{{hex}}</view>
<view class="t-result__box-text">HEX</view>
</view>
<template v-else>
<view class="t-result__item">
<view class="t-result__box-input">{{rgba.r}}</view>
<view class="t-result__box-text">R</view>
</view>
<view class="t-result__item">
<view class="t-result__box-input">{{rgba.g}}</view>
<view class="t-result__box-text">G</view>
</view>
<view class="t-result__item">
<view class="t-result__box-input">{{rgba.b}}</view>
<view class="t-result__box-text">B</view>
</view>
<view class="t-result__item">
<view class="t-result__box-input">{{rgba.a}}</view>
<view class="t-result__box-text">A</view>
</view>
</template>
<view class="t-result__item t-select" @click="select">
<view class="t-result__box-input">
<view>切换</view>
<view>模式</view>
</view>
</view>
</view>
<view class="t-alternative">
<view class="t-alternative__item" v-for="(item,index) in colorList" :key="index">
<view class="t-alternative__item-content" :style="{ background: 'rgba(' + item.r + ',' + item.g + ',' + item.b + ',' + item.a + ')' }"
@click="selectColor(item)">
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
color: {
type: Object,
default () {
return {
r: 0,
g: 0,
b: 0,
a: 0
}
}
},
spareColor: {
type: Array,
default () {
return []
}
}
},
data() {
return {
show: false,
active: false,
// rgba
rgba: {
r: 0,
g: 0,
b: 0,
a: 1
},
// hsb
hsb: {
h: 0,
s: 0,
b: 0
},
site: [{
top: 0,
left: 0
}, {
left: 0
}, {
left: 0
}],
index: 0,
bgcolor: {
r: 255,
g: 0,
b: 0,
a: 1
},
hex: '#000000',
mode: true,
colorList: [{
r: 244,
g: 67,
b: 54,
a: 1
}, {
r: 233,
g: 30,
b: 99,
a: 1
}, {
r: 156,
g: 39,
b: 176,
a: 1
}, {
r: 103,
g: 58,
b: 183,
a: 1
}, {
r: 63,
g: 81,
b: 181,
a: 1
}, {
r: 33,
g: 150,
b: 243,
a: 1
}, {
r: 3,
g: 169,
b: 244,
a: 1
}, {
r: 0,
g: 188,
b: 212,
a: 1
}, {
r: 0,
g: 150,
b: 136,
a: 1
}, {
r: 76,
g: 175,
b: 80,
a: 1
}, {
r: 139,
g: 195,
b: 74,
a: 1
}, {
r: 205,
g: 220,
b: 57,
a: 1
}, {
r: 255,
g: 235,
b: 59,
a: 1
}, {
r: 255,
g: 193,
b: 7,
a: 1
}, {
r: 255,
g: 152,
b: 0,
a: 1
}, {
r: 255,
g: 87,
b: 34,
a: 1
}, {
r: 121,
g: 85,
b: 72,
a: 1
}, {
r: 158,
g: 158,
b: 158,
a: 1
}, {
r: 0,
g: 0,
b: 0,
a: 0.5
}, {
r: 0,
g: 0,
b: 0,
a: 0
}, ]
};
},
created() {
this.rgba = this.color;
if (this.spareColor.length !== 0) {
this.colorList = this.spareColor;
}
},
methods: {
/**
* 初始化
*/
init() {
// hsb
this.hsb = this.rgbToHex(this.rgba);
// this.setColor();
this.setValue(this.rgba);
},
moveHandle() {},
open() {
this.show = true;
this.$nextTick(() => {
this.init();
setTimeout(() => {
this.active = true;
setTimeout(() => {
this.getSelectorQuery();
}, 350)
}, 50)
})
},
close() {
this.active = false;
this.$nextTick(() => {
setTimeout(() => {
this.show = false;
}, 500)
})
},
confirm() {
this.close();
this.$emit('confirm', {
rgba: this.rgba,
hex: this.hex
})
},
//
select() {
this.mode = !this.mode
},
//
selectColor(item) {
this.setColorBySelect(item)
},
touchstart(e, index) {
const {
pageX,
pageY
} = e.touches[0];
this.pageX = pageX;
this.pageY = pageY;
this.setPosition(pageX, pageY, index);
},
touchmove(e, index) {
const {
pageX,
pageY
} = e.touches[0];
this.moveX = pageX;
this.moveY = pageY;
this.setPosition(pageX, pageY, index);
},
touchend(e, index) {},
/**
* 设置位置
*/
setPosition(x, y, index) {
this.index = index;
const {
top,
left,
width,
height
} = this.position[index];
//
this.site[index].left = Math.max(0, Math.min(parseInt(x - left), width));
if (index === 0) {
this.site[index].top = Math.max(0, Math.min(parseInt(y - top), height));
//
this.hsb.s = parseInt((100 * this.site[index].left) / width);
this.hsb.b = parseInt(100 - (100 * this.site[index].top) / height);
this.setColor();
this.setValue(this.rgba);
} else {
this.setControl(index, this.site[index].left);
}
},
/**
* 设置 rgb 颜色
*/
setColor() {
const rgb = this.HSBToRGB(this.hsb);
this.rgba.r = rgb.r;
this.rgba.g = rgb.g;
this.rgba.b = rgb.b;
},
/**
* 设置二进制颜色
* @param {Object} rgb
*/
setValue(rgb) {
this.hex = '#' + this.rgbToHex(rgb);
},
setControl(index, x) {
const {
top,
left,
width,
height
} = this.position[index];
if (index === 1) {
this.hsb.h = parseInt((360 * x) / width);
this.bgcolor = this.HSBToRGB({
h: this.hsb.h,
s: 100,
b: 100
});
this.setColor()
} else {
this.rgba.a = (x / width).toFixed(1);
}
this.setValue(this.rgba);
},
/**
* rgb 二进制 hex
* @param {Object} rgb
*/
rgbToHex(rgb) {
let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
hex.map(function(str, i) {
if (str.length == 1) {
hex[i] = '0' + str;
}
});
return hex.join('');
},
setColorBySelect(getrgb) {
const {
r,
g,
b,
a
} = getrgb;
let rgb = {}
rgb = {
r: r ? parseInt(r) : 0,
g: g ? parseInt(g) : 0,
b: b ? parseInt(b) : 0,
a: a ? a : 0,
};
this.rgba = rgb;
this.hsb = this.rgbToHsb(rgb);
this.changeViewByHsb();
},
changeViewByHsb() {
const [a, b, c] = this.position;
this.site[0].left = parseInt(this.hsb.s * a.width / 100);
this.site[0].top = parseInt((100 - this.hsb.b) * a.height / 100);
this.setColor(this.hsb.h);
this.setValue(this.rgba);
this.bgcolor = this.HSBToRGB({
h: this.hsb.h,
s: 100,
b: 100
});
this.site[1].left = this.hsb.h / 360 * b.width;
this.site[2].left = this.rgba.a * c.width;
},
/**
* hsb rgb
* @param {Object} 颜色模式 H(hues)表示色相S(saturation)表示饱和度Bbrightness表示亮度
*/
HSBToRGB(hsb) {
let rgb = {};
let h = Math.round(hsb.h);
let s = Math.round((hsb.s * 255) / 100);
let v = Math.round((hsb.b * 255) / 100);
if (s == 0) {
rgb.r = rgb.g = rgb.b = v;
} else {
let t1 = v;
let t2 = ((255 - s) * v) / 255;
let t3 = ((t1 - t2) * (h % 60)) / 60;
if (h == 360) h = 0;
if (h < 60) {
rgb.r = t1;
rgb.b = t2;
rgb.g = t2 + t3;
} else if (h < 120) {
rgb.g = t1;
rgb.b = t2;
rgb.r = t1 - t3;
} else if (h < 180) {
rgb.g = t1;
rgb.r = t2;
rgb.b = t2 + t3;
} else if (h < 240) {
rgb.b = t1;
rgb.r = t2;
rgb.g = t1 - t3;
} else if (h < 300) {
rgb.b = t1;
rgb.g = t2;
rgb.r = t2 + t3;
} else if (h < 360) {
rgb.r = t1;
rgb.g = t2;
rgb.b = t1 - t3;
} else {
rgb.r = 0;
rgb.g = 0;
rgb.b = 0;
}
}
return {
r: Math.round(rgb.r),
g: Math.round(rgb.g),
b: Math.round(rgb.b)
};
},
rgbToHsb(rgb) {
let hsb = {
h: 0,
s: 0,
b: 0
};
let min = Math.min(rgb.r, rgb.g, rgb.b);
let max = Math.max(rgb.r, rgb.g, rgb.b);
let delta = max - min;
hsb.b = max;
hsb.s = max != 0 ? 255 * delta / max : 0;
if (hsb.s != 0) {
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta;
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta;
else hsb.h = 4 + (rgb.r - rgb.g) / delta;
} else hsb.h = -1;
hsb.h *= 60;
if (hsb.h < 0) hsb.h = 0;
hsb.s *= 100 / 255;
hsb.b *= 100 / 255;
return hsb;
},
getSelectorQuery() {
const views = uni.createSelectorQuery().in(this);
views
.selectAll('.boxs')
.boundingClientRect(data => {
if (!data || data.length === 0) {
setTimeout(() => this.getSelectorQuery(), 20)
return
}
this.position = data;
// this.site[0].top = data[0].height;
// this.site[0].left = 0;
// this.site[1].left = data[1].width;
// this.site[2].left = data[2].width;
this.setColorBySelect(this.rgba);
})
.exec();
}
},
watch: {
spareColor(newVal) {
this.colorList = newVal;
}
}
};
</script>
<style>
.t-wrapper {
position: fixed;
top: 0;
bottom: 0;
left: 0;
width: 100%;
box-sizing: border-box;
z-index: 9999;
}
.t-box {
width: 100%;
position: absolute;
bottom: 0;
padding: 30upx 0;
padding-top: 0;
background: #fff;
transition: all 0.3s;
transform: translateY(100%);
}
.t-box.active {
transform: translateY(0%);
}
.t-header {
display: flex;
justify-content: space-between;
width: 100%;
height: 100upx;
border-bottom: 1px #eee solid;
box-shadow: 1px 0 2px rgba(0, 0, 0, 0.1);
background: #fff;
}
.t-header-button {
display: flex;
align-items: center;
width: 150upx;
height: 100upx;
font-size: 30upx;
color: #666;
padding-left: 20upx;
}
.t-header-button:last-child {
justify-content: flex-end;
padding-right: 20upx;
}
.t-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: -1;
transition: all 0.3s;
opacity: 0;
}
.t-mask.active {
opacity: 1;
}
.t-color__box {
position: relative;
height: 400upx;
background: rgb(255, 0, 0);
overflow: hidden;
box-sizing: border-box;
margin: 0 20upx;
margin-top: 20upx;
box-sizing: border-box;
}
.t-background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
}
.t-color-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 400upx;
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
}
.t-pointer {
position: absolute;
bottom: -8px;
left: -8px;
z-index: 2;
width: 15px;
height: 15px;
border: 1px #fff solid;
border-radius: 50%;
}
.t-show-color {
width: 100upx;
height: 50upx;
}
.t-control__box {
margin-top: 50upx;
width: 100%;
display: flex;
padding-left: 20upx;
box-sizing: border-box;
}
.t-control__color {
flex-shrink: 0;
width: 100upx;
height: 100upx;
border-radius: 50%;
background-color: #fff;
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
background-size: 36upx 36upx;
background-position: 0 0, 18upx 18upx;
border: 1px #eee solid;
overflow: hidden;
}
.t-control__color-content {
width: 100%;
height: 100%;
}
.t-control-box__item {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
padding: 0 30upx;
}
.t-controller {
position: relative;
width: 100%;
height: 16px;
background-color: #fff;
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
background-size: 32upx 32upx;
background-position: 0 0, 16upx 16upx;
}
.t-hue {
width: 100%;
height: 100%;
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
}
.t-transparency {
width: 100%;
height: 100%;
background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0));
}
.t-circle {
position: absolute;
/* right: -10px; */
top: -2px;
width: 20px;
height: 20px;
box-sizing: border-box;
border-radius: 50%;
background: #fff;
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1);
}
.t-result__box {
margin-top: 20upx;
padding: 10upx;
width: 100%;
display: flex;
box-sizing: border-box;
}
.t-result__item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10upx;
width: 100%;
box-sizing: border-box;
}
.t-result__box-input {
padding: 10upx 0;
width: 100%;
font-size: 28upx;
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
color: #999;
text-align: center;
background: #fff;
}
.t-result__box-text {
margin-top: 10upx;
font-size: 28upx;
line-height: 2;
}
.t-select {
flex-shrink: 0;
width: 150upx;
padding: 0 30upx;
}
.t-select .t-result__box-input {
border-radius: 10upx;
border: none;
color: #999;
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);
background: #fff;
}
.t-select .t-result__box-input:active {
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.1);
}
.t-alternative {
display: flex;
flex-wrap: wrap;
/* justify-content: space-between; */
width: 100%;
padding-right: 10upx;
box-sizing: border-box;
}
.t-alternative__item {
margin-left: 12upx;
margin-top: 10upx;
width: 50upx;
height: 50upx;
border-radius: 10upx;
background-color: #fff;
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
background-size: 36upx 36upx;
background-position: 0 0, 18upx 18upx;
border: 1px #eee solid;
overflow: hidden;
}
.t-alternative__item-content {
width: 50upx;
height: 50upx;
background: rgba(255, 0, 0, 0.5);
}
.t-alternative__item:active {
transition: all 0.3s;
transform: scale(1.1);
}
</style>
-89
View File
@@ -1,89 +0,0 @@
<template>
<view class="wave-wrap waveAnimation">
<view class="waveWrapperInner bgTop"><view class="wave waveTop"></view></view>
<view class="waveWrapperInner bgMiddle"><view class="wave waveMiddle"></view></view>
<view class="waveWrapperInner bgBottom"><view class="wave waveBottom"></view></view>
</view>
</template>
<script>
export default {
name: 'wave',
props: {
height: {
type: String,
default: '35rpx'
}
}
};
</script>
<style lang="scss" scoped>
.wave-wrap {
overflow: hidden;
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
margin: auto;
}
.waveWrapperInner {
position: absolute;
width: 100%;
overflow: hidden;
height: 100%;
}
.wave {
position: absolute;
left: 0;
width: 200%;
height: 100%;
background-repeat: repeat no-repeat;
background-position: 0 bottom;
transform-origin: center bottom;
}
.bgTop {
opacity: 0.4;
}
.waveTop {
background-size: 50% 45px;
background-image: url('~@/static/wave/wave-1.png');
}
.waveAnimation .waveTop {
animation: move_wave 4s linear infinite;
}
@keyframes move_wave {
0% {
transform: translateX(0) translateZ(0) scaleY(1);
}
50% {
transform: translateX(-25%) translateZ(0) scaleY(1);
}
100% {
transform: translateX(-50%) translateZ(0) scaleY(1);
}
}
.bgMiddle {
opacity: 0.6;
}
.waveMiddle {
background-size: 50% 40px;
background-image: url('~@/static/wave/wave-2.png');
}
.waveAnimation .waveMiddle {
animation: move_wave 3.5s linear infinite;
}
.bgBottom {
opacity: 0.95;
}
.waveBottom {
background-size: 50% 35px;
background-image: url('~@/static/wave/wave-1.png');
}
.waveAnimation .waveBottom {
animation: move_wave 2s linear infinite;
}
</style>
-50
View File
@@ -1,50 +0,0 @@
// 应用设置存储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))
}
-16
View File
@@ -1,16 +0,0 @@
/** 配置后台管理员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
@@ -1,177 +0,0 @@
{
"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"
}
]
}
}
+2
View File
@@ -0,0 +1,2 @@
我们需要全局缓存一个点赞的列表,使用 metadata.name 做key,存储到store并且开启持久化存储,如果已经点过赞,直接显示已
经点赞的样式。同时我们的点赞接口是不会返回内容的,所以再捕获异常的时候不要提示异常信息。
Vendored
+43
View File
@@ -0,0 +1,43 @@
VITE_APP_TITLE='uni-halo'
VITE_APP_DESC='基于halo开源博客系统API开发的多端博客。'
VITE_APP_PORT=9000
VITE_UNI_APPID='__UNI__5748B6E'
VITE_WX_APPID='wx1bb161867082c72f'
# 微信开发者工具 CLI 路径,仅当默认安装路径不正确时配置(就是当 pnpm dev:mp 无法自动打开微信开发者工具时,才需要配置,通常是你更改了默认的安装位置导致的,一般出现在windows系统)
# macOS 示例:
# WECHAT_DEVTOOLS_CLI_PATH = '/Applications/wechatwebdevtools.app/Contents/MacOS/cli'
# Windows 示例:
# WECHAT_DEVTOOLS_CLI_PATH = 'C:\Program Files (x86)\Tencent\微信web开发者工具\cli.bat'
# h5部署网站的base,配置到 manifest.config.ts 里的 h5.router.base
# https://uniapp.dcloud.net.cn/collocation/manifest.html#h5-router
# 比如你要部署到 https://unibest.tech/doc/ ,则配置为 /doc/
VITE_APP_PUBLIC_BASE=/
# 默认后台请求地址
# 不同命令会按 mode 叠加读取 .env.development / .env.test / .env.production。
# 微信小程序如果没有配置下面的专用地址,也会回退使用这个值。
VITE_UNI_HALO_BASEURL=''
# 备注:如果后台带统一前缀,则也要加到后面,eg: https://blog.xiaoxiaomo.cn/api
# 微信小程序专用后台请求地址,按微信开发者工具 envVersion 区分。
# 不配置时会回退使用 VITE_UNI_HALO_BASEURL。
# VITE_UNI_HALO_BASEURL__WEIXIN_DEVELOP = 'https://dev.xxx.com'
# VITE_UNI_HALO_BASEURL__WEIXIN_TRIAL = 'https://trial.xxx.com'
# VITE_UNI_HALO_BASEURL__WEIXIN_RELEASE = 'https://prod.xxx.com'
# h5是否需要配置代理
VITE_APP_PROXY_ENABLE=false
# 下面的不用修改,只要不跟你后台的统一前缀冲突就行。如果修改了,记得修改 `nginx` 里面的配置
VITE_APP_PROXY_PREFIX='/dev-api'
# 第二个请求地址 (目前alova中可以使用)
VITE_UNI_HALO_BASEURL_SECONDARY='https://blog.xiaoxiaomo.cn'
# 认证模式,'single' | 'double' ==> 单token | 双token
VITE_AUTH_MODE='single'
# 原生插件资源复制开关,启用后 App 构建会把根目录 nativeplugins 复制到 dist
VITE_COPY_NATIVE_RES_ENABLE=true
+11
View File
@@ -0,0 +1,11 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV='development'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE=false
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP=false
# development mode 后台请求地址
# VITE_UNI_HALO_BASEURL='https://www.yijunzhao.cn'
# VITE_UNI_HALO_BASEURL='https://www.xhhao.com'
VITE_UNI_HALO_BASEURL='https://ryanc.cc'
+9
View File
@@ -0,0 +1,9 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV='production'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE=true
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP=false
# production mode 后台请求地址
# VITE_UNI_HALO_BASEURL = 'https://prod.xxx.com'
+9
View File
@@ -0,0 +1,9 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV='development'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE=false
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP=false
# test mode 后台请求地址
# VITE_UNI_HALO_BASEURL = 'https://test.xxx.com'
+60
View File
@@ -0,0 +1,60 @@
import uniHelper from '@uni-helper/eslint-config'
export default uniHelper({
unocss: true,
vue: true,
markdown: false,
ignores: [
// 忽略uni_modules目录
'**/uni_modules/',
// 忽略原生插件目录
'**/nativeplugins/',
'dist',
// unplugin-auto-import 生成的类型文件,每次提交都改变,所以加入这里吧,与 .gitignore 配合使用
'auto-import.d.ts',
// vite-plugin-uni-pages 生成的类型文件,每次切换分支都一堆不同的,所以直接 .gitignore
'uni-pages.d.ts',
// 插件生成的文件
'src/pages.json',
'src/manifest.json',
// 忽略自动生成文件
'src/service/**',
],
// https://eslint-config.antfu.me/rules
rules: {
'no-useless-return': 'off',
'no-console': 'off',
'no-unused-vars': 'off',
'vue/no-unused-refs': 'off',
'unused-imports/no-unused-vars': 'off',
'eslint-comments/no-unlimited-disable': 'off',
'jsdoc/check-param-names': 'off',
'jsdoc/require-returns-description': 'off',
'ts/no-empty-object-type': 'off',
'no-extend-native': 'off',
// uni 条件编译注释可能包裹 import,自动排序会破坏平台条件边界
'perfectionist/sort-imports': 'off',
'vue/singleline-html-element-content-newline': [
'error',
{
externalIgnores: ['text'],
},
],
// vue SFC 调换顺序改这里
'vue/block-order': ['error', {
order: [['script', 'template'], 'style'],
}],
},
formatters: {
/**
* Format CSS, LESS, SCSS files, also the `<style>` blocks in Vue
* By default uses Prettier
*/
css: true,
/**
* Format HTML files
* By default uses Prettier
*/
html: true,
},
})
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+13 -7
View File
@@ -1,20 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<!doctype html>
<html build-time="%BUILD_TIME%">
<head>
<meta charset="UTF-8" />
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
var coverSupport =
'CSS' in window &&
typeof CSS.supports === 'function' &&
(CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
(coverSupport ? ', viewport-fit=cover' : '') +
'" />',
)
</script>
<title></title>
<title>%VITE_APP_TITLE%</title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
-72
View File
@@ -1,72 +0,0 @@
import FyShowModal from './showModal.js'
export default class Fy{
/**
* @author 大雄
* @Date 2021年7月1日20:49:58
* @description 二次封装showModel
*/
static showModal({ title = "提示", content = "提示内容", showCancel = true, backbutton = false, cancelText = "取消", cancelColor = "#000000", confirmText = "确定", confirmColor = '#3CC51F', complete = false } = {}) {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
if (this.get_app_platform() === 'android') { // android的使用自定义的模态框
new FyShowModal({ title, content, showCancel, backbutton, cancelText, cancelColor, confirmText, confirmColor,
success(res) {
if (res.confirm) {resolve(res) } else { reject(res) }
},
fail(err){
this.uniShowModal({ title, content, showCancel, cancelText, cancelColor, confirmText, confirmColor, complete }).then((res)=>resolve(res)).catch(err=>reject(err))
}
}).show();
} else { // ios直接用原生的
return this.uniShowModal({ title, content, showCancel, cancelText, cancelColor, confirmText, confirmColor, complete }).then((res)=>resolve(res)).catch(err=>reject(err))
}
// #endif
// #ifndef APP-PLUS
return this.uniShowModal({ title, content, showCancel, cancelText, cancelColor, confirmText, confirmColor, complete }).then((res)=>resolve(res)).catch(err=>reject(err))
// #endif
})
}
// 原生showModal
static uniShowModal({ title, content, showCancel, cancelText, cancelColor, confirmText, confirmColor, complete }) {
return new Promise((resolve, reject) => {
let appPlatform = null;
// #ifdef APP-PLUS
if (this.get_app_platform() === 'android' && showCancel) { // android的确认按钮在左边,需要统一到右边
appPlatform = 'android';
var tempConfirmText = confirmText;
var tempConfirmColor = confirmColor;
confirmText = cancelText;
cancelText = tempConfirmText;
confirmColor = cancelColor;
cancelColor = tempConfirmColor;
}
// #endif
uni.showModal({ title, content, showCancel, cancelText, cancelColor, confirmText, confirmColor,
success(res){
if (complete) {
resolve(res);
} else if (res.confirm) {
appPlatform === 'android' ? reject(res) : resolve(res)
} else {
appPlatform === 'android' ? resolve(res) : reject(res)
}
},
fail(err){ reject(err) }
})
})
}
/**
* @description 获取app平台(android | ios)
* */
static get_app_platform() {
// #ifndef APP-PLUS
this.showModal({ content: '仅支持app' });
// #endif
// #ifdef APP-PLUS
return plus.os.name.toLowerCase();
// #endif
}
}
-62
View File
@@ -1,62 +0,0 @@
/**
* @author 大雄
* @Date 2021年7月1日20:49:58
* @description 路由导航守卫简单版后续需要功能再完善
*/
export default function() {
// 监听路由前进
function routerPush({ type = 'navigateTo' } = {}) {
routeWatchClearModal();
}
// 监听路由后退
function routerBack() {
routeWatchClearModal();
}
// 页面跳转后,销毁当前页面未关闭的弹框
function routeWatchClearModal() {
try {
var FyShowModalView = plus.nativeObj.View.getViewById("FyShowModalView");
if (FyShowModalView) {
FyShowModalView.clear();
}
var FyShowModalCancel = plus.nativeObj.View.getViewById("FyShowModalCancel");
if (FyShowModalCancel) {
FyShowModalCancel.clear();
}
var FyShowModalConfirm = plus.nativeObj.View.getViewById("FyShowModalConfirm");
if (FyShowModalConfirm) {
FyShowModalConfirm.clear();
}
} catch(err) {
console.log(err);
}
}
uni.addInterceptor('navigateTo', {
success(e) {
routerPush({ type: 'navigateTo' });
}
})
uni.addInterceptor('redirectTo', {
success(e) {
routerPush({ type: 'redirectTo' });
}
})
uni.addInterceptor('reLaunch', {
success(e) {
routerPush({ type: 'reLaunch' });
}
})
uni.addInterceptor('switchTab', {
success(e) {
routerPush({ type: 'switchTab' });
}
})
uni.addInterceptor('navigateBack', {
success(e) {
routerBack();
}
})
}
-176
View File
@@ -1,176 +0,0 @@
/**
* @description 替换android app的uni.showModal
*/
let modalIntance = null;
export class fyShowModal {
constructor(options = {}) {
modalIntance = this;
this.modalControl = null; // 模态框句柄
this.cancelModel = null;
this.confirmModel = null;
const { screenHeight, screenWidth } = uni.getSystemInfoSync();
this.modalPaddingTop = 12; // modal顶部的内边距
this.titleHeight = 34; // 标题的高度
this.contentHeight = 60; // 内容得高度
this.contentPaddingBottom = 10; // 内容的底部内边距
this.footerHeight = 50; // 底部按钮的高度
const modalHeight = this.modalPaddingTop + this.contentPaddingBottom + this.titleHeight + this.contentHeight + this.footerHeight; // 模态框内容高度
this.screenHeight = screenHeight;
this.modalWidth = options.contentWidth || screenWidth * 0.82; // 模态框内容宽度
this.modalHeight = modalHeight; // 模态框内容高度
this.modalLeft = (screenWidth - this.modalWidth) / 2; // 模态框距离左边距离
this.modalTop = (screenHeight / 2) - (modalHeight / 2) - 30; // 模态框距离顶部距离
this.titleTop = this.modalPaddingTop + this.modalTop; // title距离顶部的距离
this.contentTop = this.modalPaddingTop + this.modalTop + this.titleHeight; // content距离顶部的距离
this.contentLeft = this.modalLeft + (this.modalWidth * 0.1);
this.contentWidth = this.modalWidth * 0.8; // 内容的宽度
this.footerBorderTop = this.contentPaddingBottom + this.contentTop + this.contentHeight; // footer的边线距离顶部的距离
this.buttonWidth = this.modalWidth/2;
// 物理返回键是否关闭弹框
this.backbutton = Boolean(options.backbutton);
let opacity = options.opacity || 0.6; // mask透明度
let modal_title = options.title || '提示'; // 标题
let model_content = options.content || '提示内容'; // 提示内容
let maskClick = typeof options.maskClick === 'undefined' ? false : options.maskClick; // 是否可以点击mask关闭模态框
let cancelText = options.cancelText || '取消';
let confirmText = options.confirmText || '确定';
let cancelColor = options.cancelColor || '#000000';
let confirmColor = options.confirmColor || '#3CC51F';
let showCancel = typeof options.showCancel === 'undefined' ? true : options.showCancel; // 是否显示取消按钮
let align = options.align || 'center'; // 内容对齐方向
let successFn = () => {};
let failFn = () => {};
this.success = options.success || successFn; // 成功返回模态框
this.fail = options.fail || failFn; // 失败返回模态框
//#ifdef APP-PLUS
this.creatView({ height: `${this.screenHeight}px`, top: 0 }, opacity, maskClick, { 'title': modal_title, 'content': model_content, cancelText, confirmText, confirmColor, cancelColor, showCancel, align });
//#endif
}
//生成提示框view
creatView(style, opa, maskClick, modelInfo) {
try {
style = { left: '0px', width: '100%', ...style };
let view = new plus.nativeObj.View('FyShowModalView', style);
view.draw([
{ tag: 'rect', id: 'modal', color: `rgba(0,0,0,${opa})`, position: { top: '0px', left: '0px', width: '100%', height: '100%' } },
{ tag: 'rect', id: 'content', color: `rgb(255,255,255)`, rectStyles: { borderWidth: '0px', radius: '8px' }, position: { top: this.modalTop+'px', left: this.modalLeft+'px', width: this.modalWidth+'px', height: this.modalHeight + 'px' } },
{ tag: 'font', id: 'title', text: modelInfo.title, textStyles: { size: '18px', weight: 'bold', color: '#000000' }, position: { top: this.titleTop+'px', left: this.modalLeft+'px', width: this.modalWidth+'px', height: this.titleHeight+'px' } },
{ tag: 'font', id: 'text', text: modelInfo.content, textStyles: { size: '15px', color: '#666', whiteSpace: 'normal', align: modelInfo.align }, position: { top: this.contentTop+'px', left: this.contentLeft+'px', width: this.contentWidth+'px', height: this.contentHeight+'px' } },
{ tag: 'rect', id: 'line', color: '#efeff1', position: { top: this.footerBorderTop+'px', left: this.modalLeft+'px', width: this.modalWidth+'px', height: '1px' } },
{ tag: 'rect', id: 'line2', color: '#efeff1', position: { top: this.footerBorderTop+'px', left: '50%', width: modelInfo.showCancel ? '1px' : '0px', height: modelInfo.showCancel ? this.footerHeight+'px' : '0px' } }
]);
// 取消按钮
if (modelInfo.showCancel) {
let viewCancel = new plus.nativeObj.View('FyShowModalCancel', { width: this.buttonWidth+'px', height: this.footerHeight+'px', top: this.footerBorderTop + 'px', left: this.modalLeft+'px' });
viewCancel.draw([
{ tag: 'rect', id: 'cancelBackground', color: `rgba(255,255,255,0)`, rectStyles: { borderWidth: '0px', radius: '8px' }, position: { top: '0px', left: '0px', width: '100%', height: '100%' } },
{ tag: 'font', id: 'cancel', text: modelInfo.cancelText, textStyles: { color: modelInfo.cancelColor, size: '16px' } },
]);
viewCancel.addEventListener('click', (e) => {
viewconfirm.drawRect({ color: 'rgba(255,255,255,0)', borderWidth: '0px', radius: '8px' }, {}, 'confirmBackground');
viewCancel.drawRect('#efeff1', {top:'0px',left:'0px',width:'100%',height:'100%'}, 'cancelBackground');
this.success({ confirm: false, cancel: true, mask: false })
this.hide();
}, false);
viewCancel.addEventListener('touchstart', (e)=>{
viewconfirm.drawRect({ color: 'rgba(255,255,255,0)', borderWidth: '0px', radius: '8px' }, {}, 'confirmBackground');
viewCancel.drawRect({ color: '#efeff1', borderWidth: '0px', radius: '8px' }, {top:'0px',left:'0px',width:'100%',height:'100%'}, 'cancelBackground');
})
this.cancelModel = viewCancel;
}
// 确认
let viewconfirm = new plus.nativeObj.View('FyShowModalConfirm', { width: modelInfo.showCancel ? this.buttonWidth+'px' : this.modalWidth+'px', height: this.footerHeight+'px', top: this.footerBorderTop + 'px', left: modelInfo.showCancel ? '50%' : this.modalLeft+'px' });
// 绘制确认
viewconfirm.draw([
{ tag: 'rect', id: 'confirmBackground', color: `rgba(255,255,255,0)`, rectStyles: { borderWidth: '0px', radius: '8px' }, position: { top: '0px', left: '0px', width: '100%', height: '100%' } },
{ tag: 'font', id: 'confirm', text: modelInfo.confirmText, textStyles: { color: modelInfo.confirmColor, size: '16px' } },
]);
// 点击确认
viewconfirm.addEventListener('click', (e) => {
if (this.cancelModel) {
this.cancelModel.drawRect({ color: 'rgba(255,255,255,0)', borderWidth: '0px', radius: '8px' }, {}, 'cancelBackground');
}
viewconfirm.drawRect('#efeff1', {top:'0px',left:'0px',width:'100%',height:'100%'}, 'confirmBackground');
this.success({ confirm: true, cancel: false, mask: false })
this.hide();
}, false);
viewconfirm.addEventListener('touchstart', (e)=>{
if (this.cancelModel) {
this.cancelModel.drawRect({ color: 'rgba(255,255,255,0)', borderWidth: '0px', radius: '8px' }, {}, 'cancelBackground');
}
viewconfirm.drawRect({ color: '#efeff1', borderWidth: '0px', radius: '8px' }, {top:'0px',left:'0px',width:'100%',height:'100%'}, 'confirmBackground');
})
//点击蒙布
if (maskClick) {
view.addEventListener('click', (e) => {
this.success({ confirm: false, cancel: true, mask: true })
this.hide();
if (this.cancelModel) {
this.cancelModel.drawRect({ color: 'rgba(255,255,255,0)', borderWidth: '0px', radius: '8px' }, {}, 'cancelBackground');
}
viewconfirm.drawRect({ color: 'rgba(255,255,255,0)', borderWidth: '0px', radius: '8px' }, {}, 'confirmBackground');
}, false);
} else {
view.addEventListener('click', (e) => {
if (this.cancelModel) {
this.cancelModel.drawRect({ color: 'rgba(255,255,255,0)', borderWidth: '0px', radius: '8px' }, {}, 'cancelBackground');
}
viewconfirm.drawRect({ color: 'rgba(255,255,255,0)', borderWidth: '0px', radius: '8px' }, {}, 'confirmBackground');
}, false);
}
this.modalControl = view;
this.confirmModel = viewconfirm;
} catch(err) {
this.fail(err);
}
}
// 显示模态框
show() {
this.modalControl.show();
if (this.cancelModel) {
this.cancelModel.show();
}
this.confirmModel.show();
if (this.backbutton) {
plus.key.addEventListener('backbutton', this.handlerBackButton);
}
}
// 关闭模态框
hide() {
if (this.backbutton) {
plus.key.removeEventListener('backbutton', this.handlerBackButton);
}
this.modalControl.clear();
if (this.cancelModel) {
this.cancelModel.clear();
}
this.confirmModel.clear();
}
// 物理返回键方法
handlerBackButton() {
try {
modalIntance && modalIntance.success({ confirm: false, cancel: true, mask: false })
modalIntance && modalIntance.hide();
} catch(err) {
console.error(err)
}
}
}
export default fyShowModal;
@@ -1,99 +0,0 @@
import buildURL from '../helpers/buildURL'
import buildFullPath from '../core/buildFullPath'
import settle from '../core/settle'
import { isUndefined } from "../utils"
/**
* 返回可选值存在的配置
* @param {Array} keys - 可选值数组
* @param {Object} config2 - 配置
* @return {{}} - 存在的配置项
*/
const mergeKeys = (keys, config2) => {
let config = {}
keys.forEach(prop => {
if (!isUndefined(config2[prop])) {
config[prop] = config2[prop]
}
})
return config
}
export default (config) => {
return new Promise((resolve, reject) => {
let fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params)
const _config = {
url: fullPath,
header: config.header,
complete: (response) => {
config.fullPath = fullPath
response.config = config
try {
// 对可能字符串不是json 的情况容错
if (typeof response.data === 'string') {
response.data = JSON.parse(response.data)
}
// eslint-disable-next-line no-empty
} catch (e) {
}
settle(resolve, reject, response)
}
}
let requestTask
if (config.method === 'UPLOAD') {
delete _config.header['content-type']
delete _config.header['Content-Type']
let otherConfig = {
// #ifdef MP-ALIPAY
fileType: config.fileType,
// #endif
filePath: config.filePath,
name: config.name
}
const optionalKeys = [
// #ifdef APP-PLUS || H5
'files',
// #endif
// #ifdef H5
'file',
// #endif
// #ifdef H5 || APP-PLUS
'timeout',
// #endif
'formData'
]
requestTask = uni.uploadFile({..._config, ...otherConfig, ...mergeKeys(optionalKeys, config)})
} else if (config.method === 'DOWNLOAD') {
// #ifdef H5 || APP-PLUS
if (!isUndefined(config['timeout'])) {
_config['timeout'] = config['timeout']
}
// #endif
requestTask = uni.downloadFile(_config)
} else {
const optionalKeys = [
'data',
'method',
// #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
'timeout',
// #endif
'dataType',
// #ifndef MP-ALIPAY
'responseType',
// #endif
// #ifdef APP-PLUS
'sslVerify',
// #endif
// #ifdef H5
'withCredentials',
// #endif
// #ifdef APP-PLUS
'firstIpv4',
// #endif
]
requestTask = uni.request({..._config,...mergeKeys(optionalKeys, config)})
}
if (config.getTask) {
config.getTask(requestTask, config)
}
})
}
@@ -1,51 +0,0 @@
'use strict'
function InterceptorManager() {
this.handlers = []
}
/**
* Add a new interceptor to the stack
*
* @param {Function} fulfilled The function to handle `then` for a `Promise`
* @param {Function} rejected The function to handle `reject` for a `Promise`
*
* @return {Number} An ID used to remove interceptor later
*/
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
})
return this.handlers.length - 1
}
/**
* Remove an interceptor from the stack
*
* @param {Number} id The ID that was returned by `use`
*/
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null
}
}
/**
* Iterate over all the registered interceptors
*
* This method is particularly useful for skipping over any
* interceptors that may have become `null` calling `eject`.
*
* @param {Function} fn The function to call for each interceptor
*/
InterceptorManager.prototype.forEach = function forEach(fn) {
this.handlers.forEach(h => {
if (h !== null) {
fn(h)
}
})
}
export default InterceptorManager
@@ -1,200 +0,0 @@
/**
* @Class Request
* @description luch-request http请求插件
* @version 3.0.7
* @Author lu-ch
* @Date 2021-09-04
* @Email webwork.s@qq.com
* 文档: https://www.quanzhan.co/luch-request/
* github: https://github.com/lei-mu/luch-request
* DCloud: http://ext.dcloud.net.cn/plugin?id=392
* HBuilderX: beat-3.0.4 alpha-3.0.4
*/
import dispatchRequest from './dispatchRequest'
import InterceptorManager from './InterceptorManager'
import mergeConfig from './mergeConfig'
import defaults from './defaults'
import { isPlainObject } from '../utils'
import clone from '../utils/clone'
export default class Request {
/**
* @param {Object} arg - 全局配置
* @param {String} arg.baseURL - 全局根路径
* @param {Object} arg.header - 全局header
* @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式
* @param {String} arg.dataType = [json] - 全局默认的dataType
* @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType支付宝小程序不支持
* @param {Object} arg.custom - 全局默认的自定义参数
* @param {Number} arg.timeout - 全局默认的超时时间单位 ms默认60000H5(HBuilderX 2.9.9+)APP(HBuilderX 2.9.9+)微信小程序2.10.0支付宝小程序
* @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书默认true.仅App安卓端支持HBuilderX 2.3.3+
* @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证cookies默认false仅H5支持HBuilderX 2.6.15+
* @param {Boolean} arg.firstIpv4 - 全DNS解析时优先使用ipv4默认false App-Android 支持 (HBuilderX 2.8.0+)
* @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器默认statusCode >= 200 && statusCode < 300
*/
constructor(arg = {}) {
if (!isPlainObject(arg)) {
arg = {}
console.warn('设置全局参数必须接收一个Object')
}
this.config = clone({...defaults, ...arg})
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
}
/**
* @Function
* @param {Request~setConfigCallback} f - 设置全局默认配置
*/
setConfig(f) {
this.config = f(this.config)
}
middleware(config) {
config = mergeConfig(this.config, config)
let chain = [dispatchRequest, undefined]
let promise = Promise.resolve(config)
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected)
})
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected)
})
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift())
}
return promise
}
/**
* @Function
* @param {Object} config - 请求配置项
* @prop {String} options.url - 请求路径
* @prop {Object} options.data - 请求参数
* @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型
* @prop {Object} [options.dataType = config.dataType] - 如果设为 json会尝试对返回的数据做一次 JSON.parse
* @prop {Object} [options.header = config.header] - 请求header
* @prop {Object} [options.method = config.method] - 请求方法
* @returns {Promise<unknown>}
*/
request(config = {}) {
return this.middleware(config)
}
get(url, options = {}) {
return this.middleware({
url,
method: 'GET',
...options
})
}
post(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'POST',
...options
})
}
// #ifndef MP-ALIPAY
put(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'PUT',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
delete(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'DELETE',
...options
})
}
// #endif
// #ifdef H5 || MP-WEIXIN
connect(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'CONNECT',
...options
})
}
// #endif
// #ifdef H5 || MP-WEIXIN || MP-BAIDU
head(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'HEAD',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
options(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'OPTIONS',
...options
})
}
// #endif
// #ifdef H5 || MP-WEIXIN
trace(url, data, options = {}) {
return this.middleware({
url,
data,
method: 'TRACE',
...options
})
}
// #endif
upload(url, config = {}) {
config.url = url
config.method = 'UPLOAD'
return this.middleware(config)
}
download(url, config = {}) {
config.url = url
config.method = 'DOWNLOAD'
return this.middleware(config)
}
}
/**
* setConfig回调
* @return {Object} - 返回操作后的config
* @callback Request~setConfigCallback
* @param {Object} config - 全局默认config
*/
@@ -1,20 +0,0 @@
'use strict'
import isAbsoluteURL from '../helpers/isAbsoluteURL'
import combineURLs from '../helpers/combineURLs'
/**
* Creates a new URL by combining the baseURL with the requestedURL,
* only when the requestedURL is not already an absolute URL.
* If the requestURL is absolute, this function returns the requestedURL untouched.
*
* @param {string} baseURL The base URL
* @param {string} requestedURL Absolute or relative URL to combine
* @returns {string} The combined full path
*/
export default function buildFullPath(baseURL, requestedURL) {
if (baseURL && !isAbsoluteURL(requestedURL)) {
return combineURLs(baseURL, requestedURL)
}
return requestedURL
}
@@ -1,30 +0,0 @@
/**
* 默认的全局配置
*/
export default {
baseURL: '',
header: {},
method: 'GET',
dataType: 'json',
// #ifndef MP-ALIPAY
responseType: 'text',
// #endif
custom: {},
// #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
timeout: 60000,
// #endif
// #ifdef APP-PLUS
sslVerify: true,
// #endif
// #ifdef H5
withCredentials: false,
// #endif
// #ifdef APP-PLUS
firstIpv4: false,
// #endif
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300
}
}
@@ -1,6 +0,0 @@
import adapter from '../adapters/index'
export default (config) => {
return adapter(config)
}
@@ -1,103 +0,0 @@
import {deepMerge, isUndefined} from '../utils'
/**
* 合并局部配置优先的配置如果局部有该配置项则用局部如果全局有该配置项则用全局
* @param {Array} keys - 配置项
* @param {Object} globalsConfig - 当前的全局配置
* @param {Object} config2 - 局部配置
* @return {{}}
*/
const mergeKeys = (keys, globalsConfig, config2) => {
let config = {}
keys.forEach(prop => {
if (!isUndefined(config2[prop])) {
config[prop] = config2[prop]
} else if (!isUndefined(globalsConfig[prop])) {
config[prop] = globalsConfig[prop]
}
})
return config
}
/**
*
* @param globalsConfig - 当前实例的全局配置
* @param config2 - 当前的局部配置
* @return - 合并后的配置
*/
export default (globalsConfig, config2 = {}) => {
const method = config2.method || globalsConfig.method || 'GET'
let config = {
baseURL: globalsConfig.baseURL || '',
method: method,
url: config2.url || '',
params: config2.params || {},
custom: {...(globalsConfig.custom || {}), ...(config2.custom || {})},
header: deepMerge(globalsConfig.header || {}, config2.header || {})
}
const defaultToConfig2Keys = ['getTask', 'validateStatus']
config = {...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2)}
// eslint-disable-next-line no-empty
if (method === 'DOWNLOAD') {
// #ifdef H5 || APP-PLUS
if (!isUndefined(config2.timeout)) {
config['timeout'] = config2['timeout']
} else if (!isUndefined(globalsConfig.timeout)) {
config['timeout'] = globalsConfig['timeout']
}
// #endif
} else if (method === 'UPLOAD') {
delete config.header['content-type']
delete config.header['Content-Type']
const uploadKeys = [
// #ifdef APP-PLUS || H5
'files',
// #endif
// #ifdef MP-ALIPAY
'fileType',
// #endif
// #ifdef H5
'file',
// #endif
'filePath',
'name',
// #ifdef H5 || APP-PLUS
'timeout',
// #endif
'formData',
]
uploadKeys.forEach(prop => {
if (!isUndefined(config2[prop])) {
config[prop] = config2[prop]
}
})
// #ifdef H5 || APP-PLUS
if (isUndefined(config.timeout) && !isUndefined(globalsConfig.timeout)) {
config['timeout'] = globalsConfig['timeout']
}
// #endif
} else {
const defaultsKeys = [
'data',
// #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
'timeout',
// #endif
'dataType',
// #ifndef MP-ALIPAY
'responseType',
// #endif
// #ifdef APP-PLUS
'sslVerify',
// #endif
// #ifdef H5
'withCredentials',
// #endif
// #ifdef APP-PLUS
'firstIpv4',
// #endif
]
config = {...config, ...mergeKeys(defaultsKeys, globalsConfig, config2)}
}
return config
}
@@ -1,16 +0,0 @@
/**
* Resolve or reject a Promise based on response status.
*
* @param {Function} resolve A function that resolves the promise.
* @param {Function} reject A function that rejects the promise.
* @param {object} response The response.
*/
export default function settle(resolve, reject, response) {
const validateStatus = response.config.validateStatus
const status = response.statusCode
if (status && (!validateStatus || validateStatus(status))) {
resolve(response)
} else {
reject(response)
}
}
@@ -1,69 +0,0 @@
'use strict'
import * as utils from './../utils'
function encode(val) {
return encodeURIComponent(val).
replace(/%40/gi, '@').
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace(/%20/g, '+').
replace(/%5B/gi, '[').
replace(/%5D/gi, ']')
}
/**
* Build a URL by appending params to the end
*
* @param {string} url The base of the url (e.g., http://www.google.com)
* @param {object} [params] The params to be appended
* @returns {string} The formatted url
*/
export default function buildURL(url, params) {
/*eslint no-param-reassign:0*/
if (!params) {
return url
}
var serializedParams
if (utils.isURLSearchParams(params)) {
serializedParams = params.toString()
} else {
var parts = []
utils.forEach(params, function serialize(val, key) {
if (val === null || typeof val === 'undefined') {
return
}
if (utils.isArray(val)) {
key = key + '[]'
} else {
val = [val]
}
utils.forEach(val, function parseValue(v) {
if (utils.isDate(v)) {
v = v.toISOString()
} else if (utils.isObject(v)) {
v = JSON.stringify(v)
}
parts.push(encode(key) + '=' + encode(v))
})
})
serializedParams = parts.join('&')
}
if (serializedParams) {
var hashmarkIndex = url.indexOf('#')
if (hashmarkIndex !== -1) {
url = url.slice(0, hashmarkIndex)
}
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
}
return url
}
@@ -1,14 +0,0 @@
'use strict'
/**
* Creates a new URL by combining the specified URLs
*
* @param {string} baseURL The base URL
* @param {string} relativeURL The relative URL
* @returns {string} The combined URL
*/
export default function combineURLs(baseURL, relativeURL) {
return relativeURL
? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
: baseURL
}
@@ -1,14 +0,0 @@
'use strict'
/**
* Determines whether the specified URL is absolute
*
* @param {string} url The URL to test
* @returns {boolean} True if the specified URL is absolute, otherwise false
*/
export default function isAbsoluteURL(url) {
// A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
// RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
// by any combination of letters, digits, plus, period, or hyphen.
return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url)
}
-116
View File
@@ -1,116 +0,0 @@
type AnyObject = Record<string | number | symbol, any>
type HttpPromise<T> = Promise<HttpResponse<T>>;
type Tasks = UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask
export interface RequestTask {
abort: () => void;
offHeadersReceived: () => void;
onHeadersReceived: () => void;
}
export interface HttpRequestConfig<T = Tasks> {
/** 请求基地址 */
baseURL?: string;
/** 请求服务器接口地址 */
url?: string;
/** 请求查询参数,自动拼接为查询字符串 */
params?: AnyObject;
/** 请求体参数 */
data?: AnyObject;
/** 文件对应的 key */
name?: string;
/** HTTP 请求中其他额外的 form data */
formData?: AnyObject;
/** 要上传文件资源的路径。 */
filePath?: string;
/** 需要上传的文件列表。使用 files 时,filePath 和 name 不生效,App、H5 2.6.15+ */
files?: Array<{
name?: string;
file?: File;
uri: string;
}>;
/** 要上传的文件对象,仅H5(2.6.15+)支持 */
file?: File;
/** 请求头信息 */
header?: AnyObject;
/** 请求方式 */
method?: "GET" | "POST" | "PUT" | "DELETE" | "CONNECT" | "HEAD" | "OPTIONS" | "TRACE" | "UPLOAD" | "DOWNLOAD";
/** 如果设为 json,会尝试对返回的数据做一次 JSON.parse */
dataType?: string;
/** 设置响应的数据类型,支付宝小程序不支持 */
responseType?: "text" | "arraybuffer";
/** 自定义参数 */
custom?: AnyObject;
/** 超时时间,仅微信小程序(2.10.0)、支付宝小程序支持 */
timeout?: number;
/** DNS解析时优先使用ipv4,仅 App-Android 支持 (HBuilderX 2.8.0+) */
firstIpv4?: boolean;
/** 验证 ssl 证书 仅5+App安卓端支持(HBuilderX 2.3.3+ */
sslVerify?: boolean;
/** 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+ */
withCredentials?: boolean;
/** 返回当前请求的task, options。请勿在此处修改options。 */
getTask?: (task: T, options: HttpRequestConfig<T>) => void;
/** 全局自定义验证器 */
validateStatus?: (statusCode: number) => boolean | void;
}
export interface HttpResponse<T = any> {
config: HttpRequestConfig;
statusCode: number;
cookies: Array<string>;
data: T;
errMsg: string;
header: AnyObject;
}
export interface HttpUploadResponse<T = any> {
config: HttpRequestConfig;
statusCode: number;
data: T;
errMsg: string;
}
export interface HttpDownloadResponse extends HttpResponse {
tempFilePath: string;
}
export interface HttpError {
config: HttpRequestConfig;
statusCode?: number;
cookies?: Array<string>;
data?: any;
errMsg: string;
header?: AnyObject;
}
export interface HttpInterceptorManager<V, E = V> {
use(
onFulfilled?: (config: V) => Promise<V> | V,
onRejected?: (config: E) => Promise<E> | E
): void;
eject(id: number): void;
}
export abstract class HttpRequestAbstract {
constructor(config?: HttpRequestConfig);
config: HttpRequestConfig;
interceptors: {
request: HttpInterceptorManager<HttpRequestConfig, HttpRequestConfig>;
response: HttpInterceptorManager<HttpResponse, HttpError>;
}
middleware<T = any>(config: HttpRequestConfig): HttpPromise<T>;
request<T = any>(config: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
get<T = any>(url: string, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
upload<T = any>(url: string, config?: HttpRequestConfig<UniApp.UploadTask>): HttpPromise<T>;
delete<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
head<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
post<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
put<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
connect<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
options<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
trace<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
download(url: string, config?: HttpRequestConfig<UniApp.DownloadTask>): Promise<HttpDownloadResponse>;
setConfig(onSend: (config: HttpRequestConfig) => HttpRequestConfig): void;
}
declare class HttpRequest extends HttpRequestAbstract { }
export default HttpRequest;
@@ -1,2 +0,0 @@
import Request from './core/Request'
export default Request
-135
View File
@@ -1,135 +0,0 @@
'use strict'
// utils is a library of generic helper functions non-specific to axios
var toString = Object.prototype.toString
/**
* Determine if a value is an Array
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an Array, otherwise false
*/
export function isArray (val) {
return toString.call(val) === '[object Array]'
}
/**
* Determine if a value is an Object
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an Object, otherwise false
*/
export function isObject (val) {
return val !== null && typeof val === 'object'
}
/**
* Determine if a value is a Date
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a Date, otherwise false
*/
export function isDate (val) {
return toString.call(val) === '[object Date]'
}
/**
* Determine if a value is a URLSearchParams object
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a URLSearchParams object, otherwise false
*/
export function isURLSearchParams (val) {
return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams
}
/**
* Iterate over an Array or an Object invoking a function for each item.
*
* If `obj` is an Array callback will be called passing
* the value, index, and complete array for each item.
*
* If 'obj' is an Object callback will be called passing
* the value, key, and complete object for each property.
*
* @param {Object|Array} obj The object to iterate
* @param {Function} fn The callback to invoke for each item
*/
export function forEach (obj, fn) {
// Don't bother if no value provided
if (obj === null || typeof obj === 'undefined') {
return
}
// Force an array if not already something iterable
if (typeof obj !== 'object') {
/*eslint no-param-reassign:0*/
obj = [obj]
}
if (isArray(obj)) {
// Iterate over array values
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj)
}
} else {
// Iterate over object keys
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj)
}
}
}
}
/**
* 是否为boolean
* @param val
* @returns {boolean}
*/
export function isBoolean(val) {
return typeof val === 'boolean'
}
/**
* 是否为真正的对象{} new Object
* @param {any} obj - 检测的对象
* @returns {boolean}
*/
export function isPlainObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}
/**
* Function equal to merge with the difference being that no reference
* to original objects is kept.
*
* @see merge
* @param {Object} obj1 Object to merge
* @returns {Object} Result of all merge properties
*/
export function deepMerge(/* obj1, obj2, obj3, ... */) {
let result = {}
function assignValue(val, key) {
if (typeof result[key] === 'object' && typeof val === 'object') {
result[key] = deepMerge(result[key], val)
} else if (typeof val === 'object') {
result[key] = deepMerge({}, val)
} else {
result[key] = val
}
}
for (let i = 0, l = arguments.length; i < l; i++) {
forEach(arguments[i], assignValue)
}
return result
}
export function isUndefined (val) {
return typeof val === 'undefined'
}
@@ -1,264 +0,0 @@
/* eslint-disable */
var clone = (function() {
'use strict';
function _instanceof(obj, type) {
return type != null && obj instanceof type;
}
var nativeMap;
try {
nativeMap = Map;
} catch(_) {
// maybe a reference error because no `Map`. Give it a dummy value that no
// value will ever be an instanceof.
nativeMap = function() {};
}
var nativeSet;
try {
nativeSet = Set;
} catch(_) {
nativeSet = function() {};
}
var nativePromise;
try {
nativePromise = Promise;
} catch(_) {
nativePromise = function() {};
}
/**
* Clones (copies) an Object using deep copying.
*
* This function supports circular references by default, but if you are certain
* there are no circular references in your object, you can save some CPU time
* by calling clone(obj, false).
*
* Caution: if `circular` is false and `parent` contains circular references,
* your program may enter an infinite loop and crash.
*
* @param `parent` - the object to be cloned
* @param `circular` - set to true if the object to be cloned may contain
* circular references. (optional - true by default)
* @param `depth` - set to a number if the object is only to be cloned to
* a particular depth. (optional - defaults to Infinity)
* @param `prototype` - sets the prototype to be used when cloning an object.
* (optional - defaults to parent prototype).
* @param `includeNonEnumerable` - set to true if the non-enumerable properties
* should be cloned as well. Non-enumerable properties on the prototype
* chain will be ignored. (optional - false by default)
*/
function clone(parent, circular, depth, prototype, includeNonEnumerable) {
if (typeof circular === 'object') {
depth = circular.depth;
prototype = circular.prototype;
includeNonEnumerable = circular.includeNonEnumerable;
circular = circular.circular;
}
// maintain two arrays for circular references, where corresponding parents
// and children have the same index
var allParents = [];
var allChildren = [];
var useBuffer = typeof Buffer != 'undefined';
if (typeof circular == 'undefined')
circular = true;
if (typeof depth == 'undefined')
depth = Infinity;
// recurse this function so we don't reset allParents and allChildren
function _clone(parent, depth) {
// cloning null always returns null
if (parent === null)
return null;
if (depth === 0)
return parent;
var child;
var proto;
if (typeof parent != 'object') {
return parent;
}
if (_instanceof(parent, nativeMap)) {
child = new nativeMap();
} else if (_instanceof(parent, nativeSet)) {
child = new nativeSet();
} else if (_instanceof(parent, nativePromise)) {
child = new nativePromise(function (resolve, reject) {
parent.then(function(value) {
resolve(_clone(value, depth - 1));
}, function(err) {
reject(_clone(err, depth - 1));
});
});
} else if (clone.__isArray(parent)) {
child = [];
} else if (clone.__isRegExp(parent)) {
child = new RegExp(parent.source, __getRegExpFlags(parent));
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else if (clone.__isDate(parent)) {
child = new Date(parent.getTime());
} else if (useBuffer && Buffer.isBuffer(parent)) {
if (Buffer.from) {
// Node.js >= 5.10.0
child = Buffer.from(parent);
} else {
// Older Node.js versions
child = new Buffer(parent.length);
parent.copy(child);
}
return child;
} else if (_instanceof(parent, Error)) {
child = Object.create(parent);
} else {
if (typeof prototype == 'undefined') {
proto = Object.getPrototypeOf(parent);
child = Object.create(proto);
}
else {
child = Object.create(prototype);
proto = prototype;
}
}
if (circular) {
var index = allParents.indexOf(parent);
if (index != -1) {
return allChildren[index];
}
allParents.push(parent);
allChildren.push(child);
}
if (_instanceof(parent, nativeMap)) {
parent.forEach(function(value, key) {
var keyChild = _clone(key, depth - 1);
var valueChild = _clone(value, depth - 1);
child.set(keyChild, valueChild);
});
}
if (_instanceof(parent, nativeSet)) {
parent.forEach(function(value) {
var entryChild = _clone(value, depth - 1);
child.add(entryChild);
});
}
for (var i in parent) {
var attrs = Object.getOwnPropertyDescriptor(parent, i);
if (attrs) {
child[i] = _clone(parent[i], depth - 1);
}
try {
var objProperty = Object.getOwnPropertyDescriptor(parent, i);
if (objProperty.set === 'undefined') {
// no setter defined. Skip cloning this property
continue;
}
child[i] = _clone(parent[i], depth - 1);
} catch(e){
if (e instanceof TypeError) {
// when in strict mode, TypeError will be thrown if child[i] property only has a getter
// we can't do anything about this, other than inform the user that this property cannot be set.
continue
} else if (e instanceof ReferenceError) {
//this may happen in non strict mode
continue
}
}
}
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(parent);
for (var i = 0; i < symbols.length; i++) {
// Don't need to worry about cloning a symbol because it is a primitive,
// like a number or string.
var symbol = symbols[i];
var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
continue;
}
child[symbol] = _clone(parent[symbol], depth - 1);
Object.defineProperty(child, symbol, descriptor);
}
}
if (includeNonEnumerable) {
var allPropertyNames = Object.getOwnPropertyNames(parent);
for (var i = 0; i < allPropertyNames.length; i++) {
var propertyName = allPropertyNames[i];
var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
if (descriptor && descriptor.enumerable) {
continue;
}
child[propertyName] = _clone(parent[propertyName], depth - 1);
Object.defineProperty(child, propertyName, descriptor);
}
}
return child;
}
return _clone(parent, depth);
}
/**
* Simple flat clone using prototype, accepts only objects, usefull for property
* override on FLAT configuration object (no nested props).
*
* USE WITH CAUTION! This may not behave as you wish if you do not know how this
* works.
*/
clone.clonePrototype = function clonePrototype(parent) {
if (parent === null)
return null;
var c = function () {};
c.prototype = parent;
return new c();
};
// private utility functions
function __objToStr(o) {
return Object.prototype.toString.call(o);
}
clone.__objToStr = __objToStr;
function __isDate(o) {
return typeof o === 'object' && __objToStr(o) === '[object Date]';
}
clone.__isDate = __isDate;
function __isArray(o) {
return typeof o === 'object' && __objToStr(o) === '[object Array]';
}
clone.__isArray = __isArray;
function __isRegExp(o) {
return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
}
clone.__isRegExp = __isRegExp;
function __getRegExpFlags(re) {
var flags = '';
if (re.global) flags += 'g';
if (re.ignoreCase) flags += 'i';
if (re.multiline) flags += 'm';
return flags;
}
clone.__getRegExpFlags = __getRegExpFlags;
return clone;
})();
export default clone
-13
View File
@@ -1,13 +0,0 @@
{
"id": "luch-request",
"name": "luch-request",
"version": "3.0.7",
"description": "基于Promise开发的跨平台、项目级别的请求库,它有更小的体积,易用的api,方便简单的自定义能力",
"keywords": [
"request",
"axios",
"网络请求",
"拦截器",
"promise"
]
}
-73
View File
@@ -1,73 +0,0 @@
import App from "./App";
import Vue from "vue";
// 挂载全局工具类
import utils from "./utils/index.js";
Vue.prototype.$utils = utils;
// 全局统一样式的对话框
import Fy from '@/js_sdk/fy-showModal/index.js'
uni.$eShowModal = (e = {}) => {
return Fy.showModal(e)
};
// 全局混入
import AppMixin from "@/common/mixins/index.js";
Vue.use(AppMixin);
// 全局过滤器
import AppFilters from "@/common/filters/index.js";
for (let fKey in AppFilters) {
Vue.filter(fKey, AppFilters[fKey]);
}
// 引用tmUI2.x框架
import tmVuetify from "./tm-vuetify";
Vue.use(tmVuetify);
// i18n部分的配置
// 引入语言包,注意路径
import Chinese from "@/common/locales/zh.js";
import English from "@/common/locales/en.js";
// 引入并使用vue-i18n
import VueI18n from "vue-i18n";
Vue.use(VueI18n);
// 构造i18n对象
const i18n = new VueI18n({
// 默认语言,这里的local属性,对应message中的zh、en属性
locale: uni.getStorageSync("Default_Lang") || "zh",
// 引入语言文件
messages: {
// 这里的属性名是任意的,您也可以把zh设置为cn等,只是后续切换语言时
// 要标识这里的语言属性,如:this.$i18n.locale = zh|en|cn|xxx
zh: Chinese,
en: English,
},
});
// 引入请求库
import {
http
} from "./common/http/index.js";
Vue.prototype.$http = http;
// 全局api管理
import ApiManager from '@/api/index.js'
Vue.use(ApiManager);
import HaloTokenConfig from '@/config/token.config.js'
Vue.prototype.$baseApiUrl = HaloTokenConfig.BASE_API
// 由于微信小程序的运行机制问题,需声明如下一行,H5和APP非必填
Vue.prototype._i18n = i18n;
Vue.config.productionTip = false;
App.mpType = "app";
const app = new Vue({
i18n,
...App,
});
app.$mount();
+148
View File
@@ -0,0 +1,148 @@
import { fileURLToPath, URL } from 'node:url'
import process from 'node:process'
import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
import { loadEnv } from 'vite'
function getMode() {
const args = process.argv.slice(2)
const modeFlagIndex = args.findIndex(arg => arg === '--mode')
return modeFlagIndex !== -1 ? args[modeFlagIndex + 1] : args[0] === 'build' ? 'production' : 'development'
}
const envDir = fileURLToPath(new URL('./env', import.meta.url))
const env = loadEnv(getMode(), envDir)
const {
VITE_APP_TITLE,
VITE_APP_DESC,
VITE_UNI_APPID,
VITE_WX_APPID,
VITE_APP_PUBLIC_BASE,
VITE_FALLBACK_LOCALE,
} = env
export default defineManifestConfig({
'name': VITE_APP_TITLE,
'appid': VITE_UNI_APPID,
'description': VITE_APP_DESC,
'versionName': '1.0.0',
'versionCode': '100',
'transformPx': false,
'locale': VITE_FALLBACK_LOCALE,
'h5': {
router: {
base: VITE_APP_PUBLIC_BASE,
},
},
'app-plus': {
usingComponents: true,
nvueStyleCompiler: 'uni-app',
compilerVersion: 3,
compatible: {
ignoreVersion: true,
},
splashscreen: {
alwaysShowBeforeRender: true,
waiting: true,
autoclose: true,
delay: 0,
},
modules: {},
distribute: {
android: {
minSdkVersion: 21,
targetSdkVersion: 30,
abiFilters: ['armeabi-v7a', 'arm64-v8a'],
permissions: [
'<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>',
'<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>',
'<uses-permission android:name="android.permission.VIBRATE"/>',
'<uses-permission android:name="android.permission.READ_LOGS"/>',
'<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>',
'<uses-feature android:name="android.hardware.camera.autofocus"/>',
'<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>',
'<uses-permission android:name="android.permission.CAMERA"/>',
'<uses-permission android:name="android.permission.GET_ACCOUNTS"/>',
'<uses-permission android:name="android.permission.READ_PHONE_STATE"/>',
'<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>',
'<uses-permission android:name="android.permission.WAKE_LOCK"/>',
'<uses-permission android:name="android.permission.FLASHLIGHT"/>',
'<uses-feature android:name="android.hardware.camera"/>',
'<uses-permission android:name="android.permission.WRITE_SETTINGS"/>',
],
},
ios: {},
sdkConfigs: {},
icons: {
android: {
hdpi: 'static/app/icons/72x72.png',
xhdpi: 'static/app/icons/96x96.png',
xxhdpi: 'static/app/icons/144x144.png',
xxxhdpi: 'static/app/icons/192x192.png',
},
ios: {
appstore: 'static/app/icons/1024x1024.png',
ipad: {
'app': 'static/app/icons/76x76.png',
'app@2x': 'static/app/icons/152x152.png',
'notification': 'static/app/icons/20x20.png',
'notification@2x': 'static/app/icons/40x40.png',
'proapp@2x': 'static/app/icons/167x167.png',
'settings': 'static/app/icons/29x29.png',
'settings@2x': 'static/app/icons/58x58.png',
'spotlight': 'static/app/icons/40x40.png',
'spotlight@2x': 'static/app/icons/80x80.png',
},
iphone: {
'app@2x': 'static/app/icons/120x120.png',
'app@3x': 'static/app/icons/180x180.png',
'notification@2x': 'static/app/icons/40x40.png',
'notification@3x': 'static/app/icons/60x60.png',
'settings@2x': 'static/app/icons/58x58.png',
'settings@3x': 'static/app/icons/87x87.png',
'spotlight@2x': 'static/app/icons/80x80.png',
'spotlight@3x': 'static/app/icons/120x120.png',
},
},
},
},
},
'quickapp': {},
'mp-weixin': {
appid: VITE_WX_APPID,
setting: {
urlCheck: false,
es6: true,
minified: true,
},
optimization: {
subPackages: true,
},
mergeVirtualHostAttributes: true,
usingComponents: true,
},
'mp-alipay': {
usingComponents: true,
styleIsolation: 'shared',
optimization: {
subPackages: true,
},
compileOptions: {
globalObjectMode: 'enable',
transpile: {
script: {
ignore: ['node_modules/**'],
},
},
},
},
'mp-baidu': {
usingComponents: true,
},
'mp-toutiao': {
usingComponents: true,
},
'uniStatistics': {
enable: false,
},
'vueVersion': '3',
})
-217
View File
@@ -1,217 +0,0 @@
{
"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
}
}
}
+14
View File
@@ -0,0 +1,14 @@
import { defineConfig } from 'openapi-ts-request'
export default defineConfig([
{
describe: 'uni-openapi-test',
schemaPath: 'https://ukw0y1.laf.run/unibest-opapi-test.json',
serversPath: './src/service',
requestLibPath: `import request from '@/http/vue-query';\n import { CustomRequestOptions_ } from '@/http/types';`,
requestOptionsType: 'CustomRequestOptions_',
isGenReactQuery: false,
reactQueryMode: 'vue',
isGenJavaScript: false,
},
])
+225 -16
View File
@@ -1,26 +1,235 @@
{
"name": "uni-halo",
"type": "module",
"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"
"unibest": {
"platforms": [
"h5",
"mp-weixin",
"app",
"mp-alipay",
"mp-toutiao"
],
"uiLibrary": "wot-ui-v2",
"loginStrategy": true,
"i18n": true,
"chartLibraries": [
"ucharts"
],
"cliVersion": "4.0.16",
"unibestVersion": "4.4.1",
"unibestUpdateTime": "2026-05-22",
"createdAt": "2026-06-09 14:03:51"
},
"keywords": [
"uni-halo",
"小莫唐尼",
"巷子工坊"
],
"author": "巷子工坊丨小莫唐尼",
"license": "AGPL-3.0",
"bugs": {
"url": "https://gitee.com/ialley-workshop-open/uni-halo/issues"
"packageManager": "pnpm@10.10.0",
"description": "基于halo开源博客系统API开发的多端博客。",
"author": {
"name": "okay-xiaomo",
"zhName": "小莫唐尼",
"email": "okay.xiaomo@gmail.com",
"github": "https://github.com/okay-xiaomo",
"gitee": "https://gitee.com/okay-xiaomo"
},
"license": "AGPL-3.0-only",
"homepage": "https://uni-halo.925i.cn",
"devDependencies": {
"vue-i18n": "^9.1.10"
"repository": "https://github.com/ialley-workshop-open/uni-halo",
"bugs": {
"url": "https://github.com/ialley-workshop-open/uni-halo/issues"
},
"engines": {
"node": ">=20",
"pnpm": ">=9"
},
"scripts": {
"openapi": "openapi-ts",
"init-husky": "git init && husky",
"init-baseFiles": "node ./scripts/create-base-files.js",
"prepare": "pnpm init-husky & pnpm init-baseFiles",
"predev": "pnpm init-baseFiles",
"predev:app": "pnpm init-baseFiles",
"predev:mp": "pnpm init-baseFiles",
"preinstall": "npx only-allow pnpm",
"uvm": "npx @dcloudio/uvm@latest",
"uvm-rm": "node ./scripts/postupgrade.js",
"bump-version": "node ./scripts/bump-version.js",
"postuvm": "echo upgrade uni-app success!",
"dev:app": "uni -p app",
"dev:app:test": "uni -p app --mode test",
"dev:app:prod": "uni -p app --mode production",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"dev:custom": "uni -p",
"dev": "uni",
"dev:test": "uni --mode test",
"dev:prod": "uni --mode production",
"dev:h5": "uni",
"dev:h5:test": "uni --mode test",
"dev:h5:prod": "uni --mode production",
"dev:h5:ssr": "uni --ssr",
"dev:mp": "uni -p mp-weixin",
"dev:mp:test": "uni -p mp-weixin --mode test",
"dev:mp:prod": "uni -p mp-weixin --mode production",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-jd": "uni -p mp-jd",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev:mp-weixin": "uni -p mp-weixin",
"dev:mp-xhs": "uni -p mp-xhs",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:app": "uni build -p app",
"build:app:test": "uni build -p app --mode test",
"build:app:prod": "uni build -p app --mode production",
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"build:custom": "uni build -p",
"build:h5": "uni build",
"build:h5:test": "uni build --mode test",
"build:h5:prod": "uni build --mode production",
"build": "uni build",
"build:test": "uni build --mode test",
"build:prod": "uni build --mode production",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp": "uni build -p mp-weixin",
"build:mp:test": "uni build -p mp-weixin --mode test",
"build:mp:prod": "uni build -p mp-weixin --mode production",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-jd": "uni build -p mp-jd",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin",
"build:mp-xhs": "uni build -p mp-xhs",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
"upload:changeset": "pnpm changeset && pnpm changeset version",
"upload:mp": "node ./scripts/upload-weixin.js",
"type-check": "vue-tsc --noEmit",
"lint": "eslint",
"lint:fix": "eslint --fix",
"test": "vitest",
"test:run": "vitest run"
},
"dependencies": {
"qs": "^6.12.1"
"@alova/adapter-uniapp": "^2.0.14",
"@alova/shared": "^1.3.1",
"@changesets/cli": "^2.30.0",
"@dcloudio/uni-app": "3.0.0-4070620250821001",
"@dcloudio/uni-app-harmony": "3.0.0-4070620250821001",
"@dcloudio/uni-app-plus": "3.0.0-4070620250821001",
"@dcloudio/uni-components": "3.0.0-4070620250821001",
"@dcloudio/uni-h5": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-alipay": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-baidu": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-harmony": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-jd": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-lark": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-qq": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-toutiao": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-weixin": "3.0.0-4070620250821001",
"@dcloudio/uni-mp-xhs": "3.0.0-4070620250821001",
"@dcloudio/uni-quickapp-webview": "3.0.0-4070620250821001",
"@halo-dev/api-client": "^2.24.0",
"@qiun/ucharts": "2.5.0-20230101",
"@wot-ui/ui": "latest",
"abortcontroller-polyfill": "^1.7.8",
"alova": "^3.3.3",
"dayjs": "1.11.10",
"pinia": "2.0.36",
"pinia-plugin-persistedstate": "3.2.1",
"qs": "^6.15.2",
"vue": "^3.4.21",
"vue-i18n": "9.1.9",
"vue-router": "4.5.1",
"z-paging": "2.8.7"
},
"devDependencies": {
"@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1",
"@dcloudio/types": "^3.4.8",
"@dcloudio/uni-automator": "3.0.0-4070620250821001",
"@dcloudio/uni-cli-shared": "3.0.0-4070620250821001",
"@dcloudio/uni-stacktracey": "3.0.0-4070620250821001",
"@dcloudio/vite-plugin-uni": "3.0.0-4070620250821001",
"@iconify-json/carbon": "^1.2.4",
"@iconify/utils": "^3.0.2",
"@types/node": "^20.17.9",
"@uni-helper/eslint-config": "0.5.0",
"@uni-helper/plugin-uni": "0.1.0",
"@uni-helper/uni-env": "0.1.8",
"@uni-helper/uni-types": "1.0.0-alpha.6",
"@uni-helper/unocss-preset-uni": "0.2.11",
"@uni-helper/vite-plugin-uni-components": "0.2.3",
"@uni-helper/vite-plugin-uni-layouts": "0.1.11",
"@uni-helper/vite-plugin-uni-manifest": "0.2.12",
"@uni-helper/vite-plugin-uni-pages": "0.3.23",
"@uni-helper/vite-plugin-uni-platform": "0.0.5",
"@uni-ku/bundle-optimizer": "v1.3.15-beta.2",
"@uni-ku/root": "1.4.1",
"@unocss/eslint-plugin": "^66.2.3",
"@unocss/preset-legacy-compat": "66.0.0",
"@vitejs/plugin-vue": "^5.1.0",
"@vue/runtime-core": "^3.4.21",
"@vue/test-utils": "^2.4.10",
"@vue/tsconfig": "^0.1.3",
"autoprefixer": "^10.4.20",
"cross-env": "^10.0.0",
"enquirer": "^2.4.1",
"eslint": "^9.31.0",
"eslint-plugin-format": "^1.0.1",
"husky": "^9.1.7",
"jsdom": "^29.1.1",
"lint-staged": "^15.2.10",
"miniprogram-api-typings": "^4.1.0",
"miniprogram-ci": "^2.1.26",
"openapi-ts-request": "^1.10.0",
"picocolors": "^1.1.1",
"postcss": "^8.4.49",
"postcss-html": "^1.8.0",
"postcss-scss": "^4.0.9",
"rollup-plugin-visualizer": "^6.0.3",
"sass": "1.77.8",
"std-env": "^3.9.0",
"typescript": "~5.8.0",
"unocss": "66.0.0",
"unplugin-auto-import": "^20.0.0",
"vite": "5.2.8",
"vite-plugin-restart": "^1.0.0",
"vite-plugin-vue-devtools": "^8.0.5",
"vitest": "3.2.4",
"vue-tsc": "^3.0.6"
},
"pnpm": {
"overrides": {
"unconfig": "7.3.2"
},
"onlyBuiltDependencies": [
"@swc/core",
"@uni-helper/unocss-preset-uni",
"core-js",
"es5-ext",
"esbuild",
"less",
"protobufjs",
"vue-demi"
]
},
"overrides": {
"unconfig": "7.3.2"
},
"resolutions": {
"bin-wrapper": "npm:bin-wrapper-china",
"unconfig": "7.3.2"
},
"lint-staged": {
"*": "echo 欢迎使用 uni-halo v3.0.0 版本!"
}
}
+26
View File
@@ -0,0 +1,26 @@
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
import { tabBar } from './src/tabbar/config'
export default defineUniPages({
globalStyle: {
navigationStyle: 'default',
navigationBarTitleText: 'uni-halo',
navigationBarBackgroundColor: '#f8f8f8',
navigationBarTextStyle: 'black',
backgroundColor: '#FFFFFF',
},
easycom: {
autoscan: true,
custom: {
'^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)':
'z-paging/components/z-paging$1/z-paging$1.vue',
'^wd-(.*)': '@wot-ui/ui/components/wd-$1/wd-$1.vue',
},
},
// tabbar 的配置统一在 “./src/tabbar/config.ts” 文件中
subPackages: [{
root: 'pages-demo',
pages: [],
}],
tabBar: tabBar as any,
})
-336
View File
@@ -1,336 +0,0 @@
{
"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": "关于"
}
]
}
}
-86
View File
@@ -1,86 +0,0 @@
<template>
<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 {
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>
-591
View File
@@ -1,591 +0,0 @@
<template>
<view class="app-page pb-24">
<!-- 博主信息 -->
<view class="blogger-info" :style="[calcProfileStyle]">
<image class="avatar" :src="$utils.checkAvatarUrl(bloggerInfo.avatar)" mode="aspectFill"></image>
<view class="profile">
<view class="author mt-24 text-size-g text-weight-b">{{ bloggerInfo.nickname }}</view>
<view class="desc mt-24 text-size-m">
{{ bloggerInfo.description || '这个博主很懒,竟然没写介绍~' }}
</view>
</view>
<image v-if="calcWaveUrl" :src="calcWaveUrl" mode="scaleToFill" class="gif-wave"></image>
</view>
<!-- 统计信息 -->
<view class="statistics-wrap bg-white">
<tm-more iconColor="light-blue" :open.sync="statisticsShowMore" :maxHeight="62" label=" " open-label=" ">
<template>
<view class="statistics flex pt-24 pb-24" :class="{ 'has-solid': statisticsShowMore }">
<view class="item flex-1 text-align-center">
<view class="number text-size-xl text-bg-gradient-orange-accent">
<tm-flop :startVal="0" :decimals="0" :endVal="statistics.post"
:duration="3000"></tm-flop>
</view>
<view class="mt-6 text-align-center text-size-s text-grey-darken-1">内容数量</view>
</view>
<view class="item flex-1 text-align-center">
<view class="number text-size-xl text-bg-gradient-green-accent">
<tm-flop :startVal="0" :decimals="0" :endVal="statistics.visit"
:duration="3000"></tm-flop>
</view>
<view class="mt-6 text-size-s text-grey-darken-1">访客数量</view>
</view>
<view class="item flex-1 text-align-center">
<view class="number text-size-xl text-bg-gradient-blue-accent">
<tm-flop :startVal="0" :decimals="0" :endVal="statistics.category"
:duration="3000"></tm-flop>
</view>
<view class="mt-6 text-align-center text-size-s text-grey-darken-1">分类总数</view>
</view>
</view>
<view class="statistics solid-top has-solid flex pt-24 pb-24">
<view class="item flex-1 text-align-center">
<view class="number text-size-xl text-bg-gradient-orange-accent">
<tm-flop :startVal="0" :decimals="0" :endVal="statistics.comment"
:duration="3000"></tm-flop>
</view>
<view class="mt-6 text-align-center text-size-s text-grey-darken-1">评论数量</view>
</view>
<view class="item flex-1 text-align-center">
<view class="number text-size-xl text-bg-gradient-blue-accent">
<tm-flop :startVal="0" :decimals="0" :endVal="statistics.upvote"
:duration="3000"></tm-flop>
</view>
<view class="mt-6 text-size-s text-grey-darken-1">点赞数量</view>
</view>
</view>
</template>
</tm-more>
</view>
<!-- 功能导航 -->
<view class="nav-wrap ma-24 round-3">
<tm-grouplist :shadow="0" :round="3" :margin="[0, 0]">
<block v-for="(nav, index) in navList" :key="index">
<tm-listitem v-if="nav.show" :title="nav.title" :left-icon="nav.leftIcon" show-left-icon
:left-icon-color="nav.leftIconColor" :value="nav.rightText" @click="fnOnNav(nav)">
<template slot="rightValue">
<button class="right-value-btn" v-if="nav.openType" :open-type="nav.openType">
{{ nav.rightText }}
</button>
<text v-else>{{ nav.rightText }}</text>
</template>
</tm-listitem>
</block>
</tm-grouplist>
</view>
<!-- 版权 -->
<view v-if="copyrightConfig.enabled" class="copyright mt-40 text-size-xs text-align-center">
<view class="">{{ copyrightConfig.content }}</view>
</view>
</view>
</template>
<script>
import {checkHasAdminLogin} from '@/utils/auth.js';
import CheckAppUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update';
import {CheckWxUpdate} from '@/utils/update.js';
import tmGrouplist from '@/tm-vuetify/components/tm-grouplist/tm-grouplist.vue';
import tmListitem from '@/tm-vuetify/components/tm-listitem/tm-listitem.vue';
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
import tmMore from '@/tm-vuetify/components/tm-more/tm-more.vue';
import tmFlop from '@/tm-vuetify/components/tm-flop/tm-flop.vue';
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
import wave from '@/components/wave/wave.vue';
export default {
components: {
tmGrouplist,
tmListitem,
tmTranslate,
tmPoup,
tmMore,
tmFlop,
tmButton,
tmIcons,
wave
},
data() {
return {
statisticsShowMore: false,
//
statistics: {
post: 0, //
comment: 0, //
category: 0, //
visit: 0, // 访
upvote: 0 //
},
//
navList: [],
miniProfileCard: {
show: false
}
};
},
computed: {
haloConfigs() {
return this.$tm.vx.getters().getConfigs
},
pageConfig() {
return this.haloConfigs.pageConfig.aboutConfig;
},
postDetailConfig() {
return this.haloConfigs.basicConfig.postDetailConfig;
},
bloggerInfo() {
return this.haloConfigs.authorConfig.blogger;
},
calcProfileStyle() {
const _imgUrlOr = this.pageConfig.bgImageUrl;
return {
backgroundImage: `url(${this.$utils.checkImageUrl(_imgUrlOr)})`
}
},
calcWaveUrl() {
return this.$utils.checkImageUrl(this.pageConfig.waveImageUrl);
},
copyrightConfig() {
return this.haloConfigs.basicConfig.copyrightConfig;
}
},
watch: {
haloConfigs: {
handler(val) {
if (!val) return;
this.fnGetNavList();
},
deep: true,
immediate: true,
}
},
created() {
this.fnGetData();
},
onPullDownRefresh() {
this.fnGetData();
},
methods: {
fnGetNavList() {
const systemInfo = uni.getSystemInfoSync();
let _isWx = false;
// #ifdef MP-WEIXIN
_isWx = true;
// #endif
this.navList = [
{
key: 'archives',
title: this.haloConfigs.basicConfig.auditModeEnabled ? '内容归档' : '文章归档',
leftIcon: 'halocoloricon-classify',
leftIconColor: 'red',
rightText: this.haloConfigs.basicConfig.auditModeEnabled ? '已归档的内容' : '已归档的文章',
path: '/pagesA/archives/archives',
isAdmin: false,
type: 'page',
show: true
}, {
key: 'love',
title: '恋爱日记',
leftIcon: 'halocoloricon-attent',
leftIconColor: 'red',
rightText: '甜蜜恋人的专属',
path: '/pagesA/love/love',
isAdmin: false,
type: 'page',
show: this.haloConfigs.loveConfig.loveEnabled
}, {
key: 'disclaimers',
title: '友情链接',
leftIcon: 'icon-lianjie',
leftIconColor: 'blue',
rightText: '看看朋友们吧',
path: '/pagesA/friend-links/friend-links',
isAdmin: false,
type: 'page',
show: true
},
{
key: 'disclaimers',
title: '免责声明',
leftIcon: 'icon-map',
leftIconColor: 'red',
rightText: '博客内容免责声明',
path: '/pagesA/disclaimers/disclaimers',
isAdmin: false,
type: 'page',
show: this.haloConfigs.basicConfig.disclaimers.enabled
},
{
key: 'contact-blogger',
title: '联系博主',
leftIcon: 'icon-paper-plane',
leftIconColor: 'orange',
rightText: '博主常用联系方式',
path: '/pagesA/contact/contact',
isAdmin: false,
type: 'page',
show: this.haloConfigs.authorConfig.social.enabled
},
{
key: 'session',
title: '在线客服',
leftIcon: 'icon-headset-fill',
leftIconColor: 'cyan',
rightText: '在线客服为您答疑',
path: null,
isAdmin: false,
type: 'page',
openType: 'contact',
show: _isWx
},
{
key: 'feedback',
title: '意见反馈',
leftIcon: 'icon-comment-dots',
leftIconColor: 'light-blue',
rightText: '提交系统使用反馈',
path: null,
isAdmin: false,
type: 'page',
openType: 'feedback',
show: _isWx
},
{
key: 'about',
title: '关于项目',
leftIcon: 'icon-exclamation-circle',
leftIconColor: 'blue',
rightText: '小莫唐尼开源项目',
path: '/pagesA/about/about',
isAdmin: false,
type: 'page',
show: this.haloConfigs.basicConfig.showAboutSystem
},
// {
// key: 'cache',
// title: '',
// leftIcon: 'icon-delete',
// leftIconColor: 'gray',
// rightText: uni.getStorageInfoSync().currentSize + 'KB',
// path: 'clear',
// isAdmin: false,
// type: 'poup',
// show: true
// },
// {
// key: 'update',
// title: '',
// leftIcon: 'icon-clouddownload',
// leftIconColor: 'pink',
// rightText: ` v${systemInfo.appVersion}`,
// path: 'update',
// isAdmin: false,
// type: 'poup',
// show: true
// },
];
},
fnGetData() {
this.$httpApi.v2
.getBlogStatistics()
.then(res => {
this.statistics = res;
})
.catch(err => {
this.$tm.toast('数据加载失败,请重试!');
})
.finally(() => {
uni.stopPullDownRefresh();
});
},
fnOnNav(data) {
const {
type,
path,
isAdmin,
openType
} = data;
if (openType) {
// #ifndef MP-WEIXIN
return uni.$tm.toast('仅支持微信小程序打开!');
// #endif
// #ifdef MP-WEIXIN
return;
// #endif
}
if (!path) return;
//
if (isAdmin && !checkHasAdminLogin()) {
uni.$eShowModal({
title: '提示',
content: '未登录超管账号或登录状态已过期,是否立即登录?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
uni.navigateTo({
url: '/pagesB/login/login'
});
})
.catch(err => {
});
return;
}
if (type == 'poup') {
switch (path) {
case 'clear':
uni.$eShowModal({
title: '提示',
content: '清除后可能退出您当前的登录或已授权状态,是否确定清除缓存吗?',
showCancel: true,
cancelText: '否',
cancelColor: '#999999',
confirmText: '是',
confirmColor: '#03a9f4'
})
.then(res => {
uni.clearStorageSync();
this.navList.find(x => x.key == 'cache').rightText =
uni.getStorageInfoSync().currentSize + 'KB';
})
.catch(err => {
});
break;
case 'update':
// #ifdef APP-PLUS
CheckAppUpdate();
// #endif
// #ifdef MP-WEIXIN
CheckWxUpdate(true);
// #endif
// #ifndef APP-PLUS|| MP-WEIXIN
uni.showToast({
icon: 'none',
title: '版本无需更新!'
});
// #endif
break;
}
} else if (type == 'page') {
uni.navigateTo({
url: path
})
}
},
//
fnToNavPage(item) {
//
if (this.$utils.checkIsUrl(item.path)) {
uni.navigateTo({
url: '/pagesC/website/website?data=' +
JSON.stringify({
title: item.text || this.$haloConfig.title,
url: item.path
})
});
return;
}
switch (item.type) {
case 'tabbar':
uni.switchTab({
url: item.path
});
break;
case 'page':
uni.navigateTo({
url: item.path
});
break;
}
},
fnOnToAdTest(path) {
uni.navigateTo({
url: path
});
}
}
};
</script>
<style scoped lang="scss">
.app-page {
width: 100vw;
min-height: 100vh;
}
.blogger-info {
position: relative;
width: 100%;
height: 600rpx;
background-size: cover;
background-repeat: no-repeat;
&:before {
content: '';
width: 100%;
height: 100%;
position: absolute;
background-color: rgba(0, 0, 0, 0.3);
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAKUlEQVQImU3IMREAIAgAwJfNkQCEsH8cijjpMf6vnXlQaIiJFx+omEBfmqIEZLe2jzcAAAAASUVORK5CYII=);
z-index: 0;
}
.avatar {
position: absolute;
top: 200rpx;
left: 50%;
transform: translateX(-50%);
width: 130rpx;
height: 130rpx;
border-radius: 50%;
border: 6rpx solid #ffffff;
}
.profile {
width: 100%;
position: absolute;
top: 340rpx;
left: 0;
z-index: 6;
color: #fff;
text-align: center;
}
.gif-wave {
position: absolute;
width: 100%;
bottom: 0;
left: 0;
z-index: 99;
mix-blend-mode: screen;
height: 100rpx;
}
}
.profile-card {
position: relative;
background-color: #fff;
overflow: hidden;
&_label {
width: 120rpx;
position: absolute;
top: 8rpx;
left: -36rpx;
transform: rotateZ(-45deg);
text-align: center;
color: #ffffff;
}
.left {
width: 260rpx;
.avatar {
width: 130rpx;
height: 130rpx;
border-radius: 50%;
}
}
.right {
width: 0;
flex-grow: 1;
.photos {
&-img {
width: 130rpx;
height: 130rpx;
}
}
.photos-img + .photos-img {
margin-left: 12rpx;
}
}
}
.statistics-wrap {
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
border-radius: 0rpx 0rpx 24rpx 24rpx;
overflow: hidden;
.statistics {
&.has-solid {
.item + .item {
border-left: 2rpx solid #fafafa;
}
}
&.solid-top {
position: relative;
&:before {
content: '';
position: absolute;
top: 0;
left: 36rpx;
right: 36rpx;
height: 2rpx;
background-color: #fafafa;
}
}
}
}
.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);
}
.icon {
border-radius: 50%;
font-size: 80rpx;
}
}
.nav-wrap {
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
background-color: #fff;
}
.copyright {
color: #c0c4c7;
}
.right-value-btn {
background-color: transparent;
border: none;
padding: 0;
margin: 0;
font-size: 24rpx;
color: #c0c4c7;
border-radius: 0;
line-height: initial;
&::after {
border: none;
border-radius: 0;
transform: initial;
}
}
</style>
-258
View File
@@ -1,258 +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" 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 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: 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();
},
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);
this.loading = 'success';
this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
//
this.hasNext = res.hasNext;
const tempItems = res.items.map(item => {
item.spec.cover = this.$utils.checkThumbnailUrl(item.spec.cover, true)
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)
})
},
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;
display: flex;
flex-direction: column;
padding: 24rpx 0;
}
.auditModeEnabled {
width: 100%;
height: 80vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading-wrap {
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>

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