Browse Source

村使馆

shang 1 year ago
commit
225a59fdf2
100 changed files with 19278 additions and 0 deletions
  1. 4 0
      .gitignore
  2. 82 0
      App.vue
  3. 535 0
      README.md
  4. 184 0
      colorui/animation.css
  5. 76 0
      colorui/components/cu-custom.vue
  6. 139 0
      colorui/components/curry-slide.vue
  7. 410 0
      colorui/components/curry-swiper.vue
  8. 1226 0
      colorui/icon.css
  9. 4036 0
      colorui/main.css
  10. 35 0
      common/WXBizDataCrypt.js
  11. 0 0
      common/app.css
  12. 75 0
      common/cache.js
  13. 4 0
      common/city.area.js
  14. 187 0
      common/common.css
  15. 146 0
      common/common.scss
  16. 102 0
      common/dateTimePicker.js
  17. 493 0
      common/fa.mixin.js
  18. 40 0
      common/fa.style.mixin.js
  19. 63 0
      common/fa.weixin.mixin.js
  20. 352 0
      common/html-parser.js
  21. 157 0
      common/iconfont.css
  22. 72 0
      common/share.js
  23. 1448 0
      common/uni.css
  24. 465 0
      common/util.js
  25. 334 0
      config/api.js
  26. 647 0
      config/common.js
  27. 23 0
      config/config.js
  28. 83 0
      config/db.js
  29. 403 0
      index_fenbao/faBuWenZhang/faBuWenZhang.vue
  30. 32 0
      index_fenbao/hongsewenhua/hongsewenhua.vue
  31. 147 0
      main.js
  32. 170 0
      manifest.json
  33. 14 0
      mixins/auth.js
  34. 87 0
      mixins/common.js
  35. 30 0
      node_modules/jweixin-module/README.md
  36. 1 0
      node_modules/jweixin-module/lib/index.js
  37. 26 0
      node_modules/jweixin-module/package.json
  38. 166 0
      pages.json
  39. 337 0
      pages/huicui/huicui.vue
  40. 378 0
      pages/index/index.vue
  41. 404 0
      pages/jiyi/jiyi.vue
  42. 475 0
      pages/user/index.vue
  43. 418 0
      pages/user/login.vue
  44. 172 0
      pages/zhenxing/zhenxing.vue
  45. 57 0
      service/api/common.js
  46. 105 0
      service/api/examine.js
  47. 98 0
      service/api/gift.js
  48. 57 0
      service/api/mock.js
  49. 13 0
      service/api/page.js
  50. 163 0
      service/api/user.js
  51. 7 0
      service/config.js
  52. 222 0
      service/request/main.js
  53. 156 0
      service/request/request.js
  54. BIN
      static/image/bj.png
  55. BIN
      static/image/hc.png
  56. BIN
      static/image/hc_a.png
  57. BIN
      static/image/home.png
  58. BIN
      static/image/home_a.png
  59. BIN
      static/image/hs.png
  60. BIN
      static/image/invite.png
  61. BIN
      static/image/jf.png
  62. BIN
      static/image/jp.png
  63. BIN
      static/image/jy.png
  64. BIN
      static/image/jy_a.png
  65. BIN
      static/image/mine.png
  66. BIN
      static/image/mine_a.png
  67. BIN
      static/image/tg.png
  68. BIN
      static/image/wh.png
  69. BIN
      static/image/zx.png
  70. BIN
      static/image/zy.png
  71. BIN
      static/image/余额.png
  72. BIN
      static/image/图层 1222.png
  73. BIN
      static/image/圆角矩形 840 拷贝.png
  74. BIN
      static/image/矩形 13.png
  75. BIN
      static/image/矩形 602.png
  76. BIN
      static/image/组 128.png
  77. BIN
      static/image/组 129.png
  78. BIN
      static/image/账期 拷贝.png
  79. BIN
      static/image/账期(1).png
  80. BIN
      static/image/账期(2).png
  81. BIN
      static/image/账期(3).png
  82. BIN
      static/image/账期.png
  83. BIN
      static/image/麦田.png
  84. 91 0
      store/index.js
  85. 14 0
      store/module/examine.js
  86. 26 0
      store/module/user.js
  87. 80 0
      uni.scss
  88. 17 0
      uni_modules/custom-waterfalls-flow/changelog.md
  89. 323 0
      uni_modules/custom-waterfalls-flow/components/custom-waterfalls-flow/custom-waterfalls-flow.vue
  90. 80 0
      uni_modules/custom-waterfalls-flow/package.json
  91. 445 0
      uni_modules/custom-waterfalls-flow/readme.md
  92. 185 0
      uni_modules/mp-html/README.md
  93. 95 0
      uni_modules/mp-html/changelog.md
  94. 462 0
      uni_modules/mp-html/components/mp-html/mp-html.vue
  95. 543 0
      uni_modules/mp-html/components/mp-html/node/node.vue
  96. 1280 0
      uni_modules/mp-html/components/mp-html/parser.js
  97. 79 0
      uni_modules/mp-html/package.json
  98. 1 0
      uni_modules/mp-html/static/app-plus/mp-html/js/handler.js
  99. 1 0
      uni_modules/mp-html/static/app-plus/mp-html/js/uni.webview.min.js
  100. 0 0
      uni_modules/mp-html/static/app-plus/mp-html/local.html

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+.DS_Store
+/dist
+/unpackage
+/.hbuilderx/

+ 82 - 0
App.vue

@@ -0,0 +1,82 @@
+<script>
+import md5Libs from '@/uni_modules/uview-ui/libs/function/md5';
+import Vue from 'vue';
+import { getConfig } from '@/service/api/common.js';
+export default {
+	created() {
+		// #ifdef APP-PLUS
+		plus.navigator.closeSplashscreen();
+		// #endif
+	},
+	onLaunch: async function () {
+		// console.log('App Launch');
+		// uni.hideTabBar();
+		//加载配置
+
+		uni.getSystemInfo({
+			success: function (e) {
+				// #ifndef MP
+				Vue.prototype.StatusBar = e.statusBarHeight;
+				if (e.platform == 'android') {
+					Vue.prototype.CustomBar = e.statusBarHeight + 50;
+				} else {
+					Vue.prototype.CustomBar = e.statusBarHeight + 45;
+				}
+				// #endif
+
+				// #ifdef MP-WEIXIN
+				Vue.prototype.StatusBar = e.statusBarHeight;
+				let custom = wx.getMenuButtonBoundingClientRect();
+				Vue.prototype.Custom = custom;
+				Vue.prototype.CustomBar = custom.bottom + custom.top - e.statusBarHeight;
+				// #endif
+
+				// #ifdef MP-ALIPAY
+				Vue.prototype.StatusBar = e.statusBarHeight;
+				Vue.prototype.CustomBar = e.statusBarHeight + e.titleBarHeight;
+				// #endif
+			}
+		});
+		getConfig().then(([err, res]) => {
+			// console.log('getConfig', err, res);
+			if (!err) {
+				this.$store.commit('setGlobalConfig', res);
+				// 当前活动无效
+				if (res.activity_invalid) {
+					this.$store.commit('delActivityId'); // 删除活动ID
+					// 重新加载配置
+					getConfig().then(([err, res]) => {
+						// console.log('getConfig reload', err, res);
+						if (!err) {
+							this.$store.commit('setGlobalConfig', res);
+						}
+					});
+				}
+			}
+		});
+
+		let res = await this.$api.getConfig();
+		//console.log(res);
+		if (!res.code) {
+			return;
+		}
+		Vue.prototype.vuex_config = res.data;
+		this.vuex_config = res.data;
+	},
+	onShow: function () {
+		console.log('App 开启');
+	},
+	onHide: function () {
+		console.log('App 关闭');
+	}
+	/* 隐藏默认tabBar */
+	// onLoad:function(){
+	// 	uni.hideTabBar();
+	// }
+};
+</script>
+
+<style lang="scss">
+@import 'colorui/main.css';
+@import 'colorui/icon.css';
+</style>

+ 535 - 0
README.md

@@ -0,0 +1,535 @@
+# maramlee-waterfalls-flow-nav 使用
+
+## 前言
+
+maramlee-waterfalls-flow-nav 是基于 maramlee-waterfalls-flow 为了解决很多小伙伴儿实际场景使用瀑布流需要使用 nav 切换数据而封装的插件。
+
+没错,使用这个插件,原来的 [maramlee-waterfalls-flow](https://ext.dcloud.net.cn/plugin?id=2714#rating) 插件也是可以继续用的。
+
+## 使用方式
+
+我的插件代码中有详细的说明,如果想要了解更多,请查看插件代码中的注释。
+
+### 首先在 `script` 中定义数据和方法等
+
+```javascript
+import waterfallsFlowNav from "@/components/maramlee-waterfalls-flow-nav/maramlee-waterfalls-flow-nav.vue";
+export default {
+  components: { waterfallsFlowNav },
+  data() {
+    return {
+      navIndex: 0, // 默认获取的第几项数据
+      navData: [
+        /**
+         * nav 对应的数据
+         * 注意插件里面下面两个 prop 值的设置:
+         *   |- list_key: { type: String, default: "key" },
+         *   |- list_label: { type: String, default: "label" },
+         */
+        { key: "one", label: "nav 1" },
+        { key: "two", label: "nav 2" },
+        { key: "three", label: "nav 3" },
+      ],
+    };
+  },
+  /**
+   * 上拉加载 这很重要
+   */
+  onReachBottom() {
+    /**
+     * 这里的 waterfalls_flow_nav 值记得与你定义的 ref 值对应
+     */
+    this.$refs.waterfalls_flow_nav.getList();
+  },
+
+  methods: {
+    /**
+     * 此方法为 视图层响应子组件 add-data 事件 的方法
+     */
+    getListHandle() {
+      /**
+       * mockData 为模拟数据
+       * 实际应为接口返回的数据
+       */
+      const mockData = {
+        total_page: 1,
+        list: [
+          {
+            id: 1,
+            image_url:
+              "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599475741266&di=e36d6c01c93320e2ba1504d8357248f4&imgtype=0&src=http%3A%2F%2Fa0.att.hudong.com%2F30%2F29%2F01300000201438121627296084016.jpg",
+            title: "可爱的小猫咪呀",
+            text:
+              "小小的猫咪,甚是呆萌,呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌",
+          },
+        ],
+      };
+      /**
+       * ================= !important =================
+       * 模拟请求成功
+       * 实际场景中是 request 请求哈
+       */
+      setTimeout(() => {
+        this.$refs.waterfalls_flow_nav.successSetData(
+          mockData.list,
+          mockData.total_page
+        );
+      }, 1000);
+      /**
+       * ================= !important =================
+       * 若失败,记得调用失败回调方法
+       * 此例如下:
+       *   this.$refs.waterfalls_flow_nav.failMoreBack();
+       */
+    },
+  },
+};
+```
+
+### 再在 `template` 中使用组件
+
+注意,下面的数据使用,都是基于上一条的数据定义,记得对照着看。
+
+#### 可以只是渲染图片,不需要其他
+
+```vue
+<template>
+  <waterfallsFlowNav
+    ref="waterfalls_flow_nav"
+    v-model="navIndex"
+    :navData="navData"
+    :single="true"
+    @add-data="getListHandle"
+  />
+</template>
+```
+
+#### 有插槽(自定义内容)的情况要分情况使用
+
+##### 若只适配 app、h5 端,利用作用域插槽
+
+注意:`item` 包含 `list` 对应的数据项,可以随意搭配、自定义使用。
+
+```vue
+<template>
+  <waterfallsFlowNav
+    ref="waterfalls_flow_nav"
+    v-model="navIndex"
+    :navData="navData"
+    :single="true"
+    @add-data="getListHandle"
+  >
+    <template v-slot:default="item">
+      <!-- 此处添加插槽内容 -->
+      <!-- <view class="cnt">
+          <view class="title">{{item.title}}</view>
+          <view class="text">{{item.text}}</view>
+        </view> -->
+    </template>
+  </waterfallsFlowNav>
+</template>
+```
+
+##### 若只适配微信小程序,利用小程序插槽
+
+注意:微信小程序没有动态模板,也和 vue 的插槽使用方式不一样。
+
+由于小程序的复杂性,又不想数据提到外层,我在插件里面定义了一个 `weixin_type` prop ,用于小程序不同模板控制。小程序使用时,需使用者自己去配置 maramlee-waterfalls-flow-nav 插件的模板内容。
+
+小程序使用步骤如下:
+
+1. 第一步:页面中 template 应用插件
+
+```vue
+<template>
+  <waterfallsFlowNav
+    ref="waterfalls_flow_nav"
+    v-model="navIndex"
+    :navData="navData"
+    @add-data="getListHandle"
+  />
+</template>
+```
+
+2. 第二步:修改 maramlee-waterfalls-flow-nav 插件图片下方模板
+
+**注意:这里修改的是插件 components/maramlee-waterfalls-flow-nav/maramlee-waterfalls-flow-nav.vue**
+
+如插件中 53 行注释的类似,script 标签中 116-126 行有详细注释,请自行查看。
+
+```vue
+<!--
+  取自 maramlee-waterfalls-flow-nav 组件中 36-56 行代码
+ -->
+<!--  #ifdef  MP-WEIXIN -->
+<template v-if="!single">
+  <view v-for="(item, index2) of obj.list" :key="index2" slot="slot{{index2}}">
+    <view v-if="weixin_type === 'default'" class="cnt">
+      <view class="title">{{ item.title }}</view>
+      <view class="text">{{ item.text }}</view>
+    </view>
+    <!-- 
+          ==========================================
+          weixin_type 为 "two" 时样例代码新增示例
+          用时记得取消注释
+          ==========================================
+         -->
+    <!-- <view v-else-if="weixin_type === 'two'">我是类型为 two 的样子哟</view> -->
+  </view>
+</template>
+<!--  #endif -->
+```
+
+```javascript
+// 取自 maramlee-waterfalls-flow-nav 组件 116-133 行代码
+/**
+ * =================================================
+ * 微信小程序特殊自定义不同类型下方内容
+ * 默认为 "default"
+ * 使用示例,例如新增一个 "two" 类型:
+ *   1.修改 weixin_type 的 validator:
+ *     |- validator: (value) => ["default","two"].indexOf(value) !== -1,
+ *
+ *   2. 上方代码块处新增类型为 two 的结构代码,详情请看上方代码处
+ * =================================================
+ */
+// #ifdef MP-WEIXIN
+weixin_type: {
+  type: String,
+  default: "default",
+  validator: (value) => ["default"].indexOf(value) !== -1,
+},
+// #endif
+```
+
+##### 若需要同时兼容 app、h5、微信小程序,则需条件渲染
+
+1. 首先使用 适配 app、h5 的方式模板。
+
+```vue
+<template>
+  <waterfallsFlowNav
+    ref="waterfalls_flow_nav"
+    v-model="navIndex"
+    :navData="navData"
+    :single="true"
+    @add-data="getListHandle"
+  >
+    <!-- #ifndef  MP-WEIXIN -->
+    <template v-slot:default="item">
+      <!-- 此处添加插槽内容 -->
+      <!-- <view class="cnt">
+          <view class="title">{{item.title}}</view>
+          <view class="text">{{item.text}}</view>
+        </view> -->
+    </template>
+    <!-- #endif -->
+  </waterfallsFlowNav>
+</template>
+```
+
+2. 再与上一条小程序适配一样的方式适配即可,这里不再赘述。
+
+## 属性说明
+
+| 属性名      | 类型    | 默认值    | 是否必传 | 说明                                                        | 平台支持         |
+| ----------- | ------- | --------- | -------- | ----------------------------------------------------------- | ---------------- |
+| navData     | Array   | -         | 是       | 渲染 nav 的列表,一般格式为:[{ key: "one", label: "nav" }] | 全               |
+| navIndex    | Number  | -         | 是       | nav 高亮的 index,一般页面中设置 0                          | 全               |
+| list_key    | String  | key       | 否       | 与 navData 项的 key 对应                                    | 全               |
+| list_label  | String  | label     | 否       | 与 navData 项的 label 对应                                  | 全               |
+| mountedGet  | Boolean | true      | 否       | 插件 mounted 时是否请求数据                                 | 全               |
+| offset      | Number  | 10        | 否       | 单位是 px                                                   | 全               |
+| idKey       | String  | id        | 否       | 列表渲染的 key 的键名,值必须唯一                           | 全               |
+| imageSrcKey | String  | image_url | 否       | 图片 src 的键名                                             | 全               |
+| cols        | Number  | 2         | 否       | 列数,值必须不小于 2                                        | 全               |
+| single      | Boolean | false     | 否       | 是否是单独的渲染图片,只控制图片圆角而已                    | 全               |
+| listStyle   | Object  | -         | 否       | 单个展示项的样式                                            | 微信小程序不支持 |
+| imageStyle  | Object  | -         | 否       | 图片的样式                                                  | 全               |
+
+## 事件说明
+
+| 事件名       | 说明                           | 返回值           |
+| ------------ | ------------------------------ | ---------------- |
+| @add-data    | 加载数据事件,很重要的一个事件 | 无               |
+| @nav-click   | nav 点击时触发                 | 点击项对应 index |
+| @wapper-lick | 单项点击事件                   | 单项对应数据     |
+| @image-click | 图片点击事件                   | 单项对应数据     |
+| @image-load  | 所有图片渲染完成触发           | -                |
+
+### add-data 事件详解
+
+此事件为解决不同的人封装的请求方法不同,不同接口请求的数据不同而产生。必需要配合很重要的两个组件方法:`successSetData`、`failMoreBack` 使用。
+
+```javascript
+getListHandle() {
+  const mockData = {
+    total_page: 1,
+    list: [
+      {
+        id: 1,
+        image_url:
+          "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599475741266&di=e36d6c01c93320e2ba1504d8357248f4&imgtype=0&src=http%3A%2F%2Fa0.att.hudong.com%2F30%2F29%2F01300000201438121627296084016.jpg",
+        title: "可爱的小猫咪呀",
+        text:
+          "小小的猫咪,甚是呆萌,呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌",
+      },
+    ],
+  };
+  /**
+   * ================= !important =================
+   * 模拟请求成功
+   * 实际场景中是 request 请求哈
+   */
+  setTimeout(() => {
+    this.$refs.waterfalls_flow_nav.successSetData(
+      mockData.list,
+      mockData.total_page
+    );
+  }, 1000);
+  /**
+   * ================= !important =================
+   * 若失败,记得调用失败回调方法
+   * 此例如下:
+   *   this.$refs.waterfalls_flow_nav.failMoreBack();
+   */
+},
+```
+
+## 组件方法
+
+| 方法名         | 说明                           | 参数                | 使用场景                            |
+| -------------- | ------------------------------ | ------------------- | ----------------------------------- |
+| getList        | 获取列表                       | isRefresh, navClick | 数据获取                            |
+| refresh        | 刷新对应的 moreNavIndex 数据   | -                   | 下拉刷新                            |
+| successSetData | 数据请求获取成功后设置组件数据 | list, total_page    | add-data 事件中,数据请求成功后调用 |
+| failMoreBack   | 数据请求获取失败后设置组件数据 | -                   | add-data 事件中,数据请求失败后调用 |
+| initNavLists   | 初始化插件内 moreNavLists 数据 | -                   | 使用情况比较少,需要时即调用        |
+
+### getList 方法说明
+
+获取列表方法
+
+`this.$refs.waterfalls_flow_nav.getList();`
+
+#### 参数
+
+1. isRefresh
+
+   默认 `false`
+
+   是否强制刷新,若为 `true`,即请求之前先把 `moreNavIndex` 对应的原来的数据清空,常用在下拉刷新
+
+2. navClick
+
+   默认 `false`
+
+   是否是 nav 点击,此值区分一般的加载和点击 nav 加载,由于 nav 点击,如果已经有数据,即无需加载数据
+
+#### 用法示例
+
+1. 页面中用于上拉加载更多(`onReachBottom`)中:`this.$refs.waterfalls_flow_nav.getList();`
+2. 页面中用于重新加载此项:`this.$refs.waterfalls_flow_nav.getList(true);`
+
+### successSetData 使用
+
+**注意查看 add-data 事件详解**
+
+请求数据成功后调用
+
+`this.$refs.waterfalls_flow_nav.successSetData(list, total_page);`
+
+#### 参数
+
+1. list
+
+   必传
+
+   请求返回需要渲染的 list 数据
+
+2. total_page
+
+   必传
+
+   请求返回的总页数
+
+### initNavLists 与 refresh 比较
+
+initNavLists 是初始化 `moreNavLists` 的数据,即所有的数据清空。
+
+refresh 只清空 `moreNavIndex` 对应即当前选择的 nav 项的数据。
+
+## 提示
+
+如果你看到了这里,还有以下情况:
+
+1. 插件使用方法有不懂的地方;
+2. 插件本身研究不明白的地方;
+3. 觉得插件有待提高的建议;
+4. 或者其他你遇到的问题;
+5. 纯粹想前端技术交流也行。
+
+可以加我微信,微信号:`ml-maramlee`,备注以`ml-${姓名}-${1}`的形式,其中 1、2、3、4 对应前面的情况(前端都看得懂哈),例如:`ml-maram-1`。
+
+**注:人家是有脾气的,不这样备注不给加哟~**
+
+**再注:觉得好用记得收藏、评论,这样可以让更多人看到,让更多人受益。当然,解答不易,欢迎赞赏。**
+
+**申明:主要是看到评论里有或多或少的问题,加微信有助于解决问题,只接受技术交流,其他请勿扰。**
+
+## 使用样例
+
+pages/nav/nav.vue
+
+```vue
+<template>
+  <view class="content">
+    <waterfallsFlowNav
+      ref="waterfalls_flow_nav"
+      v-model="navIndex"
+      :navData="navData"
+      @add-data="getListHandle"
+    >
+      <template v-slot:default="item">
+        <view class="cnt">
+          <view class="title">{{ item.title }}</view>
+          <view class="text">{{ item.text }}</view>
+        </view>
+      </template>
+    </waterfallsFlowNav>
+  </view>
+</template>
+<script>
+import waterfallsFlowNav from "@/components/maramlee-waterfalls-flow-nav/maramlee-waterfalls-flow-nav.vue";
+export default {
+  components: { waterfallsFlowNav },
+  data() {
+    return {
+      navIndex: 0,
+      navData: [
+        { key: "one", label: "nav 1" },
+        { key: "two", label: "nav 2" },
+        { key: "three", label: "nav 3" },
+      ],
+    };
+  },
+  /**
+   * 上拉加载
+   * 这很重要
+   */
+  onReachBottom() {
+    this.$refs.waterfalls_flow_nav.getList();
+  },
+  methods: {
+    getListHandle() {
+      const mockData = {
+        total_page: 1,
+        list: [
+          {
+            id: 1,
+            image_url:
+              "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599475741266&di=e36d6c01c93320e2ba1504d8357248f4&imgtype=0&src=http%3A%2F%2Fa0.att.hudong.com%2F30%2F29%2F01300000201438121627296084016.jpg",
+            title: "可爱的小猫咪呀",
+            text:
+              "小小的猫咪,甚是呆萌,呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌呆萌",
+          },
+          {
+            id: 2,
+            image_url:
+              "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599475934834&di=7a37b8d628252c4aced6ed0decba9442&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F43%2F74%2F01300000164151121808741085971.jpg",
+            title: "迪士尼动画",
+            text: "迪士尼动画之……",
+          },
+          {
+            id: 3,
+            image_url:
+              "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599476083909&di=a5debff35edec5de105bc105d6fdbce3&imgtype=0&src=http%3A%2F%2Fa2.att.hudong.com%2F77%2F77%2F01300000336597125202779973172.jpg",
+            title: "火箭",
+            text: "火箭升空瞬间,宏伟壮观啊",
+          },
+          {
+            id: 5,
+            image_url:
+              "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599476129760&di=7a3db0b14f6a74240bbfa7922ba22f45&imgtype=0&src=http%3A%2F%2Fa4.att.hudong.com%2F82%2F55%2F01300000349330124003555691086.jpg",
+            title: "华佗",
+            text: "华佗人物画像 中国画 线条画 毛笔画 彩色画",
+          },
+          {
+            id: 6,
+            image_url:
+              "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599476215687&di=97c2bbf6f3a1a3e2a6a2dc77dfe4bea7&imgtype=0&src=http%3A%2F%2Fa4.att.hudong.com%2F72%2F82%2F19300000009075130804824786610.jpg",
+            title: "恐龙",
+            text: "恐龙来啦",
+          },
+          {
+            id: 7,
+            image_url:
+              "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599476258176&di=29622b0f0cfd659aecebabaae352d02c&imgtype=0&src=http%3A%2F%2F1882.img.pp.sohu.com.cn%2Fimages%2Fblog%2F2011%2F3%2F25%2F13%2F13%2Fu48513077_12fa4ba953ag213.jpg",
+            title: "手",
+            text: "什么?",
+          },
+          {
+            id: 8,
+            image_url:
+              "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599476300222&di=49712f992d8f7bbb1a5851eced71cbe2&imgtype=0&src=http%3A%2F%2Fa2.att.hudong.com%2F71%2F56%2F16300000988660128426569668958.jpg",
+            title: "百年好合",
+            text: "百年好合 结婚 庚帖 二次元",
+          },
+          {
+            id: 9,
+            image_url:
+              "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599476416001&di=ea1a1f8f9b1274d39c05af3e48041e6a&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F12420002963%2F641",
+            title: "5G",
+            text: "5G 来啦",
+          },
+          {
+            id: 12,
+            image_url:
+              "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599476567983&di=040976a1cd1a6e5510a237c57bdcff06&imgtype=0&src=http%3A%2F%2Finews.gtimg.com%2Fnewsapp_bt%2F0%2F12421051168%2F641",
+            title: "王者荣耀",
+            text: "王者荣耀 龙 快来打龙 请求集合",
+          },
+        ],
+      };
+      /**
+       * ================= !important =================
+       * 模拟请求成功
+       * 实际场景中是 request 请求哈
+       */
+      setTimeout(() => {
+        this.$refs.waterfalls_flow_nav.successSetData(
+          mockData.list,
+          mockData.total_page
+        );
+      }, 1000);
+      /**
+       * ================= !important =================
+       * 若失败,记得调用失败回调方法
+       * 此例如下:
+       *   this.$refs.waterfalls_flow_nav.failMoreBack();
+       */
+    },
+  },
+};
+</script>
+<style>
+page {
+  background-color: #eee;
+}
+</style>
+<style lang="scss" scoped>
+.content {
+  padding: 10px;
+  .cnt {
+    padding: 10px;
+    .title {
+      font-size: 16px;
+    }
+    .text {
+      font-size: 14px;
+      margin-top: 5px;
+    }
+  }
+}
+</style>
+```

+ 184 - 0
colorui/animation.css

@@ -0,0 +1,184 @@
+/* 
+  Animation 微动画  
+  基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28
+ */
+
+/* css 滤镜 控制黑白底色gif的 */
+.gif-black{  
+  mix-blend-mode: screen;  
+}
+.gif-white{  
+  mix-blend-mode: multiply; 
+}
+
+
+/* Animation css */
+[class*=animation-] {
+    animation-duration: .5s;
+    animation-timing-function: ease-out;
+    animation-fill-mode: both
+}
+
+.animation-fade {
+    animation-name: fade;
+    animation-duration: .8s;
+    animation-timing-function: linear
+}
+
+.animation-scale-up {
+    animation-name: scale-up
+}
+
+.animation-scale-down {
+    animation-name: scale-down
+}
+
+.animation-slide-top {
+    animation-name: slide-top
+}
+
+.animation-slide-bottom {
+    animation-name: slide-bottom
+}
+
+.animation-slide-left {
+    animation-name: slide-left
+}
+
+.animation-slide-right {
+    animation-name: slide-right
+}
+
+.animation-shake {
+    animation-name: shake
+}
+
+.animation-reverse {
+    animation-direction: reverse
+}
+
+@keyframes fade {
+    0% {
+        opacity: 0
+    }
+
+    100% {
+        opacity: 1
+    }
+}
+
+@keyframes scale-up {
+    0% {
+        opacity: 0;
+        transform: scale(.2)
+    }
+
+    100% {
+        opacity: 1;
+        transform: scale(1)
+    }
+}
+
+@keyframes scale-down {
+    0% {
+        opacity: 0;
+        transform: scale(1.8)
+    }
+
+    100% {
+        opacity: 1;
+        transform: scale(1)
+    }
+}
+
+@keyframes slide-top {
+    0% {
+        opacity: 0;
+        transform: translateY(-100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateY(0)
+    }
+}
+
+@keyframes slide-bottom {
+    0% {
+        opacity: 0;
+        transform: translateY(100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateY(0)
+    }
+}
+
+@keyframes shake {
+
+    0%,
+    100% {
+        transform: translateX(0)
+    }
+
+    10% {
+        transform: translateX(-9px)
+    }
+
+    20% {
+        transform: translateX(8px)
+    }
+
+    30% {
+        transform: translateX(-7px)
+    }
+
+    40% {
+        transform: translateX(6px)
+    }
+
+    50% {
+        transform: translateX(-5px)
+    }
+
+    60% {
+        transform: translateX(4px)
+    }
+
+    70% {
+        transform: translateX(-3px)
+    }
+
+    80% {
+        transform: translateX(2px)
+    }
+
+    90% {
+        transform: translateX(-1px)
+    }
+}
+
+@keyframes slide-left {
+    0% {
+        opacity: 0;
+        transform: translateX(-100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateX(0)
+    }
+}
+
+@keyframes slide-right {
+    0% {
+        opacity: 0;
+        transform: translateX(100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateX(0)
+    }
+}

+ 76 - 0
colorui/components/cu-custom.vue

@@ -0,0 +1,76 @@
+<template>
+	<view>
+		<view class="cu-custom" :style="[{height:CustomBar + 'px'}]">
+			<view class="cu-bar fixed" :style="style" :class="[bgImage!=''?'none-bg text-white bg-img':'',bgColor]">
+				<view class="action" @tap="BackPage" v-if="isBack">
+					<text class="cuIcon-back"></text>
+					<slot name="backText"></slot>
+				</view>
+				<view class="content" :style="[{top:StatusBar + 'px'}]">
+					<slot name="content"></slot>
+				</view>
+				<slot name="right"></slot>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				StatusBar: this.StatusBar,
+				CustomBar: this.CustomBar
+			};
+		},
+		name: 'cu-custom',
+		computed: {
+			style() {
+				var StatusBar= this.StatusBar;
+				var CustomBar= this.CustomBar;
+				var bgImage = this.bgImage;
+				var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
+				if (this.bgImage) {
+					style = `${style}background-image:url(${bgImage});`;
+				}
+				return style
+			}
+		},
+		props: {
+			bgColor: {
+				type: String,
+				default: ''
+			},
+			isBack: {
+				type: [Boolean, String],
+				default: false
+			},
+			bgImage: {
+				type: String,
+				default: ''
+			},
+			// backIndex: {
+			// 	type: bool,
+			// 	default: false
+			// }
+		},
+		methods: {
+			BackPage() {
+				// if (this.backIndex) {
+				// 	uni.switchTab({
+				// 		url: 'pages/user/index'
+				// 	});
+				// }else {
+					uni.navigateBack({
+						delta: 1
+					});
+				// } 
+		
+			}
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 139 - 0
colorui/components/curry-slide.vue

@@ -0,0 +1,139 @@
+<template>
+  <view class="carousel-3d-slide" :style="slideStyle" :class="computedClasses" @click="goTo()">
+    <slot :index="index" :isCurrent="isCurrent" :leftIndex="leftIndex" :rightIndex="rightIndex"/>
+  </view>
+</template>
+
+<script>
+/* eslint-disable */
+export default {
+  name: 'curry-slide',
+  props: {
+    index: {
+      type: Number
+    }
+  },
+  data () {
+    return {
+      parent: this.$parent,
+      styles: {},
+      zIndex: 999
+    }
+  },
+  computed: {
+    isCurrent () {
+      return this.index === this.parent.currentIndex
+    },
+    leftIndex () {
+      return this.getSideIndex(this.parent.leftIndices)
+    },
+    rightIndex () {
+      return this.getSideIndex(this.parent.rightIndices)
+    },
+    slideStyle () {
+      let styles = {}
+      if (!this.isCurrent) {
+        const rIndex = this.leftIndex
+        const lIndex = this.rightIndex
+        if (rIndex >= 0 || lIndex >= 0) {
+          styles = rIndex >= 0 ? this.calculatePosition(rIndex, true, this.zIndex) : this.calculatePosition(lIndex, false, this.zIndex)
+          styles.opacity = 1
+          styles.visibility = 'visible'
+        }
+        if (this.parent.hasHiddenSlides) {
+          if (this.matchIndex(this.parent.leftOutIndex)) {
+            styles = this.calculatePosition(this.parent.leftIndices.length - 1, false, this.zIndex)
+          } else if (this.matchIndex(this.parent.rightOutIndex)) {
+            styles = this.calculatePosition(this.parent.rightIndices.length - 1, true, this.zIndex)
+          }
+        }
+      }
+      return Object.assign(styles, {
+        'border-width': this.parent.border + 'px',
+        'width': this.parent.slideWidth + 'px',
+        'height': this.parent.slideHeight + 'px',
+        'transition': ' transform ' + this.parent.animationSpeed + 'ms, ' +
+        '               opacity ' + this.parent.animationSpeed + 'ms, ' +
+        '               visibility ' + this.parent.animationSpeed + 'ms'
+      })
+    },
+    computedClasses () {
+      return {
+        [`left-${this.leftIndex + 1}`]: this.leftIndex >= 0,
+        [`right-${this.rightIndex + 1}`]: this.rightIndex >= 0,
+        'current': this.isCurrent
+      }
+    }
+  },
+  methods: {
+    getSideIndex (array) {
+      let index = -1
+      array.forEach((pos, i) => {
+        if (this.matchIndex(pos)) {
+          index = i
+        }
+      })
+      return index
+    },
+    matchIndex (index) {
+      return (index >= 0) ? this.index === index : (this.parent.total + index) === this.index
+    },
+    calculatePosition (i, positive, zIndex) {
+      const z = !this.parent.disable3d ? parseInt(this.parent.inverseScaling) + ((i + 1) * 100) : 0
+      const y = !this.parent.disable3d ? parseInt(this.parent.perspective) : 0
+      const leftRemain = (this.parent.space === 'auto')
+        ? parseInt((i + 1) * (this.parent.width / 1.2), 10) // 1.5
+        : parseInt((i + 1) * (this.parent.space), 10)
+      const transform = (positive)
+        ? 'translateX(' + (leftRemain) + 'px) translateZ(-' + z + 'px) ' +
+        'rotateY(-' + y + 'deg)'
+        : 'translateX(-' + (leftRemain) + 'px) translateZ(-' + z + 'px) ' +
+        'rotateY(' + y + 'deg)'
+      const top = this.parent.space === 'auto' ? 0 : parseInt((i + 1) * (this.parent.space))
+      return {
+        transform: transform,
+        top: top,
+        zIndex: zIndex - (Math.abs(i) + 1)
+      }
+    },
+    goTo () {
+      if (!this.isCurrent) {
+        if (this.parent.clickable === true) {
+          this.parent.goFar(this.index)
+        }
+      } else {
+        this.parent.onMainSlideClick()
+      }
+    }
+  }
+}
+</script>
+
+<style>
+  .carousel-3d-slide {
+    position: absolute;
+    opacity: 0;
+    visibility: hidden;
+    overflow: hidden;
+    top: 0;
+    border-color: #023c41;
+    border-style: solid;
+    background-size: cover;
+    background-color: #ccc;
+    display: block;
+    margin: 0;
+    box-sizing: border-box;
+  }
+  .carousel-3d-slide {
+    text-align: left;
+  }
+  .carousel-3d-slide img {
+    width: 100%;
+  }
+  .carousel-3d-slide.current {
+    opacity: 1 !important;
+    visibility: visible !important;
+    transform: none !important;
+    z-index: 999;
+  }
+</style>

+ 410 - 0
colorui/components/curry-swiper.vue

@@ -0,0 +1,410 @@
+<template>
+  <view class="carousel-3d-container" :style="{height: this.slideHeight + 'px'}">
+    <view class="carousel-3d-slider" :style="{width: this.slideWidth + 'px', height: this.slideHeight + 'px'}">
+      <slot></slot>
+    </view>
+  </view>
+</template>
+
+<script>
+/* eslint-disable */
+  const noop = () => {
+  }
+  export default {
+    name: 'curry-swiper',
+    props: {
+      // Number of slides
+      count: {
+        type: [Number, String],
+        default: 0
+      },
+      // Slides perspective position
+      perspective: {
+        type: [Number, String],
+        default: 35
+      },
+      // Number of slides displayed on each page
+      display: {
+        type: [Number, String],
+        default: 3
+      },
+      loop: {
+        type: Boolean,
+        default: true
+      },
+      // Animation between slides in milliseconds
+      animationSpeed: {
+        type: [Number, String],
+        default: 500
+      },
+      // Animation direction
+      dir: {
+        type: String,
+        default: 'ltr'
+      },
+      width: {
+        type: [Number, String],
+        default: 360
+      },
+      height: {
+        type: [Number, String],
+        default: 270
+      },
+      border: {
+        type: [Number, String],
+        default: 1
+      },
+      // Space between slides in pixels
+      space: {
+        type: [Number, String],
+        default: 'auto'
+      },
+      // Start slide index. First slide has 0 index
+      startIndex: {
+        type: [Number, String],
+        default: 0
+      },
+      // Enable navigation by clicking on slide
+      clickable: {
+        type: Boolean,
+        default: true
+      },
+      disable3d: {
+        type: Boolean,
+        default: false
+      },
+      // Minimum distance in pixels to swipe before a slide advance is triggered
+      minSwipeDistance: {
+        type: Number,
+        default: 10
+      },
+      // Slide inverse scaling
+      inverseScaling: {
+        type: [Number, String],
+        default: 300
+      },
+      onLastSlide: {
+        type: Function,
+        default: noop
+      },
+      onSlideChange: {
+        type: Function,
+        default: noop
+      },
+      bias: {
+        type: String,
+        default: 'left'
+      },
+      onMainSlideClick: {
+        type: Function,
+        default: noop
+      }
+    },
+    data () {
+      return {
+        viewport: 0,
+        currentIndex: 0,
+        total: 0,
+        dragOffset: 0,
+        dragStartX: 0,
+        mousedown: false,
+        zIndex: 998
+      }
+    },
+    watch: {
+      count () {
+        this.computeData()
+      }
+    },
+    computed: {
+      isLastSlide () {
+        return this.currentIndex === this.total - 1
+      },
+      isFirstSlide () {
+        return this.currentIndex === 0
+      },
+      isNextPossible () {
+        return !(!this.loop && this.isLastSlide)
+      },
+      isPrevPossible () {
+        return !(!this.loop && this.isFirstSlide)
+      },
+      slideWidth () {
+        const vw = this.viewport
+        const sw = parseInt(this.width) + (parseInt(this.border, 10) * 2)
+        return vw < sw ? vw : sw
+      },
+      slideHeight () {
+        const sw = parseInt(this.width, 10) + (parseInt(this.border, 10) * 2)
+        const sh = parseInt(parseInt(this.height) + (this.border * 2), 10)
+        const ar = this.calculateAspectRatio(sw, sh)
+        return this.slideWidth / ar
+      },
+      visible () {
+        const v = (this.display > this.total) ? this.total : this.display
+        return v
+      },
+      hasHiddenSlides () {
+        return this.total > this.visible
+      },
+      leftIndices () {
+        let n = (this.visible - 1) / 2
+        n = (this.bias.toLowerCase() === 'left' ? Math.ceil(n) : Math.floor(n))
+        const indices = []
+        for (let m = 1; m <= n; m++) {
+          indices.push((this.dir === 'ltr')
+            ? (this.currentIndex + m) % (this.total)
+            : (this.currentIndex - m) % (this.total))
+        }
+        return indices
+      },
+      rightIndices () {
+        let n = (this.visible - 1) / 2
+        n = (this.bias.toLowerCase() === 'right' ? Math.ceil(n) : Math.floor(n))
+        const indices = []
+        for (let m = 1; m <= n; m++) {
+          indices.push((this.dir === 'ltr')
+            ? (this.currentIndex - m) % (this.total)
+            : (this.currentIndex + m) % (this.total))
+        }
+        return indices
+      },
+      leftOutIndex () {
+        let n = (this.visible - 1) / 2
+        n = (this.bias.toLowerCase() === 'left' ? Math.ceil(n) : Math.floor(n))
+        n++
+        if (this.dir === 'ltr') {
+          return ((this.total - this.currentIndex - n) <= 0)
+            ? (-parseInt(this.total - this.currentIndex - n))
+            : (this.currentIndex + n)
+        } else {
+          return (this.currentIndex - n)
+        }
+      },
+      rightOutIndex () {
+        let n = (this.visible - 1) / 2
+        n = (this.bias.toLowerCase() === 'right' ? Math.ceil(n) : Math.floor(n))
+        n++
+        if (this.dir === 'ltr') {
+          return (this.currentIndex - n)
+        } else {
+          return ((this.total - this.currentIndex - n) <= 0)
+            ? (-parseInt(this.total - this.currentIndex - n, 10))
+            : (this.currentIndex + n)
+        }
+      }
+    },
+    methods: {
+      /**
+       * Go to next slide
+       */
+      goNext () {
+        if (this.isNextPossible) {
+          this.isLastSlide ? this.goSlide(0) : this.goSlide(this.currentIndex + 1)
+        }
+      },
+      /**
+       * Go to previous slide
+       */
+      goPrev () {
+        if (this.isPrevPossible) {
+          this.isFirstSlide ? this.goSlide(this.total - 1) : this.goSlide(this.currentIndex - 1)
+        }
+      },
+      /**
+       * Go to slide
+       * @param  {String} index of slide where to go
+       */
+      goSlide (index) {
+        this.currentIndex = (index < 0 || index > this.total - 1) ? 0 : index
+        if (this.isLastSlide) {
+          if (this.onLastSlide !== noop) {
+            console.warn('onLastSlide deprecated, please use @last-slide')
+          }
+          this.onLastSlide(this.currentIndex)
+          this.$emit('last-slide', this.currentIndex)
+        }
+        this.$emit('before-slide-change', this.currentIndex)
+        setTimeout(() => this.animationEnd(), this.animationSpeed)
+      },
+      /**
+       * Go to slide far slide
+       */
+      goFar (index) {
+        let diff = (index === this.total - 1 && this.isFirstSlide) ? -1 : (index - this.currentIndex)
+        if (this.isLastSlide && index === 0) {
+          diff = 1
+        }
+        const diff2 = (diff < 0) ? -diff : diff
+        let timeBuff = 0
+        let i = 0
+        while (i < diff2) {
+          i += 1
+          const timeout = (diff2 === 1) ? 0 : (timeBuff)
+          setTimeout(() => (diff < 0) ? this.goPrev(diff2) : this.goNext(diff2), timeout)
+          timeBuff += (this.animationSpeed / (diff2))
+        }
+      },
+      /**
+       * Trigger actions when animation ends
+       */
+      animationEnd () {
+        if (this.onSlideChange !== noop) {
+          console.warn('onSlideChange deprecated, please use @after-slide-change')
+        }
+        this.onSlideChange(this.currentIndex)
+        this.$emit('after-slide-change', this.currentIndex)
+      },
+      /**
+       * Trigger actions when mouse is released
+       * @param  {Object} e The event object
+       */
+      handleMouseup () {
+        this.mousedown = false
+        this.dragOffset = 0
+      },
+      /**
+       * Trigger actions when mouse is pressed
+       * @param  {Object} e The event object
+       */
+      handleMousedown (e) {
+        if (!e.touches) {
+          e.preventDefault()
+        }
+        this.mousedown = true
+        this.dragStartX = ('ontouchstart' in window) ? e.touches[0].clientX : e.clientX
+      },
+      /**
+       * Trigger actions when mouse is pressed and then moved (mouse drag)
+       * @param  {Object} e The event object
+       */
+      handleMousemove (e) {
+          if (!this.mousedown) {
+            return
+          }
+          const eventPosX = ('ontouchstart' in window) ? e.touches[0].clientX : e.clientX
+          const deltaX = (this.dragStartX - eventPosX)
+          this.dragOffset = deltaX
+          if (this.dragOffset > this.minSwipeDistance) {
+            this.handleMouseup()
+            this.goNext()
+          } else if (this.dragOffset < -this.minSwipeDistance) {
+            this.handleMouseup()
+            this.goPrev()
+          }
+      },
+      /**
+       * A mutation observer is used to detect changes to the containing node
+       * in order to keep the magnet container in sync with the height its reference node.
+       */
+      attachMutationObserver () {
+        const MutationObserver = window.MutationObserver ||
+          window.WebKitMutationObserver ||
+          window.MozMutationObserver
+        if (MutationObserver) {
+          const config = {
+            attributes: true,
+            childList: true,
+            characterData: true
+          }
+          this.mutationObserver = new MutationObserver(() => {
+            this.$nextTick(() => {
+              this.computeData()
+            })
+          })
+          if (this.$el) {
+            this.mutationObserver.observe(this.$el, config)
+          }
+        }
+      },
+      /**
+       * Stop listening to mutation changes
+       */
+      detachMutationObserver () {
+        if (this.mutationObserver) {
+          this.mutationObserver.disconnect()
+        }
+      },
+      /**
+       * Get the number of slides
+       * @return {Number} Number of slides
+       */
+      getSlideCount () {
+        if (this.$slots.default !== undefined) {
+          return this.$slots.default.filter((value) => {
+            return value.tag !== void 0
+          }).length
+        }
+        return 0
+      },
+      /**
+       * Calculate slide with and keep defined aspect ratio
+       * @return {Number} Aspect ratio number
+       */
+      calculateAspectRatio (width, height) {
+        return Math.min(width / height)
+      },
+      /**
+       * Re-compute the number of slides and current slide
+       */
+      computeData (firstRun) {
+        this.total = this.getSlideCount()
+        if (firstRun || this.currentIndex >= this.total) {
+          this.currentIndex = parseInt(this.startIndex) > this.total - 1 ? this.total - 1 : parseInt(this.startIndex)
+        }
+        this.viewport = this.$el.clientWidth
+      },
+      setSize () {
+        this.$el.style.cssText += 'height:' + this.slideHeight + 'px;'
+        this.$el.childNodes[0].style.cssText += 'width:' + this.slideWidth + 'px;' + ' height:' + this.slideHeight + 'px;'
+      }
+    },
+    mounted () {
+      this.computeData(true)
+      this.attachMutationObserver()
+      if (!this.$isServer) {
+        window.addEventListener('resize', this.setSize)
+        if ('ontouchstart' in window) {
+          this.$el.addEventListener('touchstart', this.handleMousedown)
+          this.$el.addEventListener('touchend', this.handleMouseup)
+          this.$el.addEventListener('touchmove', this.handleMousemove)
+        } else {
+          this.$el.addEventListener('mousedown', this.handleMousedown)
+          this.$el.addEventListener('mouseup', this.handleMouseup)
+          this.$el.addEventListener('mousemove', this.handleMousemove)
+        }
+      }
+    },
+    beforeDestroy () {
+      if (!this.$isServer) {
+        this.detachMutationObserver()
+        if ('ontouchstart' in window) {
+          this.$el.removeEventListener('touchmove', this.handleMousemove)
+        } else {
+          this.$el.removeEventListener('mousemove', this.handleMousemove)
+        }
+        window.removeEventListener('resize', this.setSize)
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .carousel-3d-container {
+    min-height: 1px;
+    width: 100%;
+    position: relative;
+    z-index: 0;
+    overflow: hidden;
+    margin: 0px auto;
+    box-sizing: border-box;
+  }
+  .carousel-3d-slider {
+    position: relative;
+    margin: 0 auto;
+    transform-style: preserve-3d;
+    -webkit-perspective: 1000px;
+    -moz-perspective: 1000px;
+    perspective: 1000px;
+  }
+</style>

File diff suppressed because it is too large
+ 1226 - 0
colorui/icon.css


File diff suppressed because it is too large
+ 4036 - 0
colorui/main.css


+ 35 - 0
common/WXBizDataCrypt.js

@@ -0,0 +1,35 @@
+var crypto = require('crypto')
+
+function WXBizDataCrypt(appId, sessionKey) {
+  this.appId = appId
+  this.sessionKey = sessionKey
+}
+
+WXBizDataCrypt.prototype.decryptData = function (encryptedData, iv) {
+  // base64 decode
+  var sessionKey = new Buffer(this.sessionKey, 'base64')
+  encryptedData = new Buffer(encryptedData, 'base64')
+  iv = new Buffer(iv, 'base64')
+
+  try {
+     // 解密
+    var decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv)
+    // 设置自动 padding 为 true,删除填充补位
+    decipher.setAutoPadding(true)
+    var decoded = decipher.update(encryptedData, 'binary', 'utf8')
+    decoded += decipher.final('utf8')
+    
+    decoded = JSON.parse(decoded)
+
+  } catch (err) {
+    throw new Error('Illegal Buffer')
+  }
+
+  if (decoded.watermark.appid !== this.appId) {
+    throw new Error('Illegal Buffer')
+  }
+
+  return decoded
+}
+
+module.exports = WXBizDataCrypt

+ 0 - 0
common/app.css


+ 75 - 0
common/cache.js

@@ -0,0 +1,75 @@
+// 缓存前缀 
+var postfix = 'yuncache';
+
+/**
+ * 设置缓存 
+ * 
+ * @param  {[type]} k [键名]
+ * @param  {[type]} v [键值]
+ * @param  {[type]} t [时间、单位秒]
+ */
+function put(k, v, t) {
+	uni.setStorageSync(k, v)
+	var seconds = parseInt(t);
+	if (seconds > 0) {
+		var timestamp = Date.parse(new Date());
+		timestamp = timestamp / 1000 + seconds;
+		uni.setStorageSync(k + postfix, timestamp + "")
+	} else {
+		uni.removeStorageSync(k + postfix)
+	}
+}
+
+/**
+ * 获取缓存 
+ * 
+ * @param  {[type]} k   [键名]
+ * @param  {[type]} def [获取为空时默认]
+ */
+function get(k, def) {
+	var deadtime = parseInt(uni.getStorageSync(k + postfix))
+	if (deadtime) {
+		if (parseInt(deadtime) < Date.parse(new Date()) / 1000) {
+			if (def) {
+				return def;
+			} else {
+				return false;
+			}
+		}
+	}
+	var res = uni.getStorageSync(k);
+	if (res) {
+		return res;
+	} else {
+		if (def == undefined || def == "") {
+			def = false;
+		}
+		return def;
+	}
+}
+
+/**
+ * 清理指定缓存
+ * 
+ * @return {[type]} [description]
+ */
+function remove(k) {
+	uni.removeStorageSync(k);
+	uni.removeStorageSync(k + postfix);
+}
+
+/**
+ * 清理所有缓存
+ * 
+ * @return {[type]} [description]
+ */
+function clear() {
+	uni.clearStorageSync();
+}
+
+export default {
+	put,
+	get,
+	remove,
+	clear,
+}

File diff suppressed because it is too large
+ 4 - 0
common/city.area.js


+ 187 - 0
common/common.css

@@ -0,0 +1,187 @@
+.flex_layout{display: flex; align-items: center; flex-wrap: wrap;}
+.flex_between{ display: flex; align-items: center; flex-wrap: wrap; justify-content: space-between;}
+.nodata{padding: 50rpx 30rpx;text-align: center;color: #999;font-size: 24rpx;text-align: center;background: #fff;}
+.submit_btn{padding: 30upx 85upx; margin-top: 20upx;}
+.submit_btn button{width: 100%; height: 80upx; line-height: 80upx; border-radius: 80upx; background: rgba(36,255,255,0.2); font-size: 30upx; color: #fff;}
+a{text-decoration: none;}
+.noresult{ color: #fff; text-align: center; font-size: 24upx;}
+
+/*弹窗样式*/
+    .Appointment_failed{position: fixed; left: 60upx; right: 60upx; top: 50%; transform: translateY(-50%); background: #fff; border-radius: 10px; overflow: hidden; z-index: 100;}
+    .notice_title{color: #fff; font-size: 30upx; text-align: center; padding: 30upx; background: #4a85b2;}
+    .notice_content{padding: 30upx;}
+    .notice_item{padding: 15upx 0;}
+    .notice_item view{color: #333; font-size: 30upx;}
+    .notice_item view{color: #333; font-size: 24upx;}
+    .notice_btn{padding-bottom: 30upx;}
+    .notice_btn button{width: 450upx; height: 75upx; line-height: 75upx; border-radius: 75upx; background: #4a85b2; color: #fff; display: block; margin: 0 auto;}
+  .ornament{position: relative;}
+    .ornament>image{width: 100%; height: 257upx; display: block;}
+    .ornament_pos{position: absolute; width: 114upx; height: 114upx; left: 50%; top: 50%; transform: translate(-50%,-50%);}
+    .ornament_pos image{width: 114upx; height: 114upx; display: block;}
+    .ornament_delete{position: absolute; padding: 20upx; top: 0upx; right: 0upx; width: 70upx; height: 70upx;}
+    .ornament_delete i{display: block; font-size: 30upx;}
+    .ornament_delete image{width: 30upx; height: 30upx; display: block;}
+    .ornament_title{color: #4A85B2; font-size: 36upx; text-align: center; margin-top: 60upx;}
+    .ornament_text{color: #999; font-size: 30upx; text-align: center; margin-top: 30upx; margin-bottom: 60upx; padding: 0 30upx;}
+/*弹窗样式end*/
+    .pull-up {
+        width: 100%;
+        margin-bottom: 20upx;
+        text-align: center;
+        height: 80upx;
+        color: #c1c1c1;
+    }
+    .rect-text {
+        height: 100%;
+        line-height: 80upx;
+    }
+    .rect-wrap {
+        margin: 0 auto;
+        width: 100upx;
+        height: 60upx;
+        text-align: center;
+        font-size: 8upx;
+        margin-top: 8upx;
+    }
+    .rect-wrap .rect {
+        background-color: #7ad237;
+    height: 100%;
+    width: 6upx;
+    display: inline-block;
+    -webkit-animation: stretchdelay 1.2s infinite ease-in-out;
+    animation: stretchdelay 1.2s infinite ease-in-out;
+    }
+    .rect-wrap .rect {
+        margin: 4upx;
+    }
+    .rect-wrap .rect2 {
+    -webkit-animation-delay: -1.1s;
+    animation-delay: -1.1s;
+  }
+  .rect-wrap .rect3 {
+    -webkit-animation-delay: -1.0s;
+    animation-delay: -1.0s;
+  }
+  .rect-wrap .rect4 {
+    -webkit-animation-delay: -0.9s;
+    animation-delay: -0.9s;
+  }
+  .rect-wrap .rect5 {
+    -webkit-animation-delay: -0.8s;
+    animation-delay: -0.8s;
+  }
+    @keyframes stretchdelay {
+        0%, 40%, 100% {
+            transform: scaleY(0.4);
+            -webkit-transform: scaleY(0.4);
+        }
+        20% {
+            transform: scaleY(1.0);
+            -webkit-transform: scaleY(1.0);
+        }
+    }
+    @-webkit-keyframes stretchdelay {
+        0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
+        20% { -webkit-transform: scaleY(1.0) }
+     }
+/* #ifdef H5 */
+    .positionFixe {
+        padding-top: 90upx;
+    }
+    /* #endif */
+    
+    /* #ifdef MP-WEIXIN */
+    .positionFixe {
+        padding-top: 130upx;
+    }
+    /* #endif */
+    .positionFixe{position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: -1; box-sizing: border-box; background: #4a85b2;}
+    .positionFixe image{display: block; width: 100%; height: 1108upx;}
+
+/*iconfont文件*/
+@font-face {
+  font-family: 'iconfont';  /* project id 1340048 */
+  src: url('//at.alicdn.com/t/font_1340048_2mrk0i19u8d.eot');
+  src: url('//at.alicdn.com/t/font_1340048_2mrk0i19u8d.eot?#iefix') format('embedded-opentype'),
+  url('//at.alicdn.com/t/font_1340048_2mrk0i19u8d.woff2') format('woff2'),
+  url('//at.alicdn.com/t/font_1340048_2mrk0i19u8d.woff') format('woff'),
+  url('//at.alicdn.com/t/font_1340048_2mrk0i19u8d.ttf') format('truetype'),
+  url('//at.alicdn.com/t/font_1340048_2mrk0i19u8d.svg#iconfont') format('svg');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;   
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-jinru:before {
+  content: "\e615";
+}
+
+.icon-zhuangtai:before {
+  content: "\e634";
+}
+
+.icon-icon:before {
+  content: "\e630";
+}
+
+.icon-right:before {
+  content: "\e63b";
+}
+
+.icon-hide:before {
+  content: "\e600";
+}
+
+.icon-address:before {
+  content: "\e62a";
+}
+
+.icon-dianhua:before {
+  content: "\e61b";
+}
+
+.icon-dianhua1:before {
+  content: "\e601";
+}
+
+.icon-biao:before {
+  content: "\e6d5";
+}
+
+.icon-rili:before {
+  content: "\e642";
+}
+
+.icon-add:before {
+  content: "\e6ab";
+}
+
+.icon-liaotian:before {
+  content: "\e610";
+}
+
+.icon-fabu:before {
+  content: "\e60d";
+}
+
+.icon-aiqingmiao:before {
+  content: "\e602";
+}
+
+.icon-shezhi:before {
+  content: "\e72c";
+}
+
+.icon-date:before {
+  content: "\e607";
+}
+
+.icon-zxt:before {
+  content: "\e627";
+}

File diff suppressed because it is too large
+ 146 - 0
common/common.scss


+ 102 - 0
common/dateTimePicker.js

@@ -0,0 +1,102 @@
+// JavaScript Document
+/**
+ * 自定义多列时间选择器
+ */
+function withData(param) {
+  return param < 10 ? '0' + param : '' + param;
+}
+function getLoopArray(start, end) {
+  var start = start || 0;
+  var end = end || 1;
+  var array = [];
+  for (var i = start; i <= end; i++) {
+    array.push(withData(i));
+  }
+  return array;
+}
+function getMonthDay(year, month) {
+  var flag = year % 400 == 0 || (year % 4 == 0 && year % 100 != 0), array = null;
+ 
+  switch (month) {
+    case '01':
+    case '03':
+    case '05':
+    case '07':
+    case '08':
+    case '10':
+    case '12':
+      array = getLoopArray(1, 31)
+      break;
+    case '04':
+    case '06':
+    case '09':
+    case '11':
+      array = getLoopArray(1, 30)
+      break;
+    case '02':
+      array = flag ? getLoopArray(1, 29) : getLoopArray(1, 28)
+      break;
+    default:
+      array = '月份格式不正确,请重新输入!'
+  }
+  return array;
+}
+function getNewDateArry() {
+  // 当前时间的处理
+  var newDate = new Date();
+  var year = withData(newDate.getFullYear()),
+    mont = withData(newDate.getMonth() + 1),
+    date = withData(newDate.getDate()),
+    hour = withData(newDate.getHours()),
+    minu = withData(newDate.getMinutes()),
+    seco = withData(newDate.getSeconds());
+ 
+  return [year, mont, date, hour, minu, seco];
+}
+function dateTimePicker(startYear, endYear, date) {
+  // 返回默认显示的数组和联动数组的声明
+  var dateTime = [], dateTimeArray = [[], [], [], []];
+  var start = startYear || 2020;
+  var end = endYear || 2100;
+  // 默认开始显示数据
+  var defaultDate = date ? [...date.split(' ')[0].split('-'), ...date.split(' ')[1].split(':')] : getNewDateArry();
+	console.log(defaultDate);
+  //console.log("defaultDate: ",defaultDate);
+  // 处理联动列表数据
+  /*年月日 时分秒*/
+  dateTimeArray[0] = getLoopArray(start, end);
+  dateTimeArray[1] = getLoopArray(1, 12);
+  dateTimeArray[2] = getMonthDay(defaultDate[0], defaultDate[1]);
+  //dateTimeArray[3] = getLoopArray(0, 23);
+	dateTimeArray[3] =['9时','11时','14时','16时']
+  //dateTimeArray[4] = getLoopArray(0, 59);
+  //dateTimeArray[5] = getLoopArray(0, 59);
+ // console.log(dateTimeArray[0]);
+ // console.log(dateTimeArray[1]);
+ // console.log(dateTimeArray[2]);
+ 
+  dateTimeArray.forEach((current, index) => {
+    dateTime.push(current.indexOf(defaultDate[index]));
+  });
+  //处理数据加上年月日时的单位
+  for(var i=0; i<dateTimeArray[0].length;i++){
+  	dateTimeArray[0][i]= dateTimeArray[0][i]+'年';
+  }
+  for(var j=0; j<dateTimeArray[1].length;j++){
+  	dateTimeArray[1][j]= dateTimeArray[1][j]+'月';
+  }
+  for(var k=0; k<dateTimeArray[2].length;k++){
+  	dateTimeArray[2][k]= dateTimeArray[2][k]+'日';
+  }
+  //
+  dateTime[3]=0
+  //console.log(dateTime);
+  return {
+    dateTimeArray: dateTimeArray,
+    dateTime: dateTime
+  }
+}
+module.exports = {
+  dateTimePicker: dateTimePicker,
+  getMonthDay: getMonthDay
+}

+ 493 - 0
common/fa.mixin.js

@@ -0,0 +1,493 @@
+export const tools = {
+	filters: {
+
+	},
+	computed: {
+
+	},
+	methods: {
+		//富文本的回调
+		navigate(e) {
+			if (e.href && e.href.indexOf('http') == -1) { //不完整的链接						
+				//详情				
+				let res = e.href.match(new RegExp("(a)|(\\d+)", 'g'));
+				if (res.length == 2) {
+					this.$u.route('/pages/article/detail', {
+						id: res[1]
+					});
+					return;
+				}
+				// #ifdef MP
+				this.$util.uniCopy({
+					content: this.vuex_config.upload.cdnurl + e.href,
+					success: () => {
+						this.$u.toast('链接已复制,请在浏览器中打开')
+					}
+				})
+				// #endif
+				// #ifndef MP				
+				window.open(this.vuex_config.upload.cdnurl + e.href);
+				// #endif
+			}
+		},
+		//预览图片
+		lookImage(index) {
+			uni.previewImage({
+				current: index,
+				urls: this.imagesList,
+				longPressActions: {
+					itemList: ['发送给朋友', '保存图片', '收藏'],
+					success: function(data) {
+						console.log(data)
+					},
+					fail: function(err) {
+						console.log(err.errMsg);
+					}
+				}
+			});
+		},
+		//复制url
+		copyUrl() {
+			this.$util.uniCopy({
+				content: window.location.href,
+				success: () => {
+					this.$u.toast('复制成功,请去粘贴发送给好友吧');
+				},
+				error: () => {
+					console.log('复制失败!')
+				}
+			})
+		},
+		//cdnurl
+		cdnurl(url) {
+			if (!/^((?:[a-z]+:)?\/\/|data:image\/)(.*)/.test(url)) {
+				return this.vuex_config.upload.cdnurl + url;
+			};
+			return url;
+		},
+		//页面跳转
+		goPage(path, auth) {
+			if (path == 'out') {
+				this.$u.vuex('vuex_token', '');
+				this.$u.vuex('vuex_user', {});
+				return;
+			}
+			if (auth && !this.vuex_token) {
+				this.$u.route('/pages/login/mobilelogin');
+				return;
+			}
+			let type = 'navigateTo';
+			
+			// #ifdef MP-WEIXIN
+			//优化当pages超过5个时的处理
+			let pages = getCurrentPages();
+			type = pages.length >= 4 ? 'redirectTo' : type;
+			// #endif
+			
+			uni.$u.route({
+				url: path,
+				type: type,
+				complete(e) {
+					console.log(e, path)
+				}
+			})
+		}
+	}
+}
+//点赞
+export const vote = {
+	methods: {
+		likes: async function() {
+			const value = uni.getStorageSync(`${this.id}_${this.vuex_user.id}`);
+			if (value == this.id) {
+				this.$u.toast('您已经点过赞了')
+				return;
+			}
+			let res = await this.$api.getArchivesVote({
+				id: this.id,
+				type: 'like'
+			})
+			this.$u.toast(res.msg);
+			if (!res.code) {
+				return;
+			};
+			//先在前端限制
+			uni.setStorageSync(`${this.id}_${this.vuex_user.id}`, this.id);
+			this.$set(this.archivesInfo, 'likes', res.data.likes)
+		},
+		collection(id, type) {
+			this.$api.addCollection({
+				aid: id,
+				type: type
+			}).then(res => {
+				this.$u.toast(res.msg)
+			})
+		}
+	}
+}
+//修改头像的事件
+export const avatar = {
+	methods: {
+		chooseAvatar() {
+			uni.$on('uAvatarCropper', this.upload);
+			this.$u.route({
+				// 关于此路径,请见下方"注意事项"
+				url: '/uview-ui/components/u-avatar-cropper/u-avatar-cropper',
+				// 内部已设置以下默认参数值,可不传这些参数
+				params: {
+					// 输出图片宽度,高等于宽,单位px
+					destWidth: 300,
+					// 裁剪框宽度,高等于宽,单位px
+					rectWidth: 300,
+					// 输出的图片类型,如果'png'类型发现裁剪的图片太大,改成"jpg"即可
+					fileType: 'jpg'
+				}
+			});
+		},
+		upload: async function(path) {
+			uni.$off('uAvatarCropper', this.upload);
+			// 可以在此上传到服务端
+			try {
+				let res = await this.$api.goUpload({
+					filePath: path
+				});
+				if (!res.code) {
+					this.$u.toast(res.msg);
+				};
+				this.form.avatar = res.data.url;
+				this.url = res.data.fullurl;
+				if (typeof this.editAvatar == 'function') {
+					this.editAvatar();
+				}
+			} catch (e) {
+				console.error(e);
+				this.$u.toast('图片上传失败!');
+			}
+		}
+	}
+}
+
+//form
+export const formRule = {
+	methods: {
+		//表单验证
+		getRules(row) {
+			let arr = row.rule.split(';');
+			let rule_arr = [];
+
+			arr.forEach(item => {
+				item = this.$u.trim(item);
+				switch (item) {
+					case 'required':
+					case 'checked':
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								if (typeof value == 'string') {
+									value = value.replace(/<[^>]+>/g, "").replace(/\s/ig, "");
+								}
+								return !(this.$u.test.empty(value));
+							},
+							message: row.title + '不能为空',
+							// 可以单个或者同时写两个触发验证方式
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'digits': //数字校验
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return this.$u.test.digits(value);
+							},
+							message: '请填写数字',
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'letters': //字母校验
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return this.$u.test.letter(value);
+							},
+							message: '请填写字母',
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'date': //日期校验
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return this.$u.test.date(value);
+							},
+							message: '请填写正确日期格式',
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'time': //时间校验
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return /^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/.test(value);
+							},
+							message: '请填写正确时间格式',
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'email': //邮箱校验
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return this.$u.test.email(value);
+							},
+							message: '请填写正确邮箱',
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'url': //网址
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return this.$u.test.url(value);
+							},
+							message: '请填写正确网址',
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'qq': //qq
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return /^[1-9][0-9]{4,10}$/.test(value);
+							},
+							message: '请填写正确QQ号码',
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'IDcard': //身份证
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return this.$u.test.idCard(value);
+							},
+							message: '请填写正确身份证件号',
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'tel': //电话
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return /^\d{3}-\d{8}$|^\d{4}-\d{7,8}$/.test(value);
+							},
+							message: '请填写正确电话号码',
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'mobile': //手机
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return this.$u.test.mobile(value);
+							},
+							message: '请填写正确手机号码',
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'zipcode': //邮编
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return /^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\d{4}$/
+									.test(value);
+							},
+							message: '请填写正确邮编',
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'chinese': //中文
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return /^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])+$/
+									.test(
+										value
+									);
+							},
+							message: '请填写中文',
+							trigger: ['change', 'blur']
+						});
+						break;
+					case 'username': //用户名
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								return /^[a-zA-Z0-9_]{3,12}$/.test(value);
+							},
+							message: '请填写3-12位数字、字母、下划线',
+							trigger: ['change', 'blur']
+						});
+
+						break;
+					case 'password': //密码
+						rule_arr.push({
+							validator: (rule, value, callback) => {
+								let val = this.$u.trim(value, 'all');
+								if (val != value) {
+									return false;
+								}
+								return /^[0-9a-zA-Z!@#$%^&*?]{6,16}$/.test(value);
+							},
+							message: '请填写6-16位字符,不能包含空格',
+							trigger: ['change', 'blur']
+						});
+						break;
+				}
+			});
+
+
+			//多选额外的判断
+			if (row.type == 'checkbox') {
+				//最少
+				if (row.minimum > 0) {
+					rule_arr.push({
+						validator: (rule, value, callback) => {
+							if (!value) {
+								return false;
+							}
+							let arr = value.split(',')
+							return arr.length >= row.minimum;
+						},
+						message: '最少必须选择' + row.minimum + '项',
+						// 可以单个或者同时写两个触发验证方式
+						trigger: ['change', 'blur']
+					});
+				}
+			}
+			if (['checkbox', 'selects', 'images', 'files'].indexOf(row.type) != -1) {
+				//最多
+				if (row.maximum > 0) {
+					rule_arr.push({
+						validator: (rule, value, callback) => {
+							if (!value) {
+								return false;
+							}
+							let arr = value.split(',')
+							return arr.length <= row.maximum;
+						},
+						message: '最多只能选择' + row.maximum + '项',
+						// 可以单个或者同时写两个触发验证方式
+						trigger: ['change', 'blur']
+					});
+				}
+			}
+
+			return rule_arr;
+		}
+	}
+}
+
+//登录成功跳转
+export const loginfunc = {
+	methods: {
+		//登录成功
+		success(index = 1) {
+			//不在H5
+			// #ifndef H5
+			uni.$u.route({
+				type: 'back',
+				delta: index
+			})
+			// #endif
+			// 在H5 刷新导致路由丢失
+			// #ifdef H5
+			var pages = getCurrentPages();
+			//有上次页面,关闭所有页面,到此页面,是从授权的,授权页面被刷新过,路由栈丢失
+			if (pages.length <= 1 || pages[0].route == 'pages/login/auth') {
+				//默认到首页
+				uni.reLaunch({
+					url: (this.vuex_lasturl || '/pages/index/index'),
+					complete(res) {
+						console.log(res)
+					}
+				})
+				return;
+			}
+			uni.$u.route({
+				type: 'back',
+				delta: index
+			})
+			// #endif
+		},
+		// #ifdef H5
+		async goAuth() {
+			if (this.$util.isWeiXinBrowser()) {
+				let url = '';
+				if (window.location.hash != '') {
+					url = window.location.origin + window.location.pathname + '?hashpath=/pages/login/auth'
+				} else {
+					url = window.location.origin + window.location.pathname.replace(/pages.*/,
+						'pages/login/auth');
+				};
+				let res = await this.$api.getAuthUrl({
+					platform: 'wechat',
+					url: url
+				});
+				if (!res.code) {
+					this.$u.toast(res.msg);
+					return;
+				}
+				var pages = getCurrentPages();
+				let len = pages.length;
+				if (len > 1) {
+					let url = pages[len - 1].route;
+					if (url.includes('login')) {
+						//找到上一个不是登录页面
+						for (let i = len - 1; i >= 0; i--) {
+							if (!pages[i].route.includes('login')) {
+								this.$u.vuex('vuex_lasturl', '/' + pages[i].route + this.$u.queryParams(pages[i]
+									.options));
+								break;
+							}
+						}
+					} else {
+						this.$u.vuex('vuex_lasturl', '/' + url + this.$u.queryParams(pages[pages.length - 1]
+							.options))
+					}
+				}
+				window.location.href = res.data;
+			}
+		},
+		// #endif
+		// #ifdef APP-PLUS
+		goAppLogin(index = 1) {
+			let that = this;
+			var all, Service;
+			// 1.发送请求获取code
+			plus.oauth.getServices(
+				function(Services) {
+					all = Services;
+					Object.keys(all).some(key => {
+						if (all[key].id == 'weixin') {
+							Service = all[key];
+						}
+					});
+					Service.authorize(
+						async function(e) {
+								console.log(e);
+								let res = await that.$api.goAppLogin({
+									code: e.code,
+									scope: e.scope
+								});
+								if (!res.code) {
+									that.$u.toast(res.msg);
+									return;
+								}
+								if (res.data.user) {
+									that.$u.vuex('vuex_token', res.data.user.token);
+									that.$u.vuex('vuex_user', res.data.user || {});
+									that.success(index);
+									return;
+								}
+								that.$u.vuex('vuex_third', res.data.third);
+								that.$u.route('/pages/login/register?bind=bind');
+							},
+							function(e) {
+								that.$u.toast('授权失败!');
+							}
+					);
+				},
+				function(err) {
+					console.log(err);
+					that.$u.toast('授权失败!');
+				}
+			);
+		}
+		// #endif
+	}
+}

+ 40 - 0
common/fa.style.mixin.js

@@ -0,0 +1,40 @@
+module.exports = {
+	computed: {
+		cmsTitleStyle() {
+			return val => {
+				let style = {};
+				if (val && val.includes('b')) {
+					style.fontWeight = 'bold';
+				}
+				if (val && val.includes('#')) {
+					style.color = val.replace('b', '').replace('|', '');
+				}
+				return style;
+			}
+		},
+		theme() {
+			if (this.vuex_theme.value) {
+				return this.vuex_theme.value;
+			}
+			return {};
+		},
+		lightColor() {
+			let color = '#f5f5f5';
+			if (this.vuex_theme.value) {
+				let theme = this.vuex_theme.value;
+				let colorArr = this.$u.colorGradient(theme.bgColor, theme.color, 10);
+				color = colorArr[9] || '#f5f5f5';
+			}
+			return color;
+		},
+		faBorderColor() {
+			let color = '#f5f5f5';
+			if (this.vuex_theme.value) {
+				let theme = this.vuex_theme.value;
+				let colorArr = this.$u.colorGradient(theme.bgColor, theme.color, 10);
+				color = colorArr[5] || '#f5f5f5';
+			}
+			return color;
+		},
+	},
+}

+ 63 - 0
common/fa.weixin.mixin.js

@@ -0,0 +1,63 @@
+//微信网页分享
+var jweixin = require('jweixin-module')  
+export const weixinShare = {
+	methods: {
+		//初始化sdk配置  
+		initJssdk: function(callback) {
+			let url = '';
+			if(window.location.hash != ''){
+			  url = window.location.origin+window.location.pathname;
+			}else{
+			  url = window.location.href;
+			}
+			this.$api.getSigned({url:url}).then(res=>{
+				if (res.code) {
+					jweixin.config({
+						debug: false,
+						appId: res.data.appId,
+						timestamp: res.data.timestamp,
+						nonceStr: res.data.nonceStr,
+						signature: res.data.signature,
+						jsApiList: [
+							'checkJsApi',
+							'updateAppMessageShareData',
+							'updateTimelineShareData',
+							'onMenuShareWeibo'
+						]
+					});
+					//配置完成后,再执行分享等功能  
+					if (typeof callback == 'function') {
+						callback();
+					}
+				}else{
+					this.$u.toast(res.msg)
+				}
+			})
+		},
+		//在需要自定义分享的页面中调用  
+		wxShare: function(data) {		
+			//每次都需要重新初始化配置,才可以进行分享  
+			this.initJssdk(function() {
+				jweixin.ready(function() {
+					var shareData = {
+						title: data && data.title ? data.title : '分享标题',
+						desc: data && data.desc ? data.desc : '分享内容',
+						link: data && data.url?data.url:window.location.origin,
+						imgUrl: data && data.img ? data.img : '',
+						success: function(res) {
+							
+						},
+						cancel: function(res) {}
+					};				
+					//自定义“分享给朋友”及“分享到QQ”按钮的分享内容;
+					jweixin.updateAppMessageShareData(shareData)
+					//自定义“分享到朋友圈”及“分享到QQ空间”按钮的分享内容;
+					jweixin.updateTimelineShareData(shareData)
+					//分享到腾讯微博
+					jweixin.onMenuShareWeibo(shareData);
+				
+				});
+			});
+		}
+	}
+}

+ 352 - 0
common/html-parser.js

@@ -0,0 +1,352 @@
+/*
+ * HTML5 Parser By Sam Blowes
+ *
+ * Designed for HTML5 documents
+ *
+ * Original code by John Resig (ejohn.org)
+ * http://ejohn.org/blog/pure-javascript-html-parser/
+ * Original code by Erik Arvidsson, Mozilla Public License
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
+ *
+ * ----------------------------------------------------------------------------
+ * License
+ * ----------------------------------------------------------------------------
+ *
+ * This code is triple licensed using Apache Software License 2.0,
+ * Mozilla Public License or GNU Public License
+ *
+ * ////////////////////////////////////////////////////////////////////////////
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.  You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ////////////////////////////////////////////////////////////////////////////
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * The Original Code is Simple HTML Parser.
+ *
+ * The Initial Developer of the Original Code is Erik Arvidsson.
+ * Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
+ * Reserved.
+ *
+ * ////////////////////////////////////////////////////////////////////////////
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ * ----------------------------------------------------------------------------
+ * Usage
+ * ----------------------------------------------------------------------------
+ *
+ * // Use like so:
+ * HTMLParser(htmlString, {
+ *     start: function(tag, attrs, unary) {},
+ *     end: function(tag) {},
+ *     chars: function(text) {},
+ *     comment: function(text) {}
+ * });
+ *
+ * // or to get an XML string:
+ * HTMLtoXML(htmlString);
+ *
+ * // or to get an XML DOM Document
+ * HTMLtoDOM(htmlString);
+ *
+ * // or to inject into an existing document/DOM node
+ * HTMLtoDOM(htmlString, document);
+ * HTMLtoDOM(htmlString, document.body);
+ *
+ */
+// Regular Expressions for parsing tags and attributes
+var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
+var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
+var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
+
+var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
+// fixed by xxx 将 ins 标签从块级名单中移除
+
+var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
+
+var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
+// (and which close themselves)
+
+var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
+
+var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
+
+var special = makeMap('script,style');
+function HTMLParser(html, handler) {
+  var index;
+  var chars;
+  var match;
+  var stack = [];
+  var last = html;
+
+  stack.last = function () {
+    return this[this.length - 1];
+  };
+
+  while (html) {
+    chars = true; // Make sure we're not in a script or style element
+
+    if (!stack.last() || !special[stack.last()]) {
+      // Comment
+      if (html.indexOf('<!--') == 0) {
+        index = html.indexOf('-->');
+
+        if (index >= 0) {
+          if (handler.comment) {
+            handler.comment(html.substring(4, index));
+          }
+
+          html = html.substring(index + 3);
+          chars = false;
+        } // end tag
+
+      } else if (html.indexOf('</') == 0) {
+        match = html.match(endTag);
+
+        if (match) {
+          html = html.substring(match[0].length);
+          match[0].replace(endTag, parseEndTag);
+          chars = false;
+        } // start tag
+
+      } else if (html.indexOf('<') == 0) {
+        match = html.match(startTag);
+
+        if (match) {
+          html = html.substring(match[0].length);
+          match[0].replace(startTag, parseStartTag);
+          chars = false;
+        }
+      }
+
+      if (chars) {
+        index = html.indexOf('<');
+        var text = index < 0 ? html : html.substring(0, index);
+        html = index < 0 ? '' : html.substring(index);
+
+        if (handler.chars) {
+          handler.chars(text);
+        }
+      }
+    } else {
+      html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
+        text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
+
+        if (handler.chars) {
+          handler.chars(text);
+        }
+
+        return '';
+      });
+      parseEndTag('', stack.last());
+    }
+
+    if (html == last) {
+      throw 'Parse Error: ' + html;
+    }
+
+    last = html;
+  } // Clean up any remaining tags
+
+
+  parseEndTag();
+
+  function parseStartTag(tag, tagName, rest, unary) {
+    tagName = tagName.toLowerCase();
+
+    if (block[tagName]) {
+      while (stack.last() && inline[stack.last()]) {
+        parseEndTag('', stack.last());
+      }
+    }
+
+    if (closeSelf[tagName] && stack.last() == tagName) {
+      parseEndTag('', tagName);
+    }
+
+    unary = empty[tagName] || !!unary;
+
+    if (!unary) {
+      stack.push(tagName);
+    }
+
+    if (handler.start) {
+      var attrs = [];
+      rest.replace(attr, function (match, name) {
+        var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
+        attrs.push({
+          name: name,
+          value: value,
+          escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
+
+        });
+      });
+
+      if (handler.start) {
+        handler.start(tagName, attrs, unary);
+      }
+    }
+  }
+
+  function parseEndTag(tag, tagName) {
+    // If no tag name is provided, clean shop
+    if (!tagName) {
+      var pos = 0;
+    } // Find the closest opened tag of the same type
+    else {
+        for (var pos = stack.length - 1; pos >= 0; pos--) {
+          if (stack[pos] == tagName) {
+            break;
+          }
+        }
+      }
+
+    if (pos >= 0) {
+      // Close all the open elements, up the stack
+      for (var i = stack.length - 1; i >= pos; i--) {
+        if (handler.end) {
+          handler.end(stack[i]);
+        }
+      } // Remove the open elements from the stack
+
+
+      stack.length = pos;
+    }
+  }
+}
+
+function makeMap(str) {
+  var obj = {};
+  var items = str.split(',');
+
+  for (var i = 0; i < items.length; i++) {
+    obj[items[i]] = true;
+  }
+
+  return obj;
+}
+
+function removeDOCTYPE(html) {
+  return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
+}
+
+function parseAttrs(attrs) {
+  return attrs.reduce(function (pre, attr) {
+    var value = attr.value;
+    var name = attr.name;
+
+    if (pre[name]) {
+			pre[name] = pre[name] + " " + value;
+    } else {
+			pre[name] = value;
+    }
+
+    return pre;
+  }, {});
+}
+
+function parseHtml(html) {
+  html = removeDOCTYPE(html);
+  var stacks = [];
+  var results = {
+    node: 'root',
+    children: []
+  };
+  HTMLParser(html, {
+    start: function start(tag, attrs, unary) {
+      var node = {
+        name: tag
+      };
+
+      if (attrs.length !== 0) {
+        node.attrs = parseAttrs(attrs);
+      }
+
+      if (unary) {
+        var parent = stacks[0] || results;
+
+        if (!parent.children) {
+          parent.children = [];
+        }
+
+        parent.children.push(node);
+      } else {
+        stacks.unshift(node);
+      }
+    },
+    end: function end(tag) {
+      var node = stacks.shift();
+      if (node.name !== tag) console.error('invalid state: mismatch end tag');
+
+      if (stacks.length === 0) {
+        results.children.push(node);
+      } else {
+        var parent = stacks[0];
+
+        if (!parent.children) {
+          parent.children = [];
+        }
+
+        parent.children.push(node);
+      }
+    },
+    chars: function chars(text) {
+      var node = {
+        type: 'text',
+        text: text
+      };
+
+      if (stacks.length === 0) {
+        results.children.push(node);
+      } else {
+        var parent = stacks[0];
+
+        if (!parent.children) {
+          parent.children = [];
+        }
+
+        parent.children.push(node);
+      }
+    },
+    comment: function comment(text) {
+      var node = {
+        node: 'comment',
+        text: text
+      };
+      var parent = stacks[0];
+
+      if (!parent.children) {
+        parent.children = [];
+      }
+
+      parent.children.push(node);
+    }
+  });
+  return results.children;
+}
+
+export default parseHtml;

+ 157 - 0
common/iconfont.css

@@ -0,0 +1,157 @@
+@font-face {
+  font-family: "iconfont"; /* Project id  */
+  src: url('@/static/iconfont.ttf') format('truetype');
+}
+
+@font-face {
+  font-family: "iconfont"; /* Project id  */
+  src: url('@/static/iconfont2.ttf') format('truetype');
+}
+
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+.icon-shipinjiankong:before {
+  content: "\e60e";
+}
+
+.icon-shipinbofang:before {
+  content: "\e602";
+}
+
+.icon-shexiangtou:before {
+  content: "\e657";
+}
+
+.icon-shipinjiankong1:before {
+  content: "\e859";
+}
+
+.icon-shipinjiankong2:before {
+  content: "\e61b";
+}
+.icon-shijian:before {
+  content: "\e62a";
+}
+.icon-didian01:before {
+  content: "\e61f";
+}
+.icon-haoyou:before {
+  content: "\e600";
+}
+
+.icon-baoming:before {
+  content: "\e624";
+}
+
+.icon-kehufuwukefu:before {
+  content: "\e698";
+}
+
+.icon-baomingshuju:before {
+  content: "\e6a1";
+}
+
+.icon-PK:before {
+  content: "\e661";
+}
+
+.icon-daohang:before {
+  content: "\eaf2";
+}
+
+.icon-Basket-Ball:before {
+  content: "\eb6f";
+}
+
+.icon-renlian:before {
+  content: "\e690";
+}
+
+.icon-Rrl_s_141:before {
+  content: "\e614";
+}
+
+.icon-running:before {
+  content: "\e60c";
+}
+
+.icon-changguanyuyue:before {
+  content: "\e601";
+}
+
+.icon-xiangfatianjia:before {
+  content: "\e639";
+}
+
+.icon-shouye:before {
+  content: "\e60d";
+}
+
+.icon-shouye1:before {
+  content: "\e74b";
+}
+
+.icon-jianshenjiaolian:before {
+  content: "\e615";
+}
+
+.icon-jianshen3:before {
+  content: "\e669";
+}
+
+.icon-ic_mianxing_jiaxiaobaoming_1:before {
+  content: "\e613";
+}
+
+.icon-icon_xinyong_xianxing_jijin-161:before {
+  content: "\e64d";
+}
+
+.icon-zuduiqingqiu:before {
+  content: "\e619";
+}
+
+.icon-baoming1:before {
+  content: "\e642";
+}
+
+.icon-tuandui:before {
+  content: "\e682";
+}
+
+.icon-tuandui_xianxing:before {
+  content: "\e69c";
+}
+
+.icon-womentuandui:before {
+  content: "\e617";
+}
+
+.icon-zcpt-peixunbaomingguanli:before {
+  content: "\e916";
+}
+
+.icon-zuixing-55:before {
+  content: "\e63c";
+}
+
+.icon-tuandui1:before {
+  content: "\f4fe";
+}
+
+.icon-zudui:before {
+  content: "\f50f";
+}
+.icon-menjindukaqi:before {
+  content: "\e671";
+}
+
+.icon-person:before {
+  content: "\e61a";
+}

+ 72 - 0
common/share.js

@@ -0,0 +1,72 @@
+export default {
+	data() {
+		return {
+			//设置默认的分享参数
+			//如果页面不设置share,就触发这个默认的分享
+			share: {
+				title: '闽南魂',
+				path: '',
+				imageUrl: 'https://feicheng.16fw.cn:1443/uploads/20230616/3babf074aed0f79b958aa4f11948f860.jpg',
+				desc: '',
+				content: ''
+			}
+		}
+	},
+	onShareAppMessage(res) {
+		console.log(this.share.path);
+		if (this.share.path == '') {
+			var pages = getCurrentPages() // 获取栈实例
+			let currentRoute = pages[pages.length - 1].route; // 获取当前页面路由			
+			let currentPage = pages[pages.length - 1]['$page']['fullPath'] //当前页面路径(带参数)
+			this.share.path = currentPage;
+		}
+		let user = this.$common.userInfo();
+		if (user) {
+			if (this.share.path.indexOf('from_userid') < 0) {
+				if (this.share.path.indexOf('?') > 0) {
+					this.share.path = this.share.path + '&from_userid=' + user.id
+				} else {
+					this.share.path = this.share.path + '?from_userid=' + user.id
+				}
+
+			}
+		}
+
+		return {
+			title: this.share.title,
+			path: this.share.path,
+			imageUrl: this.share.imageUrl,
+			desc: this.share.desc,
+			content: this.share.content,
+			success(res) {
+				uni.showToast({
+					title: '分享成功'
+				})
+			},
+			fail(res) {
+				uni.showToast({
+					title: '分享失败',
+					icon: 'none'
+				})
+			}
+		}
+	},
+	onShareTimeline(res) {
+		return {
+			title: this.share.title,
+			path: this.share.path,
+			imageUrl: this.share.imageUrl,
+			success(res) {
+				uni.showToast({
+					title: '分享成功'
+				})
+			},
+			fail(res) {
+				uni.showToast({
+					title: '分享失败',
+					icon: 'none'
+				})
+			}
+		}
+	}
+}

File diff suppressed because it is too large
+ 1448 - 0
common/uni.css


+ 465 - 0
common/util.js

@@ -0,0 +1,465 @@
+const extend = function extend(target) {
+	var sources = Array.prototype.slice.call(arguments, 1)
+
+	for (var i = 0; i < sources.length; i += 1) {
+		var source = sources[i]
+		for (var key in source) {
+			if (source.hasOwnProperty(key)) {
+				target[key] = source[key]
+			}
+		}
+	}
+
+	return target
+}
+
+/**
+ * 获取当前时间戳
+ */
+const getTimestamp = (date) => {
+	date = date || new Date()
+	return Math.round(date / 1000)
+}
+
+/**
+ * 保留指定小数位
+ */
+const toDecimal = (float, num) => {
+	if (typeof float !== 'number') float = parseFloat(float)
+	return parseFloat(float.toFixed(num))
+}
+
+/**
+ * 时间戳转 yyyy-MM-dd hh:mm
+ */
+export function formatDate(timestamp, format) {
+	const date = timestamp ? new Date(timestamp * 1000) : new Date();
+	const data = {
+		'M+': date.getMonth() + 1,
+		'd+': date.getDate(),
+		'h+': date.getHours(),
+		'm+': date.getMinutes(),
+		's+': date.getSeconds(),
+		'q+': Math.floor((date.getMonth() + 3) / 3),
+		'S+': date.getMilliseconds()
+	}
+	if (/(y+)/i.test(format)) {
+		format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
+	}
+	for (var key in data) {
+		if (new RegExp('(' + key + ')').test(format)) {
+			format = format.replace(RegExp.$1, RegExp.$1.length == 1 ?
+				data[key] : ('00' + data[key]).substr(('' + data[key]).length))
+		}
+	}
+
+	return format
+}
+
+/**
+ * 分钟、小时、天前的处理
+ */
+const timeAgo = function(timestamp, format) {
+	const minute = 1000 * 60 // 把分,时,天,周,半个月,一个月用毫秒表示
+	const hour = minute * 60
+	const day = hour * 24
+	const now = new Date().getTime() // 获取当前时间毫秒
+	const spreadTime = now - timestamp * 1000 // 时间差
+	if (spreadTime < 0) {
+		return
+	}
+
+
+	const minC = spreadTime / minute
+	const hourC = spreadTime / hour
+	const dayC = spreadTime / day
+	let result
+
+	if (spreadTime >= 0 && spreadTime <= minute) {
+		result = '刚刚'
+	} else if (minC >= 1 && minC <= 59) {
+		result = parseInt(minC) + '分钟前'
+	} else if (hourC >= 1 && hourC <= 23) {
+		result = parseInt(hourC) + '小时前'
+	} else if (dayC >= 1 && dayC < 3) {
+		result = parseInt(dayC) + '天前'
+	} else if (dayC >= 3 && dayC <= 7) {
+		result = '3天前'
+	} else {
+		result = formatDate(timestamp, format)
+	}
+
+	return result
+}
+
+/**
+ * param 将要转为URL参数字符串的对象
+ * key URL参数字符串的前缀
+ * encode true/false 是否进行URL编码,默认为true
+ *
+ * return URL参数字符串
+ */
+const urlEncode = (param, key, encode) => {
+	if (param == null) return ''
+	const t = typeof(param)
+	let paramStr = ''
+	if (t === 'string' || t === 'number' || t === 'boolean') {
+		paramStr += '&' + key + '=' + ((encode == null || encode) ? encodeURIComponent(param) : param)
+	} else {
+		for (let i in param) {
+			let k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '[' + i + ']')
+			paramStr += urlEncode(param[i], k, encode)
+		}
+	}
+
+	return paramStr
+}
+
+/**
+ * return
+ */
+const urlDecode = (query) => {
+	if (!query) return null
+	let hash, object = {}
+	const hashes = query.slice(query.indexOf('?') + 1).split('&')
+	for (let i = 0; i < hashes.length; i++) {
+		hash = hashes[i].split('=')
+		object[hash[0]] = hash[1]
+	}
+
+	return object;
+}
+
+/**
+ * 对象合并
+ */
+const deepAssign = (origin, source) => {
+	for (let key in source) {
+		if (source.hasOwnProperty(key)) {
+			if (source[key] instanceof Object) {
+				deepAssign(origin[key], source[key])
+			} else {
+				origin[key] = source[key]
+			}
+		}
+	}
+}
+
+/**
+ * 状态数据
+ */
+const display = {
+	toast: false, // 无用
+	modal: false,
+	loading: false,
+}
+
+// 显示消息提示
+const showToast = function(text, icon) {
+	wx.showToast({
+		title: text || '加载中',
+		icon: icon || 'none',
+		mask: true,
+		duration: 2000
+	})
+}
+
+// 隐藏消息提示
+const hideToast = function() {
+	wx.hideToast()
+}
+
+// 显示 loading
+const showLoading = function(text) {
+	if (!display.loading) {
+		display.loading = true
+		wx.showLoading({
+			title: text || '加载中',
+			mask: true
+		})
+	}
+}
+
+// 隐藏 loading
+const hideLoading = function() {
+	if (display.loading) {
+		display.loading = false
+		wx.hideLoading()
+	}
+}
+
+// 显示失败提示
+const showModal = (title, content) => {
+	if (display.toast) hideToast()
+	if (display.loading) hideLoading()
+
+	if (!display.modal) {
+		title = title || '信息提示'
+		content = content || '未知错误' // content 为 null 时会报错
+		display.modal = true
+		wx.showModal({
+			title,
+			content: typeof content === 'object' ? JSON.stringify(content) : content,
+			showCancel: false,
+			confirmColor: '#3B99FC',
+			complete: function(e) {
+				display.modal = false
+			}
+		})
+	}
+}
+
+export function buildPageUrl(page, options) {
+	let pageUrl = page;
+	if (options) {
+		let params = urlEncode(options)
+		pageUrl += '?' + params.substring(1, params.length);
+	}
+
+	return pageUrl;
+}
+
+export function encodeRedirectUrl(page, options) {
+	return encodeURIComponent(buildPageUrl(page, options));
+}
+
+export function decodeRedirectUrl(url) {
+	return decodeURIComponent(url);
+}
+
+/**
+ * 处理富文本里的图片宽度自适应
+ * 1.去掉img标签里的style、width、height属性
+ * 2.img标签添加style属性:max-width:100%;height:auto
+ * 3.修改所有style里的width属性为max-width:100%
+ * 4.去掉<br/>标签
+ * 
+ * @param html
+ * @returns {void|string|*}
+ */
+export function formatRichText(html){
+  let newContent= html.replace(/<img[^>]*>/gi,function(match,capture){
+      match = match.replace(/style="[^"]+"/gi, '').replace(/style='[^']+'/gi, '');
+      match = match.replace(/width="[^"]+"/gi, '').replace(/width='[^']+'/gi, '');
+      match = match.replace(/height="[^"]+"/gi, '').replace(/height='[^']+'/gi, '');
+      return match;
+  });
+  newContent = newContent.replace(/style="[^"]+"/gi,function(match,capture){
+      match = match.replace(/width:[^;]+;/gi, 'max-width:100%;').replace(/width:[^;]+;/gi, 'max-width:100%;');
+      return match;
+  });
+  newContent = newContent.replace(/<br[^>]*\/>/gi, '');
+  newContent = newContent.replace(/\<img/gi, '<img style="max-width:100%;height:auto;display:block;margin:10px 0;"');
+  return newContent;
+}
+
+function strlen(value) {
+	//中文、中文标点、全角字符按1长度,英文、英文符号、数字按0.5长度计算
+	let cnReg = /([\u4e00-\u9fa5]|[\u3000-\u303F]|[\uFF00-\uFF60])/g;
+	let mat = value.match(cnReg);
+	let length = 0;
+	if (mat) {
+		return (length = mat.length + (value.length - mat.length) * 0.5);
+	} else {
+		return (length = value.length * 0.5);
+	}
+}
+
+/**
+ *
+ *  判断是否在微信浏览器 true是
+ */
+function isWeiXinBrowser() {
+	// #ifdef H5
+	let ua = window.navigator.userAgent.toLowerCase()
+	if (ua.match(/MicroMessenger/i) == 'micromessenger') {
+		return true
+	} else {
+		return false
+	}
+	// #endif
+	return false
+}
+
+
+/**
+ * 获取url参数
+ * @param {*} name
+ * @param {*}
+ * @returns
+ */
+function getQueryString(name, url) {
+	var url = url || window.location.href
+	var reg = new RegExp('(^|&|/?)' + name + '=([^&|/?]*)(&|/?|$)', 'i')
+	var r = url.substr(1).match(reg)
+	if (r != null) {
+		return r[2]
+	}
+	return null
+}
+
+//路径转化
+function getPath(path) {
+	return path?path.split('?').shift():path;
+}
+
+//复制内容
+function uniCopy({
+	content,
+	success,
+	error
+}) {
+
+	content = typeof content === 'string' ? content : content.toString() // 复制内容,必须字符串,数字需要转换为字符串
+
+	/**
+	 * 小程序端 和 app端的复制逻辑
+	 */
+	//#ifndef H5
+	uni.setClipboardData({
+		data: content,
+		success: function() {
+			success("复制成功~")
+		},
+		fail: function() {
+			error("复制失败~")
+		}
+	});
+	//#endif
+
+	/**
+	 * H5端的复制逻辑
+	 */
+	// #ifdef H5
+	if (!document.queryCommandSupported('copy')) { //为了兼容有些浏览器 queryCommandSupported 的判断
+		// 不支持
+		error('浏览器不支持')
+	}
+	let textarea = document.createElement("textarea")
+	textarea.value = content
+	textarea.readOnly = "readOnly"
+	document.body.appendChild(textarea)
+	textarea.select() // 选择对象
+	textarea.setSelectionRange(0, content.length) //核心
+	let result = document.execCommand("copy") // 执行浏览器复制命令
+	if (result) {
+		success("复制成功~")
+	} else {
+		error("复制失败,请检查h5中调用该方法的方式,是不是用户点击的方式调用的,如果不是请改为用户点击的方式触发该方法,因为h5中安全性,不能js直接调用!")
+	}
+	textarea.remove()
+	// #endif
+}
+
+
+//设置缓存
+function setDb(name, value, db_time = 7200) {
+	let time = (new Date()).getTime();
+	let data = {
+		value: value,
+		time: time,
+		db_time: db_time
+	}
+	uni.setStorageSync(name, data);
+}
+//获取缓存
+function getDb(name) {
+	try {
+		let res = uni.getStorageSync(name);
+		if (!res) {
+			return '';
+		}
+		let time = (new Date()).getTime();
+		if ((time - res.time) / 1000 >= res.db_time) {
+			uni.removeStorageSync(name);
+			return '';
+		}
+		return res.value;
+	} catch (e) {
+		//TODO handle the exception
+		return '';
+	}
+}
+
+/**
+ * 下载图片
+ */
+function getCachedImage(image_url) {
+	return new Promise((resolve, reject) => {
+		let arr = image_url.split('/');
+		let image_name = arr[arr.length - 1];
+		var u = getDb('cms' + image_name);
+		if (u) {
+			resolve(u);
+		} else {
+			// 本地没有缓存 需要下载
+			uni.downloadFile({
+				url: image_url,
+				success: res => {
+					if (res.statusCode === 200) {
+						uni.saveFile({
+							tempFilePath: res.tempFilePath,
+							success: function(res) {
+								setDb('cms' + image_name, res.savedFilePath)
+								resolve(res.savedFilePath);
+							}
+						});
+					} else {
+						reject('下载失败');
+					}
+				},
+				fail: function() {
+					reject('下载失败');
+				}
+			});
+		}
+	});
+}
+/**
+ * 重设tabbar
+ * @param {Object} tablist
+ */
+function setTabbar(tablist) {
+	tablist.list.forEach((item, index) => {
+		uni.setTabBarItem({
+			index: index,
+			text: item.text,
+			iconPath: item.image,
+			selectedIconPath: item.selectedImage,
+			pagePath: item.path
+		})
+	})
+	uni.setTabBarStyle({
+		color: tablist.color,
+		selectedColor: tablist.selectColor,
+		backgroundColor: tablist.bgColor,
+		borderStyle: 'black'
+	})
+}
+
+
+export default {
+	extend,
+	toDecimal,
+	getTimestamp,
+	formatDate,
+	timeAgo,
+	urlEncode,
+	urlDecode,
+	deepAssign,
+	showToast,
+	hideToast,
+	showLoading,
+	hideLoading,
+	showModal,
+	formatRichText,	
+	strlen,
+	isWeiXinBrowser,
+	getQueryString,
+	getPath,
+	uniCopy,
+	setDb,
+	getDb,
+	getCachedImage,
+	setTabbar
+}

+ 334 - 0
config/api.js

@@ -0,0 +1,334 @@
+import {
+	baseUrl,
+	baseApiUrl
+} from './config.js';
+import * as common from './common.js' //引入common
+import * as db from './db.js' //引入common
+// 需要登陆的,都写到这里,否则就是不需要登陆的接口
+let methodsToken = ['profile', 'refreshUser', 'wxLogin', 'changeMobile', 'getWuyuanUser'];
+const post = (method, data, callback, type, orgurl) => {
+	let userToken = '';
+	let auth = '';
+	// 判断token是否存在
+	// console.log("method: ", method);
+	if (methodsToken.indexOf(method) >= 0) {
+		// 获取用户token
+		let auth = db.get("auth");
+		// console.log(auth);
+		let nowdate = (new Date()) / 1000; //当前时间戳
+		//新增用户判断是否登录逻辑begin
+		common.isLogin();
+		//新增用户判断是否登录逻辑end
+		if (!auth || auth.createtime + auth.expires_in < nowdate) {
+			common.toLogin();
+			return false;
+		} else {
+			userToken = auth.token;
+		}
+	}
+
+	if (type) {
+		method = type + '/' + method
+	} else {
+		method = '/' + method
+	}
+
+	let realurl = baseApiUrl + method;
+
+	if (orgurl) {
+		realurl = baseUrl + orgurl;
+	}
+
+
+	uni.showLoading({
+		title: '',
+		icon: 'loading'
+	});
+	uni.request({
+		url: realurl,
+		data: data,
+		header: {
+			'Accept': 'application/json',
+			'Content-Type': 'application/x-www-form-urlencoded',
+			'token': userToken,
+			'__token__': userToken
+
+		},
+		method: 'POST',
+		success: (response) => {
+			uni.hideLoading();
+			const result = response.data
+			if (result.msg == 'Please login' || result.msg == '请登陆') {
+				db.del("user");
+				db.del("auth");
+				console.log('未登陆')
+				uni.showToast({
+					title: result.msg,
+					icon: 'none',
+					duration: 2000,
+					complete: function() {
+						uni.reLaunch({
+							url: '/pages/index/index',
+						})
+					}
+				});
+			}
+			callback(result);
+		},
+		fail: (error) => {
+			uni.hideLoading();
+			if (error && error.response) {
+				showError(error.response);
+			}
+		},
+	});
+}
+
+// 上传图片
+export const uploadImage = (method, data = {}, callback, num = 9, type) => {
+	if (type) {
+		method = type + '/' + method
+	} else {
+		method = method
+	}
+	let userToken = '';
+	let auth = db.get("auth");
+	userToken = auth.token;
+	uni.chooseImage({
+		count: num,
+		success: (res) => {
+			uni.showLoading({
+				title: '上传中...'
+			});
+			let tempFilePaths = res.tempFilePaths
+			for (var i = 0; i < tempFilePaths.length; i++) {
+				data.file = tempFilePaths[i]
+				uni.uploadFile({
+					url: baseApiUrl + method,
+					filePath: tempFilePaths[i],
+					fileType: 'image',
+					name: 'file',
+					headers: {
+						'Accept': 'application/json',
+						'Content-Type': 'multipart/form-data',
+						'token': userToken
+					},
+					formData: data,
+					success: (uploadFileRes) => {
+						callback(JSON.parse(uploadFileRes.data))
+					},
+					fail: (error) => {
+						if (error && error.response) {
+							common.showError(error.response);
+						}
+					},
+					complete: () => {
+						setTimeout(function() {
+							uni.hideLoading();
+						}, 250);
+					},
+				});
+			}
+		}
+	});
+}
+
+const get = (url, callback) => {
+	uni.showLoading({
+		title: '加载中'
+	});
+	let realurl = baseUrl + url;
+
+	if (url.indexOf('http:') >= 0 || url.indexOf('https:') >= 0) {
+		let realurl = url;
+	}
+	uni.request({
+		url: realurl,
+		header: {
+			'Accept': 'application/json',
+			'Content-Type': 'application/x-www-form-urlencoded', //自定义请求头信息
+		},
+		method: 'GET',
+		success: (response) => {
+			callback(response.data);
+		},
+		fail: (error) => {
+			if (error && error.response) {
+				showError(error.response);
+			}
+		},
+		complete: () => {
+			setTimeout(function() {
+				uni.hideLoading();
+			}, 250);
+		}
+	});
+}
+
+const showError = error => {
+	let errorMsg = ''
+	switch (error.status) {
+		case 400:
+			errorMsg = '请求参数错误'
+			break
+		case 401:
+			errorMsg = '未授权,请登录'
+			break
+		case 403:
+			errorMsg = '跨域拒绝访问'
+			break
+		case 404:
+			errorMsg = `请求地址出错: ${error.config.url}`
+			break
+		case 408:
+			errorMsg = '请求超时'
+			break
+		case 500:
+			errorMsg = '服务器内部错误'
+			break
+		case 501:
+			errorMsg = '服务未实现'
+			break
+		case 502:
+			errorMsg = '网关错误'
+			break
+		case 503:
+			errorMsg = '服务不可用'
+			break
+		case 504:
+			errorMsg = '网关超时'
+			break
+		case 505:
+			errorMsg = 'HTTP版本不受支持'
+			break
+		default:
+			errorMsg = error.msg
+			break
+	}
+	uni.showToast({
+		title: errorMsg,
+		icon: 'none',
+		duration: 2000
+	});
+}
+
+
+
+const syncget = (url, data) => {
+	return new Promise(function(resolve, reject) {
+		get(url + queryParams(data, true), (result) => {
+			try {
+				resolve(result);
+			} catch (e) {
+				reject(e);
+			}
+		})
+	})
+
+}
+
+const syncpost = (f, m, d) => {
+	return new Promise(function(resolve, reject) {
+		post(f, d, (result) => {
+			try {
+				resolve(result);
+			} catch (e) {
+				reject(e);
+			}
+		}, m, m);
+	})
+
+}
+// 登录
+export const third = (data, callback) => post('third', data, callback, 'discover/User');
+// 用户绑定手机
+export const bindphone = (data, callback) => post('bind', data, callback, 'discover/User');
+// 修改用户信息
+export const profile = (data, callback) => post('profile', data, callback, 'discover/User');
+// 发送验证码
+export const sendSmsVerify = (data, callback) => post('sendSmsVerify', data, callback, 'discover/User');
+// 刷新用户
+export const refreshUser = (data, callback) => post('refreshUser', data, callback, 'discover/User');
+
+// 注册
+export const register = (data, callback) => post('register', data, callback, 'discover/User');
+// 登录
+export const login = (data, callback) => post('login', data, callback, 'discover/User');
+// 退出登录
+export const logout = (data, callback) => post('logout', data, callback, 'discover/User');
+// 上传头像
+export const upload = (data, callback) => post('upload', data, callback, 'discover/Ajax');
+
+// 绑定手机
+export const changeMobile = (data, callback) => post('changeMobile', data, callback, 'discover/User');
+
+//获取手机号前先登录
+export const wxLogin = (data, callback) => post('wxLogin', data, callback, 'user');
+//获取手机号
+export const getMobile = (data, callback) => post('getPhoneNumber', data, callback, 'user');
+//取CMS配置
+export const getConfig = async (params = {}) => await syncget('addons/cms/api.common/init', params);
+
+
+function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
+	let prefix = isPrefix ? '?' : ''
+	let _result = []
+	if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets';
+	for (let key in data) {
+		let value = data[key]
+		// 去掉为空的参数
+		if (['', undefined, null].indexOf(value) >= 0) {
+			continue;
+		}
+		if (value.length > 0) {
+			value = value.replace(/%/g, "%25");
+			value = value.replace(/\&/g, "%26");
+			value = value.replace(/\+/g, "%2B");
+		}
+
+		// 如果值为数组,另行处理
+		if (value.constructor === Array) {
+			// e.g. {ids: [1, 2, 3]}
+			switch (arrayFormat) {
+				case 'indices':
+					// 结果: ids[0]=1&ids[1]=2&ids[2]=3
+					for (let i = 0; i < value.length; i++) {
+						_result.push(key + '[' + i + ']=' + value[i])
+					}
+					break;
+				case 'brackets':
+					// 结果: ids[]=1&ids[]=2&ids[]=3
+					value.forEach(_value => {
+						_result.push(key + '[]=' + _value)
+					})
+					break;
+				case 'repeat':
+					// 结果: ids=1&ids=2&ids=3
+					value.forEach(_value => {
+						_result.push(key + '=' + _value)
+					})
+					break;
+				case 'comma':
+					// 结果: ids=1,2,3
+					let commaStr = "";
+					value.forEach(_value => {
+						commaStr += (commaStr ? "," : "") + _value;
+					})
+					_result.push(key + '=' + commaStr)
+					break;
+				default:
+					value.forEach(_value => {
+						_result.push(key + '[]=' + _value)
+					})
+			}
+		} else {
+			_result.push(key + '=' + value)
+		}
+	}
+	return _result.length ? prefix + _result.join('&') : ''
+}
+
+//取轮播图
+export const getBannerList = (data, callback) => get('/api/wuyuan/swiper_icon/lists' + queryParams(data, true),
+	callback);
+export const getWuyuanUser = (data, callback) => post('getWuyuanUser', data, callback, 'wuyuan/user');

+ 647 - 0
config/common.js

