Kaynağa Gözat

文物管家

shang 1 yıl önce
işleme
2a49418347
100 değiştirilmiş dosya ile 29323 ekleme ve 0 silme
  1. 4 0
      .gitignore
  2. 82 0
      App.vue
  3. 535 0
      README.md
  4. 538 0
      answer_pages/examine/answer.vue
  5. 180 0
      answer_pages/examine/history.vue
  6. 531 0
      answer_pages/examine/matchanswer.vue
  7. 463 0
      answer_pages/examine/review.vue
  8. 249 0
      answer_pages/examine/summary.vue
  9. 390 0
      answer_pages/examine/top.vue
  10. 292 0
      answer_pages/gift/detail.vue
  11. 463 0
      answer_pages/gift/exchange.vue
  12. 228 0
      answer_pages/gift/express_feed.vue
  13. 293 0
      answer_pages/gift/index.vue
  14. 356 0
      answer_pages/gift/order.vue
  15. 460 0
      answer_pages/gift/order_detail.vue
  16. 52 0
      answer_pages/home/activity.vue
  17. 332 0
      answer_pages/home/dashboard.vue
  18. 649 0
      answer_pages/home/index.vue
  19. 145 0
      answer_pages/home/welcome.vue
  20. 90 0
      answer_pages/user/autoLogin.vue
  21. 138 0
      answer_pages/user/changeCode.vue
  22. 244 0
      answer_pages/user/changePhone.vue
  23. 517 0
      answer_pages/user/login.vue
  24. 233 0
      answer_pages/user/mine.vue
  25. 53 0
      answer_pages/user/tips.vue
  26. 184 0
      colorui/animation.css
  27. 1226 0
      colorui/icon.css
  28. 4032 0
      colorui/main.css
  29. 35 0
      common/WXBizDataCrypt.js
  30. 1265 0
      common/WeCropper.js
  31. 0 0
      common/app.css
  32. 75 0
      common/cache.js
  33. 4 0
      common/city.area.js
  34. 187 0
      common/common.css
  35. 147 0
      common/common.scss
  36. 102 0
      common/dateTimePicker.js
  37. 493 0
      common/fa.mixin.js
  38. 40 0
      common/fa.style.mixin.js
  39. 63 0
      common/fa.weixin.mixin.js
  40. 352 0
      common/html-parser.js
  41. 157 0
      common/iconfont.css
  42. 385 0
      common/md5.js
  43. 72 0
      common/share.js
  44. 1448 0
      common/uni.css
  45. 465 0
      common/util.js
  46. 91 0
      components/FuWenBen/changelog.md
  47. 825 0
      components/FuWenBen/components/sp-editor/color-picker.vue
  48. 152 0
      components/FuWenBen/components/sp-editor/link-edit.vue
  49. 654 0
      components/FuWenBen/components/sp-editor/sp-editor.vue
  50. 238 0
      components/FuWenBen/icons/editor-icon.css
  51. BIN
      components/FuWenBen/icons/iconfont.ttf
  52. 82 0
      components/FuWenBen/package.json
  53. 269 0
      components/FuWenBen/readme.md
  54. 1 0
      components/FuWenBen/static/image-resize.min.js
  55. 8 0
      components/FuWenBen/static/quill.min.js
  56. 68 0
      components/FuWenBen/utils/index.js
  57. 115 0
      components/SCimage/SCimage.vue
  58. 161 0
      components/Segmented/Segmented.vue
  59. 275 0
      components/TouGao/TouGao.vue
  60. 204 0
      components/course-item/course-item.vue
  61. 137 0
      components/fa-array-download/fa-array-download.vue
  62. 140 0
      components/fa-array/fa-array.vue
  63. 59 0
      components/fa-captchaparts/fa-captchaparts.vue
  64. 88 0
      components/fa-check-radio/fa-check-radio.vue
  65. 485 0
      components/fa-editor/fa-editor.vue
  66. 355 0
      components/fa-selectpages/fa-selectpages.vue
  67. 302 0
      components/fa-selects/fa-selects.vue
  68. 45 0
      components/fa-switch/fa-switch.vue
  69. 224 0
      components/fa-upload-file/fa-upload-file.vue
  70. 97 0
      components/fa-upload-image/fa-upload-image.vue
  71. 203 0
      components/load-more/load-more.vue
  72. 99 0
      components/mapComponent/mapComponent.vue
  73. 153 0
      components/quick-cart/quick-cart.vue
  74. 170 0
      components/tki-tree/style.css
  75. 302 0
      components/tki-tree/tki-tree.vue
  76. 557 0
      config/api.js
  77. 647 0
      config/common.js
  78. 18 0
      config/config.js
  79. 83 0
      config/db.js
  80. 134 0
      index_fenbao/GuanLi/GouJian.vue
  81. 218 0
      index_fenbao/GuanLi/GuanLi.vue
  82. 314 0
      index_fenbao/GuanLi/XiangQing.vue
  83. 262 0
      index_fenbao/GuanLi/gengDuoFC.vue
  84. 418 0
      index_fenbao/GuanLi/zhiYuanGeRen.vue
  85. 362 0
      index_fenbao/HuoHuaLiYong/YanXue/HuoHuaLiYong.vue
  86. 271 0
      index_fenbao/HuoHuaLiYong/YanXue/XiangQing.vue
  87. 94 0
      index_fenbao/HuoHuaLiYong/YanXue/YanXue.vue
  88. 173 0
      index_fenbao/HuoHuaLiYong/YanXue/baoMing.vue
  89. 146 0
      index_fenbao/HuoHuaLiYong/YouJin/YouJin.vue
  90. 156 0
      index_fenbao/HuoHuaLiYong/YouJin/gengDuo.vue
  91. 179 0
      index_fenbao/HuoHuaLiYong/chanPin/chanPin.vue
  92. 108 0
      index_fenbao/HuoHuaLiYong/chanPin/chanPinXQ.vue
  93. 141 0
      index_fenbao/HuoHuaLiYong/chanPin/chengGuo.vue
  94. 169 0
      index_fenbao/HuoHuaLiYong/chanPin/gengDuoChengGuo.vue
  95. 166 0
      index_fenbao/HuoHuaLiYong/waJue/gengDuo.vue
  96. 151 0
      index_fenbao/HuoHuaLiYong/waJue/waJue.vue
  97. 96 0
      index_fenbao/XinWen/XinWen.vue
  98. 125 0
      index_fenbao/fuWu/FaLv/FaLv.vue
  99. 84 0
      index_fenbao/fuWu/FaLv/XiangQing.vue
  100. 0 0
      index_fenbao/fuWu/baoMing/baoMing.vue

+ 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';
+</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>
+```

+ 538 - 0
answer_pages/examine/answer.vue

@@ -0,0 +1,538 @@
+<template>
+	<view class="wrap">
+		<u-navbar title="开始答题" :placeholder="true" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<view class="main" v-if="hasLoad">
+			<view class="process">
+				<view class="b-state">
+					<view class="b-count">
+						<view class="s-tit1">答题进度</view>
+						<view class="s-num">{{ state.session_seq }}</view>
+						<view class="s-tit2">/{{ state.total_count }}</view>
+					</view>
+					<view class="b-time" v-if="state.session_expired != -1">
+						<view class="s-tit">剩余时间</view>
+						<view class="s-time">{{ costTime(state.session_expired, 'countdown') }}</view>
+					</view>
+				</view>
+				<view class="b-bar">
+					<view class="s-percent" :style="barPercent"></view>
+				</view>
+			</view>
+			<view class="answer">
+				<view class="b-head">
+					<view class="s-type">{{ state.type_text }}</view>
+					<view class="s-time"></view>
+				</view>
+				<view class="b-title">
+					<view class="s-num">{{ state.session_seq }}.</view>
+					<text class="s-title">
+						{{ state.title }}
+						<text class="s-score">({{ state.score }}分)</text>
+					</text>
+				</view>
+				<view class="b-title" v-if="state.image" style="align-items: center; justify-content: center">
+					<u--image :width="200" mode="scaleToFill" :src="state.image"></u--image>
+				</view>
+				<view class="b-options">
+					<view class="b-item" v-for="(item, index) in state.optionList" :key="index" @click="onChoose(index)" :class="[optionState(index)]">
+						<view class="s-num">{{ optionNum(index) }}.</view>
+						<view class="s-cont">{{ item.label }}</view>
+						<view class="s-state iconfont" :class="[optionIcon(index)]"></view>
+					</view>
+				</view>
+				<view class="b-result" :class="resultIcon" v-if="state.state == 2 && activity_type == 2">
+					<view class="b-right">
+						<view class="s-tit">正确答案:</view>
+						<view class="s-cont">{{ trueAnswer }}</view>
+					</view>
+					<view class="b-mine">
+						<view class="s-tit">已选答案:</view>
+						<view class="s-cont">{{ userAnswer }}</view>
+					</view>
+				</view>
+				<view class="b-explain" v-if="state.state == 2 && state.explain && activity_type == 2">
+					<text>解析:{{ state.explain }}</text>
+				</view>
+				<view class="b-action">
+					<view class="b-next" @click="onFinish" v-if="state.session_status == 2">查看成绩</view>
+					<view class="b-next" @click="onFinish" v-else-if="state.session_status == 3">超时结束</view>
+					<view class="b-submit" :class="btnState" @click="onSubmitAnswer" v-else-if="state.state == 1">提交答案</view>
+					<view class="b-next" :class="btnState" @click="onGetNew" v-else-if="state.state == 2">进入下一题</view>
+					<view class="b-home" @click="onHome">稍后再答</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 加载中 -->
+		<load-more :loadingType="loadingType" :loadingText="loadingText" color="#ffffff" :top="500"></load-more>
+	</view>
+</template>
+
+<script>
+import { getNew, getResult, submitAnswer } from '@/service/api/examine.js';
+// import mixinsCommon from '@/mixins/common.js';
+// import mixinsAuth from '../../mixins/auth.js';
+export default {
+	// mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			hasLoad: false,
+			session_id: 0,
+			activity_type: 1, //默认为1比赛类型,不显示答案
+			state: {
+				optionList: [],
+				trueAnswer: [],
+				userAnswer: []
+			},
+			disable: {
+				submit: false
+			},
+			loadingType: 1,
+			loadingText: ''
+		};
+	},
+	onLoad(options) {
+		console.log('onLoad', options);
+		this.session_id = options.session_id;
+		this.activity_type = options.activity_type;
+		this.onGetNew();
+
+		// 倒计时
+		if (this.state.session_expired != -1) {
+			setInterval(() => {
+				if (this.state.session_expired > 0) {
+					this.state.session_expired--;
+				}
+			}, 1000);
+		}
+	},
+	computed: {
+		barPercent() {
+			return 'width: ' + (this.state.session_seq * 100) / this.state.total_count + '%';
+		},
+		optionNum() {
+			return (index) => {
+				return String.fromCharCode(64 + (index + 1));
+			};
+		},
+		optionState() {
+			return (index) => {
+				console.log('optionState', index);
+				let optionRight = false;
+				let optionWrong = false;
+				if (this.state.state == 2 && this.activity_type == 2) {
+					if (this.state.type === 'radio' || this.state.type === 'yesorno') {
+						if (this.state.trueAnswer.includes(index)) {
+							optionRight = true;
+						} else if (this.state.userAnswer.includes(index)) {
+							optionWrong = true;
+						}
+					} else {
+						if (this.state.trueAnswer.includes(index)) {
+							optionRight = true;
+						} else if (
+							(this.state.userAnswer.includes(index) && !this.state.trueAnswer.includes(index)) ||
+							(this.state.trueAnswer.includes(index) && !this.state.userAnswer.includes(index))
+						) {
+							optionWrong = true;
+						}
+					}
+				}
+				let ret = '';
+				ret = this.state.userAnswer.includes(index) ? 'f-select' : '';
+				ret += optionRight ? ' f-right' : '';
+				ret += optionWrong ? ' f-wrong' : '';
+				return ret;
+
+				// return {
+				// 	'f-select': this.state.userAnswer.includes(index),
+				// 	'f-right': optionRight,
+				// 	'f-wrong': optionWrong
+				// }
+			};
+		},
+		optionIcon() {
+			return (index) => {
+				console.log('optionIcon', index);
+				return {
+					'icon-roundcheck': this.state.userAnswer.includes(index)
+					// 'icon-roundcheckfill': this.state.trueAnswer.includes(index),
+				};
+			};
+		},
+		resultIcon() {
+			let ret = '';
+			ret += this.state.result == 2 ? ' f-right' : '';
+			ret += this.state.result == 3 ? ' f-wrong' : '';
+			ret += this.state.result == 4 ? ' f-timeout' : '';
+			ret += this.state.result == 5 ? ' f-timeout-end' : '';
+			return ret;
+
+			// return {
+			// 	'f-right': this.state.result == 2,
+			// 	'f-wrong': this.state.result == 3,
+			// 	'f-timeout': this.state.result == 4,
+			// 	'f-timeout-end': this.state.result == 5,
+			// }
+		},
+		trueAnswer() {
+			console.log('1', this.state.trueAnswer);
+			if (!this.state.trueAnswer) this.state.trueAnswer = [];
+			let options = this.state.trueAnswer.map((item) => {
+				return String.fromCharCode(64 + (parseInt(item) + 1));
+			});
+			return options.join('、');
+		},
+		userAnswer() {
+			console.log('2', this.state.trueAnswer);
+			if (!this.state.trueAnswer) this.state.trueAnswer = [];
+			let options = this.state.userAnswer.map((item) => {
+				return String.fromCharCode(64 + (parseInt(item) + 1));
+			});
+			return options.join('、');
+		},
+		btnState() {
+			return this.disable.submit === true || this.state.userAnswer.length === 0 ? 'disable' : '';
+			// return {
+			// 	'disable': this.disable.submit === true || this.state.userAnswer.length === 0
+			// }
+		}
+	},
+
+	methods: {
+		onChoose(index) {
+			if (this.state.state != 1) {
+				return;
+			}
+			let valueIndex = this.state.userAnswer.indexOf(index);
+			if (valueIndex !== -1) {
+				this.state.userAnswer.splice(valueIndex, 1);
+			} else {
+				if (this.state.type === 'checkbox') {
+					this.state.userAnswer.push(index);
+				} else {
+					this.state.userAnswer = [index];
+				}
+			}
+			if (this.state.userAnswer.length === 0) {
+				this.disable.submit = true;
+			} else {
+				this.disable.submit = false;
+			}
+		},
+		onGetNew() {
+			getNew(this.session_id).then(([err, res]) => {
+				console.log('getNew', err, res);
+				if (!err) {
+					this.hasLoad = true;
+					this.loadingType = -1;
+					this.state = res;
+					this.state.userAnswer = []; // 初始化提交的答案
+				} else {
+					if (err.data.code == 502) {
+						// 超时结束
+						this.onFinish();
+					} else {
+						if (!this.hasLoad) {
+							this.loadingType = 3;
+							this.loadingText = err.data.msg || '加载失败';
+						}
+					}
+				}
+			});
+		},
+		onSubmitAnswer() {
+			if (this.disable.submit) {
+				return;
+			}
+			if (this.state.userAnswer.length === 0) {
+				return this.$logic.showToast('还未选择答案');
+			}
+			this.disable.submit = true;
+			submitAnswer(this.state.id, this.state.userAnswer.join(',')).then(([err, res]) => {
+				console.log('submitAnswer', err, res);
+				this.disable.submit = false;
+				if (!err) {
+					this.state = res;
+					if (this.state.session_status == 3) {
+						this.$logic.showToast('答题会话已超时结束');
+					}
+				}
+			});
+		},
+		onFinish() {
+			uni.navigateTo({
+				url: '/answer_pages/examine/summary?session_id=' + this.session_id + '&activity_type=' + this.activity_type
+			});
+		},
+		onHome() {
+			uni.reLaunch({
+				url: '/answer_pages/home/index'
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+// page {
+// 	padding-bottom: env(safe-area-inset-bottom);
+// 	background: #da5650;
+// }
+
+.wrap {
+	background: #da5650;
+}
+
+.main {
+	padding: 40upx;
+}
+
+.process {
+	display: flex;
+	flex-direction: column;
+
+	.b-state {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		color: #fff;
+
+		.b-count {
+			display: flex;
+			align-items: center;
+
+			.s-tit1,
+			.s-tit2 {
+				font-size: 24upx;
+			}
+
+			.s-num {
+				margin: 0 10upx;
+				font-size: 32upx;
+			}
+		}
+
+		.b-time {
+			display: flex;
+			align-items: center;
+
+			.s-tit {
+				font-size: 24upx;
+			}
+
+			.s-time {
+				margin-left: 10upx;
+				font-size: 28upx;
+			}
+		}
+	}
+
+	.b-bar {
+		margin-top: 10upx;
+		background: rgba(255, 255, 255, 0.5);
+		width: 100%;
+		height: 24upx;
+		position: relative;
+
+		.s-percent {
+			position: absolute;
+			left: 0;
+			bottom: 0;
+			width: 30%;
+			height: 24upx;
+			display: inline-block;
+			background: #fff;
+		}
+	}
+}
+
+.answer {
+	margin-top: 50upx;
+	padding: 40upx;
+	background: #fff;
+	border-radius: 20upx;
+	overflow: hidden;
+	display: flex;
+	flex-direction: column;
+
+	.b-head {
+		display: flex;
+		align-items: center;
+		// justify-content: space-around;
+
+		.s-type {
+			width: fit-content;
+			background: #da5650;
+			padding: 0 20upx;
+			font-size: 28upx;
+			color: #fff;
+			height: 50upx;
+			line-height: 50upx;
+		}
+
+		.s-time {
+			/* 题目剩余时间 */
+		}
+	}
+
+	.b-title {
+		margin-top: 20upx;
+		font-size: 30upx;
+		display: flex;
+
+		.s-num {
+			font-weight: bold;
+		}
+
+		.s-title {
+			margin-left: 10upx;
+			flex: 1;
+		}
+
+		.s-score {
+			margin-left: 20upx;
+			color: #666;
+		}
+	}
+
+	.b-options {
+		margin-top: 40upx;
+
+		.b-item {
+			margin-bottom: 20upx;
+			padding: 20upx;
+			font-size: 28upx;
+			display: flex;
+			// align-items: center;
+			background: #f7f7f7;
+
+			&:last-child {
+				margin-bottom: 0;
+			}
+
+			&.f-select {
+				background: #ecf3fe;
+				color: #338ada;
+			}
+
+			&.f-right {
+				background: #ecf6f0;
+				color: #1eab69;
+			}
+
+			&.f-wrong {
+				background: #ffebeb;
+				color: #d13b42;
+			}
+
+			.s-num {
+				font-weight: bold;
+			}
+
+			.s-cont {
+				margin-left: 10upx;
+				flex: 1;
+			}
+
+			.s-state {
+				margin-top: 2upx;
+				margin-left: 10upx;
+				font-size: 32upx;
+			}
+		}
+	}
+
+	.b-result {
+		padding: 80upx 0;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		background-repeat: no-repeat;
+		background-size: 180upx auto;
+		background-position: center center;
+
+		&.f-right {
+			background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/result_right.png);
+		}
+
+		&.f-wrong {
+			background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/result_wrong.png);
+		}
+
+		&.f-timeout {
+			background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/result_timeout.png);
+		}
+
+		&.f-timeout-end {
+			background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/result_timeout_end.png);
+		}
+
+		.b-right,
+		.b-mine {
+			display: flex;
+			align-items: center;
+
+			.s-tit {
+				font-size: 26upx;
+				color: #333;
+			}
+
+			.s-cont {
+				margin-left: 10upx;
+				font-weight: bold;
+				color: #da5650;
+				font-size: 36upx;
+			}
+		}
+	}
+
+	.b-explain {
+		font-size: 28upx;
+		color: #808080;
+	}
+
+	.b-action {
+		margin-top: 50upx;
+		display: flex;
+		flex-direction: column;
+
+		.b-submit {
+			height: 100upx;
+			line-height: 100upx;
+			text-align: center;
+			color: #fff;
+			font-size: 32upx;
+			background: #da5650;
+			border-radius: 50upx;
+			letter-spacing: 10upx;
+
+			&.disable {
+				background: rgba($color: #da5650, $alpha: 0.7);
+			}
+		}
+
+		.b-next {
+			height: 100upx;
+			line-height: 100upx;
+			text-align: center;
+			color: #fff;
+			font-size: 32upx;
+			background: #ff8d1a;
+			border-radius: 50upx;
+			letter-spacing: 10upx;
+
+			&.disable {
+				background: rgba($color: #ff8d1a, $alpha: 0.2);
+			}
+		}
+
+		.b-home {
+			margin-top: 40upx;
+			text-align: center;
+			color: #808080;
+			font-size: 28upx;
+		}
+	}
+}
+</style>

+ 180 - 0
answer_pages/examine/history.vue

@@ -0,0 +1,180 @@
+<template>
+	<view class="wrap">
+		<u-navbar title="答题历史" :placeholder="true" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<view class="main">
+			<view class="questions" v-if="state.items.length > 0">
+				<view class="b-item" v-for="(item, index) in state.items" :key="index" @click="onReview(item.id)">
+					<view class="b-main">
+						<view class="b-title">
+							<view class="s-num">{{ index + 1 }}.</view>
+							<text class="s-cont">【{{ item.type_text }}】{{ item.title }}</text>
+						</view>
+						<view class="b-info">
+							<view class="s-flag f-right" v-if="item.result == 2">{{ item.result_text }}</view>
+							<view class="s-flag f-wrong" v-else-if="item.result == 3">{{ item.result_text }}</view>
+							<view class="s-flag f-timeout" v-else-if="item.result == 4">{{ item.result_text }}</view>
+							<view class="s-flag f-timeout-end" v-else-if="item.result == 5">{{ item.result_text }}</view>
+							<view class="s-time">{{ costTime(item.cost_time) }}</view>
+						</view>
+					</view>
+					<view class="b-more">
+						<view class="iconfont icon-more"></view>
+					</view>
+				</view>
+			</view>
+			<!-- 加载中 -->
+			<load-more :loadingType="loadingType" :loadingText="loadingText" color="#ffffff"></load-more>
+		</view>
+	</view>
+</template>
+
+<script>
+import { getHistory } from '@/service/api/examine.js';
+import mixinsCommon from '@/mixins/common.js';
+import mixinsAuth from '../../mixins/auth.js';
+export default {
+	mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			state: {
+				items: []
+			},
+			loadingType: 1,
+			loadingText: ''
+		};
+	},
+	onLoad(options) {
+		this.sessionId = options.session_id;
+		this.loadHistory(true);
+	},
+	onReachBottom() {
+		if (this.loadingType !== 1 && this.loadingType !== 2) {
+			this.loadHistory();
+		}
+	},
+	methods: {
+		loadHistory(refresh) {
+			console.log('loadHistory', refresh);
+			if (refresh) {
+				this.page = 1;
+				this.state.items = [];
+			} else {
+				this.page++;
+			}
+			this.loadingType = 1;
+			this.loadingText = '';
+			getHistory(this.sessionId, this.page, 10).then(([err, res]) => {
+				console.log('getHistory', err, res);
+				this.loadingType = -1;
+				if (!err) {
+					if (res.items.length > 0) {
+						this.state.items = [...this.state.items, ...res.items];
+					} else {
+						this.loadingType = 2;
+						if (this.page == 1) {
+							this.loadingText = '暂无数据';
+						}
+						this.page--; // 重置分页
+					}
+				} else {
+					this.loadingType = 3;
+				}
+			});
+		},
+		onReview(recordId) {
+			uni.navigateTo({
+				url: '/answer_pages/examine/review?record_id=' + recordId
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+// page {
+// 	padding-bottom: env(safe-area-inset-bottom);
+// 	background: #da5650;
+// }
+
+.wrap {
+	background: #da5650;
+}
+
+.main {
+	padding: 40upx;
+}
+
+.questions {
+	.b-item {
+		display: flex;
+		margin-bottom: 40upx;
+		background: #fff;
+		border-radius: 20upx;
+		padding: 20upx;
+
+		.b-main {
+			flex: 1;
+			display: flex;
+			flex-direction: column;
+
+			.b-title {
+				display: flex;
+
+				.s-num {
+					font-size: 28upx;
+				}
+
+				.s-cont {
+					margin-left: 0upx;
+					font-size: 28upx;
+				}
+			}
+
+			.b-info {
+				margin-top: 10upx;
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+
+				.s-flag {
+					font-size: 24upx;
+					padding: 0 10upx;
+					height: 50upx;
+					line-height: 50upx;
+					color: #fff;
+					border-radius: 10upx;
+
+					&.f-right {
+						background: #1eab69;
+					}
+
+					&.f-wrong {
+						background: #d13b42;
+					}
+
+					&.f-timeout {
+						background: #ff8d1a;
+					}
+
+					&.f-timeout-end {
+						background: #338ada;
+					}
+				}
+
+				.s-time {
+					color: #da5650;
+					font-size: 24upx;
+				}
+			}
+		}
+
+		.b-more {
+			margin-left: 10upx;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			color: #e5e5e5;
+		}
+	}
+}
+</style>

+ 531 - 0
answer_pages/examine/matchanswer.vue

@@ -0,0 +1,531 @@
+<template>
+	<view class="wrap">
+		<u-navbar title="开始答题" :placeholder="true" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<view class="main" v-if="hasLoad">
+			<view class="process">
+				<view class="b-state">
+					<view class="b-count">
+						<view class="s-tit1">答题进度</view>
+						<view class="s-num">{{ state.session_seq }}</view>
+						<view class="s-tit2">/{{ state.total_count }}</view>
+					</view>
+					<view class="b-time" v-if="state.session_expired != -1">
+						<view class="s-tit">剩余时间</view>
+						<view class="s-time">{{ costTime(state.session_expired, 'countdown') }}</view>
+					</view>
+				</view>
+				<view class="b-bar">
+					<view class="s-percent" :style="barPercent"></view>
+				</view>
+			</view>
+			<view class="answer">
+				<view class="b-head">
+					<view class="s-type">{{ state.type_text }}</view>
+					<view class="s-time"></view>
+				</view>
+				<view class="b-title">
+					<view class="s-num">{{ state.session_seq }}.</view>
+					<text class="s-title">
+						{{ state.title }}
+						<text class="s-score">({{ state.score }}分)</text>
+					</text>
+				</view>
+				<view class="b-options">
+					<view class="b-item" v-for="(item, index) in state.optionList" :key="index" @click="onChoose(index)" :class="[optionState(index)]">
+						<view class="s-num">{{ optionNum(index) }}.</view>
+						<view class="s-cont">{{ item.label }}</view>
+						<view class="s-state iconfont" :class="[optionIcon(index)]"></view>
+					</view>
+				</view>
+				<!-- <view class="b-result" :class="resultIcon" v-if="state.state == 2">
+					<view class="b-right">
+						<view class="s-tit">正确答案:</view>
+						<view class="s-cont">{{trueAnswer}}</view>
+					</view>
+					<view class="b-mine">
+						<view class="s-tit">已选答案:</view>
+						<view class="s-cont">{{userAnswer}}</view>
+					</view>
+				</view> -->
+				<!-- <view class="b-explain" v-if="state.state == 2 && state.explain">
+					<text>解析:{{state.explain}}</text>
+				</view> -->
+				<view class="b-action">
+					<view class="b-next" @click="onFinish" v-if="state.session_status == 2">查看成绩</view>
+					<view class="b-next" @click="onFinish" v-else-if="state.session_status == 3">超时结束</view>
+					<view class="b-submit" :class="btnState" @click="onSubmitAnswer" v-else-if="state.state == 1">提交答案</view>
+					<view class="b-next" :class="btnState" @click="onGetNew" v-else-if="state.state == 2">进入下一题</view>
+					<view class="b-home" @click="onHome">稍后再答</view>
+				</view>
+			</view>
+		</view>
+		<!-- 加载中 -->
+		<load-more :loadingType="loadingType" :loadingText="loadingText" color="#ffffff" :top="500"></load-more>
+	</view>
+</template>
+
+<script>
+import { getNew, getResult, submitAnswer } from '@/service/api/examine.js';
+import mixinsCommon from '@/mixins/common.js';
+import mixinsAuth from '../../mixins/auth.js';
+export default {
+	mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			hasLoad: false,
+			session_id: 0,
+			state: {
+				optionList: [],
+				trueAnswer: [],
+				userAnswer: []
+			},
+			disable: {
+				submit: false
+			},
+			loadingType: 1,
+			loadingText: ''
+		};
+	},
+	onLoad(options) {
+		console.log('onLoad', options);
+		this.session_id = options.session_id;
+		this.onGetNew();
+
+		// 倒计时
+		if (this.state.session_expired != -1) {
+			setInterval(() => {
+				if (this.state.session_expired > 0) {
+					this.state.session_expired--;
+				}
+			}, 1000);
+		}
+	},
+	computed: {
+		barPercent() {
+			return 'width: ' + (this.state.session_seq * 100) / this.state.total_count + '%';
+		},
+		optionNum() {
+			return (index) => {
+				return String.fromCharCode(64 + (index + 1));
+			};
+		},
+		optionState() {
+			return (index) => {
+				console.log('optionState', index);
+				let optionRight = false;
+				let optionWrong = false;
+				if (this.state.state == 2) {
+					if (this.state.type === 'radio' || this.state.type === 'yesorno') {
+						if (this.state.trueAnswer.includes(index)) {
+							optionRight = true;
+						} else if (this.state.userAnswer.includes(index)) {
+							optionWrong = true;
+						}
+					} else {
+						if (this.state.trueAnswer.includes(index)) {
+							optionRight = true;
+						} else if (
+							(this.state.userAnswer.includes(index) && !this.state.trueAnswer.includes(index)) ||
+							(this.state.trueAnswer.includes(index) && !this.state.userAnswer.includes(index))
+						) {
+							optionWrong = true;
+						}
+					}
+				}
+				let ret = '';
+				ret = this.state.userAnswer.includes(index) ? 'f-select' : '';
+				ret += optionRight ? ' f-right' : '';
+				ret += optionWrong ? ' f-wrong' : '';
+				return ret;
+
+				// return {
+				// 	'f-select': this.state.userAnswer.includes(index),
+				// 	'f-right': optionRight,
+				// 	'f-wrong': optionWrong
+				// }
+			};
+		},
+		optionIcon() {
+			return (index) => {
+				console.log('optionIcon', index);
+				return {
+					'icon-roundcheck': this.state.userAnswer.includes(index)
+					// 'icon-roundcheckfill': this.state.trueAnswer.includes(index),
+				};
+			};
+		},
+		resultIcon() {
+			let ret = '';
+			ret += this.state.result == 2 ? ' f-right' : '';
+			ret += this.state.result == 3 ? ' f-wrong' : '';
+			ret += this.state.result == 4 ? ' f-timeout' : '';
+			ret += this.state.result == 5 ? ' f-timeout-end' : '';
+			return ret;
+
+			// return {
+			// 	'f-right': this.state.result == 2,
+			// 	'f-wrong': this.state.result == 3,
+			// 	'f-timeout': this.state.result == 4,
+			// 	'f-timeout-end': this.state.result == 5,
+			// }
+		},
+		trueAnswer() {
+			console.log('1', this.state.trueAnswer);
+			if (!this.state.trueAnswer) this.state.trueAnswer = [];
+			let options = this.state.trueAnswer.map((item) => {
+				return String.fromCharCode(64 + (parseInt(item) + 1));
+			});
+			return options.join('、');
+		},
+		userAnswer() {
+			console.log('2', this.state.trueAnswer);
+			if (!this.state.trueAnswer) this.state.trueAnswer = [];
+			let options = this.state.userAnswer.map((item) => {
+				return String.fromCharCode(64 + (parseInt(item) + 1));
+			});
+			return options.join('、');
+		},
+		btnState() {
+			return this.disable.submit === true || this.state.userAnswer.length === 0 ? 'disable' : '';
+			// return {
+			// 	'disable': this.disable.submit === true || this.state.userAnswer.length === 0
+			// }
+		}
+	},
+	methods: {
+		onChoose(index) {
+			if (this.state.state != 1) {
+				return;
+			}
+			let valueIndex = this.state.userAnswer.indexOf(index);
+			if (valueIndex !== -1) {
+				this.state.userAnswer.splice(valueIndex, 1);
+			} else {
+				if (this.state.type === 'checkbox') {
+					this.state.userAnswer.push(index);
+				} else {
+					this.state.userAnswer = [index];
+				}
+			}
+			if (this.state.userAnswer.length === 0) {
+				this.disable.submit = true;
+			} else {
+				this.disable.submit = false;
+			}
+		},
+		onGetNew() {
+			getNew(this.session_id).then(([err, res]) => {
+				console.log('getNew', err, res);
+				if (!err) {
+					this.hasLoad = true;
+					this.loadingType = -1;
+					this.state = res;
+					this.state.userAnswer = []; // 初始化提交的答案
+				} else {
+					if (err.data.code == 502) {
+						// 超时结束
+						this.onFinish();
+					} else {
+						if (!this.hasLoad) {
+							this.loadingType = 3;
+							this.loadingText = err.data.msg || '加载失败';
+						}
+					}
+				}
+			});
+		},
+		onSubmitAnswer() {
+			if (this.disable.submit) {
+				return;
+			}
+			if (this.state.userAnswer.length === 0) {
+				return this.$logic.showToast('还未选择答案');
+			}
+			this.disable.submit = true;
+			submitAnswer(this.state.id, this.state.userAnswer.join(',')).then(([err, res]) => {
+				console.log('submitAnswer', err, res);
+				this.disable.submit = false;
+				if (!err) {
+					this.state = res;
+					if (this.state.session_status == 3) {
+						this.$logic.showToast('答题会话已超时结束');
+					}
+				}
+			});
+		},
+		onFinish() {
+			uni.reLaunch({
+				url: '/answer_pages/examine/summary?session_id=' + this.session_id
+			});
+		},
+		onHome() {
+			uni.reLaunch({
+				url: '/answer_pages/home/index'
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+// page {
+// 	padding-bottom: env(safe-area-inset-bottom);
+// 	background: #da5650;
+// }
+
+.wrap {
+	background: #da5650;
+}
+
+.main {
+	padding: 40upx;
+}
+
+.process {
+	display: flex;
+	flex-direction: column;
+
+	.b-state {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		color: #fff;
+
+		.b-count {
+			display: flex;
+			align-items: center;
+
+			.s-tit1,
+			.s-tit2 {
+				font-size: 24upx;
+			}
+
+			.s-num {
+				margin: 0 10upx;
+				font-size: 32upx;
+			}
+		}
+
+		.b-time {
+			display: flex;
+			align-items: center;
+
+			.s-tit {
+				font-size: 24upx;
+			}
+
+			.s-time {
+				margin-left: 10upx;
+				font-size: 28upx;
+			}
+		}
+	}
+
+	.b-bar {
+		margin-top: 10upx;
+		background: rgba(255, 255, 255, 0.5);
+		width: 100%;
+		height: 24upx;
+		position: relative;
+
+		.s-percent {
+			position: absolute;
+			left: 0;
+			bottom: 0;
+			width: 30%;
+			height: 24upx;
+			display: inline-block;
+			background: #fff;
+		}
+	}
+}
+
+.answer {
+	margin-top: 50upx;
+	padding: 40upx;
+	background: #fff;
+	border-radius: 20upx;
+	overflow: hidden;
+	display: flex;
+	flex-direction: column;
+
+	.b-head {
+		display: flex;
+		align-items: center;
+		// justify-content: space-around;
+
+		.s-type {
+			width: fit-content;
+			background: #da5650;
+			padding: 0 20upx;
+			font-size: 28upx;
+			color: #fff;
+			height: 50upx;
+			line-height: 50upx;
+		}
+
+		.s-time {
+			/* 题目剩余时间 */
+		}
+	}
+
+	.b-title {
+		margin-top: 20upx;
+		font-size: 30upx;
+		display: flex;
+
+		.s-num {
+			font-weight: bold;
+		}
+
+		.s-title {
+			margin-left: 10upx;
+			flex: 1;
+		}
+
+		.s-score {
+			margin-left: 20upx;
+			color: #666;
+		}
+	}
+
+	.b-options {
+		margin-top: 40upx;
+
+		.b-item {
+			margin-bottom: 20upx;
+			padding: 20upx;
+			font-size: 28upx;
+			display: flex;
+			// align-items: center;
+			background: #f7f7f7;
+
+			&:last-child {
+				margin-bottom: 0;
+			}
+
+			&.f-select {
+				background: #ecf3fe;
+				color: #338ada;
+			}
+
+			&.f-right {
+				background: #ecf6f0;
+				color: #1eab69;
+			}
+
+			&.f-wrong {
+				background: #ffebeb;
+				color: #d13b42;
+			}
+
+			.s-num {
+				font-weight: bold;
+			}
+
+			.s-cont {
+				margin-left: 10upx;
+				flex: 1;
+			}
+
+			.s-state {
+				margin-top: 2upx;
+				margin-left: 10upx;
+				font-size: 32upx;
+			}
+		}
+	}
+
+	.b-result {
+		padding: 80upx 0;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		background-repeat: no-repeat;
+		background-size: 180upx auto;
+		background-position: center center;
+
+		&.f-right {
+			background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/result_right.png);
+		}
+
+		&.f-wrong {
+			background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/result_wrong.png);
+		}
+
+		&.f-timeout {
+			background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/result_timeout.png);
+		}
+
+		&.f-timeout-end {
+			background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/result_timeout_end.png);
+		}
+
+		.b-right,
+		.b-mine {
+			display: flex;
+			align-items: center;
+
+			.s-tit {
+				font-size: 26upx;
+				color: #333;
+			}
+
+			.s-cont {
+				margin-left: 10upx;
+				font-weight: bold;
+				color: #da5650;
+				font-size: 36upx;
+			}
+		}
+	}
+
+	.b-explain {
+		font-size: 28upx;
+		color: #808080;
+	}
+
+	.b-action {
+		margin-top: 50upx;
+		display: flex;
+		flex-direction: column;
+
+		.b-submit {
+			height: 100upx;
+			line-height: 100upx;
+			text-align: center;
+			color: #fff;
+			font-size: 32upx;
+			background: #da5650;
+			border-radius: 50upx;
+			letter-spacing: 10upx;
+
+			&.disable {
+				background: rgba($color: #da5650, $alpha: 0.7);
+			}
+		}
+
+		.b-next {
+			height: 100upx;
+			line-height: 100upx;
+			text-align: center;
+			color: #fff;
+			font-size: 32upx;
+			background: #ff8d1a;
+			border-radius: 50upx;
+			letter-spacing: 10upx;
+
+			&.disable {
+				background: rgba($color: #ff8d1a, $alpha: 0.2);
+			}
+		}
+
+		.b-home {
+			margin-top: 40upx;
+			text-align: center;
+			color: #808080;
+			font-size: 28upx;
+		}
+	}
+}
+</style>

+ 463 - 0
answer_pages/examine/review.vue

@@ -0,0 +1,463 @@
+<template>
+	<view class="wrap">
+		<u-navbar title="答题详情" :placeholder="true" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<view class="main" v-if="hasLoad">
+			<view class="process">
+				<view class="b-state">
+					<view class="b-count">
+						<view class="s-tit1">答题进度</view>
+						<view class="s-num">{{ state.session_seq }}</view>
+						<view class="s-tit2">/{{ state.total_count }}</view>
+					</view>
+					<view class="b-time">
+						<view class="s-tit">花费时间</view>
+						<view class="s-time">{{ costTime(state.cost_time) }}</view>
+					</view>
+				</view>
+				<view class="b-bar">
+					<view class="s-percent" :style="barPercent"></view>
+				</view>
+			</view>
+			<view class="answer">
+				<view class="b-type">{{ state.type_text }}</view>
+				<view class="b-title">
+					<view class="s-num">{{ state.session_seq }}.</view>
+					<text class="s-title">
+						{{ state.title }}
+						<text class="s-score">({{ state.score }}分)</text>
+					</text>
+				</view>
+				<view class="b-options">
+					<view class="b-item" v-for="(item, index) in state.optionList" :key="index" :class="[optionState(index)]">
+						<view class="s-num">{{ optionNum(index) }}.</view>
+						<view class="s-cont">{{ item.label }}</view>
+						<view class="s-state iconfont" :class="[optionIcon(index)]"></view>
+					</view>
+				</view>
+				<view class="b-result" :class="resultIcon">
+					<view class="b-right">
+						<view class="s-tit">正确答案:</view>
+						<view class="s-cont">{{ trueAnswer }}</view>
+					</view>
+					<view class="b-mine">
+						<view class="s-tit">已选答案:</view>
+						<view class="s-cont">{{ userAnswer }}</view>
+					</view>
+				</view>
+				<view class="b-explain" v-if="state.explain">
+					<text>解析:{{ state.explain }}</text>
+				</view>
+				<view class="b-action">
+					<view class="b-last" :class="{ disabled: disabled.hasLast }" @click="onLast">上一题</view>
+					<view class="b-next" :class="{ disabled: disabled.hasNext }" @click="onNext">下一题</view>
+				</view>
+				<view class="b-home" @click="onHome">回到首页</view>
+			</view>
+		</view>
+		<!-- 加载中 -->
+		<load-more :loadingType="loadingType" :loadingText="loadingText" color="#ffffff" :top="500"></load-more>
+	</view>
+</template>
+
+<script>
+import { getResult } from '@/service/api/examine.js';
+import mixinsCommon from '@/mixins/common.js';
+import mixinsAuth from '../../mixins/auth.js';
+export default {
+	mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			hasLoad: false,
+			state: {},
+			disabled: {
+				hasLast: false,
+				hasNext: false
+			},
+			loadingType: 1,
+			loadingText: ''
+		};
+	},
+	onLoad(options) {
+		this.loadResult(options.record_id);
+	},
+	computed: {
+		barPercent() {
+			return 'width: ' + (this.state.session_seq * 100) / this.state.total_count + '%';
+		},
+		optionNum() {
+			return (index) => {
+				return String.fromCharCode(64 + (index + 1));
+			};
+		},
+		optionState() {
+			return (index) => {
+				console.log('optionState', index);
+				let optionRight = false;
+				let optionWrong = false;
+				if (this.state.state == 2) {
+					if (this.state.type === 'radio' || this.state.type === 'yesorno') {
+						if (this.state.trueAnswer.includes(index)) {
+							optionRight = true;
+						} else if (this.state.userAnswer.includes(index)) {
+							optionWrong = true;
+						}
+					} else {
+						if (this.state.trueAnswer.includes(index)) {
+							optionRight = true;
+						} else if (
+							(this.state.userAnswer.includes(index) && !this.state.trueAnswer.includes(index)) ||
+							(this.state.trueAnswer.includes(index) && !this.state.userAnswer.includes(index))
+						) {
+							optionWrong = true;
+						}
+					}
+				}
+				let ret = '';
+				ret += optionRight ? 'f-right' : '';
+				ret += optionWrong ? 'f-wrong' : '';
+				return ret;
+
+				// return {
+				// 	'f-right': optionRight,
+				// 	'f-wrong': optionWrong
+				// }
+			};
+		},
+		optionIcon() {
+			return (index) => {
+				console.log('optionIcon', index);
+				return this.state.userAnswer.includes(index) ? 'icon-roundcheck' : '';
+				// return {
+				// 	'icon-roundcheck': this.state.userAnswer.includes(index),
+				// 	// 'icon-roundcheckfill': this.state.trueAnswer.includes(index),
+				// }
+			};
+		},
+		resultIcon() {
+			let ret = '';
+			ret += this.state.result == 2 ? ' f-right' : '';
+			ret += this.state.result == 3 ? ' f-wrong' : '';
+			ret += this.state.result == 4 ? ' f-timeout' : '';
+			ret += this.state.result == 5 ? ' f-timeout-end' : '';
+			return ret;
+
+			// return {
+			// 	'f-right': this.state.result == 2,
+			// 	'f-wrong': this.state.result == 3,
+			// 	'f-timeout': this.state.result == 4,
+			// 	'f-timeout-end': this.state.result == 5,
+			// }
+		},
+		trueAnswer() {
+			let options = this.state.trueAnswer.map((item) => {
+				return String.fromCharCode(64 + (parseInt(item) + 1));
+			});
+			return options.join('、');
+		},
+		userAnswer() {
+			let options = this.state.userAnswer.map((item) => {
+				return String.fromCharCode(64 + (parseInt(item) + 1));
+			});
+			return options.join('、');
+		}
+	},
+	methods: {
+		loadResult(recordId) {
+			if (!recordId) {
+				this.$logic.showToast('没有更多记录');
+				return;
+			}
+			getResult(recordId).then(([err, res]) => {
+				console.log('getResult', err, res);
+				if (!err) {
+					this.hasLoad = true;
+					this.loadingType = -1;
+					this.state = res;
+					if (!res.last_id) {
+						this.disabled.hasLast = true;
+					} else {
+						this.disabled.hasLast = false;
+					}
+					if (!res.next_id) {
+						this.disabled.hasNext = true;
+					} else {
+						this.disabled.hasNext = false;
+					}
+				} else {
+					if (!this.hasLoad) {
+						this.loadingType = 3;
+						this.loadingText = err.data.msg || '加载失败';
+					}
+				}
+			});
+		},
+		onLast() {
+			this.loadResult(this.state.last_id);
+		},
+		onNext() {
+			this.loadResult(this.state.next_id);
+		},
+		onHome() {
+			uni.reLaunch({
+				url: '/answer_pages/home/index'
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+// page {
+// 	padding-bottom: env(safe-area-inset-bottom);
+// 	background: #da5650;
+// }
+
+.wrap {
+	background: #da5650;
+}
+
+.main {
+	padding: 40upx;
+}
+
+.process {
+	display: flex;
+	flex-direction: column;
+
+	.b-state {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		color: #fff;
+
+		.b-count {
+			display: flex;
+			align-items: center;
+
+			.s-tit1,
+			.s-tit2 {
+				font-size: 24upx;
+			}
+
+			.s-num {
+				margin: 0 10upx;
+				font-size: 32upx;
+			}
+		}
+
+		.b-time {
+			display: flex;
+			align-items: center;
+
+			.s-tit {
+				font-size: 24upx;
+			}
+
+			.s-time {
+				margin-left: 10upx;
+				font-size: 28upx;
+			}
+		}
+	}
+
+	.b-bar {
+		margin-top: 10upx;
+		background: rgba(255, 255, 255, 0.5);
+		width: 100%;
+		height: 24upx;
+		position: relative;
+
+		.s-percent {
+			position: absolute;
+			left: 0;
+			bottom: 0;
+			width: 30%;
+			height: 24upx;
+			display: inline-block;
+			background: #fff;
+		}
+	}
+}
+
+.answer {
+	margin-top: 50upx;
+	padding: 40upx;
+	background: #fff;
+	border-radius: 20upx;
+	overflow: hidden;
+	display: flex;
+	flex-direction: column;
+
+	.b-type {
+		width: fit-content;
+		background: #da5650;
+		padding: 0 20upx;
+		font-size: 28upx;
+		color: #fff;
+		height: 50upx;
+		line-height: 50upx;
+	}
+
+	.b-title {
+		margin-top: 20upx;
+		font-size: 30upx;
+		display: flex;
+
+		.s-num {
+			font-weight: bold;
+		}
+
+		.s-title {
+			margin-left: 10upx;
+			flex: 1;
+			color: #333;
+		}
+
+		.s-score {
+			margin-left: 20upx;
+			color: #666;
+		}
+	}
+
+	.b-options {
+		margin-top: 40upx;
+
+		.b-item {
+			margin-bottom: 20upx;
+			padding: 20upx;
+			font-size: 28upx;
+			display: flex;
+			// align-items: center;
+			background: #f7f7f7;
+
+			&:last-child {
+				margin-bottom: 0;
+			}
+
+			&.f-select {
+				background: #ecf3fe;
+				color: #338ada;
+			}
+
+			&.f-right {
+				background: #ecf6f0;
+				color: #1eab69;
+			}
+
+			&.f-wrong {
+				background: #ffebeb;
+				color: #d13b42;
+			}
+
+			.s-num {
+				font-weight: bold;
+			}
+
+			.s-cont {
+				margin-left: 10upx;
+				flex: 1;
+			}
+
+			.s-state {
+				margin-left: 10upx;
+				font-size: 36upx;
+			}
+		}
+	}
+
+	.b-result {
+		padding: 80upx 0;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		background-repeat: no-repeat;
+		background-size: 180upx auto;
+		background-position: center center;
+
+		&.f-right {
+			background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/result_right.png);
+		}
+
+		&.f-wrong {
+			background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/result_wrong.png);
+		}
+
+		&.f-timeout {
+			background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/result_timeout.png);
+		}
+
+		&.f-timeout-end {
+			background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/result_timeout_end.png);
+		}
+
+		.b-right,
+		.b-mine {
+			display: flex;
+			align-items: center;
+
+			.s-tit {
+				font-size: 26upx;
+				color: #333;
+			}
+
+			.s-cont {
+				margin-left: 10upx;
+				font-weight: bold;
+				color: #da5650;
+				font-size: 36upx;
+			}
+		}
+	}
+
+	.b-explain {
+		font-size: 28upx;
+		color: #808080;
+	}
+
+	.b-action {
+		margin-top: 50upx;
+		display: flex;
+
+		.b-last {
+			flex: 1;
+			height: 78upx;
+			line-height: 78upx;
+			text-align: center;
+			color: #da5650;
+			font-size: 32upx;
+			border: 1upx solid #da5650;
+			border-radius: 50upx;
+			letter-spacing: 5upx;
+
+			&.disabled {
+				color: #da5650a3;
+				border: 1upx solid #da5650a3;
+			}
+		}
+
+		.b-next {
+			margin-left: 50upx;
+			flex: 1;
+			height: 80upx;
+			line-height: 80upx;
+			text-align: center;
+			color: #fff;
+			font-size: 32upx;
+			background: #da5650;
+			border-radius: 50upx;
+			letter-spacing: 5upx;
+
+			&.disabled {
+				background: #da5650a3;
+			}
+		}
+	}
+
+	.b-home {
+		margin-top: 40upx;
+		text-align: center;
+		color: #808080;
+		font-size: 28upx;
+	}
+}
+</style>

+ 249 - 0
answer_pages/examine/summary.vue

@@ -0,0 +1,249 @@
+<template>
+	<view class="wrap">
+		<u-navbar title="答题统计" :placeholder="true" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<view class="main" v-if="hasLoad">
+			<view class="result" v-if="state.state && state.state != 1">
+				<view class="b-summary">
+					<view class="b-title" v-if="state.state == 2">获得成绩</view>
+					<view class="b-title" v-else-if="state.state == 3">超时结束</view>
+					<view class="b-score">
+						<view class="s-num">{{ state.total_score || 0 }}</view>
+						<view class="s-tit">分</view>
+					</view>
+					<text class="break-score" v-if="state.break_sid > 0">上次{{ breakTips }},恭喜打破记录!</text>
+				</view>
+				<view class="b-detail">
+					<view class="b-item">
+						<view class="s-tit">总答题数</view>
+						<view class="s-cont">{{ state.answer_count }}道</view>
+					</view>
+					<view class="b-item">
+						<view class="s-tit">答对数</view>
+						<view class="s-cont">{{ state.right_count }}道</view>
+					</view>
+					<view class="b-item">
+						<view class="s-tit">答错数</view>
+						<view class="s-cont">{{ state.wrong_count }}道</view>
+					</view>
+					<view class="b-item" v-if="state.state == 3">
+						<view class="s-tit">超时题数</view>
+						<view class="s-cont f-focus">{{ state.timeout_count }}道</view>
+					</view>
+					<view class="b-item">
+						<view class="s-tit">答题耗时</view>
+						<view class="s-cont f-focus">{{ costTime(state.cost_time) }}</view>
+					</view>
+					<view class="b-item">
+						<view class="s-tit">答题时间</view>
+						<view class="s-cont">{{ startTime }}</view>
+					</view>
+				</view>
+				<view class="b-action">
+					<view class="b-history" v-if="activity_type == 2" @click="onHistory">查看历史</view>
+					<view class="b-home" @click="onHome">返回首页</view>
+				</view>
+			</view>
+		</view>
+		<!-- 加载中 -->
+		<load-more :loadingType="loadingType" :loadingText="loadingText" color="#ffffff" :top="500"></load-more>
+	</view>
+</template>
+
+<script>
+import { getSummary } from '@/service/api/examine.js';
+// import mixinsCommon from '@/mixins/common.js';
+// import mixinsAuth from '../../mixins/auth.js';
+import { formatDate } from '../../common/util.js';
+export default {
+	// mixins: [mixinsCommon],
+	data() {
+		return {
+			hasLoad: false,
+			session_id: 0,
+			state: {},
+			loadingType: 1,
+			loadingText: '',
+			activity_type: 1 //默认为1比赛类型,不显示查看历史
+		};
+	},
+
+	onLoad(options) {
+		switch (options.from) {
+			case 'last':
+				uni.setNavigationBarTitle({
+					title: '上次答题'
+				});
+				break;
+			case 'max':
+				uni.setNavigationBarTitle({
+					title: '最好成绩'
+				});
+				break;
+		}
+		this.session_id = options.session_id;
+		this.activity_type = options.activity_type;
+		getSummary(options.session_id).then(([err, res]) => {
+			console.log('getSummary', err, res);
+			if (!err) {
+				this.hasLoad = true;
+				this.loadingType = -1;
+				this.state = res;
+			} else {
+				if (!this.hasLoad) {
+					this.loadingType = 3;
+					this.loadingText = err.data.msg || '加载失败';
+				}
+				this.$logic.showToast('答题未完成').then(([err, res]) => {
+					uni.reLaunch({
+						url: '/answer_pages/home/index'
+					});
+				});
+			}
+		});
+	},
+	computed: {
+		breakTips() {
+			if (this.state.total_score > this.state.break_score) {
+				return this.state.break_score + '分';
+			} else {
+				return this.costTime(this.state.break_time);
+			}
+		},
+		startTime() {
+			return formatDate(this.state.created_at, 'yyyy-MM-dd hh:mm:ss');
+		}
+	},
+
+	methods: {
+		onHistory() {
+			uni.navigateTo({
+				url: '/answer_pages/examine/history?session_id=' + this.session_id
+			});
+		},
+		onHome() {
+			uni.reLaunch({
+				url: '/answer_pages/home/index'
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+// page {
+// 	padding-bottom: env(safe-area-inset-bottom);
+// 	background: #da5650;
+// }
+
+.wrap {
+	background: #da5650;
+}
+
+.main {
+	padding: 40upx;
+}
+
+.result {
+	margin-top: 50upx;
+	padding: 40upx;
+	background: #fff;
+	border-radius: 20upx;
+	overflow: hidden;
+	display: flex;
+	flex-direction: column;
+
+	.b-summary {
+		margin-top: 20upx;
+		font-size: 30upx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+
+		.b-title {
+			font-size: 36upx;
+		}
+
+		.b-score {
+			margin-top: 20upx;
+			display: flex;
+			align-items: baseline;
+
+			.s-num {
+				color: #ff8d1a;
+				font-weight: bold;
+				font-size: 80upx;
+			}
+
+			.s-tit {
+				margin-left: 10upx;
+				font-size: 28upx;
+			}
+		}
+
+		.break-score {
+			margin-top: 20upx;
+			color: #da5650;
+			font-size: 32upx;
+			font-weight: bold;
+		}
+	}
+
+	.b-detail {
+		margin-top: 40upx;
+		padding: 20upx;
+		background: #f7f7f7;
+		border-radius: 10upx;
+
+		.b-item {
+			font-size: 28upx;
+			height: 60upx;
+			line-height: 60upx;
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+
+			.s-tit {
+				color: #808080;
+			}
+
+			.s-cont {
+				color: #383838;
+			}
+
+			.f-focus {
+				color: #ff8d1a;
+			}
+		}
+	}
+
+	.b-action {
+		margin-top: 50upx;
+		display: flex;
+
+		.b-history {
+			flex: 1;
+			height: 78upx;
+			line-height: 78upx;
+			text-align: center;
+			color: #da5650;
+			font-size: 32upx;
+			border: 1upx solid #da5650;
+			border-radius: 50upx;
+			letter-spacing: 5upx;
+		}
+
+		.b-home {
+			margin-left: 50upx;
+			flex: 1;
+			height: 80upx;
+			line-height: 80upx;
+			text-align: center;
+			color: #fff;
+			font-size: 32upx;
+			background: #da5650;
+			border-radius: 50upx;
+			letter-spacing: 5upx;
+		}
+	}
+}
+</style>

+ 390 - 0
answer_pages/examine/top.vue

@@ -0,0 +1,390 @@
+<template>
+	<view class="wrap">
+		<u-navbar title="答题榜单" :placeholder="true" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<view class="top-bg">
+			<image src="https://huli-app.wenlvti.net/app_static/wuyuan/static/static/imgs/top_bg.png" mode="widthFix"></image>
+		</view>
+
+		<view class="main">
+			<scroll-view class="top-scroll" scroll-y scroll-with-animation :lower-threshold="100" @scrolltolower="onScrollToLower" :style="{ height: scrollHeight + 'px' }">
+				<!-- 数据列表 -->
+				<view class="user-list">
+					<view class="b-item" v-for="(item, index) in userData.items" :key="index">
+						<view class="b-sort">
+							<image :src="'https://huli-app.wenlvti.net/app_static/wuyuan/static/static/imgs/top_' + (index + 1) + '.png'" mode="aspectFit" v-if="index < 3"></image>
+							<text v-else>{{ index + 1 }}</text>
+						</view>
+						<view class="b-avatar">
+							<image :src="item.avatar_url || '/static/imgs/user.png'" mode="aspectFill"></image>
+						</view>
+						<view class="b-user">
+							<view class="s-name">{{ item.name }}</view>
+							<view class="s-org">{{ item.group_name }}</view>
+						</view>
+						<view class="b-data">
+							<view class="s-score">{{ item.max_score }}分</view>
+							<view class="s-time">{{ costTime(item.cost_time) }}</view>
+						</view>
+					</view>
+				</view>
+				<!-- 加载中 -->
+				<load-more :loadingType="loadingType" :loadingText="loadingText"></load-more>
+			</scroll-view>
+		</view>
+
+		<view class="navbar">
+			<view class="b-mine" @click="onGoLast('max', mineData.max_sid)">
+				<view class="s-tit1">第</view>
+				<view class="s-num">{{ mineData.group_top }}</view>
+				<view class="s-tit2">名</view>
+				<view class="s-mine">
+					<image :src="mineData.avatar_url || '/static/imgs/user.png'" mode="aspectFill"></image>
+				</view>
+				<view class="s-score">{{ mineData.max_score }}分</view>
+				<view class="s-time">{{ costTime(mineData.cost_time) }}</view>
+			</view>
+			<picker class="b-group" @change="onFilter" :value="groupIndex" :range="groupList" range-key="name">
+				<view class="b-main">
+					<view class="s-icon iconfont icon-unfold"></view>
+					<view class="s-name">{{ groupList[groupIndex].name }}排行</view>
+				</view>
+			</picker>
+		</view>
+	</view>
+</template>
+
+<script>
+import { getTopList, getMineTop } from '@/service/api/examine.js';
+import Util from '../../common/util';
+import mixinsCommon from '@/mixins/common.js';
+import mixinsAuth from '../../mixins/auth.js';
+// import noneData from '@/components/none-data/none-data.vue';
+// import loadMore from '@/components/load-more/load-more.vue';
+export default {
+	mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			hasLoad: false,
+			refreshTime: 0,
+			scrollHeight: 100,
+			userData: {
+				items: []
+			},
+			mineData: {},
+			groupList: [
+				{
+					id: 0,
+					name: '全部'
+				}
+			],
+			groupIndex: 0,
+			loadingType: 1,
+			loadingText: ''
+		};
+	},
+	onLoad(options) {
+		// switchTab不支持传参,单页才有效
+		console.log('onLoad', options);
+		this.initScrollHeight();
+		if (this.$store.state.examine.topGroupId == -1) {
+			const groupId = options.groupId || 0;
+			this.refreshTime = Util.getTimestamp();
+			this.loadTopList(groupId, true);
+			this.loadMineTop(groupId, true);
+		}
+	},
+	onShow() {
+		// switchTab时通过这种方式传参
+		console.log('onShow', this.$store.state.examine.topGroupId);
+		const groupId = this.$store.state.examine.topGroupId;
+		if (groupId != -1) {
+			this.$store.commit('examine/delTopGroupId');
+			this.refreshTime = Util.getTimestamp();
+			this.loadTopList(groupId, true);
+			this.loadMineTop(groupId, true);
+		} else {
+			if (this.hasLoad) {
+				const timestamp = Util.getTimestamp();
+				if (timestamp - this.refreshTime >= 5) {
+					this.refreshTime = timestamp;
+					this.loadTopList(this.groupList[this.groupIndex].id, true);
+					this.loadMineTop(this.groupList[this.groupIndex].id, false);
+				}
+			}
+		}
+	},
+	computed: {},
+	methods: {
+		loadTopList(group, refresh) {
+			console.log('loadTopList', group, refresh);
+			if (refresh) {
+				this.page = 1;
+				this.userData.items = [];
+			} else {
+				this.page++;
+			}
+			this.loadingType = 1;
+			this.loadingText = '';
+			getTopList(group, this.page, 10).then(([err, res]) => {
+				console.log('getTopList', err, res);
+				this.loadingType = -1;
+				this.hasLoad = true;
+				if (!err) {
+					if (res.items.length > 0) {
+						this.userData.items = [...this.userData.items, ...res.items];
+					} else {
+						this.loadingType = 2;
+						if (this.page == 1) {
+							this.loadingText = '暂无数据';
+						}
+						this.page--; // 重置分页
+					}
+				} else {
+					this.loadingType = 3;
+				}
+			});
+		},
+		loadMineTop(group, init) {
+			console.log('loadMineTop', group, init);
+			getMineTop(group).then(([err, res]) => {
+				console.log('getMineTop', err, res);
+				if (!err) {
+					this.mineData = res.mineData;
+					this.groupList = res.userGroups;
+					if (init) {
+						this.groupList.map((item, index) => {
+							if (item.id == group) {
+								this.groupIndex = index;
+							}
+						});
+					}
+				}
+			});
+		},
+		onFilter(e) {
+			console.log('onFilter', e.detail.value);
+			this.groupIndex = e.detail.value;
+			this.loadTopList(this.groupList[this.groupIndex].id, true);
+			this.loadMineTop(this.groupList[this.groupIndex].id, false);
+		},
+		initScrollHeight() {
+			const systemInfo = uni.getSystemInfoSync();
+			const windowHeight = systemInfo.windowHeight;
+			const searchBarHeight = this.$logic.rpx2px(360);
+			this.scrollHeight = windowHeight - searchBarHeight;
+		},
+		onScrollToLower(e) {
+			console.log('onScrollToLower', e);
+			this.loadTopList(this.groupList[this.groupIndex].id, false);
+		},
+		onGoLast(from, sessionId) {
+			if (!sessionId) {
+				return;
+			}
+			uni.navigateTo({
+				url: '/answer_pages/examine/summary?from=' + from + '&session_id=' + sessionId
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+// page {
+// 	padding-bottom: env(safe-area-inset-bottom);
+// 	background: $pq-bg-color;
+// }
+
+.wrap {
+}
+
+.top-bg {
+	position: relative;
+
+	image {
+		width: 100%;
+		height: 460upx;
+	}
+}
+
+.main {
+	padding: 40upx 40upx 160upx 40upx;
+	display: flex;
+	flex-direction: column;
+}
+
+.top-scroll {
+	margin-top: -250upx;
+	padding-bottom: 20upx;
+	position: relative;
+	width: 670upx;
+	height: 100%;
+	background: #fff;
+	border-radius: 20upx;
+	z-index: 100;
+}
+
+.user-list {
+	padding: 40upx 40upx 20upx 10upx;
+
+	.b-item {
+		margin-bottom: 50upx;
+		height: 100upx;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+
+		&:last-child {
+			margin-bottom: 0;
+		}
+
+		.b-sort {
+			width: 100upx;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+
+			text {
+				font-size: 36upx;
+				color: #808080;
+			}
+
+			image {
+				width: 60upx;
+				height: 60upx;
+			}
+		}
+
+		.b-avatar {
+			width: 100upx;
+			height: 100upx;
+
+			image {
+				width: 100upx;
+				height: 100upx;
+				border-radius: 50%;
+			}
+		}
+
+		.b-user {
+			margin-left: 20upx;
+			flex: 1;
+			display: flex;
+			flex-direction: column;
+
+			.s-name {
+				font-size: 32upx;
+			}
+
+			.s-org {
+				margin-top: 10upx;
+				font-size: 26upx;
+				color: #a6a6a6;
+			}
+		}
+
+		.b-data {
+			display: flex;
+			flex-direction: column;
+			align-items: flex-end;
+
+			.s-score {
+				color: #ffc300;
+				font-size: 40upx;
+			}
+
+			.s-time {
+				margin-top: 10upx;
+				color: #a6a6a6;
+				font-size: 24upx;
+			}
+		}
+	}
+}
+
+.navbar {
+	position: fixed;
+	z-index: 999;
+	left: 0;
+	/* #ifdef  H5 */
+	bottom: calc(100upx + env(safe-area-inset-bottom));
+	/* #endif */
+	/* #ifdef  MP-WEIXIN */
+	bottom: 0upx;
+	/* #endif */
+	width: 100%;
+	height: 120upx;
+	display: flex;
+	align-items: center;
+	background: #fff;
+	border-top: 1upx solid #f7f7f7;
+	justify-content: space-between;
+
+	.b-mine {
+		margin-left: 20upx;
+		display: flex;
+		align-items: center;
+
+		.s-tit1,
+		.s-tit2 {
+			color: #808080;
+			font-size: 28upx;
+		}
+
+		.s-num {
+			margin: 0 10upx;
+			font-size: 50upx;
+			color: #da5650;
+			font-weight: bold;
+		}
+
+		.s-mine {
+			margin-left: 20upx;
+			width: 70upx;
+			height: 70upx;
+
+			image {
+				width: 70upx;
+				height: 70upx;
+				border-radius: 50%;
+			}
+		}
+
+		.s-score {
+			margin-left: 20upx;
+			color: #ffc300;
+			font-size: 36upx;
+		}
+
+		.s-time {
+			margin-left: 20upx;
+			color: #808080;
+			font-size: 24upx;
+		}
+	}
+
+	.b-group {
+		margin-right: 20upx;
+
+		.b-main {
+			padding: 0 20upx;
+			display: flex;
+			align-items: center;
+			background: $pq-bg-color;
+			border-radius: 30upx;
+			height: 60upx;
+
+			.s-icon {
+				color: #a6a6a6;
+				font-size: 30upx;
+			}
+
+			.s-name {
+				margin-left: 10upx;
+				font-size: 28upx;
+				color: #a6a6a6;
+			}
+		}
+	}
+}
+</style>

+ 292 - 0
answer_pages/gift/detail.vue

@@ -0,0 +1,292 @@
+<template>
+	<view class="wrap" v-if="hasLoad">
+		<u-navbar title="礼品详情" :placeholder="true" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<view class="carousel">
+			<swiper indicator-dots circular="true" duration="400">
+				<swiper-item class="swiper-item" v-for="(item, index) in state.photo_list" :key="index">
+					<view class="image-wrapper">
+						<image :src="item" mode="aspectFill"></image>
+					</view>
+				</swiper-item>
+			</swiper>
+		</view>
+		<view class="info">
+			<view class="b-title">{{ state.name }}</view>
+			<view class="b-data">
+				<view class="s-worth" v-if="state.order_worth > 0">价值 {{ state.order_worth }}元</view>
+				<view class="s-count">
+					<view class="s-left">库存 {{ state.count_left }}</view>
+					<view class="s-gain">已兑 {{ state.count_gain }}</view>
+				</view>
+			</view>
+		</view>
+		<view class="specs" v-if="state.spec_list">
+			<view class="b-title">选择规格</view>
+			<view class="b-items">
+				<view class="b-item" v-for="(item, index) in state.spec_list" :key="index" :class="{ 'f-active': index == specIndex }" @click="onSpecSelect(index)">
+					{{ item.name }}
+				</view>
+			</view>
+			<view class="b-data">
+				<view class="s-left">库存 {{ state.spec_list[specIndex].count_left }}</view>
+				<view class="s-gain">已兑 {{ state.spec_list[specIndex].count_gain }}</view>
+			</view>
+		</view>
+		<view class="content">
+			<view class="b-title">
+				<view class="iconfont icon-line"></view>
+				<view class="s-tit">奖品详情</view>
+				<view class="iconfont icon-line"></view>
+			</view>
+			<view class="b-rich">
+				<rich-text :nodes="goodContent"></rich-text>
+			</view>
+		</view>
+		<view class="navbar">
+			<view class="b-action">
+				<view class="s-exchange f-disable" v-if="state.order_state == 1">兑换未开始</view>
+				<view class="s-exchange f-disable" v-else-if="state.order_state == 2">兑换已结束</view>
+				<view class="s-exchange f-disable" v-else-if="state.order_state == 3">兑换机会不足</view>
+				<view class="s-exchange f-disable" v-else-if="state.order_state == 4">已兑换完</view>
+				<view class="s-exchange" @click="onExchange()" v-else>立即兑换</view>
+			</view>
+		</view>
+		<!-- 加载中 -->
+		<load-more :loadingType="loadingType" :loadingText="loadingText" :top="300"></load-more>
+	</view>
+</template>
+
+<script>
+import { getDetail } from '@/service/api/gift.js';
+import { formatRichText } from '@/common/util.js';
+// import mixinsCommon from '@/mixins/common.js';
+// import mixinsAuth from '../../mixins/auth.js';
+export default {
+	// mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			state: {},
+			specIndex: 0,
+			hasLoad: false,
+			loadingType: 1,
+			loadingText: ''
+		};
+	},
+	onLoad(options) {
+		this.loadDetail(options.gift_id);
+	},
+	computed: {
+		goodContent() {
+			return formatRichText(this.state.content);
+		}
+	},
+	methods: {
+		loadDetail(giftId) {
+			getDetail(giftId).then(([err, res]) => {
+				console.log('getDetail', err, res);
+				if (!err) {
+					this.hasLoad = true;
+					this.loadingType = -1;
+					this.state = res;
+					// if (err.code == 0) {
+					// 	uni.navigateTo({
+					// 		url: '/answer_pages/home/dashboard'
+					// 	});
+					// }
+				} else {
+					this.loadingType = 3;
+					this.loadingText = err.data.msg || '加载失败';
+				}
+			});
+		},
+		onExchange() {
+			let specId = this.state.spec_list ? this.state.spec_list[this.specIndex].id : 0;
+			uni.navigateTo({
+				url: '/answer_pages/gift/exchange?gift_id=' + this.state.id + '&spec_id=' + specId
+			});
+		},
+		onSpecSelect(index) {
+			this.specIndex = index;
+			this.state.order_worth = this.state.spec_list[this.specIndex].worth;
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+// page {
+// 	padding-bottom: env(safe-area-inset-bottom);
+// 	background: $pq-bg-color;
+// }
+
+.wrap {
+}
+
+.carousel {
+	height: 500upx;
+	position: relative;
+
+	swiper {
+		height: 100%;
+	}
+
+	.swiper-item {
+		display: flex;
+		justify-content: center;
+		align-content: center;
+		height: 320upx;
+		overflow: hidden;
+
+		image {
+			width: 100%;
+			height: 100%;
+		}
+	}
+
+	.image-wrapper {
+		width: 100%;
+		height: 100%;
+	}
+}
+
+.info {
+	padding: 30upx;
+	background: #fff;
+
+	.b-title {
+		font-size: 28upx;
+	}
+
+	.b-data {
+		margin-top: 20upx;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+
+		.s-worth {
+			color: #999;
+			font-size: 24upx;
+		}
+
+		.s-count {
+			color: #999;
+			font-size: 24upx;
+			display: flex;
+			align-items: center;
+
+			.s-left {
+			}
+
+			.s-gain {
+				margin-left: 40upx;
+			}
+		}
+	}
+}
+
+.specs {
+	margin-top: 30upx;
+	padding: 30upx;
+	background: #fff;
+
+	.b-title {
+		font-size: 30upx;
+	}
+
+	.b-items {
+		margin-top: 30upx;
+
+		.b-item {
+			margin-right: 20upx;
+			padding: 0 40upx;
+			height: 60upx;
+			line-height: 60upx;
+			display: inline-block;
+			background: #fff;
+			border: 1upx solid #e4eaf1;
+			color: #667085;
+			font-size: 26upx;
+
+			&.f-active {
+				background: #da5650;
+				border: none;
+				color: #fff;
+			}
+		}
+	}
+
+	.b-data {
+		margin-top: 30upx;
+		color: #666;
+		font-size: 24upx;
+		display: flex;
+		align-items: center;
+
+		.s-left {
+		}
+
+		.s-gain {
+			margin-left: 40upx;
+		}
+	}
+}
+
+.content {
+	margin-top: 30upx;
+
+	.b-title {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+
+		.iconfont {
+			color: #ccc;
+		}
+
+		.s-tit {
+			margin: 0 20upx;
+			font-size: 24upx;
+			color: #666;
+		}
+	}
+
+	.b-rich {
+		margin-top: 30upx;
+		padding: 30upx 0;
+		background: #fff;
+		font-size: 28upx;
+	}
+}
+
+.navbar {
+	position: fixed;
+	left: 0;
+	bottom: 0;
+	width: 100%;
+	height: 100upx;
+	background: #fff;
+	// border-top: 1upx solid #f6f6f6;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+
+	.b-action {
+		flex: 1;
+
+		.s-exchange {
+			background: #da5650;
+			height: 100upx;
+			padding: 0 60upx;
+			line-height: 100upx;
+			font-size: 30upx;
+			color: #fff;
+			letter-spacing: 4upx;
+			text-align: center;
+
+			&.f-disable {
+				background: rgba(218, 86, 80, 0.8);
+			}
+		}
+	}
+}
+</style>

+ 463 - 0
answer_pages/gift/exchange.vue

@@ -0,0 +1,463 @@
+<template>
+	<view class="wrap">
+		<u-navbar title="礼品兑换" :placeholder="true" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<view class="main" v-if="hasLoad">
+			<view class="gift-view">
+				<view class="b-left">
+					<image :src="giftData.cover_url" mode="aspectFill"></image>
+				</view>
+				<view class="b-main">
+					<view class="b-title">{{ giftData.name }}</view>
+					<view class="b-spec" v-if="giftData.spec_data">
+						<view class="s-tit">规格:</view>
+						<view class="s-value">{{ giftData.spec_data.name }}</view>
+					</view>
+					<view class="b-fund">
+						<view class="s-worth" v-if="giftData.order_worth > 0">价值 {{ giftData.order_worth }}元</view>
+					</view>
+				</view>
+			</view>
+			<template v-if="giftData.type == 1">
+				<view class="box">
+					<view class="b-row">
+						<view class="b-tit">领取方式</view>
+						<view class="b-cont f-option">
+							<picker @change="onFilter" :value="gainMethodIndex" :range="gainMethodList" range-key="name">
+								<view class="b-main">
+									<view class="s-cont">{{ gainMethodList[gainMethodIndex].name }}</view>
+									<view class="iconfont icon-more"></view>
+								</view>
+							</picker>
+						</view>
+					</view>
+					<view class="b-row" v-if="gainMethodList[gainMethodIndex].value == 1 && giftData.offline_place">
+						<view class="b-tit">领取地点</view>
+						<view class="b-cont f-text">{{ giftData.offline_place }}</view>
+					</view>
+					<view class="b-address" v-if="gainMethodList[gainMethodIndex].value == 2">
+						<view class="b-row">
+							<view class="b-tit">收件人:</view>
+							<view class="b-cont">
+								<input type="text" value="" placeholder="请填写收件人姓名" @input="onInputText" data-field="name" />
+							</view>
+						</view>
+						<view class="b-row">
+							<view class="b-tit">手机号:</view>
+							<view class="b-cont">
+								<input type="text" value="" placeholder="请填写收件人手机号" @input="onInputText" data-field="phone" />
+							</view>
+						</view>
+						<view class="b-row">
+							<view class="b-tit">所在地区:</view>
+							<view class="b-cont f-area">
+								<area-picker v-on:getAddressResultObj="getAddressResultObj" :addressList.sync="addressList">
+									<div class="b-areas">
+										<view class="b-item">
+											<view class="s-cont">{{ addressList[0] }}</view>
+										</view>
+										<view class="b-item">
+											<view class="s-cont">{{ addressList[1] }}</view>
+										</view>
+										<view class="b-item">
+											<view class="s-cont">{{ addressList[2] }}</view>
+										</view>
+									</div>
+								</area-picker>
+							</view>
+						</view>
+						<view class="b-row">
+							<view class="b-tit">详细地址:</view>
+							<view class="b-cont f-textarea">
+								<textarea placeholder="请填写详细地址,包括门牌号" auto-height @input="onInputText" data-field="address"></textarea>
+							</view>
+						</view>
+					</view>
+					<view class="line"></view>
+					<!-- <view class="b-row f-fund">
+						<view class="b-tit">剩余机会:</view>
+						<view class="b-cont">
+							<view class="s-num">{{chanceData.chance_left}}</view>
+						</view>
+					</view> -->
+				</view>
+				<view class="box">
+					<view class="b-row">
+						<view class="b-tit">留言:</view>
+						<view class="b-cont f-textarea">
+							<textarea placeholder="这里填写内容" auto-height @input="onInputText" data-field="note"></textarea>
+						</view>
+					</view>
+				</view>
+			</template>
+			<!-- <view class="box" v-else>
+				<view class="b-row f-fund">
+					<view class="b-tit">剩余机会:</view>
+					<view class="b-cont">
+						<view class="s-num">{{chanceData.chance_left}}</view>
+					</view>
+				</view>
+			</view> -->
+			<view class="navbar">
+				<view class="b-action">
+					<view class="s-exchange" @click="onExchange" v-if="amountEnough">确认兑换</view>
+					<view class="s-exchange f-disable" v-else>机会不足</view>
+				</view>
+			</view>
+		</view>
+		<!-- 加载中 -->
+		<load-more :loadingType="loadingType" :loadingText="loadingText" :top="300"></load-more>
+	</view>
+</template>
+
+<script>
+import { getPreview, exchange } from '@/service/api/gift.js';
+import mixinsCommon from '@/mixins/common.js';
+import mixinsAuth from '../../mixins/auth.js';
+export default {
+	mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			disable: {
+				submit: false
+			},
+			giftData: {},
+			chanceData: {},
+			orderData: {},
+			formData: {
+				gift_id: 0,
+				gift_spec_id: 0,
+				gain_method: 0,
+				name: '',
+				phone: '',
+				province: '',
+				city: '',
+				area: '',
+				address: '',
+				note: ''
+			},
+			gainMethodList: [
+				{
+					value: 2,
+					name: '快递邮寄'
+				}
+			],
+			gainMethodIndex: 0,
+			hasLoad: false,
+			loadingType: 1,
+			loadingText: '',
+			addressList: ['', '', '']
+		};
+	},
+	onLoad(options) {
+		this.loadPreview(options.gift_id, options.spec_id);
+		this.formData.gift_id = options.gift_id;
+		this.formData.gift_spec_id = options.spec_id;
+	},
+	computed: {
+		amountEnough() {
+			return this.chanceData.chance_left > 0;
+		}
+	},
+	methods: {
+		loadPreview(giftId, specId) {
+			getPreview(giftId, specId).then(([err, res]) => {
+				console.log('getPreview', err, res);
+				if (!err) {
+					this.hasLoad = true;
+					this.loadingType = -1;
+					this.giftData = res.gift_data;
+					res.chance_data.chance_left = 100;
+					this.chanceData = res.chance_data;
+					this.orderData = res.order_data;
+					// 领取方式
+					if (this.giftData.gain_methods) {
+						this.gainMethodList = [];
+						if (this.giftData.gain_methods.indexOf('2') !== -1) {
+							console.log('test2');
+							this.gainMethodList.push({
+								value: 2,
+								name: '快递邮寄'
+							});
+							console.log(this.gainMethodList);
+						}
+						if (this.giftData.gain_methods.indexOf('1') !== -1) {
+							console.log('test1');
+							this.gainMethodList.push({
+								value: 1,
+								name: '线下领取'
+							});
+						}
+					}
+					this.formData.gain_method = this.gainMethodList[this.gainMethodIndex].value;
+				} else {
+					this.loadingType = 3;
+					this.loadingText = err.data.msg || '加载失败';
+				}
+			});
+		},
+		onExchange() {
+			if (this.disable.submit) {
+				return;
+			}
+			if (this.giftData.type == 1) {
+				if (this.formData.gain_method == 2) {
+					if (!this.formData.name) {
+						return this.$logic.showToast('收件人不能为空');
+					}
+					if (!this.formData.phone) {
+						return this.$logic.showToast('手机号不能为空');
+					}
+					if (!this.formData.province || !this.formData.city || !this.formData.area) {
+						return this.$logic.showToast('所在地区不能为空');
+					}
+					if (!this.formData.address) {
+						return this.$logic.showToast('详细地址不能为空');
+					}
+				}
+			} else {
+				this.formData.gain_method = 3;
+			}
+			this.disable.submit = true;
+			exchange(this.formData).then(([err, res]) => {
+				console.log('exchange', err, res);
+				this.disable.submit = false;
+				if (!err) {
+					uni.reLaunch({
+						url: '/answer_pages/gift/order_detail?submit=1&id=' + res.id
+					});
+				}
+			});
+		},
+		// 输入内容
+		onInputText(e) {
+			console.log('onInputText', e);
+			this.formData[e.target.dataset.field] = e.detail.value;
+		},
+		getAddressResultObj(e) {
+			console.log('getAddressResultObj', e);
+			this.addressList = [e.province.name, e.city.name, e.county.name];
+			this.formData.province = e.province.name;
+			this.formData.city = e.city.name;
+			this.formData.area = e.county.name;
+		},
+		onFilter(e) {
+			console.log('onFilter', e.detail.value);
+			this.gainMethodIndex = e.detail.value;
+			this.formData.gain_method = this.gainMethodList[this.gainMethodIndex].value;
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+// page {
+// 	padding-bottom: env(safe-area-inset-bottom);
+// 	background: $pq-bg-color;
+// }
+
+.wrap {
+}
+
+.gift-view {
+	background: #fff;
+	padding: 30upx;
+	display: flex;
+
+	.b-left {
+		width: 160upx;
+		height: 160upx;
+
+		image {
+			width: 160upx;
+			height: 160upx;
+		}
+	}
+
+	.b-main {
+		margin-left: 30upx;
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+
+		.b-title {
+			font-size: 28upx;
+			color: #333;
+		}
+
+		.b-spec {
+			margin-top: 20upx;
+			display: flex;
+			align-items: center;
+
+			.s-tit {
+				font-size: 24upx;
+				color: #999;
+			}
+
+			.s-value {
+				margin-left: 10upx;
+				font-size: 24upx;
+				color: #666;
+			}
+		}
+
+		.b-fund {
+			margin-top: 20upx;
+			display: flex;
+			align-items: baseline;
+
+			.s-worth {
+				font-size: 24upx;
+				color: #999;
+			}
+		}
+	}
+}
+
+.box {
+	margin-top: 30upx;
+	background: #fff;
+
+	.b-row {
+		padding: 30upx;
+		display: flex;
+		justify-content: space-between;
+
+		&.f-fund {
+			padding: 40upx 30upx;
+
+			.b-cont {
+				.s-num {
+					font-size: 32upx;
+					color: #da5650;
+				}
+			}
+		}
+
+		.b-tit {
+			font-size: 28upx;
+			width: 160upx;
+		}
+
+		.b-cont {
+			// flex: 1;
+			display: flex;
+			align-items: center;
+
+			&.f-option {
+				.b-main {
+					display: flex;
+					align-items: center;
+
+					.s-cont {
+						font-size: 28upx;
+					}
+
+					.iconfont {
+						margin-left: 10upx;
+					}
+				}
+			}
+
+			&.f-textarea {
+				textarea {
+					width: 530upx;
+					font-size: 28upx;
+				}
+			}
+
+			&.f-text {
+				font-size: 28upx;
+				color: #666;
+			}
+		}
+	}
+
+	.b-address {
+		.b-row {
+			border-bottom: 1upx solid #eee;
+
+			&:last-child {
+				border-bottom: none;
+			}
+
+			.b-tit {
+				color: #666;
+				width: 160upx;
+			}
+
+			.b-cont {
+				flex: 1;
+
+				&.f-area {
+					.b-areas {
+						display: flex;
+						align-items: center;
+					}
+
+					.b-item {
+						margin-right: 40upx;
+
+						.s-cont {
+							font-size: 28upx;
+						}
+					}
+				}
+
+				textarea {
+					width: 530upx;
+					height: 200upx;
+				}
+			}
+		}
+	}
+
+	.line {
+		width: 100%;
+		height: 16upx;
+		background-image: url(https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/answer/imgs/line-bg.png);
+		background-repeat: repeat-x;
+		background-size: auto 100%;
+
+		image {
+			width: 100%;
+			height: 20upx;
+		}
+	}
+
+	input {
+		font-size: 28upx;
+	}
+
+	.input-placeholder {
+		font-size: 28upx;
+	}
+}
+
+.navbar {
+	position: fixed;
+	left: 0;
+	bottom: 0;
+	width: 100%;
+	height: 100upx;
+	background: #fff;
+	border-top: 1upx solid #f6f6f6;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+
+	.b-action {
+		flex: 1;
+		.s-exchange {
+			background: #da5650;
+			height: 100upx;
+			padding: 0 60upx;
+			line-height: 100upx;
+			font-size: 30upx;
+			color: #fff;
+			letter-spacing: 4upx;
+			text-align: center;
+		}
+	}
+}
+</style>

+ 228 - 0
answer_pages/gift/express_feed.vue

@@ -0,0 +1,228 @@
+<template>
+	<view class="wrap">
+		<u-navbar title="订单物流" :placeholder="true" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<view class="main" v-if="hasLoad">
+			<view class="box">
+				<view class="b-row">
+					<view class="b-tit">快递公司</view>
+					<view class="b-cont">
+						<text class="s-text">{{ state.company }}</text>
+					</view>
+				</view>
+				<view class="b-row">
+					<view class="b-tit">快递单号</view>
+					<view class="b-cont">
+						<text class="s-text">{{ state.number }}</text>
+						<text class="s-copy" @click="onCopy(state.number)">点击复制</text>
+					</view>
+				</view>
+				<view class="b-row">
+					<view class="b-tit">发货时间</view>
+					<view class="b-cont">
+						<text class="s-text">{{ timeFormat(state.deployed_at) }}</text>
+					</view>
+				</view>
+				<view class="b-row">
+					<view class="b-tit">当前状态</view>
+					<view class="b-cont">
+						<text class="s-text">{{ state.state_text }}</text>
+					</view>
+				</view>
+			</view>
+			<view class="feed-detail">
+				<view class="address-data">
+					<view class="iconfont icon-pos"></view>
+					<text class="b-address">
+						<text class="s-tit">收货地址:</text>
+						<text class="s-cont">{{ state.address }}</text>
+					</text>
+				</view>
+				<view class="feed-data">
+					<view class="iconfont icon-feed"></view>
+					<view class="feed-list">
+						<view class="b-row" v-for="(item, index) in state.track_list" :key="index">
+							<view class="s-cont">{{ item.context }}</view>
+							<view class="s-time">{{ item.time }}</view>
+						</view>
+						<view class="b-text" v-if="state.track_list.length === 0">暂无数据</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 加载中 -->
+		<load-more :loadingType="loadingType" :loadingText="loadingText" :top="300"></load-more>
+	</view>
+</template>
+
+<script>
+import { getExpressFeed } from '@/service/api/gift.js';
+// import mixinsCommon from '@/mixins/common.js';
+// import mixinsAuth from '../../mixins/auth.js';
+export default {
+	// mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			state: {},
+			hasLoad: false,
+			loadingType: 1,
+			loadingText: ''
+		};
+	},
+	onLoad(options) {
+		this.loadFeed(options.id);
+	},
+	methods: {
+		loadFeed(recordId) {
+			getExpressFeed(recordId).then(([err, res]) => {
+				console.log('getExpressFeed', err, res);
+				if (!err) {
+					this.hasLoad = true;
+					this.loadingType = -1;
+					this.state = res;
+				} else {
+					this.loadingType = 3;
+					this.loadingText = err.data.msg || '加载失败';
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+// page {
+// 	padding-bottom: env(safe-area-inset-bottom);
+// 	background: $pq-bg-color;
+// }
+
+.wrap {
+}
+
+.box {
+	margin-top: 30upx;
+	background: #fff;
+	padding: 15upx 0;
+
+	.b-row {
+		padding: 15upx 30upx;
+		display: flex;
+		// justify-content: space-between;
+
+		.b-tit {
+			font-size: 28upx;
+			width: 180upx;
+			color: #666;
+		}
+
+		.b-cont {
+			display: flex;
+			align-items: center;
+			font-size: 28upx;
+			color: #333;
+		}
+
+		.s-copy {
+			margin-left: 20upx;
+			font-size: 24upx;
+			color: #ffc300;
+		}
+
+		.s-province,
+		.s-city,
+		.s-area {
+			margin-right: 10upx;
+		}
+
+		.s-phone {
+			margin-left: 20upx;
+		}
+
+		.s-btn {
+			padding: 0 30upx;
+			background: #ffc300;
+			color: #fff;
+			font-size: 24upx;
+			height: 60upx;
+			line-height: 60upx;
+			border-radius: 30upx;
+		}
+	}
+}
+
+.feed-detail {
+	padding: 30upx;
+	background: #fff;
+	margin-top: 30upx;
+
+	.address-data {
+		display: flex;
+
+		.iconfont {
+			margin-right: 20upx;
+			color: #999;
+			font-size: 36upx;
+		}
+
+		.b-address {
+			color: #999;
+			font-size: 28upx;
+			line-height: 40upx;
+
+			.s-tit {
+				margin-right: 10upx;
+			}
+
+			.s-cont {
+			}
+		}
+	}
+
+	.feed-data {
+		margin-top: 60upx;
+		display: flex;
+
+		.iconfont {
+			margin-right: 20upx;
+			color: #aaa;
+			font-size: 36upx;
+		}
+
+		.feed-list {
+			.b-text {
+				color: #999;
+				font-size: 28upx;
+			}
+
+			.b-row {
+				margin-bottom: 30upx;
+
+				&:last-child {
+					margin-bottom: 0;
+				}
+
+				&:first-child {
+					.s-cont {
+						color: #333;
+					}
+
+					.s-time {
+						color: #666;
+					}
+				}
+
+				.s-cont {
+					color: #999;
+					font-size: 28upx;
+				}
+
+				.s-time {
+					margin-top: 10upx;
+					color: #999;
+					font-size: 26upx;
+				}
+			}
+		}
+	}
+}
+</style>

+ 293 - 0
answer_pages/gift/index.vue

@@ -0,0 +1,293 @@
+<template>
+	<view class="wrap">
+		<u-navbar title="礼品兑换" :placeholder="true" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<!-- 暂时注释3月12 -->
+		<!-- <view class="main"> -->
+		<view class="main" v-if="hasLoad">
+			<view class="user-data" :style="'background-image:url(' + topImage + ')'">
+				<view class="b-chance">
+					<view class="s-tit">积分余额</view>
+					<view class="s-num">{{ score.score }}</view>
+					<view class="s-unit">个</view>
+				</view>
+				<view class="b-action" @click="onJumpOrder">
+					<view class="s-btn">兑换订单</view>
+				</view>
+				<view class="b-state">
+					<view class="s-tit f-wait" v-if="giftData.gift_state == 0">兑奖未开始</view>
+					<view class="s-tit f-ing" v-else-if="giftData.gift_state == 1">兑奖进行中</view>
+					<view class="s-tit f-end" v-else-if="giftData.gift_state == 2">兑奖已结束</view>
+				</view>
+			</view>
+			<view class="all-gifts">
+				<view class="b-items" v-if="allList.length > 0">
+					<view class="b-item" v-for="(item, index) in allList" :key="index" @click="onJumpGift(item.id)">
+						<view class="b-left">
+							<image :src="item.cover_url" mode="aspectFill"></image>
+						</view>
+						<view class="b-main">
+							<view class="b-title too-long">{{ item.name }}</view>
+							<view class="b-center">
+								<view class="b-fund">
+									<view class="s-worth" v-if="item.worth > 0">价值 {{ item.worth }}元</view>
+								</view>
+								<view class="b-action">
+									<view class="s-action" v-if="item.count_left > 0">兑换</view>
+									<view class="s-action f-disable" v-else>已兑换完</view>
+								</view>
+							</view>
+							<view class="b-amount">
+								<view class="s-left">库存 {{ item.count_left }}</view>
+								<view class="s-gain">已兑 {{ item.count_gain }}</view>
+							</view>
+						</view>
+					</view>
+				</view>
+				<view class="b-empty" v-else>暂无奖品</view>
+			</view>
+		</view>
+		<!-- 加载中 -->
+		<load-more :loadingType="loadingType" :loadingText="loadingText" :top="300"></load-more>
+	</view>
+</template>
+
+<script>
+import { getWrapData } from '@/service/api/gift.js';
+// import mixinsCommon from '@/mixins/common.js';
+// import mixinsAuth from '../../mixins/auth.js';
+// import loadMore from '@/components/load-more/load-more.vue';
+export default {
+	// mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			giftData: {},
+			userInfo: {},
+			score: {},
+			allList: [],
+			hasLoad: false,
+			loadingType: 1,
+			loadingText: '',
+			display: {
+				avatarChange: false
+			}
+		};
+	},
+	onShow() {
+		// 暂时注释3月12
+		this.loadWrapData();
+	},
+	computed: {
+		topImage() {
+			// return this.$store.getters.globalConfig.gift_top_img || 'https://huli-app.wenlvti.net/app_static/wuyuan/static/static/imgs/gift_top_bg.jpg'
+			return 'https://huli-app.wenlvti.net/app_static/wuyuan/static/static/imgs/gift_top_bg.jpg';
+		}
+	},
+	methods: {
+		loadWrapData() {
+			getWrapData().then(([err, res]) => {
+				console.log('getWrapData', err, res);
+				if (!err) {
+					this.hasLoad = true;
+					this.loadingType = -1;
+					this.giftData = res.giftData;
+					this.userInfo = res.userInfo;
+					this.allList = res.giftList;
+				} else {
+					this.loadingType = 3;
+					this.loadingText = err.data.msg || '加载失败';
+				}
+				// 积分信息
+				// this.getScore();
+			});
+		},
+		onJumpOrder() {
+			uni.navigateTo({
+				url: '/answer_pages/gift/order'
+			});
+		},
+		onJumpGift(giftId) {
+			uni.navigateTo({
+				url: '/answer_pages/gift/detail?gift_id=' + giftId
+			});
+		},
+		getScore() {
+			this.$api.getWuyuanUser({}, (val) => {
+				console.log(val);
+				this.score = val.data.user;
+				// val.data.score;
+				// val.data.exp;
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.user-data {
+	padding: 160upx 40upx 30upx 40upx;
+	background: #fff;
+	background-image: url('https://huli-app.wenlvti.net/app_static/wuyuan/static/static/imgs/gift_top_bg.jpg');
+	background-repeat: no-repeat;
+	background-size: cover;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+
+	.b-chance {
+		margin-top: 50upx;
+		display: flex;
+		align-items: center;
+
+		.s-tit {
+			font-size: 28upx;
+			color: #eee;
+		}
+
+		.s-num {
+			margin: 0 20upx;
+			font-size: 60upx;
+			font-weight: bold;
+			color: #fbc315;
+		}
+
+		.s-unit {
+			font-size: 28upx;
+			color: #eee;
+		}
+	}
+
+	.b-action {
+		margin-top: 30upx;
+		border: 2upx solid #fff;
+		color: #fff;
+		font-size: 28upx;
+		padding: 0 50upx;
+		height: 60upx;
+		line-height: 60upx;
+		border-radius: 30upx;
+	}
+
+	.b-state {
+		margin-top: 50upx;
+		display: flex;
+		align-items: center;
+
+		.s-tit {
+			color: #fff;
+			font-size: 24upx;
+			padding: 0 20upx;
+			background: #fbc315;
+			height: 60upx;
+			line-height: 60upx;
+			border-radius: 10upx;
+
+			&.f-wait {
+				background: #444c69;
+			}
+
+			&.f-ing {
+				background: #ffc300;
+			}
+
+			&.f-end {
+				background: #c4c4c4;
+			}
+		}
+	}
+}
+
+.all-gifts {
+	background: #fff;
+
+	.b-empty {
+		height: 100upx;
+		line-height: 100upx;
+		text-align: center;
+		color: #999;
+		font-size: 28upx;
+	}
+
+	.b-items {
+		.b-item {
+			padding: 30upx;
+			border-bottom: 1upx solid #eee;
+			display: flex;
+
+			&:last-child {
+				border-bottom: none;
+			}
+
+			.b-left {
+				width: 160upx;
+				height: 160upx;
+
+				image {
+					width: 160upx;
+					height: 160upx;
+				}
+			}
+
+			.b-main {
+				margin-left: 30upx;
+				flex: 1;
+				display: flex;
+				flex-direction: column;
+
+				.b-title {
+					font-size: 28upx;
+					color: #333;
+					width: 500upx;
+				}
+
+				.b-center {
+					margin-top: 20upx;
+					display: flex;
+					justify-content: space-between;
+
+					.b-fund {
+						display: flex;
+						align-items: baseline;
+
+						.s-worth {
+							font-size: 24upx;
+							color: #999;
+						}
+					}
+
+					.b-action {
+						.s-action {
+							background: #da5650;
+							color: #fff;
+							font-size: 24upx;
+							padding: 0 30upx;
+							border-radius: 30upx;
+							height: 60upx;
+							line-height: 60upx;
+
+							&.f-disable {
+								background: rgba(218, 86, 80, 0.8);
+							}
+						}
+					}
+				}
+
+				.b-amount {
+					margin-top: 10upx;
+					display: flex;
+					justify-content: space-between;
+
+					.s-left {
+						font-size: 24upx;
+						color: #999;
+					}
+
+					.s-gain {
+						font-size: 24upx;
+						color: #999;
+					}
+				}
+			}
+		}
+	}
+}
+</style>

+ 356 - 0
answer_pages/gift/order.vue

@@ -0,0 +1,356 @@
+<template>
+	<view class="wrap">
+		<u-navbar title="礼品订单" :placeholder="true" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<view class="filter-box">
+			<view class="b-item" v-for="(item, index) in groupList" :key="index" :class="[{ 'f-active': index == groupIndex }]" @click="onGroupSelect(index)">
+				<view class="s-tit">{{ item.name }}</view>
+			</view>
+		</view>
+		<view class="gift-list" v-if="state.items.length > 0">
+			<view class="b-item" v-for="(item, index) in state.items" :key="index">
+				<view class="b-head">
+					<view class="s-order">订单编号:{{ item.number }}</view>
+					<view class="s-status" :class="[stateStyle(item.status)]">{{ item.status_text }}</view>
+				</view>
+				<view class="b-gift" @click="onRecordDetail(item.id)">
+					<view class="b-left">
+						<image :src="item.gift_data.cover_url" mode="aspectFill"></image>
+					</view>
+					<view class="b-main">
+						<view class="b-title">{{ item.gift_data.name }}</view>
+						<view class="b-spec" v-if="item.gift_data.spec_data">
+							<view class="s-tit">规格:</view>
+							<view class="s-value">{{ item.gift_data.spec_data.name }}</view>
+						</view>
+						<view class="b-fund">
+							<view class="s-worth" v-if="item.gift_data.order_worth">价值 {{ item.gift_data.order_worth }}元</view>
+						</view>
+					</view>
+				</view>
+				<view class="b-foot">
+					<view class="s-time">兑换时间:{{ timeFormat(item.created_at) }}</view>
+				</view>
+				<view class="b-action">
+					<view class="b-btn" @click="onRecordDetail(item.id)">订单详情</view>
+				</view>
+			</view>
+		</view>
+		<!-- 加载中 -->
+		<load-more :loadingType="loadingType" :loadingText="loadingText" color="#888888"></load-more>
+		<view class="navbar">
+			<view class="b-action">
+				<view class="s-home" @click="onHome()">回到奖品首页</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { getOrderList } from '@/service/api/gift.js';
+import Util from '../../common/util';
+// import mixinsCommon from '@/mixins/common.js';
+// import mixinsAuth from '../../mixins/auth.js';
+// import loadMore from '@/components/load-more/load-more.vue';
+export default {
+	// mixins: [mixinsCommon],
+	data() {
+		return {
+			hasLoad: false,
+			refreshTime: 0,
+			groupIndex: 0,
+			groupList: [
+				{
+					name: '全部',
+					value: 0
+				},
+				{
+					name: '待发货',
+					value: 1
+				},
+				{
+					name: '已发货',
+					value: 2
+				},
+				{
+					name: '已取消',
+					value: 3
+				}
+			],
+			state: {
+				items: []
+			},
+			loadingType: 1,
+			loadingText: ''
+		};
+	},
+	onLoad(options) {
+		this.loadOrderList(true);
+	},
+	onShow() {
+		if (this.hasLoad) {
+			const timestamp = Util.getTimestamp();
+			if (timestamp - this.refreshTime >= 10) {
+				this.refreshTime = timestamp;
+				this.loadOrderList(true);
+			}
+		}
+	},
+	onReachBottom() {
+		if (this.loadingType !== 1 && this.loadingType !== 2) {
+			this.loadOrderList();
+		}
+	},
+	computed: {
+		stateStyle() {
+			return (status) => {
+				return {
+					'f-wait': status === 1,
+					'f-done': status === 2,
+					'f-cancel': status === 3
+				};
+			};
+		}
+	},
+	methods: {
+		loadOrderList(refresh) {
+			console.log('loadOrderList', refresh);
+			if (refresh) {
+				this.page = 1;
+				this.state.items = [];
+			} else {
+				this.page++;
+			}
+			this.loadingType = 1;
+			this.loadingText = '';
+			getOrderList(this.groupList[this.groupIndex].value, this.page, 10).then(([err, res]) => {
+				console.log('getList', err, res);
+				this.loadingType = -1;
+				this.hasLoad = true;
+				if (!err) {
+					if (res.items.length > 0) {
+						this.state.items = [...this.state.items, ...res.items];
+					} else {
+						this.loadingType = 2;
+						if (this.page == 1) {
+							this.loadingText = '暂无订单';
+						}
+						this.page--; // 重置分页
+					}
+				} else {
+					this.loadingType = 3;
+				}
+			});
+		},
+		onGroupSelect(index) {
+			this.groupIndex = index;
+			this.loadOrderList(true);
+		},
+		onRecordDetail(recordId) {
+			uni.navigateTo({
+				url: '/answer_pages/gift/order_detail?id=' + recordId
+			});
+		},
+		onHome() {
+			uni.navigateTo({
+				url: '/answer_pages/gift/index'
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+// page {
+// 	padding-bottom: env(safe-area-inset-bottom);
+// 	background: $pq-bg-color;
+// }
+
+.wrap {
+	padding-bottom: 50upx;
+}
+
+.filter-box {
+	padding: 30upx 50upx 0 50upx;
+	margin-bottom: 30upx;
+	background: #fff;
+	// border-bottom: 1upx solid #eee;
+	display: flex;
+	align-items: center;
+
+	.b-item {
+		margin: 0 40upx;
+		height: 80upx;
+		line-height: 80upx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 30upx;
+		padding-bottom: 6upx;
+
+		&.f-active {
+			padding-bottom: 0;
+			border-bottom: 6upx solid #da5650;
+
+			.s-tit {
+				color: #da5650;
+			}
+		}
+
+		.s-tit {
+			color: #333;
+		}
+	}
+}
+
+.gift-list {
+	.b-item {
+		background: #fff;
+		margin-bottom: 30upx;
+
+		&:last-child {
+			margin-bottom: 0;
+		}
+
+		.b-head {
+			padding: 20upx 30upx;
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			border-bottom: 1upx solid #eee;
+
+			.s-order {
+				color: #666;
+				font-size: 26upx;
+			}
+
+			.s-status {
+				font-size: 28upx;
+
+				&.f-wait {
+					color: #ffc300;
+				}
+
+				&.f-done {
+					color: #da5650;
+				}
+
+				&.f-cancel {
+					color: #666;
+				}
+			}
+		}
+
+		.b-gift {
+			padding: 30upx;
+			display: flex;
+			border-bottom: 1upx solid #eee;
+
+			.b-left {
+				width: 160upx;
+				height: 160upx;
+
+				image {
+					width: 160upx;
+					height: 160upx;
+				}
+			}
+
+			.b-main {
+				margin-left: 30upx;
+				flex: 1;
+				display: flex;
+				flex-direction: column;
+
+				.b-title {
+					font-size: 28upx;
+					color: #333;
+				}
+
+				.b-spec {
+					margin-top: 20upx;
+					display: flex;
+					align-items: center;
+
+					.s-tit {
+						font-size: 24upx;
+						color: #999;
+					}
+
+					.s-value {
+						margin-left: 10upx;
+						font-size: 24upx;
+						color: #666;
+					}
+				}
+
+				.b-fund {
+					margin-top: 20upx;
+					display: flex;
+					align-items: baseline;
+
+					.s-worth {
+						font-size: 24upx;
+						color: #999;
+					}
+				}
+			}
+		}
+
+		.b-foot {
+			padding: 20upx 30upx;
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+
+			.s-time {
+				color: #999;
+				font-size: 26upx;
+			}
+		}
+
+		.b-action {
+			padding: 20upx 30upx 40upx 30upx;
+			display: flex;
+			align-items: center;
+			justify-content: flex-end;
+
+			.b-btn {
+				padding: 0 20upx;
+				border: 1upx solid #bbb;
+				color: #666;
+				font-size: 26upx;
+				line-height: 60upx;
+				height: 60upx;
+				border-radius: 10upx;
+			}
+		}
+	}
+}
+
+.navbar {
+	position: fixed;
+	left: 0;
+	bottom: 0;
+	width: 100%;
+	height: 100upx;
+	background: #fff;
+	// border-top: 1upx solid #f6f6f6;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+
+	.b-action {
+		flex: 1;
+		.s-home {
+			background: #da5650;
+			height: 100upx;
+			padding: 0 60upx;
+			line-height: 100upx;
+			font-size: 30upx;
+			color: #fff;
+			letter-spacing: 4upx;
+			text-align: center;
+		}
+	}
+}
+</style>

+ 460 - 0
answer_pages/gift/order_detail.vue

@@ -0,0 +1,460 @@
+<template>
+	<view class="wrap">
+		<u-navbar title="订单详情" :placeholder="true" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<view class="main" v-if="hasLoad">
+			<view class="state-box" :class="stateStyle">
+				<view class="iconfont" :class="stateIcon"></view>
+				<view class="s-tit">{{ state.status_text }}</view>
+			</view>
+			<view class="gift-view" @click="onJumpGift(state.gift_data.id)">
+				<view class="b-left">
+					<image :src="state.gift_data.cover_url" mode="aspectFill"></image>
+				</view>
+				<view class="b-main">
+					<view class="b-title">{{ state.gift_data.name }}</view>
+					<view class="b-spec" v-if="state.gift_data.spec_data">
+						<view class="s-tit">规格:</view>
+						<view class="s-value">{{ state.gift_data.spec_data.name }}</view>
+					</view>
+					<view class="b-fund">
+						<view class="s-worth" v-if="state.gift_data.order_worth > 0">价值 {{ state.gift_data.order_worth }}元</view>
+					</view>
+				</view>
+			</view>
+			<view class="box" v-if="state.gift_data.type == 1">
+				<view class="b-row">
+					<view class="b-tit">领取方式</view>
+					<view class="b-cont">
+						<view class="s-cont s-text">{{ state.gain_method == 1 ? '线下领取' : '快递邮寄' }}</view>
+					</view>
+				</view>
+				<view class="b-row" v-if="state.gain_method == 1 && state.gift_data.offline_place">
+					<view class="b-tit">领取地点</view>
+					<view class="b-cont">
+						<view class="s-cont s-text">{{ state.gift_data.offline_place }}</view>
+					</view>
+				</view>
+				<template v-if="state.address_data">
+					<view class="b-row">
+						<view class="b-tit">收货人</view>
+						<view class="b-cont">
+							<text class="s-name s-text">{{ state.address_data.name }}</text>
+							<text class="s-phone s-text">{{ state.address_data.phone }}</text>
+						</view>
+					</view>
+					<view class="b-row">
+						<view class="b-tit">收件地址</view>
+						<view class="b-cont">
+							<view class="s-text">
+								<text class="s-province">{{ state.address_data.province }}</text>
+								<text class="s-city">{{ state.address_data.city }}</text>
+								<text class="s-area">{{ state.address_data.area }}</text>
+								<text class="s-address">{{ state.address_data.address }}</text>
+							</view>
+						</view>
+					</view>
+				</template>
+				<view class="b-row">
+					<view class="b-tit">留言</view>
+					<view class="b-cont">
+						<text class="s-text">{{ state.note }}</text>
+					</view>
+				</view>
+				<template v-if="state.express_data">
+					<view class="b-row">
+						<view class="b-tit">快递公司</view>
+						<view class="b-cont">
+							<text class="s-text">{{ state.express_data.company }}</text>
+						</view>
+					</view>
+					<view class="b-row">
+						<view class="b-tit">快递单号</view>
+						<view class="b-cont">
+							<text class="s-text">{{ state.express_data.number }}</text>
+							<text class="s-copy" @click="onCopy(state.express_data.number)">点击复制</text>
+						</view>
+					</view>
+				</template>
+				<view class="b-row" v-if="state.status == 2">
+					<view class="b-tit">发货时间</view>
+					<view class="b-cont">
+						<text class="s-text">{{ timeFormat(state.deployed_at) }}</text>
+					</view>
+				</view>
+			</view>
+			<view class="box" v-if="state.gift_data.type == 2">
+				<view class="b-row">
+					<view class="b-tit">券码信息</view>
+					<view class="b-cont">
+						<text class="s-text">{{ state.gift_data.code_data.data }}</text>
+						<text class="s-copy" @click="onCopy(state.gift_data.code_data.data)">点击复制</text>
+					</view>
+				</view>
+				<view class="b-row f-link" v-if="state.gift_data.external_link">
+					<view class="b-tit">兑换入口</view>
+					<view class="b-cont">
+						<view class="s-btn" @click="onExternalLink">点击进入</view>
+					</view>
+				</view>
+				<view class="b-row" v-if="state.status == 2">
+					<view class="b-tit">发货时间</view>
+					<view class="b-cont">
+						<text class="s-text">{{ timeFormat(state.deployed_at) }}</text>
+					</view>
+				</view>
+			</view>
+			<view class="box">
+				<view class="b-row">
+					<view class="b-tit">订单编号</view>
+					<view class="b-cont">
+						<text class="s-text">{{ state.number }}</text>
+						<text class="s-copy" @click="onCopy(state.id)">点击复制</text>
+					</view>
+				</view>
+				<view class="b-row">
+					<view class="b-tit">兑换时间</view>
+					<view class="b-cont">
+						<text class="s-text">{{ timeFormat(state.created_at) }}</text>
+					</view>
+				</view>
+			</view>
+
+			<view class="action" v-if="submitIn">
+				<view class="b-home" @click="onHome">返回首页</view>
+				<view class="b-order" @click="onOrderList">我的订单</view>
+			</view>
+
+			<view class="navbar" v-if="!submitIn && state.gift_data.type == 1 && (state.status == 1 || state.express_data)">
+				<view class="b-action">
+					<view class="b-btn f-cancel" @click="onCancel" v-if="state.status == 1">取消订单</view>
+					<view class="b-btn f-feed" @click="onExpressFeed" v-if="state.express_data">物流动态</view>
+				</view>
+			</view>
+		</view>
+		<!-- 加载中 -->
+		<load-more :loadingType="loadingType" :loadingText="loadingText" :top="300"></load-more>
+	</view>
+</template>
+
+<script>
+import { getOrderDetail, orderCancel } from '@/service/api/gift.js';
+import mixinsCommon from '@/mixins/common.js';
+import mixinsAuth from '../../mixins/auth.js';
+export default {
+	mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			recordId: 0,
+			state: null,
+			hasLoad: false,
+			submitIn: false,
+			loadingType: 1,
+			loadingText: ''
+		};
+	},
+	onLoad(options) {
+		this.loadDetail(options.id);
+		this.recordId = options.id;
+		if (options.submit) {
+			this.submitIn = true;
+		}
+	},
+	computed: {
+		stateStyle() {
+			return {
+				'f-wait': this.state.status === 1,
+				'f-done': this.state.status === 2,
+				'f-cancel': this.state.status === 3
+			};
+		},
+		stateIcon() {
+			return {
+				'icon-wait': this.state.status === 1,
+				'icon-done': this.state.status === 2,
+				'icon-cancel': this.state.status === 3
+			};
+		}
+	},
+	methods: {
+		loadDetail(recordId) {
+			getOrderDetail(recordId).then(([err, res]) => {
+				console.log('getOrderDetail', err, res);
+				if (!err) {
+					this.hasLoad = true;
+					this.loadingType = -1;
+					this.state = res;
+				} else {
+					this.loadingType = 3;
+					this.loadingText = err.data.msg || '加载失败';
+				}
+			});
+		},
+		onExpressFeed() {
+			uni.navigateTo({
+				url: '/answer_pages/gift/express_feed?id=' + this.recordId
+			});
+		},
+		onCancel() {
+			orderCancel(this.recordId).then(([err, res]) => {
+				console.log('orderCancel', err, res);
+				if (!err) {
+					this.$logic.showToast('取消成功');
+					this.loadDetail(this.recordId);
+				}
+			});
+		},
+		onExternalLink() {
+			uni.navigateTo({
+				url: '/answer_pages/home/webview?url=' + encodeURIComponent(this.state.gift_data.external_link)
+			});
+		},
+		onOrderList() {
+			uni.reLaunch({
+				url: '/answer_pages/gift/order'
+			});
+		},
+		onHome() {
+			uni.navigateTo({
+				url: '/answer_pages/gift/index'
+			});
+		},
+		onJumpGift(giftId) {
+			uni.navigateTo({
+				url: '/answer_pages/gift/detail?gift_id=' + giftId
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+// page {
+// 	padding-bottom: env(safe-area-inset-bottom);
+// 	background: $pq-bg-color;
+// }
+
+.wrap {
+	padding-bottom: 100upx;
+}
+
+.state-box {
+	height: 180upx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	background: #fff;
+
+	&.f-wait {
+		color: #ffc300;
+	}
+
+	&.f-done {
+		color: #da5650;
+	}
+
+	&.f-cancel {
+		color: #666;
+	}
+
+	.iconfont {
+		font-size: 64upx;
+	}
+
+	.s-tit {
+		margin-left: 20upx;
+		font-size: 50upx;
+		font-weight: bold;
+	}
+}
+
+.gift-view {
+	margin-top: 30upx;
+	background: #fff;
+	padding: 30upx;
+	display: flex;
+
+	.b-left {
+		width: 160upx;
+		height: 160upx;
+
+		image {
+			width: 160upx;
+			height: 160upx;
+		}
+	}
+
+	.b-main {
+		margin-left: 30upx;
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+
+		.b-title {
+			font-size: 28upx;
+			color: #333;
+		}
+
+		.b-spec {
+			margin-top: 20upx;
+			display: flex;
+			align-items: center;
+
+			.s-tit {
+				font-size: 24upx;
+				color: #999;
+			}
+
+			.s-value {
+				margin-left: 10upx;
+				font-size: 24upx;
+				color: #666;
+			}
+		}
+
+		.b-fund {
+			margin-top: 20upx;
+			display: flex;
+			align-items: baseline;
+
+			.s-worth {
+				font-size: 24upx;
+				color: #999;
+			}
+		}
+	}
+}
+
+.box {
+	margin-top: 30upx;
+	background: #fff;
+	padding: 15upx 0;
+
+	.b-row {
+		padding: 15upx 30upx;
+		display: flex;
+		// justify-content: space-between;
+		&.f-link {
+			align-items: center;
+		}
+
+		.b-tit {
+			font-size: 28upx;
+			width: 180upx;
+			color: #666;
+		}
+
+		.b-cont {
+			width: 510upx;
+			display: flex;
+			align-items: center;
+			font-size: 28upx;
+			color: #333;
+		}
+
+		.s-copy {
+			margin-left: 20upx;
+			font-size: 24upx;
+			color: #ffc300;
+		}
+
+		.s-province,
+		.s-city,
+		.s-area {
+			margin-right: 10upx;
+		}
+
+		.s-phone {
+			margin-left: 20upx;
+		}
+
+		.s-text {
+			color: #333;
+			font-weight: bold;
+		}
+
+		.s-btn {
+			padding: 0 30upx;
+			background: #ffc300;
+			color: #fff;
+			font-size: 24upx;
+			height: 60upx;
+			line-height: 60upx;
+			border-radius: 30upx;
+		}
+	}
+}
+
+.action {
+	margin-top: 100upx;
+	display: flex;
+	padding: 0 20upx;
+
+	.b-home {
+		flex: 1;
+		height: 78upx;
+		line-height: 78upx;
+		text-align: center;
+		color: #da5650;
+		font-size: 32upx;
+		border: 1upx solid #da5650;
+		border-radius: 50upx;
+		letter-spacing: 5upx;
+	}
+
+	.b-order {
+		margin-left: 50upx;
+		flex: 1;
+		height: 80upx;
+		line-height: 80upx;
+		text-align: center;
+		color: #fff;
+		font-size: 32upx;
+		background: #da5650;
+		border-radius: 50upx;
+		letter-spacing: 5upx;
+	}
+}
+
+.navbar {
+	position: fixed;
+	left: 0;
+	bottom: 0;
+	width: 100%;
+	height: 100upx;
+	background: #fff;
+	border-top: 1upx solid #f6f6f6;
+	display: flex;
+	align-items: center;
+	justify-content: flex-end;
+	border-top: 1upx solid #eee;
+
+	.b-action {
+		margin: 0 30upx;
+		display: flex;
+		align-items: center;
+
+		.b-btn {
+			margin-left: 20upx;
+			background: #da5650;
+			height: 60upx;
+			border-radius: 30upx;
+			padding: 0 30upx;
+			line-height: 60upx;
+			font-size: 26upx;
+			color: #fff;
+			letter-spacing: 4upx;
+
+			&.f-feed {
+				color: #fff;
+				background: #ffc300;
+			}
+
+			&.f-cancel {
+				color: #666;
+				background: none;
+				border: 2upx solid #eee;
+			}
+		}
+	}
+}
+</style>

+ 52 - 0
answer_pages/home/activity.vue

@@ -0,0 +1,52 @@
+<template>
+	<view class="wrap">
+		<u-navbar :autoBack="true" title="答题" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view class="b-image" v-if="activityImage" @click="onPreview">
+			<image :src="activityImage || '/static/imgs/activity_loading.jpg'" mode="widthFix"></image>
+		</view>
+		<view class="b-rich" v-else>
+			<rich-text :nodes="activityRich"></rich-text>
+		</view>
+	</view>
+</template>
+
+<script>
+import mixinsCommon from '@/mixins/common.js';
+import mixinsAuth from '../../mixins/auth.js';
+import { formatRichText } from '@/common/util.js';
+export default {
+	mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {};
+	},
+	onLoad() {},
+	computed: {
+		activityRich() {
+			return formatRichText(this.$store.getters.globalConfig.activity_rich);
+		},
+		activityImage() {
+			return this.$store.getters.globalConfig.activity_img;
+		}
+	},
+	methods: {
+		onPreview() {
+			uni.previewImage({
+				current: 0,
+				urls: [this.activityImage]
+			});
+		}
+	}
+};
+</script>
+
+<style>
+.b-rich {
+	background: #fff;
+	font-size: 28upx;
+}
+
+.b-image image {
+	width: 100%;
+	height: 460upx;
+}
+</style>

+ 332 - 0
answer_pages/home/dashboard.vue

@@ -0,0 +1,332 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="答题" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view>
+			<u-swiper
+				:list="bannerlist"
+				imgMode="aspectFill"
+				:height="200"
+				:indicator="true"
+				radius="5"
+				:autoplay="true"
+				:circular="true"
+				indicatorStyle="bottom: 10px"
+				indicatorMode="dot"
+				indicatorActiveColor="#fff"
+				indicatorInactiveColor="rgba(255, 255, 255, 0.35)"
+			></u-swiper>
+		</view>
+
+		<view>
+			<view class="flex justify-center gui-margin-top" style="margin-top: 40rpx">
+				<view style="width: 300rpx">
+					<u-subsection :list="tablist" :current="testIndex" mode="subsection" @change="onTestSelect"></u-subsection>
+				</view>
+			</view>
+			<view class="activity-list">
+				<view class="b-items" v-if="state.items.length > 0">
+					<view class="b-item" v-for="(item, index) in state.items" :key="index" @click="onJumpActivitty(item.id)">
+						<view class="b-cover" v-if="item.cover_url">
+							<image :src="item.cover_url" mode="aspectFill"></image>
+						</view>
+						<view class="b-main">
+							<text class="b-title">{{ item.name }}</text>
+							<view class="b-time">
+								<view class="s-start">开始时间 {{ datetime(item.start_time) }}</view>
+								<view class="s-end">结束时间 {{ datetime(item.end_time) }}</view>
+							</view>
+							<view class="b-foot">
+								<view class="b-state">
+									<view class="s-state f-wait" v-if="item.state == 0">未开始</view>
+									<view class="s-state f-ing" v-else-if="item.state == 1">进行中</view>
+									<view class="s-state f-end" v-else-if="item.state == 2">已结束</view>
+								</view>
+								<view class="b-action">
+									<view class="s-enter">进入活动</view>
+								</view>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 加载中 -->
+		<load-more :loadingType="loadingType" :loadingText="loadingText"></load-more>
+	</view>
+</template>
+
+<script>
+import { getActivityList } from '@/service/api/common.js';
+import { formatDate } from '../../common/util.js';
+// import mixinsCommon from '@/mixins/common.js';
+
+let that;
+export default {
+	// mixins: [mixinsCommon],
+	data() {
+		return {
+			official: false,
+			tablist: ['全部', '比赛', '练习'],
+			src: 'https://huli-app.wenlvti.net/uploads/20230608/8432937eabc5a32dcdbab19d2ab68f51.png',
+			shh: 'https://huli-app.wenlvti.net/uploads/20230608/667d2bde4141dbe791d8a080edc90d38.png',
+			jfdh: 'https://huli-app.wenlvti.net/uploads/20230608/462f2c03a21e5e66a82837e253198bac.png',
+
+			testIndex: 0,
+			testList: [
+				{
+					name: '全部',
+					value: 'all'
+				},
+				{
+					name: '比赛',
+					value: '1'
+				},
+				{
+					name: '练习',
+					value: '2'
+				}
+			],
+			state: {
+				items: []
+			},
+			loadingType: 1,
+			loadingText: '',
+			bannerlist: ['https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/dati_bg.png'],
+			tasklist: [],
+			page: 1
+		};
+	},
+	onLoad(options) {
+		that = this;
+		this.loadActivityList(true);
+		// 清空了activityId导致积分进不去
+		// if (this.$store.getters.activityId) {
+		// 	this.$store.commit('delActivityId');
+		// }
+		// this.getBannerList();
+	},
+	onShow() {
+		// 获取任务列暂时注释
+		// this.getTaskList();
+	},
+	onReachBottom() {
+		if (this.loadingType !== 1 && this.loadingType !== 2) {
+			this.loadActivityList();
+		}
+	},
+	computed: {
+		datetime() {
+			return (timestamp) => {
+				return formatDate(timestamp, 'yyyy-MM-dd hh:mm');
+			};
+		}
+	},
+	methods: {
+		// getBannerList() {
+		// 	this.$api.getBannerList({ block: 'Examine', type: 'banner' }, function (res) {
+		// 		if (res.code > 0) {
+		// 			that.bannerlist = [];
+		// 			for (var i = 0; i < res.data.length; i++) {
+		// 				that.bannerlist.push({ title: res.data[i].title, url: that.$config.baseUrl + res.data[i].image, page: res.data[i].url });
+		// 			}
+		// 		}
+		// 	});
+		// },
+		bannerClick(index) {
+			this.$common.navigateTo(this.bannerlist[index].page);
+		},
+		getTaskList() {
+			this.$api.getTaskList({}, function (res) {
+				console.log(res);
+				that.tasklist = res.data;
+			});
+		},
+		loadActivityList(refresh) {
+			// console.log('-------------------------------');
+			// console.log('loadActivityList', refresh);
+			if (refresh) {
+				this.page = 1;
+				this.state.items = [];
+			} else {
+				this.page++;
+			}
+
+			this.loadingType = 1;
+			this.loadingText = '';
+			getActivityList(this.testList[this.testIndex].value, this.page, 10).then(([err, res]) => {
+				console.log('getActivityList', err, res);
+
+				this.loadingType = -1;
+				if (!err) {
+					if (res.items.length > 0) {
+						this.state.items = [...this.state.items, ...res.items];
+					} else {
+						this.loadingType = 2;
+						if (this.page == 1) {
+							this.loadingText = '暂无数据';
+						}
+						this.page--; // 重置分页
+					}
+				} else {
+					this.loadingType = 3;
+				}
+			});
+		},
+		onTestSelect(index) {
+			this.testIndex = index;
+			this.loadActivityList(true);
+		},
+		onJumpActivitty(id) {
+			uni.navigateTo({
+				url: '/answer_pages/home/welcome?from=jump&aid=' + id
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+/deep/.u-swiper-indicator__wrapper__dot--active {
+	width: 5px !important;
+}
+.box {
+	height: auto;
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xy_bgt.png');
+	background-repeat: repeat-y;
+	background-attachment: fixed;
+	background-size: 100% 100%;
+}
+.number {
+	background-color: #ff5500;
+	width: 60rpx;
+	height: 60rpx;
+	color: white;
+	text-align: center;
+	font-size: 40rpx;
+	padding: 10rpx;
+	margin: 35rpx;
+	border-radius: 60upx;
+}
+
+.wrap {
+}
+
+.filter-box {
+	padding: 40upx;
+	background: #fff;
+	display: flex;
+	align-items: center;
+	justify-content: space-around;
+
+	.b-item {
+		width: 240upx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 30upx;
+
+		&.f-active {
+			font-weight: bold;
+		}
+
+		.s-tit {
+			color: #333;
+		}
+	}
+}
+
+.activity-list {
+	padding: 40upx 40upx 0 40upx;
+
+	.b-items {
+		.b-item {
+			margin-bottom: 40upx;
+			border-bottom: 1upx solid #eee;
+			display: flex;
+			flex-direction: column;
+			background: #fff;
+			border-radius: 20upx;
+
+			&:last-child {
+				border-bottom: none;
+				margin-bottom: 0;
+			}
+
+			.b-cover {
+				width: 670upx;
+				height: 300upx;
+
+				image {
+					border-radius: 20upx 20upx 0 0;
+					width: 670upx;
+					height: 300upx;
+				}
+			}
+
+			.b-main {
+				padding: 20upx;
+			}
+
+			.b-title {
+				font-size: 32upx;
+			}
+
+			.b-time {
+				margin-top: 10upx;
+				display: flex;
+				flex-direction: column;
+
+				font-size: 26upx;
+				color: #999;
+				line-height: 40upx;
+			}
+
+			.b-foot {
+				margin-top: 10upx;
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+
+				.b-state {
+					.s-state {
+						padding: 0 20upx;
+						background: #da5650;
+						color: #fff;
+						font-size: 24upx;
+						height: 40upx;
+						line-height: 40upx;
+
+						&.f-wait {
+							background: #444c69;
+						}
+
+						&.f-ing {
+							background: #ffc300;
+						}
+
+						&.f-end {
+							background: #c4c4c4;
+						}
+					}
+				}
+
+				.b-action {
+					.s-enter {
+						padding: 0 30upx;
+						height: 60upx;
+						line-height: 60upx;
+						text-align: center;
+						color: #fff;
+						font-size: 28upx;
+						background: #da5650;
+						border-radius: 30upx;
+					}
+				}
+			}
+		}
+	}
+}
+</style>

+ 649 - 0
answer_pages/home/index.vue

@@ -0,0 +1,649 @@
+<template>
+	<view class="wrap">
+		<u-navbar :autoBack="true" title="答题" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view class="logo" @click="onStart">
+			<image :src="topImage || 'https://huli-app.wenlvti.net/app_static/minnanhun/image/lb_3.jpg'" mode="widthFix"></image>
+		</view>
+		<!-- <view class="main" v-if="hasLoad"> -->
+		<view class="main">
+			<view>
+				<u-popup :show="show" :round="10" mode="center" bgColor="#fdfff3" @close="close" @open="open">
+					<view>
+						<text class="b-content">{{ examineData.rule_content }}</text>
+					</view>
+				</u-popup>
+			</view>
+
+			<view class="user-info">
+				<!-- 名次头像 -->
+				<view style="display: flex; justify-content: space-between; align-items: center">
+					<view v-if="selfData.ranking < 100" style="display: flex; justify-content: space-between; align-items: center; margin-right: 10rpx">
+						<view class="s-tit1">第</view>
+						<view class="s-num" style="color: #da5650; font-size: 40rpx; font-weight: 700">{{ selfData.ranking }}</view>
+						<view class="s-tit2">名</view>
+					</view>
+					<view v-else style="color: #da5650; font-size: 30rpx; font-weight: 700">未上榜</view>
+					<view class="b-avatar">
+						<image :src="selfData.avatar_url || 'https://huli-app.wenlvti.net/app_static/minnanhun/image/lb_3.jpg'" mode="aspectFill"></image>
+					</view>
+					<!-- 分数 -->
+					<view class="b-score" v-if="examineData.max_sid > 0" @click="onGoLast('max', examineData.max_sid)">
+						<!-- 暂时注释显示undefind -->
+						<text class="s-score">{{ selfData.match_score ? selfData.match_score : selfData.max_score }}分</text>
+
+						<!-- 暂时注释 -->
+						<!-- <text class="s-tit">{{ costTime(selfData.cost_time) }}</text> -->
+					</view>
+				</view>
+				<!-- 名次头像end -->
+				<!-- 		
+				<view class="b-info">
+					<view class="s-name">{{ selfData.name }}</view>
+				</view> -->
+
+				<view style="display: flex; flex-direction: column">
+					<u-button customStyle="width:136rpx" open-type="share" @click="shareBtn" shape="circle" size="small" text="分享" color="#da5650"></u-button>
+				</view>
+			</view>
+			<view class="examine">
+				<view class="b-chance">
+					<view class="s-tit" style="margin-left: 30rpx">答题机会</view>
+					<view class="s-flag">×</view>
+					<view class="s-num">{{ examineData.challenge_chance }}</view>
+					<button @click="show = true" size="mini" style="font-size: 28upx; color: #da5650; margin-left: 250rpx">答题规则</button>
+				</view>
+				<view class="b-start disable" v-if="examineData.activity_status == 0">答题未开始</view>
+				<template v-else-if="examineData.activity_status == 1">
+					<view class="b-start" :class="{ disable: disable.submit }" @click="onContinue" v-if="examineData.session_data && examineData.session_data.status == 1">
+						继续答题
+					</view>
+					<view class="b-start" :class="{ disable: disable.submit }" @click="onStart" v-else-if="examineData.challenge_chance > 0">
+						{{ examineData.last_sid > 0 ? '重新答题' : '开始答题' }}
+					</view>
+					<view class="b-start disable" v-else>完成答题</view>
+				</template>
+				<view class="b-start disable" v-else-if="examineData.activity_status == 2">答题已结束</view>
+				<view class="b-start f-finish" style="color: #da5650" v-if="examineData.last_sid > 0" @click="onGoLast('last', examineData.last_sid)">上次成绩</view>
+			</view>
+
+			<view class="top-data">
+				<view class="b-tit" style="font-size: 30rpx; color: #ff0000; margin-left: 20rpx">排 行 榜</view>
+				<view style="height: 20rpx"></view>
+				<scroll-view class="top-scroll" scroll-y scroll-with-animation :lower-threshold="100" @scrolltolower="onScrollToLower" :style="{ height: scrollHeight + 'px' }">
+					<!-- 数据列表排行榜 -->
+					<view class="user-list">
+						<view class="b-item" v-for="(item, index) in userData.ranking_list" :key="index">
+							<view class="b-sort">
+								<image
+									:src="'https://huli-app.wenlvti.net/app_static/wuyuan/static/static/imgs/top_' + item.ranking + '.png'"
+									mode="aspectFit"
+									v-if="item.ranking < 4"
+								></image>
+								<text v-else>{{ item.ranking }}</text>
+							</view>
+							<view class="b-avatar">
+								<image :src="item.avatar_url || 'https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/bm_top.png'" mode="aspectFill"></image>
+							</view>
+							<view class="b-user">
+								<view class="s-name">{{ item.name }}</view>
+							</view>
+							<view class="b-data">
+								<!-- 暂时注释显示undefind -->
+								<view class="s-score">{{ item.match_score ? item.match_score : item.max_score }}分</view>
+
+								<!-- 暂时注释显示 -->
+								<!-- <view class="s-time">{{ costTime(item.cost_time) }}</view> -->
+							</view>
+						</view>
+					</view>
+					<!-- 加载中 -->
+					<load-more :loadingsType="loadingsType" :loadingsText="loadingsText"></load-more>
+				</scroll-view>
+			</view>
+
+			<!-- <view style="height: 100rpx"></view> -->
+
+			<!-- <view class="rules">
+				<view class="b-tit">答题规则</view>
+				<text class="b-content">{{examineData.rule_content}}</text>
+			</view> -->
+		</view>
+		<!-- </view> -->
+		<view class="bottom_show" v-if="shareUserData.user_id">
+			<view v-if="shareUserData.ranking < 100" style="display: flex; justify-content: space-between; align-items: center; margin-right: 30rpx">
+				<view class="s-tit1">第</view>
+				<view class="s-num" style="color: #da5650; font-size: 40rpx; font-weight: 700">{{ shareUserData.ranking }}</view>
+				<view class="s-tit2">名</view>
+			</view>
+			<view v-else style="color: #da5650; font-size: 30rpx; font-weight: 700; margin-right: 30rpx">未上榜</view>
+			<view class="b-avatar" style="width: 100upx; height: 100upx">
+				<image
+					style="width: 100upx; height: 100upx; border-radius: 50%"
+					:src="shareUserData.avatar_url || 'https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/bm_top.png'"
+					mode="aspectFill"
+				></image>
+			</view>
+			<view style="margin-right: 50rpx; margin-left: 30rpx">{{ shareUserData.name }}</view>
+			<view class="">
+				<view class="">
+					<text class="" style="color: #da5650">{{ shareUserData.match_score ? shareUserData.match_score : shareUserData.max_score }}分</text>
+					<view class=""></view>
+				</view>
+			</view>
+
+			<view class="gui-margin">
+				<button type="default" class="gui-button gui-gtbg-red gui-noborder button" style="width: 100rpx" @click="toLike">
+					<text class="gui-color-white gui-button-text gui-icons button-text">&#xe6ea;点赞</text>
+				</button>
+			</view>
+		</view>
+
+		<!-- 加载中 -->
+		<load-more :loadingType="loadingType" :loadingText="loadingText" :top="300"></load-more>
+		<!-- 上传头像 -->
+		<upload-avatar :show="display.avatarChange" :image="userInfo.avatar_url" @bindClose="onAvatarClose" @bindChange="onAvatarChange"></upload-avatar>
+	</view>
+</template>
+
+<script>
+import { getHomeData } from '@/service/api/page.js';
+import { startChallenge, getRankingList, getNew, userShare } from '@/service/api/examine.js';
+// 暂时注释可以正常显示页面
+// import mixinsCommon from '@/mixins/common.js';
+// import mixinsAuth from '../../mixins/auth.js';
+import Util from '../../common/util';
+export default {
+	// mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			SharerId: '' /* 分享人的id */,
+			userInfo: {},
+			show: false,
+			topData: {},
+			examineData: {},
+			hasLoad: false,
+			loadingType: 1,
+			loadingText: '',
+			disable: {
+				submit: false
+			},
+			display: {
+				avatarChange: false
+			},
+			loadingsType: 1,
+			loadingsText: '',
+			userData: {
+				ranking_list: []
+			},
+			selfData: {},
+			shareUserData: {},
+			refreshTime: 0,
+			scrollHeight: 100,
+			from_userid: 0
+		};
+	},
+	onLoad(e) {
+		console.log(e, 989898989);
+		this.SharerId = e.SharerId;
+		this.initScrollHeight();
+		this.refreshTime = Util.getTimestamp();
+		// 暂时注释3月12
+		this.loadRankingList(e.from_userid);
+		this.loadHomeData();
+		if (e.from_userid) {
+			this.from_userid = e.from_userid;
+		}
+	},
+
+	computed: {
+		topImage() {
+			return this.$store.getters.globalConfig.home_top_img;
+		}
+	},
+	methods: {
+		/* 测试分享功能 */
+		shareBtn() {
+			// console.log(this.SharerId, '33333333');
+
+			uni.share({
+				provider: 'weixin',
+				type: 0,
+				scene: 'WXSceneSession',
+				title: '分享标题',
+				summary: '分享摘要',
+				id: this.SharerId,
+				href: 'http://example.com',
+				imageUrl: 'https://huli-app.wenlvti.net//uploads/20230424/e3a3e0dccdc71d5ad6aab5a42fcfd1d1.jpg',
+				success: function () {
+					console.log('分享成功');
+				},
+				fail: function (err) {
+					console.log('分享失败:' + err.errMsg);
+				}
+			});
+		},
+		open() {
+			// console.log('open');
+		},
+		close() {
+			this.show = false;
+			// console.log('close');
+		},
+		loadHomeData() {
+			getHomeData().then(([err, res]) => {
+				console.log('getHomeData', err, res);
+				if (!err) {
+					this.hasLoad = true;
+					this.loadingType = -1;
+					this.userInfo = res.userInfo;
+					this.topData = res.topData;
+					this.examineData = res.examineData;
+					if (!this.userInfo.avatar_url) {
+						if (!this.$store.getters['user/uploadAvatarTip']) {
+							this.$store.commit('user/setUploadAvatarTip', true);
+							this.display.avatarChange = true;
+						}
+					}
+				} else {
+					if (!this.hasLoad) {
+						this.loadingType = 3;
+						this.loadingText = err.data.msg || '加载失败';
+					}
+				}
+			});
+		},
+		loadRankingList(share_user_id) {
+			// if (refresh) {
+			// 	this.page = 1;
+			// 	this.userData.ranking_list = [];
+			// } else {
+			// 	this.page++;
+			// }
+			this.loadingsType = 1;
+			this.loadingsText = '';
+			getRankingList(share_user_id).then(([err, res]) => {
+				console.log('getRankingList66', err, res);
+				this.loadingsType = -1;
+				this.hasLoad = true;
+				this.userData.ranking_list = res.ranking_list;
+				console.log(this.userData.ranking_list, '成绩列表');
+
+				this.selfData = res.self;
+				console.log(this.selfData, '分数');
+				this.shareUserData = res.share_user;
+				//刷新重复出现,数据太少的原因?
+				// if (!err) {
+				// 	if (res.ranking_list.length > 0) {
+				// 		this.userData.ranking_list = [...this.userData.ranking_list, ...res.ranking_list]
+				// 	} else {
+				// 		this.loadingsType = 2
+				// 		if (this.page == 1) {
+				// 			this.loadingsText = '暂无数据'
+				// 		}
+				// 		this.page-- // 重置分页
+				// 	}
+				// } else {
+				// 	this.loadingsType = 3
+				// }
+			});
+		},
+		toLike() {
+			let param = {
+				share_user_id: 0
+			};
+			if (this.from_userid > 0) {
+				param.share_user_id = this.from_userid;
+			}
+			userShare(param.share_user_id).then(([err, res]) => {
+				console.log('userShare', err, res);
+			});
+		},
+		onStart() {
+			uni.navigateTo({
+				url: '/answer_pages/examine/summary'
+			});
+
+			// 暂时注释3月12
+			if (this.disable.submit) {
+				return;
+			}
+			this.disable.submit = true;
+			startChallenge().then(([err, res]) => {
+				console.log('startChallenge', err, res);
+				this.disable.submit = false;
+				if (!err) {
+					if (res.state === 'new' || res.state === 'exist') {
+						uni.navigateTo({
+							url: '/answer_pages/examine/answer?session_id=' + res.session_id + '&activity_type=' + this.examineData.activity_type
+						});
+					} else if (res.state === 'end') {
+						uni.navigateTo({
+							url: '/answer_pages/examine/summary?session_id=' + res.session_id + '&activity_type=' + this.examineData.activity_type
+						});
+					}
+				}
+			});
+		},
+		onContinue() {
+			uni.navigateTo({
+				url: '/answer_pages/examine/answer?session_id=' + this.examineData.session_data.session_id + '&activity_type=' + this.examineData.activity_type
+			});
+		},
+		onGoTop(groupId) {
+			this.$store.commit('examine/setTopGroupId', groupId);
+			uni.navigateTo({
+				url: '/answer_pages/examine/top'
+			});
+		},
+		onGoLast(from, sessionId) {
+			if (!sessionId) {
+				return;
+			}
+			uni.navigateTo({
+				url: '/answer_pages/examine/summary?from=' + from + '&session_id=' + sessionId
+			});
+		},
+		onAvatarClose() {
+			this.display.avatarChange = false;
+		},
+		onAvatarChange(avatarUrl) {
+			this.userInfo.avatar_url = avatarUrl;
+		},
+		onFilter(e) {
+			this.loadRankingList(true);
+		},
+		initScrollHeight() {
+			const systemInfo = uni.getSystemInfoSync();
+			const windowHeight = systemInfo.windowHeight;
+			const searchBarHeight = this.$logic.rpx2px(360);
+			this.scrollHeight = windowHeight - searchBarHeight;
+		},
+		onScrollToLower(e) {
+			this.loadRankingList(false);
+		}
+	}
+};
+</script>
+
+<style>
+.logo image {
+	width: 100%;
+	height: 460upx;
+}
+.wrap {
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xy_bgt.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	background-repeat: repeat-y;
+	height: auto;
+}
+.main {
+	padding: 40upx;
+}
+
+.user-info {
+	padding: 0 30upx 0 20upx;
+	display: flex;
+	align-items: center;
+	background: #ffffff;
+	border-radius: 20upx;
+	justify-content: space-between;
+}
+
+.user-info .b-avatar {
+	width: 100upx;
+	height: 100upx;
+}
+
+.user-info .b-avatar image {
+	width: 100upx;
+	height: 100upx;
+	border-radius: 50%;
+}
+
+.user-info .b-info {
+	margin-left: 10upx;
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+}
+
+.user-info .b-info .s-name {
+	font-size: 28upx;
+}
+
+.user-info .b-info .s-org {
+	margin-top: 10upx;
+	padding: 0 20upx;
+	width: fit-content;
+	background: #da5650;
+	border-radius: 10upx;
+	color: #fff;
+	line-height: 20px;
+	height: 20px;
+	font-size: 24upx;
+	text-align: center;
+}
+
+.user-info .b-score {
+	margin-left: 15upx;
+	margin-right: 10upx;
+	display: flex;
+	align-items: center;
+	text-align: center;
+	color: #ffc300;
+}
+
+.user-info .b-score .s-score {
+	font-size: 32upx;
+	font-weight: bold;
+}
+
+.user-info .b-score .s-tit {
+	margin-top: 10upx;
+	font-size: 24upx;
+	color: #999;
+}
+
+.examine {
+	margin-top: 40upx;
+}
+
+.examine .b-chance {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.examine .b-chance .s-tit {
+	font-size: 26upx;
+	color: #808080;
+}
+
+.examine .b-chance .s-flag {
+	margin: 0 10upx;
+	font-size: 26upx;
+	color: #808080;
+}
+
+.examine .b-chance .s-num {
+	font-size: 36upx;
+	color: #da5650;
+	font-weight: bold;
+}
+
+.examine .b-start {
+	margin-top: 20upx;
+	height: 100upx;
+	line-height: 100upx;
+	text-align: center;
+	color: #fff;
+	font-size: 32upx;
+	background: #da5650;
+	border-radius: 50upx;
+	letter-spacing: 10upx;
+}
+
+.examine .b-start.disable {
+	background: rgba(218, 86, 80, 0.8);
+}
+
+.examine .b-start.f-finish {
+	font-size: 28upx;
+	background: #ffffff;
+	color: #da5650;
+}
+
+.top-data {
+	margin-top: 40upx;
+}
+
+.top-data .b-tit {
+	font-size: 28upx;
+	font-weight: bold;
+}
+
+.top-data .b-main {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	flex-wrap: wrap;
+}
+
+.top-data .b-main .b-item {
+	margin-top: 20upx;
+	padding: 30upx;
+	width: 260upx;
+	background: #ffffff;
+	border-radius: 20upx;
+	display: flex;
+	align-items: center;
+}
+
+.top-data .b-main .b-item .b-top {
+	flex: 1;
+	display: flex;
+	align-items: center;
+}
+
+.top-data .b-main .b-item .b-top .s-tit1,
+.top-data .b-main .b-item .b-top .s-tit2 {
+	font-size: 26upx;
+	color: #808080;
+}
+
+.top-data .b-main .b-item .b-top .s-num {
+	margin: 0 20upx;
+	font-size: 32upx;
+	color: #da5650;
+}
+
+.top-data .b-main .b-item .b-more .iconfont {
+	font-size: 30upx;
+	color: #ccc;
+}
+
+.rules {
+	margin-top: 50upx;
+	padding: 30upx;
+	background: #ffffff;
+	border-radius: 20upx;
+}
+
+.rules .b-tit {
+	color: #da5650;
+	font-size: 30upx;
+}
+
+.rules .b-content {
+	margin-top: 20upx;
+	display: block;
+	color: #383838;
+	font-size: 24upx;
+	line-height: 40upx;
+}
+
+.user-list {
+	padding: 40upx 40upx 20upx 10upx;
+}
+
+.user-list .b-item {
+	margin-bottom: 50upx;
+	height: 100upx;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+}
+
+.user-list .b-item:last-child {
+	margin-bottom: 0;
+}
+
+.user-list .b-item .b-sort {
+	width: 100upx;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+}
+
+.user-list .b-item .b-sort text {
+	font-size: 36upx;
+	color: #808080;
+}
+
+.user-list .b-item .b-sort image {
+	width: 60upx;
+	height: 60upx;
+}
+
+.user-list .b-item .b-avatar {
+	width: 100upx;
+	height: 100upx;
+}
+
+.user-list .b-item .b-avatar image {
+	width: 100upx;
+	height: 100upx;
+	border-radius: 50%;
+}
+
+.user-list .b-item .b-user {
+	margin-left: 20upx;
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+}
+
+.user-list .b-item .b-user .s-name {
+	font-size: 32upx;
+}
+
+.user-list .b-item .b-user .s-org {
+	margin-top: 10upx;
+	font-size: 26upx;
+	color: #a6a6a6;
+}
+
+.user-list .b-item .b-data {
+	display: flex;
+	flex-direction: column;
+	align-items: flex-end;
+}
+
+.user-list .b-item .b-data .s-score {
+	color: #ffc300;
+	font-size: 40upx;
+}
+
+.user-list .b-item .b-data .s-time {
+	margin-top: 10upx;
+	color: #a6a6a6;
+	font-size: 24upx;
+}
+
+button::after {
+	border: none;
+}
+</style>

+ 145 - 0
answer_pages/home/welcome.vue

@@ -0,0 +1,145 @@
+<template>
+	<view class="wrap">
+		<u-navbar title="答题即将开始" bgColor="rgba(255,255,255,0.3)" leftText="返回" :autoBack="true" titleStyle="font-weight:bold;color:#7a5831"></u-navbar>
+		<view style="position: relative" class="screen" v-if="screenImage" :style="{ height: screenHeight + 'px' }">
+			<image :src="screenImage" mode="aspectFill" :style="{ height: screenHeight + 'px' }"></image>
+			<view @click="skipBtn" style="position: absolute; top: 200rpx; right: 38rpx; display: flex; align-items: center; justify-content: space-between">
+				<u-button customStyle="width:100rpx" shape="circle" size="mini" text="跳过" color="#3e3c41"></u-button>
+			</view>
+
+			<view class="b-countdown" @click="onJumHome">
+				<view class="s-text">{{ screenCountdown }}</view>
+			</view>
+		</view>
+		<!-- 加载中 -->
+
+		<load-more :loadingType="loadingType" :loadingText="loadingText" :top="300"></load-more>
+	</view>
+</template>
+
+<script>
+import { getConfig } from '@/service/api/common.js';
+import { silentReload, smartLogin } from '@/service/request/main.js';
+import Util from '../../common/util.js';
+// import mixinsCommon from '../../mixins/common.js';
+
+export default {
+	// mixins: [mixinsCommon],
+	data() {
+		return {
+			screenHeight: 0,
+			screenImage: 'https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/dtydy.png',
+			screenCountdown: 5,
+			loadingType: 1,
+			loadingText: '',
+			interval: 0,
+			aid: 0,
+			user: {}
+		};
+	},
+	onLoad(options) {
+		console.log('web onLoad', options);
+		let params = options;
+		this.aid = options.aid;
+		let from = params.from;
+		if (params.scene) {
+			params = Util.urlDecode(decodeURIComponent(params.scene));
+			from = 'qrcode';
+		}
+		this.$store.commit('setActivityId', params.aid);
+		getConfig().then(([err, res]) => {
+			// console.log('getConfig', err, res);
+			if (!err) {
+				this.user = this.$common.userInfo();
+
+				this.loadingType = -1;
+				this.$store.commit('setGlobalConfig', res);
+				// this.screenImage = res.welcome_img;
+				this.screenCountdown = res.welcome_countdown || 5;
+				if (this.screenImage) {
+					// 验证码倒计时
+					this.interval = setInterval(() => {
+						this.screenCountdown--;
+					}, 1000);
+					setTimeout(() => {
+						if (this.interval > 0) {
+							this.onJumHome(from);
+						}
+					}, this.screenCountdown * 1000);
+				} else {
+					this.onJumHome(from);
+				}
+			} else {
+				this.loadingType = 3;
+				this.loadingText = err.data.msg || '加载失败';
+			}
+		});
+
+		// 获取窗口高度
+		const systemInfo = uni.getSystemInfoSync();
+		this.screenHeight = systemInfo.screenHeight;
+	},
+	onShow() {},
+	methods: {
+		onJumHome(from) {
+			if (this.interval > 0) {
+				clearInterval(this.interval);
+				this.interval = 0;
+			}
+			if (this.$store.getters.accessToken) {
+				// console.log(this.user, '78787878');
+				uni.redirectTo({
+					url: '/answer_pages/home/index?SharerId=' + this.user.id // 跳到首页
+				});
+			} else {
+				if (from === 'qrcode') {
+					// console.info('needLogin qrcode', 'silentReload');
+					silentReload(false, ''); // 扫描尝试自动登录
+				} else {
+					smartLogin('/answer_pages/home/dashboard');
+				}
+			}
+		},
+		/* 跳过事件 */
+		skipBtn() {
+			clearTimeout(this.interval);
+			this.interval = null;
+			uni.redirectTo({
+				url: '/answer_pages/home/index?SharerId=' + this.user.id // 跳到首页
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.wrap {
+}
+
+.screen {
+	width: 750upx;
+
+	image {
+		width: 750upx;
+	}
+
+	.b-countdown {
+		position: absolute;
+		right: 80upx;
+		bottom: 80upx;
+		width: 80upx;
+		height: 80upx;
+		border-radius: 40upx;
+		background: rgba(0, 0, 0, 0.5);
+		display: flex;
+		align-items: center;
+		justify-content: center;
+
+		.s-text {
+			font-size: 32upx;
+			font-weight: bold;
+			color: #fff;
+		}
+	}
+}
+</style>

+ 90 - 0
answer_pages/user/autoLogin.vue

@@ -0,0 +1,90 @@
+<template>
+	<view class="wrap">
+		<view class="tips">
+			<image class="b-image" src="/static/imgs/loading_image.png" alt="" mode="aspectFit"></image>
+			<view class="b-tit">尝试自动登录,请稍后....</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { wechatAutoLogin } from '@/service/api/user.js';
+import { smartLogin } from '@/service/request/main.js';
+import { encodeRedirectUrl, decodeRedirectUrl } from '../../common/util.js';
+
+export default {
+	data() {
+		return {};
+	},
+	onLoad(options) {
+		console.log('onLoad autoLogin', options);
+		// 微信小程序自动登录
+		const returnPage = options.from ? decodeRedirectUrl(options.from) : 'pages/home/index';
+		uni.login().then(([err, res]) => {
+			console.log('uni.login', err, res);
+			if (!err) {
+				wechatAutoLogin(res.code).then(([err, res]) => {
+					console.log('wechatAutoLogin', err, res);
+					this.$store.commit('unlock', 'silentReload');
+					if (!err) {
+						console.warn('needLogin silentReload', 'success');
+						this.$store.commit('setAccessToken', res.token);
+						this.$store.commit('setActivityId', res.activity_id);
+						// 返回之前页面
+						uni.reLaunch({
+							url: '/' + returnPage
+						});
+					} else {
+						console.error('needLogin silentReload', 'fail');
+						// uni.reLaunch({
+						// 	url: '/answer_pages/user/login?from=' + encodeRedirectUrl(returnPage)
+						// })
+						smartLogin(returnPage);
+					}
+				});
+			} else {
+				console.error('needLogin silentReload', 'code fail');
+				this.$store.commit('unlock', 'silentReload');
+				// uni.reLaunch({
+				// 	url: '/answer_pages/user/login?from=' + encodeRedirectUrl(returnPage)
+				// })
+				smartLogin(returnPage);
+			}
+		});
+	},
+	methods: {}
+};
+</script>
+
+<style lang="scss">
+page {
+	padding-bottom: env(safe-area-inset-bottom);
+	background: #ffffff;
+	height: 100%;
+}
+
+.wrap {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	width: 100%;
+	height: 100%;
+}
+
+.tips {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+
+	.b-image {
+		width: 300upx;
+		height: 240upx;
+	}
+
+	.b-tit {
+		margin-top: 40upx;
+		color: #666;
+		font-size: 28upx;
+	}
+}
+</style>

+ 138 - 0
answer_pages/user/changeCode.vue

@@ -0,0 +1,138 @@
+<template>
+	<view class="wrap">
+		<view class="user-info">
+			<view class="b-item">
+				<view class="s-tit">旧登录码</view>
+				<input class="s-input" :value="form.oldCode" maxlength="12" @input="onInput" data-field="oldCode" placeholder="必须输入旧的" />
+			</view>
+			<view class="b-item">
+				<view class="s-tit">新登录码</view>
+				<input class="s-input" :value="form.newCode" maxlength="12" @input="onInput" data-field="newCode" placeholder="字符长度3-12" />
+			</view>
+			<view class="b-item">
+				<view class="s-tit">重复输入</view>
+				<input class="s-input" :value="form.repeatCode" maxlength="12" @input="onInput" @blur="onBlur" data-field="repeatCode" placeholder="重复新的登录码" />
+			</view>
+		</view>
+		<view class="btn-save" :class="{ disable: disable.submit }" @click="onSave">保存修改</view>
+	</view>
+</template>
+
+<script>
+import { changeCode } from '@/service/api/user.js';
+import mixinsCommon from '@/mixins/common.js';
+import mixinsAuth from '@/mixins/auth.js';
+export default {
+	mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			form: {
+				oldCode: '',
+				newCode: '',
+				repeatCode: ''
+			},
+			disable: {
+				submit: false
+			}
+		};
+	},
+	onLoad() {},
+	methods: {
+		onBlur(e) {
+			console.log('onBlur', e);
+			const field = e.target.dataset.field;
+			if (field === 'repeatCode') {
+				if (this.newCode) {
+					if (this.repeatCode != this.newCode) {
+						return this.$logic.showToast('重复输入新的登录码不对');
+					}
+				}
+			}
+		},
+		onInput(e) {
+			console.log('onInput', e);
+			const field = e.target.dataset.field;
+			this.form[field] = e.detail.value;
+		},
+		onSave() {
+			if (this.disable.submit) {
+				return;
+			}
+			if (!this.form.oldCode) {
+				return this.$logic.showToast('旧登录码不能为空');
+			}
+			if (!this.form.newCode) {
+				return this.$logic.showToast('新登录码不能为空');
+			}
+			if (this.form.newCode.length < 3) {
+				return this.$logic.showToast('新登录码长度不能小于3位');
+			}
+			if (this.form.repeatCode != this.form.newCode) {
+				return this.$logic.showToast('重复输入新登录码不对');
+			}
+			this.disable.submit = true;
+			changeCode(this.form.oldCode, this.form.newCode).then(([err, res]) => {
+				console.log('changeCode', err, res);
+				this.disable.submit = false;
+				if (!err) {
+					this.$logic.showToast('修改成功').then(([err, res]) => {
+						uni.reLaunch({
+							url: '/answer_pages/user/mine'
+						});
+					});
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+page {
+	padding-bottom: env(safe-area-inset-bottom);
+	background: $pq-bg-color;
+}
+
+.wrap {
+	padding: 0 40upx;
+}
+
+.user-info {
+	margin-top: 40upx;
+	background: #fff;
+	border-radius: 20upx;
+
+	.b-item {
+		padding: 0 40upx;
+		border-bottom: 1upx solid $pq-bg-color;
+		height: 100upx;
+		display: flex;
+		align-items: center;
+		position: relative;
+
+		.s-tit {
+			color: #000000;
+			font-size: 26upx;
+		}
+
+		.s-input {
+			flex: 1;
+			color: #808080;
+			text-align: right;
+			font-size: 26upx;
+		}
+	}
+}
+
+.btn-save {
+	margin-top: 50upx;
+	height: 100upx;
+	line-height: 100upx;
+	text-align: center;
+	color: #fff;
+	font-size: 32upx;
+	background: #da5650;
+	border-radius: 50upx;
+	letter-spacing: 10upx;
+}
+</style>

+ 244 - 0
answer_pages/user/changePhone.vue

@@ -0,0 +1,244 @@
+<template>
+	<view class="wrap">
+		<view class="main">
+			<view class="user-info">
+				<view class="b-item">
+					<view class="s-tit">旧手机号</view>
+					<text class="s-input">{{ form.oldPhone }}</text>
+				</view>
+				<template v-if="loginMethods.indexOf('phone_captcha') !== -1">
+					<view class="b-item">
+						<view class="s-tit">新手机号</view>
+						<input class="s-input" :value="form.newPhone" minlength="11" maxlength="11" @input="onInput" data-field="newPhone" placeholder="请输入新手机号" />
+					</view>
+					<view class="b-item">
+						<view class="s-tit">验证码</view>
+						<input class="s-input" :value="form.newCaptcha" maxlength="12" @input="onInput" data-field="newCaptcha" placeholder="请输入验证码" />
+						<view class="s-captcha" :class="{ disable: captcha.second > 0 }" @click="onPhoneCaptcha">
+							<text v-if="!captcha.retry && !captcha.second">获取验证码</text>
+							<text v-else-if="captcha.second > 0">重新获取 {{ captcha.second }}秒</text>
+							<text v-else-if="captcha.retry">重新获取</text>
+						</view>
+					</view>
+				</template>
+			</view>
+			<template v-if="loginMethods.indexOf('phone_captcha') !== -1">
+				<view class="btn-save" :class="{ disable: disable.submit }" @click="onSave">保存修改</view>
+			</template>
+			<!-- #ifdef  MP-WEIXIN -->
+			<button class="btn-save f-wechat open-data-btn" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber" v-if="loginMethods.indexOf('wechat_phone') !== -1">
+				授权微信手机号
+			</button>
+			<!-- #endif -->
+		</view>
+	</view>
+</template>
+
+<script>
+import { getUserInfo, sendPhoneCaptcha, changePhone, changeWechatPhone } from '@/service/api/user.js';
+import mixinsCommon from '@/mixins/common.js';
+import mixinsAuth from '@/mixins/auth.js';
+export default {
+	mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			form: {
+				oldPhone: '',
+				newPhone: '',
+				newCaptcha: ''
+			},
+			disable: {
+				submit: false
+			},
+			captcha: {
+				retry: false,
+				second: 0
+			}
+		};
+	},
+	onLoad() {
+		getUserInfo().then(([err, res]) => {
+			console.log('getUserInfo', err, res);
+			if (!err) {
+				this.form.oldPhone = res.phone;
+			}
+		});
+	},
+	computed: {
+		loginMethods() {
+			return this.$store.getters.globalConfig.login_methods || [];
+		}
+	},
+	methods: {
+		onInput(e) {
+			console.log('onInput', e);
+			const field = e.target.dataset.field;
+			this.form[field] = e.detail.value;
+		},
+		onPhoneCaptcha() {
+			if (this.disable.captcha) {
+				return;
+			}
+			if (!this.form.newPhone) {
+				return this.$logic.showToast('新手机号不能为空');
+			}
+			if (this.form.newPhone.length != 11) {
+				return this.$logic.showToast('新手机号长度必须为11位');
+			}
+			if (this.form.newPhone == this.form.oldPhone) {
+				return this.$logic.showToast('未修改手机号');
+			}
+			this.disable.captcha = true;
+			uni.login().then(([err, res]) => {
+				console.log('uni.login', err, res);
+				sendPhoneCaptcha(this.form.newPhone, res ? res.code : '', false).then(([err, res]) => {
+					console.log('sendPhoneCaptcha', err, res);
+					if (!err) {
+						uni.showToast({
+							title: '发送成功'
+						});
+
+						// 验证码倒计时
+						this.captcha.second = 60;
+						var interval = setInterval(() => {
+							this.captcha.second--;
+						}, 1000);
+						setTimeout(() => {
+							clearInterval(interval);
+							this.captcha.retry = true;
+							this.captcha.second = 0;
+							this.disable.captcha = false;
+						}, this.captcha.second * 1000);
+					} else {
+						this.disable.captcha = false;
+					}
+				});
+			});
+		},
+		onSave() {
+			if (this.disable.submit) {
+				return;
+			}
+			if (!this.form.newPhone) {
+				return this.$logic.showToast('新手机号不能为空');
+			}
+			if (this.form.newPhone.length != 11) {
+				return this.$logic.showToast('新手机号长度必须为11位');
+			}
+			if (this.form.newPhone == this.form.oldPhone) {
+				return this.$logic.showToast('未修改手机号');
+			}
+			if (!this.form.newCaptcha) {
+				return this.$logic.showToast('验证码不能为空');
+			}
+			this.disable.submit = true;
+			changePhone(this.form.newPhone, this.form.newCaptcha).then(([err, res]) => {
+				console.log('changePhone', err, res);
+				this.disable.submit = false;
+				if (!err) {
+					this.$logic.showToast('修改成功').then(([err, res]) => {
+						uni.reLaunch({
+							url: '/answer_pages/user/mine'
+						});
+					});
+				}
+			});
+		},
+		onGetPhoneNumber(e) {
+			console.log('onGetPhoneNumber', e);
+			if (e.detail.errMsg.indexOf(':ok') === -1) {
+				// fail user deny
+				this.$logic.showToast(e.detail.errMsg.replace('getPhoneNumber:', ''));
+				return;
+			}
+			uni.showLoading({
+				title: '修改手机号中'
+			});
+			uni.login().then(([err, res]) => {
+				console.log('uni.login', err, res);
+				changeWechatPhone(e.detail.encryptedData, e.detail.iv, e.detail.code || '', res ? res.code : '').then(([err, res]) => {
+					console.log('changeWechatPhone', err, res);
+					uni.hideLoading();
+					if (!err) {
+						this.$logic.showToast('修改成功').then(([err, res]) => {
+							uni.reLaunch({
+								url: '/answer_pages/user/mine'
+							});
+						});
+					}
+				});
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+page {
+	padding-bottom: env(safe-area-inset-bottom);
+	background: $pq-bg-color;
+}
+
+.wrap {
+	padding: 0 40upx;
+}
+
+.user-info {
+	margin-top: 40upx;
+	background: #fff;
+	border-radius: 20upx;
+
+	.b-item {
+		padding: 0 40upx;
+		border-bottom: 1upx solid $pq-bg-color;
+		height: 100upx;
+		display: flex;
+		align-items: center;
+		position: relative;
+
+		.s-tit {
+			color: #000000;
+			font-size: 26upx;
+		}
+
+		.s-input {
+			flex: 1;
+			color: #808080;
+			text-align: right;
+			font-size: 26upx;
+		}
+
+		.s-captcha {
+			margin-left: 20upx;
+			padding: 0 30upx;
+			background: #fff;
+			border: 2upx #ccc solid;
+			height: 64upx;
+			line-height: 64upx;
+			border-radius: 32upx;
+			color: #808080;
+			font-size: 24upx;
+
+			&.disable {
+				color: #999;
+			}
+		}
+	}
+}
+
+.btn-save {
+	margin-top: 50upx;
+	height: 100upx;
+	line-height: 100upx;
+	text-align: center;
+	color: #fff;
+	font-size: 32upx;
+	background: #da5650;
+	border-radius: 50upx;
+	letter-spacing: 10upx;
+
+	&.f-wechat {
+		background: #35c773;
+	}
+}
+</style>

+ 517 - 0
answer_pages/user/login.vue

@@ -0,0 +1,517 @@
+<template>
+	<view class="wrap">
+		<view class="logo">
+			<image :src="topImage || '/static/imgs/login_top_loading.jpg'" mode="widthFix"></image>
+		</view>
+		<view class="main">
+			<template v-if="loginMethod === 'name_code'">
+				<view class="form">
+					<view class="b-row">
+						<view class="b-input">
+							<view class="s-icon iconfont icon-account"></view>
+							<input type="text" :value="form.name" @input="onInputName" placeholder="请输入成员姓名" />
+						</view>
+					</view>
+					<view class="b-row">
+						<view class="b-input">
+							<view class="s-icon iconfont icon-password"></view>
+							<input type="text" :value="form.code" @input="onInputCode" placeholder="请输入登录码" />
+						</view>
+					</view>
+				</view>
+				<view class="submit" :class="{ disable: disable.submit }" @click="onLoginByNameCode">立即登录</view>
+			</template>
+			<template v-else-if="loginMethod === 'number_code'">
+				<view class="form">
+					<view class="b-row">
+						<view class="b-input">
+							<view class="s-icon iconfont icon-card"></view>
+							<input type="text" :value="form.number" @input="onInputNumber" placeholder="请输入成员编号" />
+						</view>
+					</view>
+					<view class="b-row">
+						<view class="b-input">
+							<view class="s-icon iconfont icon-password"></view>
+							<input type="text" :value="form.code" @input="onInputCode" placeholder="请输入登录码" />
+						</view>
+					</view>
+				</view>
+				<view class="submit" :class="{ disable: disable.submit }" @click="onLoginByNumberCode">立即登录</view>
+			</template>
+			<template v-else-if="loginMethod === 'phone_captcha'">
+				<view class="form">
+					<view class="b-row">
+						<view class="b-input">
+							<view class="s-icon iconfont icon-account"></view>
+							<input type="text" :value="form.phone" @input="onInputPhone" placeholder="请输入手机号" />
+						</view>
+					</view>
+					<view class="b-row">
+						<view class="b-input">
+							<view class="s-icon iconfont icon-password"></view>
+							<input type="text" :value="form.captcha" @input="onInputCaptcha" placeholder="请输入验证码" />
+							<view class="s-captcha" :class="{ disable: captcha.second > 0 }" @click="onPhoneCaptcha">
+								<text v-if="!captcha.retry && !captcha.second">获取验证码</text>
+								<text v-else-if="captcha.second > 0">重新获取 {{ captcha.second }}秒</text>
+								<text v-else-if="captcha.retry">重新获取</text>
+							</view>
+						</view>
+					</view>
+				</view>
+				<view class="submit" :class="{ disable: disable.submit }" @click="onLoginByPhoneCaptcha">立即登录</view>
+			</template>
+			<!-- #ifdef  MP-WEIXIN -->
+			<button
+				class="submit f-wechat open-data-btn"
+				open-type="getPhoneNumber"
+				@getphonenumber="onGetPhoneNumber"
+				v-if="loginMethods.length === 1 && loginMethod === 'wechat_phone'"
+			>
+				微信快捷登录
+			</button>
+			<!-- #endif -->
+			<view class="other" v-if="loginMethods.length >= 3 || (loginMethods.length === 2 && loginMethods.indexOf('wechat_phone') === -1)">
+				<view class="split">
+					<view class="s-line"></view>
+					<view class="s-text">其他登录方式</view>
+					<view class="s-line"></view>
+				</view>
+				<view class="login-method">
+					<view class="b-method f-name" v-if="loginMethods.indexOf('name_code') !== -1 && loginMethod !== 'name_code'" @click="onChangeMethod('name_code')">
+						<view class="iconfont icon-key"></view>
+						<view class="s-text">姓名登录码</view>
+					</view>
+					<view class="b-method f-number" v-if="loginMethods.indexOf('number_code') !== -1 && loginMethod !== 'number_code'" @click="onChangeMethod('number_code')">
+						<view class="iconfont icon-key"></view>
+						<view class="s-text">编号登录码</view>
+					</view>
+					<view class="b-method f-phone" v-if="loginMethods.indexOf('phone_captcha') !== -1 && loginMethod !== 'phone_captcha'" @click="onChangeMethod('phone_captcha')">
+						<view class="iconfont icon-phone"></view>
+						<view class="s-text">手机验证码</view>
+					</view>
+					<!-- #ifdef  MP-WEIXIN -->
+					<button
+						class="b-method f-wechat open-data-btn"
+						open-type="getPhoneNumber"
+						@getphonenumber="onGetPhoneNumber"
+						v-if="loginMethods.indexOf('wechat_phone') !== -1"
+					>
+						<view class="iconfont icon-wechat"></view>
+						<view class="s-text">微信快捷登录</view>
+					</button>
+					<!-- #endif -->
+				</view>
+			</view>
+			<view class="tips">{{ loginNote }}</view>
+		</view>
+		<view class="select-activity" v-if="selectActivity" @click="onJumpActivity">
+			<view class="iconfont icon-dashboard"></view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { loginByNameCode, loginByNumberCode, loginByPhoneCaptcha, loginByWechatPhone, sendPhoneCaptcha } from '@/service/api/user.js';
+import { smartLogin } from '@/service/request/main.js';
+import mixinsCommon from '@/mixins/common.js';
+import { decodeRedirectUrl } from '../../common/util';
+export default {
+	mixins: [mixinsCommon],
+	data() {
+		return {
+			form: {
+				name: '',
+				number: '',
+				code: '',
+				phone: '',
+				captcha: ''
+			},
+			loginMethod: '',
+			disable: {
+				captcha: false,
+				submit: false
+			},
+			captcha: {
+				retry: false,
+				second: 0
+			}
+		};
+	},
+	onLoad(options) {
+		this.returnPage = options.from ? decodeRedirectUrl(options.from) : 'pages/home/index';
+		console.log('login returnPage', this.returnPage);
+		smartLogin('', true); // 判断是否扫码或进入活动列表页,因为当前页没有接口请求和权限校验
+	},
+	computed: {
+		selectActivity() {
+			return this.$store.getters.globalConfig.select_activity;
+		},
+		topImage() {
+			return this.$store.getters.globalConfig.login_top_img;
+		},
+		loginNote() {
+			return this.$store.getters.globalConfig.login_note || '欢迎参加答题竞赛活动。';
+		},
+		loginMethods() {
+			// ['name_code','number_code',phone_captcha','wechat_phone']
+			let loginMethods = this.$store.getters.globalConfig.login_methods || [];
+			if (loginMethods.length > 0) {
+				this.loginMethod = loginMethods[0];
+				console.log('this.loginMethod');
+			}
+
+			return loginMethods;
+		}
+	},
+	methods: {
+		onInputName(e) {
+			// console.log('onInputName', e)
+			this.form.name = e.detail.value;
+		},
+		onInputNumber(e) {
+			// console.log('onInputName', e)
+			this.form.number = e.detail.value;
+		},
+		onInputCode(e) {
+			// console.log('onInputCode', e)
+			this.form.code = e.detail.value;
+		},
+		onInputPhone(e) {
+			// console.log('onInputPhone', e)
+			this.form.phone = e.detail.value;
+		},
+		onInputCaptcha(e) {
+			// console.log('onInputCaptcha', e)
+			this.form.captcha = e.detail.value;
+		},
+		onChangeMethod(method) {
+			this.loginMethod = method;
+		},
+		onPhoneCaptcha() {
+			if (this.disable.captcha) {
+				return;
+			}
+			this.disable.captcha = true;
+			uni.login().then(([err, res]) => {
+				console.log('uni.login', err, res);
+				sendPhoneCaptcha(this.form.phone, res ? res.code : '', true).then(([err, res]) => {
+					console.log('sendPhoneCaptcha', err, res);
+					if (!err) {
+						uni.showToast({
+							title: '发送成功'
+						});
+
+						// 验证码倒计时
+						this.captcha.second = 60;
+						var interval = setInterval(() => {
+							this.captcha.second--;
+						}, 1000);
+						setTimeout(() => {
+							clearInterval(interval);
+							this.captcha.retry = true;
+							this.captcha.second = 0;
+							this.disable.captcha = false;
+						}, this.captcha.second * 1000);
+					} else {
+						this.disable.captcha = false;
+					}
+				});
+			});
+		},
+		onLoginByNameCode() {
+			if (this.disable.submit) {
+				return;
+			}
+			if (!this.form.name) {
+				return this.$logic.showToast('成员姓名不能为空');
+			}
+			if (!this.form.code) {
+				return this.$logic.showToast('登录码不能为空');
+			}
+			this.disable.submit = true;
+			uni.login().then(([err, res]) => {
+				console.log('uni.login', err, res);
+				loginByNameCode(this.form.name, this.form.code, res ? res.code : '').then(([err, res]) => {
+					console.log('loginByNameCode', err, res);
+					this.disable.submit = false;
+					if (!err) {
+						this.$store.commit('setAccessToken', res.token);
+						this.$store.commit('setActivityId', res.activity_id);
+						this.$logic.showToast('登录成功').then(([err, res]) => {
+							uni.reLaunch({
+								url: '/' + this.returnPage
+							});
+						});
+					}
+				});
+			});
+		},
+		onLoginByNumberCode() {
+			if (this.disable.submit) {
+				return;
+			}
+			if (!this.form.number) {
+				return this.$logic.showToast('成员编号不能为空');
+			}
+			if (!this.form.code) {
+				return this.$logic.showToast('登录码不能为空');
+			}
+			this.disable.submit = true;
+			uni.login().then(([err, res]) => {
+				console.log('uni.login', err, res);
+				loginByNumberCode(this.form.number, this.form.code, res ? res.code : '').then(([err, res]) => {
+					console.log('loginByNumberCode', err, res);
+					this.disable.submit = false;
+					if (!err) {
+						this.$store.commit('setAccessToken', res.token);
+						this.$store.commit('setActivityId', res.activity_id);
+						this.$logic.showToast('登录成功').then(([err, res]) => {
+							uni.reLaunch({
+								url: '/' + this.returnPage
+							});
+						});
+					}
+				});
+			});
+		},
+		onLoginByPhoneCaptcha() {
+			if (this.disable.submit) {
+				return;
+			}
+			if (!this.form.phone) {
+				return this.$logic.showToast('手机号不能为空');
+			}
+			if (!this.form.captcha) {
+				return this.$logic.showToast('验证码不能为空');
+			}
+			this.disable.submit = true;
+			uni.login().then(([err, res]) => {
+				console.log('uni.login', err, res);
+				loginByPhoneCaptcha(this.form.phone, this.form.captcha, res ? res.code : '').then(([err, res]) => {
+					console.log('loginByPhoneCaptcha', err, res);
+					this.disable.submit = false;
+					if (!err) {
+						this.$store.commit('setAccessToken', res.token);
+						this.$store.commit('setActivityId', res.activity_id);
+						this.$logic.showToast('登录成功').then(([err, res]) => {
+							uni.reLaunch({
+								url: '/' + this.returnPage
+							});
+						});
+					}
+				});
+			});
+		},
+		onGetPhoneNumber(e) {
+			console.log('onGetPhoneNumber', e);
+			if (e.detail.errMsg.indexOf(':ok') === -1) {
+				// fail user deny
+				this.$logic.showToast(e.detail.errMsg.replace('getPhoneNumber:', ''));
+				return;
+			}
+			uni.showLoading({
+				title: '尝试登录中'
+			});
+			uni.login().then(([err, res]) => {
+				console.log('uni.login', err, res);
+
+				loginByWechatPhone(e.detail.encryptedData, e.detail.iv, e.detail.code || '', res[1].code).then(([err, res]) => {
+					console.log('loginByWechatPhone', err, res);
+					uni.hideLoading();
+					if (!err) {
+						this.$store.commit('setAccessToken', res.token);
+						this.$store.commit('setActivityId', res.activity_id);
+						this.$logic.showToast('登录成功').then(([err, res]) => {
+							uni.reLaunch({
+								url: '/' + this.returnPage
+							});
+						});
+					}
+				});
+			});
+		},
+		onJumpActivity() {
+			uni.redirectTo({
+				url: '/answer_pages/home/dashboard'
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+page {
+	padding-bottom: env(safe-area-inset-bottom);
+	background: #ffffff;
+}
+
+.wrap {
+}
+
+.logo {
+	image {
+		width: 100%;
+		height: 460upx;
+	}
+}
+
+.main {
+	padding: 50upx;
+}
+
+.form {
+	.b-row {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		margin-bottom: 30upx;
+		border-bottom: 1upx solid #e5e5e5;
+	}
+
+	.b-input {
+		flex: 1;
+		padding: 0 20upx;
+		width: 100%;
+		height: 90upx;
+		display: flex;
+		align-items: center;
+
+		.s-icon {
+			color: #ccc;
+			font-size: 40upx;
+		}
+
+		input {
+			margin-left: 30upx;
+			flex: 1;
+			color: #333;
+			font-size: 28upx;
+		}
+
+		.s-captcha {
+			padding: 0 30upx;
+			background: #fff;
+			border: 2upx #ccc solid;
+			height: 64upx;
+			line-height: 64upx;
+			border-radius: 32upx;
+			color: #808080;
+			font-size: 24upx;
+
+			&.disable {
+				color: #999;
+			}
+		}
+	}
+}
+
+.submit {
+	margin-top: 80upx;
+	height: 100upx;
+	line-height: 100upx;
+	text-align: center;
+	color: #fff;
+	font-size: 32upx;
+	background: #da5650;
+	border-radius: 50upx;
+	letter-spacing: 10upx;
+
+	&.f-wechat {
+		background: #35c773;
+	}
+
+	&.disable {
+		background: rgba($color: #da5650, $alpha: 0.2);
+	}
+}
+
+.split {
+	margin-top: 50upx;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+
+	.s-line {
+		width: 200upx;
+		height: 1upx;
+		background: #eee;
+	}
+
+	.s-text {
+		font-size: 28upx;
+		color: #808080;
+	}
+}
+
+.login-method {
+	margin-top: 30upx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	flex-wrap: wrap;
+
+	.b-method {
+		margin: 0 20upx 20upx 20upx;
+		width: 250upx;
+		// padding: 0 20upx;
+		height: 80upx;
+		border-radius: 10upx;
+		background: #808080;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		color: #fff;
+
+		&.f-name {
+			background: #64a2fa;
+		}
+
+		&.f-number {
+			background: #ee6c77;
+		}
+
+		&.f-phone {
+			background: #ff9c45;
+		}
+
+		&.f-wechat {
+			background: #35c773;
+		}
+
+		.iconfont {
+			margin-right: 10upx;
+			font-size: 40upx;
+		}
+
+		.s-text {
+			font-size: 26upx;
+		}
+	}
+}
+
+.tips {
+	margin-top: 50upx;
+	width: 100%;
+	color: #383838;
+	font-size: 24upx;
+}
+
+.select-activity {
+	position: absolute;
+	bottom: 50upx;
+	right: 50upx;
+	background: #fff;
+	border: 2upx solid #ddd;
+	width: 100upx;
+	height: 100upx;
+	border-radius: 50upx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+
+	.iconfont {
+		font-size: 50upx;
+		color: #da5650;
+	}
+}
+</style>

+ 233 - 0
answer_pages/user/mine.vue

@@ -0,0 +1,233 @@
+<template>
+	<view class="wrap">
+		<view class="user-view">
+			<view class="b-avatar">
+				<image :src="userInfo.avatar_url || '/static/imgs/user.png'" mode="aspectFill"></image>
+			</view>
+			<view class="b-info">
+				<!-- #ifdef  MP-WEIXIN -->
+				<button class="s-edit iconfont icon-edit open-data-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"></button>
+				<!-- #endif -->
+				<!-- #ifdef  H5 -->
+				<view class="s-edit iconfont icon-edit" @click="onUploadAvatar"></view>
+				<!-- #endif -->
+			</view>
+		</view>
+		<view class="user-info">
+			<view class="b-item">
+				<text class="s-tit">成员姓名</text>
+				<text class="s-tip">{{ userInfo.name }}</text>
+			</view>
+			<view class="b-item">
+				<text class="s-tit">成员编号</text>
+				<text class="s-tip">{{ userInfo.number }}</text>
+			</view>
+			<view class="b-item">
+				<text class="s-tit">手机号</text>
+				<text class="s-tip">{{ userInfo.phone }}</text>
+			</view>
+			<view class="b-item">
+				<text class="s-tit">所在分组</text>
+				<text class="s-tip">{{ userInfo.group_name }}</text>
+			</view>
+			<!-- #ifdef  MP-WEIXIN -->
+			<view class="b-item" v-if="loginMethods.indexOf('phone_captcha') !== -1 || loginMethods.indexOf('wechat_phone') !== -1" @click="onChangePhone">
+				<text class="s-tit">修改手机号</text>
+				<text class="s-tip"></text>
+				<text class="s-more iconfont icon-more"></text>
+			</view>
+			<!-- #endif -->
+			<!-- #ifdef  H5 -->
+			<view class="b-item" v-if="loginMethods.indexOf('phone_captcha') !== -1" @click="onChangePhone">
+				<text class="s-tit">修改手机号</text>
+				<text class="s-tip"></text>
+				<text class="s-more iconfont icon-more"></text>
+			</view>
+			<!-- #endif -->
+			<view class="b-item" v-if="loginMethods.indexOf('name_code') !== -1 || loginMethods.indexOf('number_code') !== -1" @click="onChangeCode">
+				<text class="s-tit">修改登录码</text>
+				<text class="s-tip"></text>
+				<text class="s-more iconfont icon-more"></text>
+			</view>
+		</view>
+		<view class="btn-logout" @click="onLogout">退出登录</view>
+	</view>
+</template>
+
+<script>
+import { getUserInfo, updateUserInfo, logout } from '@/service/api/user.js';
+import { uploadImage } from '@/service/api/common.js';
+import mixinsCommon from '@/mixins/common.js';
+import mixinsAuth from '@/mixins/auth.js';
+export default {
+	mixins: [mixinsCommon, mixinsAuth],
+	data() {
+		return {
+			userInfo: {}
+		};
+	},
+	onLoad() {
+		getUserInfo().then(([err, res]) => {
+			console.log('getUserInfo', err, res);
+			if (!err) {
+				this.userInfo = res;
+			}
+		});
+	},
+	computed: {
+		loginMethods() {
+			return this.$store.getters.globalConfig.login_methods || [];
+		}
+	},
+	methods: {
+		onChangePhone() {
+			uni.navigateTo({
+				url: '/answer_pages/user/changePhone'
+			});
+		},
+		onChangeCode() {
+			uni.navigateTo({
+				url: '/answer_pages/user/changeCode'
+			});
+		},
+		onLogout() {
+			logout().then(([err, res]) => {
+				console.log('logout', err, res);
+				this.$store.commit('delAccessToken');
+				uni.reLaunch({
+					url: '/answer_pages/user/login'
+				});
+			});
+		},
+		onChooseAvatar(e) {
+			console.log('onChooseAvatar', e);
+			let imageUrl = e.detail.avatarUrl;
+			uploadImage(imageUrl).then(([err, res]) => {
+				console.log('uploadImage', err, res);
+				if (!err) {
+					updateUserInfo(res.path).then(([err, res]) => {
+						console.log('updateUserInfo', err, res);
+						if (!err) {
+							this.userInfo.avatar_url = imageUrl;
+							this.$logic.showToast('修改成功');
+						}
+					});
+				}
+			});
+		},
+		onUploadAvatar(e) {
+			console.log('onUploadAvatar', e);
+			uni.chooseImage({
+				count: 1, // 1张
+				sizeType: ['compressed'] // 压缩图
+			}).then(([err, res]) => {
+				console.log('chooseImage', err, res);
+				if (!err) {
+					let imageUrl = res.tempFilePaths[0];
+					uploadImage(imageUrl).then(([err, res]) => {
+						console.log('uploadImage', err, res);
+						if (!err) {
+							updateUserInfo(res.path).then(([err, res]) => {
+								console.log('updateUserInfo', err, res);
+								if (!err) {
+									this.userInfo.avatar_url = imageUrl;
+									this.$logic.showToast('修改成功');
+								}
+							});
+						}
+					});
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+page {
+	padding-bottom: env(safe-area-inset-bottom);
+	background: $pq-bg-color;
+}
+
+.wrap {
+	padding: 0 40upx 100upx 40upx;
+}
+
+.user-view {
+	margin-top: 50upx;
+	display: flex;
+	align-items: center;
+	flex-direction: column;
+	z-index: 1;
+
+	.b-avatar {
+		width: 180upx;
+		height: 180upx;
+
+		image {
+			width: 180upx;
+			height: 180upx;
+			border: 5upx solid #fff;
+			border-radius: 50%;
+		}
+	}
+
+	.b-info {
+		margin-top: 10upx;
+
+		.s-nickname {
+			font-weight: bold;
+		}
+
+		.s-edit {
+			margin-top: 20upx;
+			font-size: 32upx;
+			color: #bbb;
+		}
+	}
+}
+
+.user-info {
+	margin-top: 50upx;
+	background: #fff;
+	border-radius: 20upx;
+
+	.b-item {
+		padding: 0 40upx;
+		border-bottom: 1upx solid $pq-bg-color;
+		height: 100upx;
+		display: flex;
+		align-items: center;
+		position: relative;
+		font-size: 26upx;
+
+		.s-tit {
+			color: #000;
+		}
+
+		.s-tip {
+			flex: 1;
+			color: #808080;
+			text-align: right;
+		}
+
+		.s-more {
+			margin-left: 10upx;
+			color: #808080;
+		}
+	}
+}
+
+.btn-logout {
+	margin-top: 60upx;
+	height: 80upx;
+	line-height: 80upx;
+	text-align: center;
+	color: #da5650;
+	background: #ffffff;
+	font-size: 32upx;
+	border: 1px solid #da5650;
+	border-radius: 40upx;
+	letter-spacing: 10upx;
+}
+</style>

+ 53 - 0
answer_pages/user/tips.vue

@@ -0,0 +1,53 @@
+<template>
+	<view class="wrap">
+		<view class="tips">
+			<image class="b-image" src="/static/imgs/loading_image.png" alt="" mode="aspectFit"></image>
+			<view class="b-tit">请扫活动二维码进入</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {};
+	},
+	onLoad(options) {
+		console.log('onLoad tips', options);
+	},
+	methods: {}
+};
+</script>
+
+<style lang="scss">
+page {
+	padding-bottom: env(safe-area-inset-bottom);
+	background: #ffffff;
+	height: 100%;
+}
+
+.wrap {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	width: 100%;
+	height: 100%;
+}
+
+.tips {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+
+	.b-image {
+		width: 300upx;
+		height: 240upx;
+	}
+
+	.b-tit {
+		margin-top: 40upx;
+		color: #666;
+		font-size: 28upx;
+	}
+}
+</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)
+    }
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1226 - 0
colorui/icon.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 4032 - 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

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1265 - 0
common/WeCropper.js


+ 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,
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 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";
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 147 - 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";
+}

+ 385 - 0
common/md5.js

@@ -0,0 +1,385 @@
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0;   /* hex output format. 0 - lowercase; 1 - uppercase        */
+var b64pad  = "";  /* base-64 pad character. "=" for strict RFC compliance   */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_md5(s)    { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
+function b64_md5(s)    { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
+function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
+function hex_hmac_md5(k, d)
+  { return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
+function b64_hmac_md5(k, d)
+  { return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
+function any_hmac_md5(k, d, e)
+  { return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+  return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of a raw string
+ */
+function rstr_md5(s)
+{
+  return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data (raw strings)
+ */
+function rstr_hmac_md5(key, data)
+{
+  var bkey = rstr2binl(key);
+  if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
+
+  var ipad = Array(16), opad = Array(16);
+  for(var i = 0; i < 16; i++)
+  {
+    ipad[i] = bkey[i] ^ 0x36363636;
+    opad[i] = bkey[i] ^ 0x5C5C5C5C;
+  }
+
+  var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
+  return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
+}
+
+/*
+ * Convert a raw string to a hex string
+ */
+function rstr2hex(input)
+{
+  try { hexcase } catch(e) { hexcase=0; }
+  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+  var output = "";
+  var x;
+  for(var i = 0; i < input.length; i++)
+  {
+    x = input.charCodeAt(i);
+    output += hex_tab.charAt((x >>> 4) & 0x0F)
+           +  hex_tab.charAt( x        & 0x0F);
+  }
+  return output;
+}
+
+/*
+ * Convert a raw string to a base-64 string
+ */
+function rstr2b64(input)
+{
+  try { b64pad } catch(e) { b64pad=''; }
+  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  var rcode = 'NUMOOEXAXNLVGN';
+  var output = "";
+  var len = input.length;
+  for(var i = 0; i < len; i += 3)
+  {
+    var triplet = (input.charCodeAt(i) << 16)
+                | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
+                | (i + 2 < len ? input.charCodeAt(i+2)      : 0);
+    for(var j = 0; j < 4; j++)
+    {
+      if(i * 8 + j * 6 > input.length * 8) output += b64pad;
+      else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
+    }
+  }
+  return output;
+}
+
+/*
+ * Convert a raw string to an arbitrary string encoding
+ */
+function rstr2any(input, encoding)
+{
+  var divisor = encoding.length;
+  var i, j, q, x, quotient;
+
+  /* Convert to an array of 16-bit big-endian values, forming the dividend */
+  var dividend = Array(Math.ceil(input.length / 2));
+  for(i = 0; i < dividend.length; i++)
+  {
+    dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
+  }
+
+  /*
+   * Repeatedly perform a long division. The binary array forms the dividend,
+   * the length of the encoding is the divisor. Once computed, the quotient
+   * forms the dividend for the next step. All remainders are stored for later
+   * use.
+   */
+  var full_length = Math.ceil(input.length * 8 / (Math.log(encoding.length) / Math.log(2)));
+  var remainders = Array(full_length);
+  for(j = 0; j < full_length; j++)
+  {
+    quotient = Array();
+    x = 0;
+    for(i = 0; i < dividend.length; i++)
+    {
+      x = (x << 16) + dividend[i];
+      q = Math.floor(x / divisor);
+      x -= q * divisor;
+      if(quotient.length > 0 || q > 0)
+        quotient[quotient.length] = q;
+    }
+    remainders[j] = x;
+    dividend = quotient;
+  }
+
+  /* Convert the remainders to the output string */
+  var output = "";
+  for(i = remainders.length - 1; i >= 0; i--)
+    output += encoding.charAt(remainders[i]);
+
+  return output;
+}
+
+/*
+ * Encode a string as utf-8.
+ * For efficiency, this assumes the input is valid utf-16.
+ */
+function str2rstr_utf8(input)
+{
+  var output = "";
+  var i = -1;
+  var x, y;
+
+  while(++i < input.length)
+  {
+    /* Decode utf-16 surrogate pairs */
+    x = input.charCodeAt(i);
+    y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
+    if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
+    {
+      x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
+      i++;
+    }
+
+    /* Encode output as utf-8 */
+    if(x <= 0x7F)
+      output += String.fromCharCode(x);
+    else if(x <= 0x7FF)
+      output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
+                                    0x80 | ( x         & 0x3F));
+    else if(x <= 0xFFFF)
+      output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
+                                    0x80 | ((x >>> 6 ) & 0x3F),
+                                    0x80 | ( x         & 0x3F));
+    else if(x <= 0x1FFFFF)
+      output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
+                                    0x80 | ((x >>> 12) & 0x3F),
+                                    0x80 | ((x >>> 6 ) & 0x3F),
+                                    0x80 | ( x         & 0x3F));
+  }
+  return output;
+}
+
+/*
+ * Encode a string as utf-16
+ */
+function str2rstr_utf16le(input)
+{
+  var output = "";
+  for(var i = 0; i < input.length; i++)
+    output += String.fromCharCode( input.charCodeAt(i)        & 0xFF,
+                                  (input.charCodeAt(i) >>> 8) & 0xFF);
+  return output;
+}
+
+function str2rstr_utf16be(input)
+{
+  var output = "";
+  for(var i = 0; i < input.length; i++)
+    output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
+                                   input.charCodeAt(i)        & 0xFF);
+  return output;
+}
+
+/*
+ * Convert a raw string to an array of little-endian words
+ * Characters >255 have their high-byte silently ignored.
+ */
+function rstr2binl(input)
+{
+  var output = Array(input.length >> 2);
+  for(var i = 0; i < output.length; i++)
+    output[i] = 0;
+  for(var i = 0; i < input.length * 8; i += 8)
+    output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
+  return output;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2rstr(input)
+{
+  var output = "";
+  for(var i = 0; i < input.length * 32; i += 8)
+    output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
+  return output;
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length.
+ */
+function binl_md5(x, len)
+{
+  /* append padding */
+  x[len >> 5] |= 0x80 << ((len) % 32);
+  x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+  var a =  1732584193;
+  var b = -271733879;
+  var c = -1732584194;
+  var d =  271733878;
+
+  for(var i = 0; i < x.length; i += 16)
+  {
+    var olda = a;
+    var oldb = b;
+    var oldc = c;
+    var oldd = d;
+
+    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
+    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
+    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
+    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
+    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
+
+    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
+    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
+    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
+    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
+    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
+    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
+    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
+    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
+    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
+    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
+    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
+    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
+    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
+    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
+    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
+    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+    a = safe_add(a, olda);
+    b = safe_add(b, oldb);
+    c = safe_add(c, oldc);
+    d = safe_add(d, oldd);
+  }
+  return Array(a, b, c, d);
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+  return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+  return (num << cnt) | (num >>> (32 - cnt));
+}
+
+module.exports = {
+	md5 : function(str){
+		return hex_md5(str);
+	}
+}

+ 72 - 0
common/share.js

@@ -0,0 +1,72 @@
+export default {
+	data() {
+		return {
+			//设置默认的分享参数
+			//如果页面不设置share,就触发这个默认的分享
+			share: {
+				title: '',
+				path: '',
+				imageUrl: 'https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/search.png',
+				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'
+				})
+			}
+		}
+	}
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 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
+}

+ 91 - 0
components/FuWenBen/changelog.md

@@ -0,0 +1,91 @@
+## 1.3.2(2024-03-14)
+1. 更新了toolbar样式与配置,见文档
+2. 更新示例工程,媒体查询响应式写法
+3. 优化了只读模式效果,开启只读模式后,文章内容的超链接可正常点击并跳转
+## 1.3.1(2024-03-14)
+1. 优化了只读功能,开启只读后自动隐藏工具栏
+2. 更新示例工程
+## 1.3.0(2024-03-07)
+1. 新增addLink的emit事件
+## 1.2.9(2024-02-23)
+1. 更新文档
+## 1.2.8(2024-02-23)
+1. 新增了添加超链接的工具,toolbar中link字段,默认开启
+2. 优化了部分逻辑
+3. 更新文档、更新示例工程
+## 1.2.7(2024-02-23)
+1. 更新文档,更新示例工程
+2. 添加toolbar中图标字体大小可配置项
+## 1.2.6(2024-02-22)
+1. 添加导出工具按钮,可将当前已编辑的html导出至页面解析
+2. 超链接工具按钮正在尝试开发中(貌似目前官方不支持)
+## 1.2.5(2024-02-19)
+1. 更新示例工程(吸顶写法)
+2. 完善调色板功能
+## 1.2.4(2024-02-18)
+1. 修复工具栏颜色按钮底色动态切换问题
+## 1.2.3(2024-02-18)
+1. 更新示例工程
+## 1.2.2(2024-02-18)
+1. 删除log调试打印
+## 1.2.1(2024-02-18)
+1. 修复了颜色图标不会动态切换的问题
+## 1.2.0(2024-02-18)
+1. 修复选择颜色时会将所选文字删除的bug
+## 1.1.9(2024-02-04)
+1. 更新示例工程
+## 1.1.8(2024-02-04)
+1. 文档修改
+## 1.1.7(2024-02-04)
+1. 新增toolbar配置项,可自由配置工具栏工具列表
+2. 移除组件内原templates属性,默认初始化编辑器内容请看文档使用方式示例
+3. 更新文档
+## 1.1.6(2024-01-31)
+1. 更好的兼容vue2了,修复在vue2下高度可能超出的问题
+2. 示例工程兼容vue2
+## 1.1.5(2024-01-30)
+1. 修复工具栏字体按钮无效的问题
+## 1.1.4(2024-01-30)
+1. 解决默认初始化内容时前缀空格或缩进无效的问题
+2. 解决点击工具栏高亮状态后输入内容时便失去高亮的bug
+3. 更新示例工程
+## 1.1.3(2024-01-23)
+1. 重写高度动态计算逻辑,现在对不同屏幕尺寸的适应性更强了
+## 1.1.2(2024-01-17)
+1. 修复分割线会生成多条的问题
+## 1.1.1(2024-01-15)
+1. 更新文档
+## 1.1.0(2024-01-15)
+1. insertText方法在插入内容的时候会移动光标聚焦,导致焦点回滚到视口处
+2. 更新示例工程
+## 1.0.9(2024-01-04)
+1. 更新文档
+## 1.0.8(2024-01-04)
+1. 修复h5端官方cdn请求失败的问题,详见问答贴:https://ask.dcloud.net.cn/article/40900
+## 1.0.7(2024-01-03)
+1.  移除v-bind="$attrs",该写法在微信小程序不支持
+## 1.0.6(2023-12-29)
+1. 更新文档
+## 1.0.5(2023-12-29)
+1. 更新了init方法,可以使用返回的editor实例尽情自定义
+2. 组件在<editor>上添加v-bind="$attrs"属性穿透,可以使用原editor参数,官方文档:https://uniapp.dcloud.net.cn/component/editor.html
+## 1.0.4(2023-12-29)
+1. 优化了切换文字和背景颜色是,可能会导致切换后不生效的问题
+2. 修复在部分设备上的微信小程序中可能会存在颜色版无法正常滑动的问题
+3. 更友好的交互体验:添加图标悬停字样描述、添加格式化文本弹窗确认
+4. 有插入视频的需求,暂时可能无法实现,官方给予的回复是:目前各端的eidtor组件都不能直接插入视频,编辑时可以采用视频封面占位,并在图片中保存视频信息,在预览时再还原为视频。
+## 1.0.3(2023-10-13)
+	1. 更新readme文档
+	2. 更新调整组件示例项目,添加插件代码中部分注释
+## 1.0.2(2023-10-13)
+	1. 更新uni_modules规范,可一键导入组件
+	2. 更新组件示例项目(包括使用uniCloud.uploadFile多选上传图片示例方法)
+## 1.0.1(2023-10-12)
+	1. 修复小程序中自动聚焦滚动到富文本组件区域的bug
+	2. 略微调整了富文本上方toolbar工具栏中按钮的大小尺寸
+## 1.0.0(2023-9-19)
+	1. 新增字体与背景颜色板
+	2. 可自定义预设内容模板
+	3. 解决官方样例在小程序和app部分报错不兼容的问题
+	4. 可配合云存储上传富文本中插入的图片 本质上是基于官方内置富文本editor组件改版封装,所以官方有的功能都有,官方能兼容的也都兼容
+

+ 825 - 0
components/FuWenBen/components/sp-editor/color-picker.vue

@@ -0,0 +1,825 @@
+<template>
+  <view v-if="show" class="t-wrapper" @touchmove.stop.prevent="moveHandle">
+    <view class="t-mask" :class="{ active: active }" @click.stop="close"></view>
+    <view class="t-box" :class="{ active: active }">
+      <view class="t-header">
+        <view class="t-header-button" @click="close">取消</view>
+        <view class="t-header-button" @click="confirm">确认</view>
+      </view>
+      <view class="t-color__box" :style="{ background: 'rgb(' + bgcolor.r + ',' + bgcolor.g + ',' + bgcolor.b + ')' }">
+        <view
+          class="t-background boxs"
+          @touchstart="touchstart($event, 0)"
+          @touchmove="touchmove($event, 0)"
+          @touchend="touchend($event, 0)"
+        >
+          <view class="t-color-mask"></view>
+          <view class="t-pointer" :style="{ top: site[0].top - 8 + 'px', left: site[0].left - 8 + 'px' }"></view>
+        </view>
+      </view>
+      <view class="t-control__box">
+        <view class="t-control__color">
+          <view
+            class="t-control__color-content"
+            :style="{ background: 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')' }"
+          ></view>
+        </view>
+        <view class="t-control-box__item">
+          <view
+            class="t-controller boxs"
+            @touchstart="touchstart($event, 1)"
+            @touchmove="touchmove($event, 1)"
+            @touchend="touchend($event, 1)"
+          >
+            <view class="t-hue">
+              <view class="t-circle" :style="{ left: site[1].left - 12 + 'px' }"></view>
+            </view>
+          </view>
+          <view
+            class="t-controller boxs"
+            @touchstart="touchstart($event, 2)"
+            @touchmove="touchmove($event, 2)"
+            @touchend="touchend($event, 2)"
+          >
+            <view class="t-transparency">
+              <view class="t-circle" :style="{ left: site[2].left - 12 + 'px' }"></view>
+            </view>
+          </view>
+        </view>
+      </view>
+      <view class="t-result__box">
+        <view v-if="mode" class="t-result__item">
+          <view class="t-result__box-input">{{ hex }}</view>
+          <view class="t-result__box-text">HEX</view>
+        </view>
+        <template v-else>
+          <view class="t-result__item">
+            <view class="t-result__box-input">{{ rgba.r }}</view>
+            <view class="t-result__box-text">R</view>
+          </view>
+          <view class="t-result__item">
+            <view class="t-result__box-input">{{ rgba.g }}</view>
+            <view class="t-result__box-text">G</view>
+          </view>
+          <view class="t-result__item">
+            <view class="t-result__box-input">{{ rgba.b }}</view>
+            <view class="t-result__box-text">B</view>
+          </view>
+          <view class="t-result__item">
+            <view class="t-result__box-input">{{ rgba.a }}</view>
+            <view class="t-result__box-text">A</view>
+          </view>
+        </template>
+
+        <view class="t-result__item t-select" @click="select">
+          <view class="t-result__box-input">
+            <view>切换</view>
+            <view>模式</view>
+          </view>
+        </view>
+      </view>
+      <view class="t-alternative">
+        <view class="t-alternative__item" v-for="(item, index) in colorList" :key="index">
+          <view
+            class="t-alternative__item-content"
+            :style="{ background: 'rgba(' + item.r + ',' + item.g + ',' + item.b + ',' + item.a + ')' }"
+            @click="selectColor(item)"
+          ></view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    color: {
+      type: Object,
+      default: () => {
+        return {
+          r: 0,
+          g: 0,
+          b: 0,
+          a: 0
+        }
+      }
+    },
+    spareColor: {
+      type: Array,
+      default() {
+        return []
+      }
+    }
+  },
+  data() {
+    return {
+      show: false,
+      active: false,
+      // rgba 颜色
+      rgba: {
+        r: 0,
+        g: 0,
+        b: 0,
+        a: 1
+      },
+      // hsb 颜色
+      hsb: {
+        h: 0,
+        s: 0,
+        b: 0
+      },
+      site: [
+        {
+          top: 0,
+          left: 0
+        },
+        {
+          left: 0
+        },
+        {
+          left: 0
+        }
+      ],
+      index: 0,
+      bgcolor: {
+        r: 255,
+        g: 0,
+        b: 0,
+        a: 1
+      },
+      hex: '#000000',
+      mode: true,
+      colorList: [
+        {
+          r: 244,
+          g: 67,
+          b: 54,
+          a: 1
+        },
+        {
+          r: 233,
+          g: 30,
+          b: 99,
+          a: 1
+        },
+        {
+          r: 156,
+          g: 39,
+          b: 176,
+          a: 1
+        },
+        {
+          r: 103,
+          g: 58,
+          b: 183,
+          a: 1
+        },
+        {
+          r: 63,
+          g: 81,
+          b: 181,
+          a: 1
+        },
+        {
+          r: 33,
+          g: 150,
+          b: 243,
+          a: 1
+        },
+        {
+          r: 3,
+          g: 169,
+          b: 244,
+          a: 1
+        },
+        {
+          r: 0,
+          g: 188,
+          b: 212,
+          a: 1
+        },
+        {
+          r: 0,
+          g: 150,
+          b: 136,
+          a: 1
+        },
+        {
+          r: 76,
+          g: 175,
+          b: 80,
+          a: 1
+        },
+        {
+          r: 139,
+          g: 195,
+          b: 74,
+          a: 1
+        },
+        {
+          r: 205,
+          g: 220,
+          b: 57,
+          a: 1
+        },
+        {
+          r: 255,
+          g: 235,
+          b: 59,
+          a: 1
+        },
+        {
+          r: 255,
+          g: 193,
+          b: 7,
+          a: 1
+        },
+        {
+          r: 255,
+          g: 152,
+          b: 0,
+          a: 1
+        },
+        {
+          r: 255,
+          g: 87,
+          b: 34,
+          a: 1
+        },
+        {
+          r: 121,
+          g: 85,
+          b: 72,
+          a: 1
+        },
+        {
+          r: 158,
+          g: 158,
+          b: 158,
+          a: 1
+        },
+        {
+          r: 0,
+          g: 0,
+          b: 0,
+          a: 0.5
+        },
+        {
+          r: 0,
+          g: 0,
+          b: 0,
+          a: 0
+        }
+      ]
+    }
+  },
+  created() {
+    this.ready()
+  },
+  methods: {
+    ready() {
+      this.rgba = this.color
+      if (this.spareColor.length !== 0) {
+        this.colorList = this.spareColor
+      }
+    },
+    /**
+     * 初始化
+     */
+    init() {
+      // hsb 颜色
+      this.hsb = this.rgbToHex(this.rgba)
+      // this.setColor();
+      this.setValue(this.rgba)
+    },
+    moveHandle() {},
+    open() {
+      this.show = true
+      this.$nextTick(() => {
+        this.init()
+        setTimeout(() => {
+          this.active = true
+          setTimeout(() => {
+            this.getSelectorQuery()
+          }, 350)
+        }, 50)
+      })
+    },
+    close() {
+      this.active = false
+      this.$nextTick(() => {
+        setTimeout(() => {
+          this.show = false
+        }, 500)
+      })
+    },
+    confirm() {
+      this.close()
+      this.$emit('confirm', {
+        rgba: this.rgba,
+        hex: this.hex
+      })
+    },
+    // 选择模式
+    select() {
+      this.mode = !this.mode
+    },
+    // 常用颜色选择
+    selectColor(item) {
+      this.setColorBySelect(item)
+    },
+    touchstart(e, index) {
+      const { pageX, pageY, clientX, clientY } = e.touches[0]
+      // 部分机型可能没有pageX或clientX,因此此处需要做兼容
+      this.moveX = pageX || clientX
+      this.moveY = pageY || clientY
+      this.setPosition(this.moveX, this.moveY, index)
+    },
+    touchmove(e, index) {
+      const { pageX, pageY, clientX, clientY } = e.touches[0]
+      this.moveX = pageX || clientX
+      this.moveY = pageY || clientY
+      this.setPosition(this.moveX, this.moveY, index)
+    },
+    touchend(e, index) {},
+    /**
+     * 设置位置
+     */
+    setPosition(x, y, index) {
+      this.index = index
+      const { top, left, width, height } = this.position[index]
+      // 设置最大最小值
+
+      this.site[index].left = Math.max(0, Math.min(parseInt(x - left), width))
+      if (index === 0) {
+        this.site[index].top = Math.max(0, Math.min(parseInt(y - top), height))
+        // 设置颜色
+        this.hsb.s = parseInt((100 * this.site[index].left) / width)
+        this.hsb.b = parseInt(100 - (100 * this.site[index].top) / height)
+        this.setColor()
+        this.setValue(this.rgba)
+      } else {
+        this.setControl(index, this.site[index].left)
+      }
+    },
+    /**
+     * 设置 rgb 颜色
+     */
+    setColor() {
+      const rgb = this.HSBToRGB(this.hsb)
+      this.rgba.r = rgb.r
+      this.rgba.g = rgb.g
+      this.rgba.b = rgb.b
+    },
+    /**
+     * 设置二进制颜色
+     * @param {Object} rgb
+     */
+    setValue(rgb) {
+      this.hex = '#' + this.rgbToHex(rgb)
+    },
+    setControl(index, x) {
+      const { top, left, width, height } = this.position[index]
+
+      if (index === 1) {
+        this.hsb.h = parseInt((360 * x) / width)
+        this.bgcolor = this.HSBToRGB({
+          h: this.hsb.h,
+          s: 100,
+          b: 100
+        })
+        this.setColor()
+      } else {
+        this.rgba.a = (x / width).toFixed(1)
+      }
+      this.setValue(this.rgba)
+    },
+    /**
+     * rgb 转 二进制 hex
+     * @param {Object} rgb
+     */
+    rgbToHex(rgb) {
+      let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)]
+      hex.map(function (str, i) {
+        if (str.length == 1) {
+          hex[i] = '0' + str
+        }
+      })
+      return hex.join('')
+    },
+    setColorBySelect(getrgb) {
+      const { r, g, b, a } = getrgb
+      let rgb = {}
+      rgb = {
+        r: r ? parseInt(r) : 0,
+        g: g ? parseInt(g) : 0,
+        b: b ? parseInt(b) : 0,
+        a: a ? a : 0
+      }
+      this.rgba = rgb
+      this.hsb = this.rgbToHsb(rgb)
+      this.changeViewByHsb()
+    },
+    changeViewByHsb() {
+      const [a, b, c] = this.position
+      this.site[0].left = parseInt((this.hsb.s * a.width) / 100)
+      this.site[0].top = parseInt(((100 - this.hsb.b) * a.height) / 100)
+      this.setColor(this.hsb.h)
+      this.setValue(this.rgba)
+      this.bgcolor = this.HSBToRGB({
+        h: this.hsb.h,
+        s: 100,
+        b: 100
+      })
+
+      this.site[1].left = (this.hsb.h / 360) * b.width
+      this.site[2].left = this.rgba.a * c.width
+    },
+    /**
+     * hsb 转 rgb
+     * @param {Object} 颜色模式  H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度
+     */
+    HSBToRGB(hsb) {
+      let rgb = {}
+      let h = Math.round(hsb.h)
+      let s = Math.round((hsb.s * 255) / 100)
+      let v = Math.round((hsb.b * 255) / 100)
+      if (s == 0) {
+        rgb.r = rgb.g = rgb.b = v
+      } else {
+        let t1 = v
+        let t2 = ((255 - s) * v) / 255
+        let t3 = ((t1 - t2) * (h % 60)) / 60
+        if (h == 360) h = 0
+        if (h < 60) {
+          rgb.r = t1
+          rgb.b = t2
+          rgb.g = t2 + t3
+        } else if (h < 120) {
+          rgb.g = t1
+          rgb.b = t2
+          rgb.r = t1 - t3
+        } else if (h < 180) {
+          rgb.g = t1
+          rgb.r = t2
+          rgb.b = t2 + t3
+        } else if (h < 240) {
+          rgb.b = t1
+          rgb.r = t2
+          rgb.g = t1 - t3
+        } else if (h < 300) {
+          rgb.b = t1
+          rgb.g = t2
+          rgb.r = t2 + t3
+        } else if (h < 360) {
+          rgb.r = t1
+          rgb.g = t2
+          rgb.b = t1 - t3
+        } else {
+          rgb.r = 0
+          rgb.g = 0
+          rgb.b = 0
+        }
+      }
+      return {
+        r: Math.round(rgb.r),
+        g: Math.round(rgb.g),
+        b: Math.round(rgb.b)
+      }
+    },
+    rgbToHsb(rgb) {
+      let hsb = {
+        h: 0,
+        s: 0,
+        b: 0
+      }
+      let min = Math.min(rgb.r, rgb.g, rgb.b)
+      let max = Math.max(rgb.r, rgb.g, rgb.b)
+      let delta = max - min
+      hsb.b = max
+      hsb.s = max != 0 ? (255 * delta) / max : 0
+      if (hsb.s != 0) {
+        if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta
+        else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta
+        else hsb.h = 4 + (rgb.r - rgb.g) / delta
+      } else hsb.h = -1
+      hsb.h *= 60
+      if (hsb.h < 0) hsb.h = 0
+      hsb.s *= 100 / 255
+      hsb.b *= 100 / 255
+      return hsb
+    },
+    getSelectorQuery() {
+      const views = uni.createSelectorQuery().in(this)
+      views
+        .selectAll('.boxs')
+        .boundingClientRect((data) => {
+          if (!data || data.length === 0) {
+            setTimeout(() => this.getSelectorQuery(), 20)
+            return
+          }
+          this.position = data
+          // this.site[0].top = data[0].height;
+          // this.site[0].left = 0;
+          // this.site[1].left = data[1].width;
+          // this.site[2].left = data[2].width;
+          this.setColorBySelect(this.rgba)
+        })
+        .exec()
+    },
+    hex2Rgb(hexColor, alpha = 1) {
+      const color = hexColor.slice(1)
+      const r = parseInt(color.slice(0, 2), 16)
+      const g = parseInt(color.slice(2, 4), 16)
+      const b = parseInt(color.slice(4, 6), 16)
+      return {
+        r: r,
+        g: g,
+        b: b,
+        a: alpha
+      }
+    }
+  },
+  watch: {
+    spareColor(newVal) {
+      this.colorList = newVal
+    },
+    color(newVal) {
+      this.ready()
+    }
+  }
+}
+</script>
+
+<style>
+.t-wrapper {
+  position: fixed;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  box-sizing: border-box;
+  z-index: 9999;
+}
+
+.t-box {
+  width: 100%;
+  position: absolute;
+  bottom: 0;
+  padding: 30upx 0;
+  padding-top: 0;
+  background: #fff;
+  transition: all 0.3s;
+  transform: translateY(100%);
+}
+
+.t-box.active {
+  transform: translateY(0%);
+}
+
+.t-header {
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+  height: 100upx;
+  border-bottom: 1px #eee solid;
+  box-shadow: 1px 0 2px rgba(0, 0, 0, 0.1);
+  background: #fff;
+}
+
+.t-header-button {
+  display: flex;
+  align-items: center;
+  width: 150upx;
+  height: 100upx;
+  font-size: 30upx;
+  color: #666;
+  padding-left: 20upx;
+}
+
+.t-header-button:last-child {
+  justify-content: flex-end;
+  padding-right: 20upx;
+}
+
+.t-mask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.6);
+  z-index: -1;
+  transition: all 0.3s;
+  opacity: 0;
+}
+
+.t-mask.active {
+  opacity: 1;
+}
+
+.t-color__box {
+  position: relative;
+  height: 400upx;
+  background: rgb(255, 0, 0);
+  overflow: hidden;
+  box-sizing: border-box;
+  margin: 0 20upx;
+  margin-top: 20upx;
+  box-sizing: border-box;
+}
+
+.t-background {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
+}
+
+.t-color-mask {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  width: 100%;
+  height: 400upx;
+  background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
+}
+
+.t-pointer {
+  position: absolute;
+  bottom: -8px;
+  left: -8px;
+  z-index: 2;
+  width: 15px;
+  height: 15px;
+  border: 1px #fff solid;
+  border-radius: 50%;
+}
+
+.t-show-color {
+  width: 100upx;
+  height: 50upx;
+}
+
+.t-control__box {
+  margin-top: 50upx;
+  width: 100%;
+  display: flex;
+  padding-left: 20upx;
+  box-sizing: border-box;
+}
+
+.t-control__color {
+  flex-shrink: 0;
+  width: 100upx;
+  height: 100upx;
+  border-radius: 50%;
+  background-color: #fff;
+  background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
+    linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
+  background-size: 36upx 36upx;
+  background-position: 0 0, 18upx 18upx;
+  border: 1px #eee solid;
+  overflow: hidden;
+}
+
+.t-control__color-content {
+  width: 100%;
+  height: 100%;
+}
+
+.t-control-box__item {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  width: 100%;
+  padding: 0 30upx;
+}
+
+.t-controller {
+  position: relative;
+  width: 100%;
+  height: 16px;
+  background-color: #fff;
+  background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
+    linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
+  background-size: 32upx 32upx;
+  background-position: 0 0, 16upx 16upx;
+}
+
+.t-hue {
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
+}
+
+.t-transparency {
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0));
+}
+
+.t-circle {
+  position: absolute;
+  /* right: -10px; */
+  top: -2px;
+  width: 20px;
+  height: 20px;
+  box-sizing: border-box;
+  border-radius: 50%;
+  background: #fff;
+  box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1);
+}
+
+.t-result__box {
+  margin-top: 20upx;
+  padding: 10upx;
+  width: 100%;
+  display: flex;
+  box-sizing: border-box;
+}
+
+.t-result__item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 10upx;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.t-result__box-input {
+  padding: 10upx 0;
+  width: 100%;
+  font-size: 28upx;
+  box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
+  color: #999;
+  text-align: center;
+  background: #fff;
+}
+
+.t-result__box-text {
+  margin-top: 10upx;
+  font-size: 28upx;
+  line-height: 2;
+}
+
+.t-select {
+  flex-shrink: 0;
+  width: 150upx;
+  padding: 0 30upx;
+}
+
+.t-select .t-result__box-input {
+  border-radius: 10upx;
+  border: none;
+  color: #999;
+  box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);
+  background: #fff;
+}
+
+.t-select .t-result__box-input:active {
+  box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.1);
+}
+
+.t-alternative {
+  display: flex;
+  flex-wrap: wrap;
+  /* justify-content: space-between; */
+  width: 100%;
+  padding-right: 10upx;
+  box-sizing: border-box;
+}
+
+.t-alternative__item {
+  margin-left: 12upx;
+  margin-top: 10upx;
+  width: 50upx;
+  height: 50upx;
+  border-radius: 10upx;
+  background-color: #fff;
+  background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
+    linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
+  background-size: 36upx 36upx;
+  background-position: 0 0, 18upx 18upx;
+  border: 1px #eee solid;
+  overflow: hidden;
+}
+
+.t-alternative__item-content {
+  width: 50upx;
+  height: 50upx;
+  background: rgba(255, 0, 0, 0.5);
+}
+
+.t-alternative__item:active {
+  transition: all 0.3s;
+  transform: scale(1.1);
+}
+</style>

+ 152 - 0
components/FuWenBen/components/sp-editor/link-edit.vue

@@ -0,0 +1,152 @@
+<template>
+  <view class="link-edit-container" v-if="showPopup">
+    <view class="link-edit">
+      <view class="title">添加链接</view>
+      <view class="edit">
+        <view class="description">
+          链接描述:
+          <input v-model="descVal" type="text" class="input" placeholder="请输入链接描述" />
+        </view>
+        <view class="address">
+          链接地址:
+          <input v-model="addrVal" type="text" class="input" placeholder="请输入链接地址" />
+        </view>
+      </view>
+      <view class="control">
+        <view class="cancel" @click="close">取消</view>
+        <view class="confirm" @click="onConfirm">确认</view>
+      </view>
+    </view>
+    <view class="mask"></view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      showPopup: false,
+      descVal: '',
+      addrVal: ''
+    }
+  },
+  methods: {
+    open() {
+      this.showPopup = true
+      this.$emit('open')
+    },
+    close() {
+      this.showPopup = false
+      this.descVal = ''
+      this.addrVal = ''
+      this.$emit('close')
+    },
+    onConfirm() {
+      if (!this.descVal) {
+        uni.showToast({
+          title: '请输入链接描述',
+          icon: 'none'
+        })
+        return
+      }
+      if (!this.addrVal) {
+        uni.showToast({
+          title: '请输入链接地址',
+          icon: 'none'
+        })
+        return
+      }
+      this.$emit('confirm', {
+        text: this.descVal,
+        href: this.addrVal
+      })
+      this.close()
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.link-edit-container {
+  .link-edit {
+    width: 80%;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    background-color: #ffffff;
+    box-shadow: -2px -2px 4px rgba(0, 0, 0, 0.05), 2px 2px 4px rgba(0, 0, 0, 0.05);
+    border-radius: 12rpx;
+    box-sizing: border-box;
+    z-index: 999;
+    font-size: 14px;
+
+    .title {
+      height: 80rpx;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
+
+    .edit {
+      padding: 24rpx;
+      border-top: 1px solid #eeeeee;
+      border-bottom: 1px solid #eeeeee;
+      box-sizing: border-box;
+
+      .input {
+        flex: 1;
+        padding: 4px;
+        font-size: 14px;
+        border: 1px solid #eeeeee;
+        border-radius: 8rpx;
+
+        .uni-input-placeholder {
+          color: #dddddd;
+        }
+      }
+
+      .description {
+        display: flex;
+        align-items: center;
+      }
+      .address {
+        display: flex;
+        align-items: center;
+        margin-top: 24rpx;
+      }
+    }
+
+    .control {
+      height: 80rpx;
+      display: flex;
+      cursor: pointer;
+
+      .cancel {
+        flex: 1;
+        color: #dd524d;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+      .confirm {
+        border-left: 1px solid #eeeeee;
+        flex: 1;
+        color: #007aff;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+  }
+  .mask {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: rgba(0, 0, 0, 0.05);
+    z-index: 998;
+  }
+}
+</style>

+ 654 - 0
components/FuWenBen/components/sp-editor/sp-editor.vue

@@ -0,0 +1,654 @@
+<template>
+  <view class="sp-editor" :style="{ '--icon-size': iconSize, '--icon-columns': iconColumns }">
+    <view class="sp-editor-toolbar" v-if="!readOnly" @tap="format">
+      <view
+        v-if="toolbarList.includes('header')"
+        :class="formats.header === 1 ? 'ql-active' : ''"
+        class="iconfont icon-format-header-1"
+        title="标题"
+        data-name="header"
+        :data-value="1"
+      ></view>
+      <view
+        v-if="toolbarList.includes('bold')"
+        :class="formats.bold ? 'ql-active' : ''"
+        class="iconfont icon-zitijiacu"
+        title="加粗"
+        data-name="bold"
+      ></view>
+      <view
+        v-if="toolbarList.includes('italic')"
+        :class="formats.italic ? 'ql-active' : ''"
+        class="iconfont icon-zitixieti"
+        title="斜体"
+        data-name="italic"
+      ></view>
+      <view
+        v-if="toolbarList.includes('underline')"
+        :class="formats.underline ? 'ql-active' : ''"
+        class="iconfont icon-zitixiahuaxian"
+        title="下划线"
+        data-name="underline"
+      ></view>
+      <view
+        v-if="toolbarList.includes('strike')"
+        :class="formats.strike ? 'ql-active' : ''"
+        class="iconfont icon-zitishanchuxian"
+        title="删除线"
+        data-name="strike"
+      ></view>
+      <!-- #ifndef MP-BAIDU -->
+      <view
+        v-if="toolbarList.includes('alignLeft')"
+        :class="formats.align === 'left' ? 'ql-active' : ''"
+        class="iconfont icon-zuoduiqi"
+        title="左对齐"
+        data-name="align"
+        data-value="left"
+      ></view>
+      <!-- #endif -->
+      <view
+        v-if="toolbarList.includes('alignCenter')"
+        :class="formats.align === 'center' ? 'ql-active' : ''"
+        class="iconfont icon-juzhongduiqi"
+        title="居中对齐"
+        data-name="align"
+        data-value="center"
+      ></view>
+      <view
+        v-if="toolbarList.includes('alignRight')"
+        :class="formats.align === 'right' ? 'ql-active' : ''"
+        class="iconfont icon-youduiqi"
+        title="右对齐"
+        data-name="align"
+        data-value="right"
+      ></view>
+      <view
+        v-if="toolbarList.includes('alignJustify')"
+        :class="formats.align === 'justify' ? 'ql-active' : ''"
+        class="iconfont icon-zuoyouduiqi"
+        title="两端对齐"
+        data-name="align"
+        data-value="justify"
+      ></view>
+      <!-- #ifndef MP-BAIDU -->
+      <view
+        v-if="toolbarList.includes('lineHeight')"
+        :class="formats.lineHeight ? 'ql-active' : ''"
+        class="iconfont icon-line-height"
+        title="行间距"
+        data-name="lineHeight"
+        data-value="2"
+      ></view>
+      <view
+        v-if="toolbarList.includes('letterSpacing')"
+        :class="formats.letterSpacing ? 'ql-active' : ''"
+        class="iconfont icon-Character-Spacing"
+        title="字间距"
+        data-name="letterSpacing"
+        data-value="2em"
+      ></view>
+      <view
+        v-if="toolbarList.includes('marginTop')"
+        :class="formats.marginTop ? 'ql-active' : ''"
+        class="iconfont icon-722bianjiqi_duanqianju"
+        title="段前距"
+        data-name="marginTop"
+        data-value="20px"
+      ></view>
+      <view
+        v-if="toolbarList.includes('marginBottom')"
+        :class="formats.marginBottom ? 'ql-active' : ''"
+        class="iconfont icon-723bianjiqi_duanhouju"
+        title="段后距"
+        data-name="marginBottom"
+        data-value="20px"
+      ></view>
+      <!-- #endif -->
+      <!-- #ifndef MP-BAIDU -->
+      <view
+        v-if="toolbarList.includes('fontSize')"
+        :class="formats.fontFamily ? 'ql-active' : ''"
+        class="iconfont icon-font"
+        title="字体"
+        data-name="fontFamily"
+        data-value="宋体"
+      ></view>
+      <view
+        v-if="toolbarList.includes('fontSize')"
+        :class="formats.fontSize === '24px' ? 'ql-active' : ''"
+        class="iconfont icon-fontsize"
+        title="字号"
+        data-name="fontSize"
+        data-value="24px"
+      ></view>
+      <!-- #endif -->
+      <view
+        v-if="toolbarList.includes('color')"
+        :style="{ color: formats.color ? textColor : 'initial' }"
+        class="iconfont icon-text_color"
+        title="文字颜色"
+        data-name="color"
+        :data-value="textColor"
+      ></view>
+      <view
+        v-if="toolbarList.includes('backgroundColor')"
+        :style="{ color: formats.backgroundColor ? backgroundColor : 'initial' }"
+        class="iconfont icon-fontbgcolor"
+        title="背景颜色"
+        data-name="backgroundColor"
+        :data-value="backgroundColor"
+      ></view>
+      <view
+        v-if="toolbarList.includes('date')"
+        class="iconfont icon-date"
+        title="日期"
+        @tap="insertDate"
+      ></view>
+      <view
+        v-if="toolbarList.includes('listCheck')"
+        class="iconfont icon--checklist"
+        title="待办"
+        data-name="list"
+        data-value="check"
+      ></view>
+      <view
+        v-if="toolbarList.includes('listOrdered')"
+        :class="formats.list === 'ordered' ? 'ql-active' : ''"
+        class="iconfont icon-youxupailie"
+        title="有序列表"
+        data-name="list"
+        data-value="ordered"
+      ></view>
+      <view
+        v-if="toolbarList.includes('listBullet')"
+        :class="formats.list === 'bullet' ? 'ql-active' : ''"
+        class="iconfont icon-wuxupailie"
+        title="无序列表"
+        data-name="list"
+        data-value="bullet"
+      ></view>
+      <view
+        v-if="toolbarList.includes('divider')"
+        class="iconfont icon-fengexian"
+        title="分割线"
+        @click="insertDivider"
+      ></view>
+      <view
+        v-if="toolbarList.includes('indentDec')"
+        class="iconfont icon-outdent"
+        title="减少缩进"
+        data-name="indent"
+        data-value="-1"
+      ></view>
+      <view
+        v-if="toolbarList.includes('indentInc')"
+        class="iconfont icon-indent"
+        title="增加缩进"
+        data-name="indent"
+        data-value="+1"
+      ></view>
+      <view
+        v-if="toolbarList.includes('scriptSub')"
+        :class="formats.script === 'sub' ? 'ql-active' : ''"
+        class="iconfont icon-zitixiabiao"
+        title="下标"
+        data-name="script"
+        data-value="sub"
+      ></view>
+      <view
+        v-if="toolbarList.includes('scriptSuper')"
+        :class="formats.script === 'super' ? 'ql-active' : ''"
+        class="iconfont icon-zitishangbiao"
+        title="上标"
+        data-name="script"
+        data-value="super"
+      ></view>
+      <view
+        v-if="toolbarList.includes('direction')"
+        :class="formats.direction === 'rtl' ? 'ql-active' : ''"
+        class="iconfont icon-direction-rtl"
+        title="文本方向"
+        data-name="direction"
+        data-value="rtl"
+      ></view>
+      <view
+        v-if="toolbarList.includes('image')"
+        class="iconfont icon-charutupian"
+        title="图片"
+        @tap="insertImage"
+      ></view>
+      <view
+        v-if="toolbarList.includes('link')"
+        class="iconfont icon-charulianjie"
+        title="超链接"
+        @tap="insertLink"
+      ></view>
+      <view
+        v-if="toolbarList.includes('undo')"
+        class="iconfont icon-undo"
+        title="撤销"
+        @tap="undo"
+      ></view>
+      <view
+        v-if="toolbarList.includes('redo')"
+        class="iconfont icon-redo"
+        title="重做"
+        @tap="redo"
+      ></view>
+      <view
+        v-if="toolbarList.includes('removeFormat')"
+        class="iconfont icon-clearedformat"
+        title="清除格式"
+        @tap="removeFormat"
+      ></view>
+      <view
+        v-if="toolbarList.includes('clear')"
+        class="iconfont icon-shanchu"
+        title="清空"
+        @tap="clear"
+      ></view>
+      <view
+        v-if="toolbarList.includes('export')"
+        class="iconfont icon-baocun"
+        title="导出"
+        @tap="exportHtml"
+      ></view>
+    </view>
+
+    <!-- 自定义功能组件 -->
+    <!-- 调色板 -->
+    <color-picker
+      v-if="toolbarList.includes('color') || toolbarList.includes('backgroundColor')"
+      ref="colorPickerRef"
+      :color="defaultColor"
+      @confirm="confirmColor"
+    ></color-picker>
+    <!-- 添加链接的操作弹窗 -->
+    <link-edit
+      v-if="toolbarList.includes('link') && !readOnly"
+      ref="linkEditRef"
+      @confirm="confirmLink"
+    ></link-edit>
+
+    <view class="sp-editor-wrapper">
+      <editor
+        id="editor"
+        class="ql-editor editor-container"
+        :class="{ 'ql-image-overlay-none': readOnly }"
+        show-img-size
+        show-img-toolbar
+        show-img-resize
+        :placeholder="placeholder"
+        :read-only="readOnly"
+        @statuschange="onStatusChange"
+        @ready="onEditorReady"
+        @input="onEditorInput"
+      ></editor>
+      <!-- 只读蒙版 - 防止开启只读后仍能操作工具栏和图片删除 -->
+      <!-- <view class="read-only-mask" v-if="readOnly"></view> -->
+    </view>
+  </view>
+</template>
+
+<script>
+import ColorPicker from './color-picker.vue'
+import LinkEdit from './link-edit.vue'
+import { addLink, linkFlag } from '../../utils'
+
+export default {
+  components: {
+    ColorPicker,
+    LinkEdit
+  },
+  props: {
+    placeholder: {
+      type: String,
+      default: '写点什么吧 ~'
+    },
+    // 是否只读
+    readOnly: {
+      type: Boolean,
+      default: false
+    },
+    // 最大字数限制,-1不限
+    maxlength: {
+      type: Number,
+      default: -1
+    },
+    // 工具栏配置
+    toolbarConfig: {
+      type: Object,
+      default: () => {
+        return {
+          keys: [], // 要显示的工具,优先级最大
+          excludeKeys: [], // 除这些指定的工具外,其他都显示
+          iconSize: '18px', // 工具栏字体大小
+          iconColumns: 10 // 工具栏列数
+        }
+      }
+    }
+  },
+  watch: {
+    toolbarConfig: {
+      deep: true,
+      immediate: true,
+      handler(newToolbar) {
+        /**
+         * 若工具栏配置中keys存在,则以keys为准
+         * 否则以excludeKeys向toolbarAllList中排查
+         * 若keys与excludeKeys皆为空,则以toolbarAllList为准
+         */
+        if (newToolbar.keys?.length > 0) {
+          this.toolbarList = newToolbar.keys
+        } else {
+          this.toolbarList =
+            newToolbar.excludeKeys?.length > 0
+              ? this.toolbarAllList.filter((item) => !newToolbar.excludeKeys.includes(item))
+              : this.toolbarAllList
+        }
+        this.iconSize = newToolbar.iconSize || '18px'
+        this.iconColumns = newToolbar.iconColumns || 10
+      }
+    }
+  },
+  data() {
+    return {
+      formats: {},
+      textColor: '',
+      backgroundColor: '',
+      curColor: '',
+      defaultColor: { r: 0, g: 0, b: 0, a: 1 }, // 调色板默认颜色
+      iconSize: '20px', // 工具栏图标字体大小
+      iconColumns: 10, // 工具栏列数
+      toolbarList: [],
+      toolbarAllList: [
+        'bold', // 加粗
+        'italic', // 斜体
+        'underline', // 下划线
+        'strike', // 删除线
+        'alignLeft', // 左对齐
+        'alignCenter', // 居中对齐
+        'alignRight', // 右对齐
+        'alignJustify', // 两端对齐
+        'lineHeight', // 行间距
+        'letterSpacing', // 字间距
+        'marginTop', // 段前距
+        'marginBottom', // 段后距
+        'fontFamily', // 字体
+        'fontSize', // 字号
+        'color', // 文字颜色
+        'backgroundColor', // 背景颜色
+        'date', // 日期
+        'listCheck', // 待办
+        'listOrdered', // 有序列表
+        'listBullet', // 无序列表
+        'indentInc', // 增加缩进
+        'indentDec', // 减少缩进
+        'divider', // 分割线
+        'header', // 标题
+        'scriptSub', // 下标
+        'scriptSuper', // 上标
+        'direction', // 文本方向
+        'image', // 图片
+        'link', // 超链接
+        'undo', // 撤销
+        'redo', // 重做
+        'removeFormat', // 清除格式
+        'clear', // 清空
+        'export' // 导出
+      ]
+    }
+  },
+  methods: {
+    onEditorReady() {
+      // #ifdef MP-BAIDU
+      this.editorCtx = requireDynamicLib('editorLib').createEditorContext('editor')
+      // #endif
+
+      // #ifdef APP || MP-WEIXIN || H5
+      uni
+        .createSelectorQuery()
+        .in(this)
+        .select('#editor')
+        .context((res) => {
+          this.editorCtx = res.context
+          this.$emit('init', this.editorCtx)
+        })
+        .exec()
+      // #endif
+    },
+    undo() {
+      this.editorCtx.undo()
+    },
+    redo() {
+      this.editorCtx.redo()
+    },
+    format(e) {
+      let { name, value } = e.target.dataset
+      if (!name) return
+      switch (name) {
+        case 'color':
+        case 'backgroundColor':
+          this.curColor = name
+          this.showPicker()
+          break
+        default:
+          this.editorCtx.format(name, value)
+          break
+      }
+    },
+    showPicker() {
+      switch (this.curColor) {
+        case 'color':
+          this.defaultColor = this.textColor
+            ? this.$refs.colorPickerRef.hex2Rgb(this.textColor)
+            : { r: 0, g: 0, b: 0, a: 1 }
+          break
+        case 'backgroundColor':
+          this.defaultColor = this.backgroundColor
+            ? this.$refs.colorPickerRef.hex2Rgb(this.backgroundColor)
+            : { r: 0, g: 0, b: 0, a: 0 }
+          break
+      }
+      this.$refs.colorPickerRef.open()
+    },
+    confirmColor(e) {
+      switch (this.curColor) {
+        case 'color':
+          this.textColor = e.hex
+          this.editorCtx.format('color', this.textColor)
+          break
+        case 'backgroundColor':
+          this.backgroundColor = e.hex
+          this.editorCtx.format('backgroundColor', this.backgroundColor)
+          break
+      }
+    },
+    onStatusChange(e) {
+      if (e.detail.color) {
+        this.textColor = e.detail.color
+      }
+      if (e.detail.backgroundColor) {
+        this.backgroundColor = e.detail.backgroundColor
+      }
+      this.formats = e.detail
+    },
+    insertDivider() {
+      this.editorCtx.insertDivider()
+    },
+    clear() {
+      uni.showModal({
+        title: '清空编辑器',
+        content: '确定清空编辑器吗?',
+        success: ({ confirm }) => {
+          if (confirm) {
+            this.editorCtx.clear()
+          }
+        }
+      })
+    },
+    removeFormat() {
+      uni.showModal({
+        title: '文本格式化',
+        content: '确定要清除所选择部分文本块格式吗?',
+        showCancel: true,
+        success: ({ confirm }) => {
+          if (confirm) {
+            this.editorCtx.removeFormat()
+          }
+        }
+      })
+    },
+    insertDate() {
+      const date = new Date()
+      const formatDate = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`
+      this.editorCtx.insertText({ text: formatDate })
+    },
+    insertLink() {
+      this.$refs.linkEditRef.open()
+    },
+    /**
+     * 确认添加链接
+     * @param {Object} e { text: '链接描述', href: '链接地址' }
+     */
+    confirmLink(e) {
+      this.$refs.linkEditRef.close()
+      this.$emit('addLink', e)
+      addLink(this.editorCtx, e)
+    },
+    insertImage() {
+      // #ifdef APP-PLUS || H5
+      uni.chooseImage({
+        // count: 1, // 默认9
+        success: (res) => {
+          const { tempFiles } = res
+          // 将文件和编辑器示例抛出,由开发者自行上传和插入图片
+          this.$emit('upinImage', tempFiles, this.editorCtx)
+        },
+        fail() {
+          uni.showToast({
+            title: '未授权访问相册权限,请授权后使用',
+            icon: 'none'
+          })
+        }
+      })
+      // #endif
+
+      // #ifdef MP-WEIXIN
+      // 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。
+      uni.chooseMedia({
+        // count: 1, // 默认9
+        success: (res) => {
+          // 同上chooseImage处理
+          const { tempFiles } = res
+          this.$emit('upinImage', tempFiles, this.editorCtx)
+        },
+        fail() {
+          uni.showToast({
+            title: '未授权访问相册权限,请授权后使用',
+            icon: 'none'
+          })
+        }
+      })
+      // #endif
+    },
+    onEditorInput(e) {
+      // 注意不要使用getContents获取html和text,会导致重复触发onStatusChange从而失去toolbar工具的高亮状态
+      // 复制粘贴的时候detail会为空,此时应当直接return
+      if (Object.keys(e.detail).length <= 0) return
+      const { html, text } = e.detail
+      // 识别到标识立即return
+      if (text.indexOf(linkFlag) !== -1) return
+
+      const maxlength = parseInt(this.maxlength)
+      const textStr = text.replace(/[ \t\r\n]/g, '')
+      if (textStr.length > maxlength && maxlength != -1) {
+        uni.showModal({
+          content: `超过${maxlength}字数啦~`,
+          confirmText: '确定',
+          showCancel: false,
+          success: () => {
+            this.$emit('overMax', { html, text })
+          }
+        })
+      } else {
+        this.$emit('input', { html, text })
+      }
+    },
+    // 导出
+    exportHtml() {
+      this.editorCtx.getContents({
+        success: (res) => {
+          this.$emit('exportHtml', res.html)
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+@import '@/uni_modules/sp-editor/icons/editor-icon.css';
+
+.sp-editor {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  position: relative;
+}
+
+.sp-editor-toolbar {
+  box-sizing: border-box;
+  padding: calc(var(--icon-size) / 4) 0;
+  border-bottom: 1px solid #e4e4e4;
+  font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
+  display: grid;
+  grid-template-columns: repeat(var(--icon-columns), 1fr);
+}
+
+.iconfont {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: calc(var(--icon-size) * 1.8);
+  cursor: pointer;
+  font-size: var(--icon-size);
+}
+
+.sp-editor-wrapper {
+  flex: 1;
+  overflow: hidden;
+  position: relative;
+}
+
+.editor-container {
+  padding: 8rpx 16rpx;
+  box-sizing: border-box;
+  width: 100%;
+  height: 100%;
+  font-size: 16px;
+  line-height: 1.5;
+}
+
+.ql-image-overlay-none {
+  ::v-deep .ql-image-overlay {
+    pointer-events: none;
+    opacity: 0;
+  }
+}
+
+::v-deep .ql-editor.ql-blank::before {
+  font-style: normal;
+  color: #cccccc;
+}
+
+::v-deep .ql-container {
+  min-height: unset;
+}
+
+.ql-active {
+  color: #66ccff;
+}
+</style>

Dosya farkı çok büyük olduğundan ihmal edildi
+ 238 - 0
components/FuWenBen/icons/editor-icon.css


BIN
components/FuWenBen/icons/iconfont.ttf


+ 82 - 0
components/FuWenBen/package.json

@@ -0,0 +1,82 @@
+{
+	"id": "sp-editor",
+	"displayName": "官方富文本编辑器editor组件改良扩展优化版",
+	"version": "1.3.2",
+	"description": "基于官方的富文本编辑器editor组件,进行改良扩展优化版,添加了调色板,添加超链接等功能,可自定义扩展工具,快来试试吧~",
+	"keywords": [
+        "富文本",
+        "editor",
+        "编辑器"
+    ],
+	"repository": "",
+    "engines": {
+	},
+	"dcloudext": {
+		"type": "component-vue",
+		"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": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "u",
+					"百度": "y",
+					"字节跳动": "u",
+					"QQ": "u",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+	}
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 269 - 0
components/FuWenBen/readme.md


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
components/FuWenBen/static/image-resize.min.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 8 - 0
components/FuWenBen/static/quill.min.js


+ 68 - 0
components/FuWenBen/utils/index.js

@@ -0,0 +1,68 @@
+// 标识必须独一无二 - 标识是为了使用insertText插入标识文本后,查找到标识所在delta位置的索引
+export const linkFlag = '#-*=*-*=*-*=*@-link超链接标识link-@*=*-*=*-*=*-#'
+
+export function addLink(editorCtx, attr) {
+  // 先插入一段文本内容
+  editorCtx.insertText({
+    text: linkFlag
+  })
+  // 获取全文delta内容
+  editorCtx.getContents({
+    success(res) {
+      let options = res.delta.ops
+      const findex = options.findIndex(item => {
+        return item.insert && typeof item.insert !== 'object' && item.insert?.indexOf(linkFlag) !== -1
+      })
+      // 根据标识查找到插入的位置
+      if (findex > -1) {
+        const findOption = options[findex]
+        const findAttributes = findOption.attributes
+        // 将该findOption分成三部分:前内容 要插入的link 后内容
+        const [prefix, suffix] = findOption.insert.split(linkFlag);
+        const handleOps = []
+        // 前内容
+        if (prefix) {
+          const prefixOps = findAttributes ? {
+            insert: prefix,
+            attributes: findAttributes
+          } : {
+            insert: prefix
+          }
+          handleOps.push(prefixOps)
+        }
+        // 插入的link
+        const linkOps = {
+          insert: attr.text,
+          attributes: {
+            link: attr.href,
+            textDecoration: attr.textDecoration || 'none', // 下划线
+            color: attr.color || '#007aff'
+          }
+        }
+        handleOps.push(linkOps)
+        // 后内容
+        if (suffix) {
+          const suffixOps = findAttributes ? {
+            insert: suffix,
+            attributes: findAttributes
+          } : {
+            insert: suffix
+          }
+          handleOps.push(suffixOps)
+        }
+        // 删除原options[findex]并在findex位置插入上述三个ops
+        options.splice(findex, 1);
+        options.splice(findex, 0, ...handleOps);
+        // 最后重新初始化内容,注意该方法会导致光标重置到最开始位置
+        editorCtx.setContents({
+          delta: {
+            ops: options
+          }
+        })
+        // 所以最后建议使富文本光标失焦,让用户手动聚焦光标
+        editorCtx.blur()
+      }
+    }
+  })
+
+}

+ 115 - 0
components/SCimage/SCimage.vue

@@ -0,0 +1,115 @@
+<template>
+	<view>
+		<!-- 图片 -->
+		<view class="" style="margin-top: 40rpx">
+			<view class="" style="margin-bottom: 20rpx">巡查图片{{ uploadNumber + '/' + 9 }}</view>
+			<view class="">
+				<!-- 上传照片 -->
+				<view style="margin-left: 56rpx">
+					<u-upload
+						width="200rpx"
+						height="200rpx"
+						:maxCount="9"
+						:deletable="true"
+						:fileList="fileList1"
+						@afterRead="afterRead"
+						@delete="deletePic"
+						multiple
+						name="1"
+					></u-upload>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'SCimage',
+	props: {
+		fileList: {
+			type: Array,
+			default() {
+				return [];
+			}
+		}
+	},
+	data() {
+		return {
+			// 上传的图片
+			fileList1: []
+		};
+	},
+	computed: {
+		log() {
+			// console.log(this.fileList, 'uuuu');
+			return this.fileList;
+		},
+		uploadNumber() {
+			this.fileList1 = this.fileList;
+			return this.fileList1.length;
+		}
+	},
+	methods: {
+		async afterRead(event) {
+			// console.log(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++;
+			}
+			// console.log(this[`fileList${event.name}`], 'jjjjj');
+
+			this.$emit('childEvent', this[`fileList${event.name}`]);
+		},
+		// 删除图片
+		deletePic(event) {
+			console.log(event, 'event');
+			this[`fileList${event.name}`].splice(event.index, 1);
+		},
+		uploadFilePromise(url) {
+			return new Promise((resolve, reject) => {
+				let userToken = '';
+				let auth = this.$db.get('auth');
+				userToken = auth.token;
+
+				let a = uni.uploadFile({
+					url: this.$config.baseUrl + 'api/common/upload?token=' + userToken,
+					// url: 'api/common/upload?token=' + userToken,
+					filePath: url,
+					name: 'file',
+
+					formData: {},
+					success: (res) => {
+						setTimeout(() => {
+							console.log(res);
+							resolve(JSON.parse(res.data).data.fullurl);
+						}, 1000);
+					}
+				});
+			});
+		}
+	}
+};
+</script>
+
+<style></style>

+ 161 - 0
components/Segmented/Segmented.vue

@@ -0,0 +1,161 @@
+<template>
+	<view
+		:class="[styleType === 'text' ? 'segmented-control--text' : 'segmented-control--button']"
+		:style="{ borderColor: styleType === 'text' ? '' : activeColor }"
+		class="segmented-control"
+	>
+		<view
+			v-for="(item, index) in values"
+			:class="[
+				styleType === 'text' ? '' : 'segmented-control__item--button',
+				index === currentIndex && styleType === 'button' ? 'segmented-control__item--button--active' : '',
+				index === 0 && styleType === 'button' ? 'segmented-control__item--button--first' : '',
+				index === values.length - 1 && styleType === 'button' ? 'segmented-control__item--button--last' : ''
+			]"
+			:key="index"
+			:style="{
+				backgroundColor: index === currentIndex && styleType === 'button' ? activeColor : '',
+				borderColor: (index === currentIndex && styleType === 'text') || styleType === 'button' ? activeColor : 'transparent'
+			}"
+			class="segmented-control__item"
+			@click="_onClick(index)"
+		>
+			<view>
+				<text
+					:style="{ color: index === currentIndex ? (styleType === 'text' ? activeColor : '#fff') : styleType === 'text' ? '#000' : activeColor }"
+					class="segmented-control__text"
+					:class="styleType === 'text' && index === currentIndex ? 'segmented-control__item--text' : ''"
+				>
+					{{ item }}
+				</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+/**
+ * SegmentedControl 分段器
+ * @description 用作不同视图的显示
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=54
+ * @property {Number} current 当前选中的tab索引值,从0计数
+ * @property {String} styleType = [button|text] 分段器样式类型
+ * 	@value button 按钮类型
+ * 	@value text 文字类型
+ * @property {String} activeColor 选中的标签背景色与边框颜色
+ * @property {Array} values 选项数组
+ * @event {Function} clickItem 组件触发点击事件时触发,e={currentIndex}
+ */
+
+export default {
+	name: 'Segmented',
+	emits: ['clickItem'],
+	props: {
+		current: {
+			type: Number,
+			default: 0
+		},
+		target: {
+			type: Object,
+			default: {}
+		},
+		values: {
+			type: Array,
+			default() {
+				return [];
+			}
+		},
+		activeColor: {
+			type: String,
+			default: '#2979FF'
+		},
+		styleType: {
+			type: String,
+			default: 'button'
+		}
+	},
+	data() {
+		return {
+			currentIndex: 0
+		};
+	},
+	watch: {
+		current(val) {
+			if (val !== this.currentIndex) {
+				this.currentIndex = val;
+			}
+		}
+	},
+	created() {
+		this.currentIndex = this.current;
+	},
+	methods: {
+		_onClick(index) {
+			//if (this.currentIndex !== index) {
+			this.currentIndex = index;
+			this.$emit('clickItem', {
+				currentIndex: index,
+				target: this.target
+			});
+			//}
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.segmented-control {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	box-sizing: border-box;
+	/* #endif */
+	flex-direction: row;
+	height: 36px;
+	overflow: hidden;
+	/* #ifdef H5 */
+	cursor: pointer;
+	/* #endif */
+}
+
+.segmented-control__item {
+	/* #ifndef APP-NVUE */
+	display: inline-flex;
+	box-sizing: border-box;
+	/* #endif */
+	position: relative;
+	flex: 1;
+	justify-content: center;
+	align-items: center;
+}
+
+.segmented-control__item--button {
+	border-style: solid;
+	border-top-width: 1px;
+	border-bottom-width: 1px;
+	border-right-width: 1px;
+	border-left-width: 0;
+}
+
+.segmented-control__item--button--first {
+	border-left-width: 1px;
+	border-top-left-radius: 5px;
+	border-bottom-left-radius: 5px;
+}
+
+.segmented-control__item--button--last {
+	border-top-right-radius: 5px;
+	border-bottom-right-radius: 5px;
+}
+
+.segmented-control__item--text {
+	border-bottom-style: solid;
+	border-bottom-width: 2px;
+	padding: 6px 0;
+}
+
+.segmented-control__text {
+	font-size: 14px;
+	line-height: 20px;
+	text-align: center;
+}
+</style>

+ 275 - 0
components/TouGao/TouGao.vue

@@ -0,0 +1,275 @@
+<template>
+	<view class="box">
+		<view class="ban_box">
+			<view class="tit_box">
+				<view class="title">{{ titList.title }}:</view>
+				<u--input customStyle="background-color:#ffffff" placeholder="请输入标题" border="surround" v-model.trim="titVal"></u--input>
+			</view>
+			<view v-if="titList.typeVal" class="tit_box" @click="show = true">
+				<view class="title">文章类型:</view>
+				<view class="lx">
+					<view :style="{ color: typesofs ? '#000' : '#c0c4cc' }">{{ typesofs ? typesofs : '请选择类型' }}</view>
+					<u-icon name="arrow-down" color="#c0c4cc" size="20"></u-icon>
+				</view>
+			</view>
+			<view class="tit_box" v-if="titList">
+				<view class="title" style="margin-top: -120rpx">{{ titList.content }}:</view>
+				<u--textarea v-model.trim="textVal" placeholder="请输入内容"></u--textarea>
+			</view>
+			<!-- 内容 -->
+			<view v-if="titList.typeVal" class="tit_box">
+				<view class="title" style="margin-top: -120rpx">文物价值:</view>
+				<u--textarea v-model.trim="textVal2" placeholder="请输入内容"></u--textarea>
+			</view>
+		</view>
+		<!-- 图片 -->
+		<view class="" style="margin-top: 40rpx">
+			<view class="" style="margin-left: 32rpx; margin-bottom: 20rpx">上传图片:{{ uploadNumber + '/' + 9 }}</view>
+			<view class="">
+				<!-- 上传照片 -->
+				<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>
+		</view>
+		<!-- 视频 -->
+		<view class="" style="margin-left: 50rpx">
+			<view style="margin: 20rpx 0 20rpx -15rpx">请上传视频:</view>
+
+			<u-upload width="200rpx" height="200rpx" :fileList="fileList2" @afterRead="afterRead" @delete="deletePic" name="2" multiple :maxCount="10" accept="video"></u-upload>
+		</view>
+		<u-picker :show="show" @cancel="show = false" :columns="columns" @close="close" :closeOnClickOverlay="true" @confirm="confirm"></u-picker>
+		<view @click="isOk" class="tg_box">{{ titList.okTit }}</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'TouGao',
+	props: {
+		titList: {
+			type: Object,
+			default: () => {
+				return {
+					okTit: '投稿',
+					title: '文章标题',
+					content: '文章内容',
+					typeVal: true
+				};
+			}
+		}
+	},
+	data() {
+		return {
+			typesofs: '',
+			titVal: '',
+			textVal: '',
+			textVal2: '',
+			show: false,
+			columns: [['古建筑', '近现代史迹', '古遗址']],
+			// 上传的图片
+			fileList1: [
+				{
+					url: 'https://huli-app.wenlvti.net/app_static/minnanhun/image/shouyeTJ.png'
+				}
+			]
+		};
+	},
+	computed: {
+		uploadNumber() {
+			return this.fileList1.length;
+		}
+	},
+	methods: {
+		confirm(e) {
+			console.log('confirm', e);
+			this.typesofs = e.value[0];
+			this.show = false;
+		},
+		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++;
+			}
+		},
+		// 删除图片
+		deletePic(event) {
+			console.log(event, 'event');
+			this[`fileList${event.name}`].splice(event.index, 1);
+		},
+		uploadFilePromise(url) {
+			return new Promise((resolve, reject) => {
+				let a = uni.uploadFile({
+					url: 'https://mnhcdn.wenlvti.net/api/minnansoul/content/contribute', // 仅为示例,非真实的接口地址
+					filePath: url,
+					name: 'file',
+					formData: {
+						user: 'test'
+					},
+					success: (res) => {
+						setTimeout(() => {
+							resolve(res.data.data);
+						}, 1000);
+					}
+				});
+			});
+		},
+		// 投稿
+		// isOk() {
+		// 	let titleIsEmpty = this.titleVal === '';
+		// 	let textIsEmpty = this.textVal === '';
+
+		// 	// 先检查标题和内容是否为空
+		// 	if (titleIsEmpty) {
+		// 		that.$common.errorToShow('请填写投稿标题');
+		// 		return false; // 遇到错误时提前返回,避免后续条件的判断
+		// 	}
+
+		// 	if (textIsEmpty) {
+		// 		that.$common.errorToShow('请输入投稿内容');
+		// 		return false;
+		// 	}
+
+		// 	// 再检查模型ID、类型ID和栏目ID
+		// 	if (this.model_id === '' || this.main_body_column_id === '') {
+		// 		that.$common.errorToShow('请选择投稿类型');
+		// 		return false;
+		// 	}
+
+		// 	// 如果typeId是必须选择的,这里也要进行验证
+		// 	if (this.typeId === '') {
+		// 		that.$common.errorToShow('请选择内容类型');
+		// 		return false;
+		// 	} else {
+		// 		this.$api.contribute(
+		// 			{
+		// 				main_body_id: this.main_body_id,
+		// 				model_id: this.model_id,
+		// 				main_body_column_id: this.main_body_column_id,
+		// 				title: this.titleVal,
+		// 				type: this.typeId,
+		// 				content: this.titleVal
+		// 			},
+		// 			function (res) {
+		// 				if (res.code == 1) {
+		// 					that.$common.successToShow('投稿成功');
+		// 					// console.log(res, 6666);
+		// 				} else {
+		// 					that.$common.errorToShow('投稿失败请稍后再试');
+		// 				}
+		// 			}
+		// 		);
+		// 	}
+		// },
+		close() {
+			this.show = false;
+		}
+	}
+};
+</script>
+
+<style>
+.img {
+	width: 100%;
+	height: 100%;
+	border-radius: 50%;
+}
+.fk_box {
+	width: 90%;
+	display: flex;
+	justify-content: space-between;
+	margin: auto;
+	margin-top: 20rpx;
+	font-size: 30rpx;
+	color: #313131;
+	text-align: center;
+}
+.jianyi {
+	width: 170rpx;
+	height: 60rpx;
+	line-height: 58rpx;
+	border: 2rpx solid #fadcb3;
+	background-color: #fdf7ee;
+	border-radius: 10rpx;
+}
+/deep/.u-input--radius.data-v-113bc24f,
+.u-input--square.data-v-113bc24f {
+	background-color: #ffffff !important;
+}
+/deep/.u-input.data-v-113bc24f {
+	background-color: #ffffff !important;
+}
+
+.ban_box {
+	width: 90%;
+	margin: auto;
+	margin-top: 40rpx;
+}
+.tit_box {
+	width: 680rpx;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	margin-top: 40rpx;
+}
+.title {
+	margin-right: 10rpx;
+	font-size: 30rpx;
+	color: #313131;
+}
+.lx {
+	display: flex;
+	justify-content: space-between;
+	width: 540rpx;
+	height: 70rpx;
+	background-color: #ffffff;
+	border-radius: 10rpx;
+	line-height: 70rpx;
+	color: #c0c4cc;
+	padding-left: 20rpx;
+	padding-right: 20rpx;
+}
+.tg_box {
+	width: 390rpx;
+	height: 74rpx;
+	margin: auto;
+	margin-top: 40rpx;
+	font-size: 34rpx;
+	background-color: #f5c382;
+	text-align: center;
+	font-weight: bold;
+	color: #313131;
+	border-radius: 10rpx;
+	line-height: 74rpx;
+}
+</style>

+ 204 - 0
components/course-item/course-item.vue

@@ -0,0 +1,204 @@
+<template>
+	<view class="course-item" @click="onJumpDetail">
+		<view class="b-cover">
+			<image style="width: 100%; height: 100%" :src="item.cover_url" mode="aspectFill"></image>
+		</view>
+		<view class="b-main">
+			<text class="text_23">{{ item.name }}</text>
+			<view class="b-price">
+				<view class="aa" style="display: flex; margin-top: 70rpx; width: 150rpx">
+					<view lines="1" class="text_25">会员价</view>
+					<view lines="1" class="text_26">¥{{ item.price }}</view>
+				</view>
+
+				<!-- <view class="s-vip-price" v-if="item.card_price > 0 && !iphonePlatform">会员价 ¥{{ item.card_price }}</view> -->
+				<button bindtap="onClick" class="button_6">
+					<text lines="1" class="text_27">立即报名</text>
+				</button>
+			</view>
+			<!-- <view class="b-info">
+				<view class="s-category">{{ item.category_name }}</view>
+				<view class="b-counter">
+					<view class="s-learn">{{ item.count_user }}人学习</view>
+				</view>
+			</view> -->
+		</view>
+	</view>
+</template>
+
+<script>
+// import mixinsGoods from '@/mixins/goods.js';
+export default {
+	name: 'course-item',
+	// mixins: [mixinsGoods],
+	props: {
+		item: {
+			type: Object,
+			default: null
+		}
+	},
+	data() {
+		return {};
+	},
+	methods: {
+		onJumpDetail() {
+			console.log(11);
+			console.log(this.item, 565656);
+
+			uni.navigateTo({
+				url: '/pages/course/detail?cid=' + this.item.id
+			});
+		}
+	}
+};
+</script>
+
+<style>
+.button_6 {
+	background-color: rgba(202, 86, 66, 1);
+	border-radius: 10rpx;
+	height: 44rpx;
+	display: flex;
+	flex-direction: column;
+	width: 129rpx;
+	margin: 70rpx 18rpx 0 82rpx;
+}
+.text_23 {
+	width: 110rpx;
+	height: 26rpx;
+	overflow-wrap: break-word;
+	color: rgba(49, 37, 32, 1);
+	font-size: 28rpx;
+	font-family: SourceHanSansCN-Regular;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 28rpx;
+	margin-left: 1rpx;
+}
+.text_25 {
+	width: 60rpx;
+	height: 20rpx;
+	overflow-wrap: break-word;
+	color: rgba(202, 86, 66, 1);
+	font-size: 20rpx;
+	font-family: SourceHanSansCN-Regular;
+	font-weight: normal;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 20rpx;
+	margin-top: 2rpx;
+}
+.text_26 {
+	width: 56rpx;
+	height: 22rpx;
+	overflow-wrap: break-word;
+	color: rgba(202, 86, 66, 1);
+	font-size: 28rpx;
+	font-family: SourceHanSansCN-Bold;
+	font-weight: 700;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 28rpx;
+}
+.text_27 {
+	color: rgba(248, 226, 175, 1);
+	font-size: 24rpx;
+	white-space: nowrap;
+	line-height: 45rpx;
+	margin-left: -10rpx;
+}
+.course-item {
+	padding: 30upx;
+	/* background: #fff; */
+	display: flex;
+	align-items: center;
+}
+
+.course-item:last-child {
+	margin-bottom: 0;
+}
+
+.course-item .b-cover {
+	width: 307rpx;
+	height: 171rpx;
+}
+
+.course-item .b-cover image {
+	width: 100%;
+	height: 100%;
+}
+
+.course-item .b-main {
+	flex: 1;
+	height: 170rpx;
+	margin-left: 20upx;
+}
+
+.course-item .b-main .b-title {
+	font-size: 30upx;
+	color: #333;
+	max-width: 530upx;
+	display: inline-block;
+}
+
+.course-item .b-main .b-price {
+	margin-top: 16upx;
+	display: flex;
+	align-items: center;
+}
+
+.course-item .b-main .b-price .s-payment {
+	margin-right: 20upx;
+	font-size: 36upx;
+	font-weight: bold;
+	color: #dd524d;
+}
+
+.course-item .b-main .b-price .s-payment.f-small {
+	font-size: 28upx;
+}
+
+.course-item .b-main .b-price .s-vip-price {
+	margin-right: 20upx;
+	font-size: 26upx;
+	color: #f0ad4e;
+}
+
+.course-item .b-main .b-price .s-vip-free {
+	margin-right: 20upx;
+	padding: 6upx 10upx;
+	font-size: 24upx;
+	color: #555;
+	background: #f0ad4e;
+	border-radius: 5upx;
+}
+
+.course-item .b-main .b-info {
+	margin-top: 16upx;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+}
+
+.course-item .b-main .b-info .s-category {
+	font-size: 26upx;
+	color: #666;
+}
+
+.course-item .b-main .b-info .b-counter {
+	display: flex;
+	align-items: center;
+}
+
+.course-item .b-main .b-info .b-counter .s-like {
+	font-size: 24upx;
+	color: #999;
+}
+
+.course-item .b-main .b-info .b-counter .s-learn {
+	margin-left: 30upx;
+	font-size: 24upx;
+	color: #999;
+}
+</style>

+ 137 - 0
components/fa-array-download/fa-array-download.vue

@@ -0,0 +1,137 @@
+<template>
+	<view class="fa-array-download">
+		<view class="u-flex">
+			<view class="u-flex-2"><view class="">来源</view></view>
+			<view class="u-flex-7 u-p-l-10"><view class="">地址</view></view>
+			<view class="u-flex-4 u-p-l-10"><view class="">密码</view></view>
+		</view>
+		<view class="u-flex u-m-t-15" v-for="(item, index) in list" :key="index">
+			<view class="u-flex-2"><u-input v-model="item.name" :trim="trim" placeholder="来源" :border="border" :clearable="false" /></view>
+			<view class="u-flex-6 u-m-l-10 u-m-r-10 "><u-input v-model="item.url" placeholder="请输入地址" :trim="trim" :border="border" /></view>
+			<view class="u-p-l-10 u-p-r-10 u-m-r-10 u-text-center close" @click="uploadFile(index)">
+				<u-icon name="arrow-upward" color="#ffffff" size="30"></u-icon>
+			</view>
+			<view class="u-flex-3 u-m-r-10 "><u-input placeholder="密码" v-model="item.password" :trim="trim" :border="border" /></view>
+			<view class="u-p-l-10 u-p-r-10 u-text-center close" @click="del(index)"><u-icon name="close" color="#ffffff" size="30"></u-icon></view>
+		</view>
+		<view class="u-text-right u-m-t-20">
+			<u-button
+				size="mini"
+				type="primary"
+				:custom-style="{ backgroundColor: lightColor, color: theme.bgColor, border: '1px solid ' + faBorderColor }"
+				throttle-time="0"
+				@click="add"
+			>
+				<u-icon name="plus" :color="theme.bgColor" size="25"></u-icon>
+				<text class="u-m-l-5">追加</text>
+			</u-button>
+		</view>
+		<fa-upload-file ref="file" @success="uploadSuccess"></fa-upload-file>
+	</view>
+</template>
+
+<script>
+import Emitter from '@/uni_modules/uview-ui/libs/util/emitter.js';
+import FaUploadFile from '../fa-upload-file/fa-upload-file.vue'
+export default {
+	name: 'fa-array-download',
+	components:{FaUploadFile},
+	mixins: [Emitter],
+	props: {
+		contentList: {
+			type: [Array, Object],
+			default() {
+				return [];
+			}
+		},
+		showValue: {
+			type: String,
+			default() {
+				return '';
+			}
+		}
+	},
+	watch: {
+		contentList: {
+			immediate: true,
+			handler: function(val) {
+				if (val) {
+					let list = [];
+					for (let i in val) {
+						list.push({
+							name: i,
+							url: '',
+							password: ''
+						});
+					}
+					this.list = list;
+				}
+			}
+		},
+		list: {
+			deep: true,
+			handler: function(newValue) {
+				let value = this.$u.test.empty(newValue)?'':JSON.stringify(newValue);			
+				this.$emit('input', value);
+				setTimeout(() => {
+					this.dispatch('u-form-item', 'on-form-blur', value);
+				}, 50);
+			}
+		},
+		showValue: {
+			immediate: true,
+			handler(val) {
+				if (val) {
+					this.list = JSON.parse(val);
+				}
+			}
+		}
+	},
+	data() {
+		return {
+			border: true,
+			trim: true,
+			listIndex: 0,
+			list: [
+				{
+					name: '',
+					url: '',
+					password: ''
+				}
+			]
+		};
+	},
+	methods: {
+		add() {
+			this.list.push({
+				name: '',
+				url: '',
+				password: ''
+			});
+		},
+		del(index) {
+			this.list.splice(index, 1);
+		},
+		uploadFile(index) {
+			this.listIndex = index;
+			this.$refs.file.onUpload();
+		},
+		uploadSuccess(e) {
+			this.$set(this.list[this.listIndex], 'url', e);
+		},
+		getData() {
+			return this.list;
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.fa-array-download{
+	width: 100%;
+}
+.close {
+	background: #2c3e50;
+	border-radius: 10rpx;
+}
+</style>

+ 140 - 0
components/fa-array/fa-array.vue

@@ -0,0 +1,140 @@
+<template>
+	<view class="fa-array">
+		<view class="u-flex">
+			<view class="u-flex-5">
+				<view class="">{{ faKey }}</view>
+			</view>
+			<view class="u-flex-6 u-p-l-10">
+				<view class="">{{ faVal }}</view>
+			</view>
+		</view>
+		<view class="u-flex u-m-t-15" v-for="(item, index) in list" :key="index">
+			<view class="u-flex-5"><u-input v-model="item.key" :trim="trim" :border="border" /></view>
+			<view class="u-m-l-15 u-m-r-15 u-flex-5"><u-input v-model="item.value" :trim="trim" :border="border" /></view>
+			<view class="u-p-l-15 u-p-r-15 u-text-center close" @click="del(index)"><u-icon name="close" color="#ffffff" size="30"></u-icon></view>
+		</view>
+		<view class="u-text-right u-m-t-20">
+			<u-button
+				size="mini"
+				type="primary"
+				:custom-style="{ backgroundColor: lightColor, color: theme.bgColor, border: '1px solid ' + faBorderColor }"
+				throttle-time="0"
+				@click="add"
+			>
+				<u-icon name="plus" :color="theme.bgColor" size="25"></u-icon>
+				<text class="u-m-l-5">追加</text>
+			</u-button>
+		</view>
+	</view>
+</template>
+
+<script>
+import Emitter from '@/uni_modules/uview-ui/libs/util/emitter.js';
+export default {
+	name: 'fa-array',
+	mixins: [Emitter],
+	props: {
+		value:{
+			type:String,
+			default:''
+		},
+		faKey: {
+			type: String,
+			default: '键'
+		},
+		faVal: {
+			type: String,
+			default: '值'
+		},		
+		showValue: {
+			type: [String, Object],
+			default: ''
+		}
+	},
+	watch: {
+		list: {
+			deep: true,
+			handler: function(newValue) {
+				let obj = {};
+				newValue.map(item => {
+					if (item.key && item.value) {
+						obj[item.key] = item.value;
+					}
+				});
+				let value = this.$u.test.empty(obj)?'':JSON.stringify(obj);
+				this.$emit('input', value);
+				setTimeout(() => {
+					this.dispatch('u-form-item', 'on-form-blur', value);
+				}, 50);
+			}
+		},
+		showValue: {
+			immediate: true,
+			handler(val) {
+				if (val) {
+					if (this.$u.test.object(val)) {
+						let arr = [];
+						for (let i in val) {
+							arr.push({
+								key: i,
+								value: val[i]
+							});
+						}
+						if (arr.length > 0) {
+							this.list = arr;
+						}
+					} else {
+						let o = JSON.parse(val);
+						let arr = [];
+						for (let i in o) {
+							arr.push({
+								key: i,
+								value: o[i]
+							});
+						}
+						if (arr.length > 0) {
+							this.list = arr;
+						}
+					}
+				}
+			}
+		}
+	},
+	data() {
+		return {
+			border: true,
+			trim: true,
+			list: [
+				{
+					key: '',
+					value: ''
+				}
+			]
+		};
+	},
+	methods: {
+		add() {
+			this.list.push({
+				key: '',
+				value: ''
+			});
+		},
+		del(index) {
+			this.list.splice(index, 1);
+		},
+		getData() {
+			return this.list;
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.fa-array{
+	width: 100%;
+}
+.close {
+	background: #2c3e50;
+	border-radius: 10rpx;
+}
+</style>

+ 59 - 0
components/fa-captchaparts/fa-captchaparts.vue

@@ -0,0 +1,59 @@
+<template>
+	<view class="" :style="customStyle">
+		<u-form-item :required ="true" :label-position="labelPosition" prop="captcha" label-width="150" label="验证码">
+			<view class="u-m-r-15"><u-input v-model="captcha" /></view>
+			<u-image slot="right" width="250rpx" height="60rpx" :src="img_url" @click="getCaptcha"></u-image>
+		</u-form-item>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'fa-captchaparts',
+	props: {
+		value: {
+			type: String,
+			default: ''
+		},		
+		ident: {
+			type: [String, Number],
+			default: ''
+		},
+		labelPosition: {
+			type: String,
+			default: 'top'
+		},
+		customStyle: {
+			type: Object,
+			default() {
+				return {};
+			}
+		}
+	},
+	watch: {
+		captcha(newValue, oldValue) {
+			this.$emit('input', newValue);
+		}
+	},
+	mounted() {
+		this.getCaptcha();
+	},
+	data() {
+		return {
+			captcha: '',
+			img_url: ''
+		};
+	},
+	methods: {
+		getCaptcha() {
+			this.$api.getCaptcha({ ident: this.ident }).then(res => {
+				if (res.code == 1) {
+					this.img_url = res.data;
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss"></style>

+ 88 - 0
components/fa-check-radio/fa-check-radio.vue

@@ -0,0 +1,88 @@
+<template>
+	<view>
+		<view class="" v-if="type == 'checkbox'">
+			<u-checkbox-group @change="checkboxGroupChange" :width="radioCheckWidth" :wrap="radioCheckWrap">
+				<u-checkbox v-model="item.checked" :active-color="theme.bgColor" v-for="(item, index) in list" :key="index" :name="item.value">
+					{{ item.lable }}
+				</u-checkbox>
+			</u-checkbox-group>
+		</view>
+		<view class="" v-else>
+			<u-radio-group v-model="radioValue" @change="radioGroupChange">
+				<u-radio v-for="(item, index) in list" :active-color="theme.bgColor" :key="index" :name="item.value">{{ item.lable }}</u-radio>
+			</u-radio-group>
+		</view>
+	</view>
+</template>
+
+<script>
+import Emitter from '@/uni_modules/uview-ui/libs/util/emitter.js';
+export default {
+	name: 'fa-check-radio',
+	mixins: [Emitter],
+	props: {
+		value:{
+			type:[String,Number],
+			default:''
+		},
+		faList: {
+			type: [Object, Array],
+			default: []
+		},
+		type: {
+			type: String,
+			default: 'checkbox'
+		},		
+		checkValue: {
+			type: [String, Array],
+			default: ''
+		}
+	},
+	watch: {
+		faList: {
+			immediate: true,
+			handler(val) {
+				let value = [];
+				if (this.type == 'checkbox') {
+					value = this.checkValue ? this.checkValue.split(',') : [];
+				} else {
+					this.radioValue = this.checkValue;
+				}
+				let list = [];
+				for (let i in val) {
+					list.push({
+						lable: val[i],
+						value: i,
+						checked: value.indexOf(i) != -1
+					});
+				}
+				this.list = list;
+			}
+		}
+	},
+	data() {
+		return {
+			radioCheckWidth: 'auto',
+			radioCheckWrap: false,
+			list: [],
+			radioValue: ''
+		};
+	},
+	methods: {
+		checkboxGroupChange(e) {
+			this.sendValue(e.join(','));
+		},
+		radioGroupChange(e) {
+			this.sendValue(e);
+		},
+		sendValue(value){
+			this.$emit('input', value);
+			setTimeout(() => {
+				this.dispatch('u-form-item', 'on-form-blur',value);
+			}, 50);
+		}
+	}
+};
+</script>
+
+<style lang="scss"></style>

Dosya farkı çok büyük olduğundan ihmal edildi
+ 485 - 0
components/fa-editor/fa-editor.vue


+ 355 - 0
components/fa-selectpages/fa-selectpages.vue

@@ -0,0 +1,355 @@
+<template>
+	<view class="selectpage">
+		<view class="" v-if="checkeType == 'selectpage'">
+			<u-input type="select" :select-open="show" v-model="page_lable" :placeholder="'请选择' + title" @click="show = true"></u-input>
+		</view>
+		<view class="" v-if="checkeType == 'selectpages'">
+			<view class="select-pages u-flex u-flex-wrap" @click="show = true">
+				<view class="u-m-r-10" v-for="(tag, tak) in pagesLable" :key="tak">
+					<u-tag :text="tag[showField]" :bg-color="lightColor" :border-color="faBorderColor" :color="theme.bgColor" type="success" />
+				</view>
+				<view class="u-light-color" v-text="'请选择' + title" v-if="!pagesLable.length"></view>
+			</view>
+		</view>
+		<u-popup v-model="show" :popup="false" @close="close" mode="bottom" height="700">
+			<view class="u-flex u-flex-column">
+				<view class="fa-column u-p-l-30 u-p-r-30 u-p-t-20 u-p-b-20 u-border-bottom">
+					<u-search placeholder="搜索" v-model="q_word" :show-action="false"></u-search>
+				</view>
+				<view class="fa-column u-flex-1 u-flex fa-scroll">
+					<scroll-view scroll-y="true" :style="{ height: scrollHg + 'px', width: '100vw' }" @scrolltolower="goLower">
+						<!-- 多选 -->
+						<view v-if="checkeType == 'selectpages'">
+							<checkbox-group>
+								<u-cell-group>
+								<u-cell :arrow="false" v-for="(item, index) in list" :key="index" :title="item[showField]" @click.self="selectCell(index)">
+									<checkbox
+										slot="right-icon"
+										shape="square"
+										:color="theme.bgColor"
+										:value="item[keyField] + ''"
+										:checked="item.checked"
+									></checkbox>
+								</u-cell>
+								</u-cell-group>
+							</checkbox-group>
+						</view>
+						<!-- 单选 -->
+						<view class="" v-else>
+							<u-radio-group v-model="radio_value">
+								<u-cell-group>
+								<u-cell :arrow="false" v-for="(item, index) in list" :key="index" :title="item[showField]" @click.self="selectCell(index)">
+									<u-radio slot="right-icon" :active-color="theme.bgColor" :name="item[keyField] + ''"></u-radio>
+								</u-cell>
+								</u-cell-group>
+							</u-radio-group>
+						</view>
+						<view class="u-p-10"><u-loadmore :status="status" /></view>
+					</scroll-view>
+				</view>
+				<view class="fa-column select-footer u-text-center">
+					<u-gap height="10" bg-color="#eaeaec"></u-gap>
+					<view class="u-p-10 u-flex u-row-around">
+						<view class="u-flex-1" v-if="checkeType == 'selectpages'" @click="clearAll"><text>清空</text></view>
+						<!-- <view class="u-flex-1" @click="allSelect"> -->
+						<!-- <text>全选</text> -->
+						<!-- </view> -->
+						<view class="u-flex-1" @click="confirm">
+							<text>{{ checkeType == 'selectpages' ? '确定' : '取消' }}</text>
+						</view>
+					</view>
+				</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+import Emitter from '@/uni_modules/uview-ui/libs/util/emitter.js';
+export default {
+	name: 'fa-selectpages',
+	mixins: [Emitter],
+	props: {
+		value:{
+			type:[String,Number],
+			default:''
+		},
+		//查询id
+		faId: {
+			type: [Number, String],
+			default: ''
+		},
+		//显示字段
+		showField: {
+			type: String,
+			default: ''
+		},
+		//保存的键
+		keyField: {
+			type: String,
+			default: ''
+		},
+		//提示
+		title: {
+			type: String,
+			default: ''
+		},		
+		checkeType: {
+			type: String,
+			default: 'selectpage'
+		},
+		//默认的值
+		showValue: {
+			type: [String, Number],
+			default: ''
+		}
+	},
+	watch: {
+		//弹出高度
+		show(newValue, oldValue) {
+			if (newValue) {
+				this.$nextTick(() => {
+					setTimeout(() => {
+						uni.createSelectorQuery()
+							.in(this)
+							.select('.fa-scroll')
+							.boundingClientRect(rect => {
+								console.log(rect);
+								if (rect) {
+									this.scrollHg = rect.height;
+								}
+							})
+							.exec();
+					}, 100); //在百度直接获取不到,需要延时
+				});
+				if (!this.list.length) {
+					this.page = 1;
+					this.getSelectPages();
+				}
+			} else {
+				this.sendChange();
+			}
+		},
+		//默认的数据
+		showValue: {
+			immediate: true,
+			handler(val) {
+				//第一次渲染默认就好
+				if (val && !this.isFirst) {
+					this.isFirst = true;
+					this.getInitSelect();
+				}
+			}
+		},
+		//搜索
+		q_word(newValue, oldValue) {
+			this.list = [];
+			this.page = 1;
+			this.getSelectPages();
+		}
+	},
+	data() {
+		return {
+			show: false,
+			list: [],
+			radio_value: '',
+			scrollHg: 0,
+			q_word: '',
+			pageNum: 0,
+			page: 1,
+			totalPage: 0,
+			status: 'loadmore',
+
+			isFirst: false,
+
+			page_lable: '',
+			pagesLable: [], //初始化的值
+			ids_ing: [], //已经加载的值
+			ids: []
+		};
+	},
+	methods: {
+		close() {
+			this.show = false;
+		},
+		//初始化值
+		getInitSelect() {
+			if (this.showValue) {
+				let param = {
+					id: this.faId,
+					pageNumber: this.page,
+					q_word: this.q_word,
+					keyValue: this.showValue
+				};
+				this.$api.selectpage(param).then(res => {
+					if (this.checkeType == 'selectpage') {
+						this.page_lable = res.list[0][this.showField];
+						this.radio_value = res.list[0][this.keyField] + '';
+					} else {
+						this.pagesLable = res.list;
+						let ids = [];
+						res.list.forEach(item => {
+							ids.push(item[this.keyField]);
+						});
+						this.ids = ids;
+					}
+				});
+			}
+		},
+		//获取数据
+		getSelectPages() {
+			if (!this.faId) {
+				return;
+			}			
+			let param = { id: this.faId, pageNumber: this.page, q_word: this.q_word };
+			this.$api.selectpage(param).then(res => {
+				this.status = res.total == 0 || this.page >= this.totalPage ? 'nomore' : 'loadmore';
+				let list = [];
+				if (this.checkeType == 'selectpages') {
+					res.list.forEach(item => {
+						item.checked = this.ids.indexOf(item[this.keyField]) != -1;
+						list.push(item);
+						//已选的
+						this.pagesLable.forEach(it => {
+							if (item[this.keyField] == it[this.keyField]) {
+								this.ids_ing.push(it); //在已选的已经加载的数据
+							}
+						});
+					});
+				} else {
+					list = res.list;
+				}
+				//一页的数量,取第一次就好
+				if (!this.pageNum) {
+					this.pageNum = list.length;
+				}
+				this.totalPage = Math.ceil(res.total / this.pageNum);
+				this.list = [...this.list, ...list];
+			});
+		},
+		//选择
+		selectCell(index) {
+			if (this.checkeType == 'selectpages') {
+				this.$set(this.list[index], 'checked', !this.list[index].checked);
+			} else {
+				//单选
+				this.radio_value = this.list[index][this.keyField];
+				this.page_lable = this.list[index][this.showField];
+				this.$emit('input', this.radio_value);				
+				this.close();
+				setTimeout(() => {
+					this.dispatch('u-form-item', 'on-form-blur', this.radio_value);
+				}, 50);
+			}
+		},
+		//加载更多
+		goLower(e) {
+			if (this.page == this.totalPage) {
+				return;
+			}
+			this.status = 'loading';
+			this.page++;
+			this.getSelectPages();
+		},
+		//多选确定
+		confirm() {
+			if (this.checkeType == 'selectpages') {
+				//先取未加载的数据的集合
+				let data = this.pagesLable.filter(item => {					
+					if (
+						this.$u.test.empty(this.ids_ing) || this.ids_ing.find(it => {
+							return item[this.keyField] == it[this.keyField];
+						})
+					) {
+						return false;
+					} else {
+						return true;
+					}
+				});
+				
+				let ids = [];
+				let res = [];
+				
+				this.list.forEach(item => {
+					if (item.checked) {
+						ids.push(item[this.keyField]);
+						res.push(item);
+					}
+				});
+				
+				//追加未加载的选项
+				data.forEach(item => {
+					ids.push(item[this.keyField]);
+					res.push(item);
+				});
+				
+				this.pagesLable = res;
+				this.ids = ids;
+								
+				this.$emit('input', ids.join(','));
+				setTimeout(() => {
+					this.dispatch('u-form-item', 'on-form-blur', ids.join(','));
+				}, 50);
+			}
+			this.close();
+		},
+		//全选
+		allSelect() {
+			this.list.map(item => {
+				item.checked = true;
+			});
+		},
+		//清空
+		clearAll() {
+			this.list.map(item => {
+				item.checked = false;
+			});
+			this.pagesLable = [];
+		},
+		//派发事件
+		sendChange() {
+			setTimeout(() => {
+				if (this.checkeType == 'select') {
+					this.dispatch('u-form-item', 'on-form-change', this.radio_value);
+				} else {
+					this.dispatch('u-form-item', 'on-form-change', this.checkbox_value);
+				}
+			}, 50);
+		}
+		// upPage(){
+		// 	if(this.page==1){
+		// 		return;
+		// 	}
+		// 	this.page--;
+		// },
+		// nextPage(){
+		// 	if(this.page == this.totalPage){
+		// 		return;
+		// 	}
+		// 	this.page++;
+
+		// 	if(!this.list[this.page-1]){
+		// 		this.getSelectPages();
+		// 	}
+		// }
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.selectpage {
+	width: 100%;
+}
+.select-pages {
+	width: 100%;
+	border: 1px solid #dcdfe6;
+	padding: 5rpx 10rpx;
+}
+.u-flex-column {
+	flex-direction: column;
+	height: 100%;
+	.fa-column {
+		width: 100%;
+	}
+}
+</style>

+ 302 - 0
components/fa-selects/fa-selects.vue

@@ -0,0 +1,302 @@
+<template>
+	<view class="selects">
+		<view class="" style="min-height: 70rpx;padding-top: 20rpx;" @click="show = true;">
+			<rich-text :class="!lists_lable ? 'richColor' : ''" :nodes="nodes(lists_lable || '请选择' + title)"></rich-text>
+		</view>
+		<u-popup :show="show" mode="bottom" height="600" @close="close">
+			
+			<view class="u-flex u-flex-column">
+				<view class="fa-column u-flex-1 u-flex fa-scroll">
+					<scroll-view scroll-y="true" :style="[{ height: scrollHg + 'px', width: '100vw' }]">
+						<!-- 多选-->
+						<view v-if="checkeType == 'selects'">
+							<checkbox-group placement="column" @change="checkSelct">
+								<u-cell-group>
+								<u-cell :arrow="false" v-for="(item, index) in list" :key="index" @click.self="selectCell(index)">
+									<view slot="title"><rich-text :nodes="nodes(item[showField])"></rich-text></view>
+									<checkbox
+										slot="right-icon"
+										shape="square"
+										:class="item.disabled == true ? 'fa-disabled' : ''"
+										:checked="item.checked"
+										:color="theme.bgColor"
+										:disabled="item.disabled == true"
+									></checkbox>
+								</u-cell>
+								</u-cell-group>
+							</checkbox-group>
+						</view>
+						<!-- 单选 -->
+						<view class="" v-else>
+							<u-radio-group style="width: 100%;" v-model="radio_value" >
+								<u-cell-group>
+								<u-cell :arrow="false" v-for="(item, index) in list" :key="index" @click.self="selectCell(index)">
+									<view slot="title"><rich-text :nodes="nodes(item[showField])"></rich-text></view>
+									<u-radio slot="right-icon" :active-color="theme.bgColor" :name="item[keyField]" :disabled="item.disabled == true"></u-radio>
+								</u-cell>
+								</u-cell-group>
+							</u-radio-group>
+						</view>
+					</scroll-view>
+				</view>
+				<view class="fa-column select-footer u-text-center" v-if="checkeType == 'selects'">
+					<u-gap height="10" bg-color="#eaeaec"></u-gap>
+					<view class="u-p-10 u-flex u-row-around">
+						<view class="u-flex-1" v-if="checkeType == 'selects'" @click="clearAll"><text>清空</text></view>
+						<!-- <view class="u-flex-1" @click="allSelect"> -->
+						<!-- <text>全选</text> -->
+						<!-- </view> -->
+						<view class="u-flex-1" @click="confirm"><text>确定</text></view>
+					</view>
+				</view>
+				<view class="fa-column select-footer u-text-center" v-if="checkeType != 'selects'">
+					<u-gap height="5" bg-color="#eaeaec"></u-gap>
+					<view class="u-p-10 u-flex u-row-around">
+						<view class="u-flex-1" @click="close"><text>取消</text></view>
+					</view>
+				</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+import Emitter from '@/uni_modules/uview-ui/libs/util/emitter.js';
+export default {
+	name: 'fa-selects',
+	mixins: [Emitter],
+	props: {
+		value: {
+			type: [String, Number],
+			default: false
+		},
+		//数据源
+		faList: {
+			type: [Object, Array, String],
+			default: ''
+		},
+		//选择类型
+		checkeType: {
+			type: String,
+			default: 'select'
+		},
+		title: {
+			type: String,
+			default: ''
+		},
+		//显示的字段
+		showField: {
+			type: String,
+			default: 'name'
+		},
+		//取值得字段
+		keyField: {
+			type: String,
+			default: 'id'
+		},		
+		//默认的值
+		showValue: {
+			type: [String, Number],
+			default: ''
+		}
+	},
+	computed: {
+		nodes() {
+			return title => {
+				return [
+					{
+						name: 'div',
+						children: [
+							{
+								type: 'text',
+								text: title
+							}
+						]
+					}
+				];
+			};
+		}
+	},
+	watch: {
+		faList: {
+			immediate: true,
+			handler(val) {				
+				if (this.$u.test.array(val)) {
+					
+					if (val.length > 0 && this.$u.test.object(val[0])) {
+						this.list = JSON.parse(JSON.stringify(val));
+					} else {
+						this.list = [];
+						for (let i in val) {
+							this.list.push({
+								name: val[i],
+								id: i
+							});
+						}
+					}
+				} else if (this.$u.test.object(val)) {
+					this.list = [];
+					for (let i in val) {
+						this.list.push({
+							name: val[i],
+							id: i
+						});
+					}
+				}
+				if (this.checkeType == 'selects') {
+					this.list.forEach((item, index) => {
+						this.$set(this.list[index], 'checked', false);
+					});
+				}
+				// 单选的默认值
+				if (this.showValue && this.checkeType == 'select') {
+					this.list.forEach((item, index) => {
+						if (item[this.keyField] == this.showValue) {
+							this.radio_value = this.showValue;
+							this.lists_lable = item[this.showField];
+						}
+					});
+				}
+				// 多选的默认值
+				if (this.showValue && this.checkeType == 'selects') {
+					let arr = this.showValue.split(',');
+					let lables = [];
+					this.list.forEach((item, index) => {
+						arr.forEach(id => {
+							if (item[this.keyField] == id) {
+								this.$set(this.list[index], 'checked', !this.list[index].checked);
+								lables.push(item[this.showField]);
+							}
+						});
+					});
+					this.lists_lable = lables.join(',');
+				}				
+			}
+			
+		},
+		//显示高度
+		show(newValue, oldValue) {
+			if (newValue) {
+				this.show = true;
+				this.$nextTick(() => {
+					setTimeout(() => {
+						uni.createSelectorQuery()
+							.in(this)
+							.select('.fa-scroll')
+							.boundingClientRect(rect => {
+								console.log(rect);
+								if (rect) {
+									this.scrollHg = rect.height;
+								}
+							})
+							.exec();
+					}, 100); //在百度直接获取不到,需要延时
+				});
+			} else {
+				this.sendChange();
+			}
+		}
+	},
+	data() {
+		return {
+			show: false,
+			radio_value: '',
+			checkbox_value: '',
+			scrollHg: 200,
+			lists_lable: '',
+			list: []
+		};
+	},
+	methods: {
+		close() {
+			this.show = false;
+		},
+		checkSelct(e){
+			console.log(e);
+			console.log(this.list)
+			for(var i=0;i<this.list.length;i++){
+				if(this.list[i].id==e){
+					this.selectCell(i);
+					break;
+				}
+			}
+		},
+		selectCell(index) {
+			console.log(index)
+			if (this.list[index].disabled == true) {
+				return;
+			}
+			
+			if (this.checkeType == 'selects') {
+				this.$set(this.list[index], 'checked', !this.list[index].checked);
+			} else {
+				//单选值确定
+				
+				this.radio_value = this.list[index][this.keyField] || '';
+				this.lists_lable = this.list[index][this.showField] || '';
+				this.$emit('input', this.radio_value);				
+				this.close();
+				setTimeout(() => {
+					this.dispatch('u-form-item', 'on-form-blur', this.radio_value);
+				}, 50);
+			}
+		},
+		//多选的确定
+		confirm() {
+			let lable = [];
+			let ids = [];
+			this.list.forEach(item => {
+				if (item.checked) {
+					lable.push(item.name);
+					ids.push(item.id);
+				}
+			});
+			this.lists_lable = lable.join(',');
+			this.checkbox_value = ids.join(',');
+			this.$emit('input', this.checkbox_value);
+			setTimeout(() => {
+				this.dispatch('u-form-item', 'on-form-blur', this.checkbox_value);
+			}, 50);
+			this.close();
+		},
+		//全选
+		allSelect() {
+			this.list.map(item => {
+				item.checked = true;
+			});
+		},
+		//清空
+		clearAll() {
+			this.list.map(item => {
+				item.checked = false;
+			});
+		},
+		//派发事件
+		sendChange() {
+			setTimeout(() => {
+				if (this.checkeType == 'select') {
+					this.dispatch('u-form-item', 'on-form-change', this.radio_value);
+				} else {
+					this.dispatch('u-form-item', 'on-form-change', this.checkbox_value);
+				}
+			}, 50);
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.selects {
+	width: 100%;
+}
+.richColor {
+	color: #909399;
+}
+.u-flex-column {
+	flex-direction: column;
+	height: 100%;
+	.fa-column {
+		width: 100%;
+	}
+}
+</style>

+ 45 - 0
components/fa-switch/fa-switch.vue

@@ -0,0 +1,45 @@
+<template>
+	<view><u-switch v-model="switchCheck" :active-color="theme.bgColor" @change="switchChange"></u-switch></view>
+</template>
+
+<script>
+import Emitter from '@/uni_modules/uview-ui/libs/util/emitter.js';
+export default {
+	name: 'fa-switch',
+	mixins: [Emitter],
+	props: {
+		value: {
+			type: [String, Number],
+			default: 0
+		},
+		defvalue: {
+			type: [String, Number],
+			default: 0
+		}
+	},
+	watch: {
+		defvalue: {
+			immediate: true,
+			handler(newValue, oldValue) {
+				this.switchCheck = newValue == 1;
+			}
+		}
+	},
+	data() {
+		return {
+			switchCheck: false
+		};
+	},
+	methods: {
+		switchChange(e) {
+			let value = e ? '1' : '0';
+			this.$emit('input', value);
+			setTimeout(() => {
+				this.dispatch('u-form-item', 'on-form-blur', value);
+			}, 50);
+		}
+	}
+};
+</script>
+
+<style lang="scss"></style>

+ 224 - 0
components/fa-upload-file/fa-upload-file.vue

@@ -0,0 +1,224 @@
+<template>
+	<view>
+		<!-- #ifdef APP-PLUS -->
+		<fa-file ref="faFile" @up-success="onAppSuccess"></fa-file>
+		<!-- #endif -->
+		<view class="u-flex u-flex-wrap" v-if="isDom">
+			<view class="u-m-r-20 u-m-b-20" v-for="(item, index) in fileList" :key="index">
+				<view class="fa-file fa-flex u-row-right">
+					<view class="u-delete-icon" @click="delfile(index)"><u-icon name="close" color="#ffffff" size="20"></u-icon></view>
+					<text class="u-tips-color u-m-b-15" v-text="getFileType(item)"></text>
+				</view>
+			</view>
+			<view class="u-m-r-20 u-m-b-20" v-if="(fileType == 'many' && fileList.length >= 1) || fileList.length == 0">
+				<view class="fa-file fa-flex u-row-center u-col-center u-p-t-10" @click="onUpload">
+					<u-icon name="plus" size="40" color="#606266"></u-icon>
+					<view class="select-color">选择文件</view>
+				</view>
+			</view>
+		</view>
+		<view ref="input" class="input" style="display: none;"></view>
+	</view>
+</template>
+
+<script>
+import Emitter from '@/uni_modules/uview-ui/libs/util/emitter.js';
+export default {
+	name: 'fa-upload-file',
+	mixins: [Emitter],
+	props: {
+		value:{
+			type:String,
+			default:''
+		},
+		fileType: {
+			type: String,
+			default: 'single'
+		},
+		//页面展示
+		isDom:{
+			type:Boolean,
+			default:false
+		},
+		showValue:{
+			type:Array,
+			default(){
+				return []
+			}
+		}
+	},
+	watch: {
+		fileList:{
+			// immediate: true,
+			deep:true,
+			handler:function(val){
+				let value = val.join(',')
+				this.$emit('input', value);
+				setTimeout(() => {
+					this.dispatch('u-form-item', 'on-form-blur', value);
+				}, 50);
+			}
+		},
+		showValue:{
+			immediate:true,
+			handler:function(newValue){
+				if(newValue.length){
+					this.fileList = newValue;
+				}
+			}
+		}
+	},
+	computed:{
+		getFileType(){
+			return item=>{
+				let url = item.split('.');
+				return '.'+url[1];
+			}
+		}
+	},
+	data() {
+		return {
+			fileList: [],
+			vuex_token:'',
+			vuex_user:{}
+		};
+	},
+	mounted() {
+		this.vuex_token=this.$db.get('token');
+		this.vuex_user=this.$db.get('user');
+		// #ifdef H5
+		var input = document.createElement('input')
+		input.type = 'file'
+		input.onchange = (event) => {
+			 this.$api.goUpload({
+				 file:event.target.files[0]
+			 }).then(res=>{
+			 	this.onSuccess(res)
+			 })
+		}
+		this.$refs.input.$el.appendChild(input)
+		// #endif
+		
+	},
+	methods: {
+		/* 上传 */
+		onUpload() {
+			// #ifdef MP-WEIXIN
+				this.wxChooseFile();
+			// #endif
+			// #ifdef H5
+				this.h5ChooseFile()
+			// #endif
+			// #ifdef APP-PLUS
+				var formData = {};
+				let isObj = this.$u.test.object(this.vuex_config.upload.multipart);
+				if (isObj) {
+					formData = this.vuex_config.upload.multipart;
+				}
+				this.$refs.faFile.upload({
+					// nvue页面使用时请查阅nvue获取当前webview的api,当前示例为vue窗口
+					currentWebview: this.$mp.page.$getAppWebview(),
+					//调试时ios有跨域,需要后端开启跨域并且接口地址不要使用http://localhost/
+					url: this.vuex_config.upload.uploadurl,
+					//默认file,上传文件的key
+					name: 'file',
+					header: {
+						token: this.vuex_token || '',
+						uid: this.vuex_user.id || 0
+					},
+					formData: formData
+					//...其他参数
+				});
+			// #endif
+		},
+		//移除文件
+		delfile(index) {
+			this.fileList.splice(index,1);
+		},
+		// #ifdef MP-WEIXIN
+			wxChooseFile() {
+				wx.chooseMessageFile({
+					count: 1,
+					type: 'file',
+					success: ({tempFiles}) => {
+						let [{path:filePath,name:fileName}] = tempFiles;
+						this.$api.goUpload({filePath:filePath}).then(res=>{
+							this.onSuccess(res)
+						})
+					},
+					fail:function(err){
+						console.log(err)
+					}
+				})
+			},
+		// #endif
+		// #ifdef H5
+			h5ChooseFile(){
+				this.$refs.input.$el.firstChild.click();
+			},
+		// #endif
+		// #ifndef APP-PLUS
+			onSuccess(res) {
+				this.$u.toast(res.msg)
+				if(res.code){
+					if(this.isDom){
+						this.fileList.push(res.data.url);
+					}else{
+						this.$emit('success',res.data.url);
+					}
+				}
+			}
+		// #endif
+		// #ifdef APP-PLUS
+			onAppSuccess(res){
+				if(this.$u.test.jsonString(res.data)){
+					res = JSON.parse(res.data);
+					this.$common.errorToShow(res.msg)
+					if(res.code){
+						if(this.isDom){
+							this.fileList.push(res.data.url);
+						}else{
+							this.$emit('success',res.data.url);
+						}
+					}
+				}else{
+					this.$common.errorToShow('上传失败!');
+				}
+			}
+		// #endif
+	}
+};
+</script>
+
+<style lang="scss">
+.fa-file {
+	background-color: #f4f5f6;
+	width: 150rpx;
+	height: 150rpx;
+	border-radius: 10rpx;
+	.select-color {
+		color: #606266;
+	}
+	text {
+		font-size: 40rpx;
+	}
+}
+.fa-flex {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	position: relative;
+	.u-delete-icon {
+		background-color: #fa3534;
+		width: 45rpx;
+		height: 45rpx;
+		border-radius: 100px;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		position: absolute;
+		top: 15rpx;
+		right: 15rpx;
+	}
+}
+</style>

+ 97 - 0
components/fa-upload-image/fa-upload-image.vue

@@ -0,0 +1,97 @@
+<template>
+	<view>
+		<u-upload :fileList="fileList" @afterRead="afterRead" @delete="deletePic" multiple :maxCount="imgType == 'single' ? 1 : 99" :previewFullImage="true"></u-upload>
+	</view>
+</template>
+
+<script>
+// import Emitter from '@/uni_modules/uview-ui/libs/util/emitter.js';
+export default {
+	name: 'fa-upload-image',
+	// mixins: [Emitter],
+	props: {
+		value: {
+			type: String,
+			default: ''
+		},
+		imgType: {
+			type: String,
+			default: 'single'
+		},
+		fileList: {
+			type: Array,
+			default() {
+				return [];
+			}
+		}
+	},
+	created() {},
+	data() {
+		return {
+			header: {},
+			formdata: {},
+			vuex_token: '',
+			vuex_user: {}
+		};
+	},
+	methods: {
+		// 删除图片
+		deletePic(event) {
+			this.fileList.splice(event.index, 1);
+		},
+		// 新增图片
+		async afterRead(event) {
+			// 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
+			console.log(event);
+			let lists = [].concat(event.file);
+			let fileListLen = this.fileList.length;
+			lists.map((item) => {
+				this.fileList.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[fileListLen];
+				this.fileList.splice(
+					fileListLen,
+					1,
+					Object.assign(item, {
+						status: 'success',
+						message: '',
+						url: result
+					})
+				);
+				fileListLen++;
+			}
+			console.log(this.fileList);
+		},
+		uploadFilePromise(url) {
+			return new Promise((resolve, reject) => {
+				let userToken = '';
+				let auth = this.$db.get('auth');
+				userToken = auth.token;
+
+				let a = uni.uploadFile({
+					url: this.$config.baseUrl + 'api/common/upload?token=' + userToken, // 仅为示例,非真实的接口地址
+					filePath: url,
+					name: 'file',
+
+					formData: {},
+					success: (res) => {
+						setTimeout(() => {
+							console.log(res);
+							resolve(JSON.parse(res.data).data.fullurl);
+						}, 1000);
+					}
+				});
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss"></style>

+ 203 - 0
components/load-more/load-more.vue

@@ -0,0 +1,203 @@
+<template>
+	<view class="load-more" v-if="loadingType !== -1" :style="{ marginTop: marginTop + 'px' }">
+		<view class="loading-img" v-show="loadingType === 1 && showImage">
+			<view class="load1">
+				<view :style="{ background: color }"></view>
+				<view :style="{ background: color }"></view>
+				<view :style="{ background: color }"></view>
+				<view :style="{ background: color }"></view>
+			</view>
+			<view class="load2">
+				<view :style="{ background: color }"></view>
+				<view :style="{ background: color }"></view>
+				<view :style="{ background: color }"></view>
+				<view :style="{ background: color }"></view>
+			</view>
+			<view class="load3">
+				<view :style="{ background: color }"></view>
+				<view :style="{ background: color }"></view>
+				<view :style="{ background: color }"></view>
+				<view :style="{ background: color }"></view>
+			</view>
+		</view>
+		<text class="loading-text" :style="{ color: color }">
+			{{ loadingText ? loadingText : loadingType === 0 ? loadingTexts.down : loadingType === 1 ? loadingTexts.refresh : loadingType === 2 ? loadingTexts.noMore : loadingTexts.wrong }}
+		</text>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'load-more',
+	props: {
+		// 上拉的状态:0-loading前;1-loading中;2-没有更多了;3-没有更多了
+		loadingType: {
+			type: Number,
+			default: -1
+		},
+		loadingText: {
+			type: String,
+			default: ''
+		},
+		loadingTexts: {
+			type: Object,
+			default: () => {
+				return {
+					down: '上拉显示更多',
+					refresh: '正在加载...',
+					noMore: '没有更多数据了',
+					wrong: '没有更多数据了!'
+				};
+			}
+		},
+		showImage: {
+			type: Boolean,
+			default: true
+		},
+		color: {
+			type: String,
+			default: '#777777'
+		},
+		top: {
+			type: Number,
+			default: 20
+		}
+	},
+	data() {
+		return {
+			marginTop: 0
+		};
+	},
+	created() {
+		this.marginTop = this.$logic.rpx2px(this.top);
+	},
+};
+</script>
+
+<style>
+.load-more {
+	display: flex;
+	flex-direction: row;
+	height: 80upx;
+	align-items: center;
+	justify-content: center;
+}
+.loading-img {
+	height: 48rpx;
+	width: 48rpx;
+	margin-right: 20rpx;
+}
+.loading-text {
+	font-size: 30rpx;
+	color: #777777;
+}
+
+.loading-img > view {
+	position: absolute;
+}
+.load1,
+.load2,
+.load3 {
+	height: 50rpx;
+	width: 50rpx;
+}
+.load2 {
+	transform: rotate(30deg);
+}
+
+.load3 {
+	transform: rotate(60deg);
+}
+
+.loading-img > view view {
+	width: 12rpx;
+	height: 4rpx;
+	border-top-left-radius: 1px;
+	border-bottom-left-radius: 1px;
+	background: #777;
+	position: absolute;
+	opacity: 0.2;
+	transform-origin: 50%;
+	-webkit-animation: load 1.56s ease infinite;
+}
+
+.loading-img > view view:nth-child(1) {
+	transform: rotate(90deg);
+	top: 2px;
+	left: 9px;
+}
+
+.loading-img > view view:nth-child(2) {
+	-webkit-transform: rotate(180deg);
+	top: 11px;
+	right: 0px;
+}
+
+.loading-img > view view:nth-child(3) {
+	transform: rotate(270deg);
+	bottom: 4rpx;
+	left: 18rpx;
+}
+
+.loading-img > view view:nth-child(4) {
+	top: 22rpx;
+	left: 0px;
+}
+
+.load1 view:nth-child(1) {
+	animation-delay: 0s;
+}
+
+.load2 view:nth-child(1) {
+	animation-delay: 0.13s;
+}
+
+.load3 view:nth-child(1) {
+	animation-delay: 0.26s;
+}
+
+.load1 view:nth-child(2) {
+	animation-delay: 0.39s;
+}
+
+.load2 view:nth-child(2) {
+	animation-delay: 0.52s;
+}
+
+.load3 view:nth-child(2) {
+	animation-delay: 0.65s;
+}
+
+.load1 view:nth-child(3) {
+	animation-delay: 0.78s;
+}
+
+.load2 view:nth-child(3) {
+	animation-delay: 0.91s;
+}
+
+.load3 view:nth-child(3) {
+	animation-delay: 1.04s;
+}
+
+.load1 view:nth-child(4) {
+	animation-delay: 1.17s;
+}
+
+.load2 view:nth-child(4) {
+	animation-delay: 1.3s;
+}
+
+.load3 view:nth-child(4) {
+	animation-delay: 1.43s;
+}
+
+@-webkit-keyframes load {
+	0% {
+		opacity: 1;
+	}
+	100% {
+		opacity: 0.2;
+	}
+}
+</style>

+ 99 - 0
components/mapComponent/mapComponent.vue

@@ -0,0 +1,99 @@
+<template>
+	<view class="box">
+		<map
+			id="map"
+			@markertap="markertap"
+			:enable-overlooking="true"
+			enable-3D="true"
+			:scale="scale"
+			:style="{ width: '100%', height: height + 'rpx' }"
+			:markers="markers"
+			:latitude="latitudeAndLongitude.latitude"
+			:longitude="latitudeAndLongitude.longitude"
+			:show-location="latitudeAndLongitude.anchorPoint"
+		></map>
+	</view>
+	<!-- :show-location="true"定位点 -->
+</template>
+
+<script>
+export default {
+	name: 'mapComponent',
+	props: {
+		height: {
+			type: [Number, String],
+			default: 360
+		},
+		latitudeAndLongitude: {
+			type: Object,
+			default: {}
+		},
+		introduceShow: {
+			type: Boolean,
+			default: ''
+		},
+		markers: {
+			type: Array,
+			default: []
+		}
+	},
+	data() {
+		return {
+			latitude: 24.531,
+			longitude: 118.1918,
+			scale: 12 //缩放级别,
+		};
+	},
+	mounted() {
+		// console.log(this.markers, '子组件地图');
+	},
+	methods: {
+		// 点击标记点时触发
+		markertap(e) {
+			if (this.introduceShow == false) {
+				this.$emit('subComponent', true, e.markerId);
+				// console.log(e, '标记点11');
+			} else {
+				// console.log(e, '标记点222');
+				uni.navigateTo({
+					url: `/index_fenbao/GuanLi/XiangQing?id=${e.markerId}`
+				});
+			}
+		},
+		// 获取位置
+		vicinityBtn() {
+			const that = this;
+			uni.getLocation({
+				type: 'gcj02 ',
+				geocode: true,
+				success: function (res) {
+					// console.log(res, 'resres');
+					// console.log(that.latitudeAndLongitude, 'this.latitudeAndLongitude');
+					that.latitudeAndLongitude.longitude = res.longitude;
+					that.latitudeAndLongitude.latitude = res.latitude;
+					that.latitudeAndLongitude.anchorPoint = true;
+					that.$common.successToShow('定位成功');
+					that.regionTab();
+				}
+			});
+		},
+		// 定位点
+		regionTab() {
+			uni.createMapContext('map', this).moveToLocation({
+				longitude: this.latitudeAndLongitude.longitude,
+				latitude: this.latitudeAndLongitude.latitude
+			});
+		}
+	}
+};
+</script>
+
+<style>
+.map_box {
+	width: 625rpx;
+	height: 320rpx;
+	margin: auto;
+	margin-top: 40rpx;
+	overflow: hidden;
+}
+</style>

+ 153 - 0
components/quick-cart/quick-cart.vue

@@ -0,0 +1,153 @@
+<template>
+	<view class="component" v-if="show">
+		<view class="sub-block">
+			<view class="b-main" @click="onClose()">
+				<view class="b-info">
+					<view class="b-count" @click.stop="onJumpCart()">
+						<view class="iconfont icon-user-cart"></view>
+						<view class="s-num">{{ cartCount }}</view>
+					</view>
+					<view class="b-price">¥{{ totalPrice }}</view>
+				</view>
+				<view class="b-action" @click.stop="onPreview()">
+					<view class="s-submit">去结算</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'quick-cart',
+	props: {
+		show: {
+			type: Boolean,
+			default: false
+		}
+	},
+	data() {
+		return {};
+	},
+	computed: {
+		cartCount() {
+			return this.$store.getters.cartCount;
+		},
+		totalPrice() {
+			const cartList = this.$store.getters.cartList;
+			let totalPrice = 0;
+			for (var i = 0; i < cartList.length; i++) {
+				totalPrice += parseFloat(cartList[i].goods_price);
+			}
+			return totalPrice;
+		}
+	},
+	methods: {
+		onJumpCart(e) {
+			console.log('onJumpCart');
+			uni.navigateTo({
+				url: '/pages/order/cart'
+			});
+		},
+		onClose(e) {
+			console.log('onClose');
+			this.$emit('close');
+		},
+		onPreview() {
+			console.log('onPreview');
+			const cartList = this.$store.getters.cartList;
+			let goodsData = [];
+			for (var i = 0; i < cartList.length; i++) {
+				let cartItem = cartList[i];
+				goodsData.push({
+					cart_id: cartItem.id,
+					goods_type: cartItem.goods_type,
+					goods_id: cartItem.goods_id
+				});
+			}
+			uni.navigateTo({
+				url: '/pages/order/preview?goods_data=' + JSON.stringify(goodsData)
+			});
+		}
+	}
+};
+</script>
+
+<style>
+.sub-block {
+	position: fixed;
+	left: 30upx;
+	bottom: 150upx;
+	z-index: 100;
+	width: 690upx;
+}
+
+.sub-block image {
+	width: 100%;
+}
+
+.b-main {
+	width: 690upx;
+	height: 100upx;
+	background: #fff;
+	border: 2upx solid #eee;
+	border-radius: 50upx;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+}
+
+.b-info {
+	padding: 0 20upx;
+	display: flex;
+	align-items: center;
+}
+
+.b-info .b-count {
+	position: relative;
+}
+
+.b-info .b-count .iconfont {
+	font-size: 50upx;
+	width: 60upx;
+	height: 60upx;
+	color: #666;
+}
+
+.b-info .b-count .s-num {
+	position: absolute;
+	right: -10upx;
+	top: -30upx;
+	font-size: 24upx;
+	background: #da5650;
+	color: #fff;
+	width: 40upx;
+	height: 40upx;
+	line-height: 40upx;
+	text-align: center;
+	border-radius: 20upx;
+}
+
+.b-info .b-price {
+	margin-left: 20upx;
+	font-size: 28upx;
+	font-weight: bold;
+	color: #da5650;
+}
+
+.b-action {
+	padding: 0 20upx;
+	display: flex;
+	align-items: center;
+}
+
+.b-action .s-submit {
+	padding: 0 50upx;
+	height: 70upx;
+	line-height: 70upx;
+	font-size: 26upx;
+	color: #fff;
+	background: #da5650;
+	border-radius: 35upx;
+}
+</style>

+ 170 - 0
components/tki-tree/style.css

@@ -0,0 +1,170 @@
+.tki-tree-mask {
+	position: fixed;
+	top: 0rpx;
+	right: 0rpx;
+	bottom: 0rpx;
+	left: 0rpx;
+	z-index: 9998;
+	background-color: rgba(0, 0, 0, 0.6);
+	opacity: 0;
+	transition: all 0.3s ease;
+	visibility: hidden;
+}
+
+.tki-tree-mask.show {
+	visibility: visible;
+	opacity: 1;
+}
+
+.tki-tree-cnt {
+	position: fixed;
+	top: 0rpx;
+	right: 0rpx;
+	bottom: 0rpx;
+	left: 0rpx;
+	z-index: 9999;
+	top: 360rpx;
+	transition: all 0.3s ease;
+	transform: translateY(100%);
+}
+
+.tki-tree-cnt.show {
+	transform: translateY(0);
+}
+
+.tki-tree-bar {
+	background-color: #fff;
+	height: 72rpx;
+	padding-left: 20rpx;
+	padding-right: 20rpx;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	box-sizing: border-box;
+	border-bottom-width: 1rpx !important;
+	border-bottom-style: solid;
+	border-bottom-color: #f5f5f5;
+	font-size: 32rpx;
+	color: #757575;
+	line-height: 1;
+}
+
+.tki-tree-bar-confirm {
+	color: #07bb07;
+}
+
+.tki-tree-view {
+	position: absolute;
+	top: 0rpx;
+	right: 0rpx;
+	bottom: 0rpx;
+	left: 0rpx;
+	top: 72rpx;
+	background-color: #fff;
+	padding-top: 20rpx;
+	padding-right: 20rpx;
+	padding-bottom: 20rpx;
+	padding-left: 20rpx;
+}
+
+.tki-tree-view-sc {
+	height: 100%;
+	overflow: hidden;
+}
+
+.tki-tree-item {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	font-size: 26rpx;
+	color: #757575;
+	line-height: 1;
+	height: 0;
+	opacity: 0;
+	transition: 0.2s;
+	position: relative;
+	overflow: hidden;
+}
+
+.tki-tree-item.show {
+	height: 80rpx;
+	opacity: 1;
+}
+
+.tki-tree-item.showchild:before {
+	transform: rotate(90deg);
+}
+
+.tki-tree-item.last:before {
+	opacity: 0;
+}
+
+.tki-tree-icon {
+	width: 26rpx;
+	height: 26rpx;
+	margin-right: 8rpx;
+}
+
+.tki-tree-label {
+	flex: 1;
+	display: flex;
+	align-items: center;
+	height: 100%;
+	line-height: 1.2;
+}
+
+.tki-tree-check {
+	width: 40px;
+	height: 40px;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+}
+
+.tki-tree-check-yes,
+.tki-tree-check-no {
+	width: 20px;
+	height: 20px;
+	border-top-left-radius: 20%;
+	border-top-right-radius: 20%;
+	border-bottom-right-radius: 20%;
+	border-bottom-left-radius: 20%;
+	border-top-width: 2rpx;
+	border-left-width: 2rpx;
+	border-bottom-width: 2rpx;
+	border-right-width: 2rpx;
+	border-style: solid;
+	border-color: #07bb07;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	box-sizing: border-box;
+}
+
+.tki-tree-check-yes-b {
+	width: 12px;
+	height: 12px;
+	border-top-left-radius: 20%;
+	border-top-right-radius: 20%;
+	border-bottom-right-radius: 20%;
+	border-bottom-left-radius: 20%;
+	background-color: #07bb07;
+}
+
+.tki-tree-check .radio {
+	border-top-left-radius: 50%;
+	border-top-right-radius: 50%;
+	border-bottom-right-radius: 50%;
+	border-bottom-left-radius: 50%;
+}
+
+.tki-tree-check .radio .tki-tree-check-yes-b {
+	border-top-left-radius: 50%;
+	border-top-right-radius: 50%;
+	border-bottom-right-radius: 50%;
+	border-bottom-left-radius: 50%;
+}
+
+.hover-c {
+	opacity: 0.6;
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 302 - 0
components/tki-tree/tki-tree.vue


+ 557 - 0
config/api.js

@@ -0,0 +1,557 @@
+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', 'addCart',
+	'previewOrder', 'getWuyuanUser', 'archives_post', 'get_channel_fields', 'get_channel', 'lists', 'details',
+	'logOffVolunteer', 'getCrProperty', 'joinTeam', 'getTeamMember', 'getPatrolTask', 'claimCr',
+	'getPatrolLog', 'claimCrList', 'claimCrDetails', 'editMainBodyUser', , 'contribute',
+	'getPatrolTaskDetails', 'getContentDetail', 'examineVolunteer', 'userManageRegionCrAuth', 'applyVolunteer',
+	'getPhoneNumber', 'editApplyVolunteer', 'mobileBindVolunteer', 'getTeamDetails', 'examineTask', 'removeMemberm',
+	'getScoreLog', 'rankingList', 'getMainBodyColumnContentList', 'regionData', 'cityData', 'checkIn',
+	'getMainBodyUser', 'getUserContribute', 'getUserHonor', 'submitTask', 'exportPatrolRecord'
+];
+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, 'content/main_body_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, 'content/main_body_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 getColumnList = (data, callback) => post('getColumnList', data, callback,
+	'content/main_body_column');
+// 内容详情
+export const getContentDetail = (data, callback) => post('getContentDetail', data, callback,
+	'content/content');
+// 模型内容列表
+export const getContentList = (data, callback) => post('getContentList', data, callback,
+	'content/content');
+// 模型主体栏目的内容列表
+export const getMainBodyColumnContentList = (data, callback) => post('getMainBodyColumnContentList', data, callback,
+	'content/content');
+// 培训分类列表
+export const getCategoryList = (data, callback) => post('getCategoryList', data, callback, '',
+	'addons/yuneducation/course/getCategoryList');
+// 学院底部课程
+export const getHomeData = (data, callback) => post('getHomeData', data, callback, '',
+	'addons/yuneducation/page/getHomeData');
+// 课程界面数据
+export const getCourseList = (data, callback) => post('getCourseList', data, callback, '',
+	'addons/yuneducation/course/getCourseList');
+// 课程详情
+export const getCourseDetail = (data, callback) => post('getCourseDetail', data, callback, '',
+	'addons/yuneducation/course/getCourseDetail');
+// 获取课程考核
+export const getCourseExamine = (data, callback) => post('getCourseExamine', data, callback, '',
+	'addons/yuneducation/course/getCourseExamine');
+// 课程开始学习
+export const startLearn = (data, callback) => post('startLearn', data, callback, '',
+	'addons/yuneducation/course/startLearn');
+// 加入购物车
+export const addCart = (data, callback) => post('addCart', data, callback, '',
+	'addons/yuneducation/order/addCart');
+// 预览订单
+export const previewOrder = (data, callback) => post('previewOrder', data, callback, '',
+	'addons/yuneducation/order/previewOrder');
+// 提交订单
+export const submitOrder = (data, callback) => post('submitOrder', data, callback, '',
+	'addons/yuneducation/order/submitOrder');
+// 移除购物车
+export const removeCart = (data, callback) => post('removeCart', data, callback, '',
+	'addons/yuneducation/order/removeCart');
+// 讲师详情
+export const getTeacherDetail = (data, callback) => post('getTeacherDetail', data, callback, '',
+	'addons/yuneducation/teacher/getTeacherDetail');
+// 获取培训列表
+export const getScheduleList = (data, callback) => post('getScheduleList', data, callback, '',
+	'addons/yuneducation/schedule/getScheduleList');
+// 获取培训详情
+
+export const getScheduleDetail = (data, callback) => post('getScheduleDetail', data, callback, '',
+	'addons/yuneducation/schedule/getScheduleDetail');
+// 培训开始学习
+export const scheduleStartLearn = (data, callback) => post('startLearn', data, callback, '',
+	'addons/yuneducation/schedule/startLearn');
+// 获取我的课程列表
+export const getMineCourseList = (data, callback) => post('getMineCourseList', data, callback, '',
+	'addons/yuneducation/course/getMineCourseList');
+// 试播
+// export const getContentDetail = (data, callback) => post('getContentDetail', data, callback, '',
+// 	'addons/yuneducation/course/getContentDetail');
+//获取栏目
+export const getChannel = async (params = {}) => await syncpost('get_channel', '/addons/cms/api.archives/get_channel',
+	params);
+
+//我发的文章
+export const myArchives = async (params = {}) => await syncpost('my', '/addons/cms/api.archives/my', params);
+// 文物管理相关
+//场馆详情页获取管理员权限
+export const adminCrAuth = (data, callback) => post('adminCrAuth', data, callback, 'wuyuan/admin');
+//取文巡查记录
+export const getPatrolLog = (data, callback) => post('getPatrolLog', data, callback, 'wuyuan/volunteer');
+//取文物详情
+export const getCulturalDetails = (data, callback) => get('api/wuyuan/cultural_relic/details' + queryParams(data, true),
+	callback);
+// 文物管理相关end
+//取轮播图
+export const getBannerList = (data, callback) => get('/api/wuyuan/swiper_icon/lists' + queryParams(data, true),
+	callback);
+// //人物列表 传 type参数类型:inheritor=非遗传承人,history=历史人物,folklore=民俗学者,specialist=文史专家
+// export const getPeopleList = (data, callback) => get('api/wuyuan/biographies/lists' + queryParams(data, true), callback);
+// //人物详情
+// export const getPeopleDetails = (data, callback) => get('api/wuyuan/biographies/details' + queryParams(data, true), callback);
+// 获取用户信息
+export const getMainBodyUser = (data, callback) => post('getMainBodyUser', data, callback,
+	'content/main_body_user/getMainBodyUser');
+//修改头像
+export const editMainBodyUser = (data, callback) => post('editMainBodyUser', data, callback,
+	'content/main_body_user');
+//取任务列表
+export const getTaskList = (data, callback) => post('lists', data, callback, 'wuyuan/task');
+
+//取法律文章列表
+export const getArchives = async (params = {}) => await syncget('https://huli-app.wenlvti.net/addons/cms/api', params);
+
+// 积分信息
+export const getWuyuanUser = (data, callback) => post('getWuyuanUser', data, callback, 'wuyuan/user');
+//取文物分类 参数传type类型
+export const getCulturalRelicCategory = (data, callback) => get('api/content/category/getCategoryList' + queryParams(
+	data,
+	true), callback);
+// 投稿
+export const contribute = (data, callback) => post('contribute', data, callback, '',
+	'api/content/style/contribute');
+//取文物场景列表
+export const getCulturalRelic = (data, callback) => get('api/content/cultural_relic/lists' + queryParams(data, true),
+	callback);
+// 志愿报名
+export const applyVolunteer = (data, callback) => post('applyVolunteer', data, callback, '',
+	'api/volunteer/volunteer/applyVolunteer');
+// 志愿者列表
+export const lists = (data, callback) => post('lists', data, callback, '', 'api/volunteer/volunteer/lists');
+// 志愿者详情
+export const details = (data, callback) => post('details', data, callback, '', 'api/volunteer/volunteer/details');
+// 注销志愿者
+export const logOffVolunteer = (data, callback) => post('logOffVolunteer', data, callback, '',
+	'api/volunteer/volunteer/logOffVolunteer');
+// 志愿者巡查文物资产列表
+export const getCrProperty = (data, callback) => post('getCrProperty', data, callback, '',
+	'api/volunteer/patrol_property/getCrProperty');
+// 加入志愿者团队
+export const joinTeam = (data, callback) => post('joinTeam', data, callback, '',
+	'api/volunteer/team_member/joinTeam');
+// 获取团队成员列表
+export const getTeamMember = (data, callback) => post('getTeamMember', data, callback, '',
+	'api/volunteer/team/getTeamMember');
+// 移除成员
+export const removeMemberm = (data, callback) => post('removeMemberm', data, callback, '',
+	'api/volunteer/team_member/removeMember');
+// 获取志愿者任务列表
+export const getPatrolTask = (data, callback) => post('getPatrolTask', data, callback, '',
+	'api/volunteer/patrol_task/getPatrolTask');
+// 获取任务列详情
+export const getPatrolTaskDetails = (data, callback) => post('getPatrolTaskDetails', data, callback, '',
+	'api/volunteer/patrol_task/getPatrolTaskDetails');
+// 提交巡查任务
+export const submitTask = (data, callback) => post('submitTask', data, callback, '',
+	'api/volunteer/patrol_task/submitTask');
+// 计算志愿者与文物点的距离
+export const distanceCheck = (data, callback) => post('distanceCheck', data, callback, '',
+	'api/volunteer/patrol_task/distanceCheck');
+
+// 认领文物
+export const claimCr = (data, callback) => post('claimCr', data, callback, '',
+	'api/volunteer/claim_cr/claimCr');
+// 认领文物列表
+export const claimCrList = (data, callback) => post('claimCrList', data, callback, '',
+	'api/volunteer/claim_cr/claimCrList');
+// 认领文物详情暂未调用
+export const claimCrDetails = (data, callback) => post('claimCrDetails', data, callback, '',
+	'api/volunteer/claim_cr/claimCrDetails');
+// 最新公告
+export const getNotice = (data, callback) => post('getNotice', data, callback, '',
+	'api/system/notice/getNotice');
+// 区域
+export const getCategoryOnlyChildList = (data, callback) => post('getCategoryOnlyChildList', data, callback, '',
+	'api/content/category/getCategoryOnlyChildList');
+// 志愿者审核
+export const examineVolunteer = (data, callback) => post('examineVolunteer', data, callback, '',
+	'api/volunteer/volunteer/examineVolunteer');
+// 用户区域管理文物权限
+export const userManageRegionCrAuth = (data, callback) => post('userManageRegionCrAuth', data, callback, '',
+	'api/auth/user_manage_cr/userManageRegionCrAuth');
+// 修改驳回志愿者信息
+export const editApplyVolunteer = (data, callback) => post('editApplyVolunteer', data, callback, '',
+	'api/volunteer/volunteer/editApplyVolunteer');
+// 老志愿者手机号绑定志愿者
+export const getPhoneNumber = (data, callback) => post('getPhoneNumber', data, callback, '',
+	'api/volunteer/volunteer/getPhoneNumber');
+// 手动绑定志愿者手机号
+export const mobileBindVolunteer = (data, callback) => post('mobileBindVolunteer', data, callback, '',
+	'api/volunteer/volunteer/mobileBindVolunteer');
+// 提交任务审核
+export const examineTask = (data, callback) => post('examineTask', data, callback, '',
+	'api/volunteer/patrol_task/examineTask');
+// 获取用户积分列表
+export const getScoreLog = (data, callback) => post('getScoreLog', data, callback, '',
+	'api/content/main_body_user/getScoreLog');
+// 志愿者排行
+export const rankingList = (data, callback) => post('rankingList', data, callback, '',
+	'api/volunteer/volunteer/rankingList');
+// 活动列表
+export const activityLists = (data, callback) => get('api/activity/activity/lists' + queryParams(data, true),
+	callback);
+// 活动详情
+export const activityDetails = (data, callback) => get('api/activity/activity/details' + queryParams(data, true),
+	callback);
+// 志愿者排行详情
+export const rankingDetails = (data, callback) => get('api/volunteer/volunteer/rankingDetails' + queryParams(data,
+		true),
+	callback);
+// 区域数据统计
+export const regionData = (data, callback) => post('regionData', data, callback, '',
+	'api/volunteer/statistics/regionData');
+// 市级数据统计
+export const cityData = (data, callback) => post('cityData', data, callback, '',
+	'api/volunteer/statistics/cityData');
+// 新闻公告
+export const getNewsNotice = (data, callback) => post('getNewsNotice', data, callback, '',
+	'api/system/notice/getNewsNotice');
+// 打卡
+export const checkIn = (data, callback) => post('checkIn', data, callback, '',
+	'api/content/main_body_user/checkIn');
+// 活动报名
+export const activitySignup = (data, callback) => post('activitySignup', data, callback, '',
+	'api/activity/activity/activitySignup');
+// 我的投稿
+export const getUserContribute = (data, callback) => post('getUserContribute', data, callback, '',
+	'api/content/main_body_user/getUserContribute');
+// 我的荣誉
+export const getUserHonor = (data, callback) => post('getUserHonor', data, callback, '',
+	'api/content/main_body_user/getUserHonor');
+// 轮播
+export const getIndexBanner = (data, callback) => post('getIndexBanner', data, callback, '',
+	'api/content/banner_function/getIndexBanner');
+// 导出
+export const exportPatrolRecord = (data, callback) => post('exportPatrolRecord', data, callback, '',
+	'api/volunteer/patrol_task/exportPatrolRecord');

+ 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
+}

+ 18 - 0
config/config.js

@@ -0,0 +1,18 @@
+// // // 前端路径
+// export const baseUrl = 'http://smartct.com/'
+// // api调用目录
+// export const baseApiUrl = 'http://smartct.com/api/'
+// // cnd域名。没有就填写后端域名
+// export const cndUrl = 'http://smartct.com/'
+
+// // 前端路径
+export const baseUrl = 'https://meng.wenlvti.net/'
+// api调用目录
+export const baseApiUrl = 'https://meng.wenlvti.net/api/'
+// cnd域名。没有就填写后端域名
+// export const cndUrl = 'https://meng.wenlvti.net/'
+
+export const baseLogo = 'https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/logo.png'
+export const title = '文物管家'
+// 主题样式
+export var bgClass = 'bg-orange text-black'

+ 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
+}

+ 134 - 0
index_fenbao/GuanLi/GouJian.vue

@@ -0,0 +1,134 @@
+<template>
+	<view class="body">
+		<u-navbar :title="item.title + '介绍'" bgColor="rgba(255,255,255,0.3)" :placeholder="true" :autoBack="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<u-swiper :list="list" keyName="image" :height="300" :indicator="true"></u-swiper>
+		<view class="box">
+			<!-- 	<view style="color: #940000; font-size: 24rpx; margin-left: 24rpx; display: flex; align-items: center; margin-top: 40rpx">
+				<view style="font-weight: 700; font-size: 18px">构件介绍</view>
+			</view> -->
+
+			<!-- 			<view class="info">
+				<view style="width: 40%">创建年代: {{ item.age }}</view>
+
+				<view style="width: 40%">文物类型:{{ item.type }}</view>
+
+				<view style="width: 40%">文物等级:{{ item.level }}</view>
+
+				<view style="width: 40%">所属区域:{{ item.area }}</view>
+			</view> -->
+			<view style="color: #940000; font-size: 24rpx; margin-left: 24rpx; margin-top: 40rpx; margin-bottom: 30rpx">
+				<view style="font-weight: 700; font-size: 18px">构件介绍</view>
+			</view>
+
+			<view style="width: 92% margin: auto;">
+				<image style="margin-left: 50rpx; border-radius: 10rpx" :src="item.image"></image>
+			</view>
+			<view style="width: 92%; margin: auto; margin-top: 10rpx">
+				<u-parse :content="item.content"></u-parse>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that = this;
+export default {
+	data() {
+		return {
+			item: {},
+			list: []
+		};
+	},
+	onLoad(e) {
+		that = this;
+		this.getContentDetail(e.id);
+	},
+
+	methods: {
+		// 详情
+		getContentDetail(id) {
+			this.$api.getContentDetail(
+				{
+					main_body_id: 1,
+					id: id
+				},
+				function (res) {
+					// console.log(res, 666666666);
+					that.item = res.data;
+					that.list = Object.values(res.data.association_me_list);
+				}
+			);
+		}
+	}
+};
+</script>
+
+<style>
+.box .info {
+	display: flex;
+	justify-content: space-between;
+	flex-wrap: wrap;
+	margin: 20rpx;
+	margin-top: 8px;
+}
+
+.box .info > view {
+	margin-top: 10px;
+	font-size: 14px;
+	margin-left: 4px;
+	color: #940000;
+}
+
+.box .u-info {
+	margin: 20rpx;
+}
+
+.box .text {
+	margin: 20rpx;
+}
+
+.box {
+	height: auto;
+	width: 100%;
+	background-image: url('https://huli-app.wenlvti.net/app_static/wuyuan/static/shouhu/wyj-feiyi3-001.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	border-radius: 16px 16px 0 0;
+	position: relative;
+	top: -20px;
+	padding-top: 10rpx;
+}
+
+.body {
+	background-image: url('https://huli-app.wenlvti.net/app_static/wuyuan/static/wenwu/wyj-jiemian1-BJ.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	height: auto;
+}
+
+.boxs > view {
+	overflow: hidden;
+	width: 355px;
+	height: 160px;
+	border-radius: 5px;
+	margin-bottom: 30px;
+}
+
+.text-img-img {
+	display: flex;
+	overflow: none;
+	overflow-x: scroll;
+	margin-top: 5rpx;
+}
+
+.text-img-img > view {
+	color: #940000;
+	margin-right: 10px;
+}
+
+.text-img-info {
+	color: #940000;
+	display: flex;
+	justify-content: space-between;
+}
+</style>

+ 218 - 0
index_fenbao/GuanLi/GuanLi.vue

@@ -0,0 +1,218 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="文物管理平台" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view style="width: 90%; margin: auto">
+			<u-swiper
+				:list="swiperList"
+				imgMode="aspectFill"
+				bgColor="transparent"
+				keyName="image"
+				:height="160"
+				:indicator="true"
+				radius="5"
+				:autoplay="true"
+				:circular="true"
+				indicatorStyle="bottom: 10px"
+				indicatorMode="dot"
+				indicatorActiveColor="#fff"
+				indicatorInactiveColor="rgba(255, 255, 255, 0.35)"
+			></u-swiper>
+		</view>
+		<view class="" style="display: flex; justify-content: space-evenly; flex-wrap: wrap; width: 90%; margin: auto">
+			<view class="zj_box" @click="menuBtn(index)" v-for="(item, index) in menuList" :key="item.index">
+				<view>
+					<image style="width: 130rpx; height: 120rpx" :src="item.image"></image>
+				</view>
+				<view style="font-size: 30rpx; font-weight: 600">{{ item.title }}</view>
+			</view>
+			<!-- 个人按钮 -->
+			<view v-if="authority" class="zj_box" @click="$common.navigateTo('/index_fenbao/GuanLi/zhiYuanGeRen')">
+				<view>
+					<image style="width: 130rpx; height: 120rpx" src="https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/ht_menu5.png"></image>
+				</view>
+				<view style="font-size: 30rpx; font-weight: 600">个人记录</view>
+			</view>
+		</view>
+		<view class="map_tit" style="margin-left: 90rpx">
+			<image style="width: 211rpx; height: 52rpx" src="/static/img/right_img.png"></image>
+			<view class="">统计数据</view>
+			<image style="width: 211rpx; height: 52rpx" src="/static/img/left_img.png"></image>
+		</view>
+		<view class="tj_box">
+			<view class="btm_box">
+				<view style="font-size: 32rpx; font-weight: 600; margin-bottom: 20rpx">全市志愿者人数 (合计:{{ list.volunteerNum }})</view>
+				<view class="" style="display: flex; flex-wrap: wrap; justify-content: space-between; text-align: initial">
+					<view v-for="item in list.volunteerData" :key="item.index" style="">
+						<view style="width: 160rpx; margin-left: 20rpx">{{ item.title }}:{{ item.num }}</view>
+					</view>
+				</view>
+			</view>
+			<!-- 活跃人数 -->
+			<view class="btm_box">
+				<view style="font-size: 32rpx; font-weight: 600; margin-bottom: 20rpx">活跃人数</view>
+				<view class="" style="display: flex; flex-wrap: wrap; justify-content: space-between; text-align: initial">
+					<view v-for="item in list.activeData" :key="item.index" style="">
+						<view style="width: 160rpx; margin-left: 20rpx">{{ item.title }}:{{ item.num }}</view>
+					</view>
+				</view>
+			</view>
+			<!-- 文物 -->
+			<view class="btm_box">
+				<view style="font-size: 32rpx; font-weight: 600; margin-bottom: 20rpx">全市文物</view>
+				<view class="" style="display: flex; flex-wrap: wrap; justify-content: space-between; text-align: initial">
+					<view v-for="item in list.crData" :key="item.index" style="">
+						<view style="width: 160rpx; margin-left: 20rpx">{{ item.title }}:{{ item.num }}</view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			authority: false /* 管理页判断自己是否是志愿者 */,
+			id: '' /* 区域管理id */,
+			list: [] /* 数据统计 */,
+			swiperList: [],
+			menuList: [
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/ht_menu6.png',
+					title: '志愿者',
+					path: '/user_fenbao/houTai/zhiYuanZhe'
+				},
+				{
+					image: 'https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/ht_menu7.png',
+					title: '巡查管理',
+					path: '/user_fenbao/houTai/xunCha'
+				}
+			]
+		};
+	},
+	onLoad(o) {
+		that = this;
+		this.id = o.id;
+		this.cityData();
+		this.details();
+		this.loadGlobalFont();
+		this.getIndexBanner();
+	},
+	methods: {
+		// 轮播
+		getIndexBanner() {
+			this.$api.getIndexBanner({ main_body_id: 1 }, function (res) {
+				that.swiperList = res.data.filter((item) => {
+					return item.title == '文物管家';
+				});
+			});
+		},
+		// 判断是否为志愿者
+		details() {
+			this.$api.details({ main_body_id: 1 }, function (res) {
+				// console.log(res, '志愿者');
+				if (res.data != false && res.data.id != undefined) {
+					that.authority = true;
+				} else {
+					console.log(res, '非志愿者');
+				}
+			});
+		},
+		// 数据统计
+		cityData() {
+			this.$api.cityData({ main_body_id: 1 }, function (res) {
+				that.list = res.data;
+			});
+		},
+		menuBtn(i) {
+			uni.navigateTo({
+				url: this.menuList[i].path + '?id=' + this.id
+			});
+		},
+		// 加载字体
+		loadGlobalFont() {
+			try {
+				uni.loadFontFace({
+					family: 'MyGlobalFont', // 自定义字体名
+					source: 'url("https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/songTi.ttf")',
+					success() {
+						console.log('全局字体加载成功');
+					}
+				});
+			} catch (error) {
+				console.error('全局字体加载异常', error);
+			}
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xbg_2.png');
+	background-size: 100% 100%;
+	/* background-attachment: fixed; */
+	background-repeat: repeat-y;
+	min-height: 100%;
+	height: auto;
+}
+/deep/.u-swiper-indicator__wrapper__dot--active {
+	width: 5px !important;
+}
+.zj_box {
+	width: 150rpx;
+	/* height: 292rpx; */
+	text-align: center;
+	margin-top: 40rpx;
+}
+.tiwen {
+	width: 134rpx;
+	height: 52rpx;
+	text-align: center;
+	line-height: 52rpx;
+	font-size: 24rpx;
+	color: #ffffff;
+	margin-left: 32rpx;
+	margin-top: 10rpx;
+	background-color: #c46354;
+}
+
+.map_tit {
+	display: flex;
+	align-items: center;
+	margin-left: 60rpx;
+	margin-top: 40rpx;
+	font-size: 40rpx;
+	font-family: 'MyGlobalFont';
+	font-weight: 900;
+	line-height: 52rpx;
+	color: #444444;
+}
+.tj_box {
+	width: 90%;
+	margin: auto;
+	margin-top: 30rpx;
+	/* background-color: #fceece; */
+}
+.top_box {
+	display: flex;
+	margin-left: 260rpx;
+	justify-content: space-between;
+}
+.tit_box {
+	width: 160rpx;
+	height: 60rpx;
+	line-height: 60rpx;
+	text-align: center;
+	background-image: url('/static/img/fj_bg.png');
+	background-size: 100% 100%;
+}
+.btm_box {
+	text-align: center;
+	margin-top: 40rpx;
+}
+</style>

+ 314 - 0
index_fenbao/GuanLi/XiangQing.vue

@@ -0,0 +1,314 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" bgColor="rgba(255,255,255,0)"></u-navbar>
+		<view>
+			<u-swiper
+				@click="clickImg"
+				:list="imageList"
+				imgMode="aspectFill"
+				:height="200"
+				:indicator="true"
+				:autoplay="true"
+				:circular="true"
+				indicatorStyle="bottom: 25px"
+				indicatorMode="dot"
+				indicatorActiveColor="#fff"
+				indicatorInactiveColor="rgba(255, 255, 255, 0.35)"
+			></u-swiper>
+		</view>
+		<view class="box">
+			<view class="" style="display: flex; align-items: center; margin-left: 28rpx; margin-top: 30rpx">
+				<view style="color: #640000; margin-right: 20rpx; font-weight: 700; font-size: 36rpx">{{ list.title }}</view>
+			</view>
+			<view class="info">
+				<view style="width: 40%">始建年代: {{ list.age ? list.age : '暂无描述' }}</view>
+
+				<view style="width: 40%">文物类型:{{ list.cr_type_text ? list.cr_type_text : '暂无描述' }}</view>
+
+				<view style="width: 40%">文物等级:{{ list.level_text }}</view>
+
+				<view style="width: 40%">所属区域:{{ citySection }}</view>
+			</view>
+
+			<view style="margin-left: 24rpx; display: flex; align-items: center; margin-top: 40rpx">
+				<view style="display: flex; align-items: center">
+					<uni-icons type="location" size="22" color="#940000"></uni-icons>
+					<!-- <image style="width: 40rpx; height: 40rpx" src="/static/img/map-pin-5-line.png"></image> -->
+					<view class="item_tit">地点</view>
+				</view>
+			</view>
+			<view class="" @tap="openGuide(list.title, list.address, list.latitude, list.longitude)">
+				<view
+					class=""
+					style="
+						width: 95%;
+						margin: auto;
+						display: flex;
+						justify-content: space-between;
+						align-items: center;
+						height: 80rpx;
+						margin-top: 15rpx;
+						color: green;
+						background-color: #ffffff;
+					"
+				>
+					<view class="text flex" style="background-color: aliceblue">
+						{{ list.address }}
+						<uni-icons type="location" size="20"></uni-icons>
+					</view>
+				</view>
+			</view>
+
+			<view v-if="list.protected_area != null" style="margin-left: 24rpx; display: flex; align-items: center; margin-top: 40rpx">
+				<view style="display: flex; align-items: center">
+					<uni-icons type="map-pin-ellipse" size="22" color="#940000"></uni-icons>
+					<view class="item_tit">保护区域</view>
+				</view>
+			</view>
+			<view class="u-info">
+				<u-parse :content="list.protected_area"></u-parse>
+			</view>
+			<view v-if="list.intro" style="margin-left: 24rpx; display: flex; align-items: center; margin-top: 40rpx">
+				<view style="display: flex; align-items: center">
+					<uni-icons type="calendar" size="22" color="#940000"></uni-icons>
+					<view class="item_tit">介绍</view>
+				</view>
+			</view>
+			<view class="text">
+				<u-parse :content="list.intro"></u-parse>
+			</view>
+			<view v-if="list.environment != null" style="margin-left: 24rpx; display: flex; align-items: center; margin-top: 40rpx">
+				<view style="display: flex; align-items: center">
+					<uni-icons type="home" size="22" color="#940000"></uni-icons>
+
+					<view class="item_tit">建筑环境</view>
+				</view>
+			</view>
+			<view class="text">
+				<u-parse :content="list.environment"></u-parse>
+			</view>
+			<view class="" v-if="list.value">
+				<view style="margin-left: 24rpx; display: flex; align-items: center; margin-top: 40rpx">
+					<view style="display: flex; align-items: center">
+						<uni-icons type="medal" size="22" color="#940000"></uni-icons>
+
+						<view class="item_tit">文物价值</view>
+					</view>
+				</view>
+				<view class="text">
+					<u-parse :content="list.value"></u-parse>
+				</view>
+			</view>
+
+			<view class="" v-if="list.director">
+				<view style="margin-left: 24rpx; display: flex; align-items: center; margin-top: 40rpx">
+					<view style="display: flex; align-items: center">
+						<uni-icons type="auth" size="22" color="#940000"></uni-icons>
+
+						<view class="item_tit">文物负责人</view>
+					</view>
+				</view>
+				<view class="text">
+					<u-parse :content="list.director"></u-parse>
+				</view>
+			</view>
+		</view>
+		<!-- 构件 -->
+		<view class="" v-if="association.length > 0">
+			<view style="margin-left: 24rpx; display: flex; align-items: center; margin-top: 40rpx">
+				<view style="display: flex; align-items: center">
+					<uni-icons type="cloud-download" size="22" color="#940000"></uni-icons>
+
+					<view class="item_tit">文物相关</view>
+				</view>
+			</view>
+
+			<view class="jj_btm">
+				<view @click="$common.navigateTo(item.page)" class="container" v-for="(item, index) in association" :key="item.index">
+					<view class="mn_box">
+						<image style="border-radius: 10rpx; width: 100%; height: 100%" :lazy-load="true" :src="item.image"></image>
+					</view>
+					<view class="mn_text">{{ item.title }}</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that = this;
+export default {
+	data() {
+		return {
+			list: [],
+			association: [],
+			imageList: ['https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/sh_lb1.jpg']
+		};
+	},
+	onLoad(e) {
+		that = this;
+		this.getContentDetail(e.id);
+	},
+	computed: {
+		citySection() {
+			let cityPart;
+			if (this.list.address) {
+				const index = this.list.address.indexOf('市');
+				if (index !== -1) {
+					// 如果找到了"市",取市后面七个字
+					cityPart = this.list.address.substring(index + 1, index + 8);
+				} else {
+					// 如果没有找到"市",返回整个地址
+					cityPart = this.list.region_text;
+				}
+			} else {
+				// 如果list.address不存在或为空,也可以设定一个默认值
+				cityPart = this.list.region_text;
+			}
+			return cityPart;
+		}
+	},
+	methods: {
+		// 详情
+		getContentDetail(id) {
+			this.$api.getContentDetail(
+				{
+					main_body_id: 1,
+					id: id
+				},
+				function (res) {
+					// console.log(res, 666666666);
+					that.list = res.data;
+					let aa = Object.values(res.data.association_me_list);
+					that.association = aa.filter((item) => item.title !== '文保牌' && item.main_body_column_name !== '我和文物的故事');
+					if (res.data.images.length > 0) {
+						that.imageList = res.data.images;
+					}
+				}
+			);
+		},
+		// 图片预览
+		clickImg(index) {
+			// console.log(index, 555);
+			var imgs = this.imageList;
+			let temp = [imgs[index]];
+			// console.log('temp', temp);
+
+			uni.previewImage({
+				urls: imgs,
+				current: index,
+				success: function (data) {
+					console.log(data);
+				},
+				fail: function (err) {
+					console.log(err.errMsg);
+				}
+			});
+		},
+
+		// 唤起地图
+		openGuide(name, address, latitude, longitude) {
+			//  微信小程序
+			// #ifdef MP-WEIXIN
+			//console.log('走的这里', item);
+			wx.openLocation({
+				latitude: +latitude, // 纬度
+				longitude: +longitude, // 经度
+				name: name, // 地址名称
+				address: address, // 详细地址
+				success: function (r) {
+					console.log(r);
+				},
+				fail: function (res) {
+					console.log('拉起失败啦', res);
+				}
+			});
+			// #endif
+		}
+	}
+};
+</script>
+
+<style>
+/deep/.u-swiper-indicator__wrapper__dot--active {
+	width: 5px !important;
+}
+.box {
+	min-height: 100%;
+	height: auto;
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/wuyuan/static/shouhu/wyj-feiyi3-001.png');
+	background-repeat: repeat-y;
+	background-attachment: fixed;
+	background-size: 100% 100%;
+}
+.title {
+	font-size: 28rpx;
+	font-weight: 400;
+	color: #444444;
+	line-height: 44rpx;
+}
+.u-info {
+	font-size: 28rpx;
+	color: #444444;
+	line-height: 44rpx;
+	margin: 20rpx;
+	text-align: center;
+}
+.text {
+	margin: 20rpx;
+}
+.info {
+	display: flex;
+	justify-content: space-between;
+	flex-wrap: wrap;
+	margin: 20rpx;
+	margin-top: 16rpx;
+}
+.info_item {
+}
+.info > view {
+	margin-top: 20rpx;
+	font-size: 30rpx;
+	margin-left: 8rpx;
+	color: #940000;
+	/* font-weight: 900; */
+}
+.item_tit {
+	font-size: 32rpx;
+	color: #940000;
+	line-height: 48rpx;
+	font-weight: 700;
+}
+.jj_btm {
+	width: 92%;
+	margin: auto;
+	display: flex;
+	margin-top: 20rpx;
+	flex-wrap: wrap;
+	justify-content: space-between;
+}
+.mn_box {
+	width: 300rpx;
+	height: 200rpx;
+}
+.mn_text {
+	width: 298rpx;
+	font-size: 28rpx;
+	text-align: center;
+	color: #ffffff;
+	position: absolute;
+	top: 160rpx;
+	left: 2rpx;
+	line-height: 40rpx;
+	background: rgba(0, 0, 0, 0.3);
+	border-radius: 0px 0rpx 10rpx 10rpx;
+}
+.container {
+	position: relative;
+	margin-top: 20rpx;
+	/* 	width: 260rpx;
+	height: 182rpx; */
+}
+</style>

+ 262 - 0
index_fenbao/GuanLi/gengDuoFC.vue

@@ -0,0 +1,262 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="厦门文物" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view class="fiex_top">
+			<view class="top_box">
+				<view @click="showqy = true" class="tit_box">
+					<view class="">{{ qyVal ? qyVal : '区域' }}</view>
+					<view class="">
+						<image style="width: 40rpx; height: 40rpx" src="/static/img/map_more.png"></image>
+					</view>
+				</view>
+				<view class="tit_box" @click="showlx = true">
+					<view class="">{{ lxVal ? lxVal : '类型' }}</view>
+					<view class="">
+						<image style="width: 40rpx; height: 40rpx" src="/static/img/map_more.png"></image>
+					</view>
+				</view>
+				<view class="tit_box" @click="showjb = true">
+					<view class="">{{ jbVal ? jbVal : '级别' }}</view>
+					<view class="">
+						<image style="width: 40rpx; height: 40rpx" src="/static/img/map_more.png"></image>
+					</view>
+				</view>
+			</view>
+			<view class="" style="border: 1px solid #eba869; width: 670rpx; margin: auto; margin-top: 32rpx">
+				<u-search
+					bgColor="transparent"
+					v-model="val"
+					shape="square"
+					:showAction="true"
+					actionText="搜索"
+					@custom="searchBtn"
+					:actionStyle="{ color: '#985741', fontSize: '28rpx', fontWeight: 'bold', backgroundColor: '#fff9e9' }"
+				></u-search>
+			</view>
+		</view>
+		<scroll-view v-if="list.length" style="height: 1200rpx; margin-left: 6rpx" scroll-y="true" @scrolltolower="LoadMore">
+			<view class="wenwu_box">
+				<view @click="detailsBtn(item.id)" class="" v-for="(item, index) in list" :key="item.id">
+					<view class="gc_item">
+						<view class="gc_box" style="">
+							<image style="width: 100%; height: 100%; border-radius: 15rpx" :src="item.thumbnail"></image>
+						</view>
+						<view class="tit">{{ item.title }}</view>
+						<!-- <view class="bg_box">湖里区</view> -->
+						<u-badge type="primary" max="99" :value="item.district" :absolute="true" :offset="[10, 5]"></u-badge>
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+		<view v-else><u-empty mode="data" text="暂无数据"></u-empty></view>
+		<!-- 选择 -->
+		<u-picker keyName="title" :show="showqy" @cancel="showqy = false" :columns="columnsQy" @close="close" :closeOnClickOverlay="true" @confirm="qyBtn"></u-picker>
+		<u-picker keyName="title" :show="showlx" @cancel="showlx = false" :columns="columnsLx" @close="close" :closeOnClickOverlay="true" @confirm="lxBtn"></u-picker>
+		<u-picker keyName="title" :show="showjb" @cancel="showjb = false" :columns="columnsJb" @close="close" :closeOnClickOverlay="true" @confirm="jbBtn"></u-picker>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			val: '',
+			page: 1,
+			list: [],
+			isLoading: false,
+			showqy: false,
+			showlx: false,
+			showjb: false,
+			levelId: '',
+			cr_typeId: '',
+			regionId: '',
+			qyVal: '',
+			lxVal: '',
+			jbVal: '',
+			columnsQy: [],
+			columnsLx: [],
+			columnsJb: []
+		};
+	},
+
+	onLoad() {
+		that = this;
+		this.getContentList();
+		this.getCulturalRelicCategory();
+	},
+	methods: {
+		// 文物分类列表
+		getCulturalRelicCategory() {
+			this.$api.getCulturalRelicCategory({ main_body_id: 1 }, function (res) {
+				console.log(res, 565656);
+
+				that.columnsQy = [res.data.region_list];
+				that.columnsLx = [res.data.cr_type_list];
+				that.columnsJb = [res.data.level_list];
+			});
+		},
+
+		lxBtn(e) {
+			console.log(e, 'eeeeeee');
+			this.lxVal = e.value[0].title;
+			this.showlx = false;
+			this.list = [];
+			this.page = 1;
+			this.cr_typeId = e.value[0].id;
+
+			this.getContentList();
+		},
+		qyBtn(e) {
+			this.qyVal = e.value[0].title;
+			this.showqy = false;
+			this.list = [];
+			this.page = 1;
+			this.regionId = e.value[0].id;
+			this.getContentList();
+		},
+		jbBtn(e) {
+			this.jbVal = e.value[0].title;
+			this.showjb = false;
+			this.list = [];
+			this.page = 1;
+			this.levelId = e.value[0].id;
+			this.getContentList();
+		},
+
+		detailsBtn(id) {
+			uni.navigateTo({
+				url: '/index_fenbao/GuanLi/XiangQing?id=' + id
+			});
+		},
+		getContentList(id) {
+			this.isLoading = true;
+			this.$api.getContentList(
+				{
+					main_body_id: 1,
+					model_id: 1,
+					keywords: this.val,
+					page: this.page,
+					pageSize: '10',
+					level: this.levelId,
+					cr_type: this.cr_typeId,
+					region: this.regionId
+				},
+				function (res) {
+					that.isLoading = false;
+					that.list = [...that.list, ...res.data];
+				}
+			);
+		},
+		searchBtn() {
+			this.qyVal = '';
+			this.lxVal = '';
+			this.jbVal = '';
+			this.levelId = '';
+			this.cr_typeId = '';
+			this.regionId = '';
+			this.page = 1;
+			this.list = [];
+			this.getContentList();
+		},
+		LoadMore() {
+			if (this.isLoading) {
+				return;
+			} else {
+				// this.qyVal = '';
+				// this.lxVal = '';
+				// this.jbVal = '';
+
+				this.page++;
+				this.getContentList();
+			}
+		},
+		close() {
+			this.showqy = false;
+			this.showlx = false;
+			this.showjb = false;
+		},
+		clear() {
+			this.val = '';
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xbg_2.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	background-repeat: repeat-y;
+	height: auto;
+}
+
+.fiex_top {
+	/* margin-bottom: 20rpx; */
+}
+.top_box {
+	display: flex;
+	width: 90%;
+	margin: auto;
+	justify-content: space-between;
+}
+.tit_box {
+	display: flex;
+	align-items: center;
+	text-align: center;
+	width: 215rpx;
+	height: 56rpx;
+	justify-content: space-around;
+	border: 2rpx solid #eba869;
+}
+.tit {
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+.wenwu_box {
+	width: 92%;
+	margin: auto;
+	margin-top: 20rpx;
+	display: flex;
+	flex-flow: wrap;
+	justify-content: space-between;
+}
+.gc_item {
+	position: relative;
+	width: 324rpx;
+	color: #444444;
+	/* font-weight: 900; */
+	font-size: 32rpx;
+	margin-top: 20rpx;
+	font-family: 'MyGlobalFont';
+}
+.gc_box {
+	width: 333rpx;
+	height: 280rpx;
+}
+
+.bg_box {
+	width: 184rpx;
+	height: 62rpx;
+	margin: auto;
+	margin-top: 20rpx;
+	line-height: 62rpx;
+	color: #fef3e1;
+	font-size: 28rpx;
+	font-weight: 500;
+	background-image: url('/static/img/tab.png');
+	background-size: 100% 100%;
+}
+.u-search__action--active.data-v-0a306a29 {
+	height: 60rpx !important;
+	line-height: 60rpx !important;
+}
+.u-search__action--active.data-v-2d141374 {
+	height: 60rpx !important;
+	line-height: 60rpx !important;
+}
+</style>

+ 418 - 0
index_fenbao/GuanLi/zhiYuanGeRen.vue

@@ -0,0 +1,418 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="巡查守护" titleStyle="font-weight:bold;color:#000000" bgColor="rgba(255,255,255,0)" :placeholder="true"></u-navbar>
+		<view style="width: 92%; margin: auto">
+			<view>
+				<u-swiper
+					:list="swiperList"
+					imgMode="aspectFill"
+					:height="160"
+					:indicator="true"
+					radius="5"
+					:autoplay="true"
+					:circular="true"
+					indicatorStyle="bottom: 10px"
+					indicatorMode="dot"
+					indicatorActiveColor="#fff"
+					indicatorInactiveColor="rgba(255, 255, 255, 0.35)"
+				></u-swiper>
+			</view>
+			<!-- <view class="gy_box" @click="tougaoBtn">
+				<view class="">管理</view>
+				<uni-icons type="gear" size="22" color="#000000"></uni-icons>
+			</view> -->
+			<view class="top_box">
+				<view class="time">{{ currentYear }}年</view>
+				<view class="item_tit">巡查次数:{{ detailsList.patrolNum !== undefined ? detailsList.patrolNum : '' }}</view>
+				<view class="item_tit">月度积分:{{ detailsList.monthScore !== undefined ? detailsList.monthScore : '' }}</view>
+				<view class="item_tit">年度积分:{{ detailsList.yearScore !== undefined ? detailsList.yearScore : '' }}</view>
+				<view class="item_tit">巡查完成率:{{ detailsList.completion_rate !== undefined ? detailsList.completion_rate : '' }}</view>
+			</view>
+			<view style="display: flex; justify-content: space-around">
+				<view class="tab" @click="taskBtn">
+					<view class="jb">任务</view>
+					<view class="wb">{{ numberTasks }}</view>
+				</view>
+				<view class="tab" @click="HistoryBtn">
+					<view class="jb">历史巡查</view>
+				</view>
+
+				<view class="tab" @click="tougaoBtn">
+					<view class="jb">管理</view>
+				</view>
+			</view>
+			<view class="ranking_menu">
+				<view @click="rankingTab(index)" class="ranking_item" :class="{ active: Tab == index }" v-for="(item, index) in rankingList" :key="item.index">
+					{{ item.title }}
+				</view>
+			</view>
+
+			<scroll-view v-if="patrolList.length > 0" style="height: 600rpx" scroll-y="true" @scrolltolower="LoadMore">
+				<view>
+					<view class="tit_box" @click="checkBtn(item)" v-for="item in patrolList" :key="item.id">
+						<view class="bk" style="width: 160rpx">{{ item.cr_title }}</view>
+						<view class="bk" style="width: 240rpx">
+							{{ item.progress == 3 ? item.submit_time : item.progress == 2 ? item.submit_time : item.progress == -1 ? item.submit_time : '' }}
+						</view>
+						<view class="bk">{{ item.type_text }}</view>
+						<view class="ck">查看</view>
+					</view>
+				</view>
+			</scroll-view>
+
+			<view v-else>
+				<u-empty text="暂无数据" mode="data" icon="http://cdn.uviewui.com/uview/empty/data.png"></u-empty>
+			</view>
+		</view>
+		<!-- 非管理员提示 -->
+		<u-popup bgColor="#f2c8a2" :show="show" @close="close" mode="center" customStyle="width:600rpx;padding: 30rpx;" round="20rpx">
+			<view style="text-align: center; font-size: 32rpx">您当前还不是管理员</view>
+			<view style="margin-top: 20rpx"></view>
+			<view class="bt_box">
+				<view class="bt2" @click="show = false">确认</view>
+			</view>
+		</u-popup>
+		<!-- 去报名弹层 -->
+		<u-popup bgColor="#f2c8a2" :show="volhow" @close="close" mode="center" customStyle="width:600rpx;padding: 30rpx;" round="20rpx">
+			<view style="text-align: center; font-size: 30rpx">您当前还不是志愿者</view>
+			<view style="margin-top: 20rpx"></view>
+			<view class="bt_box">
+				<view class="bt1" @click="volhow = false">取消</view>
+				<view @click="goSignUp" class="bt2">去报名</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			currentYear: new Date().getFullYear(),
+			volhow: false /* 非志愿者弹层 */,
+			volunteerShow: false /* 是否是志愿者控制页面去报名弹层 */,
+			isLoading: false,
+			page: 1,
+			show: false,
+			Tab: 0,
+			numberTasks: '' /* 任务数量 */,
+			rankingList: [
+				{
+					title: '待巡查',
+					id: '0'
+				},
+				{
+					title: '待审核',
+					id: 2
+				},
+				{
+					title: '已通过',
+					id: 3
+				},
+
+				{
+					title: '驳回',
+					id: -1
+				},
+				{
+					title: '未提交',
+					id: -2
+				}
+			],
+			detailsList: {},
+			patrolList: [],
+			swiperList: ['https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/zyz_gr.png', 'https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/gj_zyjz.jpg']
+		};
+	},
+	onLoad() {
+		that = this;
+		this.details();
+		// this.getPatrolTask('0');
+	},
+	methods: {
+		// 志愿者详情判断是否为志愿者
+		details() {
+			this.$api.details({ main_body_id: 1 }, function (res) {
+				if (res.code == 1) {
+					if (res.data != false && res.data.id != undefined) {
+						that.detailsList = res.data;
+						// console.log(that.volunteerDetails, '是志愿者');
+						that.volhow = false;
+						that.volunteerShow = true;
+						// 任务列表
+						that.getPatrolTask('0');
+					} else {
+						// 控制非志愿者弹层
+						that.volhow = true;
+						that.volunteerShow = false;
+						// console.log(that.volunteerShow, '非志愿者点击11');
+						// console.log(res, '非志愿者');
+					}
+				} else {
+					console.log(res);
+				}
+			});
+		},
+		// 志愿者任务列表
+		getPatrolTask(id) {
+			this.isLoading = true;
+			this.$api.getPatrolTask({ volunteer_id: that.detailsList.id, main_body_id: 1, progress: id ? id : 0, page: this.page, pageSize: 10 }, function (res) {
+				if (res.code === 1) {
+					that.isLoading = false;
+					that.patrolList = [...that.patrolList, ...res.data];
+					if (id == '0') {
+						that.numberTasks = res.data.length;
+					}
+					console.log(that.patrolList, '任务');
+				} else {
+				}
+			});
+		},
+		//
+		rankingTab(i) {
+			this.Tab = i;
+			this.page = 1;
+			that.patrolList = [];
+			this.getPatrolTask(this.rankingList[i].id);
+		},
+		tougaoBtn() {
+			this.$api.userManageRegionCrAuth({ main_body_id: 1 }, function (res) {
+				if (res.data != false) {
+					uni.navigateTo({
+						url: '/index_fenbao/GuanLi/GuanLi'
+					});
+				} else {
+					that.show = true;
+					// console.log(res, '非管理员');
+				}
+			});
+		},
+		checkBtn(item) {
+			if (item.progress == 0) {
+				uni.navigateTo({
+					url: '/shouhu_fenbao/xunCha/xunCha?id=' + item.id
+				});
+			} else {
+				uni.navigateTo({
+					url: '/user_fenbao/houTai/taskDEetailsPage?id=' + item.id
+				});
+			}
+		},
+		taskBtn() {
+			uni.switchTab({
+				url: '/pages/shouhu/shouhu'
+			});
+		},
+		HistoryBtn() {
+			if (this.volunteerShow) {
+				uni.navigateTo({
+					url: '/shouhu_fenbao/shouHu/woDe'
+				});
+			}
+		},
+		goSignUp() {
+			uni.navigateTo({
+				url: '/index_fenbao/fuWu/baoMing/renLing'
+			});
+		},
+		LoadMore() {
+			if (this.isLoading) {
+				return;
+			} else {
+				this.page++;
+				this.getPatrolTask();
+			}
+		},
+		close() {
+			this.show = false;
+			this.volhow = false;
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xbg_2.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	background-repeat: repeat-y;
+	min-height: 100%;
+	height: auto;
+}
+.bt1 {
+	width: 244rpx;
+	height: 78rpx;
+	border: 1px solid #312520;
+	border-radius: 39rpx;
+	text-align: center;
+	line-height: 78rpx;
+	color: black;
+}
+/deep/.u-swiper-indicator__wrapper__dot--active {
+	width: 5px !important;
+}
+.top_box {
+	position: relative;
+	height: 260rpx;
+	display: flex;
+	text-align: center;
+	align-items: center;
+	flex-wrap: wrap;
+	padding: 30rpx;
+	margin-top: 50rpx;
+	margin-bottom: 40rpx;
+	justify-content: space-around;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/zyz_bk.png');
+	background-size: 100% 100%;
+}
+.bt2 {
+	width: 244rpx;
+	height: 78rpx;
+	background: #ca5642;
+	border-radius: 39rpx;
+	text-align: center;
+	line-height: 78rpx;
+	color: #ffffff;
+}
+.item_tit {
+	width: 302rpx;
+	font-size: 30rpx;
+}
+.time {
+	position: absolute;
+	top: -16rpx;
+	text-align: center;
+	line-height: 50rpx;
+	width: 140rpx;
+	height: 45rpx;
+	color: #934b36;
+	background-image: url('/static/img/time_bg .png');
+	background-size: 100% 100%;
+}
+.tab {
+	position: relative;
+	width: 195rpx;
+	height: 65rpx;
+	text-align: center;
+	font-weight: 600;
+	line-height: 65rpx;
+	background-image: url('/static/img/gr_tab.png');
+	background-size: 100% 100%;
+}
+.jb {
+	background: linear-gradient(180deg, #af7e44 0%, #934b36 100%);
+	-webkit-background-clip: text;
+	-webkit-text-fill-color: transparent;
+}
+.fg {
+	width: 8rpx;
+	height: 40rpx;
+	background-color: #e36f65;
+	border-radius: 10rpx;
+	margin-right: 10rpx;
+}
+.fg_tit {
+	font-size: 30rpx;
+	font-weight: 600;
+	background: linear-gradient(180deg, #af7e44 0%, #934b36 100%);
+	-webkit-background-clip: text;
+	-webkit-text-fill-color: transparent;
+}
+.tit_box {
+	display: flex;
+	align-items: center;
+	font-size: 30rpx;
+	margin: auto;
+	margin-top: 20rpx;
+	justify-content: space-between;
+}
+.ck {
+	width: 96rpx;
+	height: 40rpx;
+	text-align: center;
+	border-radius: 10rpx;
+	background-color: #eeae5a;
+}
+.wb {
+	position: absolute;
+	top: -10rpx;
+	right: -10rpx;
+	width: 35rpx;
+	line-height: 38rpx;
+	height: 35rpx;
+	color: #ffffff;
+	background: #de5448;
+	border-radius: 50%;
+}
+.wb2 {
+	position: absolute;
+	width: 35rpx;
+	height: 35rpx;
+	color: #ffffff;
+	top: -5px;
+	right: -10rpx;
+	line-height: 38rpx;
+	background: #de5448;
+	border-radius: 50%;
+}
+.gy_box {
+	width: 120rpx;
+	height: 120rpx;
+	display: flex;
+	align-items: center;
+	position: absolute;
+	top: 240rpx;
+	right: -40rpx;
+	padding-left: 10rpx;
+	font-size: 30rpx;
+	font-family: MiSans, MiSans;
+	font-weight: 500;
+	justify-content: space-evenly;
+	line-height: 44rpx;
+	z-index: 11;
+	border-radius: 50%;
+	background-color: #eeae5a;
+}
+.ranking_menu {
+	width: 90%;
+	margin: auto;
+	margin-top: 50rpx;
+	display: flex;
+	justify-content: space-between;
+	font-size: 28rpx;
+	color: rgba(68, 68, 68, 0.6);
+	text-align: center;
+	line-height: 60rpx;
+}
+.ranking_item {
+	width: 260rpx;
+	height: 62rpx;
+}
+.active {
+	background-image: url('/static/img/gr_tab.png');
+	background-size: 100% 100%;
+}
+.bk {
+	width: 180rpx;
+	overflow-x: auto;
+	white-space: nowrap;
+}
+.bt_box {
+	display: flex;
+	margin-top: 50rpx;
+	justify-content: space-around;
+}
+.bt2 {
+	width: 244rpx;
+	height: 78rpx;
+	background: #ca5642;
+	border-radius: 39rpx;
+	text-align: center;
+	line-height: 78rpx;
+	color: #ffffff;
+}
+</style>

+ 362 - 0
index_fenbao/HuoHuaLiYong/YanXue/HuoHuaLiYong.vue

@@ -0,0 +1,362 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="活化利用" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<!--成果  -->
+		<view>
+			<view class="tit_box" @click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/chanPin/gengDuoChengGuo')">
+				<view class="tit">成果展示</view>
+				<view class="tit2">查看更多</view>
+			</view>
+			<view class="rw_box" style="">
+				<view
+					@click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/chanPin/chengGuo?id=' + item.id)"
+					class="zj_box"
+					v-for="item in achievement"
+					:key="item.id"
+					style="position: relative"
+				>
+					<view class="tx_box">
+						<image
+							style="border-radius: 15rpx; width: 300rpx; height: 185rpx"
+							mode="aspectFill"
+							:src="item.image || 'https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/zwtp.png'"
+						></image>
+					</view>
+					<view class="" style="width: 280rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
+						{{ item.title }}
+					</view>
+				</view>
+			</view>
+			<!--  -->
+			<view class="tit_box" @click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/YanXue/YanXue')">
+				<view class="tit">{{ research.type_text }}</view>
+				<view class="tit2">查看更多</view>
+			</view>
+			<view class="yx_box" @click="researchBtn(research.id)">
+				<view style="position: absolute; left: 6rpx; top: 6rpx"><image style="width: 160rpx; height: 240rpx" :src="research.image"></image></view>
+				<view style="margin-left: 200rpx">
+					<view class="wwd">{{ research.title }}</view>
+					<view class="time">活动时间:{{ (research.start_time && research.start_time.split(' ')[0]) || '' }}</view>
+				</view>
+				<view class="baoming">
+					<text>查看</text>
+					<image style="margin-left: 10rpx; width: 32rpx; height: 32rpx" src="/static/img/more.png"></image>
+				</view>
+			</view>
+		</view>
+		<!--产品  -->
+		<view>
+			<view class="tit_box" @click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/chanPin/chanPin')">
+				<view class="tit">文创产品</view>
+				<view class="tit2">查看更多</view>
+			</view>
+			<view class="rw_box" style="">
+				<view
+					@click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/chanPin/chanPinXQ?id=' + item.id)"
+					class="zj_box"
+					v-for="item in product"
+					:key="item.id"
+					style="position: relative"
+				>
+					<view class="tx_box">
+						<image style="border-radius: 15rpx; width: 300rpx; height: 185rpx" mode="aspectFill" :src="item.image"></image>
+					</view>
+					<view class="" style="width: 280rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
+						{{ item.title }}
+					</view>
+				</view>
+			</view>
+
+			<!-- 	<view v-for="item in product" :key="item.id" @click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/chanPin/chanPinXQ?id=' + item.id)" class="yx_box">
+				<view style="position: absolute; left: 6rpx; top: 6rpx"><image style="width: 160rpx; height: 240rpx" :src="item.thumbnail"></image></view>
+				<view style="margin-left: 200rpx">
+					<view class="wwd">{{ item.title }}</view>
+			
+				</view>
+				<view class="baoming">
+					<text>查看</text>
+					<image style="margin-left: 10rpx; width: 32rpx; height: 32rpx" src="/static/img/more.png"></image>
+				</view>
+			</view> -->
+		</view>
+		<!--挖掘  -->
+		<view>
+			<view class="tit_box" @click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/waJue/gengDuo')">
+				<view class="tit">价值挖掘</view>
+				<view class="tit2">查看更多</view>
+			</view>
+			<view class="gg_box">
+				<view @click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/waJue/waJue?id=' + item.id)" class="gg_item" v-for="item in excavateList" :key="item.id">
+					<view class="" style="display: flex; align-items: center">
+						<image :src="item.image" style="border-radius: 10rpx; margin-right: 30rpx; width: 128rpx; height: 128rpx"></image>
+						<view class="">
+							<view>
+								<view class="rw_tit1">{{ item.title }}</view>
+								<!-- <view class="rw_tit2">精彩瞬间</view> -->
+								<view class="rw_tit2">精彩投稿</view>
+							</view>
+						</view>
+					</view>
+					<view class="">
+						<image style="width: 40rpx; height: 40rpx" src="/static/img/more.png"></image>
+					</view>
+				</view>
+			</view>
+
+			<!-- 		<view  class="yx_box">
+				<view style="position: absolute; left: 6rpx; top: 6rpx">
+					<image style="width: 160rpx; height: 240rpx" :src="excavateList.image"></image>
+				</view>
+				<view style="margin-left: 200rpx">
+					<view class="wwd">{{ excavateList.title }}</view>
+				</view>
+				<view class="baoming">
+					<text>查看</text>
+					<image style="margin-left: 10rpx; width: 32rpx; height: 32rpx" src="/static/img/more.png"></image>
+				</view>
+			</view> -->
+		</view>
+		<!--游径  -->
+		<view>
+			<view class="tit_box" @click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/YouJin/gengDuo')">
+				<view class="tit">文物主题游径</view>
+				<view class="tit2">查看更多</view>
+			</view>
+			<view class="yx_box" @click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/YouJin/YouJin?id=' + youJingList.id)">
+				<view style="position: absolute; left: 6rpx; top: 6rpx">
+					<image style="width: 164rpx; height: 240rpx" :src="youJingList.image"></image>
+				</view>
+				<view style="margin-left: 200rpx">
+					<view class="wwd">{{ youJingList.title }}</view>
+					<!-- <view class="time">文物路线一览表</view> -->
+				</view>
+				<view class="baoming">
+					<text>查看</text>
+					<image style="margin-left: 10rpx; width: 32rpx; height: 32rpx" src="/static/img/more.png"></image>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			achievement: [] /* 成果 */,
+			research: {} /* 研学 */,
+			product: [] /* 产品 */,
+			travelPathList: {} /* 游径 */,
+			excavateList: [] /* 价值挖掘 */,
+			youJingList: {} /* 游径 */
+		};
+	},
+	onLoad() {
+		that = this;
+		this.activityLists();
+		this.getContentList();
+		this.travelPath();
+		this.youJing();
+		this.loadGlobalFont();
+		this.excavateGetContentList();
+	},
+	methods: {
+		// 文物研学
+		activityLists() {
+			this.$api.activityLists({ main_body_id: 1, type: 1 }, function (res) {
+				// console.log(res, '活动');
+				if (res.code == 1) {
+					that.research = res.data[0];
+				}
+			});
+		},
+		// 产品
+		getContentList() {
+			this.$api.getContentList({ main_body_id: 1, model_id: 9, page: 1, pageSize: 4 }, function (res) {
+				// console.log(res, '产品');
+
+				if (res.code == 1) {
+					that.product = res.data;
+				}
+			});
+		},
+		// 成果
+		travelPath() {
+			this.$api.getMainBodyColumnContentList({ main_body_id: 1, model_id: 11 }, function (res) {
+				res.data.forEach((item) => {
+					if (item.name == '活化利用成果') {
+						that.achievement = item.content_list;
+						// console.log(that.achievement, '活化利用成果');
+					}
+				});
+			});
+		},
+		// 游径
+		youJing() {
+			this.$api.activityLists({ main_body_id: 1, type: 2 }, function (res) {
+				if (res.code == 1) {
+					that.youJingList = res.data[0];
+					// console.log(that.youJingList, '游径');
+				}
+			});
+		},
+		// 价值挖掘
+		excavateGetContentList() {
+			this.$api.getContentList({ main_body_id: 1, model_id: 11, main_body_column_id: 72, page: 1, pageSize: 4 }, function (res) {
+				// console.log(res, '价值挖掘');
+				if (res.code == 1) {
+					that.excavateList = res.data;
+				}
+			});
+		},
+		/* 研学详情 */
+		researchBtn(id) {
+			uni.navigateTo({
+				url: '/index_fenbao/HuoHuaLiYong/YanXue/XiangQing?id=' + id
+			});
+		},
+
+		// 加载字体
+		loadGlobalFont() {
+			try {
+				uni.loadFontFace({
+					family: 'MyGlobalFont', // 自定义字体名
+					source: 'url("https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/songTi.ttf")',
+					success() {
+						console.log('全局字体加载成功');
+					}
+				});
+			} catch (error) {
+				console.error('全局字体加载异常', error);
+			}
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xy_bgt.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	background-repeat: repeat-y;
+	min-height: 100%;
+	height: auto;
+	/* background-color: #fff9e9; */
+}
+.yx_box {
+	position: relative;
+	width: 92%;
+	height: 252rpx;
+	display: flex;
+	justify-content: space-around;
+	align-items: center;
+	margin: auto;
+	margin-top: 34rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/hh_item_bg.png');
+	background-size: 100% 100%;
+}
+.gg_box {
+	width: 92%;
+	margin: auto;
+	margin-top: 20rpx;
+	padding: 20rpx;
+	border: 2rpx solid #ebc9a9;
+	background-color: #ffffff;
+	border-radius: 10rpx;
+}
+.gg_item {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	margin-top: 25rpx;
+	width: 630rpx;
+	/* height: 80rpx; */
+}
+.rw_tit1 {
+	font-size: 30rpx;
+	color: #444444;
+	line-height: 44rpx;
+}
+.rw_tit2 {
+	font-size: 26rpx;
+	color: rgba(68, 68, 68, 0.4);
+	line-height: 40rpx;
+}
+.rw_box {
+	width: 92%;
+	margin: auto;
+	margin-top: 30rpx;
+	display: flex;
+	flex-wrap: wrap;
+	padding: 0 20rpx 20rpx 20rpx;
+	justify-content: space-between;
+	border-radius: 10rpx;
+	border: 2rpx solid #ebc9a9;
+	background-color: #ffffff;
+}
+.zj_box {
+	margin-top: 30rpx;
+}
+.left_m {
+	margin-left: 50rpx !important;
+}
+.wwd {
+	width: 300rpx;
+	font-size: 36rpx;
+	font-weight: 400;
+	color: #444444;
+	line-height: 52rpx;
+}
+.time {
+	width: 300rpx;
+	font-size: 28rpx;
+	color: rgba(68, 68, 68, 0.6);
+	line-height: 44rpx;
+	margin-top: 12rpx;
+}
+.baoming {
+	display: flex;
+	/* width: 116rpx; */
+	height: 56rpx;
+	line-height: 56rpx;
+	justify-content: center;
+	align-items: center;
+	margin-left: 32rpx;
+	font-size: 28rpx;
+	color: #eba869;
+	border: 2rpx solid #eba869;
+}
+.map_tit {
+	display: flex;
+	align-items: center;
+	margin-left: 90rpx;
+	margin-top: 40rpx;
+	font-size: 40rpx;
+	font-family: 'MyGlobalFont';
+	font-weight: 900;
+	line-height: 52rpx;
+	color: #444444;
+}
+.tit_box {
+	width: 90%;
+	margin: auto;
+	margin-top: 40rpx;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	font-family: 'MyGlobalFont';
+}
+.tit {
+	font-size: 32rpx;
+	color: #444444;
+	font-weight: 600;
+}
+.tit2 {
+	font-size: 28rpx;
+	color: rgba(68, 68, 68, 0.6);
+}
+</style>

+ 271 - 0
index_fenbao/HuoHuaLiYong/YanXue/XiangQing.vue

@@ -0,0 +1,271 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" bgColor="rgba(255,255,255,0)" :placeholder="true"></u-navbar>
+		<!-- 日历 -->
+		<view class="aaa">
+			<!-- <uv-calendars readonly="true" :selected="selected" @monthSwitch="monthSwitch" mode="multiple" insert="false" lunar="true" /> -->
+			<uv-calendars readonly="true" :insert="true" mode="range" lunar="true" :date="date" />
+		</view>
+		<view class="map_tit" style="margin-left: 90rpx">
+			<image style="width: 211rpx; height: 52rpx" src="/static/img/right_img.png"></image>
+			<view class="">路线明细</view>
+			<image style="width: 211rpx; height: 52rpx" src="/static/img/left_img.png"></image>
+		</view>
+		<view class="ban_box">
+			<view style="display: flex; justify-content: space-around; margin-top: 100rpx">
+				<!-- <view class="lx_tn"></view> -->
+				<view class="time_box">
+					<view class="">开始时间:{{ list.start_time }}</view>
+					<view class="">结束时间:{{ list.end_time }}</view>
+					<!-- <view class="">活动名额:35人</view> -->
+				</view>
+			</view>
+			<!-- 路线 -->
+
+			<view class="lx_tit">
+				<text style="color: #000000">路线:</text>
+				<text v-for="(item, index) in content_list" :key="item.id">{{ item.title }}{{ index < content_list.length - 1 ? ' →' : '' }}</text>
+			</view>
+
+			<!-- 地图 -->
+			<view style="height: 560rpx; overflow: hidden">
+				<mapComponent :height="height" :markers="markers" :latitudeAndLongitude="latitudeAndLongitude"></mapComponent>
+			</view>
+
+			<!-- <view style="font-size: 28rpx; margin-top: 20rpx">福建省厦门市思明区寿山路与思明南路交叉正北方向12米</view> -->
+			<view style="font-size: 28rpx; margin-top: 20rpx">活动描述:{{ list.desc }}</view>
+			<view @click="signUp" class="ewm_box">立即报名</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			date: ['', ''],
+			list: {},
+			content_list: [],
+			latitudeAndLongitude: {
+				latitude: '24.503559',
+				longitude: '118.13093'
+			},
+			// 绑定上dateList即可需
+			dateList: [],
+			selected: [],
+			markers: [],
+			height: '600'
+		};
+	},
+
+	created() {
+		// this.updateDateListForCurrentMonth();
+		// this.dimension();
+	},
+	onLoad(o) {
+		that = this;
+		this.activityDetails(o.id);
+	},
+	methods: {
+		activityDetails(id) {
+			this.$api.activityDetails({ main_body_id: 1, id: id }, function (res) {
+				if (res.code == 1) {
+					// console.log(res, '研学详情');
+					that.list = res.data;
+					that.content_list = Object.values(that.list.content_list);
+					if (that.content_list.length > 0) {
+						that.markers = [];
+						that.latitudeAndLongitude.latitude = that.content_list[0].latitude;
+						that.latitudeAndLongitude.longitude = that.content_list[0].longitude;
+						that.content_list.forEach((item, index) => {
+							that.markers.push({
+								id: parseFloat(item.id),
+								latitude: parseFloat(item.latitude), //纬度
+								longitude: parseFloat(item.longitude), //经度
+								iconPath: item.thumbnail, //显示的图标
+								width: 30, //宽
+								height: 30, //高
+								title: item.title, //标注点名
+								label: {
+									//自定义标记点上方的文本
+									content: item.title, //文本
+									color: '#444444', //文字颜色
+									fontSize: 14, //文本大小
+									bgColor: '#f1cdb2', // 背景颜色(半透明黑色)
+									borderRadius: 5, // 边框圆角
+									padding: [2, 2], // 内边距
+									textAlign: 'center', // 文本对齐方式
+									display: 'ALWAYS' // 始终显示标签
+								},
+								joinCluster: true
+							});
+						});
+					}
+
+					// console.log(that.markers, '地图');
+					that.date = [];
+					that.date.push(res.data.start_time.split(' ')[0]);
+					that.date.push(res.data.end_time.split(' ')[0]);
+					// console.log(that.date, '日历时间');
+				}
+			});
+		},
+		signUp() {
+			if (this.list.status == 1) {
+				uni.navigateTo({
+					url: '/index_fenbao/HuoHuaLiYong/YanXue/baoMing?id=' + this.list.id
+				});
+			} else if (this.list.status == 2) {
+				this.$common.errorToShow('当前活动' + this.list.status_text);
+			} else if (this.list.status == 4) {
+				this.$common.errorToShow('当前活动' + this.list.status_text);
+			}
+		}
+		// updateDateListForCurrentMonth() {
+		// 	const currentDate = new Date();
+		// 	this.getSaturdaysOfMonth(currentDate.getFullYear(), currentDate.getMonth() + 1); // 月份是从 0 开始计数的
+		// },
+
+		// getSaturdaysOfMonth(year, month) {
+		// 	const daysInMonth = new Date(year, month - 1, 0).getDate();
+
+		// 	this.dateList = [];
+		// 	for (let day = 1; day <= daysInMonth; day++) {
+		// 		const date = new Date(year, month - 1, day);
+		// 		if (date.getDay() === 0) {
+		// 			this.dateList.push(date.toISOString().substring(0, 10));
+		// 		}
+		// 	}
+		// 	console.log(this.dateList);
+		// },
+
+		// monthSwitch({ year, month }) {
+		// 	this.getSaturdaysOfMonth(year, month);
+		// 	this.dimension();
+		// },
+		// 周六标注点
+		// dimension() {
+		// 	let a = [];
+		// 	this.dateList.forEach((item) => {
+		// 		a.push({ date: item, info: '活动' });
+		// 	});
+		// 	this.selected = a;
+		// 	console.log(this.selected);
+		// }
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xbg_2.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	background-repeat: repeat-y;
+	min-height: 100%;
+	height: auto;
+}
+/deep/.uv-calendar__content.data-v-0d8ea832 {
+	background-color: #fff9e9 !important;
+}
+.uv-calendar__content.data-v-513749fa {
+	background-color: #fff9e9 !important;
+}
+.uv-calendar__backtoday.data-v-173dfca3 {
+	display: none !important;
+}
+.uv-calendar__backtoday.data-v-5b1004fa {
+	display: none !important;
+}
+.uv-calendar-item__weeks-box-item.data-v-101dc4d8 {
+	height: 90rpx !important;
+}
+.uv-calendar-item__weeks-box-item.data-v-6a001a42 {
+	height: 90rpx !important;
+}
+.map_tit {
+	display: flex;
+	align-items: center;
+	margin-left: 60rpx;
+	margin-top: 40rpx;
+	font-size: 40rpx;
+	font-family: Songti SC, Songti SC;
+	font-weight: 900;
+	line-height: 52rpx;
+	color: #444444;
+}
+.ban_box {
+	width: 692rpx;
+	margin: auto;
+	margin-top: 20rpx;
+	padding: 40rpx 40rpx 130rpx 40rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/lx_bg.png');
+	background-size: 100% 100%;
+}
+.lx_tn {
+	width: 163rpx;
+	height: 150rpx;
+	background-image: url('/static/img/lx_ta.png');
+	background-size: 100% 100%;
+}
+.lx_tit {
+	color: #894514;
+	margin: 20rpx 0 20rpx 10rpx;
+	font-size: 30rpx;
+	font-weight: 700;
+}
+.rili {
+	width: 670rpx;
+	height: 1054rpx;
+	margin: auto;
+}
+.img {
+	width: 100%;
+	height: 100%;
+}
+.xwgg {
+	width: 170rpx;
+	height: 89rpx;
+	margin: auto;
+	margin-top: 70rpx;
+	font-size: 32rpx;
+	font-weight: 900;
+	text-align: center;
+	line-height: 89rpx;
+	letter-spacing: 1px;
+	color: #feece3;
+	background-image: url('/static/img/title2.png');
+	background-size: 100% 100%;
+}
+
+.time_box {
+	height: 96rpx;
+	font-size: 32rpx;
+	font-weight: 400;
+	color: #894514;
+	line-height: 48rpx;
+}
+
+.map_box {
+	width: 670rpx;
+	height: 670rpx;
+	margin: auto;
+	margin-top: 40rpx;
+	overflow: hidden;
+	background: #fcfdfd;
+	border-radius: 0rpx 0rpx 0rpx 0rpx;
+	opacity: 1;
+}
+.ewm_box {
+	height: 80rpx;
+	background-color: #efb781;
+	border-radius: 35rpx;
+	font-size: 32rpx;
+	text-align: center;
+	line-height: 80rpx;
+	margin: 20rpx 32rpx 0 32rpx;
+}
+</style>

+ 94 - 0
index_fenbao/HuoHuaLiYong/YanXue/YanXue.vue

@@ -0,0 +1,94 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="文物研学" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view v-for="item in research" :key="item.id" class="yx_box" @click="application(item.id)">
+			<view style="position: absolute; left: 6rpx; top: 6rpx">
+				<image style="width: 160rpx; height: 240rpx" :src="item.image"></image>
+			</view>
+			<view style="margin-left: 200rpx">
+				<view class="wwd">{{ item.title }}</view>
+				<view class="time">活动时间:{{ item.start_time.split(' ')[0] }}</view>
+			</view>
+			<view class="baoming">
+				<text>报名</text>
+				<image style="margin-left: 10rpx; width: 32rpx; height: 32rpx" src="/static/img/arrow-right-line.png"></image>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			research: []
+		};
+	},
+	onLoad() {
+		that = this;
+		this.activityLists();
+	},
+	methods: {
+		// 文物研学
+		activityLists() {
+			this.$api.activityLists({ main_body_id: 1, type: 1 }, function (res) {
+				// console.log(res, '文物研学');
+				that.research = res.data;
+			});
+		},
+		application(id) {
+			uni.navigateTo({
+				url: '/index_fenbao/HuoHuaLiYong/YanXue/XiangQing?id=' + id
+			});
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xbg_2.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	background-repeat: repeat-y;
+	height: 100%;
+}
+.yx_box {
+	position: relative;
+	width: 670rpx;
+	height: 252rpx;
+	display: flex;
+	align-items: center;
+	margin: auto;
+	margin-top: 34rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/hh_item_bg.png');
+	background-size: 100% 100%;
+}
+.wwd {
+	font-size: 36rpx;
+	font-weight: 400;
+	color: #444444;
+	line-height: 52rpx;
+}
+.time {
+	font-size: 28rpx;
+	color: rgba(68, 68, 68, 0.6);
+	line-height: 44rpx;
+	margin-top: 12rpx;
+}
+.baoming {
+	display: flex;
+	width: 116rpx;
+	height: 56rpx;
+	line-height: 56rpx;
+	justify-content: center;
+	align-items: center;
+	margin-left: 32rpx;
+	font-size: 28rpx;
+	color: #eba869;
+	border: 2rpx solid #eba869;
+}
+</style>

+ 173 - 0
index_fenbao/HuoHuaLiYong/YanXue/baoMing.vue

@@ -0,0 +1,173 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="报名" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<!-- 表单 -->
+		<view class="example">
+			<uni-forms style="padding: 0 20rpx 0 20rpx" label-position="top" ref="valiForm" :rules="rules" :modelValue="valiFormData">
+				<uni-forms-item label="您的姓名" label-width="80px" required name="name">
+					<uni-easyinput v-model="valiFormData.name" placeholder="请输入姓名" />
+				</uni-forms-item>
+				<uni-forms-item label="您的联系电话" label-width="100px" required name="mobile">
+					<uni-easyinput v-model="valiFormData.mobile" placeholder="请输联系电话" />
+				</uni-forms-item>
+
+				<uni-forms-item label="您的身份证" label-width="100px" required name="idCard">
+					<uni-easyinput v-model="valiFormData.idCard" placeholder="请输入身份证号" />
+				</uni-forms-item>
+			</uni-forms>
+			<view class="text-wrapper_3" @click="submit('valiForm')">
+				<text>我要报名</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			id: '' /* 活动id */,
+			/* 表单数据 */
+			valiFormData: {
+				name: '',
+				mobile: '',
+				idCard: ''
+			},
+			/* 校验规则 */
+			rules: {
+				// 对name字段进行必填验证
+				name: {
+					// name 字段的校验规则
+					rules: [
+						// 校验 name 不能为空
+						{
+							required: true,
+							errorMessage: '请填写姓名'
+						},
+						// 对name字段进行长度验证
+						{
+							minLength: 2,
+							maxLength: 6,
+							errorMessage: '{label}长度在 {minLength} 到 {maxLength} 个字符'
+						}
+					],
+					// 当前表单域的字段中文名,可不填写
+					label: '姓名',
+					validateTrigger: 'submit'
+				},
+				/* 手机号校验 */
+				mobile: {
+					// mobile 字段的校验规则
+					rules: [
+						// 校验 mobile 不能为空
+						{
+							required: true,
+							errorMessage: '请填写联系电话'
+						},
+						// 对mobile字段进行长度验证
+						{
+							minLength: 11,
+							maxLength: 11,
+							errorMessage: '{label}长度为 {minLength}  个字符'
+						}
+					],
+					// 当前表单域的字段中文名,可不填写
+					label: '手机号',
+					validateTrigger: 'submit'
+				},
+				/* 身份证校验 */
+				idCard: {
+					// idCard 字段的校验规则
+					rules: [
+						// 校验 idCard 不能为空
+						{
+							required: true,
+							errorMessage: '请填报名人数'
+						},
+						// 对idCard字段进行长度验证
+						{
+							minLength: 18,
+							maxLength: 18,
+							errorMessage: '{label}长度为 {minLength}  个字符'
+						}
+					],
+					// 当前表单域的字段中文名,可不填写
+					label: '身份证号',
+					validateTrigger: 'submit'
+				}
+			}
+		};
+	},
+	onLoad(o) {
+		that = this;
+		this.id = o.id;
+	},
+	methods: {
+		submit() {
+			this.$refs.valiForm.validate(['id'], async (err, valiFormData) => {
+				if (!err) {
+					console.log('校验成功');
+					this.$api.activitySignup(
+						{
+							main_body_id: 1,
+							activity_id: this.id,
+							name: this.valiFormData.name,
+							mobile: this.valiFormData.mobile,
+							id_card: this.valiFormData.idCard
+						},
+						function (res) {
+							console.log(res, '报名');
+							if (res.code == 1) {
+								that.$common.errorToShow(res.msg);
+							} else {
+								that.$common.errorToShow('请稍后再试');
+							}
+						}
+					);
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	height: 100%;
+	background-color: #efb781;
+}
+/deep/.uni-forms-item__error {
+	color: #ea3535 !important;
+}
+
+.text-wrapper_3 {
+	background-color: #fde6b0;
+	border-radius: 40rpx;
+	height: 80rpx;
+	color: #312520;
+	text-align: center;
+	line-height: 80rpx;
+	font-size: 32rpx;
+	flex-direction: column;
+	width: 620rpx;
+	margin-top: 40rpx;
+	margin-bottom: 20rpx;
+	margin-left: 30rpx;
+	font-weight: bold;
+	position: fixed;
+	bottom: 100rpx;
+}
+.example {
+	height: 87%;
+	background-color: rgba(255, 255, 255, 0.15);
+	margin: 20rpx 32rpx 0 32rpx;
+}
+.uni-forms {
+	padding: 0 20rpx 0 20rpx;
+}
+.uni-forms-item {
+	margin-bottom: 30rpx !important;
+}
+</style>

+ 146 - 0
index_fenbao/HuoHuaLiYong/YouJin/YouJin.vue

@@ -0,0 +1,146 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="文物主题游径" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view class="ban_box">
+			<view class="map_box">
+				<mapComponent :latitudeAndLongitude="latitudeAndLongitude" :height="height"></mapComponent>
+			</view>
+		</view>
+		<!-- 路线 -->
+		<view class="lx_box">
+			<view class="lx_tit">
+				<view class="">
+					<text style="color: #d85f4c">{{ list.title }}:</text>
+					{{ list.content }}
+				</view>
+			</view>
+		</view>
+		<view class="btm_box">
+			<view class="" style="display: flex; align-items: center; justify-content: start">
+				<view class="Serial"></view>
+				<view style="font-size: 32rpx; margin-left: 20rpx">{{ list.title }}</view>
+			</view>
+
+			<view class="introduce">{{ list.desc }}</view>
+			<!--  -->
+			<view class="fx_box">
+				<view style="display: flex; align-items: center">
+					<uni-icons type="redo" color="#5b5b5b" size="20"></uni-icons>
+					<view class="">分享</view>
+				</view>
+				<view style="display: flex; align-items: center">
+					<uni-icons type="star" color="#5b5b5b" size="20"></uni-icons>
+					<view class="">收藏</view>
+				</view>
+				<view style="display: flex; align-items: center">
+					<uni-icons type="paperplane" color="#5b5b5b" size="20"></uni-icons>
+					<view class="">前往</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			latitudeAndLongitude: {
+				latitude: '24.503559',
+				longitude: '118.13093'
+			},
+			list: {},
+			height: 1000
+		};
+	},
+	onLoad(o) {
+		that = this;
+		this.activityDetails(o.id);
+	},
+	methods: {
+		activityDetails(id) {
+			this.$api.activityDetails({ main_body_id: 1, id: id }, function (res) {
+				if (res.code == 1) {
+					console.log(res, '游径详情');
+					that.list = res.data;
+					// that.content_list = Object.values(that.list.content_list);
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xy_bgt.png');
+	background-size: 100% 100%;
+	background-repeat: repeat-y;
+	height: auto;
+}
+.ban_box {
+	width: 90%;
+	height: 1000rpx;
+	position: relative;
+	margin: auto;
+	margin-top: 20rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/lx_bg.png');
+	background-size: 100% 100%;
+}
+.map_box {
+	width: 600rpx;
+	height: 875rpx;
+	overflow: hidden;
+	padding-top: 130rpx;
+	margin: auto;
+}
+.img {
+	position: absolute;
+	width: 692rpx;
+	height: 1000rpx;
+}
+.lx_box {
+	width: 90%;
+	margin: auto;
+}
+.lx_tit {
+	margin: 20rpx 0 20rpx 10rpx;
+	font-size: 28rpx;
+	font-weight: 700;
+}
+.btm_box {
+	width: 653rpx;
+	/* height: 345rpx; */
+	padding: 40rpx;
+	margin: auto;
+	font-size: 28rpx;
+	font-weight: 600;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/yjlx_bg.png');
+	background-size: 100% 100%;
+}
+.Serial {
+	width: 35rpx;
+	height: 35rpx;
+	background-color: #d92e2e;
+	border-radius: 50%;
+	text-align: center;
+	color: #ffffff;
+}
+.introduce {
+	height: 130rpx;
+	margin-left: 54rpx;
+	overflow: scroll;
+}
+.fx_box {
+	display: flex;
+	align-items: center;
+	font-weight: 500;
+	justify-content: space-evenly;
+
+	color: #5b5b5b;
+	font-size: 26rpx;
+}
+</style>

+ 156 - 0
index_fenbao/HuoHuaLiYong/YouJin/gengDuo.vue

@@ -0,0 +1,156 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="主题游径" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view @click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/YouJin/YouJin?id=' + item.id)" v-for="item in list" :key="item.id" class="yx_box">
+			<view style="position: absolute; left: 6rpx; top: 6rpx">
+				<image style="width: 160rpx; height: 240rpx" :src="item.image"></image>
+			</view>
+			<view style="margin-left: 200rpx">
+				<view class="wwd">{{ item.title }}</view>
+			</view>
+			<view class="baoming">
+				<text>查看</text>
+				<image style="margin-left: 10rpx; width: 32rpx; height: 32rpx" src="/static/img/arrow-right-line.png"></image>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+let that;
+export default {
+	data() {
+		return {
+			list: []
+		};
+	},
+	onLoad(o) {
+		that = this;
+		this.youJing();
+	},
+	methods: {
+		// 游径
+		youJing() {
+			this.$api.activityLists({ main_body_id: 1, type: 2 }, function (res) {
+				if (res.code == 1) {
+					that.list = res.data;
+					console.log(that.youJingList, '更多游径');
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xbg_2.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	background-repeat: repeat-y;
+	height: 100%;
+}
+
+.img {
+	width: 100%;
+	height: 100%;
+}
+
+.xq {
+	width: 55rpx;
+	height: 180rpx;
+	text-align: center;
+	line-height: 50rpx;
+	font-size: 30rpx;
+	margin-left: 50rpx;
+	margin-top: 80rpx;
+	color: #ffffff;
+	background-image: url('/static/img/wc_tit_bg.png');
+	background-size: 100% 100%;
+}
+.right_box {
+	writing-mode: vertical-lr;
+	writing-mode: tb-lr;
+	letter-spacing: 6rpx;
+}
+.tit {
+	font-size: 28rpx;
+	font-weight: 600;
+	background: linear-gradient(180deg, #af7e44 0%, #934b36 100%);
+	-webkit-background-clip: text;
+	-webkit-text-fill-color: transparent;
+}
+.fenge {
+	width: 2rpx;
+	height: 200rpx;
+	background-color: #934b36;
+	margin-top: 55rpx;
+	margin-left: 10rpx;
+	background-image: linear-gradient(to bottom right, #ecd9c3, #934b36);
+}
+.kc_box {
+	width: 92%;
+	margin: auto;
+	padding: 30upx;
+	display: flex;
+	align-items: center;
+	margin-top: 30rpx;
+}
+.b-cover {
+	width: 225rpx;
+	height: 125rpx;
+}
+.b-main {
+	flex: 1;
+	margin-left: 20upx;
+}
+.text_23 {
+	color: #272727;
+	font-size: 28rpx;
+	margin-top: 10rpx;
+	font-weight: 600;
+}
+.text_25 {
+	font-size: 26rpx;
+	color: #727070;
+	margin-top: 10rpx;
+	font-weight: 400;
+}
+.yx_box {
+	position: relative;
+	width: 670rpx;
+	height: 252rpx;
+	display: flex;
+	align-items: center;
+	margin: auto;
+	margin-top: 34rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/hh_item_bg.png');
+	background-size: 100% 100%;
+}
+.wwd {
+	width: 300rpx;
+	font-size: 36rpx;
+	font-weight: 400;
+	color: #444444;
+	line-height: 52rpx;
+}
+.time {
+	font-size: 28rpx;
+	color: rgba(68, 68, 68, 0.6);
+	line-height: 44rpx;
+	margin-top: 12rpx;
+}
+.baoming {
+	display: flex;
+	width: 116rpx;
+	height: 56rpx;
+	line-height: 56rpx;
+	justify-content: center;
+	align-items: center;
+	margin-left: 32rpx;
+	font-size: 28rpx;
+	color: #eba869;
+	border: 2rpx solid #eba869;
+}
+</style>

+ 179 - 0
index_fenbao/HuoHuaLiYong/chanPin/chanPin.vue

@@ -0,0 +1,179 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="文创产品" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view @click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/chanPin/chanPinXQ?id=' + item.id)" v-for="item in list" :key="item.id" class="yx_box">
+			<view style="position: absolute; left: 6rpx; top: 6rpx">
+				<image style="width: 160rpx; height: 240rpx" :src="item.image"></image>
+			</view>
+			<view style="margin-left: 200rpx">
+				<view class="wwd">{{ item.title }}</view>
+			</view>
+			<view class="baoming">
+				<text>查看</text>
+				<image style="margin-left: 10rpx; width: 32rpx; height: 32rpx" src="/static/img/arrow-right-line.png"></image>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+let that;
+export default {
+	data() {
+		return {
+			list: [],
+			page: 1,
+			isLoading: false
+		};
+	},
+	onLoad(o) {
+		that = this;
+		this.getContentList();
+	},
+	methods: {
+		// 产品
+		getContentList() {
+			this.isLoading = true;
+			this.$api.getContentList({ main_body_id: 1, model_id: 9 }, function (res) {
+				console.log(res, '产品');
+				that.isLoading = true;
+				that.list = [...that.list, ...res.data];
+			});
+		}
+	},
+	/* 页面触底 */
+	onReachBottom() {
+		if (this.isLoading) {
+			return;
+		} else {
+			this.page++;
+			this.getContentList();
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xbg_2.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	background-repeat: repeat-y;
+	height: 100%;
+}
+
+.img {
+	width: 100%;
+	height: 100%;
+}
+
+.xq {
+	width: 55rpx;
+	height: 180rpx;
+	text-align: center;
+	line-height: 50rpx;
+	font-size: 30rpx;
+	margin-left: 50rpx;
+	margin-top: 80rpx;
+	color: #ffffff;
+	background-image: url('/static/img/wc_tit_bg.png');
+	background-size: 100% 100%;
+}
+.right_box {
+	writing-mode: vertical-lr;
+	writing-mode: tb-lr;
+	letter-spacing: 6rpx;
+}
+.tit {
+	font-size: 28rpx;
+	font-weight: 600;
+	background: linear-gradient(180deg, #af7e44 0%, #934b36 100%);
+	-webkit-background-clip: text;
+	-webkit-text-fill-color: transparent;
+}
+.fenge {
+	width: 2rpx;
+	height: 200rpx;
+	background-color: #934b36;
+	margin-top: 55rpx;
+	margin-left: 10rpx;
+	background-image: linear-gradient(to bottom right, #ecd9c3, #934b36);
+}
+.kc_box {
+	width: 92%;
+	margin: auto;
+	padding: 30upx;
+	display: flex;
+	align-items: center;
+	margin-top: 30rpx;
+}
+.b-cover {
+	width: 225rpx;
+	height: 125rpx;
+}
+.b-main {
+	flex: 1;
+	margin-left: 20upx;
+}
+.text_23 {
+	color: #272727;
+	font-size: 28rpx;
+	margin-top: 10rpx;
+	font-weight: 600;
+}
+.text_25 {
+	font-size: 26rpx;
+	color: #727070;
+	margin-top: 10rpx;
+	font-weight: 400;
+}
+.text_23 {
+	color: #272727;
+	font-size: 28rpx;
+	margin-top: 10rpx;
+	font-weight: 600;
+}
+.text_25 {
+	font-size: 26rpx;
+	color: #727070;
+	margin-top: 10rpx;
+	font-weight: 400;
+}
+.yx_box {
+	position: relative;
+	width: 670rpx;
+	height: 252rpx;
+	display: flex;
+	align-items: center;
+	margin: auto;
+	margin-top: 34rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/hh_item_bg.png');
+	background-size: 100% 100%;
+}
+.wwd {
+	width: 300rpx;
+	font-size: 36rpx;
+	font-weight: 400;
+	color: #444444;
+	line-height: 52rpx;
+}
+.time {
+	font-size: 28rpx;
+	color: rgba(68, 68, 68, 0.6);
+	line-height: 44rpx;
+	margin-top: 12rpx;
+}
+.baoming {
+	display: flex;
+	width: 116rpx;
+	height: 56rpx;
+	line-height: 56rpx;
+	justify-content: center;
+	align-items: center;
+	margin-left: 32rpx;
+	font-size: 28rpx;
+	color: #eba869;
+	border: 2rpx solid #eba869;
+}
+</style>

+ 108 - 0
index_fenbao/HuoHuaLiYong/chanPin/chanPinXQ.vue

@@ -0,0 +1,108 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="产品详情" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view class="">
+			<view class="box_2">
+				<image style="width: 100%; height: 100%" :src="list.image" />
+			</view>
+
+			<text lines="1" class="text_4">详情介绍</text>
+			<view class="text-wrapper_1">
+				<view class="text_5"><u-parse :content="list.content"></u-parse></view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			list: {}
+		};
+	},
+	onLoad(o) {
+		that = this;
+		this.getContentDetail(o.id);
+	},
+	methods: {
+		getContentDetail(id) {
+			this.$api.getContentDetail({ main_body_id: 1, id: id }, function (res) {
+				console.log(res, '产品详情');
+				that.list = res.data;
+			});
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	height: auto;
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/wuyuan/static/shouhu/wyj-feiyi3-001.png');
+	background-attachment: fixed;
+	background-size: 100% 100%;
+	background-repeat: repeat-y;
+	min-height: 100%;
+	height: auto;
+	/* background-color: #fff9e9; */
+}
+.box_2 {
+	box-shadow: 0px 30px 60px 0px rgba(19, 104, 158, 0.1);
+	background-color: rgba(255, 255, 255, 1);
+	border-radius: 20rpx;
+	position: relative;
+	width: 690rpx;
+	height: 322rpx;
+	display: flex;
+	flex-direction: column;
+	margin: 0 0 40rpx 30rpx;
+	box-shadow: rgba(0, 0, 0, 0.1) 0px 20rpx 15px -6rpx, rgba(0, 0, 0, 0.05) 0px 8rpx 12rpx -4rpx;
+}
+.text_4 {
+	width: 112rpx;
+	height: 28rpx;
+	overflow-wrap: break-word;
+	color: rgba(49, 37, 32, 1);
+	font-size: 32rpx;
+	font-family: SourceHanSansCN-Bold;
+	font-weight: 700;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 28rpx;
+	margin: 60rpx 0 0 47rpx;
+}
+.text-group_4 {
+	width: 263rpx;
+	height: 61rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+	margin: 163rpx 0 0 180rpx;
+}
+.text-wrapper_1 {
+	/* background-color: rgba(255, 255, 255, 1); */
+	border-radius: 20rpx;
+	/* height: 500rpx; */
+	display: flex;
+	flex-direction: column;
+	width: 686rpx;
+	margin: 10rpx 0 0 32rpx;
+}
+.text_5 {
+	width: 652rpx;
+	/* height: 279rpx; */
+	overflow-wrap: break-word;
+	font-size: 28rpx;
+	font-family: SourceHanSansCN-Regular;
+	font-weight: normal;
+	text-align: justifyLeft;
+	line-height: 48rpx;
+	margin: 44rpx 0 0 16rpx;
+	overflow: scroll;
+	text-indent: 2rem;
+}
+</style>

+ 141 - 0
index_fenbao/HuoHuaLiYong/chanPin/chengGuo.vue

@@ -0,0 +1,141 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="成果展示" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view class="">
+			<view style="width: 90%; margin: auto; margin-bottom: 30rpx">
+				<u-swiper
+					@click="clickImg"
+					:list="swiperList"
+					imgMode="aspectFill"
+					:height="160"
+					:indicator="true"
+					radius="5"
+					:autoplay="true"
+					:circular="true"
+					indicatorStyle="bottom: 10px"
+					indicatorMode="dot"
+					indicatorActiveColor="#fff"
+					indicatorInactiveColor="rgba(255, 255, 255, 0.35)"
+				></u-swiper>
+			</view>
+
+			<text lines="1" class="text_4">详情介绍</text>
+			<view class="text-wrapper_1">
+				<view class="text_5"><u-parse :content="list.content"></u-parse></view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			swiperList: [],
+			list: {}
+		};
+	},
+	onLoad(o) {
+		that = this;
+		this.getContentDetail(o.id);
+	},
+	methods: {
+		getContentDetail(id) {
+			this.$api.getContentDetail({ main_body_id: 1, id: id }, function (res) {
+				// console.log(res, '成果详情');
+				that.list = res.data;
+				that.swiperList = res.data.images;
+			});
+		},
+		// 图片预览
+		clickImg(index) {
+			// console.log(index, 555);
+			var imgs = this.list.images;
+			let temp = [imgs[index]];
+			// console.log('temp', temp);
+
+			uni.previewImage({
+				urls: imgs,
+				current: index,
+				success: function (data) {
+					console.log(data);
+				},
+				fail: function (err) {
+					console.log(err.errMsg);
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	height: auto;
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/wuyuan/static/shouhu/wyj-feiyi3-001.png');
+	background-attachment: fixed;
+	background-repeat: repeat-y;
+	background-size: 100% 100%;
+	min-height: 100%;
+	height: auto;
+	/* background-color: #fff9e9; */
+}
+/deep/.u-swiper-indicator__wrapper__dot--active {
+	width: 5px !important;
+}
+.group_13 {
+	width: 100%;
+	height: 420rpx;
+}
+.box_2 {
+	box-shadow: 0px 30px 60px 0px rgba(19, 104, 158, 0.1);
+	background-color: rgba(255, 255, 255, 1);
+	border-radius: 20rpx;
+	position: relative;
+	width: 690rpx;
+	height: 322rpx;
+	display: flex;
+	flex-direction: column;
+	margin: 0 0 40rpx 30rpx;
+	box-shadow: rgba(0, 0, 0, 0.1) 0px 20rpx 15px -6rpx, rgba(0, 0, 0, 0.05) 0px 8rpx 12rpx -4rpx;
+}
+.text_4 {
+	width: 112rpx;
+	height: 28rpx;
+	overflow-wrap: break-word;
+	color: rgba(49, 37, 32, 1);
+	font-size: 32rpx;
+	font-family: SourceHanSansCN-Bold;
+	font-weight: 700;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 28rpx;
+	margin: 60rpx 0 0 47rpx;
+}
+
+.text-wrapper_1 {
+	/* background-color: rgba(255, 255, 255, 1); */
+	border-radius: 20rpx;
+	/* height: 500rpx; */
+	display: flex;
+	flex-direction: column;
+	width: 686rpx;
+	margin: 0 0 0 32rpx;
+}
+.text_5 {
+	width: 652rpx;
+	/* height: 279rpx; */
+	overflow-wrap: break-word;
+	font-size: 28rpx;
+	font-family: SourceHanSansCN-Regular;
+	font-weight: normal;
+	text-align: justifyLeft;
+	line-height: 48rpx;
+	margin: 44rpx 0 0 16rpx;
+	overflow: scroll;
+	text-indent: 2rem;
+}
+</style>

+ 169 - 0
index_fenbao/HuoHuaLiYong/chanPin/gengDuoChengGuo.vue

@@ -0,0 +1,169 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="成果展示" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view @click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/chanPin/chengGuo?id=' + item.id)" v-for="item in achievement" :key="item.id" class="yx_box">
+			<view style="position: absolute; left: 6rpx; top: 6rpx">
+				<image style="width: 160rpx; height: 240rpx" :src="item.image"></image>
+			</view>
+			<view style="margin-left: 200rpx">
+				<view class="wwd">{{ item.title }}</view>
+			</view>
+			<view class="baoming">
+				<text>查看</text>
+				<image style="margin-left: 10rpx; width: 32rpx; height: 32rpx" src="/static/img/arrow-right-line.png"></image>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+let that;
+export default {
+	data() {
+		return {
+			achievement: []
+		};
+	},
+	onLoad(o) {
+		that = this;
+		this.travelPath();
+	},
+	methods: {
+		travelPath() {
+			this.$api.getMainBodyColumnContentList({ main_body_id: 1, model_id: 11 }, function (res) {
+				res.data.forEach((item) => {
+					if (item.name == '活化利用成果') {
+						that.achievement = item.content_list;
+						console.log(that.achievement, '活化利用成果');
+					}
+				});
+			});
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xbg_2.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	background-repeat: repeat-y;
+	height: 100%;
+}
+
+.img {
+	width: 100%;
+	height: 100%;
+}
+
+.xq {
+	width: 55rpx;
+	height: 180rpx;
+	text-align: center;
+	line-height: 50rpx;
+	font-size: 30rpx;
+	margin-left: 50rpx;
+	margin-top: 80rpx;
+	color: #ffffff;
+	background-image: url('/static/img/wc_tit_bg.png');
+	background-size: 100% 100%;
+}
+.right_box {
+	writing-mode: vertical-lr;
+	writing-mode: tb-lr;
+	letter-spacing: 6rpx;
+}
+.tit {
+	font-size: 28rpx;
+	font-weight: 600;
+	background: linear-gradient(180deg, #af7e44 0%, #934b36 100%);
+	-webkit-background-clip: text;
+	-webkit-text-fill-color: transparent;
+}
+.fenge {
+	width: 2rpx;
+	height: 200rpx;
+	background-color: #934b36;
+	margin-top: 55rpx;
+	margin-left: 10rpx;
+	background-image: linear-gradient(to bottom right, #ecd9c3, #934b36);
+}
+.kc_box {
+	width: 92%;
+	margin: auto;
+	padding: 30upx;
+	display: flex;
+	align-items: center;
+	margin-top: 30rpx;
+}
+.b-cover {
+	width: 225rpx;
+	height: 125rpx;
+}
+.b-main {
+	flex: 1;
+	margin-left: 20upx;
+}
+.text_23 {
+	color: #272727;
+	font-size: 28rpx;
+	margin-top: 10rpx;
+	font-weight: 600;
+}
+.text_25 {
+	font-size: 26rpx;
+	color: #727070;
+	margin-top: 10rpx;
+	font-weight: 400;
+}
+.text_23 {
+	color: #272727;
+	font-size: 28rpx;
+	margin-top: 10rpx;
+	font-weight: 600;
+}
+.text_25 {
+	font-size: 26rpx;
+	color: #727070;
+	margin-top: 10rpx;
+	font-weight: 400;
+}
+.yx_box {
+	position: relative;
+	width: 670rpx;
+	height: 252rpx;
+	display: flex;
+	align-items: center;
+	margin: auto;
+	margin-top: 34rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/hh_item_bg.png');
+	background-size: 100% 100%;
+}
+.wwd {
+	width: 300rpx;
+	font-size: 36rpx;
+	font-weight: 400;
+	color: #444444;
+	line-height: 52rpx;
+}
+.time {
+	font-size: 28rpx;
+	color: rgba(68, 68, 68, 0.6);
+	line-height: 44rpx;
+	margin-top: 12rpx;
+}
+.baoming {
+	display: flex;
+	width: 116rpx;
+	height: 56rpx;
+	line-height: 56rpx;
+	justify-content: center;
+	align-items: center;
+	margin-left: 32rpx;
+	font-size: 28rpx;
+	color: #eba869;
+	border: 2rpx solid #eba869;
+}
+</style>

+ 166 - 0
index_fenbao/HuoHuaLiYong/waJue/gengDuo.vue

@@ -0,0 +1,166 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="价值挖掘" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view @click="$common.navigateTo('/index_fenbao/HuoHuaLiYong/waJue/waJue?id=' + item.id)" v-for="item in list" :key="item.id" class="yx_box">
+			<view style="position: absolute; left: 6rpx; top: 6rpx">
+				<image style="width: 160rpx; height: 240rpx" :src="item.image"></image>
+			</view>
+			<view style="margin-left: 200rpx">
+				<view class="wwd">{{ item.title }}</view>
+			</view>
+			<view class="baoming">
+				<text>查看</text>
+				<image style="margin-left: 10rpx; width: 32rpx; height: 32rpx" src="/static/img/arrow-right-line.png"></image>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+let that;
+export default {
+	data() {
+		return {
+			list: [],
+			page: 1,
+			isLoading: false
+		};
+	},
+	onLoad(o) {
+		that = this;
+		this.getContentList();
+	},
+	methods: {
+		getContentList() {
+			this.isLoading = true;
+			this.$api.getContentList({ main_body_id: 1, model_id: 11, main_body_column_id: 72, page: this.page, pageSize: 10 }, function (res) {
+				that.isLoading = false;
+				console.log(res, '更多挖掘');
+				that.list = [...that.list, ...res.data];
+			});
+		}
+	},
+	/* 页面触底 */
+	onReachBottom() {
+		if (this.isLoading) {
+			return;
+		} else {
+			this.page++;
+			this.getContentList();
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xbg_2.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	background-repeat: repeat-y;
+	height: 100%;
+}
+
+.img {
+	width: 100%;
+	height: 100%;
+}
+
+.xq {
+	width: 55rpx;
+	height: 180rpx;
+	text-align: center;
+	line-height: 50rpx;
+	font-size: 30rpx;
+	margin-left: 50rpx;
+	margin-top: 80rpx;
+	color: #ffffff;
+	background-image: url('/static/img/wc_tit_bg.png');
+	background-size: 100% 100%;
+}
+.right_box {
+	writing-mode: vertical-lr;
+	writing-mode: tb-lr;
+	letter-spacing: 6rpx;
+}
+.tit {
+	font-size: 28rpx;
+	font-weight: 600;
+	background: linear-gradient(180deg, #af7e44 0%, #934b36 100%);
+	-webkit-background-clip: text;
+	-webkit-text-fill-color: transparent;
+}
+.fenge {
+	width: 2rpx;
+	height: 200rpx;
+	background-color: #934b36;
+	margin-top: 55rpx;
+	margin-left: 10rpx;
+	background-image: linear-gradient(to bottom right, #ecd9c3, #934b36);
+}
+.kc_box {
+	width: 92%;
+	margin: auto;
+	padding: 30upx;
+	display: flex;
+	align-items: center;
+	margin-top: 30rpx;
+}
+.b-cover {
+	width: 225rpx;
+	height: 125rpx;
+}
+.b-main {
+	flex: 1;
+	margin-left: 20upx;
+}
+.text_23 {
+	color: #272727;
+	font-size: 28rpx;
+	margin-top: 10rpx;
+	font-weight: 600;
+}
+.text_25 {
+	font-size: 26rpx;
+	color: #727070;
+	margin-top: 10rpx;
+	font-weight: 400;
+}
+.yx_box {
+	position: relative;
+	width: 670rpx;
+	height: 252rpx;
+	display: flex;
+	align-items: center;
+	margin: auto;
+	margin-top: 34rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/hh_item_bg.png');
+	background-size: 100% 100%;
+}
+.wwd {
+	width: 300rpx;
+	font-size: 36rpx;
+	font-weight: 400;
+	color: #444444;
+	line-height: 52rpx;
+}
+.time {
+	font-size: 28rpx;
+	color: rgba(68, 68, 68, 0.6);
+	line-height: 44rpx;
+	margin-top: 12rpx;
+}
+.baoming {
+	display: flex;
+	width: 116rpx;
+	height: 56rpx;
+	line-height: 56rpx;
+	justify-content: center;
+	align-items: center;
+	margin-left: 32rpx;
+	font-size: 28rpx;
+	color: #eba869;
+	border: 2rpx solid #eba869;
+}
+</style>

+ 151 - 0
index_fenbao/HuoHuaLiYong/waJue/waJue.vue

@@ -0,0 +1,151 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="价值挖掘" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view class="">
+			<view v-if="images.length > 0" style="width: 90%; margin: auto">
+				<u-swiper
+					@click="clickImg"
+					:list="images"
+					imgMode="aspectFill"
+					:height="160"
+					:indicator="true"
+					radius="5"
+					:autoplay="true"
+					:circular="true"
+					indicatorStyle="bottom: 10px"
+					indicatorMode="dot"
+					indicatorActiveColor="#fff"
+					indicatorInactiveColor="rgba(255, 255, 255, 0.35)"
+				></u-swiper>
+			</view>
+			<view v-else class="box_2">
+				<image style="width: 100%; height: 100%" :src="list.image" />
+			</view>
+			<view style="width: 76%; display: flex; justify-content: space-between; align-items: center">
+				<view class="text_4">投稿人:{{ list.from }}</view>
+
+				<view class="text_4">{{ list.publish_at }}</view>
+			</view>
+			<view class="text-wrapper_1">
+				<u-parse :content="list.content"></u-parse>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			list: {},
+			images: []
+		};
+	},
+	onLoad(o) {
+		that = this;
+		this.getContentDetail(o.id);
+	},
+	methods: {
+		getContentDetail(id) {
+			this.$api.getContentDetail({ main_body_id: 1, id: id }, function (res) {
+				console.log(res, '挖掘详情');
+				that.list = res.data;
+				if (res.data.images.length > 0) {
+					that.images = res.data.images;
+				}
+			});
+		},
+		// 图片预览
+		clickImg(index) {
+			// console.log(index, 555);
+			var imgs = this.list.images;
+			let temp = [imgs[index]];
+			// console.log('temp', temp);
+
+			uni.previewImage({
+				urls: imgs,
+				current: index,
+				success: function (data) {
+					console.log(data);
+				},
+				fail: function (err) {
+					console.log(err.errMsg);
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style>
+/deep/.u-swiper-indicator__wrapper__dot--active {
+	width: 5px !important;
+}
+.box {
+	height: auto;
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/wuyuan/static/shouhu/wyj-feiyi3-001.png');
+	background-attachment: fixed;
+	background-repeat: repeat-y;
+	background-size: 100% 100%;
+	min-height: 100%;
+	height: auto;
+	/* background-color: #fff9e9; */
+}
+.box_2 {
+	box-shadow: 0px 30px 60px 0px rgba(19, 104, 158, 0.1);
+	background-color: rgba(255, 255, 255, 1);
+	border-radius: 20rpx;
+	position: relative;
+	width: 690rpx;
+	height: 322rpx;
+	display: flex;
+	flex-direction: column;
+	margin: 0 0 20rpx 30rpx;
+	box-shadow: rgba(0, 0, 0, 0.1) 0px 20rpx 15px -6rpx, rgba(0, 0, 0, 0.05) 0px 8rpx 12rpx -4rpx;
+}
+.text_4 {
+	width: 112rpx;
+	height: 28rpx;
+	overflow-wrap: break-word;
+	color: #727070;
+	font-size: 26rpx;
+	font-family: SourceHanSansCN-Bold;
+	text-align: left;
+	white-space: nowrap;
+	line-height: 28rpx;
+	margin: 60rpx 0 0 47rpx;
+}
+.text-group_4 {
+	width: 263rpx;
+	height: 61rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+	margin: 163rpx 0 0 180rpx;
+}
+.text-wrapper_1 {
+	/* background-color: rgba(255, 255, 255, 1); */
+	border-radius: 20rpx;
+	/* height: 500rpx; */
+	display: flex;
+	flex-direction: column;
+	width: 686rpx;
+	margin: 20rpx 0 0 32rpx;
+}
+.text_5 {
+	width: 652rpx;
+	height: 279rpx;
+	overflow-wrap: break-word;
+	font-size: 28rpx;
+	font-family: SourceHanSansCN-Regular;
+	font-weight: normal;
+	text-align: justifyLeft;
+	line-height: 48rpx;
+	margin: 44rpx 0 0 16rpx;
+	overflow: scroll;
+	text-indent: 2rem;
+}
+</style>

+ 96 - 0
index_fenbao/XinWen/XinWen.vue

@@ -0,0 +1,96 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" :title="list.title" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+
+		<view class="ban_box">
+			<view class="kc_box">
+				<u-parse :content="list.content"></u-parse>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			list: []
+		};
+	},
+	onLoad(o) {
+		that = this;
+		this.getNewsNotice(o.id);
+	},
+	methods: {
+		// 公告
+		getNewsNotice(id) {
+			this.$api.getNotice({ main_body_id: 1, id: id }, function (res) {
+				if (res.code == 1) {
+					that.list = res.data;
+
+					// console.log(that.newsListTit,'新闻')
+				}
+			});
+		}
+		// getContentDetail(id) {
+		// 	this.$api.getContentDetail({ main_body_id: 1, id: id }, function (res) {
+		// 		console.log(res);
+		// 		that.list = res.data;
+		// 	});
+		// }
+	}
+};
+</script>
+
+<style>
+.box {
+	min-height: 100%;
+	height: auto;
+	width: 100%;
+	padding-bottom: 50rpx;
+	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xbg_2.png');
+	background-repeat: repeat-y;
+	background-size: cover;
+	background-size: 100% 100%;
+}
+.ban_box {
+	width: 92%;
+	margin: auto;
+	margin-top: 20rpx;
+}
+.map_tit {
+	display: flex;
+	align-items: center;
+	margin-left: 60rpx;
+	margin-top: 40rpx;
+	font-size: 40rpx;
+	font-weight: 700;
+	line-height: 52rpx;
+	color: #444444;
+}
+.ban_item {
+	display: flex;
+	margin-top: 20rpx;
+	font-size: 30rpx;
+}
+.kc_box {
+	width: 92%;
+	margin: auto;
+	padding: 30upx;
+	display: flex;
+	align-items: center;
+	margin-top: 30rpx;
+}
+.b-cover {
+	width: 225rpx;
+	height: 125rpx;
+}
+
+.text_25 {
+	font-size: 26rpx;
+	color: #727070;
+	margin-top: 10rpx;
+	font-weight: 400;
+}
+</style>

+ 125 - 0
index_fenbao/fuWu/FaLv/FaLv.vue

@@ -0,0 +1,125 @@
+<template>
+	<view class="box">
+		<u-navbar :autoBack="true" title="法律法规" bgColor="rgba(255,255,255,0)" :placeholder="true" titleStyle="font-weight:bold;color:#000000"></u-navbar>
+		<view class="" style="border: 1px solid #eba869; width: 670rpx; margin: auto; margin-top: 32rpx">
+			<u-search
+				bgColor="transparent"
+				v-model="val"
+				shape="square"
+				:showAction="true"
+				actionText="搜索"
+				@custom="searchBtn"
+				:actionStyle="{ color: '#985741', fontSize: '28rpx', fontWeight: 'bold', backgroundColor: '#fff9e9' }"
+			></u-search>
+		</view>
+		<view class="fg_box" @click="lawBtn(item.id)" v-for="item in lawList" :key="item.id">
+			<view class="">
+				<image style="width: 180rpx; height: 266rpx" src="https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/fg_img.png"></image>
+			</view>
+			<view class="">
+				<view class="title">{{ item.title }}</view>
+				<view style="font-size: 24rpx; color: rgba(68, 68, 68, 0.4)">{{ item.create_date }}</view>
+			</view>
+			<view class="">
+				<image :src="item.img" style="width: 40rpx; height: 40rpx"></image>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+let that;
+export default {
+	data() {
+		return {
+			val: '',
+			lawList: []
+		};
+	},
+	onLoad() {
+		that = this;
+		this.getArchives();
+	},
+	methods: {
+		getArchives() {
+			uni.request({
+				url: 'https://huli-app.wenlvti.net/addons/cms/api.archives/index',
+				data: {
+					page: 1,
+					channel: 35,
+					model: -1,
+					menu_index: 1
+				},
+				success: function (res) {
+					that.lawList = res.data.data.pageList.data;
+					// console.log(that.lawList, 545);
+				}
+			});
+		},
+		searchBtn() {
+			uni.request({
+				url: 'https://huli-app.wenlvti.net//addons/cms/api.search/index',
+				data: {
+					model: 6,
+					page: 1,
+					orderby: 'default',
+					orderway: 'asc',
+					q: this.val
+				},
+				success: function (res) {
+					console.log(res, 545);
+					that.lawList = res.data.data.pageList.data;
+				}
+			});
+		},
+		lawBtn(id) {
+			uni.navigateTo({
+				url: '/index_fenbao/fuWu/FaLv/XiangQing?id=' + id
+			});
+		}
+	}
+};
+</script>
+
+<style>
+.box {
+	width: 100%;
+	padding-bottom: 50rpx;
+	/* 	background-image: url('https://huli-app.wenlvti.net/app_static/WenWuGuanJia/image/xbg_2.png');
+	background-size: 100% 100%;
+	background-attachment: fixed;
+	background-repeat: repeat-y; */
+	min-height: 100%;
+	height: auto;
+	background-color: #fff9e9;
+}
+/deep/.u-input--radius.data-v-113bc24f,
+.u-input--square.data-v-113bc24f {
+	border: 1px solid #eba869 !important;
+}
+.fg_box {
+	display: flex;
+	align-items: center;
+	justify-content: space-around;
+	margin: auto;
+	margin-top: 40rpx;
+	width: 670rpx;
+	height: 322rpx;
+	border: 2rpx solid #ebc9a9;
+}
+.title {
+	width: 346rpx;
+	/* height: 96rpx; */
+	font-size: 32rpx;
+	color: #444444;
+	line-height: 48rpx;
+}
+.u-search__action--active.data-v-0a306a29 {
+	height: 60rpx !important;
+	line-height: 60rpx !important;
+}
+.u-search__action--active.data-v-2d141374 {
+	height: 60rpx !important;
+	line-height: 60rpx !important;
+}
+</style>

Dosya farkı çok büyük olduğundan ihmal edildi
+ 84 - 0
index_fenbao/fuWu/FaLv/XiangQing.vue


+ 0 - 0
index_fenbao/fuWu/baoMing/baoMing.vue


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor