mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-07-27 04:20:43 +08:00
Compare commits
10 Commits
main
...
759373e327
| Author | SHA1 | Date | |
|---|---|---|---|
| 759373e327 | |||
| 7d74a40f51 | |||
| 50cad268d3 | |||
| 8510b71ff6 | |||
| a012a57cf0 | |||
| d8ad0772bf | |||
| f105dedc23 | |||
| c847dfb1c4 | |||
| 862d176593 | |||
| 3945ee611d |
@@ -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).
|
||||||
@@ -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": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['@commitlint/config-conventional'],
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
---
|
||||||
@@ -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: 开发工作流程和最佳实践指南
|
||||||
|
---
|
||||||
@@ -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` - 构建生产版本
|
||||||
@@ -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
|
||||||
|
---
|
||||||
@@ -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
|
||||||
|
---
|
||||||
@@ -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
|
||||||
|
---
|
||||||
@@ -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
-4
@@ -1,5 +1,48 @@
|
|||||||
node_modules/
|
# Logs
|
||||||
unpackage/
|
logs
|
||||||
package-lock.json
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
.trae
|
*.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
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
npx --no-install commitlint --edit "$1"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
npx lint-staged --allow-empty
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
scene: git_message
|
||||||
|
---
|
||||||
|
|
||||||
|
在此处编写规则,自定义 AI 生成提交信息的风格。
|
||||||
|
|
||||||
|
使用中文编写提交信息
|
||||||
@@ -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 规范
|
||||||
|
- 注意页面栈和导航管理
|
||||||
Vendored
+15
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+107
@@ -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"
|
||||||
|
}
|
||||||
Vendored
+80
@@ -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"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<script>
|
|
||||||
import HaloTokenConfig from '@/config/uhalo.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>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# 更新日志
|
|
||||||
|
|
||||||
## 暂无更新记录
|
|
||||||
@@ -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/*
|
|
||||||
```
|
|
||||||
@@ -1,661 +1,21 @@
|
|||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
MIT License
|
||||||
Version 3, 19 November 2007
|
|
||||||
|
Copyright (c) 2025 菲鸽
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this license document, but changing it is not allowed.
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
Preamble
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
The GNU Affero General Public License is a free, copyleft license for
|
furnished to do so, subject to the following conditions:
|
||||||
software and other kinds of works, specifically designed to ensure
|
|
||||||
cooperation with the community in the case of network server software.
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
The licenses for most software and other practical works are designed
|
|
||||||
to take away your freedom to share and change the works. By contrast,
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
our General Public Licenses are intended to guarantee your freedom to
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
share and change all versions of a program--to make sure it remains free
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
software for all its users.
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
When we speak of free software, we are referring to freedom, not
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
SOFTWARE.
|
||||||
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/>.
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</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>
|
<p align="center"><b style="font-size: 20px;color: #10B5FB">正式支持 Halo2.X 版本,免费开源</b></p>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
import {
|
|
||||||
getPersonalToken
|
|
||||||
} from '@/utils/token.js'
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
import qs from 'qs'
|
|
||||||
|
|
||||||
import {
|
|
||||||
getAppConfigs
|
|
||||||
} from '@/config/index.js'
|
|
||||||
import { getNologinEmail, getOpenid } from "@/utils/auth";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getPostList: (params) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/posts`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
getPostByName: (name) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/posts/${name}`, {}, {
|
|
||||||
header: {
|
|
||||||
'Wechat-Session-Id': getOpenid(),
|
|
||||||
'nologin-email': getNologinEmail()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
getPostListByKeyword: (params) => {
|
|
||||||
return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/indices/-/search`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
getCategoryList: (params) => {
|
|
||||||
const param = qs.stringify(params, {
|
|
||||||
allowDots: true,
|
|
||||||
encodeValuesOnly: true,
|
|
||||||
skipNulls: true,
|
|
||||||
encode: false,
|
|
||||||
arrayFormat: 'repeat'
|
|
||||||
})
|
|
||||||
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/categories?${param}`, {})
|
|
||||||
},
|
|
||||||
|
|
||||||
getCategoryPostList: (name, params) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/categories/${name}/posts`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
getPostCommentList: (params) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/comments`, 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)
|
|
||||||
},
|
|
||||||
|
|
||||||
getTagList: (params) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/tags`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
getPostByTagName: (tagName, params) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.content.halo.run/v1alpha1/tags/${tagName}/posts`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
getMomentList: (params) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.moment.halo.run/v1alpha1/moments`, params, {
|
|
||||||
custom: {
|
|
||||||
personalToken: getPersonalToken()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
getMomentByName: (name) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.moment.halo.run/v1alpha1/moments/${name}`, {}, {
|
|
||||||
custom: {
|
|
||||||
personalToken: getPersonalToken()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
getBlogStatistics: () => {
|
|
||||||
return HttpHandler.Get(`/apis/api.halo.run/v1alpha1/stats/-`, {})
|
|
||||||
},
|
|
||||||
|
|
||||||
getPhotoGroupList: (params) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.photo.halo.run/v1alpha1/photogroups`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
getPhotoListByGroupName: (params) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.photo.halo.run/v1alpha1/photos`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
getFriendLinkGroupList: (params) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.link.halo.run/v1alpha1/linkgroups`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
getFriendLinkList: (params) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.link.halo.run/v1alpha1/links`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
requestRestrictReadCheck: (restrictType, code, keyId) => {
|
|
||||||
const params = {
|
|
||||||
code: code,
|
|
||||||
templateType: 'post',
|
|
||||||
restrictType: restrictType,
|
|
||||||
keyId: keyId
|
|
||||||
}
|
|
||||||
return HttpHandler.Post(`/apis/tools.muyin.site/v1alpha1/restrict-read/check`, params, {
|
|
||||||
header: {
|
|
||||||
'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
|
|
||||||
'Wechat-Session-Id': getOpenid(),
|
|
||||||
'nologin-email': getNologinEmail()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
createVerificationCode: () => {
|
|
||||||
return HttpHandler.Get(`/apis/tools.muyin.site/v1alpha1/restrict-read/create`, null, {
|
|
||||||
header: {
|
|
||||||
'Authorization': getAppConfigs().pluginConfig.toolsPlugin?.Authorization,
|
|
||||||
'Wechat-Session-Id': getOpenid(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
submitLink(form) {
|
|
||||||
return HttpHandler.Post(`/apis/linkssubmit.muyin.site/v1alpha1/submit`, form, {
|
|
||||||
header: {
|
|
||||||
'Authorization': getAppConfigs().pluginConfig.linksSubmitPlugin?.Authorization,
|
|
||||||
'Wechat-Session-Id': getOpenid(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
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, {})
|
|
||||||
},
|
|
||||||
|
|
||||||
submitUpvote(data) {
|
|
||||||
return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/trackers/upvote`, data, {})
|
|
||||||
},
|
|
||||||
|
|
||||||
getVoteList: (params) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.vote.kunkunyu.com/v1alpha1/votes`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
getVoteDetail: (name) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.vote.kunkunyu.com/v1alpha1/votes/${name}/detail`, {})
|
|
||||||
},
|
|
||||||
|
|
||||||
getVoteUserList: (name) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.vote.kunkunyu.com/v1alpha1/votes/${name}/user-list`, {})
|
|
||||||
},
|
|
||||||
|
|
||||||
submitVote: (name, data, canAnonymously = true) => {
|
|
||||||
return HttpHandler.Post(`/apis/api.vote.kunkunyu.com/v1alpha1/votes/${name}/submit`, data, {
|
|
||||||
custom: {
|
|
||||||
personalToken: canAnonymously ? undefined : getPersonalToken()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
checkPluginAvailable: (name) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.plugin.halo.run/v1alpha1/plugins/${name}/available`, {})
|
|
||||||
},
|
|
||||||
|
|
||||||
getDoubanDetail: (url) => {
|
|
||||||
return HttpHandler.Get(`/apis/api.douban.moony.la/v1alpha1/doubanmovies/-/getDoubanDetail`, { url })
|
|
||||||
},
|
|
||||||
|
|
||||||
postTrackersCounter: (data) => {
|
|
||||||
return HttpHandler.Post(`/apis/api.halo.run/v1alpha1/trackers/counter`, data)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -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`, {})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* Halo 数据看板插件
|
|
||||||
* @link https://www.halo.run/store/apps/app-rtnbbgfk
|
|
||||||
*/
|
|
||||||
import {
|
|
||||||
getPersonalToken
|
|
||||||
} from '@/utils/token.js'
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
import qs from 'qs'
|
|
||||||
|
|
||||||
import {
|
|
||||||
getAppConfigs
|
|
||||||
} from '@/config/index.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取图表统计数据
|
|
||||||
* @description - 标签、分类、文章发布趋势、评论活跃用户、获取热门文章top10
|
|
||||||
*/
|
|
||||||
getChartData: () => {
|
|
||||||
return HttpHandler.Get(`/apis/api.data.statistics.xhhao.com/v1alpha1/chart/data`,{})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 获取Github配置信息
|
|
||||||
*/
|
|
||||||
getGithubConfig:()=>{
|
|
||||||
return HttpHandler.Get('/apis/api.data.statistics.xhhao.com/v1alpha1/github/config',{})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 获取 Uptime Kuma 状态页面数据
|
|
||||||
*/
|
|
||||||
getUptimeStatus:()=>{
|
|
||||||
return HttpHandler.Get('/apis/api.data.statistics.xhhao.com/v1alpha1/github/config',{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* 功能:请求工具
|
|
||||||
* 作者:小莫唐尼
|
|
||||||
* 邮箱:studio@925i.cn
|
|
||||||
* 时间:2022年07月21日 18:58:03
|
|
||||||
* 版本:v0.1.0
|
|
||||||
* 修改记录:
|
|
||||||
* 修改内容:
|
|
||||||
* 修改人员:
|
|
||||||
* 修改时间:
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HaloTokenConfig from '@/config/uhalo.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.replace(/\/+$/, "");
|
|
||||||
|
|
||||||
config.header = {
|
|
||||||
...config.header,
|
|
||||||
ContentType: 'application/json',
|
|
||||||
dataType: 'json'
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
})
|
|
||||||
|
|
||||||
setInterceptors(http)
|
|
||||||
|
|
||||||
export {
|
|
||||||
http
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
/**
|
|
||||||
* 功能:http拦截
|
|
||||||
* 作者:小莫唐尼
|
|
||||||
* 邮箱:studio@925i.cn
|
|
||||||
* 时间:2022年07月21日 19:02:14
|
|
||||||
* 版本:v0.1.0
|
|
||||||
* 修改记录:
|
|
||||||
* 修改内容:
|
|
||||||
* 修改人员:
|
|
||||||
* 修改时间:
|
|
||||||
*/
|
|
||||||
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.personalToken) {
|
|
||||||
config.header['Authorization'] = `Bearer ${config.custom.personalToken}`
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}, (config) => {
|
|
||||||
// 可使用async await 做异步操作
|
|
||||||
return Promise.reject(config);
|
|
||||||
});
|
|
||||||
|
|
||||||
http.interceptors.response.use((response) => {
|
|
||||||
/* 对响应成功做点什么 可使用async await 做异步操作*/
|
|
||||||
// if (response.data.code !== 200) { // 服务端返回的状态码不等于200,则reject()
|
|
||||||
// return Promise.reject(response) // return Promise.reject 可使promise状态进入catch
|
|
||||||
// if (response.config.custom.verification) { // 演示自定义参数的作用
|
|
||||||
// return response.data
|
|
||||||
// }
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -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
@@ -1,100 +0,0 @@
|
|||||||
/**
|
|
||||||
* markdown配置
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {getAppConfigs} from '@/config/index.js'
|
|
||||||
import HaloTokenConfig from '@/config/uhalo.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),
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/**
|
|
||||||
* 功能:插件检查
|
|
||||||
*/
|
|
||||||
import {
|
|
||||||
NeedPluginIds,
|
|
||||||
NeedPlugins,
|
|
||||||
checkNeedPluginAvailable
|
|
||||||
} from "@/utils/plugin.js"
|
|
||||||
|
|
||||||
const HaloPluginAvailableMixin = {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
NeedPluginIds,
|
|
||||||
NeedPlugins,
|
|
||||||
uniHaloPluginAvailableError: "",
|
|
||||||
uniHaloPluginAvailable: true,
|
|
||||||
uniHaloPluginPageClass: "",
|
|
||||||
uniHaloPluginId: "", // 当前需要的插件
|
|
||||||
uniHaloPluginInfo: "" // 当前插件信息
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
uniHaloPluginAvailable: {
|
|
||||||
immediate: true,
|
|
||||||
handler(val) {
|
|
||||||
if (val) {
|
|
||||||
this.uniHaloPluginPageClass = ""
|
|
||||||
} else {
|
|
||||||
this.uniHaloPluginPageClass = "box-border items-center justify-center"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/** 设置插件ID */
|
|
||||||
setPluginId(pluginId) {
|
|
||||||
this.uniHaloPluginId = pluginId
|
|
||||||
this.uniHaloPluginInfo = NeedPlugins.get(pluginId)
|
|
||||||
},
|
|
||||||
/** 检查插件状态 */
|
|
||||||
async checkPluginAvailable(pluginId) {
|
|
||||||
pluginId = pluginId ?? this.uniHaloPluginId
|
|
||||||
if (!pluginId) return false;
|
|
||||||
const available = await checkNeedPluginAvailable(pluginId)
|
|
||||||
this.uniHaloPluginAvailable = available
|
|
||||||
return available
|
|
||||||
},
|
|
||||||
/** 设置错误信息 */
|
|
||||||
setPluginError(text) {
|
|
||||||
this.uniHaloPluginAvailableError = text
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HaloPluginAvailableMixin;
|
|
||||||
@@ -1,349 +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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 文本省略样式 结束 */
|
|
||||||
|
|
||||||
.box-border{
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义尺寸变量(单位:rpx)
|
|
||||||
$spacing-sizes: (
|
|
||||||
0: 0,
|
|
||||||
2: 4rpx,
|
|
||||||
4: 8rpx,
|
|
||||||
8: 16rpx,
|
|
||||||
12: 24rpx,
|
|
||||||
24: 48rpx,
|
|
||||||
48: 96rpx
|
|
||||||
);
|
|
||||||
|
|
||||||
// 内边距类
|
|
||||||
@each $name, $size in $spacing-sizes {
|
|
||||||
// 全方向内边距:p-{size}
|
|
||||||
.uh-p-#{$name} {
|
|
||||||
padding: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 水平方向内边距:px-{size}
|
|
||||||
.uh-px-#{$name} {
|
|
||||||
padding-left: $size !important;
|
|
||||||
padding-right: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 垂直方向内边距:py-{size}
|
|
||||||
.uh-py-#{$name} {
|
|
||||||
padding-top: $size !important;
|
|
||||||
padding-bottom: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上内边距:pt-{size}
|
|
||||||
.uh-pt-#{$name} {
|
|
||||||
padding-top: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 右内边距:pr-{size}
|
|
||||||
.uh-pr-#{$name} {
|
|
||||||
padding-right: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下内边距:pb-{size}
|
|
||||||
.uh-pb-#{$name} {
|
|
||||||
padding-bottom: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 左内边距:pl-{size}
|
|
||||||
.uh-pl-#{$name} {
|
|
||||||
padding-left: $size !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//外边距工具类
|
|
||||||
@each $name, $size in $spacing-sizes {
|
|
||||||
// 全方向外边距:m-{size}
|
|
||||||
.uh-m-#{$name} {
|
|
||||||
margin: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 水平方向外边距:mx-{size}
|
|
||||||
.uh-mx-#{$name} {
|
|
||||||
margin-left: $size !important;
|
|
||||||
margin-right: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 垂直方向外边距:my-{size}
|
|
||||||
.uh-my-#{$name} {
|
|
||||||
margin-top: $size !important;
|
|
||||||
margin-bottom: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上外边距:mt-{size}
|
|
||||||
.uh-mt-#{$name} {
|
|
||||||
margin-top: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 右外边距:mr-{size}
|
|
||||||
.uh-mr-#{$name} {
|
|
||||||
margin-right: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下外边距:mb-{size}
|
|
||||||
.uh-mb-#{$name} {
|
|
||||||
margin-bottom: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 左外边距:ml-{size}
|
|
||||||
.uh-ml-#{$name} {
|
|
||||||
margin-left: $size !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// gap 类
|
|
||||||
@each $name, $size in $spacing-sizes {
|
|
||||||
// 全方向gap:gap-{size}
|
|
||||||
.uh-gap-#{$name} {
|
|
||||||
gap: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 水平方向gap:gap-x-{size}
|
|
||||||
.uh-gap-x-#{$name} {
|
|
||||||
column-gap: $size !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 垂直方向gap:gap-y-{size}
|
|
||||||
.uh-gap-y-#{$name} {
|
|
||||||
row-gap: $size !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.w-full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-full {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-col {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-1 {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-shrink-0 {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-center {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-start {
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-end {
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-between {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-around {
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-end {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-start {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-center {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-break {
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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:22rpx 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,231 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="card" :class="[loading]">
|
|
||||||
<view v-if="loading !== 'success'" class="card-error" @click="fnGetData()">
|
|
||||||
{{ loadingText }}
|
|
||||||
</view>
|
|
||||||
<template v-else>
|
|
||||||
<view class="tag">豆瓣</view>
|
|
||||||
<view class="flex w-full">
|
|
||||||
<view v-if="posterEmpty" class="poster round-2">无封面</view>
|
|
||||||
<image v-else class="poster round-2" :src="poster" mode="aspectFill" @error="onPosterError"></image>
|
|
||||||
<view class="box">
|
|
||||||
<view class="title text-overflow">{{ detail.spec.name }}</view>
|
|
||||||
<view class="flex" style="align-items: center; margin-top: 12rpx">
|
|
||||||
<text class="text-size-s">评分:</text>
|
|
||||||
<tm-rate v-model="detail.spec.score / 2" color="orange" size="24" :margin="2" :num="5"></tm-rate>
|
|
||||||
<text class="text-size-s" style="margin-left: 4rpx">{{ detail.spec.score }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="content text-overflow-2">{{ detail.spec.cardSubtitle }}</view>
|
|
||||||
<view class="flex flex-wrap" style="margin-left: -10rpx">
|
|
||||||
<tm-tags color="orange" :shadow="0" size="s" model="fill">{{ types[detail.spec.type] }}</tm-tags>
|
|
||||||
<tm-tags v-for="(gen, genIndex) in detail.spec.genres" :key="genIndex" color="light-blue" :shadow="0" size="s" model="fill">{{ gen }}</tm-tags>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- 扩展内容 -->
|
|
||||||
<view class="btn-group">
|
|
||||||
<tm-button theme="bg-gradient-orange-accent" icon="icon-copy" :shadow="0" :dense="true" size="m" @click="copy('post')">原文地址</tm-button>
|
|
||||||
<tm-button theme="bg-gradient-light-blue-accent" icon="icon-copy" :shadow="0" :dense="true" size="m" @click="copy('douban')">豆瓣地址</tm-button>
|
|
||||||
<tm-button theme="bg-gradient-light-blue-accent" icon="icon-copy" :shadow="0" :dense="true" size="m" @click="copy('info')">资源信息</tm-button>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
|
||||||
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
|
|
||||||
import tmRate from '@/tm-vuetify/components/tm-rate/tm-rate.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ArticleDouban',
|
|
||||||
components: {
|
|
||||||
tmButton,
|
|
||||||
tmTags,
|
|
||||||
tmRate
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
url: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
index: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
article: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: 'loading',
|
|
||||||
loadingText: '加载中,请稍等...',
|
|
||||||
detail: null,
|
|
||||||
types: {
|
|
||||||
movie: '电影',
|
|
||||||
book: '图书',
|
|
||||||
music: '音乐',
|
|
||||||
game: '游戏',
|
|
||||||
drama: '舞台剧'
|
|
||||||
},
|
|
||||||
poster: '',
|
|
||||||
posterEmpty: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.fnGetData();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onPosterError() {
|
|
||||||
if (!this.article.spec.cover) {
|
|
||||||
this.poster = '';
|
|
||||||
this.posterEmpty = true;
|
|
||||||
} else {
|
|
||||||
this.poster = this.$utils.checkImageUrl(this.article.spec.cover);
|
|
||||||
this.posterEmpty = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fnGetData() {
|
|
||||||
this.loadingText = '加载中,请稍等...';
|
|
||||||
this.loading = 'loading';
|
|
||||||
this.$httpApi.v2
|
|
||||||
.getDoubanDetail(this.url)
|
|
||||||
.then((res) => {
|
|
||||||
this.detail = res;
|
|
||||||
this.poster = res.spec.poster;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.loading = 'success';
|
|
||||||
}, 200);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
this.loading = 'error';
|
|
||||||
this.loadingText = '豆瓣内容加载失败,点击重试';
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
showToast(content) {
|
|
||||||
uni.showToast({
|
|
||||||
icon: 'none',
|
|
||||||
title: content,
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
},
|
|
||||||
copy(type) {
|
|
||||||
if (type === 'post') {
|
|
||||||
const articleUrl = this.$baseApiUrl + (this.article?.status?.permalink ?? '');
|
|
||||||
this.$utils.copyText(articleUrl, '文章原文地址复制成功');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type === 'douban') {
|
|
||||||
this.$utils.copyText(this.detail?.spec.link, '豆瓣资源地址复制成功');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type === 'info') {
|
|
||||||
const content = `名称:${this.detail?.spec.name}丨其他:${this.detail?.spec.cardSubtitle}丨标签:${this.detail?.spec.genres.join('/')}丨时间:${
|
|
||||||
this.detail?.spec.pubdate
|
|
||||||
}丨评分:${this.detail?.spec.score}分丨链接:${this.detail?.spec.link}`;
|
|
||||||
this.$utils.copyText(content, '资源信息复制成功');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.w-full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wp-50 {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 24rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
background-color: #ffff;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
|
|
||||||
&.error {
|
|
||||||
padding: 0;
|
|
||||||
border-style: dashed;
|
|
||||||
border-color: #e88080;
|
|
||||||
color: #e88080;
|
|
||||||
background-color: rgba(232, 128, 128, 0.075);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.loading {
|
|
||||||
padding: 0;
|
|
||||||
border-style: dashed;
|
|
||||||
border-color: rgba(3, 174, 252, 1);
|
|
||||||
color: rgba(3, 174, 252, 1);
|
|
||||||
background-color: rgba(3, 174, 252, 0.075);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-error {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 50rpx 24rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.poster {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 180rpx;
|
|
||||||
height: 220rpx;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background-color: #eee;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
flex-grow: 1;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 26rpx;
|
|
||||||
padding-left: 24rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin-top: 12rpx;
|
|
||||||
line-height: 36rpx;
|
|
||||||
color: rgba(0, 0, 0, 0.85);
|
|
||||||
}
|
|
||||||
.tag {
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
font-size: 24rpx;
|
|
||||||
padding: 2rpx 12rpx;
|
|
||||||
background-color: #f5c618;
|
|
||||||
border-radius: 0 6rpx 0 12rpx;
|
|
||||||
}
|
|
||||||
.btn-group {
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 22rpx;
|
|
||||||
gap: 0 22rpx;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="article-min-card" @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>
|
|
||||||
</view>
|
|
||||||
<view class="right">
|
|
||||||
<view class="title text-overflow">{{ article.spec.title }}</view>
|
|
||||||
<view class="content text-overflow">{{ 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>
|
|
||||||
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>
|
|
||||||
@@ -1,569 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="vote-card" :class="[loading]">
|
|
||||||
<view v-if="loading !=='success'" class="vote-error" @click="fnGetData()">
|
|
||||||
{{loadingText}}
|
|
||||||
</view>
|
|
||||||
<template v-else>
|
|
||||||
<view class="vote-card-head flex flex-col">
|
|
||||||
<view class="flex justify-between">
|
|
||||||
<view class="flex">
|
|
||||||
<tm-tags color="orange" style="min-width:40rpx;" :shadow="0" rounded size="s"
|
|
||||||
model="fill">{{ index + 1 }}</tm-tags>
|
|
||||||
<tm-tags color="light-blue" :shadow="0" rounded size="s"
|
|
||||||
model="fill">{{ vote.spec._uh_type }}</tm-tags>
|
|
||||||
<tm-tags :color="vote.spec._uh_state.color" size="s" rounded :shadow="0"
|
|
||||||
model="fill">{{vote.spec._uh_state.state}}</tm-tags>
|
|
||||||
</view>
|
|
||||||
<view class="flex-shrink">
|
|
||||||
<tm-button theme="light-blue" :shadow="0" dense size="s"
|
|
||||||
@click="handleToVoteDetail(vote)">查看投票详情</tm-button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="title">
|
|
||||||
{{ vote.spec.title }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="vote-card-body">
|
|
||||||
|
|
||||||
<view v-if="vote.spec.remark" class="remark text-size-s">
|
|
||||||
{{vote.spec.remark}}
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!-- 单选 -->
|
|
||||||
<view v-if="vote.spec.type==='single'" class="single">
|
|
||||||
<view class="w-full flex flex-col uh-gap-8">
|
|
||||||
<template v-if="vote.spec.isVoted || vote.spec.hasEnded">
|
|
||||||
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
|
||||||
class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
|
|
||||||
'--percent': option._uh_percent + '%'
|
|
||||||
}">
|
|
||||||
<view class="is-voted-item-content flex w-full flex-between uh-gap-4">
|
|
||||||
<view class="text-align-left flex-1 text-break">
|
|
||||||
{{option.title }}
|
|
||||||
</view>
|
|
||||||
<view class="flex-shrink ">
|
|
||||||
{{option._uh_percent }}%
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
|
||||||
class="vote-select-option flex-1 w-full text-break"
|
|
||||||
:class="[option.checked?'light-blue':'grey-lighten-3']"
|
|
||||||
@click="handleSelectSingleOption(option)">
|
|
||||||
{{option.title }}
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 多选 -->
|
|
||||||
<view v-else-if="vote.spec.type==='multiple'" class="multiple">
|
|
||||||
<view class="w-full flex flex-col uh-gap-8">
|
|
||||||
<template v-if="vote.spec.isVoted || vote.spec.hasEnded">
|
|
||||||
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
|
||||||
class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
|
|
||||||
'--percent': option._uh_percent + '%'
|
|
||||||
}">
|
|
||||||
<view class="is-voted-item-content flex w-full flex-between uh-gap-4">
|
|
||||||
<view class="text-align-left flex-1 text-break">
|
|
||||||
{{option.title }}
|
|
||||||
</view>
|
|
||||||
<view class="flex-shrink ">
|
|
||||||
{{option._uh_percent }}%
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
|
||||||
class="vote-select-option flex-1 w-full text-break"
|
|
||||||
:class="[option.checked?'light-blue':'grey-lighten-3']"
|
|
||||||
@click="handleSelectCheckboxOption(option)">
|
|
||||||
{{option.title }}
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- PK -->
|
|
||||||
<view v-else-if="vote.spec.type==='pk'" class="pk">
|
|
||||||
<view class="pk-container">
|
|
||||||
<view class="radio-item" v-for="(option,optionIndex) in vote.spec.options"
|
|
||||||
:key="optionIndex" :class="[optionIndex==0?'radio-left':'radio-right']"
|
|
||||||
:style="{width:option._uh_percent + '%'}">
|
|
||||||
<view class="option-item"
|
|
||||||
:class="[optionIndex==0?'option-item-left':'option-item-right']">
|
|
||||||
{{option._uh_percent }}%
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="option-foot w-full flex flex-between uh-mt-12">
|
|
||||||
<view class="w-full flex flex-col uh-gap-8">
|
|
||||||
<template v-if="vote.spec.isVoted || vote.spec.hasEnded">
|
|
||||||
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
|
||||||
class="is-voted-item" :class="[option.checked?'selected':'']" :style="{
|
|
||||||
'--percent': option._uh_percent + '%'
|
|
||||||
}">
|
|
||||||
<view class="is-voted-item-content flex w-full flex-between uh-gap-4">
|
|
||||||
<view class="text-align-left flex-1 text-break">
|
|
||||||
{{option.title }}
|
|
||||||
</view>
|
|
||||||
<view class="flex-shrink ">
|
|
||||||
{{option._uh_percent }}%
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<view v-for="(option,optionIndex) in vote.spec.options" :key="optionIndex"
|
|
||||||
class="vote-select-option flex-1 w-full text-break"
|
|
||||||
:class="[option.checked?'light-blue':'grey-lighten-3']"
|
|
||||||
@click="handleSelectSingleOption(option)">
|
|
||||||
选项{{ optionIndex+1}}:{{option.title }}
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
<view class="vote-card-foot flex flex-between">
|
|
||||||
<view class="left flex">
|
|
||||||
<tm-tags v-if="vote.spec.timeLimit==='permanent'" color="grey-darken-2" rounded size="s"
|
|
||||||
model="text">结束:永久有效 </tm-tags>
|
|
||||||
<tm-tags v-else color="grey-darken-2" rounded size="s" model="text">
|
|
||||||
<template
|
|
||||||
v-if="vote.spec._state=='未开始'">开始:{{ {d: vote.spec.startDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
|
|
||||||
</template>
|
|
||||||
<template v-else>结束:{{ {d: vote.spec.endDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
|
|
||||||
</template>
|
|
||||||
</tm-tags>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="right flex flex-end">
|
|
||||||
<tm-tags color="grey-darken-2" rounded size="s" model="text">{{ vote.stats.voteCount }}
|
|
||||||
人已参与</tm-tags>
|
|
||||||
<tm-tags v-if="vote.spec.isVoted" color="blue" rounded size="s" model="text">已投票</tm-tags>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="submitForm.voteData.length!==0" class="box-border uh-mt-12 w-full uh-px-2">
|
|
||||||
<tm-button v-if="fnCalcIsVoted()" theme="white" text :block="true" class="w-full">您已参与投票</tm-button>
|
|
||||||
<tm-button v-else-if="vote.spec._uh_state.state==='未开始'" theme="orange" text class="w-full" :height="72"
|
|
||||||
:block="true" @click="handleSubmitTip('投票未开始')">投票未开始</tm-button>
|
|
||||||
<tm-button v-else-if="vote.spec._uh_state.state==='已结束'" theme="red" text class="w-full" :height="72"
|
|
||||||
:block="true" @click="handleSubmitTip('投票已结束')">投票已结束</tm-button>
|
|
||||||
<tm-button v-else-if="!vote.spec.canAnonymously" theme="red" :shadow="0" class="w-full" :height="72"
|
|
||||||
text :block="true" @click="handleSubmit()">不支持匿名投票</tm-button>
|
|
||||||
<tm-button v-else-if="submitForm.voteData.length===0" theme="white" text class="w-full" :height="72"
|
|
||||||
:block="true" @click="handleSubmitTip('请选择选项')">提交投票(请选择选项)</tm-button>
|
|
||||||
<tm-button v-else theme="light-blue" class="w-full" :height="72" :block="true" :loading="submitLoading"
|
|
||||||
:disabled="submitLoading" @click="handleSubmit()">提交投票</tm-button>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
|
||||||
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
|
|
||||||
import {
|
|
||||||
VOTE_TYPES,
|
|
||||||
calcVoteState,
|
|
||||||
calcVotePercent,
|
|
||||||
voteCacheUtil
|
|
||||||
} from '@/utils/vote.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "ArticleVote",
|
|
||||||
components: {
|
|
||||||
tmButton,
|
|
||||||
tmTags
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
voteId: {
|
|
||||||
type: String,
|
|
||||||
default: ""
|
|
||||||
},
|
|
||||||
index: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
article: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: "loading",
|
|
||||||
loadingText: "加载中,请稍等...",
|
|
||||||
detail: null,
|
|
||||||
vote: null,
|
|
||||||
submitForm: {
|
|
||||||
voteData: []
|
|
||||||
},
|
|
||||||
submitLoading: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.fnGetData();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fnGetData() {
|
|
||||||
this.loadingText = "加载中,请稍等...";
|
|
||||||
this.loading = "loading";
|
|
||||||
this.$httpApi.v2
|
|
||||||
.getVoteDetail(this.voteId)
|
|
||||||
.then(res => {
|
|
||||||
this.pageTitle = "投票详情" + `(${VOTE_TYPES[res.vote.spec.type]})`
|
|
||||||
|
|
||||||
const tempVoteRes = res;
|
|
||||||
|
|
||||||
tempVoteRes.vote.spec.isVoted = this.fnCalcIsVoted()
|
|
||||||
tempVoteRes.vote.spec.disabled = this.fnCalcIsVoted()
|
|
||||||
tempVoteRes.vote.spec._uh_state = calcVoteState(tempVoteRes.vote)
|
|
||||||
tempVoteRes.vote.spec._uh_type = VOTE_TYPES[tempVoteRes.vote.spec.type]
|
|
||||||
|
|
||||||
tempVoteRes.vote.spec.options.map((option, index) => {
|
|
||||||
option.value = option.id
|
|
||||||
option.label = option.title
|
|
||||||
option.isVoted = this.fnCalcIsVoted()
|
|
||||||
option.checked = this.fnCalcIsChecked(option)
|
|
||||||
option._uh_percent = calcVotePercent(tempVoteRes.vote, option);
|
|
||||||
option.dataStr = JSON.stringify(option)
|
|
||||||
|
|
||||||
return option
|
|
||||||
})
|
|
||||||
|
|
||||||
this.vote = tempVoteRes.vote
|
|
||||||
this.detail = tempVoteRes;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.loading = 'success';
|
|
||||||
}, 200);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
this.loading = 'error';
|
|
||||||
this.loadingText = "投票内容加载失败,点击重试"
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
fnCalcIsVoted() {
|
|
||||||
return voteCacheUtil.has(this.voteId)
|
|
||||||
},
|
|
||||||
fnCalcIsChecked(option) {
|
|
||||||
const data = voteCacheUtil.get(this.voteId)
|
|
||||||
if (!data) return false;
|
|
||||||
const checked = data.selected.includes(option.id)
|
|
||||||
return checked
|
|
||||||
},
|
|
||||||
formatJsonStr(jsonStr) {
|
|
||||||
return jsonStr ? JSON.parse(jsonStr) : {}
|
|
||||||
},
|
|
||||||
handleSubmitTip(text) {
|
|
||||||
uni.showToast({
|
|
||||||
icon: "none",
|
|
||||||
title: text
|
|
||||||
})
|
|
||||||
},
|
|
||||||
handleSubmit() {
|
|
||||||
if (!this.vote.spec.canAnonymously) {
|
|
||||||
uni.showModal({
|
|
||||||
icon: "none",
|
|
||||||
title: "提示",
|
|
||||||
content: "该投票不支持匿名,请到博主的 网站端 进行投票!",
|
|
||||||
cancelColor: "#666666",
|
|
||||||
cancelText: "关闭",
|
|
||||||
confirmText: "复制地址",
|
|
||||||
success: (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
console.log("this.article", this.article)
|
|
||||||
const articleUrl = this.$baseApiUrl + (this.article?.status?.permalink ?? "")
|
|
||||||
this.$utils.copyText(articleUrl, "原文地址复制成功")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showLoading({
|
|
||||||
title: "正在保存..."
|
|
||||||
})
|
|
||||||
this.submitLoading = true
|
|
||||||
this.$httpApi.v2
|
|
||||||
.submitVote(this.voteId, this.submitForm, this.vote.spec.canAnonymously)
|
|
||||||
.then(res => {
|
|
||||||
uni.showToast({
|
|
||||||
icon: "none",
|
|
||||||
title: "提交成功"
|
|
||||||
})
|
|
||||||
|
|
||||||
voteCacheUtil.set(this.voteId, {
|
|
||||||
selected: [...this.submitForm.voteData],
|
|
||||||
data: this.vote
|
|
||||||
})
|
|
||||||
|
|
||||||
this.fnGetData()
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
uni.showToast({
|
|
||||||
icon: "none",
|
|
||||||
title: "提交失败,请重试"
|
|
||||||
})
|
|
||||||
}).finally(() => {
|
|
||||||
this.submitLoading = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSelectSingleOption(option) {
|
|
||||||
if (this.vote.spec._uh_state.state == '未开始') {
|
|
||||||
this.showToast(`投票未开始`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.vote.spec.hasEnded) return
|
|
||||||
if (this.vote.spec.disabled) return
|
|
||||||
this.vote.spec.options.map(item => {
|
|
||||||
if (option.id == item.id) {
|
|
||||||
item.checked = true
|
|
||||||
} else {
|
|
||||||
item.checked = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.submitForm.voteData = this.vote.spec.options.filter(x => x.checked).map(item => item.id)
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSelectCheckboxOption(option) {
|
|
||||||
if (this.vote.spec._uh_state.state == '未开始') {
|
|
||||||
this.showToast(`投票未开始`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.vote.spec.hasEnded) return
|
|
||||||
if (this.vote.spec.disabled) return
|
|
||||||
|
|
||||||
const checkedList = this.vote.spec.options.filter(x => x.checked && x.id != option.id)
|
|
||||||
|
|
||||||
if (this.vote.spec.type === 'multiple' && checkedList.length >= this.vote.spec.maxVotes) {
|
|
||||||
this.showToast(`最多选择 ${this.vote.spec.maxVotes} 项`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.vote.spec.options.map(item => {
|
|
||||||
if (option.id == item.id) {
|
|
||||||
item.checked = !item.checked
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.submitForm.voteData = this.vote.spec.options.filter(x => x.checked).map(item => item.id)
|
|
||||||
},
|
|
||||||
handleToVoteDetail(vote) {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pagesA/vote-detail/vote-detail?name=${vote.metadata.name}`
|
|
||||||
});
|
|
||||||
},
|
|
||||||
showToast(content) {
|
|
||||||
uni.showToast({
|
|
||||||
icon: "none",
|
|
||||||
title: content,
|
|
||||||
mask: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.w-full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wp-50 {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 24rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
background-color: #ffff;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
|
|
||||||
&.error {
|
|
||||||
padding: 0;
|
|
||||||
border-style: dashed;
|
|
||||||
border-color: #e88080;
|
|
||||||
color: #e88080;
|
|
||||||
background-color: rgba(232, 128, 128, 0.075);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.loading {
|
|
||||||
padding: 0;
|
|
||||||
border-style: dashed;
|
|
||||||
border-color: rgba(3, 174, 252, 1);
|
|
||||||
color: rgba(3, 174, 252, 1);
|
|
||||||
background-color: rgba(3, 174, 252, 0.075);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.vote-error {
|
|
||||||
padding: 50rpx 24rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-card-head {
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
padding: 12rpx 0;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-card-body {
|
|
||||||
|
|
||||||
.remark {
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: rgba(0, 0, 0, 0.75);
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-card-foot {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-top: 6px;
|
|
||||||
margin-top: 12px;
|
|
||||||
border-top: 2rpx solid #eee;
|
|
||||||
|
|
||||||
.left {}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-voted-item {
|
|
||||||
min-height: 72rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
background-color: rgba(229, 229, 229, 0.75);
|
|
||||||
font-size: 24rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
width: var(--percent);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(208, 208, 208, 1);
|
|
||||||
z-index: 0;
|
|
||||||
border-radius: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background-color: rgba(3, 169, 244, 0.35);
|
|
||||||
color: #ffffff;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background-color: rgba(3, 169, 244, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-voted-item-content {
|
|
||||||
box-sizing: border-box;
|
|
||||||
min-height: 72rpx;
|
|
||||||
padding: 12rpx 24rpx;
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-select-option {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 20rpx 24rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
transition: all 0.1s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.single {
|
|
||||||
::v-deep {}
|
|
||||||
}
|
|
||||||
|
|
||||||
.multiple {
|
|
||||||
::v-deep {}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pk {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 12rpx;
|
|
||||||
|
|
||||||
::v-deep {
|
|
||||||
|
|
||||||
.pk-container {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-item {
|
|
||||||
flex-grow: 1;
|
|
||||||
min-width: 30% !important;
|
|
||||||
max-width: 70% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-left {}
|
|
||||||
|
|
||||||
.radio-right {}
|
|
||||||
|
|
||||||
.option-item {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
padding: 24rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-item-left {
|
|
||||||
background: linear-gradient(90deg, #3B82F6, #60A5FA);
|
|
||||||
color: white;
|
|
||||||
clip-path: polygon(0 0, calc(100% - 40rpx) 0, 100% 100%, 0 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-item-right {
|
|
||||||
background: linear-gradient(90deg, #F87171, #EF4444);
|
|
||||||
color: white;
|
|
||||||
clip-path: polygon(0 0, 100% 0, 100% 100%, 40rpx 100%);
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-foot {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 6rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #666;
|
|
||||||
|
|
||||||
.left {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-right: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-left: 24rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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.25);
|
|
||||||
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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -1,272 +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 class="refresh">
|
|
||||||
<text class="icon iconfont icon-sync-alt text-weight-s text-grey-darken-1"></text>
|
|
||||||
<text class="text-weight-s text-grey-darken-1 ml-6" style="font-size: 26rpx;" @click="fnGetData">刷新</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>
|
|
||||||
@@ -1,233 +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", {
|
|
||||||
isSubmit:false,
|
|
||||||
refresh: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleClose(refresh = false) {
|
|
||||||
this.isShow = false;
|
|
||||||
this.$emit("on-close", {
|
|
||||||
isSubmit:true,
|
|
||||||
refresh: refresh
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.title {
|
|
||||||
margin: 24rpx 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,343 +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;
|
|
||||||
background-color: rgba(0, 0, 0, 0.085);
|
|
||||||
backdrop-filter: blur(3rpx);
|
|
||||||
&.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;
|
|
||||||
}
|
|
||||||
@@ -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 text-overflow-2">{{ 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 text-overflow-2">{{ item.title }}</text>
|
|
||||||
</view>
|
|
||||||
<!-- 用户信息 -->
|
|
||||||
<view v-if="false" 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,479 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="contribution-heatmap pa-24">
|
|
||||||
<view class="header">
|
|
||||||
<view class="title">
|
|
||||||
<view v-if="false" style="color: #7e7e7f">数据范围:</view>
|
|
||||||
<view>{{ dataRangeYears }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="controls">
|
|
||||||
<tm-stepper v-model="currentYear" :width="220" :height="48" :min="0" :max="2099" :shadow="0" :round="2" color="light-blue" @change="changeYear"></tm-stepper>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="heatmap-container">
|
|
||||||
<view class="weeks">
|
|
||||||
<view class="week-label" v-for="week in weeks" :key="week">{{ week }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="heatmap-content">
|
|
||||||
<view class="months" :style="[calcContentStyle]">
|
|
||||||
<view class="month-label" v-for="month in monthLabels" :key="month.index">
|
|
||||||
{{ month.name }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="days-container" :style="[calcContentStyle]">
|
|
||||||
<view
|
|
||||||
v-for="(day, index) in displayDays"
|
|
||||||
:key="index"
|
|
||||||
class="day-cell"
|
|
||||||
:style="{ backgroundColor: getDayColor(day) }"
|
|
||||||
@click="handleDayClick(day, index)"
|
|
||||||
></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="footer">
|
|
||||||
<view class="releases-count">
|
|
||||||
<text>累计 {{ calcAllYearCount }} 篇</text>
|
|
||||||
<text>丨</text>
|
|
||||||
<text>本年 {{ calcCurrentYearCount }} 篇</text>
|
|
||||||
</view>
|
|
||||||
<view class="legend">
|
|
||||||
<text class="legend-text">少</text>
|
|
||||||
<view v-for="(color, index) in intensityColors" :key="index" class="day-cell legend-day-cell" :style="{ backgroundColor: color }"></view>
|
|
||||||
<text class="legend-text">多</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import tmStepper from '@/tm-vuetify/components/tm-stepper/tm-stepper.vue';
|
|
||||||
export default {
|
|
||||||
name: 'Heatmap',
|
|
||||||
components: {
|
|
||||||
tmStepper
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
year: {
|
|
||||||
type: Number,
|
|
||||||
default: () => new Date().getFullYear()
|
|
||||||
},
|
|
||||||
chartData: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
weeks: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
|
||||||
intensityColors: [
|
|
||||||
'#ebedf0', // 无贡献
|
|
||||||
'#dbeafe', // 1-2次
|
|
||||||
'#93c5fd', // 3-4次
|
|
||||||
'#3b82f6', // 5-6次
|
|
||||||
'#1e40af' // 7次以上
|
|
||||||
],
|
|
||||||
displayDays: [],
|
|
||||||
showTooltip: null,
|
|
||||||
currentYear: '1900',
|
|
||||||
currentYearData: [],
|
|
||||||
yearList:[]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
dataRangeYears() {
|
|
||||||
const arr = this.chartData;
|
|
||||||
const dateField = 'name';
|
|
||||||
|
|
||||||
if (!arr || !Array.isArray(arr) || arr.length === 0) {
|
|
||||||
return { minDate: null, maxDate: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提取所有有效日期
|
|
||||||
const validDates = arr
|
|
||||||
.map((item) => {
|
|
||||||
if (item && item[dateField]) {
|
|
||||||
const date = new Date(item[dateField]);
|
|
||||||
return isNaN(date.getTime()) ? null : date;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.filter((date) => date !== null);
|
|
||||||
|
|
||||||
if (validDates.length === 0) {
|
|
||||||
return { minDate: null, maxDate: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
// 找到最小和最大日期
|
|
||||||
const minDate = new Date(Math.min(...validDates.map((date) => date.getTime())));
|
|
||||||
const maxDate = new Date(Math.max(...validDates.map((date) => date.getTime())));
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
minDate: this.formatDate(minDate),
|
|
||||||
maxDate: this.formatDate(maxDate)
|
|
||||||
};
|
|
||||||
|
|
||||||
return `${result.minDate} 至 ${result.maxDate}`;
|
|
||||||
},
|
|
||||||
// 计算内容宽度
|
|
||||||
calcContentStyle() {
|
|
||||||
const rowCount = Math.ceil(this.displayDays.length / 7);
|
|
||||||
const singleWidth = 36;
|
|
||||||
return {
|
|
||||||
width: rowCount * singleWidth + 'rpx'
|
|
||||||
};
|
|
||||||
},
|
|
||||||
//累计的发文次数
|
|
||||||
calcAllYearCount() {
|
|
||||||
return this.chartData.reduce((acc, cur) => {
|
|
||||||
return acc + cur.total;
|
|
||||||
}, 0);
|
|
||||||
},
|
|
||||||
// 统计当前年累计的发文次数
|
|
||||||
calcCurrentYearCount() {
|
|
||||||
return this.currentYearData.reduce((acc, cur) => {
|
|
||||||
return acc + cur.total;
|
|
||||||
}, 0);
|
|
||||||
},
|
|
||||||
monthLabels() {
|
|
||||||
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
|
|
||||||
const labels = [];
|
|
||||||
|
|
||||||
if (this.displayDays.length === 0) return labels;
|
|
||||||
|
|
||||||
// 计算每个格子的总宽度(包括间距)
|
|
||||||
const cellTotalWidth = 15 + 1.5 * 2; // width + margin * 2
|
|
||||||
|
|
||||||
let currentMonth = -1;
|
|
||||||
let monthStartWeek = 0;
|
|
||||||
|
|
||||||
// 遍历所有周
|
|
||||||
const totalWeeks = Math.ceil(this.displayDays.length / 7);
|
|
||||||
|
|
||||||
for (let week = 0; week < totalWeeks; week++) {
|
|
||||||
// 找到这一周的第一个有效日期
|
|
||||||
let weekMonth = -1;
|
|
||||||
for (let day = 0; day < 7; day++) {
|
|
||||||
const dayIndex = week * 7 + day;
|
|
||||||
if (dayIndex < this.displayDays.length && this.displayDays[dayIndex].date) {
|
|
||||||
weekMonth = this.displayDays[dayIndex].date.getMonth();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果找到了有效月份
|
|
||||||
if (weekMonth !== -1) {
|
|
||||||
// 如果是新的月份
|
|
||||||
if (weekMonth !== currentMonth) {
|
|
||||||
// 如果不是第一个月份,先计算前一个月份的宽度
|
|
||||||
if (currentMonth !== -1) {
|
|
||||||
const weeksInMonth = week - monthStartWeek;
|
|
||||||
const width = weeksInMonth * cellTotalWidth;
|
|
||||||
labels.push({
|
|
||||||
name: months[currentMonth],
|
|
||||||
width: width,
|
|
||||||
marginLeft: monthStartWeek === 0 ? 0 : 2,
|
|
||||||
index: currentMonth
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
currentMonth = weekMonth;
|
|
||||||
monthStartWeek = week;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理最后一个月份
|
|
||||||
if (currentMonth !== -1) {
|
|
||||||
const weeksInMonth = totalWeeks - monthStartWeek;
|
|
||||||
const width = weeksInMonth * cellTotalWidth;
|
|
||||||
labels.push({
|
|
||||||
name: months[currentMonth],
|
|
||||||
width: width,
|
|
||||||
marginLeft: monthStartWeek === 0 ? 0 : 2,
|
|
||||||
index: currentMonth
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
year: {
|
|
||||||
immediate: true,
|
|
||||||
handler(newYear) {
|
|
||||||
this.generateDisplayData(newYear);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
chartData: {
|
|
||||||
deep: true,
|
|
||||||
handler(newData) {
|
|
||||||
this.mergeContributionData(newData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.initYearList()
|
|
||||||
this.filterAndSetCurrentYearData(this.year);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initYearList(){
|
|
||||||
for (var index = 1900; index < 2099; index++) {
|
|
||||||
this.yearList.push(index)
|
|
||||||
}
|
|
||||||
this.yearList = this.yearList.reverse();
|
|
||||||
},
|
|
||||||
filterAndSetCurrentYearData(year) {
|
|
||||||
this.currentYear = year;
|
|
||||||
this.currentYearData = this.chartData.filter((x) => x.name.includes(this.currentYear));
|
|
||||||
this.generateDisplayData(year);
|
|
||||||
},
|
|
||||||
generateDisplayData(year) {
|
|
||||||
const startDate = new Date(year, 0, 1);
|
|
||||||
const endDate = new Date(year, 11, 31);
|
|
||||||
const days = [];
|
|
||||||
|
|
||||||
// 计算第一周的偏移量(确保周一开始)
|
|
||||||
let firstDay = startDate.getDay();
|
|
||||||
firstDay = firstDay === 0 ? 6 : firstDay - 1; // 将周日(0)转换为6,周一(1)转换为0
|
|
||||||
|
|
||||||
// 添加空白填充,使第一周从周一开始
|
|
||||||
for (let i = 0; i < firstDay; i++) {
|
|
||||||
days.push({ date: null, count: 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成一年的日期
|
|
||||||
let currentDate = new Date(startDate);
|
|
||||||
while (currentDate <= endDate) {
|
|
||||||
// 默认无贡献
|
|
||||||
days.push({
|
|
||||||
date: new Date(currentDate),
|
|
||||||
count: 0
|
|
||||||
});
|
|
||||||
currentDate.setDate(currentDate.getDate() + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.displayDays = days;
|
|
||||||
this.mergeContributionData(this.currentYearData);
|
|
||||||
},
|
|
||||||
mergeContributionData(contributionData) {
|
|
||||||
if (!contributionData || contributionData.length === 0) return;
|
|
||||||
|
|
||||||
// 将传入的数据合并到显示数据中
|
|
||||||
const dataMap = new Map();
|
|
||||||
contributionData.forEach((item) => {
|
|
||||||
const dateStr = this.formatDate(item.date);
|
|
||||||
dataMap.set(dateStr, item.total);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.displayDays.forEach((day) => {
|
|
||||||
if (day.date) {
|
|
||||||
const dateStr = this.formatDate(day.date);
|
|
||||||
if (dataMap.has(dateStr)) {
|
|
||||||
day.count = dataMap.get(dateStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
formatDate(date) {
|
|
||||||
date = new Date(date);
|
|
||||||
// 将日期格式化为 YYYY-MM-DD
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
||||||
const day = String(date.getDate()).padStart(2, '0');
|
|
||||||
return `${year}-${month}-${day}`;
|
|
||||||
},
|
|
||||||
formatDisplayDate(date) {
|
|
||||||
// 将日期格式化为中文显示格式
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const month = date.getMonth() + 1;
|
|
||||||
const day = date.getDate();
|
|
||||||
return `${year}年${month}月${day}日`;
|
|
||||||
},
|
|
||||||
getDayColor(day) {
|
|
||||||
if (day.count === 0) return this.intensityColors[0];
|
|
||||||
if (day.count <= 2) return this.intensityColors[1];
|
|
||||||
if (day.count <= 4) return this.intensityColors[2];
|
|
||||||
if (day.count <= 6) return this.intensityColors[3];
|
|
||||||
return this.intensityColors[4];
|
|
||||||
},
|
|
||||||
getTooltipText(day) {
|
|
||||||
if (!day.date) return '无数据';
|
|
||||||
|
|
||||||
const dateStr = this.formatDisplayDate(day.date);
|
|
||||||
if (day.count === 0) {
|
|
||||||
return `${dateStr}: 发布 0 篇文章`;
|
|
||||||
} else {
|
|
||||||
return `${dateStr}: 发布 ${day.count} 篇文章`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleDayClick(day, index) {
|
|
||||||
uni.showToast({
|
|
||||||
icon: 'none',
|
|
||||||
title: this.getTooltipText(day)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
changeYear(value) {
|
|
||||||
this.filterAndSetCurrentYearData(value);
|
|
||||||
this.$emit('year-change', value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.contribution-heatmap {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.12), 0 2rpx 4rpx rgba(0, 0, 0, 0.24);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #000;
|
|
||||||
border: 2rpx solid #ebedf0;
|
|
||||||
border-radius: 10rpx;
|
|
||||||
padding: 8rpx 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.year-selector {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.year-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #0969da;
|
|
||||||
font-size: 28rpx;
|
|
||||||
padding: 8rpx 16rpx;
|
|
||||||
border-radius: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.year-btn:hover {
|
|
||||||
background-color: #f6f8fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.year-display {
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
min-width: 120rpx;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
border: 2rpx solid #ebedf0;
|
|
||||||
border-radius: 10rpx;
|
|
||||||
padding: 8rpx 3rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-text {
|
|
||||||
margin: 0 10rpx;
|
|
||||||
}
|
|
||||||
.legend-day-cell {
|
|
||||||
width: 24rpx!important;
|
|
||||||
height: 24rpx!important;
|
|
||||||
}
|
|
||||||
.heatmap-container {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
padding-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weeks {
|
|
||||||
margin-top: -2rpx;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-right: 10rpx;
|
|
||||||
padding-top: 48rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.week-label {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #7e7e7f;
|
|
||||||
height: 30rpx;
|
|
||||||
line-height: 30rpx;
|
|
||||||
margin-bottom: 6rpx;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.months {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.month-label {
|
|
||||||
flex: 1;
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #7e7e7f;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heatmap-content {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.days-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
height: 252rpx; /* 7行 * 30rpx + 7行 * 6rpx间距 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.day-cell {
|
|
||||||
width: 32rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
border-radius: 4rpx;
|
|
||||||
margin: 3rpx;
|
|
||||||
background-color: #ebedf0;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
margin-top: 12rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #586069;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.releases-count{
|
|
||||||
display: flex;
|
|
||||||
gap: 0 6rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 2rpx solid #ebedf0;
|
|
||||||
border-radius: 10rpx;
|
|
||||||
padding: 8rpx 12rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
<template>
|
|
||||||
<tm-poup v-model="isShow" width="80vw" height="800rpx" position="center" :round="6" @change="fnOnChange">
|
|
||||||
<view class="fulled pa-4">
|
|
||||||
<view class="mt-24 fulled text-weight-b text-size-sm text-align-center text-overflow-2 pb-24">{{ title }}</view>
|
|
||||||
<view class="fulled mt-2">
|
|
||||||
<scroll-view class="fulled" scroll-y :style="{
|
|
||||||
height: url? '600rpx': '680rpx'
|
|
||||||
}">
|
|
||||||
<mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain"
|
|
||||||
:loading-img="markdownConfig.loadingGif" :scroll-table="true" :selectable="true"
|
|
||||||
:tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
|
|
||||||
:content="content" :markdown="true" :showLineNumber="true" :showLanguageName="true"
|
|
||||||
:copyByLongPress="true"/>
|
|
||||||
</scroll-view>
|
|
||||||
</view>
|
|
||||||
<view v-if="url" class="fulled mt-12 flex flex-center">
|
|
||||||
<tm-button theme="bg-gradient-light-blue-accent" size="m" @click="fnToWebview()">
|
|
||||||
点击跳转内容链接
|
|
||||||
</tm-button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</tm-poup>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
|
|
||||||
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
|
||||||
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
|
|
||||||
|
|
||||||
import MarkdownConfig from '@/common/markdown/markdown.config.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'notify-dialog',
|
|
||||||
components: {
|
|
||||||
tmPoup,
|
|
||||||
tmButton,
|
|
||||||
mpHtml
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
show: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
url: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isShow: false,
|
|
||||||
markdownConfig: MarkdownConfig,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.isShow = this.show;
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fnToWebview() {
|
|
||||||
if (this.$utils.checkIsUrl(this.url)) {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pagesC/website/website?data=' +
|
|
||||||
JSON.stringify({
|
|
||||||
title: this.title,
|
|
||||||
url: encodeURIComponent(this.url)
|
|
||||||
}),
|
|
||||||
success: () => {
|
|
||||||
this.fnOnChange(false)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fnOnChange(e) {
|
|
||||||
this.isShow = false;
|
|
||||||
this.$emit('on-change', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view v-if="pluginInfo" class="plugin-unavailable flex flex-col flex-center"
|
|
||||||
:class="{border:useBorder,'decoration':useDecoration}" :style="[calcCustomStyle]">
|
|
||||||
<!-- 图标 -->
|
|
||||||
<image class="plugin-logo" :src="pluginInfo.logo" mode="scaleToFill"></image>
|
|
||||||
<!-- 名称 -->
|
|
||||||
<view class="plugin-name">
|
|
||||||
{{ pluginInfo.name }}
|
|
||||||
</view>
|
|
||||||
<view class="plugin-error">
|
|
||||||
未安装/启用插件
|
|
||||||
</view>
|
|
||||||
<!-- 描述 -->
|
|
||||||
<view class="plugin-desc">
|
|
||||||
{{ pluginInfo.desc }}
|
|
||||||
</view>
|
|
||||||
<view v-if="errorText" class="plugin-tip">
|
|
||||||
{{errorText}}
|
|
||||||
</view>
|
|
||||||
<!-- 插件地址 -->
|
|
||||||
<view class="plugin-url">
|
|
||||||
插件地址:{{ pluginInfo.url }}
|
|
||||||
</view>
|
|
||||||
<!-- 反馈按钮/复制地址 -->
|
|
||||||
<view class="plugin-btns w-full flex-center">
|
|
||||||
<!-- #ifndef MP-WEIXIN -->
|
|
||||||
<tm-button theme="light-blue" :block="true" class="w-full" :height="70" @click="copy">复制地址</tm-button>
|
|
||||||
<!-- #endif -->
|
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
|
||||||
<tm-button theme="light-blue" plan text :height="70" @click="copy">复制地址</tm-button>
|
|
||||||
<tm-button theme="light-blue" open-type="contact" :height="70">提交反馈</tm-button>
|
|
||||||
<!-- #endif -->
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="plugin-copyright">
|
|
||||||
提示:请确保 Halo 博客已安装相关插件
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
NeedPluginIds,
|
|
||||||
NeedPlugins,
|
|
||||||
CheckNeedPluginAvailable
|
|
||||||
} from "@/utils/plugin.js"
|
|
||||||
import utils from '@/utils/index.js'
|
|
||||||
|
|
||||||
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "plugin-unavailable",
|
|
||||||
components: {
|
|
||||||
tmButton
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
pluginId: {
|
|
||||||
type: String,
|
|
||||||
default: ""
|
|
||||||
},
|
|
||||||
errorText: {
|
|
||||||
type: String,
|
|
||||||
default: ""
|
|
||||||
},
|
|
||||||
useDecoration: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
useBorder: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
customStyle: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
NeedPluginIds,
|
|
||||||
NeedPlugins,
|
|
||||||
pluginInfo: null,
|
|
||||||
defaultStyle: {
|
|
||||||
width: "80vw",
|
|
||||||
borderRadius: "24rpx"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
calcCustomStyle() {
|
|
||||||
const style = this.customStyle ?? {}
|
|
||||||
return {
|
|
||||||
...this.defaultStyle,
|
|
||||||
...style
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
pluginId: {
|
|
||||||
deep: true,
|
|
||||||
immediate: true,
|
|
||||||
handler(newVal, oldVal) {
|
|
||||||
this.pluginInfo = NeedPlugins.get(newVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
copy() {
|
|
||||||
utils.copyText(this.pluginInfo.url, "插件地址已复制")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.plugin-unavailable {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: auto;
|
|
||||||
padding: 100rpx 36rpx;
|
|
||||||
gap: 24rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
|
|
||||||
&.border {
|
|
||||||
// border: 2rpx solid #E2E8F0;
|
|
||||||
border: 2rpx solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.decoration {
|
|
||||||
background-color: rgba(255, 255, 255, 0.95);
|
|
||||||
box-shadow: 0 0 12rpx rgba(226, 232, 240, 0.35);
|
|
||||||
backdrop-filter: blur(6rpx);
|
|
||||||
border-top: 12rpx solid rgba(3, 169, 244, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.plugin-logo {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 120rpx;
|
|
||||||
height: 120rpx;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plugin-name {
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
font-size: 32rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plugin-error {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 6rpx 16rpx;
|
|
||||||
border-radius: 36rpx;
|
|
||||||
background-color: rgba(255, 61, 49, 0.075);
|
|
||||||
color: rgba(255, 61, 49, 1);
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plugin-tip {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 10rpx 20rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
border: 2rpx dashed #f2c97d;
|
|
||||||
color: #f0a020;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plugin-desc {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 60vw;
|
|
||||||
font-size: 24rpx;
|
|
||||||
text-align: center;
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plugin-url {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 16rpx 24rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
background-color: #F1F5F9;
|
|
||||||
color: #666;
|
|
||||||
font-size: 24rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plugin-btns {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plugin-copyright {
|
|
||||||
font-size: 20rpx;
|
|
||||||
color: #999;
|
|
||||||
transform: scale(0.9) translateY(20px);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'restrict-read-skeleton',
|
|
||||||
props: {
|
|
||||||
loading: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
buttonText: {
|
|
||||||
type: String,
|
|
||||||
default: '刷新'
|
|
||||||
},
|
|
||||||
buttonColor: {
|
|
||||||
type: String,
|
|
||||||
default: '#07c160'
|
|
||||||
},
|
|
||||||
buttonSize: {
|
|
||||||
type: String,
|
|
||||||
default: 'normal', // 'small', 'normal', 'large'
|
|
||||||
validator: value => ['small', 'normal', 'large'].includes(value)
|
|
||||||
},
|
|
||||||
lines: {
|
|
||||||
type: Number,
|
|
||||||
default: 4,
|
|
||||||
validator: value => value >= 1 && value <= 6
|
|
||||||
},
|
|
||||||
skeletonColor: {
|
|
||||||
type: String,
|
|
||||||
default: '#f5f5f5'
|
|
||||||
},
|
|
||||||
skeletonHighlight: {
|
|
||||||
type: String,
|
|
||||||
default: '#e8e8e8'
|
|
||||||
},
|
|
||||||
animationDuration: {
|
|
||||||
type: Number,
|
|
||||||
default: 1.5
|
|
||||||
},
|
|
||||||
showButton: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
tipText: {
|
|
||||||
type: String,
|
|
||||||
default: '' // 默认不显示提示文字
|
|
||||||
},
|
|
||||||
tipColor: {
|
|
||||||
type: String,
|
|
||||||
default: '#666666' // 提示文字颜色
|
|
||||||
},
|
|
||||||
tipSize: {
|
|
||||||
type: Number,
|
|
||||||
default: 24 // 提示文字大小,单位rpx
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleRefresh() {
|
|
||||||
this.$emit('refresh');
|
|
||||||
},
|
|
||||||
onTouchStart() {
|
|
||||||
this.$emit('touchstart');
|
|
||||||
},
|
|
||||||
onTouchEnd() {
|
|
||||||
this.$emit('touchend');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!-- 骨架屏容器 -->
|
|
||||||
<view class="container">
|
|
||||||
<!-- 骨架屏内容 -->
|
|
||||||
<view class="skeleton" v-if="loading">
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<view class="skeleton-body">
|
|
||||||
<view
|
|
||||||
v-for="(item, index) in Array(lines).fill(0)"
|
|
||||||
:key="index"
|
|
||||||
class="skeleton-line"
|
|
||||||
:class="{
|
|
||||||
'short': index === lines - 2,
|
|
||||||
'shorter': index === lines - 1
|
|
||||||
}"
|
|
||||||
:style="{
|
|
||||||
background: `linear-gradient(90deg, ${skeletonColor} 25%, ${skeletonHighlight} 50%, ${skeletonColor} 75%)`,
|
|
||||||
animationDuration: `${animationDuration}s`
|
|
||||||
}">
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 实际内容 -->
|
|
||||||
<view v-else>
|
|
||||||
<slot></slot>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 提示文字和按钮容器 -->
|
|
||||||
<view v-if="showButton" class="button-container">
|
|
||||||
<!-- 提示文字 -->
|
|
||||||
<text
|
|
||||||
v-if="tipText"
|
|
||||||
class="tip-text"
|
|
||||||
:style="{
|
|
||||||
color: tipColor,
|
|
||||||
fontSize: tipSize + 'rpx'
|
|
||||||
}"
|
|
||||||
>{{ tipText }}</text>
|
|
||||||
|
|
||||||
<!-- 按钮 -->
|
|
||||||
<button
|
|
||||||
class="overlay-button"
|
|
||||||
:class="[buttonSize, { 'button-hover': hover }]"
|
|
||||||
hover-class="none"
|
|
||||||
@touchstart="onTouchStart"
|
|
||||||
@touchend="onTouchEnd"
|
|
||||||
@click="handleRefresh"
|
|
||||||
:style="{ backgroundColor: buttonColor }"
|
|
||||||
>
|
|
||||||
{{ buttonText }}
|
|
||||||
</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
/* 容器样式 */
|
|
||||||
.container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 200rpx;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 骨架屏样式 */
|
|
||||||
.skeleton {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 30rpx;
|
|
||||||
|
|
||||||
&-body {
|
|
||||||
margin: 20rpx 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-line {
|
|
||||||
height: 32rpx;
|
|
||||||
background-size: 400% 100%;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
animation: skeleton-loading 1.5s ease infinite;
|
|
||||||
|
|
||||||
&.short {
|
|
||||||
width: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.shorter {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 按钮容器样式 */
|
|
||||||
.button-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 提示文字样式 */
|
|
||||||
.tip-text {
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 按钮样式 */
|
|
||||||
.overlay-button {
|
|
||||||
position: relative; // 改为相对定位
|
|
||||||
transform: none; // 移除transform
|
|
||||||
color: white;
|
|
||||||
border-radius: 50rpx;
|
|
||||||
padding: 0 40rpx;
|
|
||||||
height: 80rpx;
|
|
||||||
line-height: 80rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.95); // 简化active状态的transform
|
|
||||||
}
|
|
||||||
|
|
||||||
&.small {
|
|
||||||
height: 60rpx;
|
|
||||||
line-height: 60rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
padding: 0 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.large {
|
|
||||||
height: 100rpx;
|
|
||||||
line-height: 100rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
padding: 0 50rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-hover {
|
|
||||||
transform: translateY(-4rpx); // 简化hover状态的transform
|
|
||||||
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 骨架屏动画 */
|
|
||||||
@keyframes skeleton-loading {
|
|
||||||
0% {
|
|
||||||
background-position: 100% 50%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 0 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -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,252 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="vote-card" @click="$emit('on-click',vote)">
|
|
||||||
<view class="vote-card-head flex">
|
|
||||||
<view class="left flex flex-center w-full">
|
|
||||||
<view class="flex-shrink">
|
|
||||||
<tm-tags color="light-blue" :shadow="0" rounded size="s" model="fill">{{ vote.spec._uh_type}}</tm-tags>
|
|
||||||
</view>
|
|
||||||
<view class="title text-overflow">
|
|
||||||
{{ vote.spec.title }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="vote-card-body w-full">
|
|
||||||
<view v-if="vote.spec.remark" class="remark text-overflow-2 text-size-s">
|
|
||||||
{{vote.spec.remark}}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="vote-card-foot flex flex-between">
|
|
||||||
<view class="left flex">
|
|
||||||
<tm-tags :color="vote.spec._uh_state.color" size="s" rounded :shadow="0"
|
|
||||||
model="text">{{vote.spec._uh_state.state}}</tm-tags>
|
|
||||||
|
|
||||||
<tm-tags v-if="vote.spec.isVoted" color="blue" rounded size="s" model="text">已投票</tm-tags>
|
|
||||||
|
|
||||||
<tm-tags v-if="vote.spec.timeLimit==='permanent'" color="grey-darken-2" rounded size="s"
|
|
||||||
model="text">结束时间:永久有效 </tm-tags>
|
|
||||||
<tm-tags v-else color="grey-darken-2" rounded size="s" model="text">
|
|
||||||
<template
|
|
||||||
v-if="vote.spec._uh_state.state=='未开始'">开始时间:{{ {d: vote.spec.startDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
|
|
||||||
</template>
|
|
||||||
<template v-else>结束时间:{{ {d: vote.spec.endDate, f: 'yyyy-MM-dd HH:mm'} | formatTime }}
|
|
||||||
</template>
|
|
||||||
</tm-tags>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="false" class="right flex flex-end">
|
|
||||||
<tm-tags v-if="false" color="grey-darken-2" rounded size="s" model="text">{{ vote.stats.voteCount }}
|
|
||||||
人已参与</tm-tags>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import tmGroupradio from '@/tm-vuetify/components/tm-groupradio/tm-groupradio.vue';
|
|
||||||
import tmRadio from '@/tm-vuetify/components/tm-radio/tm-radio.vue';
|
|
||||||
import tmGroupcheckbox from '@/tm-vuetify/components/tm-groupcheckbox/tm-groupcheckbox.vue';
|
|
||||||
import tmCheckbox from '@/tm-vuetify/components/tm-checkbox/tm-checkbox.vue';
|
|
||||||
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
|
||||||
import tmTags from '@/tm-vuetify/components/tm-tags/tm-tags.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "VoteCard",
|
|
||||||
options: {
|
|
||||||
virtualHost: true,
|
|
||||||
styleIsolation: 'shared'
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
tmGroupradio,
|
|
||||||
tmRadio,
|
|
||||||
tmGroupcheckbox,
|
|
||||||
tmCheckbox,
|
|
||||||
tmButton,
|
|
||||||
tmTags
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
vote: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
index: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
showOptions: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.w-full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wp-50 {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0 24rpx;
|
|
||||||
padding: 24rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
background-color: #ffff;
|
|
||||||
box-shadow: 0rpx 2rpx 12rpx rgba(0, 0, 0, 0.035);
|
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
// border: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-card-head {
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.left {
|
|
||||||
.title {
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-card-body {
|
|
||||||
|
|
||||||
.remark {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-top: 0;
|
|
||||||
color: rgba(0, 0, 0, 0.75);
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-card-foot {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-top: 6px;
|
|
||||||
margin-top: 6px;
|
|
||||||
border-top: 2rpx solid #F7F7F7;
|
|
||||||
|
|
||||||
.left {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.single {
|
|
||||||
::v-deep {
|
|
||||||
.tm-groupradio {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12rpx 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tm-checkbox {
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: block;
|
|
||||||
padding: 0 12rpx;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tm-button-label {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.multiple {
|
|
||||||
::v-deep {
|
|
||||||
.tm-groupcheckbox {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12rpx 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tm-checkbox {
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: block;
|
|
||||||
padding: 0 12rpx;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tm-button-label {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pk {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 12rpx;
|
|
||||||
|
|
||||||
::v-deep {
|
|
||||||
.tm-groupradio {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tm-checkbox {
|
|
||||||
flex-grow: 1;
|
|
||||||
min-width: 30% !important;
|
|
||||||
max-width: 70% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-item {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-left {}
|
|
||||||
|
|
||||||
.radio-right {}
|
|
||||||
|
|
||||||
.option-item {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12rpx 24rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-item-left {
|
|
||||||
background: linear-gradient(90deg, #3B82F6, #60A5FA);
|
|
||||||
color: white;
|
|
||||||
clip-path: polygon(0 0, calc(100% - 40rpx) 0, 100% 100%, 0 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-item-right {
|
|
||||||
background: linear-gradient(90deg, #F87171, #EF4444);
|
|
||||||
color: white;
|
|
||||||
clip-path: polygon(0 0, 100% 0, 100% 100%, 40rpx 100%);
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-foot {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 6rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #666;
|
|
||||||
|
|
||||||
.left {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-right: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-left: 24rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -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>
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
// 应用设置存储key值
|
|
||||||
export const _AppConfigKey = 'APP_GLOBAL_CONFIGS';
|
|
||||||
export const _AppMockJsonKey = 'APP_GLOBAL_MOCK_JSON';
|
|
||||||
|
|
||||||
// 默认的应用设置
|
|
||||||
export const DefaultAppConfigs = {
|
|
||||||
basicConfig: {
|
|
||||||
tokenConfig: {
|
|
||||||
personalToken: "",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loveConfig: {},
|
|
||||||
imagesConfig: {},
|
|
||||||
authorConfig: {},
|
|
||||||
appConfig: {},
|
|
||||||
pluginConfig: {
|
|
||||||
votePlugin: {},
|
|
||||||
toolsPlugin: {},
|
|
||||||
linksPlugin:{},
|
|
||||||
linksSubmitPlugin: {},
|
|
||||||
doubanPlugin: {
|
|
||||||
position: 'bottom'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pageConfig: {
|
|
||||||
homeConfig: {
|
|
||||||
pageTitle: "首页",
|
|
||||||
useCategory: true,
|
|
||||||
bannerConfig: {
|
|
||||||
enabled: true,
|
|
||||||
showTitle: true,
|
|
||||||
showIndicator: true,
|
|
||||||
height: "400rpx",
|
|
||||||
dotPosition: "right",
|
|
||||||
type: "post",
|
|
||||||
list: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
categoryConfig: {
|
|
||||||
type: "list"
|
|
||||||
},
|
|
||||||
momentConfig: {
|
|
||||||
useTagRandomColor: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
auditConfig: {
|
|
||||||
auditModeEnabled: false,
|
|
||||||
auditModeData: {
|
|
||||||
jsonUrl: "",
|
|
||||||
jsonData: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取应用设置
|
|
||||||
*/
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
/** 应用基础配置 */
|
|
||||||
const UHaloBaseConfig = Object.freeze({
|
|
||||||
|
|
||||||
/** 基础请求域名:你的Halo博客基础域名,注意,域名后面不能带有斜杠【/】 */
|
|
||||||
// BASE_API: "",
|
|
||||||
// 示例:
|
|
||||||
BASE_API: "https://blog.xiaoxiaomo.cn",
|
|
||||||
|
|
||||||
// todo:可能有其他配置
|
|
||||||
})
|
|
||||||
|
|
||||||
export default UHaloBaseConfig;
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
{
|
|
||||||
"home": {
|
|
||||||
"bannerList": [
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.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-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.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-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "uni-halo(2.0)开源啦!!!",
|
|
||||||
"desc": "uni-halo 2.0 已独立兼容 Halo2.x 应用,欢迎小伙伴们体验。",
|
|
||||||
"cover": "/upload/uni_halo_ad_cover.png",
|
|
||||||
"time": "2024-05-25 08:59:00"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# 变量必须以 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'
|
||||||
Vendored
+9
@@ -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'
|
||||||
Vendored
+9
@@ -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'
|
||||||
@@ -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
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
+13
-7
@@ -1,20 +1,26 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html build-time="%BUILD_TIME%">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
|
||||||
<script>
|
<script>
|
||||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
var coverSupport =
|
||||||
CSS.supports('top: constant(a)'))
|
'CSS' in window &&
|
||||||
|
typeof CSS.supports === 'function' &&
|
||||||
|
(CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
|
||||||
document.write(
|
document.write(
|
||||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
'<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>
|
</script>
|
||||||
<title></title>
|
<title>%VITE_APP_TITLE%</title>
|
||||||
<!--preload-links-->
|
<!--preload-links-->
|
||||||
<!--app-context-->
|
<!--app-context-->
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"><!--app-html--></div>
|
<div id="app"><!--app-html--></div>
|
||||||
<script type="module" src="/main.js"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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。默认60000。H5(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
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "luch-request",
|
|
||||||
"name": "luch-request",
|
|
||||||
"version": "3.0.7",
|
|
||||||
"description": "基于Promise开发的跨平台、项目级别的请求库,它有更小的体积,易用的api,方便简单的自定义能力",
|
|
||||||
"keywords": [
|
|
||||||
"request",
|
|
||||||
"axios",
|
|
||||||
"网络请求",
|
|
||||||
"拦截器",
|
|
||||||
"promise"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import App from "./App";
|
|
||||||
import Vue from "vue";
|
|
||||||
|
|
||||||
// 挂载全局工具类
|
|
||||||
import utils from "./utils/index.js";
|
|
||||||
Vue.prototype.$utils = utils;
|
|
||||||
uni.$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);
|
|
||||||
|
|
||||||
// 引入请求库
|
|
||||||
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/uhalo.config.js'
|
|
||||||
Vue.prototype.$baseApiUrl = HaloTokenConfig.BASE_API
|
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
|
||||||
App.mpType = "app";
|
|
||||||
const app = new Vue({
|
|
||||||
...App,
|
|
||||||
});
|
|
||||||
|
|
||||||
app.$mount();
|
|
||||||
@@ -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',
|
||||||
|
})
|
||||||
-236
@@ -1,236 +0,0 @@
|
|||||||
{
|
|
||||||
"name" : "uni-halo",
|
|
||||||
"appid" : "__UNI__5748B6E",
|
|
||||||
"description" : "uni-halo博客:基于halo开源博客系统API开发的多端博客。",
|
|
||||||
"versionName" : "2.0.10",
|
|
||||||
"versionCode" : 210,
|
|
||||||
"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" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/* 快应用特有相关 */
|
|
||||||
"quickapp" : {},
|
|
||||||
/* 小程序特有相关 */
|
|
||||||
"mp-weixin" : {
|
|
||||||
"appid" : "wx1bb161867082c72f",
|
|
||||||
"setting" : {
|
|
||||||
"urlCheck" : false,
|
|
||||||
"minified" : true
|
|
||||||
},
|
|
||||||
"usingComponents" : true,
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mp-alipay" : {
|
|
||||||
"usingComponents" : true,
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mp-baidu" : {
|
|
||||||
"usingComponents" : true,
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mp-toutiao" : {
|
|
||||||
"usingComponents" : true,
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false,
|
|
||||||
"version" : "2"
|
|
||||||
},
|
|
||||||
"vueVersion" : "2",
|
|
||||||
"h5" : {
|
|
||||||
"title" : "uni-halo",
|
|
||||||
"devServer" : {
|
|
||||||
"disableHostCheck" : true,
|
|
||||||
"proxy" : {
|
|
||||||
"/apis" : {
|
|
||||||
"target" : "https://blog.xiaoxiaomo.cn",
|
|
||||||
"changeOrigin" : true,
|
|
||||||
"secure" : false,
|
|
||||||
"pathRewrite" : {
|
|
||||||
"^/apis" : "/apis"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"optimization" : {
|
|
||||||
"preload" : true,
|
|
||||||
"treeShaking" : {
|
|
||||||
"enable" : true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
},
|
|
||||||
"router" : {
|
|
||||||
"base" : "/uni-halo/",
|
|
||||||
"mode" : "history"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"_spaceID" : "699a26a5-a6cd-4b54-b603-5155b312a5f5",
|
|
||||||
"mp-qq" : {
|
|
||||||
"appid" : "1112285420",
|
|
||||||
"setting" : {
|
|
||||||
"minified" : true,
|
|
||||||
"postcss" : true,
|
|
||||||
"es6" : true
|
|
||||||
},
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mp-jd" : {
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mp-kuaishou" : {
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mp-lark" : {
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"quickapp-webview-huawei" : {
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"quickapp-webview-union" : {
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"app-harmony" : {
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mp-harmony" : {
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mp-xhs" : {
|
|
||||||
"uniStatistics" : {
|
|
||||||
"enable" : false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
|
])
|
||||||
+227
-15
@@ -1,23 +1,235 @@
|
|||||||
{
|
{
|
||||||
"name": "uni-halo",
|
"name": "uni-halo",
|
||||||
"version": "2.0.10",
|
"type": "module",
|
||||||
"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>",
|
"version": "1.0.0",
|
||||||
"repository": {
|
"unibest": {
|
||||||
"type": "git",
|
"platforms": [
|
||||||
"url": "git+https://gitee.com/ialley-workshop-open/uni-halo.git"
|
"h5",
|
||||||
},
|
"mp-weixin",
|
||||||
"keywords": [
|
"app",
|
||||||
"uni-halo",
|
"mp-alipay",
|
||||||
"小莫唐尼",
|
"mp-toutiao"
|
||||||
"巷子工坊"
|
|
||||||
],
|
],
|
||||||
"author": "巷子工坊丨小莫唐尼",
|
"uiLibrary": "wot-ui-v2",
|
||||||
"license": "AGPL-3.0",
|
"loginStrategy": true,
|
||||||
"bugs": {
|
"i18n": true,
|
||||||
"url": "https://gitee.com/ialley-workshop-open/uni-halo/issues"
|
"chartLibraries": [
|
||||||
|
"ucharts"
|
||||||
|
],
|
||||||
|
"cliVersion": "4.0.16",
|
||||||
|
"unibestVersion": "4.4.1",
|
||||||
|
"unibestUpdateTime": "2026-05-22",
|
||||||
|
"createdAt": "2026-06-09 14:03:51"
|
||||||
},
|
},
|
||||||
|
"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",
|
"homepage": "https://uni-halo.925i.cn",
|
||||||
|
"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": {
|
"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 版本!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
-375
@@ -1,375 +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": "submit-link/submit-link",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "友链提交",
|
|
||||||
"enablePullDownRefresh": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "moment-detail/moment-detail",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "瞬间详情",
|
|
||||||
"enablePullDownRefresh": true,
|
|
||||||
"app-plus": {
|
|
||||||
"pullToRefresh": {
|
|
||||||
"color": "#03a9f4",
|
|
||||||
"style": "circle"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "votes/votes",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "投票列表",
|
|
||||||
"enablePullDownRefresh": true,
|
|
||||||
"app-plus": {
|
|
||||||
"pullToRefresh": {
|
|
||||||
"color": "#03a9f4",
|
|
||||||
"style": "circle"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "vote-detail/vote-detail",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "投票详情",
|
|
||||||
"enablePullDownRefresh": true,
|
|
||||||
"app-plus": {
|
|
||||||
"pullToRefresh": {
|
|
||||||
"color": "#03a9f4",
|
|
||||||
"style": "circle"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "data-visual/data-visual",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "数据看板",
|
|
||||||
"enablePullDownRefresh": true,
|
|
||||||
"app-plus": {
|
|
||||||
"pullToRefresh": {
|
|
||||||
"color": "#03a9f4",
|
|
||||||
"style": "circle"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": "关于"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="app-page flex flex-center">
|
|
||||||
<PluginUnavailable v-if="!uniHaloPluginAvailable" :pluginId="uniHaloPluginId" :error-text="uniHaloPluginAvailableError" :use-border="false" :use-decoration="false" />
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import pluginAvailableMixin from '@/common/mixins/pluginAvailable.js';
|
|
||||||
import PluginUnavailable from '@/components/plugin-unavailable/plugin-unavailable.vue';
|
|
||||||
|
|
||||||
const homePagePath = '/pages/tabbar/home/home';
|
|
||||||
const startPagePath = '/pagesA/start/start';
|
|
||||||
const articleDetailPath = '/pagesA/article-detail/article-detail';
|
|
||||||
|
|
||||||
const _DEV_ = false;
|
|
||||||
const _DEV_TO_TYPE_ = 'page';
|
|
||||||
const _DEV_TO_PATH_ = '/pagesA/data-visual/data-visual';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mixins: [pluginAvailableMixin],
|
|
||||||
components: {
|
|
||||||
PluginUnavailable
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
configs() {
|
|
||||||
return this.$tm.vx.getters().getConfigs;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async onLoad(options) {
|
|
||||||
// 本地开发,快速跳转页面,发布请设置 _DEV_ = false
|
|
||||||
if (_DEV_ && _DEV_TO_PATH_) {
|
|
||||||
if (_DEV_TO_TYPE_ == 'tabbar') {
|
|
||||||
uni.switchTab({
|
|
||||||
url: _DEV_TO_PATH_
|
|
||||||
});
|
|
||||||
} else if (_DEV_TO_TYPE_ == 'page') {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: _DEV_TO_PATH_
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查插件
|
|
||||||
this.setPluginId(this.NeedPluginIds.PluginUniHalo);
|
|
||||||
this.setPluginError('阿偶,检测到当前插件没有安装或者启用,无法启动 uni-halo 哦,请联系管理员');
|
|
||||||
if (!(await this.checkPluginAvailable())) return;
|
|
||||||
|
|
||||||
// 获取配置
|
|
||||||
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.auditConfig.auditModeEnabled) {
|
|
||||||
if (res.auditConfig.auditModeData.jsonUrl) {
|
|
||||||
await uni.$tm.vx.actions('config/fetchMockJson');
|
|
||||||
} else {
|
|
||||||
const mockJson = uni.$utils.checkJsonAndParse(res.auditConfig.auditModeData.jsonData);
|
|
||||||
if (mockJson.ok) {
|
|
||||||
uni.$tm.vx.commit('config/setMockJson', mockJson.jsonData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 进入检查
|
|
||||||
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>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.app-page {
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,626 +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 {
|
|
||||||
NeedPluginIds,
|
|
||||||
NeedPlugins,
|
|
||||||
checkNeedPluginAvailable
|
|
||||||
} from "@/utils/plugin.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;
|
|
||||||
},
|
|
||||||
calcAuditModeEnabled() {
|
|
||||||
return this.haloConfigs.auditConfig.auditModeEnabled
|
|
||||||
},
|
|
||||||
calcVotePluginEnabled() {
|
|
||||||
return !!this.haloConfigs?.pluginConfig?.votePlugin?.enabled;
|
|
||||||
},
|
|
||||||
calcLinksPluginEnabled() {
|
|
||||||
return !!this.haloConfigs?.pluginConfig?.linksPlugin?.enabled;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
haloConfigs: {
|
|
||||||
handler(val) {
|
|
||||||
if (!val) return;
|
|
||||||
this.fnGetNavList();
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
immediate: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.fnGetData();
|
|
||||||
},
|
|
||||||
onPullDownRefresh() {
|
|
||||||
this.fnGetData();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fnGetNavList() {
|
|
||||||
const systemInfo = uni.getSystemInfoSync();
|
|
||||||
let _isWx = false;
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
_isWx = true;
|
|
||||||
// #endif
|
|
||||||
this.navList = [{
|
|
||||||
key: 'data-visual',
|
|
||||||
title: '数据看板',
|
|
||||||
leftIcon: 'icon-chart-bar',
|
|
||||||
leftIconColor: 'light-blue',
|
|
||||||
rightText: '站点数据可视化',
|
|
||||||
path: '/pagesA/data-visual/data-visual',
|
|
||||||
isAdmin: false,
|
|
||||||
type: 'page',
|
|
||||||
show: await checkNeedPluginAvailable(NeedPluginIds.PluginDataStatistics)
|
|
||||||
},{
|
|
||||||
key: 'archives',
|
|
||||||
title: this.calcAuditModeEnabled ? '内容归档' : '文章归档',
|
|
||||||
leftIcon: 'halocoloricon-classify',
|
|
||||||
leftIconColor: 'red',
|
|
||||||
rightText: this.calcAuditModeEnabled ? '全部已归档内容' : '全部已归档文章',
|
|
||||||
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: 'vote',
|
|
||||||
title: '投票中心',
|
|
||||||
leftIcon: 'icon-box',
|
|
||||||
leftIconColor: 'red',
|
|
||||||
rightText: '查看和进行投票',
|
|
||||||
path: '/pagesA/votes/votes',
|
|
||||||
isAdmin: false,
|
|
||||||
type: 'page',
|
|
||||||
show: !this.calcAuditModeEnabled && !!this.calcVotePluginEnabled,
|
|
||||||
}, {
|
|
||||||
key: 'disclaimers',
|
|
||||||
title: '友情链接',
|
|
||||||
leftIcon: 'icon-lianjie',
|
|
||||||
leftIconColor: 'blue',
|
|
||||||
rightText: '看看博主朋友们吧',
|
|
||||||
path: '/pagesA/friend-links/friend-links',
|
|
||||||
isAdmin: false,
|
|
||||||
type: 'page',
|
|
||||||
show: !!this.calcLinksPluginEnabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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: encodeURIComponent(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>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user