@@ -0,0 +1,647 @@
+import * as db from './db.js' //引入common
+import * as api from './api.js'
+import {
+	baseUrl,
+	cndUrl,
+	bgClass,
+	title,
+	baseLogo,
+} from './config.js'
+import htmlParser from '@/common/html-parser' //引入htmlParser
+
+
+/**
+ * 跳转登陆页面
+ */
+function toLogin() {
+
+	uni.showToast({
+		title: '请登录...',
+		icon: 'loading',
+		duration: 2000,
+		success: function(res) {
+			var pages = getCurrentPages() // 获取栈实例
+			let currentRoute = pages[pages.length - 1].route; // 获取当前页面路由
+			//console.log("路由当前页面路径"+currentRoute)
+			let currentPage = pages[pages.length - 1]['$page']['fullPath'] //当前页面路径(带参数)
+			// console.log(currentPage)
+
+			// #ifdef H5 || APP-PLUS
+			uni.navigateTo({
+				url: '/pages/user/login?redirect=' + escape(currentPage)
+			})
+			// #endif
+			// #ifdef MP-WEIXIN
+			uni.navigateTo({
+				url: '/pages/user/login?redirect=' + escape(currentPage),
+				animationType: 'pop-in',
+				animationDuration: 200
+			});
+			// #endif
+		}
+	})
+}
+
+/**
+ * 是否登陆,和绑定手机号,否则返回登录页
+ */
+function isLogin() {
+	let user = db.get('user');
+	//用户存在,不跳转,不存在直接跳转
+	if (user) {
+		if (user.id) {
+			// if(user.mobile==''){
+			// 	uni.navigateTo({
+			// 		url: '/pages/user/bind'
+			// 	})
+			// }	
+
+		} else {
+			console.log("user: ", user);
+			db.del('user');
+			db.del('auth');
+			toLogin()
+		}
+	}
+}
+/**
+ * 无图标提示
+ * @param {String} msg 提示消息
+ * @param {Function} callback 回调函数
+ */
+
+function normalToShow(msg = '保存成功', callback = function() {}) {
+	uni.showToast({
+		title: msg,
+		icon: 'none',
+		duration: 2000,
+	});
+	setTimeout(function() {
+		callback();
+	}, 1000);
+}
+/**
+ * 成功提示
+ * @param {String} msg 提示消息
+ * @param {Function} callback 回调函数
+ */
+
+function successToShow(msg = '保存成功', callback = function() {}) {
+	uni.showToast({
+		title: msg,
+		icon: 'success',
+		duration: 2000,
+	});
+	setTimeout(function() {
+		callback();
+	}, 1500);
+}
+
+/**
+ * 失败提示
+ * @param {String} msg 提示消息
+ * @param {Function} callback 回调函数
+ */
+
+function errorToShow(msg = '操作失败', callback = function() {}) {
+	uni.showToast({
+		title: msg,
+		icon: 'none',
+		duration: 2000,
+	});
+	setTimeout(function() {
+		callback();
+	}, 2000);
+}
+
+/**
+ * 加载显示
+ * @param {String} msg 提示消息
+ */
+function loadToShow(msg = '加载中', icon = 'loading') {
+	uni.showToast({
+		title: msg,
+		icon: icon,
+	});
+}
+
+/**
+ * 加载隐藏
+ */
+
+function loadToHide() {
+	uni.hideToast();
+}
+
+/**
+ * 提示框
+ * @param {String} title 提示标题
+ * @param {String} content 提示内容
+ * @param {Function} callback 回调函数
+ * @param {Boolean} showCancel = [true|false] 显示关闭按钮
+ * @param {String} cancelText 关闭按钮文字 
+ * @param {String} confirmText 确定按钮文字 
+ * @example
+ * modelShow('提示','确认执行此操作吗?',()=>{},()=>{},true,'取消','确定')
+ */
+function modelShow(title = '提示', content = '确认执行此操作吗?', callback = () => {}, callback2 = () => {}, showCancel = true,
+	cancelText = '拒绝',
+	confirmText = '通过') {
+	uni.showModal({
+		title: title,
+		content: content,
+		showCancel: showCancel,
+		cancelText: cancelText,
+		confirmText: confirmText,
+		success: function(res) {
+			if (res.confirm) {
+				// 用户点击确定操作
+				setTimeout(() => {
+					callback()
+				}, 500)
+			} else if (res.cancel) {
+				// 用户取消操作
+				setTimeout(() => {
+					callback2()
+				}, 500)
+			}
+		}
+	});
+}
+
+
+
+/**
+ * 判断数组
+ * @param {Object} arr 数组
+ */
+function isArray(object) {
+	return object && typeof object === 'object'
+}
+
+/**
+ * 统一跳转
+ * @param {String} url 跳转链接
+ */
+function navigateTo(url) {
+	if (url == '')
+		return;
+	if (url.indexOf("/pages/index/index") >= 0 || url.indexOf("answer_pages/home/dashboard") >= 0 || url.indexOf(
+			"/pages/shouhu/shouhu") >= 0 || url.indexOf("/pages/feiyi/feiyi") >= 0 || url.indexOf(
+			"/pages/user/index") >= 0) {
+		uni.switchTab({
+			url: url,
+			animationType: 'pop-in',
+			animationDuration: 300
+		});
+	} else {
+		uni.navigateTo({
+			url: url,
+			animationType: 'pop-in',
+			animationDuration: 300
+		});
+	}
+
+}
+
+/**
+ * 关闭当前页面并跳转
+ * @param {String} url 跳转链接
+ */
+function redirectTo(url) {
+	uni.redirectTo({
+		url: url,
+		animationType: 'pop-in',
+		animationDuration: 300
+	})
+}
+/**
+ * 返回上一层的逻辑判断
+ * @param {num} delta 跳转上一层,或者几层
+ */
+function navigateBack(delta = 1) {
+	let pages = getCurrentPages(); //当前页
+	//console.log(pages);
+	//当有前一页的时候。
+	if (pages.length > 1) {
+		let page = pages[pages.length - 2]; //上个页面
+		// console.log(page.route);
+		if (page.route == "pages/index/index" || page.route == 'pages/index/homepage' || page.route ==
+			'pages/user/index' || page.route == 'pages/consult/index') {
+			uni.switchTab({
+				url: '/' + page.route
+			})
+		} else if (page.route == "pages/user/login") {
+			uni.switchTab({
+				url: '/pages/user/index'
+			})
+		} else if (page.route == "pages/user/register") {
+			uni.switchTab({
+				url: '/pages/user/user'
+			})
+		} else {
+			uni.navigateBack({
+				delta: delta
+			})
+		}
+	} else {
+		uni.switchTab({
+			url: '/pages/index/index'
+		})
+	}
+
+}
+
+/**
+ *  判断是否在微信浏览器
+ */
+function isWeiXinBrowser() {
+	// #ifdef H5
+	// window.navigator.userAgent属性包含了浏览器类型、版本、操作系统类型、浏览器引擎类型等信息,这个属性可以用来判断浏览器类型
+	let ua = window.navigator.userAgent.toLowerCase()
+	// 通过正则表达式匹配ua中是否含有MicroMessenger字符串
+	if (ua.match(/MicroMessenger/i) == 'micromessenger') {
+		return true
+	} else {
+		return false
+	}
+	// #endif
+
+	// #ifdef MP
+	return false;
+	// #endif
+}
+
+
+/**
+ * 加载cms列表
+ * @param {Array} data
+ * 
+ */
+
+function cmsGetList(data, callback = function() {}) {
+	api.cmsGetList(data, res => {
+		if (res.code) {
+			res.data.list.forEach(function(ele, index) {
+				ele.ctime = timeToDate(ele.createtime);
+				if (ele.image.substring(0, 9) == '/uploads/') {
+					ele.image = cndUrl + ele.image
+				}
+			});
+			callback(res.data)
+		} else {
+			errorToShow(res.msg)
+		}
+	})
+}
+
+
+/**
+ * 加载cms详情
+ * @param {Array} data
+ * 
+ */
+
+function cmsGetDetails(data, callback = function() {}) {
+	api.cmsGetDetails(data, res => {
+		//console.log(data)
+		if (res.code) {
+			res.data.content = htmlParser(res.data.content); //	设置资源cdn;
+			res.data.ctime = timeToDate(res.data.createtime);
+			if (res.data.image) {
+				if (res.data.image.substring(0, 9) == '/uploads/') {
+					res.data.image = cndUrl + res.data.image
+				}
+			}
+			callback(res.data)
+		} else {
+			modelShow(
+				'错误提示',
+				res.msg,
+				() => {
+					uni.navigateBack({})
+				},
+				false
+			);
+		}
+	})
+}
+/**
+ * 初始化页面
+ * @param {Function} callback 回调函数
+ * @param {String} route	页面路径/默认当前路径
+ */
+function initPages(callback = function() {}, route) {
+
+	if (!route) {
+		let pages = getCurrentPages();
+		let page = pages[pages.length - 1];
+		route = page.route
+	}
+	let init_data = db.get('init_' + route)
+
+	setTimeout(() => {
+		api.getInit({
+			route: route
+		}, res => {
+			// console.log(res);
+			if (res.code) {
+
+				res.data.slide.forEach(function(ele, index) {
+					if (ele.image.substring(0, 9) == '/uploads/') {
+						ele.image = cndUrl + ele.image
+					}
+				});
+				res.data.content.forEach(function(ele, index) {
+					ele.content = ele.content.replace(new RegExp(`style="width: 100%;`, 'g'),
+						'style="width: 100%;margin-top: -3px;') //	无缝连接图片;
+					ele.content = ele.content.replace(new RegExp(`src="/uploads`, 'g'),
+						'src="' + cndUrl + '/uploads')
+					ele.content = htmlParser(ele.content); //	设置资源cdn;
+				});
+				db.set('init_' + route, res.data)
+				callback(res.data)
+			}
+		});
+	}, 1000);
+	if (init_data) {
+		callback(init_data)
+	} else {
+		// loadToShow('初始化页面...');
+	}
+}
+
+/**
+ * 跳转链接
+ * @param {String} url 跳转链接
+ */
+function toUrl(url) {
+	if (url) {
+
+		var objExp = new RegExp(/http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/);
+		if (objExp.test(url) != true) {
+			// #ifdef APP-PLUS
+			url = baseUrl + url
+			// #endif
+			// #ifdef H5
+			url = baseUrl + url
+			// #endif
+		}
+		//console.log(url)
+		//return false
+		// #ifdef MP
+		uni.navigateTo({
+			url: url
+		})
+		// #endif
+
+		// #ifdef APP-PLUS
+		plus.runtime.openURL(url);
+		// #endif
+
+		// #ifdef H5
+		window.open(url);
+		// #endif
+
+	}
+}
+
+
+/**
+ * 保存登陆状态
+ * @param {Array} data 用户数据
+ */
+function saveLogin(data) {
+	db.set('auth', data.auth)
+
+	if (data.user.avatar.substring(0, 9) == '/uploads/') {
+		data.user.avatar = cndUrl + data.user.avatar
+	}
+	db.set('user', data.user)
+}
+
+
+/**
+ * 刷新用户
+ * @param {Function} callback 回调函数(用户数据)
+ * @example 
+ * refreshUser((user)=>{_this.user = user})
+ */
+function refreshUser(callback = function() {}) {
+	let user = db.get('user');
+	if (user.id) {
+		api.refreshUser({}, res => {
+			if (res.code == 1) {
+				saveLogin(res.data)
+				callback(res.data)
+			} else {
+				db.del('user');
+				toLogin()
+			}
+		})
+	} else {
+		db.del('user');
+		db.del('auth');
+		toLogin()
+	}
+}
+
+
+
+/**
+ * 清理用户
+ * @param {String} page 清理后跳转页面
+ */
+function cleanUser(page) {
+	db.del('user')
+	db.del('auth')
+	if (page) {
+		redirectTo(page)
+	}
+}
+
+
+
+
+/**
+ * 字符串校验
+ * @param {String} str 字符串
+ * @param {String} model = [number|mobile|name|idcard|] 模式
+ * @example 
+ * testString('17080057443','mobile')
+ * testString('17080057443','mobile')
+ */
+
+function testString(str, model = null) {
+	if (typeof(model) == 'number') {
+		if (str.length >= model) {
+			return true
+		}
+	} else {
+		switch (model) {
+			case null:
+				return false
+				break
+			case 'idcard':
+				return RegExp(/^[1-9]\d{5}(19|20)\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/)
+					.test(str)
+				break
+			case 'mobile':
+				return RegExp(/^1[0-9]{10}$/).test(str)
+				break
+			case 'name':
+				return RegExp(/^[\u4E00-\u9FA5\uf900-\ufa2d·s]{2,20}$/).test(str)
+				break
+			default:
+				return false
+				break
+		}
+	}
+	return false
+}
+
+
+
+
+/**
+ * 时间戳转时差
+ * @param {String} date 时间戳
+ */
+
+function timeToDate(time) {
+	time = time * 1000
+	var interval = new Date().getTime() - time;
+	//计算出相差天数
+	var returnTime = "";
+	var days = Math.floor(interval / (24 * 3600 * 1000))
+	if (days == 0) {
+		//计算出小时数
+		var leaveTime = interval % (24 * 3600 * 1000) //计算天数后剩余的毫秒数
+		var hours = Math.floor(leaveTime / (3600 * 1000))
+		if (hours == 0) {
+			//计算相差分钟数
+			leaveTime = leaveTime % (3600 * 1000) //计算小时数后剩余的毫秒数
+			var minutes = Math.floor(leaveTime / (60 * 1000))
+			if (minutes == 0) {
+				//计算相差秒数
+				leaveTime = leaveTime % (60 * 1000) //计算分钟数后剩余的毫秒数
+				var seconds = Math.round(leaveTime / 1000)
+				return seconds + "秒前";
+			}
+			return minutes + "分钟前";
+		}
+		return hours + "小时前";
+	}
+	return days + "天前";
+}
+
+
+
+/**
+ * 基本信息
+ */
+function baseInfo() {
+	return {
+		bgClass: bgClass,
+		title: title,
+		baseLogo: baseLogo,
+	}
+}
+//校验邮箱格式
+function checkEmail(email) {
+	return RegExp(/^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/).test(
+		email);
+}
+//校验手机格式
+function checkMobile(mobile) {
+	return RegExp(/^1[0-9]{10}$/).test(mobile);
+}
+/**
+ * 用户信息
+ */
+function userInfo() {
+	let user = db.get('user')
+	if (user) {
+		return user
+	} else {
+		return false;
+	}
+
+	// return {
+	// 	bgClass: bgClass,
+	// 	title: title,
+	// 	baseLogo: baseLogo,
+	// }
+}
+
+
+/**
+ * 上传图片
+ * @param {Array} data 附带数据
+ * @param {Function} callback 回调函数
+ * @param {int} num 限制数量
+ * @param {String} type 类型
+ * @return {String} url 
+ */
+function uploadImage(data = {}, callback = function() {}, num = 1, type) {
+	api.uploadImage(
+		'common/upload', data, (res) => {
+			if (res.code) {
+				if (res.data.url.substring(0, 9) == '/uploads/') {
+					res.data.url = cndUrl + res.data.url
+				}
+				callback(res.data.url)
+			} else {
+				errorToShow(res.msg)
+			}
+		}, type)
+
+}
+
+function formatRichText(html) {
+	let newContent = html.replace(/<img[^>]*>/gi, function(match, capture) {
+		match = match.replace(/style="[^"]+"/gi, '').replace(/style='[^']+'/gi, '');
+		match = match.replace(/width="[^"]+"/gi, '').replace(/width='[^']+'/gi, '');
+		match = match.replace(/height="[^"]+"/gi, '').replace(/height='[^']+'/gi, '');
+		return match;
+	});
+	newContent = newContent.replace(/style="[^"]+"/gi, function(match, capture) {
+		match = match.replace(/width:[^;]+;/gi, 'max-width:100%;').replace(/width:[^;]+;/gi, 'max-width:100%;');
+		return match;
+	});
+	newContent = newContent.replace(/<br[^>]*\/>/gi, '');
+	newContent = newContent.replace(/\<img/gi, '<img style="max-width:100%;height:auto;display:block;margin:10px 0;"');
+	return newContent;
+}
+
+
+export {
+	toLogin,
+	isLogin,
+	normalToShow,
+	successToShow,
+	errorToShow,
+	isArray,
+	loadToShow,
+	loadToHide,
+	navigateTo,
+	navigateBack,
+	redirectTo,
+	modelShow,
+	isWeiXinBrowser,
+	initPages,
+	toUrl,
+	saveLogin,
+	refreshUser,
+	cleanUser,
+	testString,
+	timeToDate,
+	cmsGetList,
+	cmsGetDetails,
+	baseInfo,
+	userInfo,
+	uploadImage,
+	checkEmail,
+	checkMobile,
+	formatRichText
+}

File diff suppressed because it is too large
+ 23 - 0
config/config.js


+ 83 - 0
config/db.js

@@ -0,0 +1,83 @@
+import * as common from './common.js' //引入common
+
+//取值
+function get(key,sync = true) {
+    try {
+		if(sync){
+			return uni.getStorageSync(key);
+		}else{
+			let data = '';
+			uni.getStorage({
+				key:key,
+				success: function (res) {
+					data = res.data;
+				}
+			});
+			return data;
+		}
+    } catch (e) {
+        return false;
+    }
+}
+
+//赋值
+function set(key, value, sync = true) {
+    try {
+        if (sync) {
+            return uni.setStorageSync(key, value);
+        } else {
+            uni.setStorage({
+                key: key,
+                data: value
+            });
+        }
+    } catch (e) {
+
+    }
+}
+
+//移除
+function del(key, sync = true){
+    try {
+        if (sync) {
+            return uni.removeStorageSync(key);
+        } else {
+            uni.removeStorage({
+                key: key
+            });
+        }
+    } catch (e) {
+        return false;
+    }
+}
+
+//清空
+function clear(sync = true){
+    try {
+        if (sync) {
+            return uni.clearStorageSync();
+        } else {
+            uni.clearStorage();
+        }
+    } catch (e) {
+        return false;
+    }
+}
+
+//获取用户token,如果缓存有,直接返回,如果没有,就先微信登陆,然后服务器登陆,最后返回token
+function userToken(callback) {
+    var token = get('userToken');
+    if (token){
+        callback(token);
+    }else{
+        //如果没有登陆,就去登陆
+        common.toLogin();
+    }
+}
+export {
+    get,
+    set,
+    del,
+    clear,
+    userToken
+}

+ 403 - 0
index_fenbao/faBuWenZhang/faBuWenZhang.vue

@@ -0,0 +1,403 @@
+<template>
+	<view class="body" style="background-color: #efefef">
+		<u-navbar :autoBack="true" title="发布" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#121212"></u-navbar>
+		<view class="nav_fabu">
+			<view class="nav_left">
+				<view class="left_icon"></view>
+				<text>发文章</text>
+			</view>
+			<view class="nav_right">
+				<view class="right_icon"></view>
+				<text>发文章</text>
+			</view>
+		</view>
+		<!-- 上传发布组件 -->
+		<view class="" style="margin-bottom: 20rpx">
+			<u--input v-model="titleVal" customStyle="background-color: #ffffff;height: 105rpx;" placeholder="请填写标题" border="surround" clearable></u--input>
+		</view>
+
+		<view class="box">
+			<view class="sc_box">
+				<text class="sc_tp">请上传图片</text>
+				<text class="sc_tp">{{ uploadNumber + '/' + 9 }}</text>
+			</view>
+			<view style="margin-left: 56rpx">
+				<u-upload
+					width="200rpx"
+					height="200rpx"
+					:maxCount="9"
+					:deletable="true"
+					:fileList="fileList1"
+					@afterRead="afterRead"
+					@delete="deletePic"
+					name="1"
+					multiple
+				></u-upload>
+			</view>
+			<view class="" style="background-color: #efefef; height: 20rpx; width: 100%"><!-- 分割背景 --></view>
+			<view class="banxin" style="background-color: #ffffff; margin-top: 20rpx">
+				<u--textarea v-model="textVal" placeholder="请输入内容"></u--textarea>
+			</view>
+			<!-- 分区 -->
+			<u-popup :show="showTanCeng" mode="bottom" @close="close" bgColor="#efefef">
+				<view class="show_box">
+					<view class="" style="display: flex; justify-content: space-between">
+						<text @click="showTanCeng = false">取消</text>
+						<text class="box_ok">确认</text>
+					</view>
+					<view class="box_fenqu" @click="show = true">
+						<text>分区</text>
+						<view class="">
+							<text style="color: #999999; font-size: 28rpx">生活 - 日常</text>
+							<uni-icons color="#999999" type="forward" size="20"></uni-icons>
+						</view>
+					</view>
+					<!-- 添加标签 -->
+					<view class="box_tj">
+						<text style="color: #999999; font-size: 28rpx">还可以添加 2 个标签</text>
+						<view class="" style="display: flex">
+							<u-tag
+								v-for="item in tags"
+								:key="item"
+								@close.stop="closeBtn(item)"
+								:show="closeTag"
+								closable
+								shape="circle"
+								:text="item.label"
+								:type="item.type"
+							></u-tag>
+							<!-- <u-tag @close="close1 = false" closable shape="circle" text="标签" type="error"></u-tag> -->
+						</view>
+						<!-- 推荐标签 -->
+						<view class="" style="margin-top: 20rpx">
+							<view style="color: #000000; font-size: 30rpx">推荐标签</view>
+							<view class="" style="display: flex; margin-top: 20rpx">
+								<u-tag size="mini" shape="circle" text="标签" type="warning"></u-tag>
+								<u-tag size="mini" shape="circle" text="标签" type="success"></u-tag>
+							</view>
+						</view>
+					</view>
+
+					<u-picker
+						title="选择分区"
+						:show="show"
+						@close="show = false"
+						ref="uPicker"
+						@cancel="show = false"
+						:closeOnClickOverlay="true"
+						:columns="columns"
+						@confirm="confirm"
+						@change="changeHandler"
+					></u-picker>
+				</view>
+			</u-popup>
+			<view class="" style="background-color: #efefef; height: 20rpx; width: 100%"><!-- 分割背景 --></view>
+			<view class="fq_box" @click="fenquBtn">
+				<text>分区和标签</text>
+				<view class="bq_box">
+					<u-tag size="mini" shape="circle" text="标签" type="warning"></u-tag>
+					<u-tag size="mini" shape="circle" text="标签" type="success"></u-tag>
+					<u-tag size="mini" shape="circle" text="标签" type="error"></u-tag>
+					<u-icon name="arrow-right" color="#666666" size="16"></u-icon>
+				</view>
+			</view>
+
+			<!-- 评论 -->
+			<view class="" style="background-color: #efefef; height: 20rpx; width: 100%"><!-- 分割背景 --></view>
+			<view class="pl_box">
+				<text>开启评论</text>
+				<u-switch activeColor="#5ac725" v-model="switchval" @change="change"></u-switch>
+			</view>
+			<view class="queren">确认发布</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			closeTag: true,
+			tags: [
+				{ label: '标签1', type: 'primary', closable: true },
+				{ label: '标签2', type: 'warning ', closable: true },
+				{ label: '标签3', type: 'success', closable: true }
+			],
+			showTanCeng: false,
+			show: false,
+			columns: [
+				['中国', '美国'],
+				['深圳', '厦门', '上海', '拉萨']
+			],
+			columnData: [
+				['深圳', '厦门', '上海', '拉萨'],
+				['得州', '华盛顿', '纽约', '阿拉斯加']
+			],
+			titleVal: '',
+			textVal: '',
+			switchval: '',
+			fileList1: [
+				{
+					url: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/shouyeTJ.png'
+				},
+				{
+					url: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_shouye_tp.png'
+				},
+				{
+					url: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/shouyeBJ.png'
+				},
+				{
+					url: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/shouyeTJ.png'
+				},
+				{
+					url: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_shouye_tp.png'
+				},
+				{
+					url: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/shouyeBJ.png'
+				},
+				{
+					url: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/shouyeTJ.png'
+				},
+				{
+					url: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_shouye_tp.png'
+				},
+				{
+					url: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/shouyeBJ.png'
+				}
+			]
+		};
+	},
+	onLoad() {},
+	computed: {
+		uploadNumber() {
+			return this.fileList1.length;
+		}
+	},
+	methods: {
+		fenquBtn() {
+			// this.show = true;
+			this.showTanCeng = true;
+		},
+		// 关闭标签
+		closeBtn(item) {
+			console.log(item);
+			this.tags.splice(this.tags.indexOf(item), 1);
+		},
+		// 分区组件方法
+		changeHandler(e) {
+			const {
+				columnIndex,
+				value,
+				values, // values为当前变化列的数组内容
+				index,
+				// 微信小程序无法将picker实例传出来,只能通过ref操作
+				picker = this.$refs.uPicker
+			} = e;
+			// 当第一列值发生变化时,变化第二列(后一列)对应的选项
+			if (columnIndex === 0) {
+				// picker为选择器this实例,变化第二列对应的选项
+				picker.setColumnValues(1, this.columnData[index]);
+			}
+		},
+		//// 分区组件方法 回调参数为包含columnIndex、value、values
+		confirm(e) {
+			console.log('confirm', e);
+			this.show = false;
+		},
+		change(e) {
+			console.log('change', e);
+		},
+		// 删除图片
+		deletePic(event) {
+			console.log(event, 'event');
+			this[`fileList${event.name}`].splice(event.index, 1);
+		},
+		// 新增图片
+		async afterRead(event) {
+			// 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
+			let lists = [].concat(event.file);
+			let fileListLen = this[`fileList${event.name}`].length;
+			lists.map((item) => {
+				this[`fileList${event.name}`].push({
+					...item,
+					status: 'uploading',
+					message: '上传中'
+				});
+			});
+			for (let i = 0; i < lists.length; i++) {
+				const result = await this.uploadFilePromise(lists[i].url);
+				let item = this[`fileList${event.name}`][fileListLen];
+				this[`fileList${event.name}`].splice(
+					fileListLen,
+					1,
+					Object.assign(item, {
+						status: 'success',
+						message: '',
+						url: result
+					})
+				);
+				fileListLen++;
+			}
+		},
+		uploadFilePromise(url) {
+			return new Promise((resolve, reject) => {
+				let a = uni.uploadFile({
+					url: 'http://192.168.2.21:7001/upload', // 仅为示例,非真实的接口地址
+					filePath: url,
+					name: 'file',
+					formData: {
+						user: 'test'
+					},
+					success: (res) => {
+						setTimeout(() => {
+							resolve(res.data.data);
+						}, 1000);
+					}
+				});
+			});
+		},
+		close() {
+			this.showTanCeng = false;
+			// console.log('close');
+		}
+	}
+};
+</script>
+
+<style>
+.banxin {
+	margin: 0 32rpx 0 32rpx;
+}
+.u-upload__button.data-v-69e2a36e {
+	background-color: #efefef !important;
+}
+.u-upload__deletable.data-v-69e2a36e {
+	width: 35rpx !important;
+	height: 35rpx !important;
+}
+.u-transition {
+	margin-right: 12rpx;
+}
+/* .u-icon__icon.data-v-2ee87dc9 {
+	font-size: 25rpx !important;
+	line-height: 26rpx !important;
+} */
+.nav_fabu {
+	display: flex;
+	justify-content: space-evenly;
+	background-color: #ca5642;
+}
+.nav_left {
+	display: flex;
+	justify-content: start;
+	align-items: center;
+	width: 282rpx;
+	height: 123rpx;
+	font-size: 32rpx;
+	background: #f4f4f9;
+	border: 6px solid #ca5642;
+	border-radius: 20rpx;
+}
+.left_icon {
+	width: 50rpx;
+	height: 50rpx;
+	border-radius: 50%;
+	margin-left: 50rpx;
+	background-color: #ca5642;
+}
+.nav_right {
+	display: flex;
+	justify-content: start;
+	align-items: center;
+	width: 282rpx;
+	height: 123rpx;
+	font-size: 32rpx;
+	background: #f4f4f9;
+	border: 6px solid #ca5642;
+	border-radius: 20rpx;
+}
+.right_icon {
+	width: 50rpx;
+	height: 50rpx;
+	margin-left: 50rpx;
+	border-radius: 50%;
+	background-color: #4e7198;
+}
+
+.sc_box {
+	display: flex;
+	justify-content: space-between;
+	margin: 0 55rpx 40rpx 55rpx;
+	border-bottom: 1px #dedede solid;
+	height: 60rpx;
+}
+.sc_tp {
+	font-size: 30rpx;
+	color: #000000;
+}
+.fq_box {
+	display: flex;
+	justify-content: space-between;
+	margin: 0 32rpx 0 32rpx;
+	height: 105rpx;
+	align-items: center;
+}
+.box {
+	background-color: #ffffff;
+	padding-top: 20rpx;
+	padding-bottom: 30rpx;
+}
+.bq_box {
+	display: flex;
+	align-items: center;
+}
+.pl_box {
+	display: flex;
+	justify-content: space-between;
+	margin: 0 32rpx 0 32rpx;
+	height: 105rpx;
+	align-items: center;
+}
+.show_box {
+	width: 100%;
+	height: 500rpx;
+	padding: 20rpx;
+}
+.queren {
+	height: 70rpx;
+	background-color: #ca5642;
+	border-radius: 35rpx;
+	font-size: 30rpx;
+	color: #ffffff;
+	text-align: center;
+	line-height: 68rpx;
+	margin: 0 32rpx 0 32rpx;
+}
+.box_ok {
+	line-height: 50rpx;
+	text-align: center;
+	color: #ffffff;
+	width: 93rpx;
+	height: 52rpx;
+	background: #ca5642;
+	border-radius: 26rpx;
+}
+.box_fenqu {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	border-radius: 20rpx;
+	margin-top: 20rpx;
+	height: 105rpx;
+	background-color: #ffffff;
+	font-size: 30rpx;
+	padding: 20rpx;
+}
+.box_tj {
+	height: 300rpx;
+	border-radius: 20rpx;
+	background-color: #ffffff;
+	margin-top: 20rpx;
+	padding: 20rpx;
+}
+</style>

+ 32 - 0
index_fenbao/hongsewenhua/hongsewenhua.vue

@@ -0,0 +1,32 @@
+<template>
+	<view class="body">
+		<u-navbar :autoBack="true" title="红色文化" bgColor="rgba(195, 232, 249)" :placeholder="true" titleStyle="font-weight:bold;color:#121212"></u-navbar>
+
+		<scroll-view class="scroll-view" scroll-y="true">
+			<!-- 这里添加您的图片 -->
+			<image src="https://huli-app.wenlvti.net/app_static/minnanhun/image/hswh.png" mode="widthFix"></image>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {};
+	},
+	methods: {}
+};
+</script>
+
+<style>
+scroll-view {
+	width: 100%;
+	height: 100vh; /* 设置 scroll-view 的高度为屏幕高度 */
+	overflow: auto; /* 显示滚动条,如果需要的话 */
+}
+
+image {
+	width: 100%; /* 图片宽度填充滚动视图 */
+	height: auto; /* 高度自适应,保持图片纵横比 */
+}
+</style>

+ 147 - 0
main.js

@@ -0,0 +1,147 @@
+import Vue from 'vue'
+import App from './App'
+import * as Api from './config/api.js'
+import * as Common from './config/common.js'
+import * as Db from './config/db.js'
+import * as Config from './config/config.js'
+import share from "@/common/share.js"
+import store from './store';
+import util from "@/common/util.js"
+
+
+
+const logic = {
+	systemInfo: null,
+	/**
+	 * 显示消息提示
+	 * 
+	 * @param {Object} text
+	 * @param {Object} icon
+	 */
+	showToast(text, icon) {
+		return uni.showToast({
+			title: text || '加载中',
+			icon: icon || 'none',
+			mask: true,
+			duration: 2000
+		});
+	},
+	/**
+	 * 全局方法 - px2rpx 
+	 *
+	 * @param {Object} px
+	 */
+	px2rpx(px) {
+		if (this.systemInfo === null) {
+			this.systemInfo = uni.getSystemInfoSync()
+		}
+
+		//  官方规定屏幕宽为750rpx
+		return px / this.systemInfo.windowWidth * 750;
+	},
+
+	/**
+	 * 全局方法 - rpx2px
+	 * 
+	 * @param {Object} rpx
+	 */
+	rpx2px(rpx) {
+		if (this.systemInfo === null) {
+			this.systemInfo = uni.getSystemInfoSync()
+		}
+
+		//  官方规定屏幕宽为750rpx
+		return rpx / 750 * this.systemInfo.windowWidth;
+	},
+}
+
+// import {
+// 	createSSRApp
+// } from 'vue'
+// export function createApp() {
+// 	const app = createSSRApp(App)
+// 	app.use(store)
+// 	app.config.globalProperties.$logic = logic
+// 	return {
+// 		app
+// 	}
+// }
+
+function isPromise(obj) {
+	return (!!obj &&
+		(typeof obj === "object" || typeof obj === "function") &&
+		typeof obj.then === "function"
+	);
+}
+
+// uni方法转then数组
+uni.addInterceptor({
+	returnValue(res) {
+		if (!isPromise(res)) {
+			return res;
+		}
+		const returnValue = [undefined, undefined];
+		return res
+			.then((res) => {
+				returnValue[1] = res;
+			})
+			.catch((err) => {
+				returnValue[0] = err;
+			})
+			.then(() => returnValue);
+	},
+});
+
+import {
+	tools
+} from '@/common/fa.mixin.js'
+Vue.mixin(tools)
+//皮肤色处理
+let styleMixin = require('@/common/fa.style.mixin.js')
+Vue.mixin(styleMixin)
+import uView from '@/uni_modules/uview-ui'
+Vue.use(uView)
+
+Vue.prototype.$api = Api;
+Vue.prototype.$common = Common;
+Vue.prototype.$db = Db;
+Vue.prototype.$config = Config;
+Vue.prototype.$logic = logic;
+Vue.prototype.$util = util;
+Vue.prototype.vuex_token = '';
+Vue.prototype.user = {};
+Vue.prototype.vuex_theme = {
+	value: {
+		color: "#fff",
+		bgColor: "#c7a772"
+	}
+}
+Vue.prototype.vuex_parse_style = {
+	h1: 'padding:20rpx 0;',
+	h2: 'padding:10rpx 0;',
+	h3: 'padding:10rpx 0;',
+	h4: 'padding:10rpx 0;',
+	h5: 'padding:5rpx 0;',
+	h6: 'padding:5rpx 0;',
+	ul: 'margin-bottom:20rpx;padding-left:30rpx;',
+	ol: 'margin-bottom:20rpx;padding-left:30rpx;',
+	code: 'background-color: #f6f6f6;margin: 0 5rpx;padding: 6rpx 8rpx;border-radius: 6rpx;text-align:center;',
+	pre: 'white-space: pre;overflow: auto;background: #f6f6f6;border-radius: 8rpx;border: none;color: #1a1a1a;margin-bottom: 20rpx;padding:20rpx;',
+	'pre code': 'margin:0;padding:0;',
+	blockquote: 'padding: 15rpx;margin:0 0 20rpx 0;border-radius: 6rpx;',
+	p: 'margin-bottom:20rpx',
+	table: 'width:100%;margin-bottom:20rpx;border-collapse: collapse;',
+	th: 'background-color: whitesmoke;border: 1px solid #e6e6e6;padding:10rpx;',
+	td: 'border: 1px solid #e6e6e6;padding:10rpx;'
+}
+Vue.prototype.vuex_config = {}
+Vue.config.productionTip = false
+
+Vue.mixin(share)
+App.mpType = 'app'
+
+const app = new Vue({
+	...App,
+	store
+})
+app.$mount()

+ 170 - 0
manifest.json

@@ -0,0 +1,170 @@
+{
+	"name": "闽南魂",
+	"appid": "__UNI__5CDBB2E",
+	"description": "闽南魂",
+	"versionName": "1.0.0",
+	"versionCode": "100",
+	"transformPx": false,
+	/* 5+App特有相关 */
+	"app-plus": {
+		"usingComponents": true,
+		"nvueCompiler": "uni-app",
+		"compilerVersion": 3,
+		"splashscreen": {
+			"alwaysShowBeforeRender": true,
+			"waiting": true,
+			"autoclose": true,
+			"delay": 0
+		},
+		/* 模块配置 */
+		"modules": {
+			"Share": {}
+		},
+		/* 应用发布信息 */
+		"distribute": {
+			/* android打包配置 */
+			"android": {
+				"permissions": [
+					"<uses-feature android:name=\"android.hardware.camera\"/>",
+					"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+					"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+					"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+					"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+					"<uses-permission android:name=\"android.permission.CAMERA\"/>",
+					"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+					"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+					"<uses-permission android:name=\"android.permission.INTERNET\"/>",
+					"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+					"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+					"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
+					"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+					"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+					"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+					"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+					"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+					"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
+					"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+				],
+				"abiFilters": ["armeabi-v7a", "arm64-v8a", "x86"]
+			},
+			/* ios打包配置 */
+			"ios": {
+				"idfa": false
+			},
+			/* SDK配置 */
+			"sdkConfigs": {
+				"maps": {
+					"qqmap": {
+						// 腾讯地图秘钥 https://lbs.qq.com/dev/console/key/manage
+						"key": "FHEBZ-HYVNS-KGHOD-65O3N-O32P3-5FBI6"
+					},
+					"amap": {
+						"appkey_ios": "",
+						"appkey_android": ""
+					}
+				},
+				"ad": {},
+				"share": {
+					"weixin": {
+						"appid": "wx9e0c3e6ab2ede1c9",
+						"UniversalLinks": ""
+					}
+				}
+			},
+			"icons": {
+				"android": {
+					"hdpi": "unpackage/res/icons/72x72.png",
+					"xhdpi": "unpackage/res/icons/96x96.png",
+					"xxhdpi": "unpackage/res/icons/144x144.png",
+					"xxxhdpi": "unpackage/res/icons/192x192.png"
+				},
+				"ios": {
+					"appstore": "unpackage/res/icons/1024x1024.png",
+					"ipad": {
+						"app": "unpackage/res/icons/76x76.png",
+						"app@2x": "unpackage/res/icons/152x152.png",
+						"notification": "unpackage/res/icons/20x20.png",
+						"notification@2x": "unpackage/res/icons/40x40.png",
+						"proapp@2x": "unpackage/res/icons/167x167.png",
+						"settings": "unpackage/res/icons/29x29.png",
+						"settings@2x": "unpackage/res/icons/58x58.png",
+						"spotlight": "unpackage/res/icons/40x40.png",
+						"spotlight@2x": "unpackage/res/icons/80x80.png"
+					},
+					"iphone": {
+						"app@2x": "unpackage/res/icons/120x120.png",
+						"app@3x": "unpackage/res/icons/180x180.png",
+						"notification@2x": "unpackage/res/icons/40x40.png",
+						"notification@3x": "unpackage/res/icons/60x60.png",
+						"settings@2x": "unpackage/res/icons/58x58.png",
+						"settings@3x": "unpackage/res/icons/87x87.png",
+						"spotlight@2x": "unpackage/res/icons/80x80.png",
+						"spotlight@3x": "unpackage/res/icons/120x120.png"
+					}
+				}
+			},
+			"splashscreen": {
+				"androidStyle": "common"
+			}
+		}
+	},
+	/* 快应用特有相关 */
+	"quickapp": {},
+	/* 小程序特有相关 */
+	"mp-weixin": {
+		"appid": "wx9e0c3e6ab2ede1c9",
+		"setting": {
+			"urlCheck": false,
+			"es6": true,
+			"minified": true
+		},
+		"usingComponents": true,
+		"permission": {
+			"scope.userLocation": {
+				"desc": "你的位置信息将用于小程序位置接口的效果展示"
+			}
+		},
+		"requiredPrivateInfos": ["getLocation"]
+	},
+	"mp-alipay": {
+		"usingComponents": true
+	},
+	"mp-baidu": {
+		"usingComponents": true
+	},
+	"mp-toutiao": {
+		"usingComponents": true
+	},
+	"uniStatistics": {
+		"enable": false
+	},
+	"h5": {
+		"devServer": {
+			"https": false
+		},
+		"router": {
+			"base": "",
+			"mode": "hash"
+		},
+		"title": "闽南魂",
+		"optimization": {
+			"treeShaking": {
+				"enable": true
+			}
+		},
+		"domain": "http://demo.usiyi.com/discover_h5/",
+		"sdkConfigs": {
+			"maps": {
+				"qqmap": {
+					"key": "FHEBZ-HYVNS-KGHOD-65O3N-O32P3-5FBI6"
+				}
+			}
+		}
+	},
+	"vueVersion": "2"
+}

+ 14 - 0
mixins/auth.js

@@ -0,0 +1,14 @@
+import {
+	silentReload
+} from '@/service/request/main.js'
+
+export default {
+	onLoad(options) {
+		console.log('onload mixin auth')
+		if (!this.$store.getters.accessToken) {
+			console.info('needLogin onload')
+			console.info('needLogin onload', 'silentReload')
+			silentReload()
+		}
+	}
+}

+ 87 - 0
mixins/common.js

@@ -0,0 +1,87 @@
+import {
+	buildPageUrl,
+	formatDate
+} from '../common/util.js'
+
+export default {
+	data() {
+		const pages = getCurrentPages()
+		console.log(pages);
+		const PageTitle = '闽南魂';
+		if (pages.length > 0) {
+			const currentPage = pages[pages.length - 1]
+			const currentPageUrl = buildPageUrl(currentPage.route, currentPage.options)
+			return {
+				PageTitle,
+				currentPage,
+				currentPageUrl
+			}
+		} else {
+			return {
+				PageTitle
+			}
+		}
+
+	},
+	onLoad(options) {
+		console.log('page onLoad', this.currentPageUrl);
+	},
+	methods: {
+		onCopy(text) {
+			uni.setClipboardData({
+				data: text + ''
+			}).then(([err, res]) => {
+				console.log('uni.setClipboardData', err, res);
+				if (!err) {
+					uni.showToast({
+						title: '复制成功',
+						icon: 'none',
+					});
+				}
+			});
+		}
+	},
+	computed: {
+		costTime() {
+			return (seconds, type) => {
+				let second = parseInt(seconds)
+				if (second <= 0) {
+					if (type === 'countdown') {
+						return '已结束'
+					}
+					return '0秒'
+				}
+				let minute = 0 // 初始化分
+				let hour = 0 // 初始化小时
+				if (second >= 60) { // 如果秒数大于等于60,将秒数转换成分钟
+					minute = parseInt(second / 60) // 获取分钟
+					second = parseInt(second % 60) // 获取秒数
+					if (minute >= 60) { // 如果分钟大于等于60,将分钟转换成小时
+						hour = parseInt(minute / 60) // 获取小时
+						minute = parseInt(minute % 60) // 获取小时后取余的分
+					}
+				}
+				if (type === 'countdown') {
+					return `${hour.toString().padStart(2,'0')}:${minute.toString().padStart(2,'0')}:${second.toString().padStart(2,'0')}`
+				}
+				let desc = ''
+				if (hour > 0) {
+					desc = hour + '小时'
+				}
+				if (minute > 0 || hour > 0) {
+					desc += minute + '分'
+				}
+				desc += second + '秒'
+				return desc
+			}
+		},
+		timeFormat() {
+			return (timestamp) => {
+				if (!timestamp) {
+					return ''
+				}
+				return formatDate(timestamp, 'yyyy-MM-dd hh:mm:ss')
+			}
+		},
+	},
+}

+ 30 - 0
node_modules/jweixin-module/README.md

@@ -0,0 +1,30 @@
+# jweixin-module
+
+微信JS-SDK
+
+## 安装
+
+### NPM
+
+```shell
+npm install jweixin-module --save
+```
+
+### UMD
+
+```http
+https://unpkg.com/jweixin-module/out/index.js
+```
+
+## 使用
+
+```js
+var jweixin = require('jweixin-module')
+jweixin.ready(function(){
+    // TODO
+});
+```
+
+## 完整API
+
+>[微信JS-SDK说明文档](https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115)

File diff suppressed because it is too large
+ 1 - 0
node_modules/jweixin-module/lib/index.js


+ 26 - 0
node_modules/jweixin-module/package.json

@@ -0,0 +1,26 @@
+{
+  "name": "jweixin-module",
+  "version": "1.6.0",
+  "description": "微信JS-SDK",
+  "main": "lib/index.js",
+  "scripts": {},
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/zhetengbiji/jweixin-module.git"
+  },
+  "keywords": [
+    "wxjssdk",
+    "weixin",
+    "jweixin",
+    "wechat",
+    "jssdk",
+    "wx"
+  ],
+  "author": "Shengqiang Guo",
+  "license": "ISC",
+  "bugs": {
+    "url": "https://github.com/zhetengbiji/jweixin-module/issues"
+  },
+  "homepage": "https://github.com/zhetengbiji/jweixin-module#readme",
+  "devDependencies": {}
+}

