mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-07-27 04:20:43 +08:00
Compare commits
176 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c847dfb1c4 | |||
| 862d176593 | |||
| 3945ee611d | |||
| de142c4729 | |||
| 0b4090f9e9 | |||
| 3ccd4f7ccf | |||
| 6f01501fd1 | |||
| e426abf336 | |||
| a767397e23 | |||
| 17f5b4b917 | |||
| 94ff37f77f | |||
| 73bc88bad8 | |||
| 72dc278a2b | |||
| 7ba35ffd5d | |||
| 3643bdf6f6 | |||
| 21fee2dc9a | |||
| 90f6516f45 | |||
| a15621d55e | |||
| a717beb0e7 | |||
| b9ad94ddb5 | |||
| 9ab013562d | |||
| a8bd121deb | |||
| 476a1ec48e | |||
| 31ade9908a | |||
| 5606e8c59f | |||
| 61d3b22fdb | |||
| c05aa5253c | |||
| 0b599dcced | |||
| 623e3fa59a | |||
| 1075f588fa | |||
| c0d8cb2c3b | |||
| 9c6a054b12 | |||
| 6a05f664ba | |||
| 9c4d2242ae | |||
| eac027c116 | |||
| 3a08008192 | |||
| 7daf93d51f | |||
| 409be28deb | |||
| 97e358e41f | |||
| c17e668afe | |||
| 4611932ad8 | |||
| 452d021816 | |||
| d71022c224 | |||
| d521a641de | |||
| 4246d8cdcb | |||
| 6989fd8ddc | |||
| 742b51a23e | |||
| bc541a3ef6 | |||
| 67fc682164 | |||
| b43dd7e5a7 | |||
| e29ccf65c5 | |||
| 91553fc053 | |||
| 22f57b7860 | |||
| 2a25727c0b | |||
| eb69ae4af6 | |||
| 2fd83a7814 | |||
| 8963ba1921 | |||
| 051c72b584 | |||
| 552827eaac | |||
| e8d13c674f | |||
| 256bc0e00f | |||
| 9aa0e310f2 | |||
| 6a0bb0e07f | |||
| 044f737016 | |||
| 27b3d31828 | |||
| 64c9a15d51 | |||
| df4c58d3e4 | |||
| 93f23696ef | |||
| 2ed7e1a260 | |||
| e19b107ed0 | |||
| 7c15c84dba | |||
| e8edc03526 | |||
| 2b717ad449 | |||
| 5efc877a89 | |||
| 75a133415a | |||
| bf41b08b35 | |||
| 74a5efb596 | |||
| 40dd4f4b7f | |||
| 341670adcf | |||
| 115aea2580 | |||
| 58f1e7f131 | |||
| 26ce84b950 | |||
| 32b3bbaa74 | |||
| 76c00f31f6 | |||
| e28ea6ca08 | |||
| a98254e6cc | |||
| 82062d9465 | |||
| 706cee2240 | |||
| da60eb2daf | |||
| a764c3dbfe | |||
| 88fb74a795 | |||
| 52d4b7927b | |||
| ff31e0d8e8 | |||
| a65ea2617b | |||
| bb228c6705 | |||
| 62992b2688 | |||
| da141ae10a | |||
| d22224e2fc | |||
| d0b3a42c1d | |||
| c3349ae070 | |||
| ad6719e201 | |||
| 0e342d5fa3 | |||
| daf1318a90 | |||
| d4ccc9b7d6 | |||
| ce60c334ec | |||
| ca723bae80 | |||
| c312fab4b1 | |||
| d9db03959a | |||
| add84f6e4b | |||
| 850ee73fa2 | |||
| 77da85f7d4 | |||
| 4d0901b650 | |||
| 66a39dd723 | |||
| 4cdcbb1958 | |||
| e0569e287f | |||
| b42932ddad | |||
| cc05b64dec | |||
| aec54e8c5b | |||
| 15e95a2f3e | |||
| 74e5c9c3cf | |||
| 85227a8aa4 | |||
| e7a00c5b69 | |||
| 85e558f1a0 | |||
| fade65397d | |||
| a2bd2cc8ba | |||
| bdaeb98432 | |||
| ccefbcee02 | |||
| e5590c0a22 | |||
| a85131308c | |||
| e17edade28 | |||
| 959acab9c7 | |||
| 0b59f69fa0 | |||
| ab755a8623 | |||
| a67ad88c3f | |||
| 6684f80f02 | |||
| 620354b77a | |||
| 19c4e12a5b | |||
| 3b0ef59ca7 | |||
| 0386c66b70 | |||
| 9721932c34 | |||
| f9fce7d0ed | |||
| 4747d593be | |||
| 7d8e4ad559 | |||
| 3d02b8cc44 | |||
| 595c2d7cb7 | |||
| 2154daea57 | |||
| 85f166e7d0 | |||
| 0d618e21ce | |||
| 8d610473b3 | |||
| 512a4013b9 | |||
| f6069cad95 | |||
| 90ef890766 | |||
| 5727efa820 | |||
| 057a7cf6f0 | |||
| a81c506a14 | |||
| 70dc7cd0a7 | |||
| ee28753094 | |||
| 91923c0b42 | |||
| f6feecb324 | |||
| 6fdaeed792 | |||
| f838da43d8 | |||
| 21d723d260 | |||
| d701867b83 | |||
| 66f4ccffc0 | |||
| 9720743c80 | |||
| 6ba87b1972 | |||
| a0f3d1b220 | |||
| 6e80fa14c8 | |||
| 22a8e92dc2 | |||
| 6b1e0e5cc0 | |||
| 261617fc12 | |||
| 5000da9a64 | |||
| 9573b303e2 | |||
| f12262f4df | |||
| 1305b4a555 | |||
| b7e2025ebb |
@@ -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
|
||||||
|
---
|
||||||
|
# unibest 项目概览
|
||||||
|
|
||||||
|
这是一个基于 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 # 关闭末尾空格修剪
|
||||||
+48
-5
@@ -1,5 +1,48 @@
|
|||||||
node_modules/
|
# Logs
|
||||||
unpackage/
|
logs
|
||||||
config/halo.config.js
|
*.log
|
||||||
config/ad.config.js
|
npm-debug.log*
|
||||||
package-lock.json
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
.hbuilderx
|
||||||
|
|
||||||
|
.stylelintcache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
docs/.vitepress/dist
|
||||||
|
docs/.vitepress/cache
|
||||||
|
|
||||||
|
src/types
|
||||||
|
# 单独把这个文件排除掉,用以解决部分电脑生成的 auto-import.d.ts 的API不完整导致类型提示报错问题
|
||||||
|
!src/types/auto-import.d.ts
|
||||||
|
src/manifest.json
|
||||||
|
src/pages.json
|
||||||
|
|
||||||
|
# 2025-10-15 by 菲鸽: lock 文件还是需要加入版本管理,今天又遇到版本不一致导致无法运行的问题了。
|
||||||
|
# pnpm-lock.yaml
|
||||||
|
# package-lock.json
|
||||||
|
|
||||||
|
# TIPS:如果某些文件已经加入了版本管理,现在重新加入 .gitignore 是不生效的,需要执行下面的操作
|
||||||
|
# `git rm -r --cached .` 然后提交 commit 即可。
|
||||||
|
|
||||||
|
# git rm -r --cached file1 file2 ## 针对某些文件
|
||||||
|
# git rm -r --cached dir1 dir2 ## 针对某些文件夹
|
||||||
|
# git rm -r --cached . ## 针对所有文件
|
||||||
|
|
||||||
|
# 更新 uni-app 官方版本
|
||||||
|
# npx @dcloudio/uvm@latest
|
||||||
|
|||||||
@@ -1,24 +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-weixin" :
|
|
||||||
{
|
|
||||||
"launchtype" : "local"
|
|
||||||
},
|
|
||||||
"type" : "uniCloud"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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,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
+106
@@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
// 配置语言的文件关联
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
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,73 +0,0 @@
|
|||||||
<script>
|
|
||||||
import HaloConfig from '@/config/halo.config.js';
|
|
||||||
import HaloAdConfig from '@/config/ad.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: HaloConfig.apiUrl,
|
|
||||||
...HaloConfig,
|
|
||||||
haloAdConfig: HaloAdConfig
|
|
||||||
},
|
|
||||||
onLaunch: function() {
|
|
||||||
console.log('App Launch');
|
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
CheckAppUpdate();
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
CheckWxUpdate();
|
|
||||||
uni.$tm.vx.commit('setWxShare', HaloConfig.wxShareConfig);
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 监听中间按钮(暂时没有使用)
|
|
||||||
uni.onTabBarMidButtonTap(() => {
|
|
||||||
console.log('点击中间按钮');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化博主信息
|
|
||||||
uni.$tm.vx.actions('blogger/fnGetBlogger');
|
|
||||||
|
|
||||||
// 临时:检查是否有用户,没有的话添加一个默认的用户
|
|
||||||
uni.$tm.vx.actions('user/checkAndSetDefaultUser');
|
|
||||||
|
|
||||||
// 启动检查app的配置是否已经就绪,若未就绪则设置默认的
|
|
||||||
uni.$tm.vx.actions('setting/checkAndSetDefaultAppSettings');
|
|
||||||
},
|
|
||||||
onShow: function() {
|
|
||||||
console.log('App Show');
|
|
||||||
},
|
|
||||||
onHide: function() {
|
|
||||||
console.log('App Hide');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</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/>.
|
|
||||||
|
|||||||
@@ -1,110 +1,122 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://uni-halo.925i.cn" target="_blank" rel="noopener noreferrer">
|
<a href="https://uni-halo.925i.cn" target="_blank" rel="noopener noreferrer">
|
||||||
<img width="100" src="https://b.925i.cn/uni_halo/uni_halo_logo.png" alt="uni-halo logo" />
|
<img width="100" src="https://uni-halo.925i.cn/logo.png" alt="uni-halo logo" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center"><b>uni-halo</b> 基于Halo一款现代化的开源博客/CMS系统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>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://b.925i.cn">作者博客</a>
|
<a href="https://www.xiaoxiaomo.cn">作者主页</a>
|
||||||
<a href="https://uni-halo.925i.cn">文档地址</a>
|
<a href="https://uni-halo.925i.cn">官方文档</a>
|
||||||
<a href="https://gitee.com/ialley-workshop-open/uni-halo">Gitee仓库</a>
|
<a href="https://github.com/ialley-workshop-open/uni-halo">仓库地址</a>
|
||||||
<a href="https://github.com/ialley-workshop-open/uni-halo">Github仓库</a>
|
<a href="https://github.com/ialley-workshop-open/plugin-uni-halo">插件仓库</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
如果您觉得这个项目对您有帮助,可以帮作者买杯饮料鼓励鼓励!
|
如果您觉得这个项目对您有帮助,可以帮作者买杯饮料鼓励鼓励,同时为了项目能够持续发展,可以根据您的喜好支持一下本项目哦,非常感谢您的支持,作者也会更有动力持续维护和更新新的功能哦~
|
||||||
|
|
||||||
<table rules="none" align="center" border="0">
|
| 支付宝 | 微信 | QQ |
|
||||||
<tr>
|
|:---------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------:|
|
||||||
<td>
|
|  |  |  |
|
||||||
<center>
|
|
||||||
<img src="https://uni-halo.925i.cn/qrcode/zfb.png" width="100%" />
|
## 一、项目介绍
|
||||||
<br/>
|
|
||||||
<font color="AAAAAA">支付宝打赏</font>
|
基于 Halo 2.x 提供的 API 接口,为微信小程序提供的一套开源的博客应用。
|
||||||
</center>
|
|
||||||
</td>
|
- 完全免费开源,包括程序源码、插件源码
|
||||||
<td>
|
- 页面支持插件配置
|
||||||
<center>
|
- 使用最新流行的技术栈
|
||||||
<img src="https://uni-halo.925i.cn/qrcode/wx.png" width="100%" />
|
- 支持特色功能,恋爱日记
|
||||||
<br/>
|
- 理论支持编译为 小程序(推荐)、APP
|
||||||
<font color="AAAAAA">微信打赏</font>
|
|
||||||
</center>
|
### 部分截图
|
||||||
</td>
|
|
||||||
<td>
|
|首页|分类|我的|
|
||||||
<center>
|
|:--:|:--:|:--:|
|
||||||
<img src="https://uni-halo.925i.cn/qrcode/qq.png" width="100%" />
|
||||
|
||||||
<br/>
|
|
||||||
<font color="AAAAAA">QQ打赏</font>
|
|
||||||
</center>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
## 🌸 快速开始
|
|
||||||
|
|
||||||
详细部署文档请查阅 [uni-halo-doc](https://uni-halo.925i.cn/)
|
### 恋爱日记
|
||||||
|
|
||||||
- 1、拉取或下载项目<https://gitee.com/ialley-workshop-open/uni-halo>;
|
- 在 uni-halo 中,我们为您准备了一个恋爱日记的模块,您可以在其中记录您的恋爱故事,分享给您的朋友们,请扫示例小程序,在线体验恋爱日记的功能。
|
||||||
- 2、通过hbuilderx 导入项目;
|
|
||||||
- 3、命令行执行 npm i 安装依赖;
|

|
||||||
- 4、配置运行信息,找到项目根目录config目录,将`halo.config.template.js`修改为 `halo.config.js` 并设置相关信息;
|
|
||||||
- 5、点击hbuilderx 工具 右上角预览、或者点击工具栏 运行-内置浏览器运行;
|
<br/>
|
||||||
- 6、项目发行:
|
|
||||||
- 发行小程序:点击工具栏 发行 -> 小程序-微信
|
|
||||||
- 发行APP: 点击工具栏 发行 -> 原生App-云打包
|
|
||||||
|
|
||||||
|
|
||||||
## ⭐️ 功能清单
|
### 功能介绍
|
||||||
|
|
||||||
### 用户部分
|
- 基础功能:文章列表、文章分类、文章详情、个人图库、友情链接、我的瞬间
|
||||||
1. - [x] 文章分类
|
|
||||||
2. - [x] 文章详情&海报分享
|
|
||||||
3. - [x] 文章归档
|
|
||||||
4. - [x] 文章列表和搜索
|
|
||||||
5. - [x] 数据统计展示
|
|
||||||
6. - [x] 图库
|
|
||||||
7. - [x] 友链
|
|
||||||
8. - [x] 留言板
|
|
||||||
9. - [x] 个人日记
|
|
||||||
|
|
||||||
### 管理部分
|
- 特色功能:恋爱日记,精心为您设计了秀恩爱的模块,秀出关于你们的故事、情侣甜蜜相册、恋爱记录清单,为您的恋爱保驾护航。
|
||||||
1. - [x] 个人信息修改
|
|
||||||
2. - [x] 个人密码修改
|
|
||||||
3. - [x] 日记管理
|
|
||||||
4. - [x] 文章管理
|
|
||||||
5. - [x] 分类管理
|
|
||||||
6. - [x] 标签管理
|
|
||||||
7. - [x] 附件管理
|
|
||||||
8. - [x] 友链管理
|
|
||||||
9. - [x] 图库管理
|
|
||||||
10. - [x] 评论管理
|
|
||||||
11. - [x] 日志管理
|
|
||||||
12. - [ ] 菜单设置
|
|
||||||
13. - [ ] 系统设置
|
|
||||||
|
|
||||||
## 📃 TODO
|
|
||||||
1. - [ ] 暗黑模式
|
|
||||||
2. - [ ] 国际化
|
|
||||||
|
|
||||||
|
|
||||||
## 🌈 在线体验
|
## 二、配套插件
|
||||||
|
|
||||||
- 敬请期待...
|
### 下载地址
|
||||||
|
- 代码仓库:[https://github.com/ialley-workshop-open/plugin-uni-halo](https://github.com/ialley-workshop-open/plugin-uni-halo)
|
||||||
|
- 仓库版本:[https://github.com/ialley-workshop-open/plugin-uni-halo/releases](https://github.com/ialley-workshop-open/plugin-uni-halo/releases)
|
||||||
## 🔍 许可证
|
- 应用市场:[https://www.halo.run/store/apps/app-ryemX](https://www.halo.run/store/apps/app-ryemX)
|
||||||
|
|
||||||
|
### 使用方式
|
||||||
|
|
||||||
|
直接下载安装插件,在插件市场安装即可,安装完成后,需要配置插件,配置完成后,即可使用插件提供的功能。
|
||||||
|
|
||||||
|
|
||||||
|
## 三、相关文档
|
||||||
|
|
||||||
|
- 作者主页:[https://www.xiaoxiaomo.cn/](https://www.xiaoxiaomo.cn/)
|
||||||
|
- 作者博客:[https://blog.xiaoxiaomo.cn/](https://blog.xiaoxiaomo.cn/)
|
||||||
|
- 官方文档:[https://uni-halo.925i.cn/](https://uni-halo.925i.cn/)
|
||||||
|
- 代码仓库:[https://github.com/ialley-workshop-open/uni-halo](https://github.com/ialley-workshop-open/uni-halo)
|
||||||
|
- 插件地址:[https://github.com/ialley-workshop-open/plugin-uni-halo](https://github.com/ialley-workshop-open/uni-halo)
|
||||||
|
- 应用市场:[https://www.halo.run/store/apps/app-ryemX](https://www.halo.run/store/apps/app-ryemX)
|
||||||
|
|
||||||
|
|
||||||
|
## 四、使用交流
|
||||||
|
|
||||||
|
- 提交 issues:[Github Issues](https://github.com/ialley-workshop-open/uni-halo/issues)
|
||||||
|
- QQ 交流群:632969367
|
||||||
|
- 微信交流群:由于微信交流群时效性太短,所以有需要的话可以先加入QQ群,再邀请进微信群
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
## 🎉 加入我们
|
||||||
|
|
||||||
|
uni-halo 还在持续更新中,本项目欢迎您的参与,共同维护,逐步完善,将项目做得更强。项目采用 AGPL-3.0 开源协议,本着一切免费的原则,原则上不会收取任何费用及版权,可以放心使用。
|
||||||
|
|
||||||
|
如果你想加入我们,可以多提供一些好的建议或者提交 pr,我们将会非常乐意接受您的建议和意见。
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
## 💪 贡献者们
|
||||||
|
|
||||||
|
感谢以下所有给 uni-halo 贡献过代码的 [开发者](https://github.com/ialley-workshop-open/uni-halo/graphs/contributors)。
|
||||||
|
|
||||||
|
<a href="https://github.com/ialley-workshop-open/uni-halo/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=ialley-workshop-open/uni-halo" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
## 🔍 开源许可
|
||||||
|
|
||||||
uni-halo 使用 AGPL-3.0 协议开源,请遵守开源协议。
|
uni-halo 使用 AGPL-3.0 协议开源,请遵守开源协议。
|
||||||
|
|
||||||
## 🏭 贡献
|
<br/>
|
||||||
|
|
||||||
贡献代码请查看 [代码贡献规范](https://uni-halo.925i.cn/standard/category.html)
|
## 🙋 致谢
|
||||||
|
|
||||||
## 🙆♂ Halo
|
uni-halo 得益于以下优秀的技术支撑,我只是站在巨人的肩膀上捡了几根头发丝,但是非常感谢这些优秀的项目,带给了我们其他开发者和用户非常友好的体验!
|
||||||
- 接口文档地址(内容端):<https://api.halo.run/content-api.html>
|
|
||||||
- 接口文档地址(管理端):<https://api.halo.run/admin-api.html>
|
- [Halo:一款好用又强大的开源建站工具](https://halo.run/)
|
||||||
|
- [uni-app:是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码可编译到多个平台](https://uniapp.dcloud.net.cn/)
|
||||||
|
- [tm-vuetify:是一个为 uni-app 平台定制的颜值也非常高的 UI 框架](https://www.jx2d.cn/)
|
||||||
|
- [mp-html:一个强大的小程序富文本组件](https://jin-yufeng.gitee.io/mp-html/#/)
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
/**
|
|
||||||
* 附件管理
|
|
||||||
* @see https://api.halo.run/admin-api.html#tag/attachment-controller
|
|
||||||
*/
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 分页获取附件列表
|
|
||||||
* {
|
|
||||||
* "attachmentType": "ALIOSS" "BAIDUBOS" "HUAWEIOBS" "LOCAL" "MINIO" "QINIUOSS" "SMMS" "TENCENTCOS" "UPOSS",
|
|
||||||
* "keyword": "string"
|
|
||||||
* "mediaType": "string"
|
|
||||||
* "page": "string"
|
|
||||||
* "size": "string"
|
|
||||||
* "sort": "string"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getAttachmentsByPage: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/attachments', params, {})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有的附件类型
|
|
||||||
*/
|
|
||||||
getAttachmentsMediaTypes: () => {
|
|
||||||
return HttpHandler.Get('/api/admin/attachments/media_types')
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据附件类型获取所有的附件列表
|
|
||||||
*/
|
|
||||||
getAttachmentsTypes: () => {
|
|
||||||
return HttpHandler.Get('/api/admin/attachments/types')
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据附件Id获取附件详情
|
|
||||||
*/
|
|
||||||
getAttachmentsById: (attachmentId) => {
|
|
||||||
return HttpHandler.Get(`/api/admin/attachments/${attachmentId}`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传附件-单文件(file)
|
|
||||||
* {
|
|
||||||
* file:文件对象
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
uploadAttachment: (data) => {
|
|
||||||
return HttpHandler.Upload(`/api/admin/attachments/upload`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传附件-多文件(files)
|
|
||||||
* {
|
|
||||||
* files:文件对象集合
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
uploadAttachments: (data) => {
|
|
||||||
return HttpHandler.Upload(`/api/admin/attachments/uploads`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改一个附件信息
|
|
||||||
*/
|
|
||||||
updateAttachmentById: (attachmentId, name) => {
|
|
||||||
return HttpHandler.Put(`/api/admin/attachments/${attachmentId}`, {
|
|
||||||
name: name
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量删除附件(id集合)
|
|
||||||
*/
|
|
||||||
deleteAttachmentByIds: (attachmentIds = []) => {
|
|
||||||
return HttpHandler.Delete(`/api/admin/attachments`, attachmentIds)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除单个附件
|
|
||||||
*/
|
|
||||||
deleteAttachmentById: (attachmentId) => {
|
|
||||||
return HttpHandler.Delete(`/api/admin/attachments/${attachmentId}`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/**
|
|
||||||
* 文章分类管理
|
|
||||||
* @see https://api.halo.run/admin-api.html#tag/category-controller
|
|
||||||
*/
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 查询所有的文章分类
|
|
||||||
* {
|
|
||||||
* "sort": ["",""], // 排序
|
|
||||||
* "more": "Boolean" ,// 更多参数(回调)
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getCategoryList: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/categories', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询所有的文章分类(树形数据)
|
|
||||||
* {
|
|
||||||
* "sort": ["",""], // 排序
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getCategoryListTree: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/categories/tree_view', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询文章分类详情
|
|
||||||
* @param {Number} categoryId 分类ID
|
|
||||||
*/
|
|
||||||
getCategoryDetail: (categoryId) => {
|
|
||||||
return HttpHandler.Get(`/api/admin/categories/${categoryId}`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增文章分类
|
|
||||||
* {
|
|
||||||
* "description": "string",
|
|
||||||
* "id": 0,
|
|
||||||
* "name": "string",
|
|
||||||
* "parentId": 0,
|
|
||||||
* "password": "string",
|
|
||||||
* "priority": 0,
|
|
||||||
* "slug": "string",
|
|
||||||
* "thumbnail": "string"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
createCategory: (data) => {
|
|
||||||
return HttpHandler.Post(`/api/admin/categories`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改文章分类信息
|
|
||||||
* @param {Number} categoryId 分类id
|
|
||||||
* @param {Object} data 同新增
|
|
||||||
*/
|
|
||||||
updateCategoryById: (categoryId, data) => {
|
|
||||||
return HttpHandler.Put(`/api/admin/categories/${categoryId}`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除单个文章分类
|
|
||||||
* @param {Number} categoryId 文章分类id
|
|
||||||
*/
|
|
||||||
deleteCategoryById: (categoryId) => {
|
|
||||||
return HttpHandler.Delete(`/api/admin/categories/${categoryId}`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
/**
|
|
||||||
* 文章评论管理
|
|
||||||
* @see https://api.halo.run/admin-api.html#tag/journal-controller
|
|
||||||
*/
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 查询文章评论
|
|
||||||
* {
|
|
||||||
* "keyword":"", // 关键字
|
|
||||||
* "page": 0, // 分页索引
|
|
||||||
* "size": 10, // 分页大小
|
|
||||||
* "sort": ["",""], // 排序
|
|
||||||
* "status": "" , // 类型 "AUDITING" "PUBLISHED" "RECYCLE"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getPostsComments: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/posts/comments', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 回复文章评论
|
|
||||||
* {
|
|
||||||
* "allowNotification": true,
|
|
||||||
* "author": "string",
|
|
||||||
* "authorUrl": "string",
|
|
||||||
* "content": "string",
|
|
||||||
* "email": "string",
|
|
||||||
* "parentId": 0,
|
|
||||||
* "postId": 0
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
postPostsComments: (data) => {
|
|
||||||
return HttpHandler.Post('/api/admin/posts/comments', data)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 更新文章评论状态
|
|
||||||
* @param {Number} commentId id
|
|
||||||
* @param {String} status "AUDITING" "PUBLISHED" "RECYCLE"
|
|
||||||
*/
|
|
||||||
updatePostsCommentsStatus: (commentId, status) => {
|
|
||||||
return HttpHandler.Put(`/api/admin/posts/comments/${commentId}/status/${status}`)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 删除文章评论
|
|
||||||
* @param {Number} commentId id
|
|
||||||
*/
|
|
||||||
deletePostsCommentsById: (commentId) => {
|
|
||||||
return HttpHandler.Delete(`/api/admin/posts/comments/${commentId}`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询页面评论
|
|
||||||
* {
|
|
||||||
* "keyword":"", // 关键字
|
|
||||||
* "page": 0, // 分页索引
|
|
||||||
* "size": 10, // 分页大小
|
|
||||||
* "sort": ["",""], // 排序
|
|
||||||
* "status": "" , // 类型 "AUDITING" "PUBLISHED" "RECYCLE"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getSheetsComments: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/sheets/comments', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 回复页面评论
|
|
||||||
* {
|
|
||||||
* "allowNotification": true,
|
|
||||||
* "author": "string",
|
|
||||||
* "authorUrl": "string",
|
|
||||||
* "content": "string",
|
|
||||||
* "email": "string",
|
|
||||||
* "parentId": 0,
|
|
||||||
* "postId": 0
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
postSheetsComments: (data) => {
|
|
||||||
return HttpHandler.Post('/api/admin/sheets/comments', data)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 更新页面评论状态
|
|
||||||
* @param {Number} commentId id
|
|
||||||
* @param {String} status "AUDITING" "PUBLISHED" "RECYCLE"
|
|
||||||
*/
|
|
||||||
updateSheetsCommentsStatus: (commentId, status) => {
|
|
||||||
return HttpHandler.Put(`/api/admin/sheets/comments/${commentId}/status/${status}`)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 删除页面评论
|
|
||||||
* @param {Number} commentId id
|
|
||||||
*/
|
|
||||||
deleteSheetsCommentsById: (commentId) => {
|
|
||||||
return HttpHandler.Delete(`/api/admin/sheets/comments/${commentId}`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询日记评论
|
|
||||||
* {
|
|
||||||
* "keyword":"", // 关键字
|
|
||||||
* "page": 0, // 分页索引
|
|
||||||
* "size": 10, // 分页大小
|
|
||||||
* "sort": ["",""], // 排序
|
|
||||||
* "status": "" , // 类型 "AUDITING" "PUBLISHED" "RECYCLE"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getJournalsComments: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/journals/comments', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 回复日记评论
|
|
||||||
* {
|
|
||||||
* "allowNotification": true,
|
|
||||||
* "author": "string",
|
|
||||||
* "authorUrl": "string",
|
|
||||||
* "content": "string",
|
|
||||||
* "email": "string",
|
|
||||||
* "parentId": 0,
|
|
||||||
* "postId": 0
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
postJournalsComments: (data) => {
|
|
||||||
return HttpHandler.Post('/api/admin/journals/comments', data)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 更新日记评论状态
|
|
||||||
* @param {Number} commentId id
|
|
||||||
* @param {String} status "AUDITING" "PUBLISHED" "RECYCLE"
|
|
||||||
*/
|
|
||||||
updateJournalsCommentsStatus: (commentId, status) => {
|
|
||||||
return HttpHandler.Put(`/api/admin/journals/comments/${commentId}/status/${status}`)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 删除日记评论
|
|
||||||
* @param {Number} commentId id
|
|
||||||
*/
|
|
||||||
deleteJournalsCommentsById: (commentId) => {
|
|
||||||
return HttpHandler.Delete(`/api/admin/journals/comments/${commentId}`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/**
|
|
||||||
* 个人日记管理
|
|
||||||
* @see https://api.halo.run/admin-api.html#tag/journal-controller
|
|
||||||
*/
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 查询所有的日记列表
|
|
||||||
* {
|
|
||||||
* "keyword":"", // 关键字
|
|
||||||
* "page": 0, // 分页索引
|
|
||||||
* "size": 10, // 分页大小
|
|
||||||
* "sort": ["",""], // 排序
|
|
||||||
* "type": "" , // 类型 "INTIMATE" "PUBLIC"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getJournals: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/journals', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询最近的所有的日记列表
|
|
||||||
* {
|
|
||||||
* "top":number, // 数量
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getLatestJournals: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/journals/latest', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增个人日记
|
|
||||||
* {
|
|
||||||
* "content": "string",
|
|
||||||
* "keepRaw": true,
|
|
||||||
* "sourceContent": "string",
|
|
||||||
* "type": "INTIMATE",
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
createJournals: (data) => {
|
|
||||||
return HttpHandler.Post(`/api/admin/journals`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改个人日记信息
|
|
||||||
* @param {Number} journalsId id
|
|
||||||
* @param {Object} data 同新增
|
|
||||||
*/
|
|
||||||
updateJournalsById: (journalsId, data) => {
|
|
||||||
return HttpHandler.Put(`/api/admin/journals/${journalsId}`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除个人日记
|
|
||||||
* @param {Number} journalsId id
|
|
||||||
*/
|
|
||||||
deleteJournalsById: (journalsId) => {
|
|
||||||
return HttpHandler.Delete(`/api/admin/journals/${journalsId}`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/**
|
|
||||||
* 友链管理
|
|
||||||
* @see https://api.halo.run/admin-api.html#tag/link-controller
|
|
||||||
*/
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
// 获取友链列表
|
|
||||||
getLinkList: () => {
|
|
||||||
return HttpHandler.Get('/api/admin/links')
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 获取友链详情
|
|
||||||
* @params { Number } linkId 友链Id
|
|
||||||
*/
|
|
||||||
getLinkDetail: (linkId) => {
|
|
||||||
return HttpHandler.Get(`/api/admin/links/${linkId}`)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 新增友链
|
|
||||||
* {
|
|
||||||
* "description": "string",
|
|
||||||
* "logo": "string",
|
|
||||||
* "name": "string",
|
|
||||||
* "priority": 0,
|
|
||||||
* "team": "string",
|
|
||||||
* "url": "string"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
addLink: (data) => {
|
|
||||||
return HttpHandler.Post('/api/admin/links', data, {})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 修改友链
|
|
||||||
* {
|
|
||||||
* "description": "string",
|
|
||||||
* "logo": "string",
|
|
||||||
* "name": "string",
|
|
||||||
* "priority": 0,
|
|
||||||
* "team": "string",
|
|
||||||
* "url": "string"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
updateLink: (linkId, data) => {
|
|
||||||
return HttpHandler.Put(`/api/admin/links/${linkId}`, data, {})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 删除友链
|
|
||||||
* @params { Number } linkId 友链Id
|
|
||||||
*/
|
|
||||||
deleteLink: (linkId) => {
|
|
||||||
return HttpHandler.Delete(`/api/admin/links/${linkId}`)
|
|
||||||
},
|
|
||||||
|
|
||||||
// 获取友链分组
|
|
||||||
getLinkTeamList: (data) => {
|
|
||||||
return HttpHandler.Get('/api/admin/links/teams')
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/**
|
|
||||||
* 日志管理
|
|
||||||
* @see https://api.halo.run/admin-api.html#tag/link-controller
|
|
||||||
*/
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取日志列表列表
|
|
||||||
* params:{ top: Number}
|
|
||||||
*/
|
|
||||||
getLogsLatestList: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/logs/latest', params)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 获取日志列表列表
|
|
||||||
* params:{ page:Number,size:Number, sort:String }
|
|
||||||
*/
|
|
||||||
getLogsListByPage: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/logs', params)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 清空日志
|
|
||||||
*/
|
|
||||||
deleteAllLogs: () => {
|
|
||||||
return HttpHandler.Get(`/api/admin/logs/clear`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
/**
|
|
||||||
* 图库管理
|
|
||||||
* @see https://api.halo.run/admin-api.html#tag/photo-controller
|
|
||||||
*/
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 查询图片列表
|
|
||||||
* {
|
|
||||||
* "sort": ["",""], // 排序
|
|
||||||
* "more": "Boolean" ,// 更多参数(回调)
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getPhotos: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/photos', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询最近的图库列表(树形数据)
|
|
||||||
* {
|
|
||||||
* "sort": ["",""], // 排序
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getLatestPhotos: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/photos/latest', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询所有的图片分组
|
|
||||||
*/
|
|
||||||
getPhotosTeams: () => {
|
|
||||||
return HttpHandler.Get('/api/admin/photos/teams')
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询图片详情
|
|
||||||
* @param {Number} photoId id
|
|
||||||
*/
|
|
||||||
getPhotosDetail: (photoId) => {
|
|
||||||
return HttpHandler.Get(`/api/admin/photos/${photoId}`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增图片(单图)
|
|
||||||
*{
|
|
||||||
* "description": "string",
|
|
||||||
* "id": 0,
|
|
||||||
* "location": "string",
|
|
||||||
* "name": "string",
|
|
||||||
* "takeTime": "2019-08-24T14:15:22Z",
|
|
||||||
* "team": "string",
|
|
||||||
* "thumbnail": "string",
|
|
||||||
* "url": "string"
|
|
||||||
*}
|
|
||||||
*/
|
|
||||||
createPhotos: (data) => {
|
|
||||||
return HttpHandler.Post(`/api/admin/photos`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增图片(批量)
|
|
||||||
* {
|
|
||||||
* "description": "string",
|
|
||||||
* "id": 0,
|
|
||||||
* "location": "string",
|
|
||||||
* "name": "string",
|
|
||||||
* "takeTime": "2019-08-24T14:15:22Z",
|
|
||||||
* "team": "string",
|
|
||||||
* "thumbnail": "string",
|
|
||||||
* "url": "string"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
createPhotosBatch: (data) => {
|
|
||||||
return HttpHandler.Post(`/api/admin/photos/batch`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改图片信息
|
|
||||||
* @param {Number} photoId id
|
|
||||||
* @param {Object} data 同新增
|
|
||||||
*/
|
|
||||||
updatePhotosById: (photoId, data) => {
|
|
||||||
return HttpHandler.Put(`/api/admin/photos/${photoId}`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除单张图片
|
|
||||||
* @param {Number} photoId id
|
|
||||||
*/
|
|
||||||
deletePhotosById: (photoId) => {
|
|
||||||
return HttpHandler.Delete(`/api/admin/photos/${photoId}`)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 批量删除图片
|
|
||||||
* @param {Number} photoIds id数组
|
|
||||||
*/
|
|
||||||
deletePhotosBatchById: (photoIds) => {
|
|
||||||
return HttpHandler.Delete(`/api/admin/photos/batch`, photoIds)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
/**
|
|
||||||
* 文章管理
|
|
||||||
* @see https://api.halo.run/admin-api.html#tag/post-controller
|
|
||||||
*/
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新建和编辑文章字段
|
|
||||||
*/
|
|
||||||
const createOrEditModel = {
|
|
||||||
"categoryIds": [
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"content": "string",
|
|
||||||
"createTime": "2019-08-24T14:15:22Z",
|
|
||||||
"disallowComment": true,
|
|
||||||
"editorType": "MARKDOWN",
|
|
||||||
"keepRaw": true,
|
|
||||||
"metaDescription": "string",
|
|
||||||
"metaKeywords": "string",
|
|
||||||
"metas": [{
|
|
||||||
"key": "string",
|
|
||||||
"postId": 0,
|
|
||||||
"value": "string"
|
|
||||||
}],
|
|
||||||
"originalContent": "string",
|
|
||||||
"formatContent": "",
|
|
||||||
"password": "string",
|
|
||||||
"slug": "string",
|
|
||||||
"status": "DRAFT",
|
|
||||||
"summary": "string",
|
|
||||||
"tagIds": [
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"template": "string",
|
|
||||||
"thumbnail": "string",
|
|
||||||
"title": "string",
|
|
||||||
"topPriority": 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 查询文章列表
|
|
||||||
* @param {Object} params {
|
|
||||||
* categoryId,keyword,page,size,sort,
|
|
||||||
* status:"DRAFT" "INTIMATE" "PUBLISHED" "RECYCLE",statuses,more
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getPostsByPage: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/posts', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询最近的文章列表
|
|
||||||
* @param {Object} params {top:Number}
|
|
||||||
*/
|
|
||||||
getLatestPosts: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/posts/latest', params)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 根据状态查询文章列表
|
|
||||||
* @param {String} status:"DRAFT" "INTIMATE" "PUBLISHED" "RECYCLE"
|
|
||||||
* @param {Object} params:{ page,size,sort,more }
|
|
||||||
*/
|
|
||||||
getPostsPageByStatus: (status, params) => {
|
|
||||||
return HttpHandler.Get(`/api/admin/posts/status/${status}`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据文章id获取文章
|
|
||||||
* @param {Number} postsId 文章id
|
|
||||||
*/
|
|
||||||
getPostsById: (postsId) => {
|
|
||||||
return HttpHandler.Get(`/api/admin/posts/${postsId}`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增文章
|
|
||||||
* @param {Object} data 同新增
|
|
||||||
* @param {Boolean} isAutoSave 是否来源于自动保存
|
|
||||||
*/
|
|
||||||
createPosts: (data, isAutoSave = false) => {
|
|
||||||
return HttpHandler.Post(`/api/admin/posts?autoSave=${isAutoSave}`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改文章
|
|
||||||
* @param {Number} postsId id
|
|
||||||
* @param {Object} data 同新增
|
|
||||||
* @param {Boolean} isAutoSave 是否来源于自动保存
|
|
||||||
*/
|
|
||||||
updatePostsById: (postsId, data, isAutoSave = false) => {
|
|
||||||
return HttpHandler.Put(`/api/admin/posts/${postsId}?autoSave=${isAutoSave}`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改文章(草稿)
|
|
||||||
* @param {Number} postsId id
|
|
||||||
* @param {Object} data { content,keepRaw,originalContent }
|
|
||||||
*/
|
|
||||||
updatePostsDraftById: (postsId, data) => {
|
|
||||||
return HttpHandler.Put(`/api/admin/posts/${postsId}/status/draft/content`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改文章状态
|
|
||||||
* @param {Number} postsId id
|
|
||||||
* @param {String} status "DRAFT" "INTIMATE" "PUBLISHED" "RECYCLE"
|
|
||||||
*/
|
|
||||||
updatePostsDraftById: (postsId, status) => {
|
|
||||||
return HttpHandler.Put(`/api/admin/posts/${postsId}/status/${status}`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除文章(批量)
|
|
||||||
* @param {Array} postsIds ids
|
|
||||||
*/
|
|
||||||
deletePostsByIds: (postsIds) => {
|
|
||||||
return HttpHandler.Delete(`/api/admin/posts`, postsIds)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/**
|
|
||||||
* 标签管理
|
|
||||||
* @see https://api.halo.run/admin-api.html#tag/tag-controller
|
|
||||||
*/
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 查询文章标签列表
|
|
||||||
* {
|
|
||||||
* "sort": ["",""], // 排序
|
|
||||||
* "more": "Boolean" ,// 更多参数(回调)
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getTagsList: (params) => {
|
|
||||||
return HttpHandler.Get('/api/admin/tags', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询文章标签详情
|
|
||||||
* @param {Number} tagId id
|
|
||||||
*/
|
|
||||||
getTagsDetail: (tagId) => {
|
|
||||||
return HttpHandler.Get(`/api/admin/tags/${tagId}`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增文章标签
|
|
||||||
* {
|
|
||||||
* "color": "#e23d66", // 颜色选择器
|
|
||||||
* "name": "string",
|
|
||||||
* "slug": "string",
|
|
||||||
* "thumbnail": "string"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
createTags: (data) => {
|
|
||||||
return HttpHandler.Post(`/api/admin/tags`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改文章标签信息
|
|
||||||
* @param {Number} tagId id
|
|
||||||
* @param {Object} data 同新增
|
|
||||||
*/
|
|
||||||
updateTagsById: (tagId, data) => {
|
|
||||||
return HttpHandler.Put(`/api/admin/tags/${tagId}`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除文章标签
|
|
||||||
* @param {Number} tagId id
|
|
||||||
*/
|
|
||||||
deleteTagsById: (tagId) => {
|
|
||||||
return HttpHandler.Delete(`/api/admin/tags/${tagId}`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
/**
|
|
||||||
* 登录管理
|
|
||||||
* @see https://api.halo.run/admin-api.html#tag/admin-controller
|
|
||||||
*/
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
// 登录前检查
|
|
||||||
loginPreCheck: (data) => {
|
|
||||||
return HttpHandler.Post('/api/admin/login/precheck', data, {})
|
|
||||||
},
|
|
||||||
// 登录
|
|
||||||
login: (data) => {
|
|
||||||
return HttpHandler.Post('/api/admin/login', data, {})
|
|
||||||
},
|
|
||||||
// 刷新token
|
|
||||||
refreshToken: (refreshToken) => {
|
|
||||||
return HttpHandler.Post($`/api/admin/refresh/${refreshToken}`, {}, {})
|
|
||||||
},
|
|
||||||
|
|
||||||
// 退出登录
|
|
||||||
logout: () => {
|
|
||||||
return HttpHandler.Post('/api/admin/logout')
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取修改密码的验证码
|
|
||||||
* {
|
|
||||||
* "email": "string",
|
|
||||||
* "username": "string"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getResetPasswordCode: () => {
|
|
||||||
return HttpHandler.Post('/api/admin/password/code')
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置密码
|
|
||||||
* {
|
|
||||||
* "code": "string",
|
|
||||||
* "email": "string",
|
|
||||||
* "password": "stringst",
|
|
||||||
* "username": "string"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
resetPasswordByCode: (data) => {
|
|
||||||
return HttpHandler.Put('/api/admin/password/reset', data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取个人信息(当前登录的管理员)
|
|
||||||
*/
|
|
||||||
getAdminProfile: () => {
|
|
||||||
return HttpHandler.Get('/api/admin/users/profiles')
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改个人信息(当前登录的管理员)
|
|
||||||
* {
|
|
||||||
* "avatar": "string",
|
|
||||||
* "description": "string",
|
|
||||||
* "email": "string",
|
|
||||||
* "nickname": "string",
|
|
||||||
* "password": "stringst",
|
|
||||||
* "username": "string"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
updateAdminProfile: (data) => {
|
|
||||||
return HttpHandler.Put('/api/admin/users/profiles', data)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 修改密码
|
|
||||||
* {
|
|
||||||
* "confirmPassword": "string",
|
|
||||||
* "newPassword": "string",
|
|
||||||
* "oldPassword": "strings"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
modifyAdminPassword: (data) => {
|
|
||||||
return HttpHandler.Put('/api/admin/users/profiles/password', data)
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* 归档接口
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/archive-controlle
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取归档列表(按月)
|
|
||||||
*/
|
|
||||||
getMonthArchives: () => {
|
|
||||||
return HttpHandler.Get(`/api/content/archives/months`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取归档列表(按年)
|
|
||||||
*/
|
|
||||||
getYearArchives: () => {
|
|
||||||
return HttpHandler.Get(`/api/content/archives/years`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
/**
|
|
||||||
* 文章接口
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取文章列表
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getArticleList: (params) => {
|
|
||||||
return HttpHandler.Get('/api/content/posts', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取文章详情
|
|
||||||
* @param {String} articleId 文章id
|
|
||||||
*/
|
|
||||||
getArticleDetail: (articleId) => {
|
|
||||||
return HttpHandler.Get(`/api/content/posts/${articleId}`, {
|
|
||||||
formatDisabled: false,
|
|
||||||
sourceDisabled: true
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
/**
|
|
||||||
* 博主信息
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/user-controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取博主信息
|
|
||||||
*/
|
|
||||||
getBloggerInfo: () => {
|
|
||||||
return HttpHandler.Get(`/api/content/users/profile`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* 分类接口
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/category-controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 查询分类列表
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getCategoryList: (params) => {
|
|
||||||
return HttpHandler.Get('/api/content/categories', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询分类下的文章
|
|
||||||
* @param {String} slug 分类名称
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getCategoryPostList: (slug, params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/categories/${slug}/posts`, params)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/**
|
|
||||||
* 评论接口
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/post-controller
|
|
||||||
*/
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取评论列表接口(树形数据)
|
|
||||||
* @param {String} postId 文章id
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getPostCommentTree: (postId, params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/posts/${postId}/comments/tree_view`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取评论列表接口(列表数据)
|
|
||||||
* @param {String} postId 文章id
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getPostCommentList: (postId, params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/posts/${postId}/comments/list_view`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取置顶评论
|
|
||||||
* @param {String} postId 文章id
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getPostTopCommentList: (postId, params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/posts/${postId}/comments/top_view`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取评论的子评论列表
|
|
||||||
* @param {String} postId 文章id
|
|
||||||
* @param {String} commentParentId 要获取的评论id
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getPostChildrenCommentList: (postId, commentParentId, params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/posts/${postId}/comments/${commentParentId}/children`, params)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
/**
|
|
||||||
* 功能:全局API管理
|
|
||||||
* 作者:小莫唐尼
|
|
||||||
* 邮箱:studio@925i.cn
|
|
||||||
* 时间:2022年07月21日 19:14:44
|
|
||||||
* 版本:v0.1.0
|
|
||||||
* 修改记录:
|
|
||||||
* 修改内容:
|
|
||||||
* 修改人员:
|
|
||||||
* 修改时间:
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
import archive from './archive.js'
|
|
||||||
import article from './article.js'
|
|
||||||
import blogger from './blogger.js'
|
|
||||||
import category from './category.js'
|
|
||||||
import tag from './tag.js'
|
|
||||||
import comment from './comment.js'
|
|
||||||
import journal from './journal.js'
|
|
||||||
import link from './link.js'
|
|
||||||
import menu from './menu.js'
|
|
||||||
import option from './option.js'
|
|
||||||
import photo from './photo.js'
|
|
||||||
import post from './post.js'
|
|
||||||
import sheet from './sheet.js'
|
|
||||||
import statistics from './statistics.js'
|
|
||||||
import theme from './theme.js'
|
|
||||||
|
|
||||||
// 管理端
|
|
||||||
import admin_login from './admin/user.js'
|
|
||||||
import admin_links from './admin/links.js'
|
|
||||||
import admin_attachment from './admin/attachment.js'
|
|
||||||
import admin_category from './admin/category.js'
|
|
||||||
import admin_journal from './admin/journal.js'
|
|
||||||
import admin_photos from './admin/photos.js'
|
|
||||||
import admin_tags from './admin/tags.js'
|
|
||||||
import admin_comments from './admin/comments.js'
|
|
||||||
import admin_posts from './admin/posts.js'
|
|
||||||
import admin_logs from './admin/logs.js'
|
|
||||||
|
|
||||||
const ApiManager = {
|
|
||||||
...archive,
|
|
||||||
...article,
|
|
||||||
...blogger,
|
|
||||||
...category,
|
|
||||||
...tag,
|
|
||||||
...comment,
|
|
||||||
...journal,
|
|
||||||
...link,
|
|
||||||
...option,
|
|
||||||
...photo,
|
|
||||||
...post,
|
|
||||||
...sheet,
|
|
||||||
...statistics,
|
|
||||||
...theme,
|
|
||||||
// 管理端的api
|
|
||||||
admin: {
|
|
||||||
...admin_login,
|
|
||||||
...admin_links,
|
|
||||||
...admin_attachment,
|
|
||||||
...admin_category,
|
|
||||||
...admin_journal,
|
|
||||||
...admin_photos,
|
|
||||||
...admin_tags,
|
|
||||||
...admin_comments,
|
|
||||||
...admin_posts,
|
|
||||||
...admin_logs
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const install = (Vue) => {
|
|
||||||
Vue.prototype.$httpApi = ApiManager
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
install
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/**
|
|
||||||
* 日记接口
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/journal-controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取日记列表
|
|
||||||
* @param {String} journalId 日记id
|
|
||||||
*/
|
|
||||||
getJournals: () => {
|
|
||||||
return HttpHandler.Get(`/api/content/journals`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取日记详情
|
|
||||||
* @param {String} journalId 日记id
|
|
||||||
*/
|
|
||||||
getJournalDetail: (journalId) => {
|
|
||||||
return HttpHandler.Get(`/api/content/journals/${journalId}`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取日记置顶评论列表
|
|
||||||
* @param {String} journalId 日记id
|
|
||||||
*/
|
|
||||||
getJournalTopComments: (journalId) => {
|
|
||||||
return HttpHandler.Get(`/api/content/journals/${journalId}/comments/top_view`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取日记评论列表(列表形式)
|
|
||||||
* @param {String} journalId 日记id
|
|
||||||
*/
|
|
||||||
getJournalCommentList: (journalId) => {
|
|
||||||
return HttpHandler.Get(`/api/content/journals/${journalId}/comments/list_view`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取日记评论列表(树形式)
|
|
||||||
* @param {String} journalId 日记id
|
|
||||||
*/
|
|
||||||
getJournalCommentTree: (journalId) => {
|
|
||||||
return HttpHandler.Get(`/api/content/journals/${journalId}/comments/tree_view`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取日记评论列表(树形式)
|
|
||||||
* @param {String} journalId 日记id
|
|
||||||
* @param {String} commentParentId 评论id
|
|
||||||
*/
|
|
||||||
getJournalCommentChildren: (journalId, commentParentId) => {
|
|
||||||
return HttpHandler.Get(
|
|
||||||
`/api/content/journals/${journalId}/comments/${commentParentId}/children`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发表日记评论
|
|
||||||
* @param {Object} data 评论数据
|
|
||||||
*/
|
|
||||||
postJournalComments: (data) => {
|
|
||||||
return HttpHandler.Post(`/api/content/journals/comments`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 给日记点赞
|
|
||||||
* @param {String} journalId 日记id
|
|
||||||
*/
|
|
||||||
postJournalLikes: (journalId) => {
|
|
||||||
return HttpHandler.Post(`/api/content/journals/${journalId}/likes`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* 友链接口
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/link-controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取友链列表
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getLinkList: (params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/links`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取分组的友链列表
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getLinkListByTeam: (params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/links/team_view`, params)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
-112
@@ -1,112 +0,0 @@
|
|||||||
/**
|
|
||||||
* 普通用户登录
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 获取用户信息
|
|
||||||
export function getUserInfo() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uni.getUserProfile({
|
|
||||||
lang: 'zh_CN',
|
|
||||||
desc: '用户登录', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,
|
|
||||||
success: (res) => {
|
|
||||||
console.log(res, 'resss')
|
|
||||||
resolve(res.userInfo)
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getLogin() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uni.login({
|
|
||||||
success(res) {
|
|
||||||
console.log('----------getLogin ---------')
|
|
||||||
console.log(res)
|
|
||||||
resolve(res)
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.log(err, 'logoer')
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function wxLogin() {
|
|
||||||
uni.getProvider({
|
|
||||||
service: 'oauth',
|
|
||||||
success: function(res) {
|
|
||||||
//支持微信、qq和微博等
|
|
||||||
if (~res.provider.indexOf('weixin')) {
|
|
||||||
console.log(res, 'ress')
|
|
||||||
let _userInfo = getUserInfo();
|
|
||||||
let _loginRes = getLogin();
|
|
||||||
Promise.all([_userInfo, _loginRes]).then((res) => {
|
|
||||||
let userInfo = res[0];
|
|
||||||
let loginRes = res[1];
|
|
||||||
if (loginRes.errMsg == 'login:ok') {
|
|
||||||
uni.$tm.vx.commit('user/setWxLoginInfo', {
|
|
||||||
avatarUrl: userInfo.avatarUrl,
|
|
||||||
nickName: userInfo.nickName,
|
|
||||||
email: '',
|
|
||||||
url: ''
|
|
||||||
});
|
|
||||||
uni.showToast({
|
|
||||||
icon: 'none',
|
|
||||||
title: '登录成功!'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
icon: 'none',
|
|
||||||
title: '登录失败,请重试!'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
uni.showToast({
|
|
||||||
icon: 'none',
|
|
||||||
title: '登录失败,请重试!'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: function(err) {
|
|
||||||
uni.showToast({
|
|
||||||
icon: 'none',
|
|
||||||
title: '登录失败,请重试!'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function appWxLogin() {
|
|
||||||
uni.login({
|
|
||||||
provider: 'weixin',
|
|
||||||
success: function(loginRes) {
|
|
||||||
// 获取用户信息
|
|
||||||
uni.getUserInfo({
|
|
||||||
provider: 'weixin',
|
|
||||||
success: function(infoRes) {
|
|
||||||
uni.$tm.vx.commit('user/setWxLoginInfo', {
|
|
||||||
avatarUrl: infoRes.userInfo.avatarUrl,
|
|
||||||
nickName: infoRes.userInfo.nickName,
|
|
||||||
email: '',
|
|
||||||
url: ''
|
|
||||||
});
|
|
||||||
uni.showToast({
|
|
||||||
icon: 'none',
|
|
||||||
title: '登录成功!'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fail: function(err) {
|
|
||||||
uni.showToast({
|
|
||||||
icon: 'none',
|
|
||||||
title: '登录失败,请重试!'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* 菜单接口
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/menu-controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取菜单列表(列表)
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getMenuList: (params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/menus`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取菜单列表(树形)
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getMenuTree: (params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/menus/tree_view`, params)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
/**
|
|
||||||
* 配置接口
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/option-controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 根据key获取配置
|
|
||||||
* @param {String} key 配置的key
|
|
||||||
*/
|
|
||||||
getOptionByKey: (key) => {
|
|
||||||
return HttpHandler.Get(`/api/content/options/keys/${key}`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取配置列表(列表)
|
|
||||||
*/
|
|
||||||
getOptionList: () => {
|
|
||||||
return HttpHandler.Get(`/api/content/options/list_view`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取配置列表(键值对)
|
|
||||||
*/
|
|
||||||
getOptionMap: () => {
|
|
||||||
return HttpHandler.Get(`/api/content/options/map_view`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/**
|
|
||||||
* 图库接口
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/photo-controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取图库列表(分页查询)
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getPhotoListByPage: (params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/photos`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取图库列表(最新)
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getPhotoList: (params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/photos/latest`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取图库分组
|
|
||||||
*/
|
|
||||||
getPhotoTeams: () => {
|
|
||||||
return HttpHandler.Get(`/api/content/photos/teams`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
-82
@@ -1,82 +0,0 @@
|
|||||||
/**
|
|
||||||
* 文章接口
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/post-controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取文章列表
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getPostList: (params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/posts`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 评论文章
|
|
||||||
* @param {Object} data 数据
|
|
||||||
* {
|
|
||||||
* "allowNotification": true,
|
|
||||||
* "author": "string",
|
|
||||||
* "authorUrl": "string",
|
|
||||||
* "content": "string",
|
|
||||||
* "email": "string",
|
|
||||||
* "parentId": 0,
|
|
||||||
* "postId": 0
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
postCommentPost: (data) => {
|
|
||||||
return HttpHandler.Post(`/api/content/posts/comments`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 搜索文章
|
|
||||||
* @param {Object} data 数据
|
|
||||||
*/
|
|
||||||
getPostListByKeyword: (data) => {
|
|
||||||
return HttpHandler.Post(`/api/content/posts/search`, data)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据分类获取文章
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getPostDetailBySlug: (params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/posts/slug`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据文章id获取文章
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getPostDetailByPostId: (postId, params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/posts/${postId}`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 给文章点赞
|
|
||||||
* @param {Object} postId 文章id
|
|
||||||
*/
|
|
||||||
postLikePost: (postId) => {
|
|
||||||
return HttpHandler.Post(`/api/content/posts/${postId}/likes`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据当前文章id获取前一篇文章
|
|
||||||
* @param {Object} postId 文章id
|
|
||||||
*/
|
|
||||||
getPrevByCurrentPostId: (postId) => {
|
|
||||||
return HttpHandler.Get(`/api/content/posts/${postId}/prev`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据当前文章id获取下一篇文章
|
|
||||||
* @param {Object} postId 文章id
|
|
||||||
*/
|
|
||||||
getNextByCurrentPostId: (postId) => {
|
|
||||||
return HttpHandler.Get(`/api/content/posts/${postId}/next`)
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/**
|
|
||||||
* 自定义页面模板
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/sheet-controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取页面列表
|
|
||||||
* {
|
|
||||||
* page:
|
|
||||||
* size:
|
|
||||||
* sort:
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
getSheetsList: (params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/sheets`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据分类获取页面数据
|
|
||||||
*/
|
|
||||||
getSheetsListBySlug: (params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/sheets/slug`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取页面评论(列表)
|
|
||||||
*/
|
|
||||||
getSheetsCommentsListBySheetId: (sheetId, params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/sheets/${sheetId}/comments/list_view`, params)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 获取页面评论(树形)
|
|
||||||
*/
|
|
||||||
getSheetsCommentsTreeBySheetId: (sheetId, params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/sheets/${sheetId}/comments/tree_view`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取评论的子评论列表
|
|
||||||
* @param {String} sheetId 页面id
|
|
||||||
* @param {String} commentParentId 要获取的评论id
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getSheetsChildrenCommentList: (sheetId, commentParentId, params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/sheets/${sheetId}/comments/${commentParentId}/children`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 给页面添加一个评论
|
|
||||||
* {
|
|
||||||
* "allowNotification": true,
|
|
||||||
* "author": "string",
|
|
||||||
* "authorUrl": "string",
|
|
||||||
* "content": "string",
|
|
||||||
* "email": "string",
|
|
||||||
* "parentId": 0,
|
|
||||||
* "postId": 0
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
postSheetsComments: (data) => {
|
|
||||||
return HttpHandler.Post(`/api/content/sheets/comments`, data)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* 博客统计信息
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/statistic-controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取博客统计信息
|
|
||||||
*/
|
|
||||||
getBlogStatistics: () => {
|
|
||||||
return HttpHandler.Get(`/api/content/statistics`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取博客统计信息和用户信息
|
|
||||||
*/
|
|
||||||
getBlogStatisticsWithUser: () => {
|
|
||||||
return HttpHandler.Get(`/api/content/statistics/user`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
-24
@@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* 标签接口
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/tag-controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 查询标签列表
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getTagList: (params) => {
|
|
||||||
return HttpHandler.Get('/api/content/tags', params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询标签下的文章
|
|
||||||
* @param {String} slug 别名
|
|
||||||
* @param {Object} params 查询参数
|
|
||||||
*/
|
|
||||||
getTagPostsList: (slug, params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/tags/${slug}/posts`, params)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
* 主题设置
|
|
||||||
* @see https://api.halo.run/content-api.html#tag/theme-controller
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HttpHandler from '@/common/http/request.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取激活主题的信息
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
geActivationThemeList: (params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/themes/activation`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取激活的主题设置
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getActivationThemeSettings: (params) => {
|
|
||||||
return HttpHandler.Get(`/api/content/themes/activation/settings`, params)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据主题ID列出主题设置
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getThemeSettingsByThemeId: (themeId) => {
|
|
||||||
return HttpHandler.Get(`/api/content/themes/${themeId}/settings`)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过主题ID获取主题属性
|
|
||||||
* @param {Object} params 参数
|
|
||||||
*/
|
|
||||||
getThemePropertyByThemeId: (themeId) => {
|
|
||||||
return HttpHandler.Get(`/api/content/themes/${themeId}`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -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 HaloConfig from '@/config/halo.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 = HaloConfig.apiUrl;
|
|
||||||
|
|
||||||
config.header = {
|
|
||||||
...config.header,
|
|
||||||
'api-authorization': HaloConfig.apiAuthorization,
|
|
||||||
ContentType: 'application/json',
|
|
||||||
dataType: 'json'
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
})
|
|
||||||
setInterceptors(http)
|
|
||||||
|
|
||||||
export {
|
|
||||||
http
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/**
|
|
||||||
* 功能:http拦截
|
|
||||||
* 作者:小莫唐尼
|
|
||||||
* 邮箱:studio@925i.cn
|
|
||||||
* 时间:2022年07月21日 19:02:14
|
|
||||||
* 版本:v0.1.0
|
|
||||||
* 修改记录:
|
|
||||||
* 修改内容:
|
|
||||||
* 修改人员:
|
|
||||||
* 修改时间:
|
|
||||||
*/
|
|
||||||
import {
|
|
||||||
getAdminAccessToken
|
|
||||||
} from "@/utils/auth.js";
|
|
||||||
import {
|
|
||||||
delCache
|
|
||||||
} from "@/utils/storage";
|
|
||||||
export const setInterceptors = (http) => {
|
|
||||||
http.interceptors.request.use(
|
|
||||||
(config) => {
|
|
||||||
// 可使用async await 做异步操作
|
|
||||||
config.header = {
|
|
||||||
...config.header
|
|
||||||
// ... 可以直接加参数
|
|
||||||
};
|
|
||||||
if (getAdminAccessToken()) {
|
|
||||||
config.header['admin-authorization'] = getAdminAccessToken()
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
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'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return Promise.reject(response.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,29 +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,23 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* 可以以页面为单位来写,
|
|
||||||
* 比如首页的内容,写在index字段,个人中心写在center,共同部分写在common部分
|
|
||||||
* */
|
|
||||||
|
|
||||||
export default {
|
|
||||||
app: {
|
|
||||||
name: "alley studio",
|
|
||||||
author: "Evan Mo",
|
|
||||||
},
|
|
||||||
tabbar: {
|
|
||||||
home: "Home",
|
|
||||||
moments: "Moments",
|
|
||||||
publish: "Publish",
|
|
||||||
mall: "Mall",
|
|
||||||
mine: "Mine",
|
|
||||||
},
|
|
||||||
// 提示文本
|
|
||||||
tips: {
|
|
||||||
switchLang: "switch Lang",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* 可以以页面为单位来写,
|
|
||||||
* 比如首页的内容,写在index字段,个人中心写在center,共同部分写在common部分
|
|
||||||
* */
|
|
||||||
|
|
||||||
export default {
|
|
||||||
app: {
|
|
||||||
name: "巷子工坊",
|
|
||||||
author: "小莫唐尼",
|
|
||||||
},
|
|
||||||
tabbar: {
|
|
||||||
home: "首页",
|
|
||||||
moments: "动态",
|
|
||||||
publish: "发布",
|
|
||||||
mall: "商店",
|
|
||||||
mine: "我的",
|
|
||||||
},
|
|
||||||
// 提示文本
|
|
||||||
tips: {
|
|
||||||
switchLang: "切换语言",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/**
|
|
||||||
* markdown配置
|
|
||||||
*/
|
|
||||||
|
|
||||||
import HaloConfig from '@/config/halo.config.js'
|
|
||||||
export default {
|
|
||||||
domain: HaloConfig.apiUrl,
|
|
||||||
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.2em;
|
|
||||||
`,
|
|
||||||
h5: `
|
|
||||||
color: var(--main);
|
|
||||||
line-height: 24px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
position: relative;
|
|
||||||
font-size: 16px;
|
|
||||||
`,
|
|
||||||
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: 16px;color: #606266;word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 6px;background-color:#FFFFFF;',
|
|
||||||
|
|
||||||
loadingGif: HaloConfig.loadingGifUrl,
|
|
||||||
emptyGif: HaloConfig.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,67 +0,0 @@
|
|||||||
/**
|
|
||||||
* 功能:全局混入函数
|
|
||||||
* 作者:小莫唐尼
|
|
||||||
* 邮箱:studio@925i.cn
|
|
||||||
* 时间:2022年07月21日 17:39:32
|
|
||||||
* 版本:v0.1.0
|
|
||||||
* 修改记录:
|
|
||||||
* 修改内容:
|
|
||||||
* 修改人员:
|
|
||||||
* 修改时间:
|
|
||||||
*/
|
|
||||||
import HaloConfig from '@/config/halo.config.js';
|
|
||||||
import HaloAdConfig from '@/config/ad.config.js';
|
|
||||||
export default {
|
|
||||||
install(Vue) {
|
|
||||||
Vue.mixin({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
author: HaloConfig.author,
|
|
||||||
_isWechat: true,
|
|
||||||
haloAdConfig: HaloAdConfig
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// 获取全局应用设置
|
|
||||||
globalAppSettings() {
|
|
||||||
return uni.$tm.vx.getters().getSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
this._isWechat = true;
|
|
||||||
uni.$tm.vx.commit('setWxShare', HaloConfig.wxShareConfig);
|
|
||||||
// #endif
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
this._isWechat = false;
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* 设置页面标题
|
|
||||||
* @param {Object} title 标题
|
|
||||||
*/
|
|
||||||
fnSetPageTitle(title) {
|
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: title || HaloConfig.title
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 页面返回顶部
|
|
||||||
*/
|
|
||||||
fnToTopPage(duration = 500) {
|
|
||||||
duration = isNaN(duration) ? 500 : duration
|
|
||||||
uni.pageScrollTo({
|
|
||||||
scrollTop: 0,
|
|
||||||
duration: duration,
|
|
||||||
fail: (err) => {
|
|
||||||
console.log('err:', err);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,38 +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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,304 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="article-card " :class="cardType" @click="fnClickEvent('card')">
|
|
||||||
<view class="left">
|
|
||||||
<cache-image
|
|
||||||
class="thumbnail"
|
|
||||||
radius="12rpx"
|
|
||||||
:url="$utils.checkThumbnailUrl(article.thumbnail)"
|
|
||||||
:fileMd5="$utils.checkThumbnailUrl(article.thumbnail)"
|
|
||||||
mode="aspectFill"
|
|
||||||
></cache-image>
|
|
||||||
<!-- <image class="thumbnail" lazy-load :src="$utils.checkThumbnailUrl(article.thumbnail)" mode="aspectFill"></image> -->
|
|
||||||
</view>
|
|
||||||
<view class="right">
|
|
||||||
<view class="title">
|
|
||||||
<text class="is-top" v-if="article.topped">置顶</text>
|
|
||||||
<text class="title-text text-overflow">{{ article.title }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="content text-overflow-2">{{ article.summary }}</view>
|
|
||||||
<view class="foot">
|
|
||||||
<view class="create-time">
|
|
||||||
<text class="time-label">发布时间:</text>
|
|
||||||
{{ { d: article.createTime, f: 'yyyy-MM-dd' } | formatTime }}
|
|
||||||
</view>
|
|
||||||
<view class="visits">
|
|
||||||
<!-- <tm-icons :size="24" name="icon-filter-fill"></tm-icons> -->
|
|
||||||
浏览
|
|
||||||
<text class="number">{{ article.visits }}</text>
|
|
||||||
次
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'article-card',
|
|
||||||
props: {
|
|
||||||
from: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
article: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
cardType() {
|
|
||||||
// tb_image_text=上图下文
|
|
||||||
// tb_text_image=上文下图
|
|
||||||
if (this.from == 'home' && this.globalAppSettings.layout.home == 'h_row_col2') {
|
|
||||||
if (!['tb_image_text', 'tb_text_image', 'only_text'].some(x => x == this.globalAppSettings.layout.cardType)) {
|
|
||||||
return [this.from, this.globalAppSettings.layout.home, 'tb_image_text'];
|
|
||||||
}
|
|
||||||
return [this.from, this.globalAppSettings.layout.home, this.globalAppSettings.layout.cardType];
|
|
||||||
}
|
|
||||||
return [this.globalAppSettings.layout.home, this.globalAppSettings.layout.cardType];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fnClickEvent() {
|
|
||||||
this.$emit('on-click', this.article);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.article-card {
|
|
||||||
display: flex;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0 24rpx;
|
|
||||||
padding: 32rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
background-color: #ffff;
|
|
||||||
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
|
|
||||||
overflow: hidden;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
&.home {
|
|
||||||
&.h_row_col2 {
|
|
||||||
margin: 12rpx;
|
|
||||||
.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: 0;
|
|
||||||
.left {
|
|
||||||
width: 100%;
|
|
||||||
height: 340rpx;
|
|
||||||
.thumbnail {
|
|
||||||
::v-deep uni-image {
|
|
||||||
border-radius: 6rpx 6rpx 0 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.right {
|
|
||||||
padding-left: 0;
|
|
||||||
padding: 24rpx;
|
|
||||||
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;
|
|
||||||
font-size: 30rpx;
|
|
||||||
color: var(--main-text-color);
|
|
||||||
.is-top {
|
|
||||||
height: 40rpx;
|
|
||||||
padding: 0 12rpx;
|
|
||||||
margin-right: 10rpx;
|
|
||||||
line-height: 40rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
vertical-align: 4rpx;
|
|
||||||
color: #fff;
|
|
||||||
background-image: -webkit-linear-gradient(0deg, #3ca5f6 0, #a86af9 100%);
|
|
||||||
border-radius: 4rpx 12rpx;
|
|
||||||
}
|
|
||||||
&-text {
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
display: -webkit-box;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #909399;
|
|
||||||
height: 80rpx;
|
|
||||||
margin-top: 14rpx;
|
|
||||||
line-height: 42rpx;
|
|
||||||
}
|
|
||||||
.foot {
|
|
||||||
display: flex;
|
|
||||||
font-size: 24rpx;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
color: #909399;
|
|
||||||
margin-top: 18rpx;
|
|
||||||
|
|
||||||
.create-time {
|
|
||||||
font-size: 26rpx;
|
|
||||||
.time-label {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.visits {
|
|
||||||
.number {
|
|
||||||
padding: 0 6rpx;
|
|
||||||
font-size: 26rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="article-min-card" :class="[globalAppSettings.layout.cardType]" @click="fnClickEvent('card')">
|
|
||||||
<view class="left">
|
|
||||||
<cache-image
|
|
||||||
class="thumbnail"
|
|
||||||
radius="12rpx"
|
|
||||||
:url="$utils.checkThumbnailUrl(article.thumbnail)"
|
|
||||||
:fileMd5="$utils.checkThumbnailUrl(article.thumbnail)"
|
|
||||||
mode="aspectFill"
|
|
||||||
></cache-image>
|
|
||||||
</view>
|
|
||||||
<view class="right">
|
|
||||||
<view class="title text-overflow">{{ article.title }}</view>
|
|
||||||
<view class="content text-overflow">{{ article.summary }}</view>
|
|
||||||
<view class="foot">
|
|
||||||
<view class="create-time">
|
|
||||||
<!-- <text class="icon iconfont icon-clock"></text> -->
|
|
||||||
<text class="time-label">发布时间:</text>
|
|
||||||
{{ { d: article.createTime, f: 'yyyy-MM-dd' } | formatTime }}
|
|
||||||
</view>
|
|
||||||
<view class="visits">
|
|
||||||
<!-- <text class="icon iconfont icon-eye"></text> -->
|
|
||||||
浏览
|
|
||||||
<text class="number">{{ article.visits }}</text>
|
|
||||||
次
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'article-min-card',
|
|
||||||
props: {
|
|
||||||
article: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fnClickEvent() {
|
|
||||||
this.$emit('on-click', this.article);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.article-min-card {
|
|
||||||
display: flex;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
background-color: #ffff;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 12rpx 24rpx;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
padding: 16rpx;
|
|
||||||
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.05);
|
|
||||||
&.lr_image_text {
|
|
||||||
}
|
|
||||||
|
|
||||||
&.lr_text_image {
|
|
||||||
.left {
|
|
||||||
order: 2;
|
|
||||||
padding-left: 30rpx;
|
|
||||||
}
|
|
||||||
.right {
|
|
||||||
order: 1;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.tb_image_text {
|
|
||||||
flex-direction: column;
|
|
||||||
.left {
|
|
||||||
width: 100%;
|
|
||||||
height: 220rpx;
|
|
||||||
}
|
|
||||||
.right {
|
|
||||||
padding-left: 0;
|
|
||||||
width: 100%;
|
|
||||||
.title {
|
|
||||||
margin-top: 24rpx;
|
|
||||||
}
|
|
||||||
.foot {
|
|
||||||
justify-content: flex-start;
|
|
||||||
.create-time {
|
|
||||||
.time-label {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.visits {
|
|
||||||
margin-left: 24rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.tb_text_image {
|
|
||||||
flex-direction: column;
|
|
||||||
.left {
|
|
||||||
width: 100%;
|
|
||||||
height: 220rpx;
|
|
||||||
order: 2;
|
|
||||||
margin-top: 20rpx;
|
|
||||||
}
|
|
||||||
.right {
|
|
||||||
padding-left: 0;
|
|
||||||
width: 100%;
|
|
||||||
order: 1;
|
|
||||||
.foot {
|
|
||||||
justify-content: flex-start;
|
|
||||||
.create-time {
|
|
||||||
.time-label {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.visits {
|
|
||||||
margin-left: 24rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.only_text {
|
|
||||||
.left {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.right {
|
|
||||||
padding-left: 0;
|
|
||||||
.foot {
|
|
||||||
justify-content: flex-start;
|
|
||||||
.create-time {
|
|
||||||
.time-label {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.visits {
|
|
||||||
margin-left: 24rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
|
||||||
width: 180rpx;
|
|
||||||
height: 130rpx;
|
|
||||||
.thumbnail {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.right {
|
|
||||||
width: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding-left: 24rpx;
|
|
||||||
.title {
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--main-text-color);
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #909399;
|
|
||||||
margin-top: 14rpx;
|
|
||||||
}
|
|
||||||
.foot {
|
|
||||||
display: flex;
|
|
||||||
font-size: 24rpx;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
color: #909399;
|
|
||||||
margin-top: 14rpx;
|
|
||||||
.create-time {
|
|
||||||
font-size: 24rpx;
|
|
||||||
.time-label {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.icon {
|
|
||||||
font-size: 24rpx;
|
|
||||||
padding-right: 4rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.visits {
|
|
||||||
.icon {
|
|
||||||
font-size: 28rpx;
|
|
||||||
}
|
|
||||||
.number {
|
|
||||||
padding: 0 6rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
<template>
|
|
||||||
<tm-poup v-model="show" position="bottom" height="auto" @change="fnClose">
|
|
||||||
<view class="poup-head pa-24 text-align-center text-weight-b ">{{ title }}</view>
|
|
||||||
<view class="poup-body pa-24 pt-0 pb-0">
|
|
||||||
<view v-if="loading != 'success'" class="loading-wrap flex flex-center">
|
|
||||||
<view v-if="loading == 'loading'" class="loading">加载中...</view>
|
|
||||||
<view v-else class="error" @click="fnGetData()">加载失败,点击刷新!</view>
|
|
||||||
</view>
|
|
||||||
<block v-else>
|
|
||||||
<view v-if="total == 0" class="empty">无附件</view>
|
|
||||||
<scroll-view v-else class="poup-content" :enable-flex="true" :scroll-y="true" @touchmove.stop>
|
|
||||||
<view class="card-content">
|
|
||||||
<view class="card pa-12" v-for="(file, index) in dataList" :key="index" @click="fnOnSelect(file, index)">
|
|
||||||
<view class="card-inner round-3" :class="{ 'is-select': selectIndex == index }">
|
|
||||||
<cache-image v-if="file.isImage" class="cover" height="160rpx" :url="file.thumbPath" :fileMd5="file.thumbPath" mode="aspectFill"></cache-image>
|
|
||||||
<view v-else class="cover flex pl-46 pr-46 flex-center bg-gradient-blue-grey-accent text-align-center text-size-m">{{ file.mediaType }}</view>
|
|
||||||
|
|
||||||
<view class="name text-overflow text-size-m pa-12">{{ file.name }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
</block>
|
|
||||||
</view>
|
|
||||||
<view class="poup-foot pa-30 pb-12 pt-0">
|
|
||||||
<!-- 分页 -->
|
|
||||||
<view v-if="total > queryParams.size" class="mt-36 pl-24 pr-24">
|
|
||||||
<tm-pagination color="bg-gradient-blue-accent" :page.sync="queryParams.page" :total="total" :totalVisible="5" @change="fnGetPagingData"></tm-pagination>
|
|
||||||
</view>
|
|
||||||
<view class=" flex flex-center mt-12">
|
|
||||||
<tm-button size="m" theme="bg-gradient-blue-accent" @click="fnSava()">确定选用</tm-button>
|
|
||||||
<tm-button size="m" theme="bg-gradient-orange-accent" @click="fnUpload()">上传</tm-button>
|
|
||||||
<tm-button size="m" theme="bg-gradient-blue-grey-accent" @click="fnClose(false)">关 闭</tm-button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</tm-poup>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { getAdminAccessToken } from '@/utils/auth.js';
|
|
||||||
|
|
||||||
import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
|
|
||||||
import tmPagination from '@/tm-vuetify/components/tm-pagination/tm-pagination.vue';
|
|
||||||
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
|
||||||
export default {
|
|
||||||
name: 'attachment-select',
|
|
||||||
components: { tmPoup, tmPagination, tmButton },
|
|
||||||
props: {
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: '附件列表'
|
|
||||||
},
|
|
||||||
selectType: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
show: true,
|
|
||||||
loading: 'loading',
|
|
||||||
total: 0,
|
|
||||||
queryParams: {
|
|
||||||
size: 6,
|
|
||||||
page: 1
|
|
||||||
},
|
|
||||||
dataList: [],
|
|
||||||
select: null,
|
|
||||||
selectIndex: -1
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.fnGetData();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fnGetData() {
|
|
||||||
this.queryParams.page = 1;
|
|
||||||
this.fnGetPagingData(1);
|
|
||||||
},
|
|
||||||
fnGetPagingData(page) {
|
|
||||||
this.loading = 'loading';
|
|
||||||
const _params = {
|
|
||||||
...this.queryParams
|
|
||||||
};
|
|
||||||
_params.page = page - 1;
|
|
||||||
this.$httpApi.admin
|
|
||||||
.getAttachmentsByPage(_params)
|
|
||||||
.then(res => {
|
|
||||||
if (res.status == 200) {
|
|
||||||
this.total = res.data.total;
|
|
||||||
this.dataList = res.data.content.map(file => {
|
|
||||||
if (this.$utils.fnCheckIsFileType('image', file) && file.size / 1024 / 1024 > 2) {
|
|
||||||
file.isImage = false;
|
|
||||||
file.desc = '图片过大无法显示缩略图';
|
|
||||||
} else {
|
|
||||||
file.isImage = this.$utils.fnCheckIsFileType('image', file);
|
|
||||||
}
|
|
||||||
file.thumbPath = this.$utils.checkThumbnailUrl(file.thumbPath);
|
|
||||||
return file;
|
|
||||||
});
|
|
||||||
this.loading = 'success';
|
|
||||||
} else {
|
|
||||||
uni.$tm.toast('加载失败,请重试!');
|
|
||||||
this.loading = 'error';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
uni.$tm.toast('加载失败,请重试!');
|
|
||||||
this.loading = 'error';
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fnOnSelect(file, index) {
|
|
||||||
this.select = file;
|
|
||||||
this.selectIndex = index;
|
|
||||||
},
|
|
||||||
fnSava() {
|
|
||||||
if (this.selectType) {
|
|
||||||
if (this.$utils.fnCheckIsFileType(this.selectType, this.select)) {
|
|
||||||
this.$emit('on-select', this.select);
|
|
||||||
} else {
|
|
||||||
uni.$tm.toast('该附件类型不符合!');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.$emit('on-select', this.select);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fnClose(e) {
|
|
||||||
if (!e) {
|
|
||||||
this.$emit('on-close');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fnUpload() {
|
|
||||||
uni.chooseImage({
|
|
||||||
count: 1,
|
|
||||||
success: res => {
|
|
||||||
uni.uploadFile({
|
|
||||||
filePath: res.tempFilePaths[0],
|
|
||||||
header: {
|
|
||||||
'admin-authorization': getAdminAccessToken()
|
|
||||||
},
|
|
||||||
url: this.$baseApiUrl + '/api/admin/attachments/upload',
|
|
||||||
name: 'file',
|
|
||||||
success: upladRes => {
|
|
||||||
const _uploadRes = JSON.parse(upladRes.data);
|
|
||||||
if (_uploadRes.status == 200) {
|
|
||||||
uni.$tm.toast('上传成功!');
|
|
||||||
this.fnGetData(1);
|
|
||||||
} else {
|
|
||||||
uni.$tm.toast(_uploadRes.message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: err => {
|
|
||||||
uni.$tm.toast(err.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.poup-head {
|
|
||||||
}
|
|
||||||
.poup-body {
|
|
||||||
height: 50vh;
|
|
||||||
}
|
|
||||||
.loading-wrap {
|
|
||||||
height: 50vh;
|
|
||||||
background-color: #fafafa;
|
|
||||||
}
|
|
||||||
.poup-content {
|
|
||||||
height: inherit;
|
|
||||||
box-sizing: border-box;
|
|
||||||
.card-content {
|
|
||||||
height: inherit;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
width: 50%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
&-inner {
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.05);
|
|
||||||
border: 4rpx solid transparent;
|
|
||||||
&.is-select {
|
|
||||||
border-color: rgb(13, 141, 242);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.cover {
|
|
||||||
width: 100%;
|
|
||||||
height: 160rpx;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.name {
|
|
||||||
color: #303133;
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="bottom-tool-bar">
|
|
||||||
<tm-translate :auto="true" animation-name="fadeUp">
|
|
||||||
<view class="content flex">
|
|
||||||
<view class="input" @click="fnToComment()">
|
|
||||||
<text class="icon iconfont icon-edit"></text>
|
|
||||||
<text class="text">(*^▽^*)说点啥吧~</text>
|
|
||||||
</view>
|
|
||||||
<view class="right flex">
|
|
||||||
<!-- 点赞 -->
|
|
||||||
<view class="item likes" @click="fnDoLikes()">
|
|
||||||
<view class="iconfont icon-like"></view>
|
|
||||||
<view class="text">{{ tempPost.likes }}</view>
|
|
||||||
</view>
|
|
||||||
<!-- 评论 -->
|
|
||||||
<view class="item comment">
|
|
||||||
<view class="iconfont icon-comment-dots"></view>
|
|
||||||
<view class="text">{{ tempPost.commentCount }}</view>
|
|
||||||
</view>
|
|
||||||
<!-- 分享 -->
|
|
||||||
<view class="item share" @click="fnOnShare()"><text class="iconfont icon-share1"></text></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</tm-translate>
|
|
||||||
|
|
||||||
<tm-shareSheet @change="fnOnShareChange" :actions="share.list" title="分享文章" v-model="share.show"></tm-shareSheet>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
|
|
||||||
import tmShareSheet from '@/tm-vuetify/components/tm-shareSheet/tm-shareSheet.vue';
|
|
||||||
export default {
|
|
||||||
name: 'bottom-tool-bar',
|
|
||||||
components: {
|
|
||||||
tmTranslate,
|
|
||||||
tmShareSheet
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
// 文章数据
|
|
||||||
post: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
},
|
|
||||||
// 其他参数
|
|
||||||
params: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
share: {
|
|
||||||
show: false,
|
|
||||||
list: [
|
|
||||||
[
|
|
||||||
{ name: '微信好友', bgcolor: '#07c160', icon: 'icon-weixin', color: 'white' },
|
|
||||||
{ name: '朋友圈', bgcolor: '#04c887', icon: 'icon-pengyouquan', color: 'white' },
|
|
||||||
{ name: '生成海报', bgcolor: '#1dc0fd', icon: 'icon-QQ', color: 'white' }
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
tempPost: {}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
post: {
|
|
||||||
deep: true,
|
|
||||||
handler(val) {
|
|
||||||
console.log('watch', val);
|
|
||||||
this.tempPost = this.$utils.deepClone(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
console.log(this.post);
|
|
||||||
this.tempPost = this.$utils.deepClone(this.post);
|
|
||||||
console.log(this.tempPost);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fnToComment() {
|
|
||||||
this.$Router.push({
|
|
||||||
path: '/pagesA/comment/comment',
|
|
||||||
query: {
|
|
||||||
postId: this.post.id,
|
|
||||||
parentId: 0,
|
|
||||||
title: this.post.title,
|
|
||||||
formPage: 'comment_list',
|
|
||||||
type: 'post'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fnDoLikes() {
|
|
||||||
this.$httpApi
|
|
||||||
.postLikePost(this.post.id)
|
|
||||||
.then(res => {
|
|
||||||
if (res.status == 200) {
|
|
||||||
uni.showToast({
|
|
||||||
icon: 'none',
|
|
||||||
title: '点赞成功'
|
|
||||||
});
|
|
||||||
this.tempPost.likes += 1;
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
icon: 'none',
|
|
||||||
title: res.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
uni.showToast({
|
|
||||||
icon: 'none',
|
|
||||||
title: err.message
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fnOnShare() {
|
|
||||||
// this.$emit('on-share');
|
|
||||||
this.share.show = true;
|
|
||||||
},
|
|
||||||
fnOnShareChange(e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.bottom-tool-bar {
|
|
||||||
width: 100vw;
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 401;
|
|
||||||
|
|
||||||
::v-deep {
|
|
||||||
.tm-shareSheet-wk .uni-scroll-view-content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: space-between;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 24rpx;
|
|
||||||
background-color: #ffffff;
|
|
||||||
box-shadow: 0rpx -4rpx 24rpx rgba(0, 0, 0, 0.07);
|
|
||||||
border-radius: 24rpx 24rpx 0 0;
|
|
||||||
.input {
|
|
||||||
width: 280rpx;
|
|
||||||
padding: 12rpx 24rpx;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border-radius: 60rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #666;
|
|
||||||
.icon {
|
|
||||||
}
|
|
||||||
.text {
|
|
||||||
padding-left: 8rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
width: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding-left: 24rpx;
|
|
||||||
.item {
|
|
||||||
margin-left: 24rpx;
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
&.share {
|
|
||||||
.iconfont {
|
|
||||||
font-size: 36rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.iconfont {
|
|
||||||
font-size: 36rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.text {
|
|
||||||
padding-left: 6rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="cache-image">
|
|
||||||
<view v-if="loadStatus == 'loading'" class="img-loading" :style="[imgStyle, loadStyle]">
|
|
||||||
<!-- <text class="img-load-icon iconfont icon-loading"></text>
|
|
||||||
<text class="img-load-text">{{ loadText }}</text> -->
|
|
||||||
<image :src="loadingImgSrc" :style="[imgStyle]" mode="aspectFit"></image>
|
|
||||||
</view>
|
|
||||||
<view v-if="loadStatus == 'error'" class="img-error" :style="[imgStyle, loadErrStyle]">
|
|
||||||
<text class="img-err-icon iconfont icon-exclamation-circle"></text>
|
|
||||||
<text class="img-load-text">{{ loadErrText }}</text>
|
|
||||||
</view>
|
|
||||||
<image
|
|
||||||
v-show="loadStatus == 'success'"
|
|
||||||
:src="src"
|
|
||||||
@load="fnOnLoad"
|
|
||||||
@error="fnOnError"
|
|
||||||
:lazy-load="lazyLoad"
|
|
||||||
:style="[imgStyle]"
|
|
||||||
:mode="mode"
|
|
||||||
@click="$emit('on-click', url)"
|
|
||||||
></image>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import imageCache from '@/utils/imageCache.js';
|
|
||||||
export default {
|
|
||||||
name: 'cache-image',
|
|
||||||
props: {
|
|
||||||
url: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
lazyLoad: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
loadStyle: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
color: '#333'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loadErrStyle: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {
|
|
||||||
color: 'rgba(244, 67, 54,1)'
|
|
||||||
// backgroundColor: 'rgba(244, 67, 54,0.2)'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default: 'aspectFill'
|
|
||||||
},
|
|
||||||
loadText: {
|
|
||||||
type: String,
|
|
||||||
default: '加载中...'
|
|
||||||
},
|
|
||||||
loadErrText: {
|
|
||||||
type: String,
|
|
||||||
default: '加载失败'
|
|
||||||
},
|
|
||||||
fileMd5: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
styles: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: String,
|
|
||||||
default: '100%'
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: String,
|
|
||||||
default: '100%'
|
|
||||||
},
|
|
||||||
radius: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
imgStyle: {},
|
|
||||||
src: '', // 图片地址
|
|
||||||
loadStatus: 'loading'
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
loadingImgSrc() {
|
|
||||||
return getApp().globalData.loadingGifUrl;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
// 监听图片md5值的变化
|
|
||||||
fileMd5(val) {
|
|
||||||
// 查找获取图片缓存
|
|
||||||
this.fnGetImageCache();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
console.log('cache-image', this.url);
|
|
||||||
this.imgStyle = {
|
|
||||||
width: this.width,
|
|
||||||
height: this.height,
|
|
||||||
borderRadius: this.radius,
|
|
||||||
...this.styles
|
|
||||||
};
|
|
||||||
|
|
||||||
// 查找获取图片缓存
|
|
||||||
this.fnGetImageCache();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 查找获取图片缓存
|
|
||||||
async fnGetImageCache() {
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
var result = await imageCache.getImageCache(this.url, this.fileMd5);
|
|
||||||
if (result) {
|
|
||||||
this.src = result;
|
|
||||||
} else {
|
|
||||||
this.src = this.url;
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
// #ifndef APP-PLUS
|
|
||||||
this.src = this.url;
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
fnOnLoad() {
|
|
||||||
this.loadStatus = 'success';
|
|
||||||
},
|
|
||||||
fnOnError() {
|
|
||||||
this.loadStatus = 'error';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.cache-image {
|
|
||||||
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-err-icon {
|
|
||||||
font-size: 36rpx;
|
|
||||||
}
|
|
||||||
@keyframes xhRote {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="category-mini-card">
|
|
||||||
<!-- <image class="img" lazy-load :src="$utils.checkThumbnailUrl(category.thumbnail)" mode="aspectFill"></image> -->
|
|
||||||
<cache-image
|
|
||||||
class="img"
|
|
||||||
height="120rpx"
|
|
||||||
:url="$utils.checkThumbnailUrl(category.thumbnail)"
|
|
||||||
:fileMd5="$utils.checkThumbnailUrl(category.thumbnail)"
|
|
||||||
mode="aspectFill"
|
|
||||||
></cache-image>
|
|
||||||
|
|
||||||
<text class="label">{{ category.postCount }} 篇</text>
|
|
||||||
<view class="name">{{ category.name }}</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;
|
|
||||||
// border: 2rpx solid #f7f7f7;
|
|
||||||
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
|
|
||||||
.img {
|
|
||||||
width: 100%;
|
|
||||||
height: 120rpx;
|
|
||||||
border: 6rpx 6rpx 0 0;
|
|
||||||
}
|
|
||||||
.label {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 86rpx;
|
|
||||||
color: #03a9f4;
|
|
||||||
font-size: 24rpx;
|
|
||||||
background-color: rgba(255, 255, 255, 1);
|
|
||||||
border-radius: 0rpx 24rpx 0 0;
|
|
||||||
display: flex;
|
|
||||||
padding: 2rpx 12rpx;
|
|
||||||
padding-right: 24rpx;
|
|
||||||
}
|
|
||||||
.name {
|
|
||||||
font-size: 24rpx;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--main-text-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class=" comment-item flex flex-col mt-30 pt-24" :class="{ 'child-comment-item': isChild, 'no-solid': !useSolid, classItem }">
|
|
||||||
<view class="comment-item_user flex">
|
|
||||||
<image
|
|
||||||
v-if="comment.isAdmin"
|
|
||||||
class="user-avatar"
|
|
||||||
:class="{ 'is-radius': globalAppSettings.isAvatarRadius }"
|
|
||||||
:src="bloggerInfo.avatar"
|
|
||||||
mode="aspectFill"
|
|
||||||
@error="fnOnImageError(comment)"
|
|
||||||
></image>
|
|
||||||
<image
|
|
||||||
v-else
|
|
||||||
class="user-avatar"
|
|
||||||
:class="{ 'is-radius': globalAppSettings.isAvatarRadius }"
|
|
||||||
:src="comment.avatar"
|
|
||||||
mode="aspectFill"
|
|
||||||
@error="fnOnImageError(comment)"
|
|
||||||
></image>
|
|
||||||
<view class="user-info pl-14">
|
|
||||||
<view class="author">
|
|
||||||
<text class="mr-6 text-grey-darken-1 text-size-m">{{ comment.author }}</text>
|
|
||||||
<tm-tags v-if="comment.isAdmin" :dense="true" color="bg-gradient-amber-accent" size="xs" model="fill">博主</tm-tags>
|
|
||||||
|
|
||||||
<tm-tags v-else :dense="true" color="bg-gradient-light-blue-lighten " size="xs" model="fill">游客</tm-tags>
|
|
||||||
</view>
|
|
||||||
<view class="flex mt-4">
|
|
||||||
<view v-if="false" class="text-size-s text-grey mr-12">IP属地:暂无信息</view>
|
|
||||||
<view class="time text-size-xs text-grey">
|
|
||||||
<text class="">{{ $tm.dayjs(comment.createTime).format('YYYY年MM月DD日') }}</text>
|
|
||||||
<text class="ml-12">{{ $tm.dayjs(comment.createTime).fromNow(true) }}前</text>
|
|
||||||
</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.content)">复制</tm-button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="comment-item_content mt-12" :class="{ 'has-bg': useContentBg, 'not-ml': isChild }" @click="$emit('on-detail', comment)" v-html="comment.content"></view>
|
|
||||||
<!-- <view v-if="useActions" class="comment-item_info text-size-s text-grey">
|
|
||||||
<text v-if="false" @click="$emit('on-todo')">点赞</text>
|
|
||||||
<text @click="$emit('on-comment', { type: 'user', comment: comment })">回复</text>
|
|
||||||
<text v-if="false" class="ml-24" @click="$emit('on-todo')">举报</text>
|
|
||||||
<text class="ml-24" @click="$emit('on-copy', comment.content)">复制内容</text>
|
|
||||||
</view> -->
|
|
||||||
</view>
|
|
||||||
</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
|
|
||||||
},
|
|
||||||
postId: {
|
|
||||||
type: Number,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
comment: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// 获取博主信息
|
|
||||||
bloggerInfo() {
|
|
||||||
let blogger = this.$tm.vx.getters().getBlogger;
|
|
||||||
blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
|
|
||||||
return blogger;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fnOnImageError(data) {
|
|
||||||
if (data.isAdmin) {
|
|
||||||
data.avatar = this.$haloConfig.author.avatar;
|
|
||||||
} else {
|
|
||||||
data.avatar = `${this.$haloConfig.defaultAvatarUrl}&rt=${new Date().getTime()}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</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,282 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="comment-list">
|
|
||||||
<!-- 顶部区域 -->
|
|
||||||
<view class="comment-list_head">
|
|
||||||
<view class="title">
|
|
||||||
评论列表
|
|
||||||
<text class="count">({{ result.total || 0 }}条)</text>
|
|
||||||
</view>
|
|
||||||
<view class="filter">
|
|
||||||
<text class="filter-item " :class="{ active: sort == 0 }" @click="fnOnSort(0)">默认</text>
|
|
||||||
<text class="filter-item " :class="{ active: sort == 1 }" @click="fnOnSort(1)">热评</text>
|
|
||||||
<!-- <text class="filter-item">全部</text> -->
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- 内容区域 -->
|
|
||||||
<view 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"
|
|
||||||
close
|
|
||||||
></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="bg-gradient-light-blue-accent" size="m" @click="fnToComment(null)">抢沙发</tm-button>
|
|
||||||
</tm-empty>
|
|
||||||
</view>
|
|
||||||
<block v-else>
|
|
||||||
<!-- 评论内容 : 目前仅支持二级评论 -->
|
|
||||||
<block v-for="(comment, index) in dataList" :key="comment.id">
|
|
||||||
<comment-item
|
|
||||||
:useContentBg="false"
|
|
||||||
:isChild="false"
|
|
||||||
:comment="comment"
|
|
||||||
:postId="postId"
|
|
||||||
:disallowComment="disallowComment"
|
|
||||||
@on-copy="fnCopyContent"
|
|
||||||
@on-comment="fnToComment"
|
|
||||||
@on-todo="fnToDo"
|
|
||||||
@on-detail="fnShowCommetnDetail"
|
|
||||||
></comment-item>
|
|
||||||
|
|
||||||
<!-- 二级评论 -->
|
|
||||||
<block v-if="comment.children && comment.children.length != 0">
|
|
||||||
<block v-for="(childComment, childIndex) in comment.children" :key="childComment.id">
|
|
||||||
<comment-item
|
|
||||||
:useContentBg="false"
|
|
||||||
:isChild="true"
|
|
||||||
:comment="childComment"
|
|
||||||
:postId="postId"
|
|
||||||
:disallowComment="disallowComment"
|
|
||||||
@on-copy="fnCopyContent"
|
|
||||||
@on-comment="fnToComment"
|
|
||||||
@on-todo="fnToDo"
|
|
||||||
@on-detail="fnShowCommetnDetail"
|
|
||||||
></comment-item>
|
|
||||||
</block>
|
|
||||||
</block>
|
|
||||||
</block>
|
|
||||||
<view v-if="false" class="to-more-comment">
|
|
||||||
<tm-button item-class="btn" :block="true" width="90vw" theme="bg-gradient-light-blue-lighten" size="m">点击查看全部评论</tm-button>
|
|
||||||
</view>
|
|
||||||
</block>
|
|
||||||
</block>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</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
|
|
||||||
},
|
|
||||||
postId: {
|
|
||||||
type: Number,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
post: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: 'loading',
|
|
||||||
sort: 0,
|
|
||||||
queryParams: {
|
|
||||||
sort: '',
|
|
||||||
more: true
|
|
||||||
},
|
|
||||||
api: 'getPostCommentTree',
|
|
||||||
result: {},
|
|
||||||
dataList: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.fnGetData();
|
|
||||||
uni.$on('comment_list_refresh', () => {
|
|
||||||
this.fnOnSort(this.sort, true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fnOnSort(type, refresh = false) {
|
|
||||||
if (this.sort == type && refresh == false) return;
|
|
||||||
const _api = ['getPostCommentTree', 'getPostTopCommentList'];
|
|
||||||
this.sort = type;
|
|
||||||
this.api = _api[type];
|
|
||||||
this.fnGetData();
|
|
||||||
},
|
|
||||||
fnGetData() {
|
|
||||||
this.loading = 'loading';
|
|
||||||
this.$httpApi[this.api](this.postId, {})
|
|
||||||
.then(res => {
|
|
||||||
if (res.status == 200) {
|
|
||||||
this.result = res.data;
|
|
||||||
this.dataList = res.data.content;
|
|
||||||
this.loading = 'success';
|
|
||||||
} else {
|
|
||||||
this.loading = 'error';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
this.loading = 'error';
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
uni.hideLoading();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fnToDo() {
|
|
||||||
uni.$tm.toast('Halo暂未支持!');
|
|
||||||
},
|
|
||||||
fnToComment(data) {
|
|
||||||
if (this.disallowComment) {
|
|
||||||
return uni.$tm.toast('文章已禁止评论!');
|
|
||||||
}
|
|
||||||
console.log(data);
|
|
||||||
let _comment = {};
|
|
||||||
if (data) {
|
|
||||||
let { type, comment } = data;
|
|
||||||
// 来自用户
|
|
||||||
_comment = {
|
|
||||||
id: this.post.id,
|
|
||||||
parentId: comment.id,
|
|
||||||
title: comment.author,
|
|
||||||
from: 'posts',
|
|
||||||
formPage: 'comment_list',
|
|
||||||
type: 'user'
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// 来自文章
|
|
||||||
_comment = {
|
|
||||||
id: this.post.id,
|
|
||||||
parentId: 0,
|
|
||||||
title: '评论文章:' + this.post.title,
|
|
||||||
formPage: 'comment_list',
|
|
||||||
from: 'posts',
|
|
||||||
type: 'post'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.$tm.vx.commit('comment/setCommentInfo', _comment);
|
|
||||||
this.$Router.push({
|
|
||||||
path: '/pagesA/comment/comment',
|
|
||||||
query: _comment
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fnCopyContent(content) {
|
|
||||||
uni.$tm.u.setClipboardData(content);
|
|
||||||
uni.$tm.toast('内容已复制成功!');
|
|
||||||
},
|
|
||||||
|
|
||||||
fnShowCommetnDetail(comment) {
|
|
||||||
this.$emit('on-comment-detail', {
|
|
||||||
postId: this.postId,
|
|
||||||
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,341 +0,0 @@
|
|||||||
// 轮播图
|
|
||||||
|
|
||||||
.Swiper-mfw-index-box {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
view {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Swiper-mfw-index {
|
|
||||||
// 轮播图
|
|
||||||
width: 100%;
|
|
||||||
.Swiper-mfw {
|
|
||||||
width: inherit;
|
|
||||||
height: 450rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
.swiper-mfw-item {
|
|
||||||
width: inherit;
|
|
||||||
height: inherit;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
.Image,
|
|
||||||
.ImageVideo {
|
|
||||||
border-radius: 12rpx;
|
|
||||||
width: inherit;
|
|
||||||
height: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 指示器
|
|
||||||
.Swiper-indicator-box {
|
|
||||||
width: inherit;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
// Top顶部 [今日首推-盒子]
|
|
||||||
.Top-date-hot {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 20rpx 24rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
.left-date-ri {
|
|
||||||
justify-content: center;
|
|
||||||
.date-ri-text {
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 60rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-top: -4rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.conter-date-nianyue {
|
|
||||||
margin: 0 14rpx;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
.left-width-bgcolor {
|
|
||||||
width: 8rpx;
|
|
||||||
height: 45rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
background-color: #fafafa;
|
|
||||||
margin-right: 14rpx;
|
|
||||||
}
|
|
||||||
.right-date-nianyue {
|
|
||||||
flex-direction: column;
|
|
||||||
.Top-yue-usa,
|
|
||||||
.Bottom-nian,
|
|
||||||
.text {
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
.text {
|
|
||||||
margin-top: -4rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.right-hot-ttf {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
margin-left: 6rpx;
|
|
||||||
.hot-text {
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 52rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 指示器 [轮播信息 -> 标题,用户,头像,所在地]
|
|
||||||
.Swiper-indicator-Top {
|
|
||||||
position: absolute;
|
|
||||||
left: 0rpx;
|
|
||||||
bottom: 110rpx;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 20rpx 24rpx;
|
|
||||||
&.no-dot {
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
.Top-item {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
// 如果有视频,则显示“视频预览”
|
|
||||||
.Top-ImageVideo {
|
|
||||||
width: 150rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 2rpx 6rpx;
|
|
||||||
margin-bottom: 10rpx;
|
|
||||||
background-color: #f0ad4e;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
align-items: center;
|
|
||||||
.Icons {
|
|
||||||
color: #242629;
|
|
||||||
font-size: 26rpx;
|
|
||||||
transform: scale(0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ImageVideo-text {
|
|
||||||
color: #242629;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标题
|
|
||||||
.Top-Title {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
.title-text {
|
|
||||||
width: 100%;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 用户信息盒子
|
|
||||||
.Bottom-UserInfo {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
margin-top: 16rpx;
|
|
||||||
align-items: center;
|
|
||||||
.UserImage-box {
|
|
||||||
width: 40rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
margin-right: 20rpx;
|
|
||||||
.Image {
|
|
||||||
width: 40rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.textbox {
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
.wo-text,
|
|
||||||
.UserInfo {
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
.wo-text {
|
|
||||||
color: #f1f2f6;
|
|
||||||
margin-right: 8rpx;
|
|
||||||
}
|
|
||||||
.UserInfo {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.jiange-box {
|
|
||||||
margin: 0 8rpx;
|
|
||||||
.jiange-text {
|
|
||||||
color: #f1f2f6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 指示器 [左边图片列表+右边按钮]
|
|
||||||
.Swiper-indicator-Bottom {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 14rpx;
|
|
||||||
background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), rgba(101, 101, 101, 0.7));
|
|
||||||
// 左边[图片列表]
|
|
||||||
.Bottom-left-Imagelist {
|
|
||||||
// width: 560rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
// 指示图(小图模式)
|
|
||||||
.Bottom-item {
|
|
||||||
width: 98rpx;
|
|
||||||
height: 78rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
|
|
||||||
.Image {
|
|
||||||
width: 98rpx;
|
|
||||||
height: 78rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
&.current {
|
|
||||||
.Image {
|
|
||||||
// border: 4rpx solid #ffda02;
|
|
||||||
border: 4rpx solid rgb(110, 186, 247);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.Bottom-item + .Bottom-item {
|
|
||||||
margin-left: 10rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 右边 [历历在目-按钮]
|
|
||||||
.Bottom-right-lili-btn {
|
|
||||||
width: 145rpx;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 10rpx;
|
|
||||||
// background-image: linear-gradient(45deg, rgb(110, 186, 247), rgb(13, 141, 242));
|
|
||||||
.Bottom-item {
|
|
||||||
width: 145rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #fff;
|
|
||||||
.indicator-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.more {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 26rpx;
|
|
||||||
.iconfont {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.text {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
// font-weight: normal;
|
|
||||||
letter-spacing: 4rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.Swiper-box {
|
|
||||||
border-radius: 12rpx;
|
|
||||||
overflow: hidden !important;
|
|
||||||
transform: translateY(0) !important;
|
|
||||||
transform: translateX(0) !important;
|
|
||||||
&.right {
|
|
||||||
.indicator-Top-box {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.Swiper-indicator-Top {
|
|
||||||
bottom: 0;
|
|
||||||
.Top-item .Top-Title .title-text {
|
|
||||||
width: 540rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.Swiper-indicator-Bottom {
|
|
||||||
width: 120rpx;
|
|
||||||
position: absolute;
|
|
||||||
top: 0rpx;
|
|
||||||
left: initial;
|
|
||||||
right: 0rpx;
|
|
||||||
flex-direction: column;
|
|
||||||
background-image: none;
|
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
|
||||||
// border-radius: 0rpx 12rpx 12rpx 0;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
.Bottom-left-Imagelist {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
.Bottom-item {
|
|
||||||
border-radius: 6rpx;
|
|
||||||
}
|
|
||||||
.Bottom-item + .Bottom-item {
|
|
||||||
margin-left: 0rpx;
|
|
||||||
margin-top: 10rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.Bottom-right-lili-btn {
|
|
||||||
width: 100rpx;
|
|
||||||
margin-left: -4rpx;
|
|
||||||
.Bottom-item {
|
|
||||||
width: initial;
|
|
||||||
margin-top: 6rpx;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
.more {
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
.text {
|
|
||||||
letter-spacing: 0;
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.Swiper-mfw {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.indicator-Top-box {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
.indicator-Btoom-box {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
@@ -1,233 +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条数据 -->
|
|
||||||
<swiper-item class="swiper-mfw-item" v-if="index <= (dotPosition == 'right' ? 3 : 4)" v-for="(item, index) in list" :key="index">
|
|
||||||
<!-- /*
|
|
||||||
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>
|
|
||||||
</swiper>
|
|
||||||
<!-- 指示器 [Top] -->
|
|
||||||
<view v-if="useTop" class="Swiper-indicator-box indicator-Top-box">
|
|
||||||
<!-- Top顶部 [今日首推-盒子] -->
|
|
||||||
<view class="Top-date-hot">
|
|
||||||
<view class="left-date-ri">
|
|
||||||
<text class="date-ri-text text">{{ date.month }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="conter-date-nianyue">
|
|
||||||
<view class="left-width-bgcolor"></view>
|
|
||||||
<view class="right-date-nianyue">
|
|
||||||
<text class="Top-yue-usa text">{{ date.monthEn }}</text>
|
|
||||||
<text class="Bottom-nian text">{{ date.year }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="right-hot-ttf">
|
|
||||||
<text class="text hot-text">{{ title }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 指示器 标题区域 -->
|
|
||||||
<view v-if="useTitle" class="Swiper-indicator-Top" :class="{ 'no-dot': !useDot }">
|
|
||||||
<block v-for="(item, index) in list" :key="index">
|
|
||||||
<view v-if="currentIndex == index" class="Top-item" :class="current == index ? 'current' : 'no'">
|
|
||||||
<!-- 如果存在视频,则显示“视频预览”提示 -->
|
|
||||||
<view v-if="item.mp4" class="Top-ImageVideo">
|
|
||||||
<!-- icon图标 -->
|
|
||||||
<view class="Icons">
|
|
||||||
<!-- 播放按钮图标 -->
|
|
||||||
<text class="iconfont icon-caret-right"></text>
|
|
||||||
</view>
|
|
||||||
<text class="text ImageVideo-text app-ttf">视频预览</text>
|
|
||||||
</view>
|
|
||||||
<!-- 标题 -->
|
|
||||||
<view class="Top-Title">
|
|
||||||
<text class="text title-text">{{ item.title }}</text>
|
|
||||||
</view>
|
|
||||||
<!-- 用户信息 -->
|
|
||||||
<view class="Bottom-UserInfo">
|
|
||||||
<!-- 头像 -->
|
|
||||||
<view class="UserImage-box"><image :src="item.avatar" class="Image" mode="aspectFill"></image></view>
|
|
||||||
<!-- 用户名 -->
|
|
||||||
<view class="textbox UserName-box">
|
|
||||||
<text class="text UserInfo">{{ item.nickname }}</text>
|
|
||||||
</view>
|
|
||||||
<view v-if="item.createTime" class="jiange-box"><text class="text jiange-text"></text></view>
|
|
||||||
<view v-if="item.createTime" class="textbox UserGPS-box">
|
|
||||||
<text class="text UserInfo">发布于 {{ item.createTime }}</text>
|
|
||||||
</view>
|
|
||||||
<view v-if="item.address" class="jiange-box"><text class="text jiange-text">·</text></view>
|
|
||||||
<view v-if="item.address" class="textbox UserGPS-box">
|
|
||||||
<text class="text UserInfo">{{ item.address }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</block>
|
|
||||||
</view>
|
|
||||||
<!-- 指示器 [左边图片列表+右边按钮] -->
|
|
||||||
<view v-if="useDot" class="Swiper-indicator-Bottom">
|
|
||||||
<!-- 左边 -->
|
|
||||||
<view class="Bottom-left-Imagelist">
|
|
||||||
<block v-for="(item, index) in list" :key="index">
|
|
||||||
<view
|
|
||||||
class="Bottom-item"
|
|
||||||
v-if="Number(index) <= (dotPosition == 'right' ? 3 : 4)"
|
|
||||||
:class="currentIndex == index ? 'current' : 'no'"
|
|
||||||
@click="SwiperIndTap(index)"
|
|
||||||
>
|
|
||||||
<image :src="item.image || item.src" class="Image" mode="aspectFill"></image>
|
|
||||||
</view>
|
|
||||||
</block>
|
|
||||||
</view>
|
|
||||||
<!-- 右边 -->
|
|
||||||
<view class="Bottom-right-lili-btn">
|
|
||||||
<view 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,177 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="journal-card mb-24 round-3 bg-white ">
|
|
||||||
<view class="head pa-24 pb-0 flex flex-between">
|
|
||||||
<view class="left flex">
|
|
||||||
<cache-image
|
|
||||||
class="avatar rounded"
|
|
||||||
radius="50%"
|
|
||||||
width="70rpx"
|
|
||||||
height="70rpx"
|
|
||||||
:url="bloggerInfo.avatar"
|
|
||||||
:fileMd5="bloggerInfo.avatar"
|
|
||||||
mode="scaleToFill"
|
|
||||||
></cache-image>
|
|
||||||
<view class="info pl-16 flex flex-col">
|
|
||||||
<view class="nickname text-weight-b text-grey-darken-4">{{ bloggerInfo.nickname }}</view>
|
|
||||||
<view class="mt-3 time text-size-m ">{{ $tm.dayjs(journal.createTime).format('YYYY-MM-DD HH:mm:ss') }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="right">
|
|
||||||
<tm-button v-if="useLike" :shadow="0" theme="light-blue" size="s" @click="fnLike(journal)">点赞({{ journal.likes }})</tm-button>
|
|
||||||
<tm-button v-if="useEdit" :shadow="0" theme="light-blue" size="s" @click="$emit('on-edit', journal)">编辑</tm-button>
|
|
||||||
<tm-button v-if="useDel" :shadow="0" theme="red" size="s" @click="fnDel(journal)">删除</tm-button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<tm-more v-if="journal.content.length > 50" :maxHeight="100" label="查看全部内容" open-label="隐藏部分内容">
|
|
||||||
<mp-html
|
|
||||||
class="evan-markdown"
|
|
||||||
lazy-load
|
|
||||||
:domain="markdownConfig.domain"
|
|
||||||
:loading-img="markdownConfig.loadingGif"
|
|
||||||
:scroll-table="true"
|
|
||||||
:selectable="true"
|
|
||||||
:tag-style="markdownConfig.tagStyle"
|
|
||||||
:container-style="markdownConfig.containStyle"
|
|
||||||
:content="journal.content"
|
|
||||||
:markdown="true"
|
|
||||||
:showLineNumber="true"
|
|
||||||
:showLanguageName="true"
|
|
||||||
:copyByLongPress="true"
|
|
||||||
/>
|
|
||||||
</tm-more>
|
|
||||||
<mp-html
|
|
||||||
v-else
|
|
||||||
class="evan-markdown"
|
|
||||||
lazy-load
|
|
||||||
:domain="markdownConfig.domain"
|
|
||||||
:loading-img="markdownConfig.loadingGif"
|
|
||||||
:scroll-table="true"
|
|
||||||
:selectable="true"
|
|
||||||
:tag-style="markdownConfig.tagStyle"
|
|
||||||
:container-style="markdownConfig.containStyle"
|
|
||||||
:content="journal.content"
|
|
||||||
:markdown="true"
|
|
||||||
:showLineNumber="true"
|
|
||||||
:showLanguageName="true"
|
|
||||||
:copyByLongPress="true"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MarkdownConfig from '@/common/markdown/markdown.config.js';
|
|
||||||
import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
|
|
||||||
import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
|
|
||||||
import tmMore from '@/tm-vuetify/components/tm-more/tm-more.vue';
|
|
||||||
export default {
|
|
||||||
name: 'journal-card',
|
|
||||||
components: { mpHtml, tmButton, tmMore },
|
|
||||||
props: {
|
|
||||||
isAdmin: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
journal: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
},
|
|
||||||
useLike: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
useEdit: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
useDel: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
markdownConfig: MarkdownConfig
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// 获取博主信息
|
|
||||||
bloggerInfo() {
|
|
||||||
let blogger = this.$tm.vx.getters().getBlogger;
|
|
||||||
blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
|
|
||||||
return blogger;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fnLike(journal) {
|
|
||||||
uni.showLoading({
|
|
||||||
mask: true,
|
|
||||||
title: '正在点赞中...'
|
|
||||||
});
|
|
||||||
this.$httpApi
|
|
||||||
.postJournalLikes(journal.id)
|
|
||||||
.then(res => {
|
|
||||||
if (res.status == 200) {
|
|
||||||
journal.likes += 1;
|
|
||||||
uni.$tm.toast('o( ̄▽ ̄)d点赞成功!');
|
|
||||||
} else {
|
|
||||||
uni.$tm.toast('Ծ‸Ծ点赞失败了~');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
uni.$tm.toast('Ծ‸Ծ点赞失败了~');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
fnDel(journal) {
|
|
||||||
uni.$eShowModal({
|
|
||||||
title: '提示',
|
|
||||||
content: '您确定要删除该日记吗?',
|
|
||||||
showCancel: true,
|
|
||||||
cancelText: '否',
|
|
||||||
cancelColor: '#999999',
|
|
||||||
confirmText: '是',
|
|
||||||
confirmColor: '#03a9f4'
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
this.$httpApi.admin
|
|
||||||
.deleteJournalsById(journal.id)
|
|
||||||
.then(res => {
|
|
||||||
if (res.status == 200) {
|
|
||||||
this.$emit('on-del', journal);
|
|
||||||
uni.$tm.toast('删除成功!');
|
|
||||||
} else {
|
|
||||||
uni.$tm.toast('Ծ‸Ծ删除失败~');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
uni.$tm.toast('Ծ‸Ծ删除失败~');
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.journal-card {
|
|
||||||
box-sizing: border-box;
|
|
||||||
box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.05);
|
|
||||||
overflow: hidden;
|
|
||||||
.avatar {
|
|
||||||
width: 70rpx;
|
|
||||||
height: 70rpx;
|
|
||||||
border: 6rpx solid #fff;
|
|
||||||
box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
.info {
|
|
||||||
justify-content: center;
|
|
||||||
.nickname {
|
|
||||||
font-size: 30rpx;
|
|
||||||
}
|
|
||||||
.time {
|
|
||||||
font-size: 26rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view style="overflow: hidden;position: fixed;width: 100%;height: 100%;pointer-events: none; top: 0;">
|
|
||||||
<view class="danmu-li" v-for="(item, index) in listData" :class="item.type" :style="item.style" :key="index">
|
|
||||||
<view class="danmu-inner">
|
|
||||||
<view class="user-box">
|
|
||||||
<view class="user-img">
|
|
||||||
<view class="img-box">
|
|
||||||
<image :src="item.avatar || 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=317894666,3379114684&fm=26&gp=0.jpg'"></image>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="user-text cl1">{{ item.nickName }}</view>
|
|
||||||
<view class="user-status cl1">{{ item.text }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
//rightToLeft leftToRight leftBottom
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: 'rightToLeft'
|
|
||||||
},
|
|
||||||
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
|
|
||||||
},
|
|
||||||
noStacked: {
|
|
||||||
//不允许堆叠(暂不可用)
|
|
||||||
type: Array,
|
|
||||||
default() {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
listData: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.hrackNum = Math.floor((this.maxTop - this.minTop) / this.hrackH);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
add(obj) {
|
|
||||||
let data = {
|
|
||||||
item: obj.item,
|
|
||||||
id: Date.parse(new Date()),
|
|
||||||
time: Math.ceil(Math.floor(Math.random() * (this.maxTime - this.minTime + 1) + this.minTime)),
|
|
||||||
type: this.type
|
|
||||||
};
|
|
||||||
if (this.type === 'leftBottom') {
|
|
||||||
let objData = {
|
|
||||||
item: data.item,
|
|
||||||
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 = 0;
|
|
||||||
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' || this.type === 'leftToRight') {
|
|
||||||
let objData = this.horStacked(data);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
horStacked(data) {
|
|
||||||
return {
|
|
||||||
item: data.item,
|
|
||||||
type: this.type,
|
|
||||||
style: {
|
|
||||||
animationDuration: `${data.time}s`,
|
|
||||||
top: `${Math.ceil(Math.random() * (this.maxTop - this.minTop + 1) + this.minTop)}px`
|
|
||||||
},
|
|
||||||
delTime: Date.parse(new Date()) + data.time * 1200
|
|
||||||
};
|
|
||||||
},
|
|
||||||
repaint(index, type) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.listData[index].type = type;
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<style></style>
|
|
||||||
<style lang="scss">
|
|
||||||
@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 leftToRight {
|
|
||||||
0% {
|
|
||||||
transform: translateX(-100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rightToLeft {
|
|
||||||
0% {
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: translateX(-100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.danmu-li {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
transform: translateX(100%);
|
|
||||||
animation-timing-function: linear;
|
|
||||||
|
|
||||||
&.leftBottomEnter {
|
|
||||||
animation-name: leftBottomEnter;
|
|
||||||
}
|
|
||||||
&.leftBottomExit {
|
|
||||||
animation-name: leftBottomExit;
|
|
||||||
animation-fill-mode: forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.rightToLeft {
|
|
||||||
animation-name: rightToLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.leftToRight {
|
|
||||||
animation-name: leftToRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
.danmu-inner {
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
.user-box {
|
|
||||||
display: flex;
|
|
||||||
padding: 3rpx 40rpx 3rpx 10rpx;
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
border-radius: 32rpx;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.user-img {
|
|
||||||
.img-box {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
image {
|
|
||||||
width: 58rpx;
|
|
||||||
height: 58rpx;
|
|
||||||
background: rgba(55, 55, 55, 1);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-status {
|
|
||||||
margin-left: 10rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 400;
|
|
||||||
color: rgba(255, 255, 255, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-text {
|
|
||||||
margin-left: 10rpx;
|
|
||||||
// white-space: nowrap;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 400;
|
|
||||||
width: 80rpx;
|
|
||||||
color: rgba(255, 255, 255, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#如何使用
|
|
||||||
###js
|
|
||||||
```javascript
|
|
||||||
import lffBarrage from '@/components/lff-barrage/lff-barrage.vue'
|
|
||||||
components:{lffBarrage},
|
|
||||||
methods:{
|
|
||||||
colrdo(){ //插入一条弹幕
|
|
||||||
this.$refs.lffBarrage.add({item:'你好呀小伙子'});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
###HTML
|
|
||||||
```html
|
|
||||||
<lff-barrage ref="lffBarrage"></lff-barrage>
|
|
||||||
```
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// 以下项目可以删减或更换顺序,但不能添加或更改名字
|
|
||||||
export default {
|
|
||||||
// 普通标签的菜单项
|
|
||||||
node: ['大小', '斜体', '粗体', '下划线', '居中', '缩进', '上移', '下移', '删除'],
|
|
||||||
// 图片的菜单项
|
|
||||||
img: ['换图', '宽度', '超链接', '预览图', '禁用预览', '上移', '下移', '删除'],
|
|
||||||
// 链接的菜单项
|
|
||||||
link: ['更换链接', '上移', '下移', '删除'],
|
|
||||||
// 音视频的菜单项
|
|
||||||
media: ['封面', '循环', '自动播放', '上移', '下移', '删除']
|
|
||||||
}
|
|
||||||
@@ -1,532 +0,0 @@
|
|||||||
/**
|
|
||||||
* @fileoverview editable 插件
|
|
||||||
*/
|
|
||||||
import config from './config'
|
|
||||||
import Parser from '../parser'
|
|
||||||
|
|
||||||
function Editable (vm) {
|
|
||||||
this.vm = vm
|
|
||||||
this.editHistory = [] // 历史记录
|
|
||||||
this.editI = -1 // 历史记录指针
|
|
||||||
vm._mask = [] // 蒙版被点击时进行的操作
|
|
||||||
|
|
||||||
vm._setData = function (path, val) {
|
|
||||||
const paths = path.split('.')
|
|
||||||
let target = vm
|
|
||||||
for (let i = 0; i < paths.length - 1; i++) {
|
|
||||||
target = target[paths[i]]
|
|
||||||
}
|
|
||||||
vm.$set(target, paths.pop(), val)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 移动历史记录指针
|
|
||||||
* @param {Number} num 移动距离
|
|
||||||
*/
|
|
||||||
const move = num => {
|
|
||||||
setTimeout(() => {
|
|
||||||
const item = this.editHistory[this.editI + num]
|
|
||||||
if (item) {
|
|
||||||
this.editI += num
|
|
||||||
vm._setData(item.key, item.value)
|
|
||||||
}
|
|
||||||
}, 200)
|
|
||||||
}
|
|
||||||
vm.undo = () => move(-1) // 撤销
|
|
||||||
vm.redo = () => move(1) // 重做
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 更新记录
|
|
||||||
* @param {String} path 更新内容路径
|
|
||||||
* @param {*} oldVal 旧值
|
|
||||||
* @param {*} newVal 新值
|
|
||||||
* @param {Boolean} set 是否更新到视图
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
vm._editVal = (path, oldVal, newVal, set) => {
|
|
||||||
// 当前指针后的内容去除
|
|
||||||
while (this.editI < this.editHistory.length - 1) {
|
|
||||||
this.editHistory.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最多存储 30 条操作记录
|
|
||||||
while (this.editHistory.length > 30) {
|
|
||||||
this.editHistory.pop()
|
|
||||||
this.editI--
|
|
||||||
}
|
|
||||||
|
|
||||||
const last = this.editHistory[this.editHistory.length - 1]
|
|
||||||
if (!last || last.key !== path) {
|
|
||||||
if (last) {
|
|
||||||
// 去掉上一次的新值
|
|
||||||
this.editHistory.pop()
|
|
||||||
this.editI--
|
|
||||||
}
|
|
||||||
// 存入这一次的旧值
|
|
||||||
this.editHistory.push({
|
|
||||||
key: path,
|
|
||||||
value: oldVal
|
|
||||||
})
|
|
||||||
this.editI++
|
|
||||||
}
|
|
||||||
|
|
||||||
// 存入本次的新值
|
|
||||||
this.editHistory.push({
|
|
||||||
key: path,
|
|
||||||
value: newVal
|
|
||||||
})
|
|
||||||
this.editI++
|
|
||||||
|
|
||||||
// 更新到视图
|
|
||||||
if (set) {
|
|
||||||
vm._setData(path, newVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 获取菜单项
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
vm._getItem = function (node, up, down) {
|
|
||||||
let items
|
|
||||||
let i
|
|
||||||
if (node.name === 'img') {
|
|
||||||
items = config.img.slice(0)
|
|
||||||
if (!vm.getSrc) {
|
|
||||||
i = items.indexOf('换图')
|
|
||||||
if (i !== -1) {
|
|
||||||
items.splice(i, 1)
|
|
||||||
}
|
|
||||||
i = items.indexOf('超链接')
|
|
||||||
if (i !== -1) {
|
|
||||||
items.splice(i, 1)
|
|
||||||
}
|
|
||||||
i = items.indexOf('预览图')
|
|
||||||
if (i !== -1) {
|
|
||||||
items.splice(i, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i = items.indexOf('禁用预览')
|
|
||||||
if (i !== -1 && node.attrs.ignore) {
|
|
||||||
items[i] = '启用预览'
|
|
||||||
}
|
|
||||||
} else if (node.name === 'a') {
|
|
||||||
items = config.link.slice(0)
|
|
||||||
if (!vm.getSrc) {
|
|
||||||
i = items.indexOf('更换链接')
|
|
||||||
if (i !== -1) {
|
|
||||||
items.splice(i, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (node.name === 'video' || node.name === 'audio') {
|
|
||||||
items = config.media.slice(0)
|
|
||||||
i = items.indexOf('封面')
|
|
||||||
if (!vm.getSrc && i !== -1) {
|
|
||||||
items.splice(i, 1)
|
|
||||||
}
|
|
||||||
i = items.indexOf('循环')
|
|
||||||
if (node.attrs.loop && i !== -1) {
|
|
||||||
items[i] = '不循环'
|
|
||||||
}
|
|
||||||
i = items.indexOf('自动播放')
|
|
||||||
if (node.attrs.autoplay && i !== -1) {
|
|
||||||
items[i] = '不自动播放'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
items = config.node.slice(0)
|
|
||||||
}
|
|
||||||
if (!up) {
|
|
||||||
i = items.indexOf('上移')
|
|
||||||
if (i !== -1) {
|
|
||||||
items.splice(i, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!down) {
|
|
||||||
i = items.indexOf('下移')
|
|
||||||
if (i !== -1) {
|
|
||||||
items.splice(i, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 显示 tooltip
|
|
||||||
* @param {object} obj
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
vm._tooltip = function (obj) {
|
|
||||||
vm.$set(vm, 'tooltip', {
|
|
||||||
top: obj.top,
|
|
||||||
items: obj.items
|
|
||||||
})
|
|
||||||
vm._tooltipcb = obj.success
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 显示滚动条
|
|
||||||
* @param {object} obj
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
vm._slider = function (obj) {
|
|
||||||
vm.$set(vm, 'slider', {
|
|
||||||
min: obj.min,
|
|
||||||
max: obj.max,
|
|
||||||
value: obj.value,
|
|
||||||
top: obj.top
|
|
||||||
})
|
|
||||||
vm._slideringcb = obj.changing
|
|
||||||
vm._slidercb = obj.change
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 点击蒙版
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
vm._maskTap = function () {
|
|
||||||
// 隐藏所有悬浮窗
|
|
||||||
while (vm._mask.length) {
|
|
||||||
(vm._mask.pop())()
|
|
||||||
}
|
|
||||||
if (vm.tooltip) {
|
|
||||||
vm.$set(vm, 'tooltip', null)
|
|
||||||
}
|
|
||||||
if (vm.slider) {
|
|
||||||
vm.$set(vm, 'slider', null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 插入节点
|
|
||||||
* @param {Object} node
|
|
||||||
*/
|
|
||||||
function insert (node) {
|
|
||||||
if (vm._edit) {
|
|
||||||
vm._edit.insert(node)
|
|
||||||
} else {
|
|
||||||
const nodes = vm.nodes.slice(0)
|
|
||||||
nodes.push(node)
|
|
||||||
vm._editVal('nodes', vm.nodes, nodes, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 在光标处插入指定 html 内容
|
|
||||||
* @param {String} html 内容
|
|
||||||
*/
|
|
||||||
vm.insertHtml = html => {
|
|
||||||
this.inserting = true
|
|
||||||
const arr = new Parser(vm).parse(html)
|
|
||||||
this.inserting = undefined
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
|
||||||
insert(arr[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 在光标处插入图片
|
|
||||||
*/
|
|
||||||
vm.insertImg = function () {
|
|
||||||
vm.getSrc && vm.getSrc('img').then(src => {
|
|
||||||
if (typeof src === 'string') {
|
|
||||||
src = [src]
|
|
||||||
}
|
|
||||||
const parser = new Parser(vm)
|
|
||||||
for (let i = 0; i < src.length; i++) {
|
|
||||||
insert({
|
|
||||||
name: 'img',
|
|
||||||
attrs: {
|
|
||||||
src: parser.getUrl(src[i])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).catch(() => { })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 在光标处插入一个链接
|
|
||||||
*/
|
|
||||||
vm.insertLink = function () {
|
|
||||||
vm.getSrc && vm.getSrc('link').then(url => {
|
|
||||||
insert({
|
|
||||||
name: 'a',
|
|
||||||
attrs: {
|
|
||||||
href: url
|
|
||||||
},
|
|
||||||
children: [{
|
|
||||||
type: 'text',
|
|
||||||
text: url
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
}).catch(() => { })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 在光标处插入一个表格
|
|
||||||
* @param {Number} rows 行数
|
|
||||||
* @param {Number} cols 列数
|
|
||||||
*/
|
|
||||||
vm.insertTable = function (rows, cols) {
|
|
||||||
const table = {
|
|
||||||
name: 'table',
|
|
||||||
attrs: {
|
|
||||||
style: 'display:table;width:100%;margin:10px 0;text-align:center;border-spacing:0;border-collapse:collapse;border:1px solid gray'
|
|
||||||
},
|
|
||||||
children: []
|
|
||||||
}
|
|
||||||
for (let i = 0; i < rows; i++) {
|
|
||||||
const tr = {
|
|
||||||
name: 'tr',
|
|
||||||
attrs: {},
|
|
||||||
children: []
|
|
||||||
}
|
|
||||||
for (let j = 0; j < cols; j++) {
|
|
||||||
tr.children.push({
|
|
||||||
name: 'td',
|
|
||||||
attrs: {
|
|
||||||
style: 'padding:2px;border:1px solid gray'
|
|
||||||
},
|
|
||||||
children: [{
|
|
||||||
type: 'text',
|
|
||||||
text: ''
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
table.children.push(tr)
|
|
||||||
}
|
|
||||||
insert(table)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 插入视频/音频
|
|
||||||
* @param {Object} node
|
|
||||||
*/
|
|
||||||
function insertMedia (node) {
|
|
||||||
if (typeof node.src === 'string') {
|
|
||||||
node.src = [node.src]
|
|
||||||
}
|
|
||||||
const parser = new Parser(vm)
|
|
||||||
// 拼接主域名
|
|
||||||
for (let i = 0; i < node.src.length; i++) {
|
|
||||||
node.src[i] = parser.getUrl(node.src[i])
|
|
||||||
}
|
|
||||||
insert({
|
|
||||||
name: 'div',
|
|
||||||
attrs: {
|
|
||||||
style: 'text-align:center'
|
|
||||||
},
|
|
||||||
children: [node]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 在光标处插入一个视频
|
|
||||||
*/
|
|
||||||
vm.insertVideo = function () {
|
|
||||||
vm.getSrc && vm.getSrc('video').then(src => {
|
|
||||||
insertMedia({
|
|
||||||
name: 'video',
|
|
||||||
attrs: {
|
|
||||||
controls: 'T'
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
src,
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
html: `<video src="${src}" style="width:100%;height:100%"></video>`
|
|
||||||
// #endif
|
|
||||||
})
|
|
||||||
}).catch(() => { })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 在光标处插入一个音频
|
|
||||||
*/
|
|
||||||
vm.insertAudio = function () {
|
|
||||||
vm.getSrc && vm.getSrc('audio').then(attrs => {
|
|
||||||
let src
|
|
||||||
if (attrs.src) {
|
|
||||||
src = attrs.src
|
|
||||||
attrs.src = undefined
|
|
||||||
} else {
|
|
||||||
src = attrs
|
|
||||||
attrs = {}
|
|
||||||
}
|
|
||||||
attrs.controls = 'T'
|
|
||||||
insertMedia({
|
|
||||||
name: 'audio',
|
|
||||||
attrs,
|
|
||||||
children: [],
|
|
||||||
src
|
|
||||||
})
|
|
||||||
}).catch(() => { })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 在光标处插入一段文本
|
|
||||||
*/
|
|
||||||
vm.insertText = function () {
|
|
||||||
insert({
|
|
||||||
name: 'p',
|
|
||||||
attrs: {},
|
|
||||||
children: [{
|
|
||||||
type: 'text',
|
|
||||||
text: ''
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 清空内容
|
|
||||||
*/
|
|
||||||
vm.clear = function () {
|
|
||||||
vm._maskTap()
|
|
||||||
vm._edit = undefined
|
|
||||||
vm.$set(vm, 'nodes', [{
|
|
||||||
name: 'p',
|
|
||||||
attrs: {},
|
|
||||||
children: [{
|
|
||||||
type: 'text',
|
|
||||||
text: ''
|
|
||||||
}]
|
|
||||||
}])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 获取编辑后的 html
|
|
||||||
*/
|
|
||||||
vm.getContent = function () {
|
|
||||||
let html = '';
|
|
||||||
// 递归遍历获取
|
|
||||||
(function traversal (nodes, table) {
|
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
|
||||||
let item = nodes[i]
|
|
||||||
if (item.type === 'text') {
|
|
||||||
html += item.text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>').replace(/\xa0/g, ' ') // 编码实体
|
|
||||||
} else {
|
|
||||||
if (item.name === 'img') {
|
|
||||||
item.attrs.i = ''
|
|
||||||
// 还原被转换的 svg
|
|
||||||
if ((item.attrs.src || '').includes('data:image/svg+xml;utf8,')) {
|
|
||||||
html += item.attrs.src.substr(24).replace(/%23/g, '#').replace('<svg', '<svg style="' + (item.attrs.style || '') + '"')
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else if (item.name === 'video' || item.name === 'audio') {
|
|
||||||
// 还原 video 和 audio 的 source
|
|
||||||
item = JSON.parse(JSON.stringify(item))
|
|
||||||
if (item.src.length > 1) {
|
|
||||||
item.children = []
|
|
||||||
for (let j = 0; j < item.src.length; j++) {
|
|
||||||
item.children.push({
|
|
||||||
name: 'source',
|
|
||||||
attrs: {
|
|
||||||
src: item.src[j]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
item.attrs.src = item.src[0]
|
|
||||||
}
|
|
||||||
} else if (item.name === 'div' && (item.attrs.style || '').includes('overflow:auto') && (item.children[0] || {}).name === 'table') {
|
|
||||||
// 还原滚动层
|
|
||||||
item = item.children[0]
|
|
||||||
}
|
|
||||||
// 还原 table
|
|
||||||
if (item.name === 'table') {
|
|
||||||
item = JSON.parse(JSON.stringify(item))
|
|
||||||
table = item.attrs
|
|
||||||
if ((item.attrs.style || '').includes('display:grid')) {
|
|
||||||
item.attrs.style = item.attrs.style.split('display:grid')[0]
|
|
||||||
const children = [{
|
|
||||||
name: 'tr',
|
|
||||||
attrs: {},
|
|
||||||
children: []
|
|
||||||
}]
|
|
||||||
for (let j = 0; j < item.children.length; j++) {
|
|
||||||
item.children[j].attrs.style = item.children[j].attrs.style.replace(/grid-[^;]+;*/g, '')
|
|
||||||
if (item.children[j].r !== children.length) {
|
|
||||||
children.push({
|
|
||||||
name: 'tr',
|
|
||||||
attrs: {},
|
|
||||||
children: [item.children[j]]
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
children[children.length - 1].children.push(item.children[j])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item.children = children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
html += '<' + item.name
|
|
||||||
for (const attr in item.attrs) {
|
|
||||||
let val = item.attrs[attr]
|
|
||||||
if (!val) continue
|
|
||||||
if (val === 'T' || val === true) {
|
|
||||||
// bool 型省略值
|
|
||||||
html += ' ' + attr
|
|
||||||
continue
|
|
||||||
} else if (item.name[0] === 't' && attr === 'style' && table) {
|
|
||||||
// 取消为了显示 table 添加的 style
|
|
||||||
val = val.replace(/;*display:table[^;]*/, '')
|
|
||||||
if (table.border) {
|
|
||||||
val = val.replace(/border[^;]+;*/g, $ => $.includes('collapse') ? $ : '')
|
|
||||||
}
|
|
||||||
if (table.cellpadding) {
|
|
||||||
val = val.replace(/padding[^;]+;*/g, '')
|
|
||||||
}
|
|
||||||
if (!val) continue
|
|
||||||
}
|
|
||||||
html += ' ' + attr + '="' + val.replace(/"/g, '"') + '"'
|
|
||||||
}
|
|
||||||
html += '>'
|
|
||||||
if (item.children) {
|
|
||||||
traversal(item.children, table)
|
|
||||||
html += '</' + item.name + '>'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})(vm.nodes)
|
|
||||||
|
|
||||||
// 其他插件处理
|
|
||||||
for (let i = vm.plugins.length; i--;) {
|
|
||||||
if (vm.plugins[i].onGetContent) {
|
|
||||||
html = vm.plugins[i].onGetContent(html) || html
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Editable.prototype.onUpdate = function (content, config) {
|
|
||||||
if (this.vm.editable) {
|
|
||||||
this.vm._maskTap()
|
|
||||||
config.entities.amp = '&'
|
|
||||||
if (!this.inserting) {
|
|
||||||
this.vm._edit = undefined
|
|
||||||
if (!content) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.vm.$set(this.vm, 'nodes', [{
|
|
||||||
name: 'p',
|
|
||||||
attrs: {},
|
|
||||||
children: [{
|
|
||||||
type: 'text',
|
|
||||||
text: ''
|
|
||||||
}]
|
|
||||||
}])
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Editable.prototype.onParse = function (node) {
|
|
||||||
// 空白单元格可编辑
|
|
||||||
if (this.vm.editable && (node.name === 'td' || node.name === 'th') && !this.vm.getText(node.children)) {
|
|
||||||
node.children.push({
|
|
||||||
type: 'text',
|
|
||||||
text: ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Editable
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
/**
|
|
||||||
* @fileoverview emoji 插件
|
|
||||||
*/
|
|
||||||
const reg = /\[(\S+?)\]/g
|
|
||||||
const data = {
|
|
||||||
笑脸: '😄',
|
|
||||||
生病: '😷',
|
|
||||||
破涕为笑: '😂',
|
|
||||||
吐舌: '😝',
|
|
||||||
脸红: '😳',
|
|
||||||
恐惧: '😱',
|
|
||||||
失望: '😔',
|
|
||||||
无语: '😒',
|
|
||||||
眨眼: '😉',
|
|
||||||
酷: '😎',
|
|
||||||
哭: '😭',
|
|
||||||
痴迷: '😍',
|
|
||||||
吻: '😘',
|
|
||||||
思考: '🤔',
|
|
||||||
困惑: '😕',
|
|
||||||
颠倒: '🙃',
|
|
||||||
钱: '🤑',
|
|
||||||
惊讶: '😲',
|
|
||||||
白眼: '🙄',
|
|
||||||
叹气: '😤',
|
|
||||||
睡觉: '😴',
|
|
||||||
书呆子: '🤓',
|
|
||||||
愤怒: '😡',
|
|
||||||
面无表情: '😑',
|
|
||||||
张嘴: '😮',
|
|
||||||
量体温: '🤒',
|
|
||||||
呕吐: '🤮',
|
|
||||||
光环: '😇',
|
|
||||||
幽灵: '👻',
|
|
||||||
外星人: '👽',
|
|
||||||
机器人: '🤖',
|
|
||||||
捂眼镜: '🙈',
|
|
||||||
捂耳朵: '🙉',
|
|
||||||
捂嘴: '🙊',
|
|
||||||
婴儿: '👶',
|
|
||||||
男孩: '👦',
|
|
||||||
女孩: '👧',
|
|
||||||
男人: '👨',
|
|
||||||
女人: '👩',
|
|
||||||
老人: '👴',
|
|
||||||
老妇人: '👵',
|
|
||||||
警察: '👮',
|
|
||||||
王子: '🤴',
|
|
||||||
公主: '🤴',
|
|
||||||
举手: '🙋',
|
|
||||||
跑步: '🏃',
|
|
||||||
家庭: '👪',
|
|
||||||
眼睛: '👀',
|
|
||||||
鼻子: '👃',
|
|
||||||
耳朵: '👂',
|
|
||||||
舌头: '👅',
|
|
||||||
嘴: '👄',
|
|
||||||
心: '❤️',
|
|
||||||
心碎: '💔',
|
|
||||||
雪人: '☃️',
|
|
||||||
情书: '💌',
|
|
||||||
大便: '💩',
|
|
||||||
闹钟: '⏰',
|
|
||||||
眼镜: '👓',
|
|
||||||
雨伞: '☂️',
|
|
||||||
音乐: '🎵',
|
|
||||||
话筒: '🎤',
|
|
||||||
游戏机: '🎮',
|
|
||||||
喇叭: '📢',
|
|
||||||
耳机: '🎧',
|
|
||||||
礼物: '🎁',
|
|
||||||
电话: '📞',
|
|
||||||
电脑: '💻',
|
|
||||||
打印机: '🖨️',
|
|
||||||
手电筒: '🔦',
|
|
||||||
灯泡: '💡',
|
|
||||||
书本: '📖',
|
|
||||||
信封: '✉️',
|
|
||||||
药丸: '💊',
|
|
||||||
口红: '💄',
|
|
||||||
手机: '📱',
|
|
||||||
相机: '📷',
|
|
||||||
电视: '📺',
|
|
||||||
中: '🀄',
|
|
||||||
垃圾桶: '🚮',
|
|
||||||
厕所: '🚾',
|
|
||||||
感叹号: '❗',
|
|
||||||
禁: '🈲',
|
|
||||||
可: '🉑',
|
|
||||||
彩虹: '🌈',
|
|
||||||
旋风: '🌀',
|
|
||||||
雷电: '⚡',
|
|
||||||
雪花: '❄️',
|
|
||||||
星星: '⭐',
|
|
||||||
水滴: '💧',
|
|
||||||
玫瑰: '🌹',
|
|
||||||
加油: '💪',
|
|
||||||
左: '👈',
|
|
||||||
右: '👉',
|
|
||||||
上: '👆',
|
|
||||||
下: '👇',
|
|
||||||
手掌: '🖐️',
|
|
||||||
好的: '👌',
|
|
||||||
好: '👍',
|
|
||||||
差: '👎',
|
|
||||||
胜利: '✌',
|
|
||||||
拳头: '👊',
|
|
||||||
挥手: '👋',
|
|
||||||
鼓掌: '👏',
|
|
||||||
猴子: '🐒',
|
|
||||||
狗: '🐶',
|
|
||||||
狼: '🐺',
|
|
||||||
猫: '🐱',
|
|
||||||
老虎: '🐯',
|
|
||||||
马: '🐎',
|
|
||||||
独角兽: '🦄',
|
|
||||||
斑马: '🦓',
|
|
||||||
鹿: '🦌',
|
|
||||||
牛: '🐮',
|
|
||||||
猪: '🐷',
|
|
||||||
羊: '🐏',
|
|
||||||
长颈鹿: '🦒',
|
|
||||||
大象: '🐘',
|
|
||||||
老鼠: '🐭',
|
|
||||||
蝙蝠: '🦇',
|
|
||||||
刺猬: '🦔',
|
|
||||||
熊猫: '🐼',
|
|
||||||
鸽子: '🕊️',
|
|
||||||
鸭子: '🦆',
|
|
||||||
兔子: '🐇',
|
|
||||||
老鹰: '🦅',
|
|
||||||
青蛙: '🐸',
|
|
||||||
蛇: '🐍',
|
|
||||||
龙: '🐉',
|
|
||||||
鲸鱼: '🐳',
|
|
||||||
海豚: '🐬',
|
|
||||||
足球: '⚽',
|
|
||||||
棒球: '⚾',
|
|
||||||
篮球: '🏀',
|
|
||||||
排球: '🏐',
|
|
||||||
橄榄球: '🏉',
|
|
||||||
网球: '🎾',
|
|
||||||
骰子: '🎲',
|
|
||||||
鸡腿: '🍗',
|
|
||||||
蛋糕: '🎂',
|
|
||||||
啤酒: '🍺',
|
|
||||||
饺子: '🥟',
|
|
||||||
汉堡: '🍔',
|
|
||||||
薯条: '🍟',
|
|
||||||
意大利面: '🍝',
|
|
||||||
干杯: '🥂',
|
|
||||||
筷子: '🥢',
|
|
||||||
糖果: '🍬',
|
|
||||||
奶瓶: '🍼',
|
|
||||||
爆米花: '🍿',
|
|
||||||
邮局: '🏤',
|
|
||||||
医院: '🏥',
|
|
||||||
银行: '🏦',
|
|
||||||
酒店: '🏨',
|
|
||||||
学校: '🏫',
|
|
||||||
城堡: '🏰',
|
|
||||||
火车: '🚂',
|
|
||||||
高铁: '🚄',
|
|
||||||
地铁: '🚇',
|
|
||||||
公交: '🚌',
|
|
||||||
救护车: '🚑',
|
|
||||||
消防车: '🚒',
|
|
||||||
警车: '🚓',
|
|
||||||
出租车: '🚕',
|
|
||||||
汽车: '🚗',
|
|
||||||
货车: '🚛',
|
|
||||||
自行车: '🚲',
|
|
||||||
摩托: '🛵',
|
|
||||||
红绿灯: '🚥',
|
|
||||||
帆船: '⛵',
|
|
||||||
游轮: '🛳️',
|
|
||||||
轮船: '⛴️',
|
|
||||||
飞机: '✈️',
|
|
||||||
直升机: '🚁',
|
|
||||||
缆车: '🚠',
|
|
||||||
警告: '⚠️',
|
|
||||||
禁止: '⛔'
|
|
||||||
}
|
|
||||||
|
|
||||||
function Emoji () {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Emoji.prototype.onUpdate = function (content) {
|
|
||||||
return content.replace(reg, ($, $1) => {
|
|
||||||
if (data[$1]) return data[$1]
|
|
||||||
return $
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Emoji.prototype.onGetContent = function (content) {
|
|
||||||
for (const item in data) {
|
|
||||||
content = content.replace(new RegExp(data[item], 'g'), '[' + item + ']')
|
|
||||||
}
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Emoji
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export default {
|
|
||||||
copyByLongPress: true, // 是否需要长按代码块时显示复制代码内容菜单
|
|
||||||
showLanguageName: true, // 是否在代码块右上角显示语言的名称
|
|
||||||
showLineNumber: true // 是否显示行号
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/**
|
|
||||||
* @fileoverview highlight 插件
|
|
||||||
* Include prismjs (https://prismjs.com)
|
|
||||||
*/
|
|
||||||
import prism from './prism.min'
|
|
||||||
import config from './config'
|
|
||||||
import Parser from '../parser'
|
|
||||||
|
|
||||||
function Highlight (vm) {
|
|
||||||
this.vm = vm
|
|
||||||
}
|
|
||||||
|
|
||||||
Highlight.prototype.onParse = function (node, vm) {
|
|
||||||
if (node.name === 'pre') {
|
|
||||||
if (vm.options.editable) {
|
|
||||||
node.attrs.class = (node.attrs.class || '') + ' hl-pre'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let i
|
|
||||||
for (i = node.children.length; i--;) {
|
|
||||||
if (node.children[i].name === 'code') break
|
|
||||||
}
|
|
||||||
if (i === -1) return
|
|
||||||
const code = node.children[i]
|
|
||||||
let className = code.attrs.class + ' ' + node.attrs.class
|
|
||||||
i = className.indexOf('language-')
|
|
||||||
if (i === -1) {
|
|
||||||
i = className.indexOf('lang-')
|
|
||||||
if (i === -1) {
|
|
||||||
className = 'language-text'
|
|
||||||
i = 9
|
|
||||||
} else {
|
|
||||||
i += 5
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
i += 9
|
|
||||||
}
|
|
||||||
let j
|
|
||||||
for (j = i; j < className.length; j++) {
|
|
||||||
if (className[j] === ' ') break
|
|
||||||
}
|
|
||||||
const lang = className.substring(i, j)
|
|
||||||
if (code.children.length) {
|
|
||||||
const text = this.vm.getText(code.children).replace(/&/g, '&')
|
|
||||||
if (!text) return
|
|
||||||
if (node.c) {
|
|
||||||
node.c = undefined
|
|
||||||
}
|
|
||||||
if (prism.languages[lang]) {
|
|
||||||
code.children = (new Parser(this.vm).parse(
|
|
||||||
// 加一层 pre 保留空白符
|
|
||||||
'<pre>' + prism.highlight(text, prism.languages[lang], lang).replace(/token /g, 'hl-') + '</pre>'))[0].children
|
|
||||||
}
|
|
||||||
node.attrs.class = 'hl-pre'
|
|
||||||
code.attrs.class = 'hl-code'
|
|
||||||
if (config.showLanguageName) {
|
|
||||||
node.children.push({
|
|
||||||
name: 'div',
|
|
||||||
attrs: {
|
|
||||||
class: 'hl-language',
|
|
||||||
style: 'user-select:none'
|
|
||||||
},
|
|
||||||
children: [{
|
|
||||||
type: 'text',
|
|
||||||
text: lang
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (config.copyByLongPress) {
|
|
||||||
node.attrs.style += (node.attrs.style || '') + ';user-select:none'
|
|
||||||
node.attrs['data-content'] = text
|
|
||||||
vm.expose()
|
|
||||||
}
|
|
||||||
if (config.showLineNumber) {
|
|
||||||
const line = text.split('\n').length; const children = []
|
|
||||||
for (let k = line; k--;) {
|
|
||||||
children.push({
|
|
||||||
name: 'span',
|
|
||||||
attrs: {
|
|
||||||
class: 'span'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
node.children.push({
|
|
||||||
name: 'span',
|
|
||||||
attrs: {
|
|
||||||
class: 'line-numbers-rows'
|
|
||||||
},
|
|
||||||
children
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Highlight
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,138 +0,0 @@
|
|||||||
const data = {
|
|
||||||
name: 'imgcache',
|
|
||||||
prefix: 'imgcache_'
|
|
||||||
}
|
|
||||||
function ImgCache (vm) {
|
|
||||||
this.vm = vm // 保存实例在其他周期使用
|
|
||||||
this.i = 0 // 用于标记第几张图
|
|
||||||
vm.imgCache = {
|
|
||||||
get list () {
|
|
||||||
return uni
|
|
||||||
.getStorageInfoSync()
|
|
||||||
.keys.filter((key) => key.startsWith(data.prefix))
|
|
||||||
.map((key) => key.split(data.prefix)[1])
|
|
||||||
},
|
|
||||||
get (url) {
|
|
||||||
return uni.getStorageSync(data.prefix + url)
|
|
||||||
},
|
|
||||||
delete (url) {
|
|
||||||
const path = uni.getStorageSync(data.prefix + url)
|
|
||||||
if (!path) return false
|
|
||||||
plus.io.resolveLocalFileSystemURL(path, (entry) => {
|
|
||||||
entry.remove()
|
|
||||||
})
|
|
||||||
uni.removeStorageSync(data.prefix + url)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
async add (url) {
|
|
||||||
const filename = await download(url)
|
|
||||||
if (filename) {
|
|
||||||
uni.setStorageSync(data.prefix + url, filename)
|
|
||||||
return 'file://' + plus.io.convertLocalFileSystemURL(filename)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
},
|
|
||||||
clear () {
|
|
||||||
uni
|
|
||||||
.getStorageInfoSync()
|
|
||||||
.keys.filter((key) => key.startsWith(data.prefix))
|
|
||||||
.forEach((key) => {
|
|
||||||
uni.removeStorageSync(key)
|
|
||||||
})
|
|
||||||
|
|
||||||
plus.io.resolveLocalFileSystemURL(`_doc/${data.name}/`, (entry) => {
|
|
||||||
entry.removeRecursively(
|
|
||||||
(entry) => {
|
|
||||||
console.log(`${data.name}缓存删除成功`, entry)
|
|
||||||
},
|
|
||||||
(e) => {
|
|
||||||
console.log(`${data.name}缓存删除失败`, e)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
ImgCache.prototype.onParse = function (node, parser) {
|
|
||||||
// 启用本插件 && 解析图片标签 && 拥有src属性 && 是网络图片
|
|
||||||
if (
|
|
||||||
this.vm.ImgCache &&
|
|
||||||
node.name === 'img' &&
|
|
||||||
node.attrs.src &&
|
|
||||||
/^https?:\/\//.test(node.attrs.src)
|
|
||||||
) {
|
|
||||||
const src = node.attrs.src
|
|
||||||
node.attrs.src = ''
|
|
||||||
node.attrs.i = this.vm.imgList.length + this.i++
|
|
||||||
parser.expose()
|
|
||||||
|
|
||||||
async function getUrl (path) {
|
|
||||||
if (await resolveFile(path)) return path
|
|
||||||
const filename = await download(src)
|
|
||||||
filename && uni.setStorageSync(data.prefix + src, filename)
|
|
||||||
return filename
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.getStorage({
|
|
||||||
key: data.prefix + src,
|
|
||||||
success: async (res) => {
|
|
||||||
const path = await getUrl(res.data)
|
|
||||||
const url = path
|
|
||||||
? 'file://' + plus.io.convertLocalFileSystemURL(path)
|
|
||||||
: src
|
|
||||||
node.attrs.src = url
|
|
||||||
this.vm.imgList[node.attrs.i] = path || src
|
|
||||||
},
|
|
||||||
fail: async () => {
|
|
||||||
const path = await getUrl()
|
|
||||||
const url = path
|
|
||||||
? 'file://' + plus.io.convertLocalFileSystemURL(path)
|
|
||||||
: src
|
|
||||||
node.attrs.src = url
|
|
||||||
this.vm.imgList[node.attrs.i] = path || src
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const taskQueue = new Set()
|
|
||||||
|
|
||||||
function download (url) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
if (taskQueue.has(url)) return
|
|
||||||
taskQueue.add(url)
|
|
||||||
const suffix = /.+\.(jpg|jpeg|png|bmp|gif|webp)/.exec(url)
|
|
||||||
const name = `${makeid(8)}_${Date.now()}${suffix ? '.' + suffix[1] : ''}`
|
|
||||||
const task = plus.downloader.createDownload(
|
|
||||||
url,
|
|
||||||
{ filename: `_doc/${data.name}/${name}` },
|
|
||||||
(download, status) => {
|
|
||||||
taskQueue.delete(url)
|
|
||||||
resolve(status === 200 ? download.filename : null)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
task.start()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断文件存在
|
|
||||||
function resolveFile (url) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
plus.io.resolveLocalFileSystemURL(url, resolve, () => resolve(null))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成uuid
|
|
||||||
function makeid (length) {
|
|
||||||
let result = ''
|
|
||||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
result += characters.charAt(Math.floor(Math.random() * characters.length))
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
export default ImgCache
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* @fileoverview markdown 插件
|
|
||||||
* Include marked (https://github.com/markedjs/marked)
|
|
||||||
* Include github-markdown-css (https://github.com/sindresorhus/github-markdown-css)
|
|
||||||
*/
|
|
||||||
import marked from './marked.min'
|
|
||||||
let index = 0
|
|
||||||
|
|
||||||
function Markdown (vm) {
|
|
||||||
this.vm = vm
|
|
||||||
vm._ids = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Markdown.prototype.onUpdate = function (content) {
|
|
||||||
if (this.vm.markdown) {
|
|
||||||
return marked(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Markdown.prototype.onParse = function (node, vm) {
|
|
||||||
if (vm.options.markdown) {
|
|
||||||
// 中文 id 需要转换,否则无法跳转
|
|
||||||
if (vm.options.useAnchor && node.attrs && /[\u4e00-\u9fa5]/.test(node.attrs.id)) {
|
|
||||||
const id = 't' + index++
|
|
||||||
this.vm._ids[node.attrs.id] = id
|
|
||||||
node.attrs.id = id
|
|
||||||
}
|
|
||||||
if (node.name === 'p' || node.name === 'table' || node.name === 'tr' || node.name === 'th' || node.name === 'td' || node.name === 'blockquote' || node.name === 'pre' || node.name === 'code') {
|
|
||||||
node.attrs.class = `md-${node.name} ${node.attrs.class || ''}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Markdown
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,579 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="(editable?'min-height:200px;':'')+containerStyle" @tap="_containTap">
|
|
||||||
<slot v-if="!nodes[0]" />
|
|
||||||
<!-- #ifndef APP-PLUS-NVUE -->
|
|
||||||
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable,editable,placeholder,'nodes']" name="span" />
|
|
||||||
<!-- #endif -->
|
|
||||||
<!-- #ifdef APP-PLUS-NVUE -->
|
|
||||||
<web-view ref="web" src="/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
|
|
||||||
<!-- #endif -->
|
|
||||||
<view v-if="tooltip" class="_tooltip_contain" :style="'top:'+tooltip.top+'px'">
|
|
||||||
<view class="_tooltip">
|
|
||||||
<view v-for="(item, index) in tooltip.items" v-bind:key="index" class="_tooltip_item" :data-i="index" @tap="_tooltipTap">{{item}}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view v-if="slider" class="_slider" :style="'top:'+slider.top+'px'">
|
|
||||||
<slider :value="slider.value" :min="slider.min" :max="slider.max" handle-size="14" block-size="14" show-value activeColor="white" style="padding:3px" @changing="_sliderChanging" @change="_sliderChange" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* mp-html v2.4.0
|
|
||||||
* @description 富文本组件
|
|
||||||
* @tutorial https://github.com/jin-yufeng/mp-html
|
|
||||||
* @property {String} container-style 容器的样式
|
|
||||||
* @property {String} content 用于渲染的 html 字符串
|
|
||||||
* @property {Boolean} copy-link 是否允许外部链接被点击时自动复制
|
|
||||||
* @property {String} domain 主域名,用于拼接链接
|
|
||||||
* @property {String} error-img 图片出错时的占位图链接
|
|
||||||
* @property {Boolean} lazy-load 是否开启图片懒加载
|
|
||||||
* @property {string} loading-img 图片加载过程中的占位图链接
|
|
||||||
* @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频
|
|
||||||
* @property {Boolean} preview-img 是否允许图片被点击时自动预览
|
|
||||||
* @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动
|
|
||||||
* @property {Boolean | String} selectable 是否开启长按复制
|
|
||||||
* @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题
|
|
||||||
* @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单
|
|
||||||
* @property {Object} tag-style 标签的默认样式
|
|
||||||
* @property {Boolean | Number} use-anchor 是否使用锚点链接
|
|
||||||
* @event {Function} load dom 结构加载完毕时触发
|
|
||||||
* @event {Function} ready 所有图片加载完毕时触发
|
|
||||||
* @event {Function} imgtap 图片被点击时触发
|
|
||||||
* @event {Function} linktap 链接被点击时触发
|
|
||||||
* @event {Function} play 音视频播放时触发
|
|
||||||
* @event {Function} error 媒体加载出错时触发
|
|
||||||
*/
|
|
||||||
// #ifndef APP-PLUS-NVUE
|
|
||||||
import node from './node/node'
|
|
||||||
// #endif
|
|
||||||
import Parser from './parser'
|
|
||||||
import markdown from './markdown/index.js'
|
|
||||||
import emoji from './emoji/index.js'
|
|
||||||
import highlight from './highlight/index.js'
|
|
||||||
import style from './style/index.js'
|
|
||||||
import imgCache from './img-cache/index.js'
|
|
||||||
import editable from './editable/index.js'
|
|
||||||
const plugins=[markdown,emoji,highlight,style,imgCache,editable,]
|
|
||||||
// #ifdef APP-PLUS-NVUE
|
|
||||||
const dom = weex.requireModule('dom')
|
|
||||||
// #endif
|
|
||||||
export default {
|
|
||||||
name: 'mp-html',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
tooltip: null,
|
|
||||||
slider: null,
|
|
||||||
nodes: [],
|
|
||||||
// #ifdef APP-PLUS-NVUE
|
|
||||||
height: 3
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
editable: Boolean,
|
|
||||||
placeholder: String,
|
|
||||||
ImgCache: Boolean,
|
|
||||||
markdown: Boolean,
|
|
||||||
containerStyle: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
copyLink: {
|
|
||||||
type: [Boolean, String],
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
domain: String,
|
|
||||||
errorImg: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
lazyLoad: {
|
|
||||||
type: [Boolean, String],
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
loadingImg: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
pauseVideo: {
|
|
||||||
type: [Boolean, String],
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
previewImg: {
|
|
||||||
type: [Boolean, String],
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
scrollTable: [Boolean, String],
|
|
||||||
selectable: [Boolean, String],
|
|
||||||
setTitle: {
|
|
||||||
type: [Boolean, String],
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
showImgMenu: {
|
|
||||||
type: [Boolean, String],
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
tagStyle: Object,
|
|
||||||
useAnchor: [Boolean, Number]
|
|
||||||
},
|
|
||||||
// #ifdef VUE3
|
|
||||||
emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],
|
|
||||||
// #endif
|
|
||||||
// #ifndef APP-PLUS-NVUE
|
|
||||||
components: {
|
|
||||||
node
|
|
||||||
},
|
|
||||||
// #endif
|
|
||||||
watch: {
|
|
||||||
editable(val) {
|
|
||||||
this.setContent(val ? this.content : this.getContent())
|
|
||||||
if (!val)
|
|
||||||
this._maskTap()
|
|
||||||
},
|
|
||||||
content (content) {
|
|
||||||
this.setContent(content)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.plugins = []
|
|
||||||
for (let i = plugins.length; i--;) {
|
|
||||||
this.plugins.push(new plugins[i](this))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
if ((this.content || this.editable) && !this.nodes.length) {
|
|
||||||
this.setContent(this.content)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeDestroy () {
|
|
||||||
this._hook('onDetached')
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
_containTap() {
|
|
||||||
if (!this._lock && !this.slider) {
|
|
||||||
this._edit = undefined
|
|
||||||
this._maskTap()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_tooltipTap(e) {
|
|
||||||
this._tooltipcb(e.currentTarget.dataset.i)
|
|
||||||
this.$set(this, 'tooltip', null)
|
|
||||||
},
|
|
||||||
_sliderChanging(e) {
|
|
||||||
this._slideringcb(e.detail.value)
|
|
||||||
},
|
|
||||||
_sliderChange(e) {
|
|
||||||
this._slidercb(e.detail.value)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @description 将锚点跳转的范围限定在一个 scroll-view 内
|
|
||||||
* @param {Object} page scroll-view 所在页面的示例
|
|
||||||
* @param {String} selector scroll-view 的选择器
|
|
||||||
* @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
|
|
||||||
*/
|
|
||||||
in (page, selector, scrollTop) {
|
|
||||||
// #ifndef APP-PLUS-NVUE
|
|
||||||
if (page && selector && scrollTop) {
|
|
||||||
this._in = {
|
|
||||||
page,
|
|
||||||
selector,
|
|
||||||
scrollTop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 锚点跳转
|
|
||||||
* @param {String} id 要跳转的锚点 id
|
|
||||||
* @param {Number} offset 跳转位置的偏移量
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
navigateTo (id, offset) {
|
|
||||||
id = this._ids[decodeURI(id)] || id
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (!this.useAnchor) {
|
|
||||||
reject(Error('Anchor is disabled'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
offset = offset || parseInt(this.useAnchor) || 0
|
|
||||||
// #ifdef APP-PLUS-NVUE
|
|
||||||
if (!id) {
|
|
||||||
dom.scrollToElement(this.$refs.web, {
|
|
||||||
offset
|
|
||||||
})
|
|
||||||
resolve()
|
|
||||||
} else {
|
|
||||||
this._navigateTo = {
|
|
||||||
resolve,
|
|
||||||
reject,
|
|
||||||
offset
|
|
||||||
}
|
|
||||||
this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
// #ifndef APP-PLUS-NVUE
|
|
||||||
let deep = ' '
|
|
||||||
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
|
|
||||||
deep = '>>>'
|
|
||||||
// #endif
|
|
||||||
const selector = uni.createSelectorQuery()
|
|
||||||
// #ifndef MP-ALIPAY
|
|
||||||
.in(this._in ? this._in.page : this)
|
|
||||||
// #endif
|
|
||||||
.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
|
|
||||||
if (this._in) {
|
|
||||||
selector.select(this._in.selector).scrollOffset()
|
|
||||||
.select(this._in.selector).boundingClientRect()
|
|
||||||
} else {
|
|
||||||
// 获取 scroll-view 的位置和滚动距离
|
|
||||||
selector.selectViewport().scrollOffset() // 获取窗口的滚动距离
|
|
||||||
}
|
|
||||||
selector.exec(res => {
|
|
||||||
if (!res[0]) {
|
|
||||||
reject(Error('Label not found'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
|
|
||||||
if (this._in) {
|
|
||||||
// scroll-view 跳转
|
|
||||||
this._in.page[this._in.scrollTop] = scrollTop
|
|
||||||
} else {
|
|
||||||
// 页面跳转
|
|
||||||
uni.pageScrollTo({
|
|
||||||
scrollTop,
|
|
||||||
duration: 300
|
|
||||||
})
|
|
||||||
}
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 获取文本内容
|
|
||||||
* @return {String}
|
|
||||||
*/
|
|
||||||
getText (nodes) {
|
|
||||||
let text = '';
|
|
||||||
(function traversal (nodes) {
|
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
|
||||||
const node = nodes[i]
|
|
||||||
if (node.type === 'text') {
|
|
||||||
text += node.text.replace(/&/g, '&')
|
|
||||||
} else if (node.name === 'br') {
|
|
||||||
text += '\n'
|
|
||||||
} else {
|
|
||||||
// 块级标签前后加换行
|
|
||||||
const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
|
|
||||||
if (isBlock && text && text[text.length - 1] !== '\n') {
|
|
||||||
text += '\n'
|
|
||||||
}
|
|
||||||
// 递归获取子节点的文本
|
|
||||||
if (node.children) {
|
|
||||||
traversal(node.children)
|
|
||||||
}
|
|
||||||
if (isBlock && text[text.length - 1] !== '\n') {
|
|
||||||
text += '\n'
|
|
||||||
} else if (node.name === 'td' || node.name === 'th') {
|
|
||||||
text += '\t'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})(nodes || this.nodes)
|
|
||||||
return text
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 获取内容大小和位置
|
|
||||||
* @return {Promise}
|
|
||||||
*/
|
|
||||||
getRect () {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uni.createSelectorQuery()
|
|
||||||
// #ifndef MP-ALIPAY
|
|
||||||
.in(this)
|
|
||||||
// #endif
|
|
||||||
.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 暂停播放媒体
|
|
||||||
*/
|
|
||||||
pauseMedia () {
|
|
||||||
for (let i = (this._videos || []).length; i--;) {
|
|
||||||
this._videos[i].pause()
|
|
||||||
}
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()'
|
|
||||||
// #ifndef APP-PLUS-NVUE
|
|
||||||
let page = this.$parent
|
|
||||||
while (!page.$scope) page = page.$parent
|
|
||||||
page.$scope.$getAppWebview().evalJS(command)
|
|
||||||
// #endif
|
|
||||||
// #ifdef APP-PLUS-NVUE
|
|
||||||
this.$refs.web.evalJs(command)
|
|
||||||
// #endif
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 设置媒体播放速率
|
|
||||||
* @param {Number} rate 播放速率
|
|
||||||
*/
|
|
||||||
setPlaybackRate (rate) {
|
|
||||||
this.playbackRate = rate
|
|
||||||
for (let i = (this._videos || []).length; i--;) {
|
|
||||||
this._videos[i].playbackRate(rate)
|
|
||||||
}
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].playbackRate=' + rate
|
|
||||||
// #ifndef APP-PLUS-NVUE
|
|
||||||
let page = this.$parent
|
|
||||||
while (!page.$scope) page = page.$parent
|
|
||||||
page.$scope.$getAppWebview().evalJS(command)
|
|
||||||
// #endif
|
|
||||||
// #ifdef APP-PLUS-NVUE
|
|
||||||
this.$refs.web.evalJs(command)
|
|
||||||
// #endif
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 设置内容
|
|
||||||
* @param {String} content html 内容
|
|
||||||
* @param {Boolean} append 是否在尾部追加
|
|
||||||
*/
|
|
||||||
setContent (content, append) {
|
|
||||||
if (!append || !this.imgList) {
|
|
||||||
this.imgList = []
|
|
||||||
}
|
|
||||||
const nodes = new Parser(this).parse(content)
|
|
||||||
// #ifdef APP-PLUS-NVUE
|
|
||||||
if (this._ready) {
|
|
||||||
this._set(nodes, append)
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
|
|
||||||
|
|
||||||
// #ifndef APP-PLUS-NVUE
|
|
||||||
this._videos = []
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this._hook('onLoad')
|
|
||||||
this.$emit('load')
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) {
|
|
||||||
// 设置懒加载,每 350ms 获取高度,不变则认为加载完毕
|
|
||||||
let height
|
|
||||||
const callback = rect => {
|
|
||||||
// 350ms 总高度无变化就触发 ready 事件
|
|
||||||
if (rect.height === height) {
|
|
||||||
this.$emit('ready', rect)
|
|
||||||
} else {
|
|
||||||
height = rect.height
|
|
||||||
setTimeout(() => {
|
|
||||||
this.getRect().then(callback)
|
|
||||||
}, 350)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.getRect().then(callback)
|
|
||||||
} else {
|
|
||||||
// 未设置懒加载,等待所有图片加载完毕
|
|
||||||
if (!this.imgList._unloadimgs) {
|
|
||||||
this.getRect(rect => {
|
|
||||||
this.$emit('ready', rect)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 调用插件钩子函数
|
|
||||||
*/
|
|
||||||
_hook (name) {
|
|
||||||
for (let i = plugins.length; i--;) {
|
|
||||||
if (this.plugins[i][name]) {
|
|
||||||
this.plugins[i][name]()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// #ifdef APP-PLUS-NVUE
|
|
||||||
/**
|
|
||||||
* @description 设置内容
|
|
||||||
*/
|
|
||||||
_set (nodes, append) {
|
|
||||||
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes) + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 接收到 web-view 消息
|
|
||||||
*/
|
|
||||||
_onMessage (e) {
|
|
||||||
const message = e.detail.data[0]
|
|
||||||
switch (message.action) {
|
|
||||||
// web-view 初始化完毕
|
|
||||||
case 'onJSBridgeReady':
|
|
||||||
this._ready = true
|
|
||||||
if (this.nodes) {
|
|
||||||
this._set(this.nodes)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
// 内容 dom 加载完毕
|
|
||||||
case 'onLoad':
|
|
||||||
this.height = message.height
|
|
||||||
this._hook('onLoad')
|
|
||||||
this.$emit('load')
|
|
||||||
break
|
|
||||||
// 所有图片加载完毕
|
|
||||||
case 'onReady':
|
|
||||||
this.getRect().then(res => {
|
|
||||||
this.$emit('ready', res)
|
|
||||||
}).catch(() => { })
|
|
||||||
break
|
|
||||||
// 总高度发生变化
|
|
||||||
case 'onHeightChange':
|
|
||||||
this.height = message.height
|
|
||||||
break
|
|
||||||
// 图片点击
|
|
||||||
case 'onImgTap':
|
|
||||||
this.$emit('imgtap', message.attrs)
|
|
||||||
if (this.previewImg) {
|
|
||||||
uni.previewImage({
|
|
||||||
current: parseInt(message.attrs.i),
|
|
||||||
urls: this.imgList
|
|
||||||
})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
// 链接点击
|
|
||||||
case 'onLinkTap': {
|
|
||||||
const href = message.attrs.href
|
|
||||||
this.$emit('linktap', message.attrs)
|
|
||||||
if (href) {
|
|
||||||
// 锚点跳转
|
|
||||||
if (href[0] === '#') {
|
|
||||||
if (this.useAnchor) {
|
|
||||||
dom.scrollToElement(this.$refs.web, {
|
|
||||||
offset: message.offset
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (href.includes('://')) {
|
|
||||||
// 打开外链
|
|
||||||
if (this.copyLink) {
|
|
||||||
plus.runtime.openWeb(href)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: href,
|
|
||||||
fail () {
|
|
||||||
uni.switchTab({
|
|
||||||
url: href
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'onPlay':
|
|
||||||
this.$emit('play')
|
|
||||||
break
|
|
||||||
// 获取到锚点的偏移量
|
|
||||||
case 'getOffset':
|
|
||||||
if (typeof message.offset === 'number') {
|
|
||||||
dom.scrollToElement(this.$refs.web, {
|
|
||||||
offset: message.offset + this._navigateTo.offset
|
|
||||||
})
|
|
||||||
this._navigateTo.resolve()
|
|
||||||
} else {
|
|
||||||
this._navigateTo.reject(Error('Label not found'))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
// 点击
|
|
||||||
case 'onClick':
|
|
||||||
this.$emit('tap')
|
|
||||||
this.$emit('click')
|
|
||||||
break
|
|
||||||
// 出错
|
|
||||||
case 'onError':
|
|
||||||
this.$emit('error', {
|
|
||||||
source: message.source,
|
|
||||||
attrs: message.attrs
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* #ifndef APP-PLUS-NVUE */
|
|
||||||
/* 根节点样式 */
|
|
||||||
._root {
|
|
||||||
padding: 1px 0;
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 长按复制 */
|
|
||||||
._select {
|
|
||||||
user-select: text;
|
|
||||||
}
|
|
||||||
/* #endif */
|
|
||||||
|
|
||||||
/* 提示条 */
|
|
||||||
._tooltip_contain {
|
|
||||||
position: absolute;
|
|
||||||
right: 20px;
|
|
||||||
left: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
._tooltip {
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: inline-block;
|
|
||||||
width: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
height: 30px;
|
|
||||||
padding: 0 3px;
|
|
||||||
overflow: scroll;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 30px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
._tooltip_item {
|
|
||||||
display: inline-block;
|
|
||||||
width: auto;
|
|
||||||
padding: 0 2vw;
|
|
||||||
line-height: 30px;
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 图片宽度滚动条 */
|
|
||||||
._slider {
|
|
||||||
position: absolute;
|
|
||||||
left: 20px;
|
|
||||||
width: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
._tooltip,
|
|
||||||
._slider {
|
|
||||||
background-color: black;
|
|
||||||
border-radius: 3px;
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,129 +0,0 @@
|
|||||||
/**
|
|
||||||
* @fileoverview style 插件
|
|
||||||
*/
|
|
||||||
// #ifndef APP-PLUS-NVUE
|
|
||||||
import Parser from './parser'
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
function Style () {
|
|
||||||
this.styles = []
|
|
||||||
}
|
|
||||||
|
|
||||||
// #ifndef APP-PLUS-NVUE
|
|
||||||
Style.prototype.onParse = function (node, vm) {
|
|
||||||
// 获取样式
|
|
||||||
if (node.name === 'style' && node.children.length && node.children[0].type === 'text') {
|
|
||||||
this.styles = this.styles.concat(new Parser().parse(node.children[0].text))
|
|
||||||
} else if (node.name) {
|
|
||||||
// 匹配样式(对非文本标签)
|
|
||||||
// 存储不同优先级的样式 name < class < id < 后代
|
|
||||||
let matched = ['', '', '', '']
|
|
||||||
for (let i = 0, len = this.styles.length; i < len; i++) {
|
|
||||||
const item = this.styles[i]
|
|
||||||
let res = match(node, item.key || item.list[item.list.length - 1])
|
|
||||||
let j
|
|
||||||
if (res) {
|
|
||||||
// 后代选择器
|
|
||||||
if (!item.key) {
|
|
||||||
j = item.list.length - 2
|
|
||||||
for (let k = vm.stack.length; j >= 0 && k--;) {
|
|
||||||
// 子选择器
|
|
||||||
if (item.list[j] === '>') {
|
|
||||||
// 错误情况
|
|
||||||
if (j < 1 || j > item.list.length - 2) break
|
|
||||||
if (match(vm.stack[k], item.list[j - 1])) {
|
|
||||||
j -= 2
|
|
||||||
} else {
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
} else if (match(vm.stack[k], item.list[j])) {
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res = 4
|
|
||||||
}
|
|
||||||
if (item.key || j < 0) {
|
|
||||||
// 添加伪类
|
|
||||||
if (item.pseudo && node.children) {
|
|
||||||
let text
|
|
||||||
item.style = item.style.replace(/content:([^;]+)/, (_, $1) => {
|
|
||||||
text = $1.replace(/['"]/g, '')
|
|
||||||
// 处理 attr 函数
|
|
||||||
.replace(/attr\((.+?)\)/, (_, $1) => node.attrs[$1.trim()] || '')
|
|
||||||
// 编码 \xxx
|
|
||||||
.replace(/\\(\w{4})/, (_, $1) => String.fromCharCode(parseInt($1, 16)))
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
const pseudo = {
|
|
||||||
name: 'span',
|
|
||||||
attrs: {
|
|
||||||
style: item.style
|
|
||||||
},
|
|
||||||
children: [{
|
|
||||||
type: 'text',
|
|
||||||
text
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
if (item.pseudo === 'before') {
|
|
||||||
node.children.unshift(pseudo)
|
|
||||||
} else {
|
|
||||||
node.children.push(pseudo)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
matched[res - 1] += item.style + (item.style[item.style.length - 1] === ';' ? '' : ';')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
matched = matched.join('')
|
|
||||||
if (matched.length > 2) {
|
|
||||||
node.attrs.style = matched + (node.attrs.style || '')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 匹配样式
|
|
||||||
* @param {object} node 要匹配的标签
|
|
||||||
* @param {string|string[]} keys 选择器
|
|
||||||
* @returns {number} 0:不匹配;1:name 匹配;2:class 匹配;3:id 匹配
|
|
||||||
*/
|
|
||||||
function match (node, keys) {
|
|
||||||
function matchItem (key) {
|
|
||||||
if (key[0] === '#') {
|
|
||||||
// 匹配 id
|
|
||||||
if (node.attrs.id && node.attrs.id.trim() === key.substr(1)) return 3
|
|
||||||
} else if (key[0] === '.') {
|
|
||||||
// 匹配 class
|
|
||||||
key = key.substr(1)
|
|
||||||
const selectors = (node.attrs.class || '').split(' ')
|
|
||||||
for (let i = 0; i < selectors.length; i++) {
|
|
||||||
if (selectors[i].trim() === key) return 2
|
|
||||||
}
|
|
||||||
} else if (node.name === key) {
|
|
||||||
// 匹配 name
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 多选择器交集
|
|
||||||
if (keys instanceof Array) {
|
|
||||||
let res = 0
|
|
||||||
for (let j = 0; j < keys.length; j++) {
|
|
||||||
const tmp = matchItem(keys[j])
|
|
||||||
// 任意一个不匹配就失败
|
|
||||||
if (!tmp) return 0
|
|
||||||
// 优先级最大的一个作为最终优先级
|
|
||||||
if (tmp > res) {
|
|
||||||
res = tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchItem(keys)
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
export default Style
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
const blank = {
|
|
||||||
' ': true,
|
|
||||||
'\n': true,
|
|
||||||
'\t': true,
|
|
||||||
'\r': true,
|
|
||||||
'\f': true
|
|
||||||
}
|
|
||||||
|
|
||||||
function Parser () {
|
|
||||||
this.styles = []
|
|
||||||
this.selectors = []
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 解析 css 字符串
|
|
||||||
* @param {string} content css 内容
|
|
||||||
*/
|
|
||||||
Parser.prototype.parse = function (content) {
|
|
||||||
new Lexer(this).parse(content)
|
|
||||||
return this.styles
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 解析到一个选择器
|
|
||||||
* @param {string} name 名称
|
|
||||||
*/
|
|
||||||
Parser.prototype.onSelector = function (name) {
|
|
||||||
// 不支持的选择器
|
|
||||||
if (name.includes('[') || name.includes('*') || name.includes('@')) return
|
|
||||||
const selector = {}
|
|
||||||
// 伪类
|
|
||||||
if (name.includes(':')) {
|
|
||||||
const info = name.split(':')
|
|
||||||
const pseudo = info.pop()
|
|
||||||
if (pseudo === 'before' || pseudo === 'after') {
|
|
||||||
selector.pseudo = pseudo
|
|
||||||
name = info[0]
|
|
||||||
} else return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分割交集选择器
|
|
||||||
function splitItem (str) {
|
|
||||||
const arr = []
|
|
||||||
let i, start
|
|
||||||
for (i = 1, start = 0; i < str.length; i++) {
|
|
||||||
if (str[i] === '.' || str[i] === '#') {
|
|
||||||
arr.push(str.substring(start, i))
|
|
||||||
start = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!arr.length) {
|
|
||||||
return str
|
|
||||||
} else {
|
|
||||||
arr.push(str.substring(start, i))
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 后代选择器
|
|
||||||
if (name.includes(' ')) {
|
|
||||||
selector.list = []
|
|
||||||
const list = name.split(' ')
|
|
||||||
for (let i = 0; i < list.length; i++) {
|
|
||||||
if (list[i].length) {
|
|
||||||
// 拆分子选择器
|
|
||||||
const arr = list[i].split('>')
|
|
||||||
for (let j = 0; j < arr.length; j++) {
|
|
||||||
selector.list.push(splitItem(arr[j]))
|
|
||||||
if (j < arr.length - 1) {
|
|
||||||
selector.list.push('>')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
selector.key = splitItem(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selectors.push(selector)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 解析到选择器内容
|
|
||||||
* @param {string} content 内容
|
|
||||||
*/
|
|
||||||
Parser.prototype.onContent = function (content) {
|
|
||||||
// 并集选择器
|
|
||||||
for (let i = 0; i < this.selectors.length; i++) {
|
|
||||||
this.selectors[i].style = content
|
|
||||||
}
|
|
||||||
this.styles = this.styles.concat(this.selectors)
|
|
||||||
this.selectors = []
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description css 词法分析器
|
|
||||||
* @param {object} handler 高层处理器
|
|
||||||
*/
|
|
||||||
function Lexer (handler) {
|
|
||||||
this.selector = ''
|
|
||||||
this.style = ''
|
|
||||||
this.handler = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
Lexer.prototype.parse = function (content) {
|
|
||||||
this.i = 0
|
|
||||||
this.content = content
|
|
||||||
this.state = this.blank
|
|
||||||
for (let len = content.length; this.i < len; this.i++) {
|
|
||||||
this.state(content[this.i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Lexer.prototype.comment = function () {
|
|
||||||
this.i = this.content.indexOf('*/', this.i) + 1
|
|
||||||
if (!this.i) {
|
|
||||||
this.i = this.content.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Lexer.prototype.blank = function (c) {
|
|
||||||
if (!blank[c]) {
|
|
||||||
if (c === '/' && this.content[this.i + 1] === '*') {
|
|
||||||
this.comment()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.selector += c
|
|
||||||
this.state = this.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Lexer.prototype.name = function (c) {
|
|
||||||
if (c === '/' && this.content[this.i + 1] === '*') {
|
|
||||||
this.comment()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (c === '{' || c === ',' || c === ';') {
|
|
||||||
this.handler.onSelector(this.selector.trimEnd())
|
|
||||||
this.selector = ''
|
|
||||||
if (c !== '{') {
|
|
||||||
while (blank[this.content[++this.i]]);
|
|
||||||
}
|
|
||||||
if (this.content[this.i] === '{') {
|
|
||||||
this.floor = 1
|
|
||||||
this.state = this.val
|
|
||||||
} else {
|
|
||||||
this.selector += this.content[this.i]
|
|
||||||
}
|
|
||||||
} else if (blank[c]) {
|
|
||||||
this.selector += ' '
|
|
||||||
} else {
|
|
||||||
this.selector += c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Lexer.prototype.val = function (c) {
|
|
||||||
if (c === '/' && this.content[this.i + 1] === '*') {
|
|
||||||
this.comment()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (c === '{') {
|
|
||||||
this.floor++
|
|
||||||
} else if (c === '}') {
|
|
||||||
this.floor--
|
|
||||||
if (!this.floor) {
|
|
||||||
this.handler.onContent(this.style)
|
|
||||||
this.style = ''
|
|
||||||
this.state = this.blank
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.style += c
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Parser
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
"use strict";function t(t){for(var e=Object.create(null),n=t.attributes.length;n--;)e[t.attributes[n].name]=t.attributes[n].value;return e}function e(){a[1]&&(this.src=a[1],this.onerror=null),this.onclick=null,this.ontouchstart=null,uni.postMessage({data:{action:"onError",source:"img",attrs:t(this)}})}function n(){window.unloadimgs-=1,0===window.unloadimgs&&uni.postMessage({data:{action:"onReady"}})}function o(r,s,c){for(var d=0;d<r.length;d++)!function(d){var u=r[d],l=void 0;if(u.type&&"node"!==u.type)l=document.createTextNode(u.text.replace(/&/g,"&"));else{var g=u.name;"svg"===g&&(c="http://www.w3.org/2000/svg"),"html"!==g&&"body"!==g||(g="div"),l=c?document.createElementNS(c,g):document.createElement(g);for(var p in u.attrs)l.setAttribute(p,u.attrs[p]);if(u.children&&o(u.children,l,c),"img"===g){if(window.unloadimgs+=1,l.onload=n,l.onerror=n,!l.src&&l.getAttribute("data-src")&&(l.src=l.getAttribute("data-src")),u.attrs.ignore||(l.onclick=function(e){e.stopPropagation(),uni.postMessage({data:{action:"onImgTap",attrs:t(this)}})}),a[2]){var h=new Image;h.src=l.src,l.src=a[2],h.onload=function(){l.src=this.src},h.onerror=function(){l.onerror()}}l.onerror=e}else if("a"===g)l.addEventListener("click",function(e){e.stopPropagation(),e.preventDefault();var n,o=this.getAttribute("href");o&&"#"===o[0]&&(n=(document.getElementById(o.substr(1))||{}).offsetTop),uni.postMessage({data:{action:"onLinkTap",attrs:t(this),offset:n}})},!0);else if("video"===g||"audio"===g)i.push(l),u.attrs.autoplay||u.attrs.controls||l.setAttribute("controls","true"),l.onplay=function(){if(uni.postMessage({data:{action:"onPlay"}}),a[3])for(var t=0;t<i.length;t++)i[t]!==this&&i[t].pause()},l.onerror=function(){uni.postMessage({data:{action:"onError",source:g,attrs:t(this)}})};else if("table"===g&&a[4]&&!l.style.cssText.includes("inline")){var f=document.createElement("div");f.style.overflow="auto",f.appendChild(l),l=f}else"svg"===g&&(c=void 0)}s.appendChild(l)}(d)}document.addEventListener("UniAppJSBridgeReady",function(){document.body.onclick=function(){return uni.postMessage({data:{action:"onClick"}})},uni.postMessage({data:{action:"onJSBridgeReady"}})});var a,i=[];window.setContent=function(t,e,n){var r=document.getElementById("content");e[0]&&(document.body.style.cssText=e[0]),e[5]||(r.style.userSelect="none"),n||(r.innerHTML="",i=[]),a=e,window.unloadimgs=0;var s=document.createDocumentFragment();o(t,s),r.appendChild(s);var c=r.scrollHeight;uni.postMessage({data:{action:"onLoad",height:c}}),window.unloadimgs||uni.postMessage({data:{action:"onReady",height:c}}),clearInterval(window.timer),window.timer=setInterval(function(){r.scrollHeight!==c&&(c=r.scrollHeight,uni.postMessage({data:{action:"onHeightChange",height:c}}))},350)},window.onunload=function(){clearInterval(window.timer)};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).uni=n()}(this,(function(){"use strict";try{var e={};Object.defineProperty(e,"passive",{get:function(){!0}}),window.addEventListener("test-passive",null,e)}catch(e){}var n=Object.prototype.hasOwnProperty;function t(e,t){return n.call(e,t)}var i=[],a=function(e,n){var t={options:{timestamp:+new Date},name:e,arg:n};if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){if("postMessage"===e){var a={data:[n]};return window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(a):window.__dcloud_weex_.postMessage(JSON.stringify(a))}var o={type:"WEB_INVOKE_APPSERVICE",args:{data:t,webviewIds:i}};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessageToService(o):window.__dcloud_weex_.postMessageToService(JSON.stringify(o))}if(!window.plus)return window.parent.postMessage({type:"WEB_INVOKE_APPSERVICE",data:t,pageId:""},"*");if(0===i.length){var r=plus.webview.currentWebview();if(!r)throw new Error("plus.webview.currentWebview() is undefined");var d=r.parent(),s="";s=d?d.id:r.id,i.push(s)}if(plus.webview.getWebviewById("__uniapp__service"))plus.webview.postMessageToUniNView({type:"WEB_INVOKE_APPSERVICE",args:{data:t,webviewIds:i}},"__uniapp__service");else{var w=JSON.stringify(t);plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat("WEB_INVOKE_APPSERVICE",'",').concat(w,",").concat(JSON.stringify(i),");"))}},o={navigateTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("navigateTo",{url:encodeURI(n)})},navigateBack:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.delta;a("navigateBack",{delta:parseInt(n)||1})},switchTab:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("switchTab",{url:encodeURI(n)})},reLaunch:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("reLaunch",{url:encodeURI(n)})},redirectTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("redirectTo",{url:encodeURI(n)})},getEnv:function(e){window.plus?e({plus:!0}):e({h5:!0})},postMessage:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};a("postMessage",e.data||{})}},r=/uni-app/i.test(navigator.userAgent),d=/Html5Plus/i.test(navigator.userAgent),s=/complete|loaded|interactive/;var w=window.my&&navigator.userAgent.indexOf("AlipayClient")>-1;var u=window.swan&&window.swan.webView&&/swan/i.test(navigator.userAgent);var c=window.qq&&window.qq.miniProgram&&/QQ/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var g=window.tt&&window.tt.miniProgram&&/toutiaomicroapp/i.test(navigator.userAgent);var v=window.wx&&window.wx.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var p=window.qa&&/quickapp/i.test(navigator.userAgent);for(var l,_=function(){window.UniAppJSBridge=!0,document.dispatchEvent(new CustomEvent("UniAppJSBridgeReady",{bubbles:!0,cancelable:!0}))},f=[function(e){if(r||d)return window.__dcloud_weex_postMessage||window.__dcloud_weex_?document.addEventListener("DOMContentLoaded",e):window.plus&&s.test(document.readyState)?setTimeout(e,0):document.addEventListener("plusready",e),o},function(e){if(v)return window.WeixinJSBridge&&window.WeixinJSBridge.invoke?setTimeout(e,0):document.addEventListener("WeixinJSBridgeReady",e),window.wx.miniProgram},function(e){if(c)return window.QQJSBridge&&window.QQJSBridge.invoke?setTimeout(e,0):document.addEventListener("QQJSBridgeReady",e),window.qq.miniProgram},function(e){if(w){document.addEventListener("DOMContentLoaded",e);var n=window.my;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){if(u)return document.addEventListener("DOMContentLoaded",e),window.swan.webView},function(e){if(g)return document.addEventListener("DOMContentLoaded",e),window.tt.miniProgram},function(e){if(p){window.QaJSBridge&&window.QaJSBridge.invoke?setTimeout(e,0):document.addEventListener("QaJSBridgeReady",e);var n=window.qa;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){return document.addEventListener("DOMContentLoaded",e),o}],m=0;m<f.length&&!(l=f[m](_));m++);l||(l={});var E="undefined"!=typeof uni?uni:{};if(!E.navigateTo)for(var b in l)t(l,b)&&(E[b]=l[b]);return E.webView=l,E}));
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<style>.hl-code,.hl-pre{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}.hl-pre{padding:1em;margin:.5em 0;overflow:auto}.hl-pre{background:#2d2d2d}.hl-block-comment,.hl-cdata,.hl-comment,.hl-doctype,.hl-prolog{color:#999}.hl-punctuation{color:#ccc}.hl-attr-name,.hl-deleted,.hl-namespace,.hl-tag{color:#e2777a}.hl-function-name{color:#6196cc}.hl-boolean,.hl-function,.hl-number{color:#f08d49}.hl-class-name,.hl-constant,.hl-property,.hl-symbol{color:#f8c555}.hl-atrule,.hl-builtin,.hl-important,.hl-keyword,.hl-selector{color:#cc99cd}.hl-attr-value,.hl-char,.hl-regex,.hl-string,.hl-variable{color:#7ec699}.hl-entity,.hl-operator,.hl-url{color:#67cdcc}.hl-bold,.hl-important{font-weight:700}.hl-italic{font-style:italic}.hl-entity{cursor:help}.hl-inserted{color:green}</style><style>.md-p{margin-block-start:1em;margin-block-end:1em}.md-blockquote,.md-table{margin-bottom:16px}.md-table{box-sizing:border-box;width:100%;overflow:auto;border-spacing:0;border-collapse:collapse}.md-tr{background-color:#fff;border-top:1px solid #c6cbd1}.md-table .md-tr:nth-child(2n){background-color:#f6f8fa}.md-td,.md-th{padding:6px 13px!important;border:1px solid #dfe2e5}.md-th{font-weight:600}.md-blockquote{padding:0 1em;color:#6a737d;border-left:.25em solid #dfe2e5}.md-code{padding:.2em .4em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:85%;background-color:rgba(27,31,35,.05);border-radius:3px}.md-pre .md-code{padding:0;font-size:100%;background:0 0;border:0}.hl-pre{position:relative}.hl-code{overflow:auto;display:block}.hl-language{font-size:12px;font-weight:600;position:absolute;right:8px;text-align:right;top:3px}.hl-pre{padding-top:1.5em}.hl-pre{font-size:14px;padding-left:3.8em;counter-reset:linenumber}.line-numbers-rows{position:absolute;pointer-events:none;top:1.5em;font-size:100%;left:0;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows .span{display:block;counter-increment:linenumber}.line-numbers-rows .span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}._address,._article,._aside,._body,._caption,._center,._cite,._footer,._header,._html,._nav,._pre,._section{display:block}._video{width:300px;height:225px;display:inline-block;background-color:#000}</style><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>body,html{width:100%;height:100%;overflow-x:scroll;overflow-y:hidden}body{margin:0}video{width:300px;height:225px}img{max-width:100%;-webkit-touch-callout:none}</style></head><body><div id="content" style="overflow:hidden"></div><script type="text/javascript" src="./js/uni.webview.min.js"></script><script type="text/javascript" src="./js/handler.js"></script></body>
|
|
||||||
@@ -1,735 +0,0 @@
|
|||||||
export default{
|
|
||||||
data(){
|
|
||||||
return{
|
|
||||||
system_info:{}, //system info
|
|
||||||
canvas_width:0, //canvas width px
|
|
||||||
canvas_height:0, //canvas height px
|
|
||||||
ctx:null, //canvas object
|
|
||||||
canvas_id:null, //canvas id
|
|
||||||
hidden:false,//Whether to hide canvas
|
|
||||||
scale:1,//canvas scale
|
|
||||||
r_canvas_scale:1,
|
|
||||||
if_ctx:true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods:{
|
|
||||||
/**
|
|
||||||
* save r-canvas.vue object
|
|
||||||
* @param {Object} that
|
|
||||||
*/
|
|
||||||
// saveThis(that){
|
|
||||||
// rCanvasThis = that
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Draw round rect text
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {Number} config.x x坐标
|
|
||||||
* @param {Number} config.y y坐标
|
|
||||||
* @param {Number} config.w 宽度
|
|
||||||
* @param {Number} config.h 高度
|
|
||||||
* @param {Number} config.radius 圆角弧度
|
|
||||||
* @param {String} config.fill_color 矩形颜色
|
|
||||||
*/
|
|
||||||
fillRoundRect(config) {
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
let x = this.compatibilitySize(parseFloat(config.x)*this.scale)
|
|
||||||
let y = this.compatibilitySize(parseFloat(config.y)*this.scale)
|
|
||||||
let w = this.compatibilitySize(parseFloat(config.w)*this.scale)
|
|
||||||
let h = this.compatibilitySize(parseFloat(config.h)*this.scale)
|
|
||||||
let radius = config.radius?parseFloat(config.radius)*this.scale:10*this.scale
|
|
||||||
|
|
||||||
let fill_color = config.fill_color || "black"
|
|
||||||
// The diameter of the circle must be less than the width and height of the rectangle
|
|
||||||
if (2 * radius > w || 2 * radius > h) {
|
|
||||||
reject("The diameter of the circle must be less than the width and height of the rectangle")
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.ctx.save();
|
|
||||||
this.ctx.translate(x, y);
|
|
||||||
//
|
|
||||||
this.drawRoundRectPath({
|
|
||||||
w: w,
|
|
||||||
h: h,
|
|
||||||
radius: radius
|
|
||||||
});
|
|
||||||
this.ctx.fillStyle = fill_color
|
|
||||||
this.ctx.fill();
|
|
||||||
this.ctx.restore();
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Draws the sides of a rounded rectangle
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {Number} config.w 宽度
|
|
||||||
* @param {Number} config.h 高度
|
|
||||||
* @param {Number} config.radius 圆角弧度
|
|
||||||
*/
|
|
||||||
drawRoundRectPath(config) {
|
|
||||||
this.ctx.beginPath(0);
|
|
||||||
this.ctx.arc(config.w - config.radius, config.h - config.radius, config.radius, 0, Math.PI / 2);
|
|
||||||
this.ctx.lineTo(config.radius, config.h);
|
|
||||||
this.ctx.arc(config.radius, config.h - config.radius, config.radius, Math.PI / 2, Math.PI);
|
|
||||||
this.ctx.lineTo(0, config.radius);
|
|
||||||
this.ctx.arc(config.radius, config.radius, config.radius, Math.PI, Math.PI * 3 / 2);
|
|
||||||
this.ctx.lineTo(config.w - config.radius, 0);
|
|
||||||
this.ctx.arc(config.w - config.radius, config.radius, config.radius, Math.PI * 3 / 2, Math.PI * 2);
|
|
||||||
this.ctx.lineTo(config.w, config.h - config.radius);
|
|
||||||
this.ctx.closePath();
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Draw special Text,line wrapping is not supported
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {String} config.text 文字
|
|
||||||
* @param {Number} config.x x坐标
|
|
||||||
* @param {Number} config.y y坐标
|
|
||||||
* @param {String} config.font_color 文字颜色
|
|
||||||
* @param {String} config.font_family 文字字体
|
|
||||||
* @param {Number} config.font_size 文字大小(px)
|
|
||||||
*/
|
|
||||||
drawSpecialText(params){
|
|
||||||
let general = params.general
|
|
||||||
let list = params.list
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
if(!general){
|
|
||||||
reject("general cannot be empty:101")
|
|
||||||
return;
|
|
||||||
}else if(list && list.length>0){
|
|
||||||
for(let i in list){
|
|
||||||
if(i != 0){
|
|
||||||
let font_size = list[i-1].font_size?parseFloat(list[i-1].font_size):20
|
|
||||||
this.ctx.setFontSize(font_size)
|
|
||||||
general.x = parseFloat(general.x) + this.ctx.measureText(list[i-1].text).width
|
|
||||||
}
|
|
||||||
list[i].x = general.x
|
|
||||||
list[i].y = general.y + (list[i].margin_top?parseFloat(list[i].margin_top):0)
|
|
||||||
await this.drawText(list[i])
|
|
||||||
}
|
|
||||||
resolve()
|
|
||||||
}else{
|
|
||||||
reject("The length of config arr is less than 0")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* array delete empty
|
|
||||||
* @param {Object} arr
|
|
||||||
*/
|
|
||||||
arrDeleteEmpty(arr){
|
|
||||||
let newArr = []
|
|
||||||
for(let i in arr){
|
|
||||||
if(arr[i]){
|
|
||||||
newArr.push(arr[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newArr
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Draw Text,support line
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {String} config.text 文字
|
|
||||||
* @param {Number} config.max_width 文字最大宽度(大于宽度自动换行)
|
|
||||||
* @param {Number} config.line_height 文字上下行间距
|
|
||||||
* @param {Number} config.x x坐标
|
|
||||||
* @param {Number} config.y y坐标
|
|
||||||
* @param {String} config.font_color 文字颜色
|
|
||||||
* @param {String} config.font_family 文字字体 默认值:Arial
|
|
||||||
* @param {String} config.text_align 文字对齐方式(left/center/right)
|
|
||||||
* @param {Number} config.font_size 文字大小(px)
|
|
||||||
* @param {Boolean} config.line_through_height 中划线大小
|
|
||||||
* @param {Boolean} config.line_through_color 中划线颜色
|
|
||||||
* @param {String} config.font_style 规定文字样式
|
|
||||||
* @param {String} config.font_variant 规定字体变体
|
|
||||||
* @param {String} config.font_weight 规定字体粗细
|
|
||||||
* @param {String} config.line_through_cap 线末端类型
|
|
||||||
* @param {String} config.line_clamp 最大行数
|
|
||||||
* @param {String} config.line_clamp_hint 超过line_clamp后,尾部显示的自定义标识 如 ...
|
|
||||||
* @param {String} config.is_line_break 是否开启换行符换行
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
drawText(config,configuration = {}){
|
|
||||||
|
|
||||||
configuration['line_num'] = configuration.line_num?configuration.line_num:0
|
|
||||||
configuration['text_width'] = configuration.text_width?configuration.text_width:0
|
|
||||||
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
|
|
||||||
if(config.text){
|
|
||||||
|
|
||||||
let draw_width = 0,draw_height = 0,draw_x = config.x,draw_y = config.y
|
|
||||||
let font_size = config.font_size?(parseFloat(config.font_size)*this.scale):(20*this.scale)
|
|
||||||
let font_color = config.font_color || "#000"
|
|
||||||
let font_family = config.font_family || "Arial"
|
|
||||||
let line_height = config.line_height || config.font_size || 20
|
|
||||||
let text_align = config.text_align || "left"
|
|
||||||
let font_weight = config.font_weight || "normal"
|
|
||||||
let font_variant = config.font_variant || "normal"
|
|
||||||
let font_style = config.font_style || "normal"
|
|
||||||
let line_clamp_hint = config.line_clamp_hint || '...'
|
|
||||||
let lineBreakJoinText = ""
|
|
||||||
let max_width = config.max_width?parseFloat(config.max_width)*this.scale:0
|
|
||||||
// checkout is line break
|
|
||||||
if(config.is_line_break){
|
|
||||||
let splitTextArr = config.text.split(/[\n]/g)
|
|
||||||
if(splitTextArr && splitTextArr.length > 0){
|
|
||||||
let newSplitTextArr = this.arrDeleteEmpty(splitTextArr)
|
|
||||||
if(newSplitTextArr && newSplitTextArr.length > 0){
|
|
||||||
lineBreakJoinText = newSplitTextArr.slice(1).join("\n")
|
|
||||||
config.text = newSplitTextArr[0]
|
|
||||||
}else{
|
|
||||||
reject("Text cannot be empty:103")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
reject("Text cannot be empty:102")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ctx.setFillStyle(font_color) // color
|
|
||||||
this.ctx.textAlign = text_align;
|
|
||||||
this.ctx.font = `${font_style} ${font_variant} ${font_weight} ${parseInt(font_size)}px ${font_family}`
|
|
||||||
if(configuration.text_width >= this.ctx.measureText(config.text).width){
|
|
||||||
draw_width = configuration.text_width
|
|
||||||
}else if(max_width > 0){
|
|
||||||
draw_width = max_width < this.ctx.measureText(config.text).width ? this.resetCompatibilitySize(max_width) : this.resetCompatibilitySize(this.ctx.measureText(config.text).width)
|
|
||||||
}else{
|
|
||||||
draw_width = this.ctx.measureText(config.text).width
|
|
||||||
}
|
|
||||||
configuration.text_width = draw_width / this.scale
|
|
||||||
if( max_width && this.compatibilitySize(this.ctx.measureText(config.text).width) > this.compatibilitySize(max_width)){
|
|
||||||
let current_text = ""
|
|
||||||
let text_arr = config.text.split("")
|
|
||||||
for(let i in text_arr){
|
|
||||||
if( this.compatibilitySize(this.ctx.measureText(current_text+text_arr[i]).width) > this.compatibilitySize(max_width) ){
|
|
||||||
// Hyphenation that is greater than the drawable width continues to draw
|
|
||||||
if(config.line_clamp && parseInt(config.line_clamp) == 1){
|
|
||||||
// Subtracting the current_text tail width from the line_clamp_hint width
|
|
||||||
let current_text_arr = current_text.split('')
|
|
||||||
let json_current_text = ''
|
|
||||||
while(true){
|
|
||||||
current_text_arr = current_text_arr.slice(1)
|
|
||||||
json_current_text = current_text_arr.join('')
|
|
||||||
if(this.compatibilitySize(this.ctx.measureText(json_current_text).width) <= this.compatibilitySize(this.ctx.measureText(line_clamp_hint).width)){
|
|
||||||
current_text = current_text.replace(json_current_text,'')
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
configuration.line_num += 1
|
|
||||||
this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
|
|
||||||
this.ctx.fillText(current_text + line_clamp_hint, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
|
|
||||||
}else{
|
|
||||||
configuration.line_num += 1
|
|
||||||
this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
|
|
||||||
this.ctx.fillText(current_text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
|
|
||||||
config.text = text_arr.slice(i).join("")
|
|
||||||
config.y = config.y + line_height
|
|
||||||
if(config.line_clamp){
|
|
||||||
config.line_clamp = parseInt(config.line_clamp) - 1
|
|
||||||
}
|
|
||||||
await this.drawText(config,configuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}else{
|
|
||||||
current_text = current_text+text_arr[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
if(config.line_through_height){
|
|
||||||
let x = parseFloat(config.x)*this.scale
|
|
||||||
let w
|
|
||||||
let y = parseFloat(config.y)*this.scale - (font_size / 2.6)
|
|
||||||
if(text_align == "left"){
|
|
||||||
w = this.ctx.measureText(config.text).width/1.1 + parseFloat(config.x)*this.scale
|
|
||||||
}else if(text_align == "right"){
|
|
||||||
w = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width/1.1
|
|
||||||
}else if(text_align == "center"){
|
|
||||||
x = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width / 1.1 / 2
|
|
||||||
w = parseFloat(config.x)*this.scale + this.ctx.measureText(config.text).width / 1.1 / 2
|
|
||||||
}
|
|
||||||
this.drawLineTo({
|
|
||||||
x:x,
|
|
||||||
y:y,
|
|
||||||
w:w,
|
|
||||||
h:y,
|
|
||||||
line_width:config.line_through_height,
|
|
||||||
line_color:config.line_through_color,
|
|
||||||
line_cap:config.line_through_cap
|
|
||||||
})
|
|
||||||
}
|
|
||||||
configuration.line_num += 1
|
|
||||||
this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
|
|
||||||
this.ctx.fillText(config.text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
|
|
||||||
if(config.line_clamp){
|
|
||||||
config.line_clamp = parseInt(config.line_clamp) - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(lineBreakJoinText){
|
|
||||||
await this.drawText({...config,text:lineBreakJoinText,y:config.y + line_height},configuration)
|
|
||||||
}
|
|
||||||
draw_height = config.font_size * configuration.line_num
|
|
||||||
draw_width = configuration.text_width
|
|
||||||
resolve({draw_width,draw_height,draw_x,draw_y})
|
|
||||||
}else{
|
|
||||||
reject("Text cannot be empty:101")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Draw Line
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {Object} config.x x坐标
|
|
||||||
* @param {Object} config.y y坐标
|
|
||||||
* @param {Object} config.w 线的宽度
|
|
||||||
* @param {Object} config.h 线的高度
|
|
||||||
* @param {Object} config.line_width 线的宽度
|
|
||||||
* @param {Object} config.line_color 线条颜色
|
|
||||||
*/
|
|
||||||
drawLineTo(config){
|
|
||||||
let x = this.compatibilitySize(config.x)
|
|
||||||
let y = this.compatibilitySize(config.y)
|
|
||||||
let w = this.compatibilitySize(config.w)
|
|
||||||
let h = this.compatibilitySize(config.h)
|
|
||||||
let line_width = config.line_width?parseFloat(config.line_width)*this.scale:1*this.scale
|
|
||||||
let line_color = config.line_color || "black"
|
|
||||||
let line_cap = config.line_cap || "butt"
|
|
||||||
this.ctx.beginPath()
|
|
||||||
this.ctx.lineCap = line_cap
|
|
||||||
this.ctx.lineWidth = line_width
|
|
||||||
this.ctx.strokeStyle = line_color
|
|
||||||
this.ctx.moveTo(x,y)
|
|
||||||
this.ctx.lineTo(w,h)
|
|
||||||
this.ctx.stroke()
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Compatibility px
|
|
||||||
* @param {Object} size
|
|
||||||
*/
|
|
||||||
compatibilitySize(size) {
|
|
||||||
let canvasSize = (parseFloat(size) / 750) * this.system_info.windowWidth
|
|
||||||
canvasSize = parseFloat(canvasSize * 2)
|
|
||||||
return canvasSize
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Restore compatibility px
|
|
||||||
* @param {Object} size
|
|
||||||
*/
|
|
||||||
resetCompatibilitySize(size) {
|
|
||||||
let canvasSize = (parseFloat(size/2)/this.system_info.windowWidth) * 750
|
|
||||||
return canvasSize
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Init canvas
|
|
||||||
*/
|
|
||||||
init(config){
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
if(!config.canvas_id){
|
|
||||||
reject("Canvas ID cannot be empty, please refer to the usage example")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.hidden = config.hidden
|
|
||||||
this.canvas_id = config.canvas_id
|
|
||||||
let system_info = await uni.getSystemInfoSync()
|
|
||||||
this.system_info = system_info
|
|
||||||
this.scale = config.scale&&parseFloat(config.scale)>0?parseInt(config.scale):1
|
|
||||||
this.canvas_width = (config.canvas_width ? this.compatibilitySize(config.canvas_width) : system_info.windowWidth) * this.scale
|
|
||||||
this.canvas_height = (config.canvas_height ? this.compatibilitySize(config.canvas_height) : system_info.windowHeight) * this.scale,
|
|
||||||
this.r_canvas_scale = 1/this.scale
|
|
||||||
this.ctx = uni.createCanvasContext(this.canvas_id,this)
|
|
||||||
this.setCanvasConfig({
|
|
||||||
global_alpha:config.global_alpha?parseFloat(config.global_alpha):1,
|
|
||||||
backgroundColor:config.background_color?config.background_color:"#fff"
|
|
||||||
})
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* clear canvas all path
|
|
||||||
*/
|
|
||||||
clearCanvas(){
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
if(!this.ctx){
|
|
||||||
reject("canvas is not initialized:101")
|
|
||||||
return
|
|
||||||
}else{
|
|
||||||
this.ctx.clearRect(0,0,parseFloat(this.canvas_width)*this.scale,parseFloat(this.canvas_height)*this.scale)
|
|
||||||
await this.draw()
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Set canvas config
|
|
||||||
* @param {Object} config
|
|
||||||
*/
|
|
||||||
setCanvasConfig(config){
|
|
||||||
this.ctx.globalAlpha = config.global_alpha
|
|
||||||
this.ctx.fillStyle = config.backgroundColor
|
|
||||||
this.ctx.fillRect(0, 0, parseFloat(this.canvas_width)*this.scale, parseFloat(this.canvas_height)*this.scale)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* set canvas width
|
|
||||||
* @param {Object} width
|
|
||||||
*/
|
|
||||||
setCanvasWidth(width){
|
|
||||||
if(!width){
|
|
||||||
uni.showToast({
|
|
||||||
title:'setCanvasWidth:width error',
|
|
||||||
icon:'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.canvas_width = this.compatibilitySize(parseFloat(width)) * this.scale
|
|
||||||
this.ctx.width = this.canvas_width
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* set canvas height
|
|
||||||
* @param {Object} height
|
|
||||||
*/
|
|
||||||
setCanvasHeight(height){
|
|
||||||
if(!height){
|
|
||||||
uni.showToast({
|
|
||||||
title:'setCanvasWidth:height error',
|
|
||||||
icon:'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.canvas_height = this.compatibilitySize(parseFloat(height)) * this.scale
|
|
||||||
this.ctx.height = this.canvas_height
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Draw to filepath
|
|
||||||
*/
|
|
||||||
draw(callback){
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
let stop = setTimeout(()=>{
|
|
||||||
this.ctx.draw(false,setTimeout(()=>{
|
|
||||||
uni.canvasToTempFilePath({
|
|
||||||
canvasId: this.canvas_id,
|
|
||||||
quality: 1,
|
|
||||||
success: (res)=>{
|
|
||||||
console.log('res',res)
|
|
||||||
resolve(res)
|
|
||||||
callback && callback(res)
|
|
||||||
},
|
|
||||||
fail:(err)=>{
|
|
||||||
reject(JSON.stringify(err)|| "Failed to generate poster:101")
|
|
||||||
}
|
|
||||||
},this)
|
|
||||||
},300))
|
|
||||||
clearTimeout(stop)
|
|
||||||
},300)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* draw rect
|
|
||||||
* @param {Number} config.x x坐标
|
|
||||||
* @param {Number} config.y y坐标
|
|
||||||
* @param {Number} config.w 图形宽度(px)
|
|
||||||
* @param {Number} config.h 图形高度(px)
|
|
||||||
* @param {Number} config.color 图形颜色
|
|
||||||
* @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius)
|
|
||||||
* @param {Number} config.border_width 边框大小
|
|
||||||
* @param {Number} config.border_color 边框颜色
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
drawRect(config){
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
if(!config.border_width || config.border_width <=0){
|
|
||||||
config.border_width = 0
|
|
||||||
}else{
|
|
||||||
config.border_width = parseFloat(config.border_width)
|
|
||||||
}
|
|
||||||
if(parseFloat(config.border_width) > 0){
|
|
||||||
let sub_config = JSON.parse(JSON.stringify(config))
|
|
||||||
sub_config.border_width = 0
|
|
||||||
sub_config.w = config.w + config.border_width
|
|
||||||
sub_config.h = config.h + config.border_width
|
|
||||||
sub_config.color = config.border_color || 'black'
|
|
||||||
if(sub_config.border_radius){
|
|
||||||
sub_config.border_radius = parseFloat(sub_config.border_radius) + parseFloat(config.border_width) / 2
|
|
||||||
}
|
|
||||||
await this.drawRect(sub_config)
|
|
||||||
}
|
|
||||||
|
|
||||||
let color = config.color || 'white'
|
|
||||||
config.x = (parseFloat(config.x) + config.border_width / 2)
|
|
||||||
config.y = (parseFloat(config.y) + config.border_width / 2)
|
|
||||||
config['color'] = color
|
|
||||||
this.ctx.fillStyle = color;
|
|
||||||
if(config.is_radius || config.border_radius){
|
|
||||||
this.setNativeBorderRadius(config)
|
|
||||||
this.ctx.fill()
|
|
||||||
}else{
|
|
||||||
console.log('config.border_width',config.border_width)
|
|
||||||
this.ctx.fillRect(this.compatibilitySize(config.x*this.scale),this.compatibilitySize(config.y*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))
|
|
||||||
}
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Draw image
|
|
||||||
* @param {Object} config
|
|
||||||
* @param {String} config.url 图片链接
|
|
||||||
* @param {Number} config.x x坐标
|
|
||||||
* @param {Number} config.y y坐标
|
|
||||||
* @param {Number} config.w 图片宽度(px)
|
|
||||||
* @param {Number} config.h 图片高度(px)
|
|
||||||
* @param {Number} config.border_width 边大小
|
|
||||||
* @param {Number} config.border_color 边颜色
|
|
||||||
* @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius)
|
|
||||||
* @param {Number} config.border_radius 圆角弧度
|
|
||||||
*/
|
|
||||||
drawImage(config){
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
if(config.url){
|
|
||||||
let type = 0 // 1、network image 2、native image 3、base64 image
|
|
||||||
let image_url
|
|
||||||
let reg = /^https?/ig;
|
|
||||||
if(reg.test(config.url)){
|
|
||||||
type = 1
|
|
||||||
}else{
|
|
||||||
if((config.url.indexOf("data:image/png;base64") != -1) || config.url.indexOf("data:image/jpeg;base64") != -1 || config.url.indexOf("data:image/gif;base64") != -1){
|
|
||||||
type = 3
|
|
||||||
}else{
|
|
||||||
type = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(type == 1){
|
|
||||||
// network image
|
|
||||||
await this.downLoadNetworkFile(config.url).then(res=>{ // two function
|
|
||||||
image_url = res
|
|
||||||
}).catch(err=>{
|
|
||||||
reject(err)
|
|
||||||
return;
|
|
||||||
})
|
|
||||||
}else if(type == 2){
|
|
||||||
// native image
|
|
||||||
const imageInfoResult = await uni.getImageInfo({
|
|
||||||
src: config.url
|
|
||||||
});
|
|
||||||
try{
|
|
||||||
if(imageInfoResult.length <= 1){
|
|
||||||
reject(imageInfoResult[0].errMsg + ':404')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}catch(e){
|
|
||||||
reject(e+':500')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let base64 = await this.urlToBase64({url:imageInfoResult[1].path})
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
await this.base64ToNative({url:base64}).then(res=>{
|
|
||||||
image_url = res
|
|
||||||
}).catch(err=>{
|
|
||||||
reject(JSON.stringify(err)+":501")
|
|
||||||
return;
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
image_url = base64
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
}else if(type == 3){
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
await this.base64ToNative({url:config.url}).then(res=>{
|
|
||||||
image_url = res
|
|
||||||
}).catch(err=>{
|
|
||||||
reject(JSON.stringify(err)+":500")
|
|
||||||
return;
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
image_url = config.url
|
|
||||||
// #endif
|
|
||||||
}else{
|
|
||||||
reject("Other Type Errors:101")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if(config.border_width){
|
|
||||||
let border_radius = 0
|
|
||||||
if(config.border_radius){
|
|
||||||
let multiple = config.w / config.border_radius
|
|
||||||
border_radius = (parseFloat(config.w) + parseFloat(config.border_width)) / multiple
|
|
||||||
}
|
|
||||||
// drawRect
|
|
||||||
await this.drawRect({
|
|
||||||
x:parseFloat(config.x) - parseFloat(config.border_width)/2,
|
|
||||||
y:parseFloat(config.y) - parseFloat(config.border_width)/2,
|
|
||||||
w:parseFloat(config.w) + parseFloat(config.border_width),
|
|
||||||
h:parseFloat(config.h) + parseFloat(config.border_width),
|
|
||||||
color:config.border_color,
|
|
||||||
border_radius:border_radius,
|
|
||||||
border_width:config.border_width,
|
|
||||||
is_radius:config.is_radius
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(config.border_radius){
|
|
||||||
config.color = config.color?config.color:'rgba(0,0,0,0)'
|
|
||||||
|
|
||||||
// 圆角有白边,+0.5的误差
|
|
||||||
config.w = config.w + 0.3
|
|
||||||
config.h = config.h + 0.3
|
|
||||||
|
|
||||||
this.setNativeBorderRadius(config)
|
|
||||||
}else if(config.is_radius){
|
|
||||||
//已废弃 is_radius
|
|
||||||
this.ctx.setStrokeStyle("rgba(0,0,0,0)")
|
|
||||||
this.ctx.save()
|
|
||||||
this.ctx.beginPath()
|
|
||||||
this.ctx.arc(this.compatibilitySize(parseFloat(config.x)*this.scale+parseFloat(config.w)*this.scale/2), this.compatibilitySize(parseFloat(config.y)*this.scale+parseFloat(config.h)*this.scale/2), this.compatibilitySize(parseFloat(config.w)*this.scale/2), 0, 2 * Math.PI, false)
|
|
||||||
this.ctx.stroke();
|
|
||||||
this.ctx.clip()
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.ctx.drawImage(image_url,this.compatibilitySize(parseFloat(config.x)*this.scale),this.compatibilitySize(parseFloat(config.y)*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))
|
|
||||||
this.ctx.restore() //Restore previously saved drawing context
|
|
||||||
resolve()
|
|
||||||
}else{
|
|
||||||
let err_msg = "Links cannot be empty:101"
|
|
||||||
reject(err_msg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* base64 to native available path
|
|
||||||
* @param {Object} config
|
|
||||||
*/
|
|
||||||
base64ToNative(config){
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
let fileName = new Date().getTime()
|
|
||||||
var filePath = `${wx.env.USER_DATA_PATH}/${fileName}_rCanvas.png`
|
|
||||||
wx.getFileSystemManager().writeFile({
|
|
||||||
filePath: filePath,
|
|
||||||
data: config.url.replace(/^data:\S+\/\S+;base64,/, ''),
|
|
||||||
encoding: 'base64',
|
|
||||||
success: function() {
|
|
||||||
resolve(filePath)
|
|
||||||
},
|
|
||||||
fail: function(error) {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* native url to base64
|
|
||||||
* @param {Object} config
|
|
||||||
*/
|
|
||||||
urlToBase64(config){
|
|
||||||
return new Promise(async (resolve,reject)=>{
|
|
||||||
if (typeof window != 'undefined') {
|
|
||||||
await this.downLoadNetworkFile(config.url).then(res=>{ // two function
|
|
||||||
resolve(res)
|
|
||||||
}).catch(err=>{
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
}else if (typeof plus != 'undefined') {
|
|
||||||
plus.io.resolveLocalFileSystemURL(config.url,(obj)=>{
|
|
||||||
obj.file((file)=>{
|
|
||||||
let fileReader = new plus.io.FileReader()
|
|
||||||
fileReader.onload = (res)=>{
|
|
||||||
resolve(res.target.result)
|
|
||||||
}
|
|
||||||
fileReader.onerror = (err)=>{
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
fileReader.readAsDataURL(file)
|
|
||||||
}, (err)=>{
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
},(err)=>{
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
}else if(typeof wx != 'undefined'){
|
|
||||||
wx.getFileSystemManager().readFile({
|
|
||||||
filePath: config.url,
|
|
||||||
encoding: 'base64',
|
|
||||||
success: function(res) {
|
|
||||||
resolve('data:image/png;base64,' + res.data)
|
|
||||||
},
|
|
||||||
fail: function(error) {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setNativeBorderRadius(config){
|
|
||||||
let border_radius = config.border_radius?(parseFloat(config.border_radius)*this.scale):(20*this.scale)
|
|
||||||
if ((parseFloat(config.w)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.w)*this.scale) / 2;
|
|
||||||
if ((parseFloat(config.h)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.h)*this.scale) / 2;
|
|
||||||
this.ctx.beginPath();
|
|
||||||
this.ctx.moveTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + border_radius), this.compatibilitySize((parseFloat(config.y)*this.scale)));
|
|
||||||
this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));
|
|
||||||
this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));
|
|
||||||
this.ctx.arcTo((this.compatibilitySize(parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));
|
|
||||||
this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));
|
|
||||||
this.ctx.closePath();
|
|
||||||
this.ctx.strokeStyle = config.color || config.border_color || 'rgba(0,0,0,0)'; // 设置绘制边框的颜色
|
|
||||||
this.ctx.stroke();
|
|
||||||
this.ctx.save()
|
|
||||||
this.ctx.clip();
|
|
||||||
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Download network file
|
|
||||||
* @param {Object} url : download url
|
|
||||||
*/
|
|
||||||
downLoadNetworkFile(url){
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
uni.downloadFile({
|
|
||||||
url,
|
|
||||||
success:(res)=>{
|
|
||||||
if(res.statusCode == 200){
|
|
||||||
resolve(res.tempFilePath)
|
|
||||||
}else{
|
|
||||||
reject("Download Image Fail:102")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail:(err)=>{
|
|
||||||
reject("Download Image Fail:101")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Save image to natice
|
|
||||||
* @param {Object} filePath : native imageUrl
|
|
||||||
*/
|
|
||||||
saveImage(filePath){
|
|
||||||
return new Promise((resolve,reject)=>{
|
|
||||||
if(!filePath){
|
|
||||||
reject("FilePath cannot be null:101")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #ifdef H5
|
|
||||||
var createA = document.createElement("a");
|
|
||||||
createA.download = filePath;
|
|
||||||
createA.href = filePath;
|
|
||||||
document.body.appendChild(createA);
|
|
||||||
createA.click();
|
|
||||||
createA.remove();
|
|
||||||
resolve()
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifndef H5
|
|
||||||
uni.saveImageToPhotosAlbum({
|
|
||||||
filePath: filePath,
|
|
||||||
success:(res)=>{
|
|
||||||
resolve(res)
|
|
||||||
},
|
|
||||||
fail:(err)=>{
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view>
|
|
||||||
<view class="r-canvas-component" :style="{width:canvas_width/scale+'px',height:canvas_height/scale+'px'}" :class="{'hidden':hidden}">
|
|
||||||
<canvas class="r-canvas" v-if="canvas_id" :canvas-id="canvas_id" :id="canvas_id" :style="{width:canvas_width+'px',height:canvas_height+'px','transform': `scale(${r_canvas_scale})`}"></canvas>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import rCanvasJS from "./r-canvas.js"
|
|
||||||
export default {
|
|
||||||
mixins:[rCanvasJS]
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.r-canvas{
|
|
||||||
transform-origin: 0 0;
|
|
||||||
}
|
|
||||||
.r-canvas-component{
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.r-canvas-component.hidden{
|
|
||||||
position: fixed;
|
|
||||||
top:-5000upx;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,784 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view v-show="show" class="t-wrapper" @touchmove.stop.prevent="moveHandle">
|
|
||||||
<view class="t-mask" :class="{active:active}" @click.stop="close"></view>
|
|
||||||
<view class="t-box" :class="{active:active}">
|
|
||||||
<view class="t-header">
|
|
||||||
<view class="t-header-button" @click="close">取消</view>
|
|
||||||
<view class="t-header-button" @click="confirm">确认</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-color__box" :style="{ background: 'rgb(' + bgcolor.r + ',' + bgcolor.g + ',' + bgcolor.b + ')'}">
|
|
||||||
<view class="t-background boxs" @touchstart="touchstart($event, 0)" @touchmove="touchmove($event, 0)" @touchend="touchend($event, 0)">
|
|
||||||
<view class="t-color-mask"></view>
|
|
||||||
<view class="t-pointer" :style="{ top: site[0].top - 8 + 'px', left: site[0].left - 8 + 'px' }"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-control__box">
|
|
||||||
<view class="t-control__color">
|
|
||||||
<view class="t-control__color-content" :style="{ background: 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')' }"></view>
|
|
||||||
</view>
|
|
||||||
<view class="t-control-box__item">
|
|
||||||
<view class="t-controller boxs" @touchstart="touchstart($event, 1)" @touchmove="touchmove($event, 1)" @touchend="touchend($event, 1)">
|
|
||||||
<view class="t-hue">
|
|
||||||
<view class="t-circle" :style="{ left: site[1].left - 12 + 'px' }"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-controller boxs" @touchstart="touchstart($event, 2)" @touchmove="touchmove($event, 2)" @touchend="touchend($event, 2)">
|
|
||||||
<view class="t-transparency">
|
|
||||||
<view class="t-circle" :style="{ left: site[2].left - 12 + 'px' }"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-result__box">
|
|
||||||
<view v-if="mode" class="t-result__item">
|
|
||||||
<view class="t-result__box-input">{{hex}}</view>
|
|
||||||
<view class="t-result__box-text">HEX</view>
|
|
||||||
</view>
|
|
||||||
<template v-else>
|
|
||||||
<view class="t-result__item">
|
|
||||||
<view class="t-result__box-input">{{rgba.r}}</view>
|
|
||||||
<view class="t-result__box-text">R</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-result__item">
|
|
||||||
<view class="t-result__box-input">{{rgba.g}}</view>
|
|
||||||
<view class="t-result__box-text">G</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-result__item">
|
|
||||||
<view class="t-result__box-input">{{rgba.b}}</view>
|
|
||||||
<view class="t-result__box-text">B</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-result__item">
|
|
||||||
<view class="t-result__box-input">{{rgba.a}}</view>
|
|
||||||
<view class="t-result__box-text">A</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<view class="t-result__item t-select" @click="select">
|
|
||||||
<view class="t-result__box-input">
|
|
||||||
<view>切换</view>
|
|
||||||
<view>模式</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="t-alternative">
|
|
||||||
<view class="t-alternative__item" v-for="(item,index) in colorList" :key="index">
|
|
||||||
<view class="t-alternative__item-content" :style="{ background: 'rgba(' + item.r + ',' + item.g + ',' + item.b + ',' + item.a + ')' }"
|
|
||||||
@click="selectColor(item)">
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
color: {
|
|
||||||
type: Object,
|
|
||||||
default () {
|
|
||||||
return {
|
|
||||||
r: 0,
|
|
||||||
g: 0,
|
|
||||||
b: 0,
|
|
||||||
a: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
spareColor: {
|
|
||||||
type: Array,
|
|
||||||
default () {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
show: false,
|
|
||||||
active: false,
|
|
||||||
// rgba 颜色
|
|
||||||
rgba: {
|
|
||||||
r: 0,
|
|
||||||
g: 0,
|
|
||||||
b: 0,
|
|
||||||
a: 1
|
|
||||||
},
|
|
||||||
// hsb 颜色
|
|
||||||
hsb: {
|
|
||||||
h: 0,
|
|
||||||
s: 0,
|
|
||||||
b: 0
|
|
||||||
},
|
|
||||||
site: [{
|
|
||||||
top: 0,
|
|
||||||
left: 0
|
|
||||||
}, {
|
|
||||||
left: 0
|
|
||||||
}, {
|
|
||||||
left: 0
|
|
||||||
}],
|
|
||||||
index: 0,
|
|
||||||
bgcolor: {
|
|
||||||
r: 255,
|
|
||||||
g: 0,
|
|
||||||
b: 0,
|
|
||||||
a: 1
|
|
||||||
},
|
|
||||||
hex: '#000000',
|
|
||||||
mode: true,
|
|
||||||
colorList: [{
|
|
||||||
r: 244,
|
|
||||||
g: 67,
|
|
||||||
b: 54,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 233,
|
|
||||||
g: 30,
|
|
||||||
b: 99,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 156,
|
|
||||||
g: 39,
|
|
||||||
b: 176,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 103,
|
|
||||||
g: 58,
|
|
||||||
b: 183,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 63,
|
|
||||||
g: 81,
|
|
||||||
b: 181,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 33,
|
|
||||||
g: 150,
|
|
||||||
b: 243,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 3,
|
|
||||||
g: 169,
|
|
||||||
b: 244,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 0,
|
|
||||||
g: 188,
|
|
||||||
b: 212,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 0,
|
|
||||||
g: 150,
|
|
||||||
b: 136,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 76,
|
|
||||||
g: 175,
|
|
||||||
b: 80,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 139,
|
|
||||||
g: 195,
|
|
||||||
b: 74,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 205,
|
|
||||||
g: 220,
|
|
||||||
b: 57,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 255,
|
|
||||||
g: 235,
|
|
||||||
b: 59,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 255,
|
|
||||||
g: 193,
|
|
||||||
b: 7,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 255,
|
|
||||||
g: 152,
|
|
||||||
b: 0,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 255,
|
|
||||||
g: 87,
|
|
||||||
b: 34,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 121,
|
|
||||||
g: 85,
|
|
||||||
b: 72,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 158,
|
|
||||||
g: 158,
|
|
||||||
b: 158,
|
|
||||||
a: 1
|
|
||||||
}, {
|
|
||||||
r: 0,
|
|
||||||
g: 0,
|
|
||||||
b: 0,
|
|
||||||
a: 0.5
|
|
||||||
}, {
|
|
||||||
r: 0,
|
|
||||||
g: 0,
|
|
||||||
b: 0,
|
|
||||||
a: 0
|
|
||||||
}, ]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.rgba = this.color;
|
|
||||||
if (this.spareColor.length !== 0) {
|
|
||||||
this.colorList = this.spareColor;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* 初始化
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
// hsb 颜色
|
|
||||||
this.hsb = this.rgbToHex(this.rgba);
|
|
||||||
// this.setColor();
|
|
||||||
this.setValue(this.rgba);
|
|
||||||
},
|
|
||||||
moveHandle() {},
|
|
||||||
open() {
|
|
||||||
this.show = true;
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.init();
|
|
||||||
setTimeout(() => {
|
|
||||||
this.active = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.getSelectorQuery();
|
|
||||||
}, 350)
|
|
||||||
}, 50)
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
this.active = false;
|
|
||||||
this.$nextTick(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.show = false;
|
|
||||||
}, 500)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
confirm() {
|
|
||||||
this.close();
|
|
||||||
this.$emit('confirm', {
|
|
||||||
rgba: this.rgba,
|
|
||||||
hex: this.hex
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// 选择模式
|
|
||||||
select() {
|
|
||||||
this.mode = !this.mode
|
|
||||||
},
|
|
||||||
// 常用颜色选择
|
|
||||||
selectColor(item) {
|
|
||||||
this.setColorBySelect(item)
|
|
||||||
},
|
|
||||||
touchstart(e, index) {
|
|
||||||
const {
|
|
||||||
pageX,
|
|
||||||
pageY
|
|
||||||
} = e.touches[0];
|
|
||||||
this.pageX = pageX;
|
|
||||||
this.pageY = pageY;
|
|
||||||
this.setPosition(pageX, pageY, index);
|
|
||||||
},
|
|
||||||
touchmove(e, index) {
|
|
||||||
const {
|
|
||||||
pageX,
|
|
||||||
pageY
|
|
||||||
} = e.touches[0];
|
|
||||||
this.moveX = pageX;
|
|
||||||
this.moveY = pageY;
|
|
||||||
this.setPosition(pageX, pageY, index);
|
|
||||||
},
|
|
||||||
touchend(e, index) {},
|
|
||||||
/**
|
|
||||||
* 设置位置
|
|
||||||
*/
|
|
||||||
setPosition(x, y, index) {
|
|
||||||
this.index = index;
|
|
||||||
const {
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
} = this.position[index];
|
|
||||||
// 设置最大最小值
|
|
||||||
|
|
||||||
this.site[index].left = Math.max(0, Math.min(parseInt(x - left), width));
|
|
||||||
if (index === 0) {
|
|
||||||
this.site[index].top = Math.max(0, Math.min(parseInt(y - top), height));
|
|
||||||
// 设置颜色
|
|
||||||
this.hsb.s = parseInt((100 * this.site[index].left) / width);
|
|
||||||
this.hsb.b = parseInt(100 - (100 * this.site[index].top) / height);
|
|
||||||
this.setColor();
|
|
||||||
this.setValue(this.rgba);
|
|
||||||
} else {
|
|
||||||
this.setControl(index, this.site[index].left);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 设置 rgb 颜色
|
|
||||||
*/
|
|
||||||
setColor() {
|
|
||||||
const rgb = this.HSBToRGB(this.hsb);
|
|
||||||
this.rgba.r = rgb.r;
|
|
||||||
this.rgba.g = rgb.g;
|
|
||||||
this.rgba.b = rgb.b;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 设置二进制颜色
|
|
||||||
* @param {Object} rgb
|
|
||||||
*/
|
|
||||||
setValue(rgb) {
|
|
||||||
this.hex = '#' + this.rgbToHex(rgb);
|
|
||||||
},
|
|
||||||
setControl(index, x) {
|
|
||||||
const {
|
|
||||||
top,
|
|
||||||
left,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
} = this.position[index];
|
|
||||||
|
|
||||||
if (index === 1) {
|
|
||||||
this.hsb.h = parseInt((360 * x) / width);
|
|
||||||
this.bgcolor = this.HSBToRGB({
|
|
||||||
h: this.hsb.h,
|
|
||||||
s: 100,
|
|
||||||
b: 100
|
|
||||||
});
|
|
||||||
this.setColor()
|
|
||||||
} else {
|
|
||||||
this.rgba.a = (x / width).toFixed(1);
|
|
||||||
}
|
|
||||||
this.setValue(this.rgba);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* rgb 转 二进制 hex
|
|
||||||
* @param {Object} rgb
|
|
||||||
*/
|
|
||||||
rgbToHex(rgb) {
|
|
||||||
let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
|
|
||||||
hex.map(function(str, i) {
|
|
||||||
if (str.length == 1) {
|
|
||||||
hex[i] = '0' + str;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return hex.join('');
|
|
||||||
},
|
|
||||||
setColorBySelect(getrgb) {
|
|
||||||
const {
|
|
||||||
r,
|
|
||||||
g,
|
|
||||||
b,
|
|
||||||
a
|
|
||||||
} = getrgb;
|
|
||||||
let rgb = {}
|
|
||||||
rgb = {
|
|
||||||
r: r ? parseInt(r) : 0,
|
|
||||||
g: g ? parseInt(g) : 0,
|
|
||||||
b: b ? parseInt(b) : 0,
|
|
||||||
a: a ? a : 0,
|
|
||||||
};
|
|
||||||
this.rgba = rgb;
|
|
||||||
this.hsb = this.rgbToHsb(rgb);
|
|
||||||
this.changeViewByHsb();
|
|
||||||
},
|
|
||||||
changeViewByHsb() {
|
|
||||||
const [a, b, c] = this.position;
|
|
||||||
this.site[0].left = parseInt(this.hsb.s * a.width / 100);
|
|
||||||
this.site[0].top = parseInt((100 - this.hsb.b) * a.height / 100);
|
|
||||||
this.setColor(this.hsb.h);
|
|
||||||
this.setValue(this.rgba);
|
|
||||||
this.bgcolor = this.HSBToRGB({
|
|
||||||
h: this.hsb.h,
|
|
||||||
s: 100,
|
|
||||||
b: 100
|
|
||||||
});
|
|
||||||
|
|
||||||
this.site[1].left = this.hsb.h / 360 * b.width;
|
|
||||||
this.site[2].left = this.rgba.a * c.width;
|
|
||||||
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* hsb 转 rgb
|
|
||||||
* @param {Object} 颜色模式 H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度
|
|
||||||
*/
|
|
||||||
HSBToRGB(hsb) {
|
|
||||||
let rgb = {};
|
|
||||||
let h = Math.round(hsb.h);
|
|
||||||
let s = Math.round((hsb.s * 255) / 100);
|
|
||||||
let v = Math.round((hsb.b * 255) / 100);
|
|
||||||
if (s == 0) {
|
|
||||||
rgb.r = rgb.g = rgb.b = v;
|
|
||||||
} else {
|
|
||||||
let t1 = v;
|
|
||||||
let t2 = ((255 - s) * v) / 255;
|
|
||||||
let t3 = ((t1 - t2) * (h % 60)) / 60;
|
|
||||||
if (h == 360) h = 0;
|
|
||||||
if (h < 60) {
|
|
||||||
rgb.r = t1;
|
|
||||||
rgb.b = t2;
|
|
||||||
rgb.g = t2 + t3;
|
|
||||||
} else if (h < 120) {
|
|
||||||
rgb.g = t1;
|
|
||||||
rgb.b = t2;
|
|
||||||
rgb.r = t1 - t3;
|
|
||||||
} else if (h < 180) {
|
|
||||||
rgb.g = t1;
|
|
||||||
rgb.r = t2;
|
|
||||||
rgb.b = t2 + t3;
|
|
||||||
} else if (h < 240) {
|
|
||||||
rgb.b = t1;
|
|
||||||
rgb.r = t2;
|
|
||||||
rgb.g = t1 - t3;
|
|
||||||
} else if (h < 300) {
|
|
||||||
rgb.b = t1;
|
|
||||||
rgb.g = t2;
|
|
||||||
rgb.r = t2 + t3;
|
|
||||||
} else if (h < 360) {
|
|
||||||
rgb.r = t1;
|
|
||||||
rgb.g = t2;
|
|
||||||
rgb.b = t1 - t3;
|
|
||||||
} else {
|
|
||||||
rgb.r = 0;
|
|
||||||
rgb.g = 0;
|
|
||||||
rgb.b = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
r: Math.round(rgb.r),
|
|
||||||
g: Math.round(rgb.g),
|
|
||||||
b: Math.round(rgb.b)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
rgbToHsb(rgb) {
|
|
||||||
let hsb = {
|
|
||||||
h: 0,
|
|
||||||
s: 0,
|
|
||||||
b: 0
|
|
||||||
};
|
|
||||||
let min = Math.min(rgb.r, rgb.g, rgb.b);
|
|
||||||
let max = Math.max(rgb.r, rgb.g, rgb.b);
|
|
||||||
let delta = max - min;
|
|
||||||
hsb.b = max;
|
|
||||||
hsb.s = max != 0 ? 255 * delta / max : 0;
|
|
||||||
if (hsb.s != 0) {
|
|
||||||
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta;
|
|
||||||
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta;
|
|
||||||
else hsb.h = 4 + (rgb.r - rgb.g) / delta;
|
|
||||||
} else hsb.h = -1;
|
|
||||||
hsb.h *= 60;
|
|
||||||
if (hsb.h < 0) hsb.h = 0;
|
|
||||||
hsb.s *= 100 / 255;
|
|
||||||
hsb.b *= 100 / 255;
|
|
||||||
return hsb;
|
|
||||||
},
|
|
||||||
getSelectorQuery() {
|
|
||||||
const views = uni.createSelectorQuery().in(this);
|
|
||||||
views
|
|
||||||
.selectAll('.boxs')
|
|
||||||
.boundingClientRect(data => {
|
|
||||||
if (!data || data.length === 0) {
|
|
||||||
setTimeout(() => this.getSelectorQuery(), 20)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.position = data;
|
|
||||||
// this.site[0].top = data[0].height;
|
|
||||||
// this.site[0].left = 0;
|
|
||||||
// this.site[1].left = data[1].width;
|
|
||||||
// this.site[2].left = data[2].width;
|
|
||||||
this.setColorBySelect(this.rgba);
|
|
||||||
})
|
|
||||||
.exec();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
spareColor(newVal) {
|
|
||||||
this.colorList = newVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.t-wrapper {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
z-index: 9999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-box {
|
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
padding: 30upx 0;
|
|
||||||
padding-top: 0;
|
|
||||||
background: #fff;
|
|
||||||
transition: all 0.3s;
|
|
||||||
transform: translateY(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-box.active {
|
|
||||||
transform: translateY(0%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
height: 100upx;
|
|
||||||
border-bottom: 1px #eee solid;
|
|
||||||
box-shadow: 1px 0 2px rgba(0, 0, 0, 0.1);
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-header-button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 150upx;
|
|
||||||
height: 100upx;
|
|
||||||
font-size: 30upx;
|
|
||||||
color: #666;
|
|
||||||
padding-left: 20upx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-header-button:last-child {
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding-right: 20upx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-mask {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
z-index: -1;
|
|
||||||
transition: all 0.3s;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-mask.active {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-color__box {
|
|
||||||
position: relative;
|
|
||||||
height: 400upx;
|
|
||||||
background: rgb(255, 0, 0);
|
|
||||||
overflow: hidden;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0 20upx;
|
|
||||||
margin-top: 20upx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-background {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-color-mask {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 400upx;
|
|
||||||
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-pointer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: -8px;
|
|
||||||
left: -8px;
|
|
||||||
z-index: 2;
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
border: 1px #fff solid;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-show-color {
|
|
||||||
width: 100upx;
|
|
||||||
height: 50upx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-control__box {
|
|
||||||
margin-top: 50upx;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
padding-left: 20upx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-control__color {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 100upx;
|
|
||||||
height: 100upx;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #fff;
|
|
||||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
|
||||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
|
||||||
background-size: 36upx 36upx;
|
|
||||||
background-position: 0 0, 18upx 18upx;
|
|
||||||
border: 1px #eee solid;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-control__color-content {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-control-box__item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 30upx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-controller {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 16px;
|
|
||||||
background-color: #fff;
|
|
||||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
|
||||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
|
||||||
background-size: 32upx 32upx;
|
|
||||||
background-position: 0 0, 16upx 16upx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-hue {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-transparency {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-circle {
|
|
||||||
position: absolute;
|
|
||||||
/* right: -10px; */
|
|
||||||
top: -2px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-result__box {
|
|
||||||
margin-top: 20upx;
|
|
||||||
padding: 10upx;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-result__item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 10upx;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-result__box-input {
|
|
||||||
padding: 10upx 0;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 28upx;
|
|
||||||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
|
|
||||||
color: #999;
|
|
||||||
text-align: center;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-result__box-text {
|
|
||||||
margin-top: 10upx;
|
|
||||||
font-size: 28upx;
|
|
||||||
line-height: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-select {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 150upx;
|
|
||||||
padding: 0 30upx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-select .t-result__box-input {
|
|
||||||
border-radius: 10upx;
|
|
||||||
border: none;
|
|
||||||
color: #999;
|
|
||||||
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-select .t-result__box-input:active {
|
|
||||||
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-alternative {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
/* justify-content: space-between; */
|
|
||||||
width: 100%;
|
|
||||||
padding-right: 10upx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-alternative__item {
|
|
||||||
margin-left: 12upx;
|
|
||||||
margin-top: 10upx;
|
|
||||||
width: 50upx;
|
|
||||||
height: 50upx;
|
|
||||||
border-radius: 10upx;
|
|
||||||
background-color: #fff;
|
|
||||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
|
||||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
|
||||||
background-size: 36upx 36upx;
|
|
||||||
background-position: 0 0, 18upx 18upx;
|
|
||||||
border: 1px #eee solid;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-alternative__item-content {
|
|
||||||
width: 50upx;
|
|
||||||
height: 50upx;
|
|
||||||
background: rgba(255, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.t-alternative__item:active {
|
|
||||||
transition: all 0.3s;
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user