+ 166 - 0
pages.json

@@ -0,0 +1,166 @@
+{
+	// "easycom": {
+	// 	"custom": {
+	// 		"autoscan": true,
+	// 		"gui-(.*)": "@/GraceUI5/components/gui-$1.vue",
+	// 		"^u-(.*)": "@/uni_modules/uview-ui/components/u-$1/u-$1.vue"
+	// 	}
+	// },
+
+	"pages": [{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarTitleText": "",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/huicui/huicui",
+			"style": {
+				"navigationBarTitleText": "",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom"
+			}
+		},
+
+		{
+			"path": "pages/jiyi/jiyi",
+			"style": {
+				"navigationBarTitleText": "",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/user/index",
+			"style": {
+				"navigationBarTitleText": "",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom"
+			}
+		},
+
+		{
+			"path": "pages/zhenxing/zhenxing",
+			"style": {
+				"navigationBarTitleText": "",
+				"enablePullDownRefresh": false
+			}
+
+		},
+		{
+			"path": "pages/user/login",
+			"style": {
+				"navigationBarTitleText": "",
+				"enablePullDownRefresh": false
+			}
+
+		}
+
+	],
+
+	//分包
+	"subPackages": [{
+			"root": "index_fenbao",
+			"pages": [
+
+				{
+					"path": "faBuWenZhang/faBuWenZhang",
+					"style": {
+						"navigationBarTitleText": "",
+						"enablePullDownRefresh": false
+					}
+
+				}, {
+					"path": "hongsewenhua/hongsewenhua",
+					"style": {
+						"navigationBarTitleText": "",
+						"enablePullDownRefresh": false
+					}
+
+				}
+			]
+		},
+		{
+			"root": "user_fenbao",
+			"pages": [{
+
+			}    ,{
+                    "path" : "jiFen/jiFen",
+                    "style" :                                                                                    
+                {
+                    "navigationBarTitleText": "",
+                    "enablePullDownRefresh": false
+                }
+                
+                }
+            ]
+		}
+	],
+
+	//分包预加载
+
+	"tabBar": {
+		"color": "#282828",
+		"selectedColor": "#379EEE",
+		"borderStyle": "white",
+		"backgroundColor": "#ffffff",
+		"custom": false, //关闭自定义tarBar
+		"list": [{
+				"pagePath": "pages/index/index",
+				"iconPath": "/static/image/home.png",
+				"selectedIconPath": "/static/image/home_a.png",
+				"text": "首页"
+			},
+			{
+				"pagePath": "pages/huicui/huicui",
+				"iconPath": "/static/image/hc.png",
+				"selectedIconPath": "/static/image/hc_a.png",
+				"text": "荟萃"
+
+			},
+
+			{
+				"pagePath": "pages/jiyi/jiyi",
+				"iconPath": "/static/image/jy.png",
+				"selectedIconPath": "/static/image/jy_a.png",
+				"text": "记忆"
+
+			},
+			{
+				"pagePath": "pages/zhenxing/zhenxing",
+				"iconPath": "/static/image/zx.png",
+				"selectedIconPath": "/static/image/invite.png",
+				"text": "振兴"
+
+			},
+			{
+				"pagePath": "pages/user/index",
+				"iconPath": "/static/image/mine.png",
+				"selectedIconPath": "/static/image/mine_a.png",
+				"text": "我的"
+
+			}
+
+		]
+	},
+	"globalStyle": {
+		"mp-alipay": {
+			/* 支付宝小程序特有相关 */
+			"transparentTitle": "always",
+			"allowsBounceVertical": "NO"
+		},
+		"navigationBarBackgroundColor": "#ffffff",
+		"navigationBarTitleText": "",
+		"navigationStyle": "custom",
+		"navigationBarTextStyle": "black"
+	},
+	"usingComponts": true,
+	"condition": { //模式配置,仅开发期间生效
+		"current": 0, //当前激活的模式(list 的索引项)
+		"list": []
+	},
+	"sitemapLocation": "sitemap.json"
+
+}

+ 337 - 0
pages/huicui/huicui.vue

@@ -0,0 +1,337 @@
+<template>
+	<!-- 村史页面 -->
+	<view class="body">
+		<view class="cs_nabBar" style="background-image: url('https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_jiyi_bj.png'); background-size: cover">
+			<u-navbar bgColor="rgba(255,255,255,0)" :placeholder="true" :leftIconSize="0" titleStyle="font-weight:bold;color:#7a5831">
+				<view class="u-nav-slot" slot="left">
+					<view class="an" style="width: 500rpx; margin-top: -12rpx">
+						<u-search :showAction="false"></u-search>
+					</view>
+				</view>
+			</u-navbar>
+		</view>
+		<view class="block_1">
+			<view class="cs_box_1">
+				<u-swiper
+					:list="bannerlist"
+					imgMode="aspectFill"
+					:height="120"
+					:indicator="true"
+					:autoplay="true"
+					:circular="true"
+					indicatorStyle="bottom: 10px"
+					@click="bannerClick"
+					indicatorMode="dot"
+					indicatorActiveColor="#fff"
+					indicatorInactiveColor="rgba(255, 255, 255, 0.35)"
+				></u-swiper>
+
+				<view class="group_2">
+					<view class="group_10">
+						<view class="box_3" v-for="item in hcCdList" :key="item">
+							<image style="width: 100%; height: 100%" :src="item.img"></image>
+							<text lines="1" class="cs_text_2">{{ item.title }}</text>
+						</view>
+					</view>
+				</view>
+			</view>
+			<view class="text-wrapper_9">
+				<u-tabs
+					:list="[
+						{ name: '广场', tabid: 1 },
+						{ name: '发现', tabid: 2 }
+					]"
+					lineColor="#4E7198"
+					:activeStyle="{
+						color: '#4E7198',
+						fontWeight: 'bold'
+					}"
+				></u-tabs>
+			</view>
+			<view class="group_11">
+				<view class="text-wrapper_3">
+					<text lines="1" class="text_8">文章</text>
+				</view>
+				<view class="cs_text-wrapper_4">
+					<text lines="1" class="text_9">图片</text>
+				</view>
+				<view class="text-wrapper_5">
+					<text lines="1" class="text_10">视频</text>
+				</view>
+			</view>
+			<view class="cs_group_12">
+				<!-- 瀑布流 -->
+
+				<custom-waterfalls-flow :value="list" style="width: 90%; margin: auto">
+					<view class="item" style="position: relative" v-for="(item, index) in list" :key="index" slot="slot{{index}}">
+						<view class="title_pubu">
+							{{ item.title }}
+						</view>
+						<!-- <view class="desc">{{ item.desc }}</view> -->
+					</view>
+				</custom-waterfalls-flow>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	onLoad(option) {
+		that = this;
+		this.getBannerList();
+	},
+	data() {
+		return {
+			bannerlist: ['https://feicheng.16fw.cn:1443/uploads/20230423/55f2ea70baa2e7ce617f939b70026472.jpg'],
+			// 荟萃菜单
+			hcCdList: [
+				{
+					title: '家乡味道',
+					img: 'https://feicheng.16fw.cn:1443/uploads/20230418/0392ac25c1668beefa612aa646bf9d46.png'
+				},
+				{
+					title: '文化广场',
+					img: 'https://feicheng.16fw.cn:1443/uploads/20230418/e269122cbba9db7b72ed4a1b27b880c5.png'
+				},
+
+				{
+					title: '村中趣事',
+					img: 'https://feicheng.16fw.cn:1443/uploads/20230423/227e4cab316a37d3b6c8c367590b71b3.png'
+				},
+
+				{
+					title: '健身广场',
+					img: 'https://feicheng.16fw.cn:1443/uploads/20230423/cd4e633ce0a68900dd46e1160c93a784.png'
+				}
+			],
+			list: [
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/mn_jy1.png',
+					title: '传统捏泥人',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/mn_jy2.png',
+					title: '青龙宫',
+					desc: '描述描述描述描述描述描述描述描述1'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/mn_jy3.png',
+					title: '火把节',
+					desc: '11'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/mn_jy9.png',
+					title: '老物件',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/mn_jy4.png',
+					title: '林后青龙宫',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/mn_jy10.png',
+					title: '书籍',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/mn_jy7.png',
+					title: '建筑',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/mn_jy8.png',
+					title: '老钟表',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/mn_jy14.png',
+					title: '老物件',
+					desc: '描述描述描述描述描述描述描述描述2'
+				}
+			]
+		};
+	},
+	methods: {
+		getBannerList() {
+			this.$api.getBannerList({ block: 'home' }, function (res) {
+				// console.log(res);
+				if (res.code > 0) {
+					that.bannerlist = [];
+					for (var i = 0; i < res.data.length; i++) {
+						if (res.data[i].type == 'banner') {
+							that.bannerlist.push({
+								title: res.data[i].title,
+								url: that.$config.baseUrl + res.data[i].image,
+								page: res.data[i].url
+							});
+						}
+					}
+				}
+			});
+		},
+		bannerClick(index) {
+			console.log(index);
+			this.$common.navigateTo(this.bannerlist[index].page);
+		}
+	}
+};
+</script>
+
+<style>
+/* 村史 */
+.u-navbar__content {
+	background-color: transparent;
+}
+.block_1 {
+	position: relative;
+	background-size: 100% 100%;
+	display: flex;
+	flex-direction: column;
+}
+.cs_box_1 {
+	width: 90%;
+	margin: auto;
+	margin-top: -525rpx;
+	display: flex;
+	flex-direction: column;
+}
+/deep/.u-swiper-indicator__wrapper__dot--active {
+	width: 5px !important;
+}
+.cs_nabBar {
+	height: 700rpx;
+}
+.title_pubu {
+	width: 100%;
+	height: 50rpx;
+	/* background-color: rgba(202, 0, 0, 0.3); */
+	background-color: rgba(60, 213, 243, 0.3);
+	font-size: 30rpx;
+	color: #ffffff;
+	position: absolute;
+	bottom: 0;
+	left: 0;
+}
+.group_2 {
+	border-radius: 20rpx;
+	background-color: #ffffff;
+	display: flex;
+	flex-direction: column;
+	justify-content: flex-center;
+	margin-top: 20rpx;
+}
+.group_10 {
+	width: 599rpx;
+	height: 108rpx;
+	flex-direction: row;
+	display: flex;
+	justify-content: space-between;
+	margin: 28rpx 0 60rpx 44rpx;
+}
+.box_3 {
+	/* background-color: rgba(255, 97, 95, 1); */
+	border-radius: 48rpx;
+	width: 108rpx;
+	height: 108rpx;
+}
+
+.cs_text_2 {
+	width: 94rpx;
+	height: 23rpx;
+	margin-top: 13rpx;
+	color: rgba(102, 102, 102, 1);
+	font-size: 24rpx;
+	white-space: nowrap;
+	line-height: 24rpx;
+}
+.text-wrapper_9 {
+	width: 250rpx;
+	margin: 31rpx 0 0 250rpx;
+}
+.group_11 {
+	width: 620rpx;
+	height: 53rpx;
+	flex-direction: row;
+	display: flex;
+	justify-content: space-between;
+	margin: 17rpx 0 0 65rpx;
+}
+.text-wrapper_3 {
+	background-color: rgba(60, 213, 243, 1);
+	border-radius: 26rpx;
+	height: 53rpx;
+	display: flex;
+	flex-direction: column;
+	width: 180rpx;
+}
+.text_8 {
+	width: 58rpx;
+	height: 29rpx;
+	overflow-wrap: break-word;
+	color: rgba(255, 255, 255, 1);
+	font-size: 30rpx;
+	font-family: SourceHanSansCN-Regular;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 30rpx;
+	margin: 12rpx 0 0 61rpx;
+}
+.cs_text-wrapper_4 {
+	background-color: rgba(78, 113, 152, 1);
+	border-radius: 26rpx;
+	height: 53rpx;
+	margin-left: 40rpx;
+	display: flex;
+	flex-direction: column;
+	width: 180rpx;
+}
+.text_9 {
+	width: 55rpx;
+	height: 28rpx;
+	overflow-wrap: break-word;
+	color: rgba(255, 255, 255, 1);
+	font-size: 30rpx;
+	font-family: SourceHanSansCN-Regular;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 30rpx;
+	margin: 13rpx 0 0 63rpx;
+}
+.text-wrapper_5 {
+	background-color: rgba(78, 113, 152, 1);
+	border-radius: 26rpx;
+	height: 53rpx;
+	margin-left: 40rpx;
+	display: flex;
+	flex-direction: column;
+	width: 180rpx;
+}
+.text_10 {
+	width: 59rpx;
+	height: 28rpx;
+	overflow-wrap: break-word;
+	color: rgba(255, 255, 255, 1);
+	font-size: 30rpx;
+	font-family: SourceHanSansCN-Regular;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 30rpx;
+	margin: 13rpx 0 0 61rpx;
+}
+.cs_group_12 {
+	width: 100%;
+	flex-direction: row;
+	display: flex;
+	justify-content: space-between;
+	margin-top: 50rpx;
+}
+</style>

+ 378 - 0
pages/index/index.vue

@@ -0,0 +1,378 @@
+<template>
+	<!-- 村史页面 -->
+	<view class="body" v-else>
+		<u-navbar :placeholder="true" bgColor="rgba(195, 232, 249)" :leftIconSize="0" titleStyle="font-weight:bold;color:#7a5831">
+			<view class="u-nav-slot" slot="left" style="display: flex; align-items: center">
+				<view class="an" style="width: 400rpx; margin-left: 50rpx; margin-top: -40rpx; line-height: 40rpx">
+					<u-notice-bar text="系统消息提示"></u-notice-bar>
+				</view>
+			</view>
+		</u-navbar>
+		<view class="block_2">
+			<view class="box_16">
+				<u-swiper
+					:list="cs_swiperList"
+					imgMode="aspectFill"
+					:height="210"
+					showTitle
+					:indicator="true"
+					:autoplay="true"
+					:circular="true"
+					@click="bannerClick"
+					indicatorMode="dot"
+					indicatorActiveColor="#fff"
+					indicatorInactiveColor="rgba(255, 7, 23, 0.3)"
+				></u-swiper>
+			</view>
+			<view
+				class="section_5"
+				style="
+					background-image: url('https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_bj.png');
+
+					background-size: 100% 100%;
+				"
+			>
+				<view class="group_3">
+					<view class="list_7">
+						<view class="image-text_20-0" @click="cs_caiDan(index)" v-for="(item, index) in menuList" :key="item">
+							<image style="width: 100%" :src="item.img"></image>
+							<text lines="1" class="text-group_2-0">{{ item.name }}</text>
+						</view>
+					</view>
+				</view>
+				<view class="text-wrapper_13">
+					<text lines="1" class="text_7">新鲜事</text>
+					<text lines="1" class="text_8">查看更多》</text>
+				</view>
+
+				<view class="banxin2">
+					<view class="cs_box_22" v-for="item in xinXianList" :key="item">
+						<view class="cs_block_5">
+							<image style="width: 100%; height: 100%; border-radius: 0 50rpx 0 20rpx" :src="item.img"></image>
+
+							<view class="tag_1">
+								<text lines="1" class="text_9">{{ item.title }}</text>
+							</view>
+
+							<view class="tag_3">
+								<!-- <text lines="1" class="text_10">红色文化活动</text> -->
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	components: {},
+	onLoad(option) {
+		that = this;
+		this.getBannerList();
+	},
+	data() {
+		return {
+			bannerlist: ['https://feicheng.16fw.cn:1443/uploads/20230423/55f2ea70baa2e7ce617f939b70026472.jpg'],
+			cs_swiperList: [
+				'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_sy_lbt1.png',
+				'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_sy_lbt2.png',
+				'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_sy_lbt3.png',
+				'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_sy_lbt4.png'
+			],
+			menuList: [
+				{
+					name: '红色文化',
+					img: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cd_tp1.png',
+					path: '/index_fenbao/hongsewenhua/hongsewenhua'
+				},
+				{
+					name: '乡愁乡音',
+					img: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cd_tp2.png'
+				},
+				{
+					name: '民俗文化',
+					img: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cd_tp3.png'
+				},
+				{
+					name: '认主归宗',
+					img: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cd_tp4.png'
+				},
+				{
+					name: '乡村振兴',
+					img: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cd_tp5.png'
+				},
+				{
+					name: '智慧助老',
+					img: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cd_tp6.png'
+				},
+
+				{
+					name: '全民运动',
+					img: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cd_tp7.png'
+				},
+
+				{
+					name: '村有圈',
+					img: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cd_tp8.png',
+					path: '/index_fenbao/faBuWenZhang/faBuWenZhang'
+				}
+			],
+			xinXianList: [
+				{
+					img: 'https://p5.itc.cn/q_70/images03/20211006/36f4a86801494fec87b552ba660c44bd.jpeg',
+					title: '每日趣事'
+				},
+				{
+					img: 'https://pic.rmb.bdstatic.com/bjh/news/bf871bc8d10a1430e7be489e88dea5cc.jpeg',
+					title: '全民健身'
+				}
+			]
+		};
+	},
+	/* 页面触底 */
+	onReachBottom() {
+		console.log('触底了');
+	},
+	methods: {
+		// 菜单
+		cs_caiDan(index) {
+			uni.navigateTo({
+				url: this.menuList[index].path
+			});
+		},
+		getBannerList() {
+			this.$api.getBannerList({ block: 'home' }, function (res) {
+				// console.log(res);
+				if (res.code > 0) {
+					that.bannerlist = [];
+					for (var i = 0; i < res.data.length; i++) {
+						if (res.data[i].type == 'banner') {
+							that.bannerlist.push({
+								title: res.data[i].title,
+								url: that.$config.baseUrl + res.data[i].image,
+								page: res.data[i].url
+							});
+						}
+					}
+				}
+			});
+		},
+		bannerClick(index) {
+			console.log(index);
+			this.$common.navigateTo(this.bannerlist[index].page);
+		}
+	}
+};
+</script>
+
+<style>
+/deep/.u-swiper-indicator__wrapper__dot--active {
+	width: 5px !important;
+}
+.box {
+	height: auto;
+	width: 100%;
+
+	background-attachment: fixed;
+	border-radius: 16px 16px 0 0;
+	position: relative;
+	top: -20px;
+	padding-top: 10rpx;
+}
+/deep/.u-icon__icon.data-v-2ee87dc9 {
+	top: -7rpx !important;
+	left: 0 !important;
+}
+.u-swiper__wrapper__item__wrapper__title {
+	height: 80rpx !important;
+	font-size: 48rpx !important;
+}
+.u-swiper__indicator {
+	z-index: 2 !important;
+}
+.u-notice-bar {
+	height: 60rpx;
+	background-color: #ffffff;
+	border-radius: 50rpx;
+}
+.u-notice__content {
+	height: 40rpx;
+	line-height: 24rpx;
+}
+.banxin {
+	width: 100%;
+	height: 100rpx;
+	margin: auto;
+	background-color: yellowgreen;
+}
+.banxin2 {
+	width: 90%;
+	margin: auto;
+}
+.u-icon__icon {
+	top: -10rpx !important;
+	left: 0 !important;
+	margin-left: 10rpx;
+}
+.u-nav-slot {
+	margin-top: 30rpx;
+}
+.cs_box_22 {
+	height: 345rpx;
+	display: flex;
+	flex-direction: column;
+	width: 686rpx;
+	margin-bottom: 50rpx;
+}
+.cs_block_5 {
+	position: relative;
+	height: 345rpx;
+	width: 686rpx;
+	display: flex;
+	flex-direction: column;
+}
+
+.tag_1 {
+	position: absolute;
+	background-color: rgba(77, 113, 153, 1);
+	border-radius: 9rpx 9rpx 32rpx 0rpx;
+	height: 48rpx;
+	display: flex;
+	flex-direction: column;
+	width: 145rpx;
+}
+.text_9 {
+	width: 47rpx;
+	height: 23rpx;
+	overflow-wrap: break-word;
+	color: rgba(255, 255, 255, 1);
+	font-size: 24rpx;
+	font-family: SourceHanSansCN-Bold;
+	font-weight: 700;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 24rpx;
+	margin: 12rpx 0 0 19rpx;
+}
+.tag_2 {
+	position: absolute;
+	bottom: 36rpx;
+	right: 20rpx;
+}
+.text_10 {
+	width: 147rpx;
+	height: 29rpx;
+	overflow-wrap: break-word;
+	color: rgba(255, 255, 255, 1);
+	font-size: 30rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 30rpx;
+}
+.block_2 {
+	width: 750rpx;
+	background-size: 100% 100%;
+	display: flex;
+	flex-direction: column;
+	justify-content: flex-end;
+}
+
+.box_16 {
+	width: 750rpx;
+	height: 420rpx;
+	display: flex;
+	flex-direction: column;
+}
+
+.section_5 {
+	width: 750rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: flex-end;
+}
+.group_3 {
+	background-color: rgba(255, 255, 255, 0.9);
+	border-radius: 32rpx;
+	position: relative;
+	width: 687rpx;
+	display: flex;
+	flex-direction: column;
+	margin: 40rpx 0 0 31rpx;
+}
+.list_7 {
+	display: flex;
+	flex-flow: wrap;
+	display: flex;
+	margin: 30rpx 0 0 31rpx;
+}
+.image-text_20-0 {
+	width: 130rpx;
+	height: 150rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+	margin: 1rpx 34rpx 20rpx 0;
+}
+
+.text-group_2-0 {
+	width: 94rpx;
+	height: 23rpx;
+	overflow-wrap: break-word;
+	color: black;
+	font-size: 24rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: center;
+	white-space: nowrap;
+	line-height: 24rpx;
+	margin: 7rpx 0 0 17rpx;
+}
+
+.text-wrapper_13 {
+	width: 669rpx;
+	height: 34rpx;
+	flex-direction: row;
+	display: flex;
+	justify-content: space-between;
+	margin: 36rpx 0 32rpx 49rpx;
+}
+.text_7 {
+	width: 105rpx;
+	height: 34rpx;
+	overflow-wrap: break-word;
+	color: rgba(49, 37, 32, 1);
+	font-size: 36rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 36rpx;
+}
+.text_8 {
+	width: 108rpx;
+	height: 23rpx;
+	overflow-wrap: break-word;
+	color: rgba(102, 102, 102, 1);
+	font-size: 24rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 24rpx;
+	margin-top: 7rpx;
+}
+
+.tag_3 {
+	position: absolute;
+	right: 80rpx;
+	bottom: 10rpx;
+	height: 48rpx;
+	display: flex;
+	flex-direction: column;
+}
+</style>

+ 404 - 0
pages/jiyi/jiyi.vue

@@ -0,0 +1,404 @@
+<template>
+	<!-- 村史页面 -->
+	<view class="body">
+		<view class="cs_nabBar" style="background-image: url('https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_wj_bj.png'); background-size: cover">
+			<u-navbar title="记忆" :placeholder="true" bgColor="rgba(255,255,255,0)" :leftIconSize="0" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		</view>
+		<view class="block_1">
+			<view class="section_1">
+				<view class="image-wrapper_2" style="background-image: url('https://huli-app.wenlvti.net/app_static/minnanhun/image/jp.png'); background-size: 100% 100%">
+					<!-- 轮播图 -->
+					<view class="" style="width: 90%; margin: auto; margin-top: 50rpx">
+						<u-swiper
+							:list="bannerlist"
+							imgMode="aspectFill"
+							:indicator="true"
+							:autoplay="true"
+							:circular="true"
+							indicatorStyle="bottom: 10px"
+							@click="bannerClick"
+							indicatorMode="dot"
+							indicatorActiveColor="#fff"
+							indicatorInactiveColor="rgba(255, 255, 255, 0.35)"
+						></u-swiper>
+					</view>
+				</view>
+				<view class="box_2">
+					<view class="cs_text-wrapper_1">
+						<!-- <text lines="1" class="cs_text_2">文物博览</text> -->
+						<text lines="1" class="cs_text_2">经典推荐</text>
+						<text lines="1" class="cs_text_3">查看更多》</text>
+					</view>
+				</view>
+			</view>
+			<view class="cs_group_12">
+				<!-- 瀑布流 -->
+
+				<custom-waterfalls-flow :value="csList" style="width: 90%; margin: auto">
+					<view class="item" style="position: relative" v-for="(item, index) in csList" :key="index" slot="slot{{index}}">
+						<view class="title_pubu">
+							{{ item.title }}
+						</view>
+						<!-- <view class="desc">{{ item.desc }}</view> -->
+					</view>
+				</custom-waterfalls-flow>
+			</view>
+			<!-- 	<view class="section_4">
+				<view class="box_7" v-for="item in 4" :key="item">
+					<view class="block_2">
+						<view class="text-group_3">
+							<text lines="1" class="cs_text_12">五代闽国铜鎏金王延翰狮子炉</text>
+							<text lines="1" class="cs_text_13">为五代闽国有铭文铸器之孤件</text>
+						</view>
+						<view class="text-wrapper_6">
+							<text lines="1" class="text_14">前往查看</text>
+						</view>
+					</view>
+					<view class="cs_block_3">
+						<image style="width: 100%; height: 100%" src="../../static/image/圆角矩形 840 拷贝.png"></image>
+					</view>
+				</view>
+			</view> -->
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+
+export default {
+	components: {},
+	data() {
+		return {
+			bannerlist: [
+				'https://huli-app.wenlvti.net/app_static/minnanhun/image/sy_lbt4.jpg',
+				'https://huli-app.wenlvti.net/app_static/minnanhun/image/sy_lbt5.png',
+				'https://huli-app.wenlvti.net/app_static/minnanhun/image/sy_lbt2.png',
+				'https://huli-app.wenlvti.net/app_static/minnanhun/image/sy_lbt1.png',
+				'https://huli-app.wenlvti.net/app_static/minnanhun/image/sy_lbt3.jpg'
+			],
+			csList: [
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_jy2.png',
+					title: '厦门老火车站',
+					desc: '描述描述描述描述描述描述描述描述1'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_jy3.png',
+					title: '海堤工程师总工程师殷孝友',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_jy1.png',
+					title: '花木兰',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_jy4.png',
+					title: '国务院为殷孝友颁发的荣誉证书',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_jy5.png',
+					title: '人民英雄',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_jy6.png',
+					title: '高崎山头日夜保卫高集海堤堤线安全',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_jy7.png',
+					title: '移山填海',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_jy8.png',
+					title: '古宅',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_jy9.png',
+					title: '厦门老地图',
+					desc: '描述描述描述描述描述描述描述描述2'
+				}
+			]
+		};
+	},
+	onLoad(option) {
+		that = this;
+		this.getBannerList();
+	},
+	methods: {
+		getBannerList() {
+			this.$api.getBannerList({ block: 'home' }, function (res) {
+				// console.log(res);
+				if (res.code > 0) {
+					that.bannerlist = [];
+					for (var i = 0; i < res.data.length; i++) {
+						if (res.data[i].type == 'banner') {
+							that.bannerlist.push({
+								title: res.data[i].title,
+								url: that.$config.baseUrl + res.data[i].image,
+								page: res.data[i].url
+							});
+						}
+					}
+				}
+			});
+		},
+		bannerClick(index) {
+			console.log(index);
+			this.$common.navigateTo(this.bannerlist[index].page);
+		}
+	}
+};
+</script>
+
+<style>
+.body {
+	/* padding-top: 20px; */
+	/* background-image: url('https://feicheng.16fw.cn:1443/uploads/20230512/88f8347924d08296bbde9d55afd74c15.jpg');
+	background-size: 100% 100%; */
+	background-attachment: fixed;
+}
+.nabBar {
+	height: 500rpx;
+}
+.u-status-bar {
+	opacity: 0 !important;
+}
+.u-navbar__content.data-v-95dec1ae {
+	background-color: transparent !important;
+}
+/deep/.u-swiper-indicator__wrapper__dot--active {
+	width: 5px !important;
+}
+.title_pubu {
+	width: 100%;
+	height: 50rpx;
+	background-color: rgba(202, 0, 0, 0.3);
+	font-size: 30rpx;
+	color: #ffffff;
+	position: absolute;
+	bottom: 0;
+	left: 0;
+}
+.cs_group_12 {
+	width: 100%;
+	flex-direction: row;
+	display: flex;
+	justify-content: space-between;
+	margin-top: 50rpx;
+}
+.block_1 {
+	width: 750rpx;
+	background-size: 100% 100%;
+	display: flex;
+	flex-direction: column;
+	padding-bottom: 20rpx;
+	background-color: #f8f8f8;
+}
+.image-wrapper_2 {
+	width: 750rpx;
+	height: 350rpx;
+}
+.section_1 {
+	position: relative;
+	width: 750rpx;
+	display: flex;
+	flex-direction: column;
+	background-color: #f8f8f8;
+	border-radius: 30rpx 30rpx 0 0;
+}
+
+.box_1 {
+	width: 400rpx;
+	height: 56rpx;
+	flex-direction: row;
+	display: flex;
+	justify-content: space-between;
+	margin: 17rpx 0 0 321rpx;
+}
+.text_1 {
+	width: 69rpx;
+	height: 34rpx;
+	overflow-wrap: break-word;
+	color: rgba(18, 18, 18, 1);
+	font-size: 36rpx;
+	font-family: SourceHanSansCN-Regular;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 36rpx;
+	margin-top: 11rpx;
+}
+.box_2 {
+	width: 749rpx;
+	display: flex;
+	flex-direction: column;
+	margin: 50rpx 0 0 0;
+	background-color: #cceef5;
+	border-radius: 30rpx 30rpx 0 0;
+}
+
+.cs_text-wrapper_1 {
+	width: 668rpx;
+	height: 34rpx;
+	flex-direction: row;
+	display: flex;
+	justify-content: space-between;
+	margin: 46rpx 0 15rpx 50rpx;
+}
+.cs_text_2 {
+	width: 140rpx;
+	height: 34rpx;
+	overflow-wrap: break-word;
+	color: rgba(49, 37, 32, 1);
+	font-size: 36rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 36rpx;
+}
+.cs_text_3 {
+	width: 108rpx;
+	height: 23rpx;
+	overflow-wrap: break-word;
+	color: rgba(102, 102, 102, 1);
+	font-size: 24rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 24rpx;
+	margin-top: 7rpx;
+}
+.text-wrapper_1 {
+	width: 668rpx;
+	height: 34rpx;
+	flex-direction: row;
+	display: flex;
+	justify-content: space-between;
+	margin: 46rpx 0 15rpx 50rpx;
+}
+.text_2 {
+	width: 140rpx;
+	height: 34rpx;
+	overflow-wrap: break-word;
+	color: rgba(49, 37, 32, 1);
+	font-size: 36rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 36rpx;
+}
+.text_3 {
+	width: 108rpx;
+	height: 23rpx;
+	overflow-wrap: break-word;
+	color: rgba(102, 102, 102, 1);
+	font-size: 24rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 24rpx;
+	margin-top: 7rpx;
+}
+
+.section_4 {
+	width: 686rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+	margin: 0 0 0 32rpx;
+}
+
+.box_7 {
+	background-color: rgba(255, 255, 255, 1);
+	border-radius: 10rpx;
+	width: 686rpx;
+	height: 170rpx;
+	margin-top: 20rpx;
+	flex-direction: row;
+	display: flex;
+}
+.block_2 {
+	width: 363rpx;
+	height: 122rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+	margin: 28rpx 0 0 19rpx;
+}
+.text-group_3 {
+	width: 361rpx;
+	height: 62rpx;
+	margin-left: 2rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+}
+.text_12 {
+	width: 361rpx;
+	height: 27rpx;
+	overflow-wrap: break-word;
+	color: rgba(49, 37, 32, 1);
+	font-size: 28rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 28rpx;
+}
+.text_13 {
+	width: 280rpx;
+	height: 22rpx;
+	overflow-wrap: break-word;
+	color: rgba(153, 153, 153, 1);
+	font-size: 22rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 22rpx;
+	margin-top: 13rpx;
+}
+.text-wrapper_6 {
+	background-color: rgba(60, 213, 243, 1);
+	border-radius: 10rpx;
+	height: 44rpx;
+	margin-top: 16rpx;
+	display: flex;
+	flex-direction: column;
+	width: 129rpx;
+}
+.text_14 {
+	width: 93rpx;
+	height: 23rpx;
+	overflow-wrap: break-word;
+	color: rgba(255, 255, 255, 1);
+	font-size: 24rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 24rpx;
+	margin: 11rpx 0 0 18rpx;
+}
+.block_3 {
+	background-color: rgba(255, 97, 95, 1);
+	border-radius: 10rpx;
+	width: 170rpx;
+	height: 170rpx;
+	display: flex;
+	flex-direction: column;
+	margin: 0 1rpx 0 133rpx;
+}
+</style>

+ 475 - 0
pages/user/index.vue

@@ -0,0 +1,475 @@
+<template>
+	<view class="body">
+		<view class="nabBar" style="background-image: url('/static/image/矩形 13.png'); background-size: cover">
+			<u-navbar :placeholder="true" :leftIconSize="0"></u-navbar>
+		</view>
+		<view class="section_1">
+			<view class="section_2">
+				<view class="box_20">
+					<view class="group_16" style="background-image: url('/static/image/组 129.png'); background-size: cover">
+						<image
+							src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/268e511f88af4703aeaf741b83b79569_mergeImage.png"
+							class="image_22"
+						></image>
+						<view class="text-group_15">
+							<text lines="1" class="text_2">白云下的棉絮</text>
+							<text lines="1" class="text_3">村民证信息</text>
+						</view>
+						<view class="section_3">
+							<view class="image-text_44">
+								<u-icon name="edit-pen" color="#FFFFFF"></u-icon>
+
+								<text lines="1" class="text-group_12">资料编辑</text>
+							</view>
+						</view>
+					</view>
+				</view>
+				<view class="box_21">
+					<view class="box_22">
+						<view class="image-wrapper_1" @click="jiFenBtn">
+							<image src="/static/image/jf.png" class="image_1"></image>
+						</view>
+						<view class="image-wrapper_2">
+							<image src="/static/image/tg.png" class="image_2"></image>
+						</view>
+					</view>
+				</view>
+				<view class="box_2">
+					<view class="group_17">
+						<view class="group_18">
+							<view class="box_23">
+								<view class="group_3">
+									<view class="image-text_34">
+										<image src="/static/image/余额.png" class="label_1"></image>
+										<text lines="1" class="text-group_1">我的收藏</text>
+									</view>
+									<uni-icons type="forward" size="24" color="#666666"></uni-icons>
+								</view>
+							</view>
+						</view>
+						<view class="fenge"></view>
+						<view class="group_19">
+							<view class="group_3">
+								<view class="image-text_35">
+									<image src="/static/image/账期(3).png" class="label_2"></image>
+									<text lines="1" class="text-group_2">邀请好友</text>
+								</view>
+								<uni-icons type="forward" size="24" color="#666666"></uni-icons>
+							</view>
+						</view>
+						<view class="fenge"></view>
+						<view class="group_20">
+							<view class="group_3">
+								<view class="image-text_35">
+									<image src="/static/image/账期 拷贝.png" class="label_2"></image>
+									<text lines="1" class="text-group_2">我的投稿</text>
+								</view>
+								<uni-icons type="forward" size="24" color="#666666"></uni-icons>
+							</view>
+						</view>
+						<view class="fenge"></view>
+						<view class="group_21">
+							<view class="group_3">
+								<view class="image-text_35">
+									<image src="/static/image/账期(1).png" class="label_2"></image>
+									<text lines="1" class="text-group_2">我的好友</text>
+								</view>
+								<uni-icons type="forward" size="24" color="#666666"></uni-icons>
+							</view>
+						</view>
+						<view class="fenge"></view>
+						<view class="group_22">
+							<view class="group_3">
+								<view class="image-text_35">
+									<image src="/static/image/账期(2).png" class="label_2"></image>
+									<text lines="1" class="text-group_2">积分日志</text>
+								</view>
+								<uni-icons type="forward" size="24" color="#666666"></uni-icons>
+							</view>
+						</view>
+						<view class="fenge"></view>
+						<view class="group_23">
+							<view class="group_3">
+								<view class="image-text_35">
+									<image src="/static/image/账期.png" class="label_2"></image>
+									<text lines="1" class="text-group_2">加入我们</text>
+								</view>
+								<uni-icons type="forward" size="24" color="#666666"></uni-icons>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+var _token = '';
+var _this;
+
+import { uploadImage } from '@/service/api/common.js';
+export default {
+	components: {},
+	data() {
+		return {
+			isLogin: false,
+			show: false,
+			username: '未登录',
+			face: '',
+			user: {
+				nickname: '未登录',
+				id: '未登录'
+			},
+			avatarUrl: '',
+			wxnickname: '',
+			items: [
+				[1, '', '荣誉', '/pages/user/medal'],
+				[0, '', '好友', '/pages/user/friends'],
+				[0, '', '积分', '/pages/user/scorelog'],
+				[0, '', '传承值', '']
+			],
+			getFace: false,
+			userdata: {
+				friendsLists: [],
+				friendsTotal: 0,
+				user: {
+					score: '0',
+					exp: '0'
+				}
+			}
+		};
+	},
+	onShow() {
+		/* 调用userInfo()从本地获取用户信息 */
+		this.user = this.$common.userInfo();
+
+		if (this.user && this.user.nickname.indexOf('微信') >= 0) {
+			this.getFace = true;
+		}
+	},
+	onLoad(option) {
+		/* 来到我的页面判断用户是否登录 */
+		_this = this;
+		this.user = this.$common.userInfo();
+		console.log(this.user);
+		// if (this.user === 'undefined' || this.user === '' || this.user === [] || this.user === null) {
+		// 	/* 跳转到登录页 */
+		// 	this.$common.toLogin();
+		// } else {
+		// 	/* 刷新 */
+		// 	this.$api.refreshUser({}, (val) => {
+		// 		console.log(val);
+		// 		if (val.code == 401) {
+		// 			this.$common.navigateTo('/pages/user/login');
+		// 			return;
+		// 		}
+
+		// 		this.user = val.data.user;
+		// 		this.avatarUrl = this.user.avatar;
+		// 		this.auth = val.data.auth;
+		// 		this.$db.set('user', val.data.user);
+		// 		this.$db.set('auth', val.data.auth);
+		// 		this.$api.getWuyuanUser({}, (val) => {
+		// 			console.log(val);
+		// 			this.userdata = val.data;
+		// 		});
+		// 	});
+		// 	this.isLogin = true;
+		// }
+	},
+	methods: {
+		jiFenBtn() {
+			uni.navigateTo({
+				url: '/user_fenbao/jiFen/jiFen'
+			});
+		}
+	}
+};
+</script>
+<style>
+.nabBar {
+	height: 500rpx;
+}
+.u-navbar--fixed.data-v-95dec1ae {
+	opacity: 0;
+}
+.uni-icons.data-v-a2e81f6e {
+	margin-right: 40rpx;
+	line-height: 100rpx;
+}
+.fenge {
+	width: 615rpx;
+	height: 1rpx;
+	margin: auto;
+	background: #ededed;
+}
+.section_1 {
+	display: flex;
+	flex-direction: column;
+	width: 750rpx;
+}
+.section_2 {
+	background-color: rgba(248, 248, 248, 1);
+	width: 749rpx;
+	display: flex;
+	flex-direction: column;
+}
+.box_21 {
+	width: 749rpx;
+	height: 200rpx;
+	/* margin-top: 65rpx; */
+	display: flex;
+	flex-direction: column;
+}
+.box_22 {
+	width: 683rpx;
+	height: 95rpx;
+	flex-direction: row;
+	display: flex;
+	justify-content: space-between;
+	margin: 65rpx 0 0 32rpx;
+}
+.image-wrapper_1 {
+	box-shadow: 8px 13px 27px 0px rgba(165, 165, 165, 0.39);
+	background-color: rgba(250, 245, 241, 1);
+	border-radius: 10rpx;
+	height: 95rpx;
+	display: flex;
+	flex-direction: column;
+	width: 330rpx;
+}
+.image_1 {
+	width: 330rpx;
+	height: 95rpx;
+}
+.image-wrapper_2 {
+	box-shadow: 8px 13px 27px 0px rgba(165, 165, 165, 0.39);
+	background-color: rgba(250, 245, 241, 1);
+	border-radius: 10rpx;
+	height: 95rpx;
+	display: flex;
+	flex-direction: column;
+	width: 330rpx;
+}
+.image_2 {
+	width: 330rpx;
+	height: 95rpx;
+}
+.box_2 {
+	background-color: rgba(255, 255, 255, 1);
+	border-radius: 20rpx;
+
+	display: flex;
+	flex-direction: column;
+	width: 690rpx;
+	margin: -1rpx 0 0 29rpx;
+}
+.group_17 {
+	width: 749rpx;
+
+	margin-left: -29rpx;
+	display: flex;
+	flex-direction: column;
+}
+.group_18 {
+	width: 749rpx;
+	height: 104rpx;
+	display: flex;
+	flex-direction: row;
+}
+.box_23 {
+	position: relative;
+	width: 749rpx;
+	height: 104rpx;
+	display: flex;
+	flex-direction: row;
+}
+
+.group_3 {
+	width: 749rpx;
+	height: 104rpx;
+	display: flex;
+	justify-content: space-between;
+}
+.image-text_34 {
+	width: 173rpx;
+	height: 44rpx;
+	flex-direction: row;
+	display: flex;
+	justify-content: space-between;
+	margin: 28rpx 0 0 53rpx;
+}
+.label_1 {
+	width: 44rpx;
+	height: 44rpx;
+}
+.text-group_1 {
+	width: 118rpx;
+	height: 29rpx;
+	overflow-wrap: break-word;
+	color: rgba(0, 0, 0, 1);
+	font-size: 30rpx;
+	font-family: SourceHanSansCN-Regular;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 30rpx;
+	margin-top: 9rpx;
+}
+
+.group_19 {
+	width: 749rpx;
+	height: 105rpx;
+	margin-top: 1rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+}
+.box_4 {
+	width: 749rpx;
+	height: 104rpx;
+	flex-direction: row;
+	display: flex;
+}
+.image-text_35 {
+	width: 174rpx;
+	height: 44rpx;
+	flex-direction: row;
+	display: flex;
+	justify-content: space-between;
+	margin: 28rpx 0 0 52rpx;
+}
+.label_2 {
+	width: 44rpx;
+	height: 44rpx;
+}
+.text-group_2 {
+	width: 118rpx;
+	height: 29rpx;
+	overflow-wrap: break-word;
+	color: rgba(0, 0, 0, 1);
+	font-size: 30rpx;
+	font-family: SourceHanSansCN-Regular;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 30rpx;
+	margin-top: 13rpx;
+}
+
+.group_20 {
+	width: 749rpx;
+	height: 105rpx;
+	margin-top: 1rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+}
+.group_21 {
+	width: 749rpx;
+	height: 106rpx;
+	margin-top: 1rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+}
+
+.group_22 {
+	width: 749rpx;
+	height: 109rpx;
+	margin-top: 1rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+}
+.group_23 {
+	width: 749rpx;
+	height: 104rpx;
+	margin-top: -3rpx;
+	display: flex;
+	flex-direction: row;
+}
+.box_20 {
+	display: flex;
+	flex-direction: column;
+	width: 679rpx;
+	position: absolute;
+	left: 35rpx;
+	top: 147rpx;
+	box-shadow: 8px 13px 27px 0px rgba(211, 245, 251);
+	background-color: rgba(211, 245, 251);
+}
+.group_16 {
+	width: 679rpx;
+	height: 388rpx;
+	flex-direction: row;
+	display: flex;
+}
+.image_22 {
+	width: 120rpx;
+	height: 120rpx;
+	margin: 52rpx 0 0 39rpx;
+}
+.text-group_15 {
+	width: 243rpx;
+	height: 82rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+	margin: 70rpx 0 0 22rpx;
+}
+.text_2 {
+	width: 243rpx;
+	height: 40rpx;
+	overflow-wrap: break-word;
+	color: rgba(255, 255, 255, 1);
+	font-size: 42rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 42rpx;
+}
+.text_3 {
+	width: 120rpx;
+	height: 23rpx;
+	overflow-wrap: break-word;
+	color: rgba(255, 255, 255, 1);
+	font-size: 24rpx;
+	font-family: Adobe Heiti Std R;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 24rpx;
+	margin-top: 19rpx;
+}
+.section_3 {
+	width: 176rpx;
+	height: 52rpx;
+	background-color: #dfc09f;
+	border-radius: 30rpx 0 0 30rpx;
+	display: flex;
+	flex-direction: row;
+	margin: 69rpx 1rpx 0 82rpx;
+}
+.image-text_44 {
+	width: 127rpx;
+	height: 26rpx;
+	flex-direction: row;
+	display: flex;
+	justify-content: space-between;
+	margin: 11rpx 0 0 30rpx;
+}
+
+.text-group_12 {
+	width: 95rpx;
+	height: 22rpx;
+	overflow-wrap: break-word;
+	color: rgba(255, 255, 255, 1);
+	font-size: 24rpx;
+	white-space: nowrap;
+	line-height: 24rpx;
+	margin-top: 3rpx;
+	margin-left: 5rpx;
+}
+</style>

+ 418 - 0
pages/user/login.vue

@@ -0,0 +1,418 @@
+<template>
+	<view class="body">
+		<u-navbar title="登录" bgColor="rgba(255,255,255,0.3)" :leftIconSize="0" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<block>
+			<!--  #ifndef  MP-WEIXIN	 -->
+			<view class="login-bg">
+				<view class="login-card">
+					<view class="login-head">
+						<image class="logoimg" :src="baseLogo" mode="widthFix"></image>
+					</view>
+					<view class="login-input login-margin-b">
+						<input type="number" v-model="username" placeholder="请输入手机号" />
+					</view>
+					<view class="login-input">
+						<input :password="true" v-model="password" placeholder="请输入密码(6-16位)" />
+					</view>
+					<view class="cu-bar btn-group margin-top">
+						<button class="cu-btn bg-orange shadow-blur round" :loading="loading" @tap="login">{{ loading ? '登录中...' : '登 录' }}</button>
+					</view>
+					<view class="flex justify-center">
+						<view class="text-gray text-sm margin-top-xl" @tap="register">注册新账户</view>
+					</view>
+				</view>
+			</view>
+			<!--  #endif -->
+
+			<!--  #ifdef  MP-WEIXIN	 -->
+			<view class="logView">
+				<button @click="onGetUserProfile" class="logbt">
+					<view class="login-head">
+						<image class="logoimg" :src="baseLogo" mode="widthFix"></image>
+					</view>
+					<view class="loginTitile"><text decode="true">请点击微信登录,并授权获取公开信息, 登录后您将获得更多权益</text></view>
+					<view style="background-color: #fbb233; width: 200rpx; border-radius: 80rpx" class="cu-btn bg-orange shadow-blur round">
+						<text class="cuIcon-lightauto"></text>
+						微信登录
+					</view>
+				</button>
+				<!-- <view class="text-gray text-sm margin-top-xl" @click="changMobileLogin()">手机登录</view> -->
+			</view>
+			<!--  #endif -->
+		</block>
+	</view>
+</template>
+
+<script>
+var _this;
+import { baseLogo } from '../../config/config.js';
+export default {
+	data() {
+		return {
+			loading: false,
+			user: [],
+			username: '',
+			password: '',
+			class_id: '',
+			ismobile: false,
+			group_id: 1,
+			code: '',
+			baseLogo: baseLogo,
+			redirect: ''
+		};
+	},
+	mounted() {
+		_this = this;
+	},
+	onLoad(e) {
+		console.log(e);
+		if (e.redirect) {
+			this.redirect = e.redirect;
+		}
+	},
+	onShow() {
+		this.user = this.$common.userInfo();
+		console.log('this.user: ', this.user);
+		if (typeof this.user == 'undefined' || this.user == '' || this.user == null) {
+		} else {
+			this.$common.navigateTo('index');
+		}
+		// #ifdef MP-WEIXIN
+		this.wxLogin();
+		// #endif
+	},
+	methods: {
+		wxLogin() {
+			wx.login({
+				success: (res) => {
+					this.code = res.code;
+				},
+				fail: function (error) {
+					console.log('login failed ' + error);
+				}
+			});
+		},
+		//切换微信登录
+		// wechatLogin(){
+		// 	_this.ismobile=false;
+		// },
+		//切换手机登录
+		// changMobileLogin(){
+		// 	_this.ismobile=true;
+
+		// },
+		register() {
+			this.$common.navigateTo('register');
+		},
+		login() {
+			_this.loading = true;
+			if (_this.username == '' || _this.username.length < 11) {
+				uni.showToast({
+					icon: 'none',
+					title: '请输入正确的手机号'
+				});
+				_this.loading = false;
+				return;
+			}
+			if (_this.password == '') {
+				uni.showToast({
+					icon: 'none',
+					title: '请输入密码'
+				});
+				_this.loading = false;
+				return;
+			}
+			_this.$api.login(
+				{
+					account: _this.username,
+					password: _this.password
+				},
+				(data) => {
+					//console.log(data);
+					if (data.code == 1) {
+						_this.loading = false;
+						//console.log(data);
+
+						try {
+							_this.$db.set('upload', 1);
+							_this.$db.set('login', 1);
+							_this.$db.set('token', data.data.userinfo.token);
+							_this.$db.set('user', data.data.userinfo);
+							_this.$db.set('auth', data.data.auth);
+							_this.$store.commit('setAccessToken', data.data.userinfo.token);
+							_this.$store.commit('setActivityId', 0);
+						} catch (e) {}
+
+						_this.$common.successToShow(data.msg, function () {
+							// _this.$common.navigateTo('index');
+
+							if (_this.redirect != '') {
+								console.log(_this.redirect);
+								if (_this.redirect.indexOf('%2F') > 0) {
+									_this.redirect = '/' + unescape(_this.redirect);
+								}
+								_this.$common.navigateTo(_this.redirect);
+								return;
+							}
+							uni.navigateBack();
+						});
+					} else {
+						_this.loading = false;
+						uni.showToast({
+							duration: 1500,
+							icon: 'none',
+							title: data.msg
+						});
+					}
+				}
+			);
+		},
+
+		onGetUserProfile() {
+			// uni.showLoading({
+			// 	title:"正在登录中..."
+			// })
+			var platform = 'wechat';
+			var that = this;
+			var fid = uni.getStorageSync('parentid') ? uni.getStorageSync('parentid') : '';
+			uni.getUserProfile({
+				desc: '用于完善会员资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
+				success: (res) => {
+					console.log(res);
+					var invite_id = _this.$db.get('invite_id');
+					if (!invite_id) invite_id = 0;
+					console.log(invite_id);
+					_this.$api.third(
+						{
+							code: _this.code,
+							platform: platform,
+							encrypted_data: res.encryptedData,
+							iv: res.iv,
+							raw_data: res.rawData,
+							signature: res.signature,
+							invite_id: invite_id,
+							appid: 'wx98c89e5f918af88f'
+						},
+						(data) => {
+							console.log(data);
+							// console.log(data.data.userinfo)
+							var res = data.data;
+							if (data.code == 1) {
+								this.$common.successToShow('登录成功!');
+								try {
+									this.$db.set('upload', 1);
+									this.$db.set('login', 1);
+									this.$db.set('auth', res.auth);
+									this.$db.set('user', res.userinfo);
+									this.$db.set('token', res.auth.token);
+									this.$store.commit('setAccessToken', res.auth.token);
+									this.$store.commit('setActivityId', 2);
+								} catch (e) {
+									console.log('e: ', e);
+								}
+								// uni.switchTab({
+								// 	url: '/pages/index/index'
+								// });
+								if (this.redirect != '') {
+									console.log(this.redirect);
+									if (this.redirect.indexOf('%2F') > 0) {
+										this.redirect = '/' + unescape(this.redirect);
+									}
+									this.$common.navigateTo(this.redirect);
+									return;
+								}
+								uni.navigateBack();
+							} else {
+								_this.wxLogin();
+							}
+						}
+					);
+				},
+				fail: (res) => {
+					console.log('res: ', res);
+					_this.wxLogin(); //重新获取登录code
+					uni.hideLoading();
+					if (res.errMsg == 'getUserInfo:cancel' || res.errMsg == 'getUserInfo:fail auth deny') {
+						uni.showModal({
+							title: '用户授权失败',
+							showCancel: false,
+							content: '请点击重新授权,如果未弹出授权,请尝试长按删除小程序,重新进入!',
+							success: function (res) {
+								if (res.confirm) {
+									console.log('用户点击确定');
+									uni.navigateBack();
+								}
+							}
+						});
+					}
+				}
+			});
+			// uni.login({
+			// 	success: loginRes => {
+			// 		uni.hideLoading();
+			// 		console.log('第一次登录'+loginRes.code)
+			// 		if (loginRes.code && loginRes.code!='') {
+			// 			console.log('2222222222222222222')
+
+			// 		}
+			// 	}
+			// })
+		}
+	}
+};
+</script>
+
+<style>
+page {
+	background: #fff;
+}
+
+.content {
+	height: 100%;
+}
+
+.logView {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	flex-direction: column;
+	align-items: center;
+	/* 垂直居中 */
+	width: 100%;
+	position: fixed;
+	left: 50%;
+	top: 50%;
+	transform: translate(-50%, -50%);
+}
+
+.logbt {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	flex-direction: column;
+	align-items: center;
+	/* 垂直居中 */
+	width: 100%;
+	background: none;
+	border: none !important;
+}
+
+.logbt:after {
+	border: none !important;
+}
+
+.logbt .logoimg {
+	width: 200rpx;
+	height: 200rpx;
+	display: block;
+}
+
+.logbt .wechatimg {
+	width: 150rpx;
+	height: 150rpx;
+	display: block;
+}
+
+.loginTitile {
+	padding: 50rpx;
+	font-size: 28rpx;
+	color: #787878;
+	line-height: 1.3;
+	text-align: center;
+}
+
+.loginBtn {
+	width: 300rpx;
+	height: 70rpx;
+	line-height: 70rpx;
+	color: #fff;
+	background: #2562a1;
+	border-radius: 10rpx;
+	border: none;
+}
+
+image {
+	width: 100rpx;
+	height: 100rpx;
+}
+
+.mobileLogin {
+	background: none;
+	color: #999;
+	text-align: center;
+	margin: 40rpx auto;
+	border: none;
+	font-size: 26rpx;
+}
+
+.landing[type='primary'] {
+	height: 84rpx;
+	line-height: 84rpx;
+	border-radius: 44rpx;
+	font-size: 32rpx;
+	/* background: linear-gradient(left, #86B5F4, #4790EF); */
+	background-color: #ffbc32;
+}
+
+.login-btn {
+	padding: 10rpx 20rpx;
+	margin-top: 60rpx;
+}
+
+.login-function {
+	overflow: auto;
+	padding: 20rpx 20rpx 30rpx 20rpx;
+}
+
+.login-forget {
+	float: left;
+	font-size: 26rpx;
+	color: #999;
+}
+
+.login-register {
+	color: #666;
+	float: right;
+	font-size: 26rpx;
+}
+
+.login-input input {
+	background: #f2f5f6;
+	font-size: 28rpx;
+	padding: 10rpx 25rpx;
+	height: 80rpx;
+	line-height: 80rpx;
+	border-radius: 40rpx;
+}
+
+.login-margin-b {
+	margin-bottom: 25rpx;
+}
+
+.login-input {
+	padding: 10rpx 20rpx;
+}
+
+.login-head {
+	font-size: 34rpx;
+	text-align: center;
+	padding: 25rpx 10rpx 55rpx 10rpx;
+}
+
+.login-head image {
+	width: 200rpx;
+}
+
+.login-card {
+	background: #fff;
+	border-radius: 12rpx;
+	padding: 10rpx 25rpx;
+	position: relative;
+	margin-top: 120rpx;
+}
+
+.login-bg {
+	height: 100%;
+	padding: 25rpx;
+}
+</style>

+ 172 - 0
pages/zhenxing/zhenxing.vue

@@ -0,0 +1,172 @@
+<template>
+	<!-- 村史界面 -->
+	<view class="body">
+		<view class="nabBar" style="background-image: url('https://huli-app.wenlvti.net/app_static/minnanhun/image/cs_zx.png'); background-size: cover">
+			<u-navbar bgColor="rgba(255,255,255,0)" :placeholder="true" :leftIconSize="0"></u-navbar>
+		</view>
+		<view class="box_7">
+			<view class="cs_group_4">
+				<view class="image-wrapper_19">
+					<view class="image_10-1" v-for="item in 4" :key="item">
+						<image style="width: 100; height: 100%" src="/static/image/zy.png"></image>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="cs_box_1">
+			<view class="box_33">
+				<view class="cs_text-wrapper_6">
+					<u-tabs
+						:list="listTab"
+						:lineWidth="55"
+						lineColor="#4E7198"
+						:activeStyle="{
+							color: '#4E7198',
+							fontWeight: 'bold'
+						}"
+					></u-tabs>
+				</view>
+				<!-- 瀑布流 -->
+
+				<view class="" style="margin-top: 80rpx; width: 90%; margin: auto">
+					<custom-waterfalls-flow :value="list" style="width: 90%; margin: auto">
+						<view class="item" style="position: relative" v-for="(item, index) in list" :key="index" slot="slot{{index}}">
+							<view class="title_pubu">
+								{{ item.title }}
+							</view>
+							<!-- <view class="desc">{{ item.desc }}</view> -->
+						</view>
+					</custom-waterfalls-flow>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			listTab: [
+				{
+					name: '百花齐放',
+					tabid: 4
+				},
+				{
+					name: '集体经济',
+					tabid: 1
+				},
+				{
+					name: '招商引资',
+					tabid: 2
+				},
+				{
+					name: '医疗服务',
+					tabid: 3
+				},
+
+				{
+					name: '百家争鸣',
+					tabid: 5
+				}
+			],
+			list: [
+				{
+					image: 'https://imagepphcloud.thepaper.cn/pph/image/117/155/574.jpg',
+					title: '让广大农民过上更好的生活 ',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+				{
+					image: 'https://p2.itc.cn/images01/20220426/d7aa1613f3504eb3aec5332913bfc796.jpeg',
+					title: '助力乡村振兴教师志愿者下乡支教 ',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+				{
+					image: 'https://img2.baidu.com/it/u=2262288823,1799977930&fm=253&fmt=auto&app=120&f=JPEG?w=650&h=436',
+					title: '厦门实施乡村振兴',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+				{
+					image: 'https://img0.baidu.com/it/u=214322660,1036744599&fm=253&fmt=auto&app=120&f=JPEG?w=640&h=404',
+					title: '把握乡村振兴战略',
+					desc: '描述描述描述描述描述描述描述描述1'
+				},
+				{
+					image: 'https://5b0988e595225.cdn.sohucs.com/images/20190524/89ec932d7834457092b8b568d5deeb1b.jpeg',
+					title: '新时代文明实践乡村振兴的小松序曲',
+					desc: '描述描述描述描述描述描述描述描述2'
+				},
+
+				{
+					image: 'https://pic.rmb.bdstatic.com/bjh/news/1acf2fad4a4c3167d7ac23bbfe3cabad.jpeg',
+					title: '透过镜头看农村生活新画卷',
+					desc: '描述描述描述描述描述描述描述描述2'
+				}
+			]
+		};
+	},
+	methods: {}
+};
+</script>
+
+<style>
+/* 村史 */
+.cs_box_1 {
+	display: flex;
+	flex-direction: column;
+	width: 750rpx;
+}
+.nabBar {
+	height: 220rpx;
+}
+
+.title_pubu {
+	width: 100%;
+	height: 50rpx;
+	/* background-color: rgba(202, 0, 0, 0.3); */
+	background-color: rgba(60, 213, 243, 0.3);
+	font-size: 30rpx;
+	color: #ffffff;
+	position: absolute;
+	bottom: 0;
+	left: 0;
+}
+.box_33 {
+	display: flex;
+	flex-direction: column;
+}
+.cs_text-wrapper_6 {
+	width: 702rpx;
+	height: 36rpx;
+	margin: 15rpx 0 80rpx 48rpx;
+}
+
+.box_7 {
+	display: flex;
+	flex-direction: column;
+	width: 749rpx;
+}
+.cs_group_4 {
+	display: flex;
+	flex-direction: column;
+	width: 749rpx;
+	margin-top: -40rpx;
+	border-radius: 46rpx 46rpx 0 0;
+	background-color: #f8f8f8;
+}
+.image-wrapper_19 {
+	width: 684rpx;
+	height: 276rpx;
+	flex-wrap: wrap;
+	display: flex;
+	justify-content: space-between;
+	margin: 45rpx 0 0 35rpx;
+}
+
+.image_10-1 {
+	width: 330rpx;
+	height: 126rpx;
+	margin: 0 24rpx 24rpx 0;
+	margin-right: 0rpx;
+}
+</style>

+ 57 - 0
service/api/common.js

@@ -0,0 +1,57 @@
+/**
+ * 授权 API
+ */
+import {
+	http
+} from '../request/main.js'
+
+/**
+ * 获取配置数据
+ * 
+ * @returns {Promise}
+ */
+export function getConfig() {
+	return http.post('/common/getConfig', {}, {
+		muteLogin: true
+	})
+}
+
+/**
+ * 获取活动列表
+ *
+ * @returns {Promise}
+ */
+// export function getActivityList(group, page, page_size) {
+// 	return http.post('/common/getActivityList', {
+// 		group,
+// 		page,
+// 		page_size
+// 	}, {
+// 		muteLogin: true
+// 	})
+// }
+export function getActivityList(activity_type, page, page_size) {
+	return http.post('/common/getActivityList', {
+		activity_type,
+		page,
+		page_size
+	}, {
+		muteLogin: true
+	})
+}
+
+/**
+ * 上传图片
+ * 
+ * @returns {Promise}
+ */
+export function uploadImage(imageUrl) {
+	return http.post('/common/uploadImage', {
+		file: {
+			file: imageUrl,
+			name: 'image'
+		}
+	}, {
+		uploadFile: true
+	})
+}

+ 105 - 0
service/api/examine.js

@@ -0,0 +1,105 @@
+/**
+ * 授权 API
+ */
+import {
+	http
+} from '../request/main.js'
+/**
+ * 开始答题
+ * 
+ * @returns {Promise}
+ */
+export function startChallenge() {
+	return http.post('/examine/startChallenge')}
+
+/**
+ * 获取新题
+ * 
+ * @returns {Promise}
+ */
+export function getNew(session_id) {
+	return http.post('/examine/getNew', {
+		session_id
+	})}
+
+/**
+ * 提交答案
+ * 
+ * @returns {Promise}
+ */
+export function submitAnswer(id, answer) {
+	return http.post('/examine/submitAnswer', {
+		id,
+		answer
+	})
+}
+
+/**
+ * 获取考核结果
+ * 
+ * @returns {Promise}
+ */
+export function getSummary(session_id) {
+	return http.post('/examine/getSummary', {
+		session_id
+	})
+}
+
+/**
+ * 获取考核历史
+ * 
+ * @returns {Promise}
+ */
+export function getHistory(session_id, page, page_size) {
+	return http.post('/examine/getHistory', {
+		session_id,
+		page,
+		page_size
+	})
+}
+
+/**
+ * 获取答题结果
+ * 
+ * @returns {Promise}
+ */
+export function getResult(id) {
+	return http.post('/examine/getResult', {
+		id
+	})
+}
+
+/**
+ * 获取排行数据
+ * 
+ * @returns {Promise}
+ */
+export function getTopList(group, page, page_size) {
+	return http.post('/examine/getTopList', {
+		group,
+		page,
+		page_size
+	})
+}
+
+/**
+ * 获取我的排行
+ * 
+ * @returns {Promise}
+ */
+export function getMineTop(group) {
+	return http.post('/examine/getMineTop', {
+		group
+	})
+}
+
+/**
+ * 获取全部排行和个人排行
+ * 比赛专用
+ * 
+ * @returns {Promise}
+ */
+export function getRankingList() {
+	return http.post('/examine/getRankingList', {
+	})
+}

+ 98 - 0
service/api/gift.js

@@ -0,0 +1,98 @@
+/**
+ * 授权 API
+ */
+import {
+	http
+} from '../request/main.js';
+
+
+/**
+ * 获取整体数据
+ * 
+ * @returns {Promise}
+ */
+export function getWrapData() {
+	return http.post('/gift/getWrapData');
+}
+
+/**
+ * 获取奖品详情
+ * 
+ * @returns {Promise}
+ */
+export function getDetail(id) {
+	return http.post('/gift/getDetail', {
+		id
+	});
+}
+
+
+/**
+ * 获取兑换预览
+ * 
+ * @returns {Promise}
+ */
+export function getPreview(gift_id, gift_spec_id) {
+	return http.post('/gift/getPreview', {
+		gift_id,
+		gift_spec_id
+	});
+}
+
+/**
+ * 进行兑换
+ * 
+ * @returns {Promise}
+ */
+export function exchange(formData) {
+	return http.post('/gift/exchange', formData);
+}
+
+
+/**
+ * 取消订单
+ * 
+ * @returns {Promise}
+ */
+export function orderCancel(order_id) {
+	return http.post('/gift/orderCancel', {
+		order_id
+	});
+}
+
+
+
+/**
+ * 获取兑换列表
+ * 
+ * @returns {Promise}
+ */
+export function getOrderList(group, page, page_size) {
+	return http.post('/gift/getOrderList', {
+		group,
+		page,
+		page_size
+	});
+}
+
+/**
+ * 获取兑换详情
+ * 
+ * @returns {Promise}
+ */
+export function getOrderDetail(id) {
+	return http.post('/gift/getOrderDetail', {
+		id
+	});
+}
+
+/**
+ * 获取物流动态
+ * 
+ * @returns {Promise}
+ */
+export function getExpressFeed(id) {
+	return http.post('/gift/getExpressFeed', {
+		id
+	});
+}

+ 57 - 0
service/api/mock.js

@@ -0,0 +1,57 @@
+/**
+ * 模拟 API
+ */
+import {
+	http
+} from '../request/index.js' // 请求拦截
+import Cache from '../../common/cache.js'
+
+/**
+ * 数据列表
+ * 
+ * @returns {Promise}
+ */
+export function getList() {
+	return new Promise(resolve => {
+		const cacheKey = 'data:list'		const data = Cache.get(cacheKey)		if (data) {
+			resolve([0, data])		} else {
+			setTimeout(() => {
+				resolve([0, [{
+					id: 1,
+					type: 1
+				}, {
+					id: 2,
+					type: 2
+				}, {
+					id: 3,
+					type: 1
+				}, {
+					id: 4,
+					type: 2
+				}]])			}, 500)			// http.get('/v1/data/list').then(([err, res]) => {
+			// 	if (! err) {
+			// 		Cache.put(cacheKey, res, 60)			// 		resolve([0, res])			// 	} else {
+			// 		resolve([err])			// 	}	
+			// })		}
+	})}
+
+/**
+ * 数据详情
+ * 
+ * @returns {Promise}
+ */
+export function getDetail(id, refresh) {
+	return new Promise(resolve => {
+		const cacheKey = 'data:detail:' + id		const data = Cache.get(cacheKey)		if (data && !refresh) {
+			resolve([0, data])		} else {
+			setTimeout(() => {
+				resolve([0, {
+					id: 1,
+					type: 1,
+					name: '测试内容'
+				}])			}, 500)			// http.get('/v1/data/detail').then(([err, res]) => {
+			// 	if (! err) {
+			// 		Cache.put(cacheKey, res, 60)			// 		resolve([0, res])			// 	} else {
+			// 		resolve([err])			// 	}	
+			// })		}
+	})}

+ 13 - 0
service/api/page.js

@@ -0,0 +1,13 @@
+/**
+ * 授权 API
+ */
+import {
+	http
+} from '../request/main.js'
+/**
+ * 获取首页数据
+ * 
+ * @returns {Promise}
+ */
+export function getHomeData() {
+	return http.post('/page/getHomeData')}

+ 163 - 0
service/api/user.js

@@ -0,0 +1,163 @@
+/**
+ * 授权 API
+ */
+import {
+	http
+} from '../request/main.js'
+
+/**
+ * 用户登录 - 姓名登录码
+ * 
+ * @returns {Promise}
+ */
+export function loginByNameCode(name, code, wx_code) {
+	return http.post('/user/loginByNameCode', {
+		name,
+		code,
+		wx_code
+	}, {
+		muteLogin: true
+	})
+}
+
+/**
+ * 用户登录 - 编号登录码
+ *
+ * @returns {Promise}
+ */
+export function loginByNumberCode(number, code, wx_code) {
+	return http.post('/user/loginByNumberCode', {
+		number,
+		code,
+		wx_code
+	}, {
+		muteLogin: true
+	})
+}
+
+/**
+ * 用户登录 - 手机验证码
+ * 
+ * @returns {Promise}
+ */
+export function loginByPhoneCaptcha(phone, captcha, wx_code) {
+	return http.post('/user/loginByPhoneCaptcha', {
+		phone,
+		captcha,
+		wx_code
+	}, {
+		muteLogin: true
+	})
+}
+
+/**
+ * 用户登录 - 微信快捷登录
+ * 
+ * @returns {Promise}
+ */
+export function loginByWechatPhone(encrypted_data, iv, code, wx_code) {
+	return http.post('/user/loginByWechatPhone', {
+		encrypted_data,
+		iv,
+		code,
+		wx_code
+	}, {
+		muteLogin: true
+	})
+}
+
+/**
+ * 小程序自动登录
+ * 
+ * @returns {Promise}
+ */
+export function wechatAutoLogin(wx_code) {
+	return http.post('/user/wechatAutoLogin', {
+		wx_code
+	}, {
+		muteLogin: true
+	})
+}
+
+
+/**
+ * 发送手机验证吗
+ * 
+ * @returns {Promise}
+ */
+export function sendPhoneCaptcha(phone, wx_code, muteLogin) {
+	return http.post('/user/sendPhoneCaptcha', {
+		phone,
+		wx_code
+	}, {
+		muteLogin: muteLogin
+	})
+}
+
+
+/**
+ * 用户登出
+ * 
+ * @returns {Promise}
+ */
+export function logout() {
+	return http.post('/user/logout')
+}
+
+/**
+ * 更新用户信息
+ * 
+ * @returns {Promise}
+ */
+export function updateUserInfo(avatar_url) {
+	return http.post('/user/updateUserInfo', {
+		avatar_url
+	})
+}
+
+/**
+ * 获取用户信息
+ * 
+ * @returns {Promise}
+ */
+export function getUserInfo() {
+	return http.post('/user/getUserInfo')
+}
+
+/**
+ * 修改登录码
+ * 
+ * @returns {Promise}
+ */
+export function changeCode(oldCode, newCode) {
+	return http.post('/user/changeCode', {
+		oldCode,
+		newCode
+	})
+}
+
+/**
+ * 修改手机号
+ * 
+ * @returns {Promise}
+ */
+export function changePhone(phone, captcha) {
+	return http.post('/user/changePhone', {
+		phone,
+		captcha
+	})
+}
+
+/**
+ * 修改手机号 - 授权微信手机号
+ * 
+ * @returns {Promise}
+ */
+export function changeWechatPhone(encrypted_data, iv, code, wx_code) {
+	return http.post('/user/changeWechatPhone', {
+		encrypted_data,
+		iv,
+		code,
+		wx_code
+	})
+}

+ 7 - 0
service/config.js

@@ -0,0 +1,7 @@
+import * as Config from '../config/config.js'
+let baseUrl =Config.baseUrl+'/addons/yunexamine'
+
+export default {
+	baseUrl
+}
+

+ 222 - 0
service/request/main.js

@@ -0,0 +1,222 @@
+import store from '../../store' // 状态管理
+import {
+	encodeRedirectUrl
+} from '../../common/util.js'
+import Request from './request.js'
+import Config from '../config.js'
+import {
+	wechatAutoLogin
+} from '../api/user.js';
+
+export const http = new Request();
+
+// 设置全局配置
+http.setConfig((config) => {
+	config.baseUrl = Config.baseUrl;
+	config.header = {
+		'Content-Type': 'application/json;charset=UTF-8'
+	};
+
+	return config
+})
+
+
+// 请求之前拦截器
+http.interceptor.beforeRequest((config, cancel) => {
+	// console.log('请求前拦截', config.url, config)
+
+	// 登录校验
+	let accessToken = store.getters.accessToken;
+	let activityId = store.getters.activityId;
+	// console.log('get.accessToken', accessToken, activityId);
+	if (!config.muteLogin) {
+		if (!accessToken) {
+			console.error('needLogin request');
+			cancel('登录过期,尝试重试'); // 取消请求
+			console.info('needLogin request', 'silentReload');
+			silentReload(config.curd === 'save');
+			return;
+		}
+		config.header['token'] = accessToken;
+
+		// config.url += '?token=' + accessToken;
+		// config.header['Cookie'] = 'token=' + accessToken;  // uploadFile时,不能传header,慎用这种方式
+	}
+	config.url += (config.url.indexOf('?') === -1 ? '?' : '&') + 'activity_id=' + activityId;
+
+	return config;
+})
+
+// 请求之后拦截器 
+http.interceptor.afterRequest((response) => {
+	// console.log('请求后拦截', response);
+
+	// 系统错误
+	if (response.statusCode === 500) {
+		console.error('系统错误');
+		setTimeout(() => {
+			uni.showToast({
+				title: '系统错误',
+				icon: 'none',
+				mask: true,
+				duration: 2000
+			})
+		}, 50);
+		return [response];
+	}
+
+	let data = response.data;
+
+	// 网络错误
+	if (!data) {
+		console.error('网络错误');
+		setTimeout(() => {
+			uni.showToast({
+				title: '网络错误',
+				icon: 'none',
+				mask: true,
+				duration: 2000
+			})
+		}, 50)
+		return [response];
+	}
+
+	if (typeof data === 'string') {
+		data = JSON.parse(data)
+	}
+
+	// 正常请求
+	if (data.code === 1) {
+		return [0, data.data || data.msg];
+	}
+
+	// 系统错误
+	console.warn('invalid request', data.msg);
+	switch (data.code) {
+		case 401: // 认证失败
+			console.error('needLogin 401');
+			store.commit('delAccessToken'); // 删除token
+			console.info('needLogin 401', 'silentReload');
+			silentReload(response.config.curd === 'save');
+			break;
+		case 402: // 小程序自动登录失败
+			break;
+		default: // 错误提示
+			setTimeout(() => {
+				uni.showToast({
+					title: data.msg || '网络错误',
+					icon: 'none',
+					mask: true,
+					duration: 2000
+				})
+			}, 50)
+			if (data.code === 403) { // 活动无效
+				store.commit('delAccessToken'); // 删除token
+				store.commit('delActivityId'); // 删除活动ID
+				smartLogin('');
+			}
+			break;
+	}
+
+	return [response];
+})
+
+/**
+ * 获取 login code
+ */
+export function silentReload(warning = false, redirectUrl = null) {
+	if (warning) {
+		setTimeout(() => {
+			uni.showToast({
+				title: '鉴权失败,请重新操作',
+				icon: 'none',
+				mask: true,
+				duration: 2000
+			})
+		}, 50)
+		return false
+	}
+	if (store.getters.isLock('silentReload')) {
+		return false
+	}
+	console.info('needLogin silentReload')
+	store.commit('lock', 'silentReload')
+	if (redirectUrl === null) {
+		const pages = getCurrentPages()
+		const currentPage = pages[pages.length - 1]
+		redirectUrl = encodeRedirectUrl(currentPage.route, currentPage.options)
+	}
+
+	// #ifdef  MP-WEIXIN
+	// 微信小程序自动登录
+	uni.reLaunch({
+		url: '/pages/user/login?redirect=' + redirectUrl
+	})
+	/* #endif */
+
+	// #ifdef  H5
+	// 跳到登录页面或活动列表页
+	smartLogin(redirectUrl)
+	/* #endif */
+
+	return true
+}
+
+/**
+ * 获取 login code
+ */
+export function smartLogin(redirectUrl, loginCheck) {
+	if (store.getters.activityId) { // 有缓存的活动ID
+		if (!loginCheck) {
+			// 直接到登录页面
+			uni.reLaunch({
+				url: '/pages/user/login?redirect=' + redirectUrl
+			})
+		}
+
+		return true;
+	}
+	const globalConfig = store.getters.globalConfig
+
+	// 配置未正确加载
+	if (!globalConfig) {
+		setTimeout(() => {
+			uni.showToast({
+				title: '数据加载失败,请点击右上角“重新进入小程序“',
+				icon: 'none',
+				mask: true,
+				duration: 2000
+			})
+		}, 50)
+		return true;
+	}
+
+	// 存在默认活动
+	if (globalConfig.default_activity) {
+		if (!loginCheck) {
+			// 直接到登录页面
+			uni.reLaunch({
+				url: '/answer_pages/user/login?redirect=' + redirectUrl
+			})
+		}
+		return true;
+	}
+
+	// 可以选择活动
+	if (globalConfig.select_activity) {
+		// 直接到活动列表
+		uni.reLaunch({
+			url: '/answer_pages/home/dashboard'
+		})
+		return true;
+	}
+
+	// 需要扫码进入
+	setTimeout(() => {
+		uni.reLaunch({
+			url: '/answer_pages/user/tips'
+		})
+	}, 50)
+
+	return true
+}

+ 156 - 0
service/request/request.js

@@ -0,0 +1,156 @@
+export default class Request {
+	// 判断url是否为绝对路径
+	static absolutelyUrl(url) {
+		return /(http|https):\/\/([\w.]+\/?)\S*/.test(url)
+	};
+
+	// 发起请求前
+	static beforeRequest(config) {
+		return config
+	};
+
+	// 发起请求后
+	static afterRequest(response) {
+		return response
+	};
+
+	// 基本配置
+	config = {
+		baseUrl: '',
+		header: {
+			'Content-Type': 'application/json;charset=UTF-8'
+		},
+		method: 'GET',
+		dataType: 'json',
+		responseType: 'text',
+		success() {},
+		fail() {},
+		complete() {}
+	};
+
+	// 过滤器
+	interceptor = {
+		beforeRequest(callback) {
+			if (callback) {
+				Request.beforeRequest = callback
+			}
+		},
+		afterRequest(callback) {
+			if (callback) {
+				Request.afterRequest = callback
+			}
+		}
+	};
+
+	// 设置配置
+	setConfig(callback) {
+		this.config = callback(this.config)
+	}
+
+	// 发起请求
+	request(options = {}) {
+		// 基本参数
+		options.baseUrl = options.baseUrl || this.config.baseUrl
+		options.dataType = options.dataType || this.config.dataType
+		options.url = Request.absolutelyUrl(options.url) ? options.url : (options.baseUrl + options.url)
+		options.data = options.data || {}
+		options.header = options.header || this.config.header
+		options.method = options.method || this.config.method
+
+		// console.log(options.header);
+		// options.
+
+		// promise 请求
+		return new Promise((resolve, reject) => {
+			let execute = true
+			let _config = null
+			// 请求完成
+			options.complete = (response) => {
+				response.config = _config
+				return resolve(Request.afterRequest(response))
+			}
+			// 默认配置 + 用户配置【复制作用】
+			let mConfig = {
+				...this.config,
+				...options
+			}
+			// 取消请求 - 回调
+			let cancel = (msg = 'request cancel') => {
+				execute = false
+				return resolve([{
+					config: mConfig,
+					data: {
+						code: 0,
+						msg: msg
+					}
+				}])
+			}
+			// 默认配置 + 用户配置 + 初始配置【复制作用】
+			_config = {
+				...mConfig,
+				...Request.beforeRequest(mConfig, cancel)
+			}
+			// 是否取消请求【如果execute == false, 那么已经resolve了,所以不需要return resolve】
+			if (!execute) return
+
+			// 发起请求
+			if (_config.hasOwnProperty('uploadFile')) {
+				const data = _config.data
+				let tConfig = {
+					url: _config.url,
+					filePath: data.file.file,
+					name: data.file.name,
+					formData: data.form,
+					// #ifdef  MP-WEIXIN
+					header: _config.header,
+					// #endif
+					success: _config.success,
+					fail: _config.fail,
+					complete: _config.complete,
+				}
+				uni.uploadFile(tConfig)
+			} else {
+				uni.request(_config)
+			}
+		})
+	};
+
+	// 接口请求 - 支持重试
+	autoRetry(options, retryMax = 3) {
+		// console.log('autoRetry', options, retryMax);
+		return new Promise((resolve, reject) => {
+			if (retryMax <= 0) {
+				return resolve(['网络错误']);
+			}
+			if (retryMax == 2) {
+				// console.log('this.request exect');
+				this.request(options).then(([err, res]) => {
+					// console.log('this.request', err, res);
+					if (!err) {
+						resolve([err, res]);
+					} else {
+						this.autoRetry(options, retryMax - 1);
+					}
+				});
+			} else {
+				return this.autoRetry(options, retryMax - 1);
+			}
+		});
+	}
+
+	// GET 请求
+	get(url, data, options = {}) {
+		options.url = url
+		options.data = data
+		options.method = 'GET'
+		return this.request(options)
+	}
+
+	// POST 请求
+	post(url, data, options = {}) {
+		options.url = url
+		options.data = data
+		options.method = 'POST'
+		return this.request(options)
+	}
+}

BIN
static/image/bj.png


BIN
static/image/hc.png


BIN
static/image/hc_a.png


BIN
static/image/home.png


BIN
static/image/home_a.png


BIN
static/image/hs.png


BIN
static/image/invite.png


BIN
static/image/jf.png


BIN
static/image/jp.png


BIN
static/image/jy.png


BIN
static/image/jy_a.png


BIN
static/image/mine.png


BIN
static/image/mine_a.png


BIN
static/image/tg.png


BIN
static/image/wh.png


BIN
static/image/zx.png


BIN
static/image/zy.png


BIN
static/image/余额.png


BIN
static/image/图层 1222.png


BIN
static/image/圆角矩形 840 拷贝.png


BIN
static/image/矩形 13.png


BIN
static/image/矩形 602.png


BIN
static/image/组 128.png


BIN
static/image/组 129.png


BIN
static/image/账期 拷贝.png


BIN
static/image/账期(1).png


BIN
static/image/账期(2).png


BIN
static/image/账期(3).png


BIN
static/image/账期.png


BIN
static/image/麦田.png


+ 91 - 0
store/index.js

@@ -0,0 +1,91 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+import Util from '../common/util.js'
+import Cache from '../common/cache.js'
+
+// modules
+import UserModule from './module/user.js'
+import ExamineModule from './module/examine.js'
+
+Vue.use(Vuex);//vue的插件机制
+
+const store = new Vuex.Store({
+	state: {
+		lockData: {},
+		accessToken: null,
+		activityId: null,
+		globalConfig: {}		
+
+	},
+	modules: {
+		user: UserModule,
+		examine: ExamineModule,
+	},
+	mutations: {
+		lock(state, key) {
+			state.lockData[key] = Util.getTimestamp(null) + 5
+		},
+		unlock(state, key) {
+			state.lockData[key] = Util.getTimestamp(null) + 2
+		},
+		setAccessToken(state, accessToken) {
+			state.accessToken = accessToken
+			Cache.put('accessToken', accessToken)
+		},
+		delAccessToken(state) {
+			state.accessToken = null;
+			Cache.remove('accessToken')
+		},
+		setActivityId(state, activityId) {
+			state.activityId = activityId
+			Cache.put('activityId', activityId)
+		},
+		delActivityId(state) {
+			state.activityId = null
+			Cache.remove('activityId')
+		},
+		setGlobalConfig(state, config) {
+			state.globalConfig = config
+			Cache.put('globalConfig', config)  // 缓存5分钟
+		},
+	},
+	getters: {
+		isLock(state, key) {
+			return key => {
+				const lockTime = state.lockData[key]
+				const timestamp = Util.getTimestamp(null)
+				console.log('isLock', key, lockTime, timestamp)
+				if (lockTime && lockTime > timestamp) {
+					return true
+				}
+				return false
+			}
+		},
+		accessToken(state) {
+			if (state.accessToken === null) {
+				state.accessToken = Cache.get('accessToken')
+			}
+			return state.accessToken
+		},
+		activityId(state) {
+			if (state.activityId === null) {
+				state.activityId = Cache.get('activityId') || 0
+			}
+			return state.activityId
+		},
+		isLogin(state) {
+			if (state.accessToken === null) {
+				state.accessToken = Cache.get('accessToken')
+			}
+			return !!state.accessToken
+		},
+		globalConfig(state) {
+			if (state.globalConfig === null) {
+				state.globalConfig = Cache.get('globalConfig')
+			}
+			return state.globalConfig
+		},
+	}
+})
+export default store

+ 14 - 0
store/module/examine.js

@@ -0,0 +1,14 @@
+export default {
+	namespaced: true,
+	state: {
+		topGroupId: -1
+	},
+	mutations: {
+		setTopGroupId(state, groupId) {
+			state.topGroupId = groupId
+		},
+		delTopGroupId(state) {
+			state.topGroupId = -1
+		},
+	}
+}

+ 26 - 0
store/module/user.js

@@ -0,0 +1,26 @@
+import Cache from '../../common/cache.js'
+
+export default {
+	namespaced: true,
+	state: {
+		uploadAvatarTip: null,
+		userInfo: {}
+	},
+	mutations: {
+		setUserInfo(state, userInfo) {
+			state.userInfo = userInfo
+		},
+		setUploadAvatarTip(state, yesOrNo) {
+			state.uploadAvatarTip = yesOrNo
+			Cache.put('uploadAvatarTip', yesOrNo)
+		},
+	},
+	getters: {
+		uploadAvatarTip(state) {
+			if (state.uploadAvatarTip === null) {
+				state.uploadAvatarTip = Cache.get('uploadAvatarTip')
+			}
+			return state.uploadAvatarTip
+		},
+	}
+}

+ 80 - 0
uni.scss

@@ -0,0 +1,80 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+@import '@/uni_modules/uview-ui/theme.scss';
+
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color:#c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm:24rpx;
+$uni-font-size-base:28rpx;
+$uni-font-size-lg:32rpx;
+
+/* 图片尺寸 */
+$uni-img-size-sm:40rpx;
+$uni-img-size-base:52rpx;
+$uni-img-size-lg:80rpx;
+
+/* Border Radius */
+$uni-border-radius-sm: 4rpx;
+$uni-border-radius-base: 6rpx;
+$uni-border-radius-lg: 12rpx;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 10px;
+$uni-spacing-row-base: 20rpx;
+$uni-spacing-row-lg: 30rpx;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 8rpx;
+$uni-spacing-col-base: 16rpx;
+$uni-spacing-col-lg: 24rpx;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:40rpx;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:36rpx;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:30rpx;
+
+/* 背景颜色 */
+$pq-bg-color: #f6f6f6;  /* #FAFAFA #f8f8f8 #f1f1f1 #f6f6f6 */

+ 17 - 0
uni_modules/custom-waterfalls-flow/changelog.md

@@ -0,0 +1,17 @@
+## 1.0.7(2022-05-26)
+1. 优化局部改变数据更新问题,避免重新加载数据,只改变局部
+## 1.0.6(2022-04-18)
+1. 修改tab快速切换时会出现的BUG
+## 1.0.5(2022-04-18)
+1. 修复可能存在数据错误的BUG;
+2. 兼容,今后可以无需调用refresh()就可以更新数据;
+## 1.0.4(2022-04-18)
+1. 修复BUG;
+## 1.0.3(2022-04-15)
+1. 优化代码;
+2. 修改懒加载数据存在的BUG;
+## 1.0.1(2022-03-11)
+1. 增加隐藏图片字段的键名字段hideImageKey,默认hide
+2. 支持在列表中配置hide参数进行隐藏图片
+## 1.0.0(2022-03-09)
+使用最简单的思想实现瀑布流

+ 323 - 0
uni_modules/custom-waterfalls-flow/components/custom-waterfalls-flow/custom-waterfalls-flow.vue

@@ -0,0 +1,323 @@
+<template>
+	<view class="waterfalls-flow">
+		<view v-for="(item,index) in data.column" :key="index" class="waterfalls-flow-column" :id="`waterfalls_flow_column_${index+1}`" :msg="msg" :style="{'width':w,'margin-left':index==0?0:m}">
+			<view :class="['column-value',{'column-value-show':item2.o}]" v-for="(item2,index2) in columnValue(index)" :key="index2" :style="[s1]" @click.stop="wapperClick(item2)">
+				<view class="inner" v-if="data.seat==1">
+					<!-- #ifdef MP-WEIXIN -->
+					<!-- #ifdef VUE2 -->
+					<slot name="slot{{item2.index}}"></slot>
+					<!-- #endif -->
+					<!-- #ifdef VUE3 -->
+					<slot :name="`slot${item2.index}`"></slot>
+					<!-- #endif -->
+					<!-- #endif -->
+					<!-- #ifndef MP-WEIXIN -->
+					<slot v-bind="item2"></slot>
+					<!-- #endif -->
+				</view>
+				<image :class="['img',{'img-hide':item2[hideImageKey]==true||item2[hideImageKey]==1},{'img-error':!item2[data.imageKey]}]" :src="item2[data.imageKey]" mode="widthFix" @load="imgLoad(item2,index+1)" @error="imgError(item2,index+1)" @click.stop="imageClick(item2)"></image>
+				<view class="inner" v-if="data.seat==2">
+					<!-- #ifdef MP-WEIXIN -->
+					<!-- #ifdef VUE2 -->
+					<slot name="slot{{item2.index}}"></slot>
+					<!-- #endif -->
+					<!-- #ifdef VUE3 -->
+					<slot :name="`slot${item2.index}`"></slot>
+					<!-- #endif -->
+					<!-- #endif -->
+					<!-- #ifndef MP-WEIXIN -->
+					<slot v-bind="item2"></slot>
+					<!-- #endif -->
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		props: {
+			value: Array,
+			column: { // 列的数量 
+				type: [String, Number],
+				default: 2
+			},
+			maxColumn: { // 最大列数 
+				type: [String, Number],
+				default: 5
+			},
+			columnSpace: { // 列之间的间距 百分比
+				type: [String, Number],
+				default: 2
+			},
+			imageKey: { // 图片key
+				type: [String],
+				default: 'image'
+			},
+			hideImageKey: { // 隐藏图片key
+				type: [String],
+				default: 'hide'
+			},
+			seat: { // 文本的位置,1图片之上 2图片之下
+				type: [String, Number],
+				default: 2
+			},
+			listStyle: { // 单个展示项的样式:eg:{'background':'red'}
+				type: Object
+			}
+		},
+		data() {
+			return {
+				data: {
+					list: this.value ? this.value : [],
+					column: this.column < 2 ? 2 : this.column,
+					columnSpace: this.columnSpace <= 5 ? this.columnSpace : 5,
+					imageKey: this.imageKey,
+					seat: this.seat
+				},
+				msg: 0,
+				listInitStyle: {
+					'border-radius': '12rpx',
+					'margin-bottom': '20rpx',
+					'background-color': '#fff'
+				},
+				adds: [], //预置数据
+				isLoaded: true,
+				curIndex: 0,
+				isRefresh: true,
+				flag: false,
+				refreshDatas: []
+			}
+		},
+		computed: {
+			// 计算列宽
+			w() {
+				const column_rate = `${100 / this.data.column - (+this.data.columnSpace)}%`;
+				return column_rate;
+			},
+			// 计算margin
+			m() {
+				const column_margin = `${(100-(100 / this.data.column - (+this.data.columnSpace)).toFixed(5)*this.data.column)/(this.data.column-1)}%`;
+				return column_margin;
+			},
+			// list样式
+			s1() {
+				return { ...this.listInitStyle, ...this.listStyle };
+			}
+		},
+		created() {
+			// 初始化
+			this.refresh();
+		},
+		methods: {
+			// 预加载图片
+			loadImages(idx = 0) {
+				let count = 0;
+				const newList = this.data.list.filter((item, index) => index >= idx);
+				for (let i = 0; i < newList.length; i++) {
+					// #ifndef APP-PLUS
+					uni.getImageInfo({
+						src: `${newList[i][this.imageKey]}.jpg`,
+						complete: res => {
+							count++;
+							if (count == newList.length) this.initValue(idx);
+						}
+					})
+					// #endif
+					// #ifdef APP-PLUS
+					plus.io.getImageInfo({
+						src: `${newList[i][this.imageKey]}.jpg`,
+						complete: res => {
+							count++;
+							if (count == newList.length) this.initValue(idx);
+						}
+					})
+					// #endif
+				}
+			},
+			// 刷新
+			refresh() {
+				if (!this.isLoaded) {
+					this.refreshDatas = this.value;
+					return false;
+				};
+				setTimeout(() => {
+					this.refreshDatas = [];
+					this.isRefresh = true;
+					this.adds = [];
+					this.data.list = this.value ? this.value : [];
+					this.data.column = this.column < 2 ? 2 : this.column >= this.maxColumn ? this.maxColumn : this.column;
+					this.data.columnSpace = this.columnSpace <= 5 ? this.columnSpace : 5;
+					this.data.imageKey = this.imageKey;
+					this.data.seat = this.seat;
+					this.curIndex = 0;
+					// 每列的数据初始化
+					for (let i = 1; i <= this.data.column; i++) {
+						this.data[`column_${i}_values`] = [];
+						this.msg++;
+					}
+					this.$nextTick(() => {
+						this.initValue(this.curIndex, 'refresh==>');
+					})
+				}, 1)
+			},
+			columnValue(index) {
+				return this.data[`column_${index+1}_values`];
+			},
+			change(newValue) {
+				for (let i = 0; i < this.data.list.length; i++) {
+					const cv = this.data[`column_${this.data.list[i].column}_values`];
+					for (let j = 0; j < cv.length; j++) {
+						if (newValue[i] && i === cv[j].index) {
+							this.data[`column_${this.data.list[i].column}_values`][j] = Object.assign(cv[j], newValue[i]);
+							this.msg++;
+							break;
+						}
+					}
+				}
+			},
+			getMin(a, s) {
+				let m = a[0][s];
+				let mo = a[0];
+				for (var i = a.length - 1; i >= 0; i--) {
+					if (a[i][s] < m) {
+						m = a[i][s];
+					}
+				}
+				mo = a.filter(i => i[s] == m);
+				return mo[0];
+			},
+			// 计算每列的高度
+			getMinColumnHeight() {
+				return new Promise(resolve => {
+					const heightArr = [];
+					for (let i = 1; i <= this.data.column; i++) {
+						const query = uni.createSelectorQuery().in(this);
+						query.select(`#waterfalls_flow_column_${i}`).boundingClientRect(data => {
+							heightArr.push({ column: i, height: data.height });
+						}).exec(() => {
+							if (this.data.column <= heightArr.length) {
+								resolve(this.getMin(heightArr, 'height'));
+							}
+						});
+					}
+				})
+			},
+			async initValue(i, from) {
+				this.isLoaded = false;
+				if (i >= this.data.list.length || this.refreshDatas.length) {
+					this.msg++;
+					this.loaded();
+					return false;
+				}
+				const minHeightRes = await this.getMinColumnHeight();
+				const c = this.data[`column_${minHeightRes.column}_values`];
+				this.data.list[i].column = minHeightRes.column;
+				c.push({ ...this.data.list[i], cIndex: c.length, index: i, o: 0 });
+				this.msg++;
+			},
+			// 图片加载完成
+			imgLoad(item, c) {
+				const i = item.index;
+				item.o = 1;
+				this.$set(this.data[`column_${c}_values`], item.cIndex, JSON.parse(JSON.stringify(item)));
+				this.initValue(i + 1);
+			},
+			// 图片加载失败
+			imgError(item, c) {
+				const i = item.index;
+				item.o = 1;
+				item[this.data.imageKey] = null;
+				this.$set(this.data[`column_${c}_values`], item.cIndex, JSON.parse(JSON.stringify(item)));
+				this.initValue(i + 1);
+			},
+			// 渲染结束
+			loaded() {
+				if (this.refreshDatas.length) {
+					this.isLoaded = true;
+					this.refresh();
+					return false;
+				}
+				this.curIndex = this.data.list.length;
+				if (this.adds.length) {
+					this.data.list = this.adds[0];
+					this.adds.splice(0, 1);
+					this.initValue(this.curIndex);
+				} else {
+					if (this.data.list.length) this.$emit('loaded');
+					this.isLoaded = true;
+					this.isRefresh = false;
+				}
+			},
+			// 单项点击事件
+			wapperClick(item) {
+				this.$emit('wapperClick', item);
+			},
+			// 图片点击事件
+			imageClick(item) {
+				this.$emit('imageClick', item);
+			}
+		},
+		watch: {
+			value: {
+				deep: true,
+				handler(newValue, oldValue) {
+					setTimeout(() => {
+						this.$nextTick(() => {
+							if (this.isRefresh) return false;
+							if (this.isLoaded) {
+								// if (newValue.length <= this.curIndex) return this.refresh();
+								if (newValue.length <= this.curIndex) return this.change(newValue);
+								this.data.list = newValue;
+								this.$nextTick(() => {
+									this.initValue(this.curIndex, 'watch==>');
+								})
+							} else {
+								this.adds.push(newValue);
+							}
+						})
+					}, 10)
+				}
+			},
+			column(newValue) {
+				this.refresh();
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	.waterfalls-flow {
+		overflow: hidden;
+
+		&-column {
+			float: left;
+		}
+	}
+
+	.column-value {
+		width: 100%;
+		font-size: 0;
+		overflow: hidden;
+		transition: opacity .4s;
+		opacity: 0;
+
+		&-show {
+			opacity: 1;
+		}
+
+		.inner {
+			font-size: 30rpx;
+		}
+
+		.img {
+			width: 100%;
+
+			&-hide {
+				display: none;
+			}
+
+			&-error {
+				background: #f2f2f2 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAiAQMAAAAatXkPAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAAIZJREFUCNdlzjEKwkAUBNAfEGyCuYBkLyLuxRYW2SKlV1JSeA2tUiZg4YrLjv9PGsHqNTPMSAQuyAJgRDHSyvBPwtZoSJXakeJI9iuRLGDygdl6V0yKDtyMAeMPZySj8yfD+UapvRPj2JOwkyAooSV5IwdDjPdCPspe8LyTl9IKJvDETKKRv6vnlUasgg0fAAAAAElFTkSuQmCC) no-repeat center center;
+			}
+		}
+	}
+</style>

+ 80 - 0
uni_modules/custom-waterfalls-flow/package.json

@@ -0,0 +1,80 @@
+{
+  "id": "custom-waterfalls-flow",
+  "displayName": "瀑布流 灵活配置 简单易用 兼容vue2vue3小程序、H5、app等多端",
+  "version": "1.0.7",
+  "description": "瀑布流,根据内容自动计算进行流式布局,简单参数配置,实现兼容多端及vue2和vue3的瀑布流布局;uv-ui发布https://ext.dcloud.net.cn/plugin?name=uv-ui",
+  "keywords": [
+    "瀑布流",
+    "瀑布流式布局"
+],
+  "repository": "https://gitee.com/my_dear_li_pan/my-uni-modules.git",
+"engines": {
+  },
+  "dcloudext": {
+    "category": [
+        "前端组件",
+        "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "插件不采集任何数据",
+      "permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "Vue": {
+          "vue2": "y",
+          "vue3": "y"
+        },
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "n"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "u",
+          "Edge": "u",
+          "Firefox": "y",
+          "Safari": "u"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "u",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        }
+      }
+    }
+  }
+}

+ 445 - 0
uni_modules/custom-waterfalls-flow/readme.md

@@ -0,0 +1,445 @@
+- <a href="#c1" title="概要">概要</a>
+- <a href="#c2" title="支持的平台">支持的平台</a>
+- <a href="#c3" title="使用方式">使用方式</a>
+- <a href="#c4" title="属性说明">属性说明</a>
+- <a href="#c5" title="事件说明">事件说明</a>
+- <a href="#c6" title="组件方法">组件方法</a>
+- <a href="#c7" title="refresh的使用示例">refresh的使用示例</a>
+- <a href="#c8" title="隐藏单项图片示例">隐藏单项图片示例</a>
+- <a href="#c9" title="完整示例">完整示例</a>
+- <a href="#c10" title="温馨提示">温馨提示</a>
+- <a href="#c11" title="关注我,不迷路">关注我,不迷路</a>
+- <a href="#c12" title="个人作品展示">个人作品展示</a>
+ 
+<div id="c1"></div>
+
+#### 概要
+
+custom-waterfalls-flow是一个瀑布流插件,灵活配置、简单易用、兼容多端、同时兼容vue2和vue3。
+
+最近在做项目的时候需要用到瀑布流,于是在插件市场找了一些,下载量最高的是用了定位来做的,我认为瀑布流可以不用定位去实现,于是我就自己写了该插件。经过反复的测试优化,最终搞定!
+
+**设置列数:** 瀑布流的列数可以通过参数直接控制,实时监听,随改随生效。列数最小为2,最大默认为5,可以通过maxColumn参数去控制最大列数,理论上可以设置无限大,具体值自己拿捏。
+
+**更新数据:** 瀑布流的每项数据,可以直接通过修改value,随改随生效,这样可以实现加载更多数据。已经渲染过的数据不会再次渲染,每次只会渲染新增的数据,这样避免了数据越多渲染越慢的情况。可以调用组件的```refresh()```方法进行数据刷新,注意vue2和vue3中调用子组件的方法有区别,也会在下面进行说明。
+
+**展示方式:** 瀑布流可以是纯图片,可以使用插槽自定义文字描述,微信小程序与app、h5使用会有些区别,也会在下面具体说明。内容高度及排序都不用担心,会根据每项的内容高度自动计算。
+
+**实现思路:** 通过配置列数,先渲染出每列,再计算每列的高度,最小的那列就加入一条数据进行渲染,然后再重复计算每列,高度小的加入数据...其实思路是很简单的。
+
+uniapp插件市场地址:[https://ext.dcloud.net.cn/plugin?id=7594](https://ext.dcloud.net.cn/plugin?id=7594)
+
+<div id="c2"></div>
+
+#### 支持的平台
+
+H5、app、微信小程序(这三个平台经过反复测试优化,兼容vue2和vue3)。
+
+百度小程序:由于插槽不能循环渲染的限制,只支持纯图片瀑布流。
+
+其他小程序:暂未测试,需要的可以自己测试和修改,思路肯定是没错的,主要是兼容插槽的问题。
+
+nvue:暂不支持,后期可能会支持,目前需要的可以自己修改源码。
+
+<div id="c3"></div>
+
+#### 使用方式
+
+**1、导入插件**
+
+该组件符合uni_modules规范,使用Hbuilderx导入插件,导入到项目根目录中的uni_modules文件夹中。
+
+**2、template中使用**
+
+uni_modules规范在项目页面中直接使用,不需要单独引入注册组件。
+
+***纯图片瀑布流使用***
+
+```
+<template>
+	<custom-waterfalls-flow :value="data.list"></custom-waterfalls-flow>
+</template>
+```
+
+***微信小程序自定义内容使用***
+
+微信小程序没有动态模板,使用for循环的方式进行渲染。
+
+```
+<template>
+	<custom-waterfalls-flow :value="data.list">
+		<view class="item" v-for="(item,index) in data.list" :key="index" slot="slot{{index}}">
+			<view class="title">{{item.title}}</view>
+			<view class="desc">{{item.desc}}</view>
+		</view>
+	</custom-waterfalls-flow>
+</template>
+```
+
+***h5、app端自定义内容使用***
+
+使用作用域插槽实现
+
+```
+<template>
+	<custom-waterfalls-flow :value="data.list">
+		<template v-slot:default="item">
+			<view class="item">
+				<view class="title">{{item.title}}</view>
+				<view class="desc">{{item.desc}}</view>
+			</view>
+		</template>
+	</custom-waterfalls-flow>
+</template>
+```
+
+***小程序、h5、app等多端自定义内容使用***
+
+条件渲染-多端同时兼容
+
+```
+<template>
+	<custom-waterfalls-flow :value="data.list">
+		<!-- #ifdef MP-WEIXIN -->
+		<view class="item" v-for="(item,index) in data.list" :key="index" slot="slot{{index}}">
+			<view class="title">{{item.title}}</view>
+			<view class="desc">{{item.desc}}</view>
+		</view>
+		<!-- #endif -->
+		<!-- #ifndef MP-WEIXIN -->
+		<template v-slot:default="item">
+			<view class="item">
+				<view class="title">{{item.title}}</view>
+				<view class="desc">{{item.desc}}</view>
+			</view>
+		</template>
+		<!-- #endif -->
+	</custom-waterfalls-flow>
+</template>
+```
+
+<div id="c4"></div>
+
+#### 属性说明
+
+参数|说明|类型|是否必填|可选值|默认值
+-|-|-|-|-|-|
+value|渲染的列表|Array|是|-|-
+column|列数|Number|否|2-maxColumn|2
+maxColumn|最大列数|Number|否|>2|5
+columnSpace|列之间的间距(单位是百分比)|Number|否|-|2
+imageKey|列表中的图片字段的键名|String|否|-|image
+hideImageKey|隐藏图片字段的键名|String|否|-|hide
+seat|自定义文字的位置,1-图片上方,2-图片下方|Number|否|1/2|2
+listStyle|单个展示项的样式|Object|否|示例:```{'background':'red'}```|-
+
+<div id="c5"></div>
+
+#### 事件说明
+
+事件名称|说明|回调参数
+-|-|-|
+@loaded|图片加载完成事件|-
+@wapperClick|单项点击事件|单项对应参数
+@imageClick|图片点击事件|单项对应参数
+
+<div id="c6"></div>
+
+#### 组件方法
+
+事件名称|说明|参数|使用场景
+-|-|-|-
+refresh|刷新数据,数据初始化,vue2中使用:```this.$refs.waterfallsFlowRef.refresh();```;vue3中使用:```const waterfallsFlowRef = ref(null);waterfallsFlowRef.value.refresh();```|-|下拉刷新等
+
+<div id="c7"></div>
+
+#### refresh的使用示例
+
+***vue2中使用***
+
+```
+<template>
+    <view>
+        <button class="btn" type="default" @click="reset()">刷新数据</button>
+    	<custom-waterfalls-flow ref="waterfallsFlowRef" :value="data.list"></custom-waterfalls-flow>
+    </view>
+</template>
+<script>
+	export default {
+	    data() {
+			return {
+				data:{
+					list: [
+						{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }, 
+						{ image: 'https://via.placeholder.com/200x200.png/2878ff', title: '我是标题2', desc: '描述描述描述描述描述描述描述描述2' }
+					]
+				}
+			}
+		},
+		reset(){
+			this.data.list = [{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }]
+			this.$refs.waterfallsFlowRef.refresh();
+		}
+	}
+</script>
+```
+
+***vue3中使用***
+
+```
+<template>
+    <view>
+        <button class="btn" type="default" @click="reset()">刷新数据</button>
+    	<custom-waterfalls-flow ref="waterfallsFlowRef" :value="data.list"></custom-waterfalls-flow>
+    </view>
+</template>
+<script setup>
+	import { reactive, ref } from 'vue';
+	const data = reactive({
+		list: [
+			{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }, 
+			{ image: 'https://via.placeholder.com/200x200.png/2878ff', title: '我是标题2', desc: '描述描述描述描述描述描述描述描述2' }
+		]
+	});
+	const waterfallsFlowRef = ref(null);
+	function reset(){
+		data.list = [{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }]
+		waterfallsFlowRef.value.refresh();
+	}
+</script>
+```
+
+<div id="c8"></div>
+
+#### 隐藏单项图片示例
+
+在数据列表中配置```hide:true```或者```hide:1```,就可以达到不显示图片的效果。支持使用参数hideImageKey自定义键名称,那就使用:```定义的键名称:true```或者```定义的键名称:1```。
+
+```
+<template>
+	<custom-waterfalls-flow :value="data.list">
+		<!-- #ifdef MP-WEIXIN -->
+		<view class="item" v-for="(item,index) in data.list" :key="index" slot="slot{{index}}">
+			<view class="title">{{item.title}}</view>
+			<view class="desc">{{item.desc}}</view>
+		</view>
+		<!-- #endif -->
+		<!-- #ifndef MP-WEIXIN -->
+		<template v-slot:default="item">
+			<view class="item">
+				<view class="title">{{item.title}}</view>
+				<view class="desc">{{item.desc}}</view>
+			</view>
+		</template>
+		<!-- #endif -->
+	</custom-waterfalls-flow>
+</template>
+<script setup>
+	import { reactive, ref } from 'vue';
+	const data = reactive({
+		list: [
+			{ image: 'https://via.placeholder.com/200x500.png/ff0000',
+			hide:1,title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }, 
+			{ image: 'https://via.placeholder.com/200x200.png/2878ff', title: '我是标题2', desc: '描述描述描述描述描述描述描述描述2' }
+		]
+	});
+</script>
+```
+
+<div id="c9"></div>
+
+#### 完整示例
+
+```
+<template>
+	<view style="padding: 0 10rpx;">
+		<view class="handle">
+			<button class="btn" type="default" @click="add()">增加数据</button>
+			<button class="btn" type="default" @click="changeColumn(1)">+列数({{column}})</button>
+			<button class="btn" type="default" @click="changeColumn(0)">-列数({{column}})</button>
+			<button class="btn" type="default" @click="reset()">刷新数据</button>
+		</view>
+		<custom-waterfalls-flow ref="waterfallsFlowRef" :value="data.list" :column="column" :columnSpace="1.5" :seat="2" @wapperClick="wapperClick" @imageClick="imageClick" @loaded="loaded">
+			<!-- #ifdef MP-WEIXIN -->
+			<view class="item" v-for="(item,index) in data.list" :key="index" slot="slot{{index}}">
+				<view class="title">{{item.title}}</view>
+				<view class="desc">{{item.desc}}</view>
+			</view>
+			<!-- #endif -->
+			<!-- #ifndef MP-WEIXIN -->
+			<template v-slot:default="item">
+				<view class="item">
+					<view class="title">{{item.title}}</view>
+					<view class="desc">{{item.desc}}</view>
+				</view>
+			</template>
+			<!-- #endif -->
+		</custom-waterfalls-flow>
+	</view>
+</template>
+<script setup>
+	// #ifdef VUE3
+	import { reactive, ref, onMounted } from 'vue';
+	const data = reactive({
+		list: [{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }, 
+					{ image: 'https://via.placeholder.com/200x200.png/2878ff', title: '我是标题2', desc: '描述描述描述描述描述描述描述描述2' }, 
+					{ image: 'https://via.placeholder.com/200x100.png/FFB6C1', title: '我是标题3', desc: '描述描述描述描述描述描述描述描述3' }, 
+					{ image: 'https://via.placeholder.com/200x300.png/9400D3', title: '我是标题4', desc: '描述描述描述描述描述描述描述描述4' }, 
+					{ image: 'https://via.placeholder.com/100x240.png/B0E0E6', title: '我是标题5', desc: '描述描述描述描述描述描述描述描述5' }, 
+					{ image: 'https://via.placeholder.com/140x280.png/7FFFAA', title: '我是标题6', desc: '描述描述描述描述描述描述描述描述6' }, 
+					{ image: 'https://via.placeholder.com/40x60.png/EEE8AA', title: '我是标题7', desc: '描述描述描述描述描述描述描述描述7' }]
+	});
+	const column = ref(3);
+
+	function add() {
+		const newArr = [{ image: 'https://via.placeholder.com/58x100.png/FF7F50', title: '我是标题8', desc: '描述描述描述描述描述描述描述描述8' }, 
+				{ image: 'https://via.placeholder.com/59x100.png/C0C0C0', title: '我是标题9', desc: '描述描述描述描述描述描述描述描述9' }, 
+				{ image: 'https://via.placeholder.com/60x100.png/FAEBD7', title: '我是标题10', desc: '描述描述描述描述描述描述描述描述10' }];
+		data.list = data.list.concat(newArr);
+	}
+
+	function changeColumn(h) {
+		column.value = !h ? column.value - 1 : column.value + 1;
+	}
+
+	function loaded() {
+		console.log('加载完成')
+	}
+
+	function wapperClick(item) {
+		console.log('单项点击事件', item)
+	}
+
+	function imageClick(item) {
+		console.log('图片点击事件', item)
+	}
+	const waterfallsFlowRef = ref(null);
+
+	function reset() {
+		data.list = [{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }]
+		waterfallsFlowRef.value.refresh();
+	}
+	// #endif
+</script>
+<script>
+	// #ifdef VUE2
+	export default {
+		data() {
+			return {
+				data: {
+					list: [{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }, 
+					{ image: 'https://via.placeholder.com/200x200.png/2878ff', title: '我是标题2', desc: '描述描述描述描述描述描述描述描述2' }, 
+					{ image: 'https://via.placeholder.com/200x100.png/FFB6C1', title: '我是标题3', desc: '描述描述描述描述描述描述描述描述3' }, 
+					{ image: 'https://via.placeholder.com/200x300.png/9400D3', title: '我是标题4', desc: '描述描述描述描述描述描述描述描述4' }, 
+					{ image: 'https://via.placeholder.com/100x240.png/B0E0E6', title: '我是标题5', desc: '描述描述描述描述描述描述描述描述5' }, 
+					{ image: 'https://via.placeholder.com/140x280.png/7FFFAA', title: '我是标题6', desc: '描述描述描述描述描述描述描述描述6' }, 
+					{ image: 'https://via.placeholder.com/40x60.png/EEE8AA', title: '我是标题7', desc: '描述描述描述描述描述描述描述描述7' }]
+				},
+				column: 3
+			}
+		},
+		methods: {
+			add() {
+				const newArr = [{ image: 'https://via.placeholder.com/58x100.png/FF7F50', title: '我是标题8', desc: '描述描述描述描述描述描述描述描述8' }, 
+				{ image: 'https://via.placeholder.com/59x100.png/C0C0C0', title: '我是标题9', desc: '描述描述描述描述描述描述描述描述9' }, 
+				{ image: 'https://via.placeholder.com/60x100.png/FAEBD7', title: '我是标题10', desc: '描述描述描述描述描述描述描述描述10' }]
+				this.data.list = this.data.list.concat(newArr);
+			},
+			changeColumn(h) {
+				this.column = !h ? this.column - 1 : this.column + 1;
+			},
+			loaded() {
+				console.log('加载完成')
+			},
+			wapperClick(item) {
+				console.log('单项点击事件', item)
+			},
+			imageClick(item) {
+				console.log('图片点击事件', item)
+			},
+			reset() {
+				this.data.list = [{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }]
+				this.$refs.waterfallsFlowRef.refresh();
+			}
+		}
+	}
+	// #endif
+</script>
+<style>
+	page {
+		background-color: #f2f5f9;
+	}
+</style>
+<style lang="scss" scoped>
+	.handle {
+		display: flex;
+		flex-direction: row;
+		flex-wrap: wrap;
+		margin-bottom: 20rpx;
+		padding: 10rpx;
+
+		.btn {
+			margin: 20rpx 10rpx;
+			padding: 0 20rpx;
+			background: #2878FF;
+			font-size: 28rpx;
+			color: #fff;
+
+			&::after {
+				border: 0;
+			}
+		}
+	}
+
+	.item {
+		padding: 10rpx 10rpx 20rpx;
+
+		.title {
+			line-height: 48rpx;
+			font-size: 28rpx;
+			color: #222;
+		}
+
+		.desc {
+			font-size: 24rpx;
+			color: #666;
+		}
+	}
+</style>
+```
+
+<div id="c10"></div>
+
+#### 温馨提示
+
+1、该插件反复测试过微信小程序、h5、app-vue三个端,vue2和vue3都兼容,其他端可能需要测试改进。
+
+2、该插件的使用hbuilderx版本最好升级到较新版本,我开发的版本是hbuilderx3.3.11.20220209。
+
+3、对此插件或相关问题有好的建议,可以直接在评论区进行讨论。
+
+4、希望遇到问题不要喷,也不要骂人,其实这种心情我能理解,写该插件也不是一时半会就完成了的,所以希望互相理解。只要有问题,我会第一时间回复解决。
+
+5、对此插件有任何问题的可以在下方留言,我会第一时间回复和解决问题。还可以加QQ群进行前端技术交流 568984539,加群备注‘地区-名字-技术类型’。
+
+#### 最后我想说:认为该插件对你有帮助的,记得收藏、好评,这样可以帮助到更多人哟!
+
+---
+
+<div id="c11"></div>
+
+#### 关注我,不迷路
+
+如果任何疑问的可以在评论区留言,还可以加QQ群交流:568984539,加群备注‘地区-名字-技术类型’。
+
+更多前端等相关知识可关注我个人博客:https://blog.csdn.net/qq_42961150?spm=1011.2124.3001.5343
+
+<div id="c12"></div>
+
+#### 个人作品展示
+
+uniapp+vue3.2+unicloud开发微信小程序:**皮皮虎去水印**。
+
+关注下方公众号:【**全网免费网盘资源**】、【**美团外卖饿了么天天领红包**】、【**去水印**】
+
+![image](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-bb657efd-fece-483e-a715-5daea480fde8/6e029310-aec8-46e9-9883-1c88dc1925ad.jpg)

+ 185 - 0
uni_modules/mp-html/README.md

@@ -0,0 +1,185 @@
+## news
+1. 欢迎加入 `QQ` 交流群:`699734691`  
+   ![group](https://6874-html-foe72-1259071903.tcb.qcloud.la/assets/group.jpg?sign=558401bccbd56b01debe1bfac6a3b55e&t=1648801090)  
+2. 示例微信小程序 `富文本插件` 添加 `获取组件包` 功能 [详细](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart?id=mp)  
+   ![富文本插件](https://6874-html-foe72-1259071903.tcb.qcloud.la/assets/case/%E5%AF%8C%E6%96%87%E6%9C%AC%E6%8F%92%E4%BB%B6.jpg?sign=200e28d06f36049c18d42cdd46270c35&t=1648801110)
+
+## 功能介绍
+- 全端支持(含 `v3、NVUE`)
+- 支持丰富的标签(包括 `table`、`video`、`svg` 等)
+- 支持丰富的事件效果(自动预览图片、链接处理等)
+- 支持设置占位图(加载中、出错时、预览时)
+- 支持锚点跳转、长按复制等丰富功能
+- 支持大部分 *html* 实体
+- 丰富的插件(关键词搜索、内容 **编辑** 等)
+- 效率高、容错性强且轻量化
+
+查看 [功能介绍](https://jin-yufeng.gitee.io/mp-html/#/overview/feature) 了解更多
+
+## 使用方法
+- `uni_modules` 方式  
+  1. 点击右上角的 `使用 HBuilder X 导入插件` 按钮直接导入项目或点击 `下载插件 ZIP` 按钮下载插件包并解压到项目的 `uni_modules/mp-html` 目录下  
+  2. 在需要使用页面的 `(n)vue` 文件中添加  
+     ```html
+     <!-- 不需要引入,可直接使用 -->
+     <mp-html :content="html" />
+     ```
+     ```javascript
+     export default {
+       data() {
+         return {
+           html: '<div>Hello World!</div>'
+         }
+       }
+     }
+     ```
+  3. 需要更新版本时在 `HBuilder X` 中右键 `uni_modules/mp-html` 目录选择 `从插件市场更新` 即可  
+
+- 源码方式  
+  1. 从 [github](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 或 [gitee](https://gitee.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 下载源码  
+     插件市场的 **非 uni_modules 版本** 无法更新,不建议从插件市场获取  
+  2. 在需要使用页面的 `(n)vue` 文件中添加  
+     ```html
+     <mp-html :content="html" />
+     ```
+     ```javascript
+     import mpHtml from '@/components/mp-html/mp-html'
+     export default {
+       // HBuilderX 2.5.5+ 可以通过 easycom 自动引入
+       components: {
+         mpHtml
+       },
+       data() {
+         return {
+           html: '<div>Hello World!</div>'
+         }
+       }
+     }
+     ```
+
+- npm 方式  
+  1. 在项目根目录下执行  
+     ```bash
+     npm install mp-html
+     ```
+  2. 在需要使用页面的 `(n)vue` 文件中添加  
+     ```html
+     <mp-html :content="html" />
+     ```
+     ```javascript
+     import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html'
+     export default {
+       // 不可省略
+       components: {
+         mpHtml
+       },
+       data() {
+         return {
+           html: '<div>Hello World!</div>'
+         }
+       }
+     }
+     ```
+  3. 需要更新版本时执行以下命令即可  
+     ```bash
+     npm update mp-html
+     ```
+  
+  使用 *cli* 方式运行的项目,通过 *npm* 方式引入时,需要在 *vue.config.js* 中配置 *transpileDependencies*,详情可见 [#330](https://github.com/jin-yufeng/mp-html/issues/330#issuecomment-913617687)  
+  如果在 **nvue** 中使用还要将 `dist/uni-app/static` 目录下的内容拷贝到项目的 `static` 目录下,否则无法运行  
+
+查看 [快速开始](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart) 了解更多
+
+## 组件属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+|:---:|:---:|:---:|---|
+| container-style | String |  | 容器的样式([2.1.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v210)) |
+| content | String |  | 用于渲染的 html 字符串 |
+| copy-link | Boolean | true | 是否允许外部链接被点击时自动复制 |
+| domain | String |  | 主域名(用于链接拼接) |
+| error-img | String |  | 图片出错时的占位图链接 |
+| lazy-load | Boolean | false | 是否开启图片懒加载 |
+| loading-img | String |  | 图片加载过程中的占位图链接 |
+| pause-video | Boolean | true | 是否在播放一个视频时自动暂停其他视频 |
+| preview-img | Boolean | true | 是否允许图片被点击时自动预览 |
+| scroll-table | Boolean | false | 是否给每个表格添加一个滚动层使其能单独横向滚动 |
+| selectable | Boolean | false | 是否开启文本长按复制 |
+| set-title | Boolean | true | 是否将 title 标签的内容设置到页面标题 |
+| show-img-menu | Boolean | true | 是否允许图片被长按时显示菜单 |
+| tag-style | Object |  | 设置标签的默认样式 |
+| use-anchor | Boolean | false | 是否使用锚点链接 |
+
+查看 [属性](https://jin-yufeng.gitee.io/mp-html/#/basic/prop) 了解更多
+
+## 组件事件
+
+| 名称 | 触发时机 |
+|:---:|---|
+| load | dom 树加载完毕时 |
+| ready | 图片加载完毕时 |
+| error | 发生渲染错误时 |
+| imgtap | 图片被点击时 |
+| linktap | 链接被点击时 |
+
+查看 [事件](https://jin-yufeng.gitee.io/mp-html/#/basic/event) 了解更多
+
+## api
+组件实例上提供了一些 `api` 方法可供调用
+
+| 名称 | 作用 |
+|:---:|---|
+| in | 将锚点跳转的范围限定在一个 scroll-view 内 |
+| navigateTo | 锚点跳转 |
+| getText | 获取文本内容 |
+| getRect | 获取富文本内容的位置和大小 |
+| setContent | 设置富文本内容 |
+| imgList | 获取所有图片的数组 |
+
+查看 [api](https://jin-yufeng.gitee.io/mp-html/#/advanced/api) 了解更多
+
+## 插件扩展  
+除基本功能外,本组件还提供了丰富的扩展,可按照需要选用
+
+| 名称 | 作用 |
+|:---:|---|
+| audio | 音乐播放器 |
+| editable | 富文本 **编辑**([示例项目](https://6874-html-foe72-1259071903.tcb.qcloud.la/editable.zip?sign=cc0017be203fb3dbca62d33a0c15792e&t=1608447445)) |
+| emoji | 解析 emoji |
+| highlight | 代码块高亮显示 |
+| markdown | 渲染 markdown |
+| search | 关键词搜索 |
+| style | 匹配 style 标签中的样式 |
+| txv-video | 使用腾讯视频 |
+| img-cache | 图片缓存 by [@PentaTea](https://github.com/PentaTea) |
+
+从插件市场导入的包中 **不含有** 扩展插件,需要使用插件参考以下方法:  
+1. 获取完整组件包  
+   ```bash
+   npm install mp-html
+   ```
+2. 编辑 `tools/config.js` 中的 `plugins` 项,选择需要的插件  
+3. 生成新的组件包  
+   在 `node_modules/mp-html` 目录下执行  
+   ```bash
+   npm install
+   npm run build:uni-app
+   ```
+4. 拷贝 `dist/uni-app` 中的内容到项目根目录  
+
+查看 [插件](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin) 了解更多
+
+## 关于 nvue
+`nvue` 使用原生渲染,不支持部分 `css` 样式,为实现和 `html` 相同的效果,组件内部通过 `web-view` 进行渲染,性能上差于原生,根据 `weex` 官方建议,`web` 标签仅应用在非常规的降级场景。因此,如果通过原生的方式(如 `richtext`)能够满足需要,则不建议使用本组件,如果有较多的富文本内容,则可以直接使用 `vue` 页面  
+由于渲染方式与其他端不同,有以下限制:  
+1. 不支持 `lazy-load` 属性
+2. 视频不支持全屏播放
+
+纯 `nvue` 模式下,[此问题](https://ask.dcloud.net.cn/question/119678) 修复前,不支持通过 `uni_modules` 引入,需要本地引入(将 [dist/uni-app](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 中的内容拷贝到项目根目录下)  
+
+## 问题反馈
+遇到问题时,请先查阅 [常见问题](https://jin-yufeng.gitee.io/mp-html/#/question/faq) 和 [issue](https://github.com/jin-yufeng/mp-html/issues) 中是否已有相同的问题  
+可通过 [issue](https://github.com/jin-yufeng/mp-html/issues/new/choose) 、插件问答或发送邮件到 [mp_html@126.com](mailto:mp_html@126.com) 提问,不建议在评论区提问(不方便回复)  
+提问请严格按照 [issue 模板](https://github.com/jin-yufeng/mp-html/issues/new/choose) ,描述清楚使用环境、`html` 内容或可复现的 `demo` 项目以及复现方式,对于 **描述不清**、**无法复现** 或重复的问题将不予回复  
+
+查看 [问题反馈](https://jin-yufeng.gitee.io/mp-html/#/question/feedback) 了解更多

+ 95 - 0
uni_modules/mp-html/changelog.md

@@ -0,0 +1,95 @@
+## v2.3.1(2022-05-20)
+1. `U` `app` 端支持使用本地图片
+2. `U` 优化了微信小程序 `selectable` 属性在 `ios` 端的处理 [详细](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable)
+3. `F` 修复了 `editable` 插件不在顶部时 `tooltip` 位置可能错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/430)
+4. `F` 修复了 `vue3` 运行到微信小程序可能报错丢失内容的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/414)
+5. `F` 修复了 `vue3` 部分标签可能被错误换行的问题
+6. `F` 修复了 `editable` 插件 `app` 端插入视频无法预览的问题
+## v2.3.0(2022-04-01)
+1. `A` 增加了 `play` 事件,音视频播放时触发,可用于与页面其他音视频进行互斥播放 [详细](basic/event#play)
+2. `U` `show-img-menu` 属性支持控制预览时是否长按弹出菜单
+3. `U` 优化 `wxs` 处理,提高渲染性能 [详细](https://developers.weixin.qq.com/community/develop/article/doc/0006cc2b204740f601bd43fa25a413)  
+4. `U` `video` 标签支持 `object-fit` 属性
+5. `U` 增加支持一些常用实体编码 [详细](https://github.com/jin-yufeng/mp-html/issues/418)
+6. `F` 修复了图片仅设置高度可能不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/410)
+7. `F` 修复了 `video` 标签高度设置为 `auto` 不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/411)
+8. `F` 修复了使用 `grid` 布局时可能样式错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/413)
+9. `F` 修复了含有合并单元格的表格部分情况下显示异常的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/417)
+10. `F` 修复了 `editable` 插件连续插入内容时顺序不正确的问题
+11. `F` 修复了 `uni-app` 包 `vue3` 使用 `audio` 插件报错的问题
+12. `F` 修复了 `uni-app` 包 `highlight` 插件使用自定义的 `prism.min.js` 报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/416)
+## v2.2.2(2022-02-26)
+1. `A` 增加了 [pauseMedia](https://jin-yufeng.gitee.io/mp-html/#/advanced/api#pauseMedia) 的 `api`,可用于暂停播放音视频 [详细](https://github.com/jin-yufeng/mp-html/issues/317)
+2. `U` 优化了长内容的加载速度  
+3. `U` 适配 `vue3` [#389](https://github.com/jin-yufeng/mp-html/issues/389)、[#398](https://github.com/jin-yufeng/mp-html/pull/398) by [@zhouhuafei](https://github.com/zhouhuafei)、[#400](https://github.com/jin-yufeng/mp-html/issues/400)
+4. `F` 修复了小程序端图片高度设置为百分比时可能不显示的问题
+5. `F` 修复了 `highlight` 插件部分情况下可能显示不完整的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/403)
+## v2.2.1(2021-12-24)
+1. `A` `editable` 插件增加上下移动标签功能
+2. `U` `editable` 插件支持在文本中间光标处插入内容
+3. `F` 修复了 `nvue` 端设置 `margin` 后可能导致高度不正确的问题
+4. `F` 修复了 `highlight` 插件使用压缩版的 `prism.css` 可能导致背景失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/367)
+5. `F` 修复了编辑状态下使用 `emoji` 插件内容为空时可能报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/371)
+6. `F` 修复了使用 `editable` 插件后将 `selectable` 属性设置为 `force` 不生效的问题
+## v2.2.0(2021-10-12)
+1. `A` 增加 `customElements` 配置项,便于添加自定义功能性标签 [详细](https://github.com/jin-yufeng/mp-html/issues/350)
+2. `A` `editable` 插件增加切换音视频自动播放状态的功能 [详细](https://github.com/jin-yufeng/mp-html/pull/341) by [@leeseett](https://github.com/leeseett)
+3. `A` `editable` 插件删除媒体标签时触发 `remove` 事件,便于删除已上传的文件
+4. `U` `editable` 插件 `insertImg` 方法支持同时插入多张图片 [详细](https://github.com/jin-yufeng/mp-html/issues/342)
+5. `U` `editable` 插入图片和音视频时支持拼接 `domian` 主域名
+6. `F` 修复了内部链接参数中包含 `://` 时被认为是外部链接的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/356)
+7. `F` 修复了部分 `svg` 标签名或属性名大小写不正确时不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/351)
+8. `F` 修复了 `nvue` 页面运行到非 `app` 平台时可能样式错误的问题
+## v2.1.5(2021-08-13)
+1. `A` 增加支持标签的 `dir` 属性
+2. `F` 修复了 `ruby` 标签文字与拼音没有居中对齐的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/325)
+3. `F` 修复了音视频标签内有 `a` 标签时可能无法播放的问题
+4. `F` 修复了 `externStyle` 中的 `class` 名包含下划线或数字时可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326)
+5. `F` 修复了 `h5` 端引入 `externStyle` 可能不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326)
+## v2.1.4(2021-07-14)
+1. `F` 修复了 `rt` 标签无法设置样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/318)
+2. `F` 修复了表格中有单元格同时合并行和列时可能显示不正确的问题
+3. `F` 修复了 `app` 端无法关闭图片长按菜单的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/322)
+4. `F` 修复了 `editable` 插件只能添加图片链接不能修改的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/312) by [@leeseett](https://github.com/leeseett)
+## v2.1.3(2021-06-12)
+1. `A` `editable` 插件增加 `insertTable` 方法
+2. `U` `editable` 插件支持编辑表格中的空白单元格 [详细](https://github.com/jin-yufeng/mp-html/issues/310)
+3. `F` 修复了 `externStyle` 中使用伪类可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/298)
+4. `F` 修复了多个组件同时使用时 `tag-style` 属性时可能互相影响的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/305) by [@woodguoyu](https://github.com/woodguoyu)
+5. `F` 修复了包含 `linearGradient` 的 `svg` 可能无法显示的问题
+6. `F` 修复了编译到头条小程序时可能报错的问题
+7. `F` 修复了 `nvue` 端不触发 `click` 事件的问题
+8. `F` 修复了 `editable` 插件尾部插入时无法撤销的问题
+9. `F` 修复了 `editable` 插件的 `insertHtml` 方法只能在末尾插入的问题
+10. `F` 修复了 `editable` 插件插入音频不显示的问题
+## v2.1.2(2021-04-24)
+1. `A` 增加了 [img-cache](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#img-cache) 插件,可以在 `app` 端缓存图片 [详细](https://github.com/jin-yufeng/mp-html/issues/292) by [@PentaTea](https://github.com/PentaTea)
+2. `U` 支持通过 `container-style` 属性设置 `white-space` 来保留连续空格和换行符 [详细](https://jin-yufeng.gitee.io/mp-html/#/question/faq#space)
+3. `U` 代码风格符合 [standard](https://standardjs.com) 标准
+4. `U` `editable` 插件编辑状态下支持预览视频 [详细](https://github.com/jin-yufeng/mp-html/issues/286)
+5. `F` 修复了 `svg` 标签内嵌 `svg` 时无法显示的问题
+6. `F` 修复了编译到支付宝和头条小程序时部分区域不可复制的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/291)
+## v2.1.1(2021-04-09)
+1. 修复了对 `p` 标签设置 `tag-style` 可能不生效的问题
+2. 修复了 `svg` 标签中的文本无法显示的问题
+3. 修复了使用 `editable` 插件编辑表格时可能报错的问题
+4. 修复了使用 `highlight` 插件运行到头条小程序时可能没有样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/280)
+5. 修复了使用 `editable` 插件 `editable` 属性为 `false` 时会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/284)
+6. 修复了 `style` 插件连续子选择器失效的问题
+7. 修复了 `editable` 插件无法修改图片和字体大小的问题
+## v2.1.0.2(2021-03-21)
+修复了 `nvue` 端使用可能报错的问题
+## v2.1.0(2021-03-20)
+1. `A` 增加了 [container-style](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#container-style) 属性 [详细](https://gitee.com/jin-yufeng/mp-html/pulls/1)
+2. `A` 增加支持 `strike` 标签
+3. `A` `editable` 插件增加 `placeholder` 属性 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable)
+4. `A` `editable` 插件增加 `insertHtml` 方法 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable)
+5. `U` 外部样式支持标签名选择器 [详细](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart#setting)
+6. `F` 修复了 `nvue` 端部分情况下可能不显示的问题
+## v2.0.5(2021-03-12)
+1. `U` [linktap](https://jin-yufeng.gitee.io/mp-html/#/basic/event#linktap) 事件增加返回内部文本内容 `innerText` [详细](https://github.com/jin-yufeng/mp-html/issues/271)
+2. `U` [selectable](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable) 属性设置为 `force` 时能够在微信 `iOS` 端生效(文本块会变成 `inline-block`) [详细](https://github.com/jin-yufeng/mp-html/issues/267)
+3. `F` 修复了部分情况下竖向无法滚动的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/182)
+4. `F` 修复了多次修改富文本数据时部分内容可能不显示的问题
+5. `F` 修复了 [腾讯视频](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#txv-video) 插件可能无法播放的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/265)
+6. `F` 修复了 [highlight](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#highlight) 插件没有设置高亮语言时没有应用默认样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/276) by [@fuzui](https://github.com/fuzui)

+ 462 - 0
uni_modules/mp-html/components/mp-html/mp-html.vue

@@ -0,0 +1,462 @@
+<template>
+  <view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="containerStyle">
+    <slot v-if="!nodes[0]" />
+    <!-- #ifndef APP-PLUS-NVUE -->
+    <node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable]" name="span" />
+    <!-- #endif -->
+    <!-- #ifdef APP-PLUS-NVUE -->
+    <web-view ref="web" src="/uni_modules/mp-html/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
+    <!-- #endif -->
+  </view>
+</template>
+
+<script>
+/**
+ * mp-html v2.3.1
+ * @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'
+const plugins=[]
+// #ifdef APP-PLUS-NVUE
+const dom = weex.requireModule('dom')
+// #endif
+export default {
+  name: 'mp-html',
+  data () {
+    return {
+      nodes: [],
+      // #ifdef APP-PLUS-NVUE
+      height: 3
+      // #endif
+    }
+  },
+  props: {
+    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: {
+    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.nodes.length) {
+      this.setContent(this.content)
+    }
+  },
+  beforeDestroy () {
+    this._hook('onDetached')
+    clearInterval(this._timer)
+  },
+  methods: {
+    /**
+     * @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) {
+      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(/&amp;/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 {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')
+      })
+
+      // 等待图片加载完毕
+      let height
+      clearInterval(this._timer)
+      this._timer = setInterval(() => {
+        this.getRect().then(rect => {
+          // 350ms 总高度无变化就触发 ready 事件
+          if (rect.height === height) {
+            this.$emit('ready', rect)
+            clearInterval(this._timer)
+          }
+          height = rect.height
+        }).catch(() => { })
+      }, 350)
+      // #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 */
+</style>

+ 543 - 0
uni_modules/mp-html/components/mp-html/node/node.vue

@@ -0,0 +1,543 @@
+<template>
+  <view :id="attrs.id" :class="'_block _'+name+' '+attrs.class" :style="attrs.style">
+    <block v-for="(n, i) in childs" v-bind:key="i">
+      <!-- 图片 -->
+      <!-- 占位图 -->
+      <image v-if="n.name==='img'&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
+      <!-- 显示图片 -->
+      <!-- #ifdef H5 || (APP-PLUS && VUE2) -->
+      <img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <!-- #endif -->
+      <!-- #ifndef H5 || APP-PLUS -->
+      <image v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <!-- #endif -->
+      <!-- #ifdef APP-PLUS && VUE3 -->
+      <image v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <!-- #endif -->
+      <!-- 文本 -->
+      <!-- #ifndef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->
+      <text v-else-if="n.text" :user-select="opts[4]" decode>{{n.text}}</text>
+      <!-- #endif -->
+      <text v-else-if="n.name==='br'">\n</text>
+      <!-- 链接 -->
+      <view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
+        <node name="span" :childs="n.children" :opts="opts" style="display:inherit" />
+      </view>
+      <!-- 视频 -->
+      <!-- #ifdef APP-PLUS -->
+      <view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" @vplay.stop="play" />
+      <!-- #endif -->
+      <!-- #ifndef APP-PLUS -->
+      <video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
+      <!-- #endif -->
+      <!-- #ifdef H5 || APP-PLUS -->
+      <iframe v-else-if="n.name==='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" />
+      <embed v-else-if="n.name==='embed'" :style="n.attrs.style" :src="n.attrs.src" />
+      <!-- #endif -->
+      <!-- #ifndef MP-TOUTIAO || ((H5 || APP-PLUS) && VUE3) -->
+      <!-- 音频 -->
+      <audio v-else-if="n.name==='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
+      <!-- #endif -->
+      <view v-else-if="(n.name==='table'&&n.c)||n.name==='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
+        <node v-if="n.name==='li'" :childs="n.children" :opts="opts" />
+        <view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
+          <node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="opts" />
+          <block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
+            <view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
+              <node :childs="tr.children" :opts="opts" />
+            </view>
+            <view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
+              <view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style">
+                <node :childs="td.children" :opts="opts" />
+              </view>
+            </view>
+          </block>
+        </view>
+      </view>
+      
+      <!-- 富文本 -->
+      <!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
+      <rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" />
+      <!-- #endif -->
+      <!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
+      <rich-text v-else-if="!n.c" :id="n.attrs.id" :style="n.f+';display:inline'" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
+      <!-- #endif -->
+      <!-- 继续递归 -->
+      <view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
+        <node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs" :childs="n2.children" :opts="opts" />
+      </view>
+      <node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts" />
+    </block>
+  </view>
+</template>
+<script module="handler" lang="wxs">
+// 行内标签列表
+var inlineTags = {
+  abbr: true,
+  b: true,
+  big: true,
+  code: true,
+  del: true,
+  em: true,
+  i: true,
+  ins: true,
+  label: true,
+  q: true,
+  small: true,
+  span: true,
+  strong: true,
+  sub: true,
+  sup: true
+}
+/**
+ * @description 判断是否为行内标签
+ */
+module.exports = {
+  isInline: function (tagName, style) {
+    return inlineTags[tagName] || (style || '').indexOf('display:inline') !== -1
+  }
+}
+</script>
+<script>
+
+import node from './node'
+export default {
+  name: 'node',
+  options: {
+    // #ifdef MP-WEIXIN
+    virtualHost: true,
+    // #endif
+    // #ifdef MP-TOUTIAO
+    addGlobalClass: false
+    // #endif
+  },
+  data () {
+    return {
+      ctrl: {}
+    }
+  },
+  props: {
+    name: String,
+    attrs: {
+      type: Object,
+      default () {
+        return {}
+      }
+    },
+    childs: Array,
+    opts: Array
+  },
+  components: {
+
+    // #ifndef H5 && VUE3
+    node
+    // #endif
+  },
+  mounted () {
+    this.$nextTick(() => {
+      for (this.root = this.$parent; this.root.$options.name !== 'mp-html'; this.root = this.root.$parent);
+    })
+    // #ifdef H5 || APP-PLUS
+    if (this.opts[0]) {
+      let i
+      for (i = this.childs.length; i--;) {
+        if (this.childs[i].name === 'img') break
+      }
+      if (i !== -1) {
+        this.observer = uni.createIntersectionObserver(this).relativeToViewport({
+          top: 500,
+          bottom: 500
+        })
+        this.observer.observe('._img', res => {
+          if (res.intersectionRatio) {
+            this.$set(this.ctrl, 'load', 1)
+            this.observer.disconnect()
+          }
+        })
+      }
+    }
+    // #endif
+  },
+  beforeDestroy () {
+    // #ifdef H5 || APP-PLUS
+    if (this.observer) {
+      this.observer.disconnect()
+    }
+    // #endif
+  },
+  methods:{
+    // #ifdef MP-WEIXIN
+    toJSON () { },
+    // #endif
+    /**
+     * @description 播放视频事件
+     * @param {Event} e
+     */
+    play (e) {
+      this.root.$emit('play')
+      // #ifndef APP-PLUS
+      if (this.root.pauseVideo) {
+        let flag = false
+        const id = e.target.id
+        for (let i = this.root._videos.length; i--;) {
+          if (this.root._videos[i].id === id) {
+            flag = true
+          } else {
+            this.root._videos[i].pause() // 自动暂停其他视频
+          }
+        }
+        // 将自己加入列表
+        if (!flag) {
+          const ctx = uni.createVideoContext(id
+            // #ifndef MP-BAIDU
+            , this
+            // #endif
+          )
+          ctx.id = id
+          this.root._videos.push(ctx)
+        }
+      }
+      // #endif
+    },
+
+    /**
+     * @description 图片点击事件
+     * @param {Event} e
+     */
+    imgTap (e) {
+      const node = this.childs[e.currentTarget.dataset.i]
+      if (node.a) {
+        this.linkTap(node.a)
+        return
+      }
+      if (node.attrs.ignore) return
+      // #ifdef H5 || APP-PLUS
+      node.attrs.src = node.attrs.src || node.attrs['data-src']
+      // #endif
+      this.root.$emit('imgtap', node.attrs)
+      // 自动预览图片
+      if (this.root.previewImg) {
+        uni.previewImage({
+          // #ifdef MP-WEIXIN
+          showmenu: this.root.showImgMenu,
+          // #endif
+          // #ifdef MP-ALIPAY
+          enablesavephoto: this.root.showImgMenu,
+          enableShowPhotoDownload: this.root.showImgMenu,
+          // #endif
+          current: parseInt(node.attrs.i),
+          urls: this.root.imgList
+        })
+      }
+    },
+
+    /**
+     * @description 图片长按
+     */
+    imgLongTap (e) {
+      // #ifdef APP-PLUS
+      const attrs = this.childs[e.currentTarget.dataset.i].attrs
+      if (this.opts[3] && !attrs.ignore) {
+        uni.showActionSheet({
+          itemList: ['保存图片'],
+          success: () => {
+            const save = path => {
+              uni.saveImageToPhotosAlbum({
+                filePath: path,
+                success () {
+                  uni.showToast({
+                    title: '保存成功'
+                  })
+                }
+              })
+            }
+            if (this.root.imgList[attrs.i].startsWith('http')) {
+              uni.downloadFile({
+                url: this.root.imgList[attrs.i],
+                success: res => save(res.tempFilePath)
+              })
+            } else {
+              save(this.root.imgList[attrs.i])
+            }
+          }
+        })
+      }
+      // #endif
+    },
+
+    /**
+     * @description 图片加载完成事件
+     * @param {Event} e
+     */
+    imgLoad (e) {
+      const i = e.currentTarget.dataset.i
+      /* #ifndef H5 || (APP-PLUS && VUE2) */
+      if (!this.childs[i].w) {
+        // 设置原宽度
+        this.$set(this.ctrl, i, e.detail.width)
+      } else /* #endif */ if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) {
+        // 加载完毕,取消加载中占位图
+        this.$set(this.ctrl, i, 1)
+      }
+    },
+
+    /**
+     * @description 链接点击事件
+     * @param {Event} e
+     */
+    linkTap (e) {
+      const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}
+      const attrs = node.attrs || e
+      const href = attrs.href
+      this.root.$emit('linktap', Object.assign({
+        innerText: this.root.getText(node.children || []) // 链接内的文本内容
+      }, attrs))
+      if (href) {
+        if (href[0] === '#') {
+          // 跳转锚点
+          this.root.navigateTo(href.substring(1)).catch(() => { })
+        } else if (href.split('?')[0].includes('://')) {
+          // 复制外部链接
+          if (this.root.copyLink) {
+            // #ifdef H5
+            window.open(href)
+            // #endif
+            // #ifdef MP
+            uni.setClipboardData({
+              data: href,
+              success: () =>
+                uni.showToast({
+                  title: '链接已复制'
+                })
+            })
+            // #endif
+            // #ifdef APP-PLUS
+            plus.runtime.openWeb(href)
+            // #endif
+          }
+        } else {
+          // 跳转页面
+          uni.navigateTo({
+            url: href,
+            fail () {
+              uni.switchTab({
+                url: href,
+                fail () { }
+              })
+            }
+          })
+        }
+      }
+    },
+
+    /**
+     * @description 错误事件
+     * @param {Event} e
+     */
+    mediaError (e) {
+      const i = e.currentTarget.dataset.i
+      const node = this.childs[i]
+      // 加载其他源
+      if (node.name === 'video' || node.name === 'audio') {
+        let index = (this.ctrl[i] || 0) + 1
+        if (index > node.src.length) {
+          index = 0
+        }
+        if (index < node.src.length) {
+          this.$set(this.ctrl, i, index)
+          return
+        }
+      } else if (node.name === 'img') {
+        // #ifdef H5 && VUE3
+        if (this.opts[0] && !this.ctrl.load) return
+        // #endif
+        // 显示错误占位图
+        if (this.opts[2]) {
+          this.$set(this.ctrl, i, -1)
+        }
+      }
+      if (this.root) {
+        this.root.$emit('error', {
+          source: node.name,
+          attrs: node.attrs,
+          // #ifndef H5 && VUE3
+          errMsg: e.detail.errMsg
+          // #endif
+        })
+      }
+    }
+  }
+}
+</script>
+<style>
+/* a 标签默认效果 */
+._a {
+  padding: 1.5px 0 1.5px 0;
+  color: #366092;
+  word-break: break-all;
+}
+
+/* a 标签点击态效果 */
+._hover {
+  text-decoration: underline;
+  opacity: 0.7;
+}
+
+/* 图片默认效果 */
+._img {
+  max-width: 100%;
+  -webkit-touch-callout: none;
+}
+
+/* 内部样式 */
+
+._block {
+  display: block;
+}
+
+._b,
+._strong {
+  font-weight: bold;
+}
+
+._code {
+  font-family: monospace;
+}
+
+._del {
+  text-decoration: line-through;
+}
+
+._em,
+._i {
+  font-style: italic;
+}
+
+._h1 {
+  font-size: 2em;
+}
+
+._h2 {
+  font-size: 1.5em;
+}
+
+._h3 {
+  font-size: 1.17em;
+}
+
+._h5 {
+  font-size: 0.83em;
+}
+
+._h6 {
+  font-size: 0.67em;
+}
+
+._h1,
+._h2,
+._h3,
+._h4,
+._h5,
+._h6 {
+  display: block;
+  font-weight: bold;
+}
+
+._image {
+  height: 1px;
+}
+
+._ins {
+  text-decoration: underline;
+}
+
+._li {
+  display: list-item;
+}
+
+._ol {
+  list-style-type: decimal;
+}
+
+._ol,
+._ul {
+  display: block;
+  padding-left: 40px;
+  margin: 1em 0;
+}
+
+._q::before {
+  content: '"';
+}
+
+._q::after {
+  content: '"';
+}
+
+._sub {
+  font-size: smaller;
+  vertical-align: sub;
+}
+
+._sup {
+  font-size: smaller;
+  vertical-align: super;
+}
+
+._thead,
+._tbody,
+._tfoot {
+  display: table-row-group;
+}
+
+._tr {
+  display: table-row;
+}
+
+._td,
+._th {
+  display: table-cell;
+  vertical-align: middle;
+}
+
+._th {
+  font-weight: bold;
+  text-align: center;
+}
+
+._ul {
+  list-style-type: disc;
+}
+
+._ul ._ul {
+  margin: 0;
+  list-style-type: circle;
+}
+
+._ul ._ul ._ul {
+  list-style-type: square;
+}
+
+._abbr,
+._b,
+._code,
+._del,
+._em,
+._i,
+._ins,
+._label,
+._q,
+._span,
+._strong,
+._sub,
+._sup {
+  display: inline;
+}
+
+/* #ifdef APP-PLUS */
+._video {
+  width: 300px;
+  height: 225px;
+}
+/* #endif */
+</style>

File diff suppressed because it is too large
+ 1280 - 0
uni_modules/mp-html/components/mp-html/parser.js


+ 79 - 0
uni_modules/mp-html/package.json

@@ -0,0 +1,79 @@
+{
+    "id": "mp-html",
+    "displayName": "mp-html 富文本组件【全端支持,可编辑】",
+    "version": "v2.3.1",
+    "description": "一个强大的富文本组件,高效轻量,功能丰富",
+    "keywords": [
+        "富文本",
+        "编辑器",
+        "html",
+        "rich-text",
+        "editor"
+    ],
+    "repository": "https://github.com/jin-yufeng/mp-html",
+    "dcloudext": {
+        "category": [
+            "前端组件",
+            "通用组件"
+        ],
+        "sale": {
+            "regular": {
+                "price": "0.00"
+            },
+            "sourcecode": {
+                "price": "0.00"
+            }
+        },
+        "contact": {
+            "qq": ""
+        },
+        "declaration": {
+            "ads": "无",
+            "data": "无",
+            "permissions": "无"
+        },
+        "npmurl": "https://www.npmjs.com/package/mp-html"
+    },
+    "uni_modules": {
+        "platforms": {
+            "cloud": {
+                "tcb": "y",
+                "aliyun": "y"
+            },
+            "client": {
+                "App": {
+                    "app-vue": "y",
+                    "app-nvue": "y"
+                },
+                "H5-mobile": {
+                    "Safari": "y",
+                    "Android Browser": "y",
+                    "微信浏览器(Android)": "y",
+                    "QQ浏览器(Android)": "y"
+                },
+                "H5-pc": {
+                    "Chrome": "y",
+                    "IE": "u",
+                    "Edge": "y",
+                    "Firefox": "y",
+                    "Safari": "y"
+                },
+                "小程序": {
+                    "微信": "y",
+                    "阿里": "y",
+                    "百度": "y",
+                    "字节跳动": "y",
+                    "QQ": "y"
+                },
+                "快应用": {
+                    "华为": "y",
+                    "联盟": "y"
+                },
+                "Vue": {
+                    "vue2": "y",
+                    "vue3": "y"
+                }
+            }
+        }
+    }
+}

File diff suppressed because it is too large
+ 1 - 0
uni_modules/mp-html/static/app-plus/mp-html/js/handler.js


File diff suppressed because it is too large
+ 1 - 0
uni_modules/mp-html/static/app-plus/mp-html/js/uni.webview.min.js


+ 0 - 0
uni_modules/mp-html/static/app-plus/mp-html/local.html


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