Browse Source

feat(app): 对接设计游学线下多的设计传播内容

EvilDragon 4 months ago
parent
commit
dd69ed8b25
40 changed files with 2696 additions and 134 deletions
  1. 27 0
      packages/app/src/components/u-parse/components/wxParseAudio.vue
  2. 86 0
      packages/app/src/components/u-parse/components/wxParseImg.vue
  3. 107 0
      packages/app/src/components/u-parse/components/wxParseTemplate0.vue
  4. 99 0
      packages/app/src/components/u-parse/components/wxParseTemplate1.vue
  5. 97 0
      packages/app/src/components/u-parse/components/wxParseTemplate10.vue
  6. 87 0
      packages/app/src/components/u-parse/components/wxParseTemplate11.vue
  7. 98 0
      packages/app/src/components/u-parse/components/wxParseTemplate2.vue
  8. 98 0
      packages/app/src/components/u-parse/components/wxParseTemplate3.vue
  9. 98 0
      packages/app/src/components/u-parse/components/wxParseTemplate4.vue
  10. 98 0
      packages/app/src/components/u-parse/components/wxParseTemplate5.vue
  11. 98 0
      packages/app/src/components/u-parse/components/wxParseTemplate6.vue
  12. 98 0
      packages/app/src/components/u-parse/components/wxParseTemplate7.vue
  13. 98 0
      packages/app/src/components/u-parse/components/wxParseTemplate8.vue
  14. 98 0
      packages/app/src/components/u-parse/components/wxParseTemplate9.vue
  15. 15 0
      packages/app/src/components/u-parse/components/wxParseVideo.vue
  16. 261 0
      packages/app/src/components/u-parse/libs/html2json.js
  17. 156 0
      packages/app/src/components/u-parse/libs/htmlparser.js
  18. 195 0
      packages/app/src/components/u-parse/libs/wxDiscode.js
  19. 102 0
      packages/app/src/components/u-parse/readme.md
  20. 232 0
      packages/app/src/components/u-parse/u-parse.css
  21. 118 0
      packages/app/src/components/u-parse/u-parse.vue
  22. 5 0
      packages/app/src/core/libs/net-images.ts
  23. 4 2
      packages/app/src/core/libs/requests.ts
  24. 9 0
      packages/app/src/core/models/moment.ts
  25. 26 15
      packages/app/src/pages/home/classmates-detail/index.vue
  26. 27 11
      packages/app/src/pages/home/classmates/index.vue
  27. 11 1
      packages/app/src/pages/home/components/article.vue
  28. 16 10
      packages/app/src/pages/home/components/class-item.vue
  29. 33 14
      packages/app/src/pages/home/components/elegant-info-card.vue
  30. 52 36
      packages/app/src/pages/home/components/offline-activity-item.vue
  31. 52 20
      packages/app/src/pages/home/offline-activity/index.vue
  32. 27 9
      packages/app/src/pages/home/offline-activity/list/index.vue
  33. 25 6
      packages/app/src/pages/home/spread/design-awards/index.vue
  34. 22 4
      packages/app/src/pages/home/spread/index.vue
  35. 9 6
      packages/app/src/pages/home/study-tour/index.vue
  36. 4 0
      packages/assets/src/assets/svgs/index.ts
  37. 2 0
      packages/assets/src/assets/svgs/leaderboardText.ts
  38. 1 0
      packages/assets/src/assets/svgs/leaderboard_text.svg
  39. 3 0
      packages/assets/src/assets/svgs/strip.svg
  40. 2 0
      packages/assets/src/assets/svgs/strip.ts

+ 27 - 0
packages/app/src/components/u-parse/components/wxParseAudio.vue

@@ -0,0 +1,27 @@
+<template>
+  <!--增加audio标签支持-->
+  <audio
+    :id="node.attr.id"
+    :class="node.classStr"
+    :style="node.styleStr"
+    :src="node.attr.src"
+    :loop="node.attr.loop"
+    :poster="node.attr.poster"
+    :name="node.attr.name"
+    :author="node.attr.author"
+    controls></audio>
+</template>
+
+<script>
+export default {
+  name: 'wxParseAudio',
+  props: {
+    node: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+};
+</script>

+ 86 - 0
packages/app/src/components/u-parse/components/wxParseImg.vue

@@ -0,0 +1,86 @@
+<template>
+  <image
+    :mode="node.attr.mode"
+    :lazy-load="node.attr.lazyLoad"
+    :class="node.classStr"
+    :style="newStyleStr || node.styleStr"
+    :data-src="node.attr.src"
+    :src="node.attr.src"
+    @tap="wxParseImgTap"
+    @load="wxParseImgLoad"
+    />
+</template>
+
+<script>
+export default {
+  name: 'wxParseImg',
+  data() {
+    return {
+      newStyleStr: '',
+      preview: true,
+    };
+  },
+  props: {
+    node: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  methods: {
+    wxParseImgTap(e) {
+      if (!this.preview) return;
+      const { src } = e.currentTarget.dataset;
+      if (!src) return;
+      let parent = this.$parent;
+      while(!parent.preview || typeof parent.preview !== 'function') {// TODO 遍历获取父节点执行方法
+      	parent = parent.$parent;
+      }
+      parent.preview(src, e);
+    },
+    // 图片视觉宽高计算函数区
+    wxParseImgLoad(e) {
+      const { src } = e.currentTarget.dataset;
+      if (!src) return;
+      const { width, height } = e.mp.detail;
+      const recal = this.wxAutoImageCal(width, height);
+      const { imageheight, imageWidth } = recal;
+      const { padding, mode } = this.node.attr;
+      const { styleStr } = this.node;
+      const imageHeightStyle = mode === 'widthFix' ? '' : `height: ${imageheight}px;`;
+      this.newStyleStr = `${styleStr}; ${imageHeightStyle}; width: ${imageWidth}px; padding: 0 ${+padding}px;`;
+    },
+    // 计算视觉优先的图片宽高
+    wxAutoImageCal(originalWidth, originalHeight) {
+      // 获取图片的原始长宽
+      const { padding } = this.node.attr;
+      const windowWidth = this.node.$screen.width - (2 * padding);
+      const results = {};
+
+      if (originalWidth < 60 || originalHeight < 60) {
+        const { src } = this.node.attr;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.removeImageUrl(src);
+        this.preview = false;
+      }
+
+      // 判断按照那种方式进行缩放
+      if (originalWidth > windowWidth) {
+        // 在图片width大于手机屏幕width时候
+        results.imageWidth = windowWidth;
+        results.imageheight = windowWidth * (originalHeight / originalWidth);
+      } else {
+        // 否则展示原来的数据
+        results.imageWidth = originalWidth;
+        results.imageheight = originalHeight;
+      }
+
+      return results;
+    },
+  },
+};
+</script>

+ 107 - 0
packages/app/src/components/u-parse/components/wxParseTemplate0.vue

@@ -0,0 +1,107 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--table类型-->
+			<block v-else-if="node.tag == 'table'">
+				<view :class="node.classStr" class="table" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate1';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate0',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;// TODO currentTarget才有dataset
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {// TODO 遍历获取父节点执行方法
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 99 - 0
packages/app/src/components/u-parse/components/wxParseTemplate1.vue

@@ -0,0 +1,99 @@
+<template>
+	<view :class="(node.tag == 'li' ? node.classStr : (node.node==='text'?'text':''))">
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<!-- <view :class="node.classStr" :style="node.styleStr"> -->
+				<view :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate2';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate1',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 97 - 0
packages/app/src/components/u-parse/components/wxParseTemplate10.vue

@@ -0,0 +1,97 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate11';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate10',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 87 - 0
packages/app/src/components/u-parse/components/wxParseTemplate11.vue

@@ -0,0 +1,87 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<!--button类型-->
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					{{node.text}}
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					{{node.text}}
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					{{node.text}}
+				</view>
+			</block>
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate11',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
packages/app/src/components/u-parse/components/wxParseTemplate2.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate3';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate2',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
packages/app/src/components/u-parse/components/wxParseTemplate3.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate4';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate3',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
packages/app/src/components/u-parse/components/wxParseTemplate4.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate5';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate4',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
packages/app/src/components/u-parse/components/wxParseTemplate5.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate6';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate5',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
packages/app/src/components/u-parse/components/wxParseTemplate6.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate7';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate6',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
packages/app/src/components/u-parse/components/wxParseTemplate7.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate8';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate7',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
packages/app/src/components/u-parse/components/wxParseTemplate8.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate9';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate8',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 98 - 0
packages/app/src/components/u-parse/components/wxParseTemplate9.vue

@@ -0,0 +1,98 @@
+<template>
+	<view>
+		<!--判断是否是标签节点-->
+		<block v-if="node.node == 'element'">
+			<block v-if="node.tag == 'button'">
+				<button type="default" size="mini">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</button>
+			</block>
+
+			<!--li类型-->
+			<block v-else-if="node.tag == 'li'">
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--video类型-->
+			<block v-else-if="node.tag == 'video'">
+				<wx-parse-video :node="node" />
+			</block>
+
+			<!--audio类型-->
+			<block v-else-if="node.tag == 'audio'">
+				<wx-parse-audio :node="node" />
+			</block>
+
+			<!--img类型-->
+			<block v-else-if="node.tag == 'img'">
+				<wx-parse-img :node="node" />
+			</block>
+
+			<!--a类型-->
+			<block v-else-if="node.tag == 'a'">
+				<view @click="wxParseATap" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+			<!--br类型-->
+			<block v-else-if="node.tag == 'br'">
+				<text>\n</text>
+			</block>
+
+			<!--其他标签-->
+			<block v-else>
+				<view :class="node.classStr" :style="node.styleStr">
+					<block v-for="(node, index) of node.nodes" :key="index">
+						<wx-parse-template :node="node" />
+					</block>
+				</view>
+			</block>
+
+		</block>
+
+		<!--判断是否是文本节点-->
+		<block v-else-if="node.node == 'text'">{{node.text}}</block>
+	</view>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate10';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+
+	export default {
+		name: 'wxParseTemplate9',
+		props: {
+			node: {},
+		},
+		components: {
+			wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+		},
+		methods: {
+			wxParseATap(e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e);
+			},
+		},
+	};
+</script>

+ 15 - 0
packages/app/src/components/u-parse/components/wxParseVideo.vue

@@ -0,0 +1,15 @@
+<template>
+  <!--增加video标签支持,并循环添加-->
+  <view :class="node.classStr" :style="node.styleStr">
+    <video :class="node.classStr" class="video-video" :src="node.attr.src"></video>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'wxParseVideo',
+  props: {
+    node: {},
+  },
+};
+</script>

+ 261 - 0
packages/app/src/components/u-parse/libs/html2json.js

@@ -0,0 +1,261 @@
+/**
+ * html2Json 改造来自: https://github.com/Jxck/html2json
+ *
+ *
+ * author: Di (微信小程序开发工程师)
+ * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
+ *               垂直微信小程序开发交流社区
+ *
+ * github地址: https://github.com/icindy/wxParse
+ *
+ * for: 微信小程序富文本解析
+ * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
+ */
+
+import wxDiscode from './wxDiscode';
+import HTMLParser from './htmlparser';
+
+function makeMap(str) {
+  const obj = {};
+  const items = str.split(',');
+  for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
+  return obj;
+}
+
+// Block Elements - HTML 5
+const block = makeMap('br,code,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,ins,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
+const inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,button,cite,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)
+const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
+
+function removeDOCTYPE(html) {
+  const isDocument = /<body.*>([^]*)<\/body>/.test(html);
+  return isDocument ? RegExp.$1 : html;
+}
+
+function trimHtml(html) {
+  return html
+    .replace(/<!--.*?-->/gi, '')
+    .replace(/\/\*.*?\*\//gi, '')
+    .replace(/[ ]+</gi, '<')
+    .replace(/<script[^]*<\/script>/gi, '')
+    .replace(/<style[^]*<\/style>/gi, '');
+}
+
+function getScreenInfo() {
+  const screen = {};
+  wx.getSystemInfo({
+    success: (res) => {
+      screen.width = res.windowWidth;
+      screen.height = res.windowHeight;
+    },
+  });
+  return screen;
+}
+
+function html2json(html, customHandler, imageProp, host) {
+  // 处理字符串
+  html = removeDOCTYPE(html);
+  html = trimHtml(html);
+  html = wxDiscode.strDiscode(html);
+  // 生成node节点
+  const bufArray = [];
+  const results = {
+    nodes: [],
+    imageUrls: [],
+  };
+
+	const screen = getScreenInfo();
+  function Node(tag) {
+    this.node = 'element';
+    this.tag = tag;
+		
+		this.$screen = screen;
+  }
+
+  HTMLParser(html, {
+    start(tag, attrs, unary) {
+      // node for this element
+      const node = new Node(tag);
+
+      if (bufArray.length !== 0) {
+        const parent = bufArray[0];
+        if (parent.nodes === undefined) {
+          parent.nodes = [];
+        }
+      }
+
+      if (block[tag]) {
+        node.tagType = 'block';
+      } else if (inline[tag]) {
+        node.tagType = 'inline';
+      } else if (closeSelf[tag]) {
+        node.tagType = 'closeSelf';
+      }
+
+      node.attr = attrs.reduce((pre, attr) => {
+        const { name } = attr;
+        let { value } = attr;
+        if (name === 'class') {
+          node.classStr = value;
+        }
+        // has multi attibutes
+        // make it array of attribute
+        if (name === 'style') {
+          node.styleStr = value;
+        }
+        if (value.match(/ /)) {
+          value = value.split(' ');
+        }
+
+        // if attr already exists
+        // merge it
+        if (pre[name]) {
+          if (Array.isArray(pre[name])) {
+            // already array, push to last
+            pre[name].push(value);
+          } else {
+            // single value, make it array
+            pre[name] = [pre[name], value];
+          }
+        } else {
+          // not exist, put it
+          pre[name] = value;
+        }
+
+        return pre;
+      }, {});
+
+      // 优化样式相关属性
+      if (node.classStr) {
+        node.classStr += ` ${node.tag}`;
+      } else {
+        node.classStr = node.tag;
+      }
+      if (node.tagType === 'inline') {
+        node.classStr += ' inline';
+      }
+
+      // 对img添加额外数据
+      if (node.tag === 'img') {
+        let imgUrl = node.attr.src;
+        imgUrl = wxDiscode.urlToHttpUrl(imgUrl, imageProp.domain);
+        Object.assign(node.attr, imageProp, {
+          src: imgUrl || '',
+        });
+        if (imgUrl) {
+          results.imageUrls.push(imgUrl);
+        }
+      }
+
+      // 处理a标签属性
+      if (node.tag === 'a') {
+        node.attr.href = node.attr.href || '';
+      }
+
+      // 处理font标签样式属性
+      if (node.tag === 'font') {
+        const fontSize = [
+          'x-small',
+          'small',
+          'medium',
+          'large',
+          'x-large',
+          'xx-large',
+          '-webkit-xxx-large',
+        ];
+        const styleAttrs = {
+          color: 'color',
+          face: 'font-family',
+          size: 'font-size',
+        };
+        if (!node.styleStr) node.styleStr = '';
+        Object.keys(styleAttrs).forEach((key) => {
+          if (node.attr[key]) {
+            const value = key === 'size' ? fontSize[node.attr[key] - 1] : node.attr[key];
+            node.styleStr += `${styleAttrs[key]}: ${value};`;
+          }
+        });
+      }
+
+      // 临时记录source资源
+      if (node.tag === 'source') {
+        results.source = node.attr.src;
+      }
+
+      if (customHandler.start) {
+        customHandler.start(node, results);
+      }
+
+      if (unary) {
+        // if this tag doesn't have end tag
+        // like <img src="hoge.png"/>
+        // add to parents
+        const parent = bufArray[0] || results;
+        if (parent.nodes === undefined) {
+          parent.nodes = [];
+        }
+        parent.nodes.push(node);
+      } else {
+        bufArray.unshift(node);
+      }
+    },
+    end(tag) {
+      // merge into parent tag
+      const node = bufArray.shift();
+      if (node.tag !== tag) {
+        console.error('invalid state: mismatch end tag');
+      }
+
+      // 当有缓存source资源时于于video补上src资源
+      if (node.tag === 'video' && results.source) {
+        node.attr.src = results.source;
+        delete results.source;
+      }
+
+      if (customHandler.end) {
+        customHandler.end(node, results);
+      }
+
+      if (bufArray.length === 0) {
+        results.nodes.push(node);
+      } else {
+        const parent = bufArray[0];
+        if (!parent.nodes) {
+          parent.nodes = [];
+        }
+        parent.nodes.push(node);
+      }
+    },
+    chars(text) {
+      if (!text.trim()) return;
+
+      const node = {
+        node: 'text',
+        text,
+      };
+
+      if (customHandler.chars) {
+        customHandler.chars(node, results);
+      }
+
+      if (bufArray.length === 0) {
+        results.nodes.push(node);
+      } else {
+        const parent = bufArray[0];
+        if (parent.nodes === undefined) {
+          parent.nodes = [];
+        }
+        parent.nodes.push(node);
+      }
+    },
+  });
+
+  return results;
+}
+
+export default html2json;

+ 156 - 0
packages/app/src/components/u-parse/libs/htmlparser.js

@@ -0,0 +1,156 @@
+/**
+ *
+ * htmlParser改造自: https://github.com/blowsie/Pure-JavaScript-HTML5-Parser
+ *
+ * author: Di (微信小程序开发工程师)
+ * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
+ *               垂直微信小程序开发交流社区
+ *
+ * github地址: https://github.com/icindy/wxParse
+ *
+ * for: 微信小程序富文本解析
+ * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
+ */
+// Regular Expressions for parsing tags and attributes
+
+const startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z0-9_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
+const endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
+const attr = /([a-zA-Z0-9_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
+
+function makeMap(str) {
+  const obj = {};
+  const items = str.split(',');
+  for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
+  return obj;
+}
+
+// Empty Elements - HTML 5
+const empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr');
+
+// Block Elements - HTML 5
+const block = makeMap('address,code,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,ins,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
+const inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,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)
+const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
+
+// Attributes that have their values filled in disabled="disabled"
+const fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
+
+function HTMLParser(html, handler) {
+  let index;
+  let chars;
+  let match;
+  let last = html;
+  const stack = [];
+
+  stack.last = () => stack[stack.length - 1];
+
+  function parseEndTag(tag, tagName) {
+    // If no tag name is provided, clean shop
+    let pos;
+    if (!tagName) {
+      pos = 0;
+    } else {
+      // Find the closest opened tag of the same type
+      tagName = tagName.toLowerCase();
+      for (pos = stack.length - 1; pos >= 0; pos -= 1) {
+        if (stack[pos] === tagName) break;
+      }
+    }
+    if (pos >= 0) {
+      // Close all the open elements, up the stack
+      for (let i = stack.length - 1; i >= pos; i -= 1) {
+        if (handler.end) handler.end(stack[i]);
+      }
+
+      // Remove the open elements from the stack
+      stack.length = pos;
+    }
+  }
+
+  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) {
+      const attrs = [];
+
+      rest.replace(attr, function genAttr(matches, name) {
+        const value = arguments[2] || arguments[3] || arguments[4] || (fillAttrs[name] ? name : '');
+
+        attrs.push({
+          name,
+          value,
+          escaped: value.replace(/(^|[^\\])"/g, '$1\\"'), // "
+        });
+      });
+
+      if (handler.start) {
+        handler.start(tagName, attrs, unary);
+      }
+    }
+  }
+
+  while (html) {
+    chars = true;
+
+    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('<');
+      let text = '';
+      while (index === 0) {
+        text += '<';
+        html = html.substring(1);
+        index = html.indexOf('<');
+      }
+      text += index < 0 ? html : html.substring(0, index);
+      html = index < 0 ? '' : html.substring(index);
+
+      if (handler.chars) handler.chars(text);
+    }
+
+    if (html === last) throw new Error(`Parse Error: ${html}`);
+    last = html;
+  }
+
+  // Clean up any remaining tags
+  parseEndTag();
+}
+
+export default HTMLParser;

+ 195 - 0
packages/app/src/components/u-parse/libs/wxDiscode.js

@@ -0,0 +1,195 @@
+// HTML 支持的数学符号
+function strNumDiscode(str) {
+  str = str.replace(/&forall;/g, '∀');
+  str = str.replace(/&part;/g, '∂');
+  str = str.replace(/&exist;/g, '∃');
+  str = str.replace(/&empty;/g, '∅');
+  str = str.replace(/&nabla;/g, '∇');
+  str = str.replace(/&isin;/g, '∈');
+  str = str.replace(/&notin;/g, '∉');
+  str = str.replace(/&ni;/g, '∋');
+  str = str.replace(/&prod;/g, '∏');
+  str = str.replace(/&sum;/g, '∑');
+  str = str.replace(/&minus;/g, '−');
+  str = str.replace(/&lowast;/g, '∗');
+  str = str.replace(/&radic;/g, '√');
+  str = str.replace(/&prop;/g, '∝');
+  str = str.replace(/&infin;/g, '∞');
+  str = str.replace(/&ang;/g, '∠');
+  str = str.replace(/&and;/g, '∧');
+  str = str.replace(/&or;/g, '∨');
+  str = str.replace(/&cap;/g, '∩');
+  str = str.replace(/&cup;/g, '∪');
+  str = str.replace(/&int;/g, '∫');
+  str = str.replace(/&there4;/g, '∴');
+  str = str.replace(/&sim;/g, '∼');
+  str = str.replace(/&cong;/g, '≅');
+  str = str.replace(/&asymp;/g, '≈');
+  str = str.replace(/&ne;/g, '≠');
+  str = str.replace(/&le;/g, '≤');
+  str = str.replace(/&ge;/g, '≥');
+  str = str.replace(/&sub;/g, '⊂');
+  str = str.replace(/&sup;/g, '⊃');
+  str = str.replace(/&nsub;/g, '⊄');
+  str = str.replace(/&sube;/g, '⊆');
+  str = str.replace(/&supe;/g, '⊇');
+  str = str.replace(/&oplus;/g, '⊕');
+  str = str.replace(/&otimes;/g, '⊗');
+  str = str.replace(/&perp;/g, '⊥');
+  str = str.replace(/&sdot;/g, '⋅');
+  return str;
+}
+
+// HTML 支持的希腊字母
+function strGreeceDiscode(str) {
+  str = str.replace(/&Alpha;/g, 'Α');
+  str = str.replace(/&Beta;/g, 'Β');
+  str = str.replace(/&Gamma;/g, 'Γ');
+  str = str.replace(/&Delta;/g, 'Δ');
+  str = str.replace(/&Epsilon;/g, 'Ε');
+  str = str.replace(/&Zeta;/g, 'Ζ');
+  str = str.replace(/&Eta;/g, 'Η');
+  str = str.replace(/&Theta;/g, 'Θ');
+  str = str.replace(/&Iota;/g, 'Ι');
+  str = str.replace(/&Kappa;/g, 'Κ');
+  str = str.replace(/&Lambda;/g, 'Λ');
+  str = str.replace(/&Mu;/g, 'Μ');
+  str = str.replace(/&Nu;/g, 'Ν');
+  str = str.replace(/&Xi;/g, 'Ν');
+  str = str.replace(/&Omicron;/g, 'Ο');
+  str = str.replace(/&Pi;/g, 'Π');
+  str = str.replace(/&Rho;/g, 'Ρ');
+  str = str.replace(/&Sigma;/g, 'Σ');
+  str = str.replace(/&Tau;/g, 'Τ');
+  str = str.replace(/&Upsilon;/g, 'Υ');
+  str = str.replace(/&Phi;/g, 'Φ');
+  str = str.replace(/&Chi;/g, 'Χ');
+  str = str.replace(/&Psi;/g, 'Ψ');
+  str = str.replace(/&Omega;/g, 'Ω');
+
+  str = str.replace(/&alpha;/g, 'α');
+  str = str.replace(/&beta;/g, 'β');
+  str = str.replace(/&gamma;/g, 'γ');
+  str = str.replace(/&delta;/g, 'δ');
+  str = str.replace(/&epsilon;/g, 'ε');
+  str = str.replace(/&zeta;/g, 'ζ');
+  str = str.replace(/&eta;/g, 'η');
+  str = str.replace(/&theta;/g, 'θ');
+  str = str.replace(/&iota;/g, 'ι');
+  str = str.replace(/&kappa;/g, 'κ');
+  str = str.replace(/&lambda;/g, 'λ');
+  str = str.replace(/&mu;/g, 'μ');
+  str = str.replace(/&nu;/g, 'ν');
+  str = str.replace(/&xi;/g, 'ξ');
+  str = str.replace(/&omicron;/g, 'ο');
+  str = str.replace(/&pi;/g, 'π');
+  str = str.replace(/&rho;/g, 'ρ');
+  str = str.replace(/&sigmaf;/g, 'ς');
+  str = str.replace(/&sigma;/g, 'σ');
+  str = str.replace(/&tau;/g, 'τ');
+  str = str.replace(/&upsilon;/g, 'υ');
+  str = str.replace(/&phi;/g, 'φ');
+  str = str.replace(/&chi;/g, 'χ');
+  str = str.replace(/&psi;/g, 'ψ');
+  str = str.replace(/&omega;/g, 'ω');
+  str = str.replace(/&thetasym;/g, 'ϑ');
+  str = str.replace(/&upsih;/g, 'ϒ');
+  str = str.replace(/&piv;/g, 'ϖ');
+  str = str.replace(/&middot;/g, '·');
+  return str;
+}
+
+function strcharacterDiscode(str) {
+  // 加入常用解析
+  str = str.replace(/&nbsp;/g, ' ');
+  str = str.replace(/&ensp;/g, ' ');
+  str = str.replace(/&emsp;/g, ' ');
+  str = str.replace(/&quot;/g, "'");
+  str = str.replace(/&amp;/g, '&');
+  str = str.replace(/&lt;/g, '<');
+  str = str.replace(/&gt;/g, '>');
+  str = str.replace(/&#8226;/g, '•');
+
+  return str;
+}
+
+// HTML 支持的其他实体
+function strOtherDiscode(str) {
+  str = str.replace(/&OElig;/g, 'Œ');
+  str = str.replace(/&oelig;/g, 'œ');
+  str = str.replace(/&Scaron;/g, 'Š');
+  str = str.replace(/&scaron;/g, 'š');
+  str = str.replace(/&Yuml;/g, 'Ÿ');
+  str = str.replace(/&fnof;/g, 'ƒ');
+  str = str.replace(/&circ;/g, 'ˆ');
+  str = str.replace(/&tilde;/g, '˜');
+  str = str.replace(/&ensp;/g, '');
+  str = str.replace(/&emsp;/g, '');
+  str = str.replace(/&thinsp;/g, '');
+  str = str.replace(/&zwnj;/g, '');
+  str = str.replace(/&zwj;/g, '');
+  str = str.replace(/&lrm;/g, '');
+  str = str.replace(/&rlm;/g, '');
+  str = str.replace(/&ndash;/g, '–');
+  str = str.replace(/&mdash;/g, '—');
+  str = str.replace(/&lsquo;/g, '‘');
+  str = str.replace(/&rsquo;/g, '’');
+  str = str.replace(/&sbquo;/g, '‚');
+  str = str.replace(/&ldquo;/g, '“');
+  str = str.replace(/&rdquo;/g, '”');
+  str = str.replace(/&bdquo;/g, '„');
+  str = str.replace(/&dagger;/g, '†');
+  str = str.replace(/&Dagger;/g, '‡');
+  str = str.replace(/&bull;/g, '•');
+  str = str.replace(/&hellip;/g, '…');
+  str = str.replace(/&permil;/g, '‰');
+  str = str.replace(/&prime;/g, '′');
+  str = str.replace(/&Prime;/g, '″');
+  str = str.replace(/&lsaquo;/g, '‹');
+  str = str.replace(/&rsaquo;/g, '›');
+  str = str.replace(/&oline;/g, '‾');
+  str = str.replace(/&euro;/g, '€');
+  str = str.replace(/&trade;/g, '™');
+
+  str = str.replace(/&larr;/g, '←');
+  str = str.replace(/&uarr;/g, '↑');
+  str = str.replace(/&rarr;/g, '→');
+  str = str.replace(/&darr;/g, '↓');
+  str = str.replace(/&harr;/g, '↔');
+  str = str.replace(/&crarr;/g, '↵');
+  str = str.replace(/&lceil;/g, '⌈');
+  str = str.replace(/&rceil;/g, '⌉');
+
+  str = str.replace(/&lfloor;/g, '⌊');
+  str = str.replace(/&rfloor;/g, '⌋');
+  str = str.replace(/&loz;/g, '◊');
+  str = str.replace(/&spades;/g, '♠');
+  str = str.replace(/&clubs;/g, '♣');
+  str = str.replace(/&hearts;/g, '♥');
+
+  str = str.replace(/&diams;/g, '♦');
+  str = str.replace(/&#39;/g, "'");
+  return str;
+}
+
+function strDiscode(str) {
+  str = strNumDiscode(str);
+  str = strGreeceDiscode(str);
+  str = strcharacterDiscode(str);
+  str = strOtherDiscode(str);
+  return str;
+}
+
+function urlToHttpUrl(url, domain) {
+  if (/^\/\//.test(url)) {
+    return `https:${url}`;
+  } else if (/^\//.test(url)) {
+    return `https://${domain}${url}`;
+  }
+  return url;
+}
+
+export default {
+  strDiscode,
+  urlToHttpUrl,
+};

+ 102 - 0
packages/app/src/components/u-parse/readme.md

@@ -0,0 +1,102 @@
+## uParse 适用于 uni-app/mpvue 的富文本解析组件
+
+> 支持 Html、Markdown 解析,Fork自: [mpvue-wxParse](https://github.com/F-loat/mpvue-wxParse)
+
+
+## 属性
+
+| 名称             | 类型          | 默认值        | 描述               |
+| -----------------|--------------- | ------------- | ----------------  |
+| loading          | Boolean        | false         | 数据加载状态       |
+| className        | String         | —             | 自定义 class 名称  |
+| content          | String         | —             | 渲染内容           |
+| noData           | String         | 数据不能为空   | 空数据时的渲染展示  |
+| startHandler     | Function       | 见源码         | 自定义 parser 函数 |
+| endHandler       | Function       | null          | 自定义 parser 函数 |
+| charsHandler     | Function       | null          | 自定义 parser 函数 |
+| imageProp        | Object         | 见下文        | 图片相关参数        |
+
+### 自定义 parser 函数具体介绍
+
+* 传入的参数为当前节点 `node` 对象及解析结果 `results` 对象,例如 `startHandler(node, results)`
+* 无需返回值,通过对传入的参数直接操作来完成需要的改动
+* 自定义函数会在原解析函数处理之后执行
+
+### imageProp 对象具体属性
+
+| 名称              | 类型           | 默认值        | 描述                |
+| -----------------|--------------- | ------------- | ------------------ |
+| mode             | String         | 'aspectFit'   | 图片裁剪、缩放的模式 |
+| padding          | Number         | 0             | 图片内边距          |
+| lazyLoad         | Boolean        | false         | 图片懒加载          |
+| domain           | String         | ''            | 图片服务域名        |
+
+## 事件
+
+| 名称             | 参数              | 描述              |
+| -----------------|----------------- | ----------------  |
+| preview          | 图片地址,原始事件 | 预览图片时触发     |
+| navigate         | 链接地址,原始事件 | 点击链接时触发     |
+
+## 基本使用方法
+
+
+``` vue
+<template>
+  <div>
+    <u-parse :content="article" @preview="preview" @navigate="navigate" />
+  </div>
+</template>
+
+<script>
+import uParse from '@/components/u-parse/u-parse.vue'
+
+export default {
+  components: {
+    uParse
+  },
+  data () {
+    return {
+      article: '<div>我是HTML代码</div>'
+    }
+  },
+  methods: {
+    preview(src, e) {
+      // do something
+    },
+    navigate(href, e) {
+      // do something
+    }
+  }
+}
+</script>
+
+<style>
+@import url("@/components/u-parse/u-parse.css");
+</style>
+```
+
+
+## 渲染 Markdown
+
+> 先将 markdown 转换为 html 即可
+
+```
+npm install marked
+```
+
+``` js
+import marked from 'marked'
+import uParse from '@/components/u-parse/u-parse.vue'
+
+export default {
+  components: {
+    uParse
+  },
+  data () {
+    return {
+      article: marked(`#hello, markdown!`)
+    }
+  }
+}
+```

+ 232 - 0
packages/app/src/components/u-parse/u-parse.css

@@ -0,0 +1,232 @@
+/**
+ * author: Di (微信小程序开发工程师)
+ * organization: WeAppDev(微信小程序开发论坛)(http://weappdev.com)
+ *         垂直微信小程序开发交流社区
+ *
+ * github地址: https://github.com/icindy/wxParse
+ *
+ * for: 微信小程序富文本解析
+ * detail : http://weappdev.com/t/wxparse-alpha0-1-html-markdown/184
+ */
+
+.wxParse {
+  width: 100%;
+  font-family: Helvetica, sans-serif;
+  font-size: 30upx;
+  color: #666;
+  line-height: 1.8;
+}
+
+.wxParse view {
+  word-break: hyphenate;
+}
+
+.wxParse .inline {
+  display: inline;
+  margin: 0;
+  padding: 0;
+}
+
+.wxParse .div {
+  margin: 0;
+  padding: 0;
+}
+
+.wxParse .h1 .text {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+.wxParse .h2 .text {
+  font-size: 1.5em;
+  margin: 0.83em 0;
+}
+.wxParse .h3 .text {
+  font-size: 1.17em;
+  margin: 1em 0;
+}
+.wxParse .h4 .text {
+  margin: 1.33em 0;
+}
+.wxParse .h5 .text {
+  font-size: 0.83em;
+  margin: 1.67em 0;
+}
+.wxParse .h6 .text {
+  font-size: 0.67em;
+  margin: 2.33em 0;
+}
+
+.wxParse .h1 .text,
+.wxParse .h2 .text,
+.wxParse .h3 .text,
+.wxParse .h4 .text,
+.wxParse .h5 .text,
+.wxParse .h6 .text,
+.wxParse .b,
+.wxParse .strong {
+  font-weight: bolder;
+}
+
+
+.wxParse .p {
+  margin: 1em 0;
+}
+
+.wxParse .i,
+.wxParse .cite,
+.wxParse .em,
+.wxParse .var,
+.wxParse .address {
+  font-style: italic;
+}
+
+.wxParse .pre,
+.wxParse .tt,
+.wxParse .code,
+.wxParse .kbd,
+.wxParse .samp {
+  font-family: monospace;
+}
+.wxParse .pre {
+  overflow: auto;
+  background: #f5f5f5;
+  padding: 16upx;
+  white-space: pre;
+  margin: 1em 0upx;
+}
+.wxParse .code {
+  display: inline;
+  background: #f5f5f5;
+}
+
+.wxParse .big {
+  font-size: 1.17em;
+}
+
+.wxParse .small,
+.wxParse .sub,
+.wxParse .sup {
+  font-size: 0.83em;
+}
+
+.wxParse .sub {
+  vertical-align: sub;
+}
+.wxParse .sup {
+  vertical-align: super;
+}
+
+.wxParse .s,
+.wxParse .strike,
+.wxParse .del {
+  text-decoration: line-through;
+}
+
+.wxParse .strong,
+.wxParse .s {
+  display: inline;
+}
+
+.wxParse .a {
+  color: deepskyblue;
+}
+
+.wxParse .video {
+  text-align: center;
+  margin: 22upx 0;
+}
+
+.wxParse .video-video {
+  width: 100%;
+}
+
+.wxParse .img {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  max-width: 100%;
+  overflow: hidden;
+}
+
+.wxParse .blockquote {
+  margin: 10upx 0;
+  padding: 22upx 0 22upx 22upx;
+  font-family: Courier, Calibri, "宋体";
+  background: #f5f5f5;
+  border-left: 6upx solid #dbdbdb;
+}
+.wxParse .blockquote .p {
+  margin: 0;
+}
+
+.wxParse .ul, .wxParse .ol {
+  display: block;
+  margin: 1em 0;
+  padding-left: 33upx;
+}
+.wxParse .ol {
+  list-style-type: disc;
+}
+.wxParse .ol {
+  list-style-type: decimal;
+}
+.wxParse .ol>weixin-parse-template,.wxParse .ul>weixin-parse-template {
+  display: list-item;
+  align-items: baseline;
+  text-align: match-parent;
+}
+
+.wxParse .ol>.li,.wxParse .ul>.li {
+  display: list-item;
+  align-items: baseline;
+  text-align: match-parent;
+}
+.wxParse .ul .ul, .wxParse .ol .ul {
+  list-style-type: circle;
+}
+.wxParse .ol .ol .ul, .wxParse .ol .ul .ul, .wxParse .ul .ol .ul, .wxParse .ul .ul .ul {
+    list-style-type: square;
+}
+
+.wxParse .u {
+  text-decoration: underline;
+}
+.wxParse .hide {
+  display: none;
+}
+.wxParse .del {
+  display: inline;
+}
+.wxParse .figure {
+  overflow: hidden;
+}
+
+.wxParse .table {
+  width: 100%;
+}
+.wxParse .thead, .wxParse .tfoot, .wxParse .tr {
+  display: flex;
+  flex-direction: row;
+}
+.wxParse .tr {
+  width:100%;
+  display: flex;
+  border-right: 2upx solid #e0e0e0;
+  border-bottom: 2upx solid #e0e0e0;
+}
+.wxParse .th,
+.wxParse .td {
+  display: flex;
+  width: 1276upx;
+  overflow: auto;
+  flex: 1;
+  padding: 11upx;
+  border-left: 2upx solid #e0e0e0;
+}
+.wxParse .td:last {
+  border-top: 2upx solid #e0e0e0;
+}
+.wxParse .th {
+  background: #f0f0f0;
+  border-top: 2upx solid #e0e0e0;
+}

+ 118 - 0
packages/app/src/components/u-parse/u-parse.vue

@@ -0,0 +1,118 @@
+<!--**
+ * forked from:https://github.com/F-loat/mpvue-wxParse
+ *
+ * github地址: https://github.com/dcloudio/uParse
+ *
+ * for: uni-app框架下 富文本解析
+ */-->
+
+<template>
+<!--基础元素-->
+<div class="wxParse" :class="className" v-if="!loading">
+  <block v-for="(node,index) of nodes" :key="index">
+    <wxParseTemplate :node="node" />
+  </block>
+</div>
+</template>
+
+<script>
+import HtmlToJson from './libs/html2json';
+import wxParseTemplate from './components/wxParseTemplate0';
+
+export default {
+  name: 'wxParse',
+  props: {
+    loading: {
+      type: Boolean,
+      default: false,
+    },
+    className: {
+      type: String,
+      default: '',
+    },
+    content: {
+      type: String,
+      default: '',
+    },
+    noData: {
+      type: String,
+      default: '<div style="color: red;">数据不能为空</div>',
+    },
+    startHandler: {
+      type: Function,
+      default() {
+        return (node) => {
+          node.attr.class = null;
+          node.attr.style = null;
+        };
+      },
+    },
+    endHandler: {
+      type: Function,
+      default: null,
+    },
+    charsHandler: {
+      type: Function,
+      default: null,
+    },
+    imageProp: {
+      type: Object,
+      default() {
+        return {
+          mode: 'aspectFit',
+          padding: 0,
+          lazyLoad: false,
+          domain: '',
+        };
+      },
+    },
+  },
+  components: {
+    wxParseTemplate,
+  },
+  data() {
+    return {
+      imageUrls: [],
+    };
+  },
+  computed: {
+    nodes() {
+      const {
+        content,
+        noData,
+        imageProp,
+        startHandler,
+        endHandler,
+        charsHandler,
+      } = this;
+      const parseData = content || noData;
+      const customHandler = {
+        start: startHandler,
+        end: endHandler,
+        chars: charsHandler,
+      };
+      const results = HtmlToJson(parseData, customHandler, imageProp, this);
+      this.imageUrls = results.imageUrls;
+      console.log(results)
+      return results.nodes;
+    },
+  },
+  methods: {
+    navigate(href, $event) {
+      this.$emit('navigate', href, $event);
+    },
+    preview(src, $event) {
+      if (!this.imageUrls.length) return;
+      wx.previewImage({
+        current: src,
+        urls: this.imageUrls,
+      });
+      this.$emit('preview', src, $event);
+    },
+    removeImageUrl(src) {
+      const { imageUrls } = this;
+      imageUrls.splice(imageUrls.indexOf(src), 1);
+    },
+  },
+};
+</script>

+ 5 - 0
packages/app/src/core/libs/net-images.ts

@@ -0,0 +1,5 @@
+export enum NetImages {
+  'default' = 'https://cdn.jsdelivr.net/gh/yangyang-yangyang/yangyang-yangyang.github.io@master/images/default.png',
+  'avatar' = 'https://cdn.jsdelivr.net/gh/yangyang-yangyang/yangyang-yangyang.github.io@master/images/avatar.png',
+  'NotContent' = 'https://image.zhuchaohui.com/zhucaohui/3819d411440c23cc9e4f4bd3a520325386d7f038ed6dfa7c2ba076bd5110d2d2.png',
+}

+ 4 - 2
packages/app/src/core/libs/requests.ts

@@ -1,6 +1,7 @@
 import { httpGet, httpPost, httpPut } from '../../utils/http'
 import { Schedule } from '../models/schedule'
 import {
+  Category,
   Content,
   DictType,
   MaterialDealer,
@@ -288,11 +289,12 @@ export const getMaterialDetail = (query: { id: string }) =>
 export const createMaterialsReferrer = (data) =>
   httpPost('/app-api/member/materials-referrer/create', data)
 export const getContents = (query: {
-  contentType: '1' | '2' | '3'
+  contentType?: '1' | '2' | '3'
   contentCategory?: string
   pageSize?: string
 }) => httpGet<{ list: Content[] }>('/app-api/member/content-manger/page', query)
-export const getAllCategories = () => httpGet('/app-api/member/categories/getAllCategories')
+export const getAllCategories = () =>
+  httpGet<Category[]>('/app-api/member/categories/getAllCategories')
 export const getContent = (query: { id: string }) =>
   httpGet<Content>('/app-api/member/content-manger/get', query)
 export const refreshToken = (refreshToken: string) =>

+ 9 - 0
packages/app/src/core/models/moment.ts

@@ -217,6 +217,15 @@ export interface Content {
   viewCount30Day: number
   createTime: string
 }
+export interface Category {
+  id: number
+  name: string
+  code: string
+  sort: number
+  parentId: any
+  level: number
+  children?: Category[]
+}
 export enum DictType {
   /**
    *  擅长空间类型

+ 26 - 15
packages/app/src/pages/home/classmates-detail/index.vue

@@ -1,22 +1,33 @@
-<route lang="yaml">
-style:
-  navigationBarTitleText: '详情'
-  navigationBarBackgroundColor: '#fff'
+<route lang="json">
+{
+  "style": {
+    "navigationBarTitleText": "详情",
+    "navigationBarBackgroundColor": "#fff"
+  }
+}
 </route>
 <script setup lang="ts">
-import { getClassmate, getMoment } from '@/core/libs/requests'
+import { logo } from '../../../core/libs/svgs'
+import { getContent } from '../../../core/libs/requests'
 import Article from '../components/article.vue'
-// import { Article } from '@pandora/ui'
 
-const { data, run } = useRequest(getClassmate)
-onMounted(run)
+const id = ref()
+const { data, run } = useRequest(() => getContent({ id: id.value }))
+onLoad(async (query: { id: string }) => {
+  id.value = query.id
+  await run()
+})
 </script>
 <template>
-  <!-- <view> -->
-  <Article :title="data?.title" :content="data?.content">
-    <template #avatar>1111</template>
-  </Article>
-
-  <!-- <SButton></SButton> -->
-  <!-- </view> -->
+  <div class="flex-grow bg-white">
+    <Article
+      :title="data?.title"
+      :author="{ name: '筑巢荟' }"
+      :content="data?.contentDetail"
+      :createAt="data?.createTime"
+      :viewNum="data?.viewsCount"
+    >
+      <template #avatar><wd-img width="28" height="28" :src="logo"></wd-img></template>
+    </Article>
+  </div>
 </template>

+ 27 - 11
packages/app/src/pages/home/classmates/index.vue

@@ -1,20 +1,36 @@
-<route lang="yaml">
-style:
-  navigationBarTitleText: 同学荟
-  navigationBarBackgroundColor: '#fff'
+<route lang="json">
+{
+  "style": {
+    "navigationBarTitleText": "同学荟",
+    "navigationBarBackgroundColor": "#fff"
+  }
+}
 </route>
 <script setup lang="ts">
-import { getClassmates } from '../../../core/libs/requests'
+import { NetImages } from '../../../core/libs/net-images'
+import { getContents } from '../../../core/libs/requests'
 import ClassItem from '../components/class-item.vue'
 
-const { data, run } = useRequest(getClassmates)
-onMounted(run)
+const { data: classmates, run: setClassmates } = useRequest(
+  () => getContents({ contentType: '1', contentCategory: '101' }),
+  { initialData: { list: [] } },
+)
+onMounted(async () => {
+  await setClassmates()
+})
 </script>
 <template>
-  <view class="mx-3.5">
-    <template v-for="it of data" :key="it.id">
-      <ClassItem></ClassItem>
+  <view class="p-3.5 flex flex-col gap-6">
+    <template v-for="it of classmates.list" :key="it.id">
+      <ClassItem :options="it"></ClassItem>
+    </template>
+    <wd-status-tip
+      v-if="!classmates.list?.length"
+      :image="NetImages.NotContent"
+      tip="暂无内容"
+    ></wd-status-tip>
+    <template v-if="classmates.list?.length">
+      <wd-loadmore custom-class="loadmore" state="finished" />
     </template>
-    <wd-loadmore custom-class="loadmore" state="finished" />
   </view>
 </template>

+ 11 - 1
packages/app/src/pages/home/components/article.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import dayjs from 'dayjs'
+import uParse from '@/components/u-parse/u-parse.vue'
 
 defineProps({
   title: {
@@ -49,6 +50,15 @@ defineProps({
         </div>
       </div>
     </div>
-    <div v-html="content"></div>
+    <!-- <u-parse :content="content"></u-parse> -->
+    <div class="content" v-html="content"></div>
   </div>
 </template>
+<style lang="scss">
+@import url('@/components/u-parse/u-parse.css');
+.content {
+  & >>> img {
+    max-width: 100%;
+  }
+}
+</style>

+ 16 - 10
packages/app/src/pages/home/components/class-item.vue

@@ -1,5 +1,9 @@
 <script lang="ts" setup>
+import { PropType } from 'vue'
 import { frame, peoples, polygon16 } from '../../../core/libs/svgs'
+import { Content } from '../../../core/models/moment'
+import dayjs from 'dayjs'
+import { useRouter } from '../../../core/utils/router'
 
 defineProps({
   customClass: {
@@ -11,16 +15,18 @@ defineProps({
     default: () => '',
   },
   options: {
-    type: Object,
+    type: Object as PropType<Content>,
     default: () => ({}),
   },
 })
-const handleClick = async () => {
-  await uni.navigateTo({ url: '/pages/home/classmates-detail/index' })
-}
+const router = useRouter()
 </script>
 <template>
-  <view class="relative h-43 flex items-end" :class="[customClass]" @click="handleClick">
+  <view
+    class="relative h-43 flex items-end"
+    :class="[customClass]"
+    @click="router.push(`/pages/home/classmates-detail/index?id=${options.id}`)"
+  >
     <view class="absolute left-0 bottom-0 rounded-2xl overflow-hidden">
       <wd-img
         custom-class="vertical-bottom"
@@ -50,19 +56,19 @@ const handleClick = async () => {
         class="flex items-center justify-between text-black/60 text-sm font-normal font-['PingFang SC'] leading-[34px]"
       >
         <wd-img width="18" height="18" :src="frame"></wd-img>
-        <div class="">07.15</div>
+        <div class="">{{ dayjs(options.studyStartDate).format('MM-DD') }}</div>
         <wd-img custom-class="mx-1" width="5" height="5" :src="polygon16" />
-        <div>08.10</div>
+        <div>{{ dayjs(options.studyEndDate).format('MM-DD') }}</div>
         <view class="flex-1"></view>
         <wd-img :src="peoples" width="16" height="16"></wd-img>
-        <div class="ml-1">45</div>
+        <div class="ml-1">{{ options.studyPersonCount }}</div>
       </view>
       <view class="flex-1"></view>
       <view
         class="flex justify-between text-black/40 text-sm font-normal font-['PingFang SC'] leading-[34px]"
       >
-        <div class="">班长:李威</div>
-        <div class="">领队:王艺臻</div>
+        <div class="">班长:{{ options.studyMonitor }}</div>
+        <div class="">领队:{{ options.studyLeader }}</div>
       </view>
     </div>
   </view>

+ 33 - 14
packages/app/src/pages/home/components/elegant-info-card.vue

@@ -1,5 +1,9 @@
 <script lang="ts" setup>
-import { frame, peoples, polygon16 } from '../../../core/libs/svgs'
+import { PropType } from 'vue'
+import { Content } from '../../../core/models/moment'
+import dayjs from 'dayjs'
+import Tag from '@/components/tag.vue'
+import { useRouter } from '../../../core/utils/router'
 
 defineProps({
   customClass: {
@@ -10,50 +14,65 @@ defineProps({
     type: String,
     default: () => '',
   },
+  options: {
+    type: Object as PropType<Content>,
+    default: () => ({}),
+  },
 })
+const router = useRouter()
 </script>
 <template>
-  <view class="relative h-43 flex items-end" :class="[customClass]">
+  <view
+    class="relative h-43 flex items-end"
+    :class="[customClass]"
+    @click="router.push(`/pages/home/classmates-detail/index?id=${options.id}`)"
+  >
     <view class="absolute left-0 bottom-0 rounded-2xl overflow-hidden">
       <wd-img
         custom-class="vertical-bottom"
         width="136"
         height="172"
         mode="scaleToFill"
-        src="https://via.placeholder.com/136x172"
+        :src="options.bannerUrl"
       />
-      <div
+      <!-- <div
         class="absolute left-2.5 bottom-2.5 px-2 py-1 bg-black/30 rounded-[20px] backdrop-blur-[10px]"
       >
         <div class="text-white text-[10px] font-normal font-['PingFang SC'] leading-relaxed">
           米兰2班
         </div>
-      </div>
+      </div> -->
     </view>
     <div
       class="w-full h-[145px] pl-39 pt-6 pr-6 pb-6 flex flex-col box-border bg-white rounded-2xl shadow"
     >
       <div class="text-black/90 text-base font-normal font-['PingFang SC'] leading-normal">
-        2024届米兰国际家具展
+        {{ options.title }}
       </div>
 
       <view
-        class="flex items-center justify-between text-black/60 text-sm font-normal font-['PingFang SC'] leading-[34px]"
+        class="mt-2.5 flex items-center justify-between text-black/60 text-sm font-normal font-['PingFang SC'] leading-[34px]"
       >
-        <wd-img width="18" height="18" :src="frame"></wd-img>
+        <!-- <wd-img width="18" height="18" :src="frame"></wd-img>
         <div class="">07.15</div>
         <wd-img custom-class="mx-1" width="5" height="5" :src="polygon16" />
         <div>08.10</div>
         <view class="flex-1"></view>
         <wd-img :src="peoples" width="16" height="16"></wd-img>
-        <div class="ml-1">45</div>
+        <div class="ml-1">45</div> -->
+        <div class="text-black/40 text-xs font-normal font-['PingFang SC'] leading-[34px]">
+          {{ dayjs(options.createTime).format('YYYY/MM/DD') }}
+        </div>
       </view>
       <view class="flex-1"></view>
-      <view
-        class="flex justify-between text-black/40 text-sm font-normal font-['PingFang SC'] leading-[34px]"
-      >
-        <div class="">班长:李威</div>
-        <div class="">领队:王艺臻</div>
+      <view class="flex text-black/40 text-sm font-normal font-['PingFang SC'] leading-[34px]">
+        <!-- <div class="">班长:李威</div>
+        <div class="">领队:王艺臻</div> -->
+        <div
+          class="h-7 rounded-[50px] border border-black/20 justify-center items-center gap-[5px] inline-flex"
+        >
+          <Tag>筑巢荟</Tag>
+        </div>
       </view>
     </div>
   </view>

+ 52 - 36
packages/app/src/pages/home/components/offline-activity-item.vue

@@ -1,8 +1,10 @@
 <script setup lang="ts">
 import Card from '@/components/card.vue'
-import { frame } from '../../../core/libs/svgs'
+import { frame, polygon16 } from '../../../core/libs/svgs'
 import { PropType } from 'vue'
 import { Content } from '../../../core/models/moment'
+import dayjs from 'dayjs'
+import { useRouter } from '../../../core/utils/router'
 
 defineProps({
   customClass: {
@@ -14,47 +16,61 @@ defineProps({
     default: () => ({}),
   },
 })
+const router = useRouter()
 </script>
 <template>
-  <card :custom-class="[customClass, 'p-0!']">
-    <view class="relative">
-      <img
-        class="w-[347px] h-[202px] rounded-tl-2xl rounded-tr-2xl vertical-bottom"
-        src="https://via.placeholder.com/347x202"
-      />
-      <div class="absolute top-4 right-4 px-2.5 bg-black/30 rounded-[20px] backdrop-blur-[15px]">
-        <div class="text-white text-xs font-normal font-['PingFang SC'] leading-relaxed">
-          已结束
-        </div>
-      </div>
-      <div
-        class="absolute top-4 left-4 px-2.5 rounded-md border border-solid border-white justify-center items-center gap-2.5 inline-flex"
-      >
-        <div class="text-white text-xs font-normal font-['PingFang SC'] leading-normal">第18期</div>
-      </div>
-    </view>
-    <div class="p-5 bg-white rounded-bl-[20px] rounded-br-[20px] shadow">
-      <div
-        class="w-[244px] text-black/40 text-base font-normal font-['PingFang SC'] leading-normal"
-      >
-        <!-- 白衣剑客·这是格斗的“芭蕾” -->
-        {{ options.title }}
-      </div>
-      <view class="flex items-center mb-4">
-        <wd-img custom-class="vertical-bottom" :src="frame" width="20" height="20"></wd-img>
-        <div class="text-black/60 text-sm font-normal font-['PingFang SC'] leading-[34px]">
-          07.15 14:20
+  <div @click="router.push(`/pages/home/classmates-detail/index?id=${options.id}`)">
+    <card :custom-class="[customClass, 'p-0!']">
+      <view class="relative">
+        <img
+          class="w-[347px] h-[202px] rounded-tl-2xl rounded-tr-2xl vertical-bottom"
+          :src="options.bannerUrl"
+        />
+        <div class="absolute top-4 right-4 px-2.5 bg-black/30 rounded-[20px] backdrop-blur-[15px]">
+          <div class="text-white text-xs font-normal font-['PingFang SC'] leading-relaxed">
+            已结束
+          </div>
         </div>
-      </view>
-      <view class="flex">
         <div
-          class="inline-block px-2.5 rounded-md border border-solid border-black/30 backdrop-blur-[6px] flex justify-center items-center"
+          class="absolute top-4 left-4 px-2.5 rounded-md border border-solid border-white justify-center items-center gap-2.5 inline-flex"
         >
-          <div class="text-black/60 text-xs font-normal font-['PingFang SC'] leading-normal">
-            举办方:筑巢荟
+          <div class="text-white text-xs font-normal font-['PingFang SC'] leading-normal">
+            第18期
           </div>
         </div>
       </view>
-    </div>
-  </card>
+      <div class="p-5 bg-white rounded-bl-[20px] rounded-br-[20px] shadow">
+        <div class="w-[244px] text-black text-base font-normal font-['PingFang SC'] leading-normal">
+          {{ options.title }}
+        </div>
+        <view class="mt-1.5 flex items-center mb-4">
+          <wd-img custom-class="vertical-bottom" :src="frame" width="20" height="20"></wd-img>
+          <div
+            class="flex items-center text-black/60 text-sm font-normal font-['PingFang SC'] leading-[34px]"
+          >
+            <div class="">{{ dayjs(options.studyStartDate).format('MM-DD') }}</div>
+            <wd-img custom-class="mx-1" width="5" height="5" :src="polygon16" />
+            <div>{{ dayjs(options.studyEndDate).format('MM-DD') }}</div>
+          </div>
+        </view>
+        <view class="flex">
+          <div
+            class="inline-block px-2.5 rounded-md border border-solid border-black/30 backdrop-blur-[6px] flex justify-center items-center"
+          >
+            <div class="text-black/60 text-xs font-normal font-['PingFang SC'] leading-normal">
+              举办方:筑巢荟
+            </div>
+          </div>
+          <div
+            v-if="options.supportBrand"
+            class="inline-block px-2.5 rounded-md border border-solid border-black/30 backdrop-blur-[6px] flex justify-center items-center"
+          >
+            <div class="text-black/60 text-xs font-normal font-['PingFang SC'] leading-normal">
+              {{ options.supportBrand }}
+            </div>
+          </div>
+        </view>
+      </div>
+    </card>
+  </div>
 </template>

+ 52 - 20
packages/app/src/pages/home/offline-activity/index.vue

@@ -10,21 +10,37 @@
 <script setup lang="ts">
 import SectionHeading from '@/components/section-heading.vue'
 import Card from '@/components/card.vue'
-import RegisterCard from '../components/register-card.vue'
 import OfflineActivityItem from '../components/offline-activity-item.vue'
-import { getByDictType, getContents } from '../../../core/libs/requests'
-import { DictType } from '../../../core/models/moment'
+import { getAllCategories, getContents } from '../../../core/libs/requests'
+import { strip, leaderboardText } from '@designer-hub/assets/src/assets/svgs'
+import { NetImages } from '../../../core/libs/net-images'
 
-const { data: tabs, run: setTabs } = useRequest(() => getByDictType(DictType.offlineActivity))
-const tab = ref<number>(0)
-const { data, run: setData } = useRequest(() => getContents({ contentType: '1' }), {
-  initialData: { list: [] },
+const { data: categories, run: setCategories } = useRequest(() => getAllCategories(), {
+  initialData: [],
 })
+const tab = ref<number>(0)
+const contentCategory = ref()
+const { data, run: setData } = useRequest(
+  () =>
+    getContents({
+      // contentType: '2',
+      contentCategory: contentCategory.value,
+    }),
+  {
+    initialData: { list: [] },
+  },
+)
+const setContentCategory = (index) => {
+  contentCategory.value = categories.value.find(({ id }) => id === 2)?.children[index].id.toString()
+}
+const handleTabChange = ({ index }) => {
+  setContentCategory(index)
+  setData()
+}
 onMounted(async () => {
-  await setTabs()
-  console.log(tabs.value)
+  await setCategories()
+  setContentCategory(tab.value)
   await setData()
-  console.log(data.value)
 })
 </script>
 
@@ -36,7 +52,7 @@ onMounted(async () => {
       path="/pages/home/offline-activity/list/index"
       end-text="查看全部"
     ></section-heading>
-    <register-card></register-card>
+    <!-- <register-card></register-card> -->
     <card custom-class="">
       <div class="my-7.5 text-black text-xl font-normal font-['PingFang SC'] leading-[10.18px]">
         筑巢荟-活动营
@@ -49,21 +65,37 @@ onMounted(async () => {
         参与专业的研讨会和工作坊,与同行们分享见解、碰撞思维火花,进一步深化对设计的理解。
       </div>
     </card>
-    <wd-tabs v-model="tab" custom-class="bg-transparent!" :slidable-num="4">
-      <block v-for="(it, item) in tabs" :key="item">
-        <wd-tab :title="it.label"></wd-tab>
+    <wd-tabs
+      v-model="tab"
+      custom-class="bg-transparent!"
+      :slidable-num="4"
+      @change="handleTabChange"
+    >
+      <block v-for="(it, item) in categories.find(({ id }) => id === 2).children" :key="item">
+        <wd-tab :title="it.name"></wd-tab>
       </block>
     </wd-tabs>
-    <Card custom-class="py-2 rounded-lg">
-      <section-heading
-        title="筑巢荟骑行俱乐部"
-        size="sm"
-        path="/pages/home/offline-activity/cycling-rankings/index"
-      ></section-heading>
+    <Card custom-class="py-2 rounded-lg" v-if="tab === 0">
+      <div class="flex items-center">
+        <wd-img width="22" height="22" :src="strip"></wd-img>
+        <wd-img width="80" :src="leaderboardText" mode="widthFix"></wd-img>
+        <div class="flex-1">
+          <section-heading
+            title="筑巢荟骑行俱乐部"
+            size="sm"
+            path="/pages/home/offline-activity/cycling-rankings/index"
+          ></section-heading>
+        </div>
+      </div>
     </Card>
     <template v-for="(it, index) in data.list" :key="index">
       <offline-activity-item class="" :options="it"></offline-activity-item>
     </template>
+    <wd-status-tip
+      v-if="!data.list?.length"
+      :image="NetImages.NotContent"
+      tip="暂无内容"
+    ></wd-status-tip>
   </view>
 </template>
 

+ 27 - 9
packages/app/src/pages/home/offline-activity/list/index.vue

@@ -3,30 +3,48 @@
 </route>
 <script setup lang="ts">
 import { DictType } from '../../../../core/models/moment'
-import { getByDictType, getContents } from '../../../../core/libs/requests'
+import { getByDictType, getContents, getAllCategories } from '../../../../core/libs/requests'
 import Card from '@/components/card.vue'
 
 const tab = ref<number>(0)
-const { data: tabs, run: setTabs } = useRequest(() => getByDictType(DictType.offlineActivity))
+const contentCategory = ref()
+const { data: categories, run: setCategories } = useRequest(() => getAllCategories(), {
+  initialData: [],
+})
 const { data, run: setData } = useRequest(
-  () => getContents({ contentType: '1', contentCategory: tabs.value[tab.value].value }),
+  () =>
+    getContents({
+      contentType: '1',
+      contentCategory: contentCategory.value,
+    }),
   {
     initialData: { list: [] },
   },
 )
+const setContentCategory = (index) => {
+  contentCategory.value = categories.value.find(({ id }) => id === 2).children[index].id.toString()
+}
+const handleTabChange = ({ index }) => {
+  setContentCategory(index)
+  setData()
+}
 onMounted(async () => {
-  await setTabs()
-  console.log(tabs.value)
+  await setCategories()
+  setContentCategory(tab.value)
   await setData()
-  console.log(data.value)
 })
 </script>
 <template>
   <div class="flex-grow flex flex-col gap-6 mx-3.5">
     <div class="mx--3.5">
-      <wd-tabs v-model="tab" custom-class="bg-transparent!" :slidable-num="4">
-        <block v-for="(it, item) in tabs" :key="item">
-          <wd-tab :title="it.label"></wd-tab>
+      <wd-tabs
+        v-model="tab"
+        custom-class="bg-transparent!"
+        :slidable-num="4"
+        @change="handleTabChange"
+      >
+        <block v-for="(it, item) in categories.find(({ id }) => id === 2).children" :key="item">
+          <wd-tab :title="it.name"></wd-tab>
         </block>
       </wd-tabs>
     </div>

+ 25 - 6
packages/app/src/pages/home/spread/design-awards/index.vue

@@ -8,21 +8,40 @@
 </route>
 
 <script setup lang="ts">
-import TiltedButton from '@/components/tilted-button.vue'
 import SectionHeading from '@/components/section-heading.vue'
 import ElegantInfoCard from '../../components/elegant-info-card.vue'
-import { shoppingBag } from '@designer-hub/assets/src/assets/svgs/index'
+import { getContents } from '../../../../core/libs/requests'
+import { NetImages } from '../../../../core/libs/net-images'
 
-const data = ref(['https://via.placeholder.com/347x128'])
+const data1 = ref(['https://via.placeholder.com/347x128'])
+const { data, run: setData } = useRequest(
+  () =>
+    getContents({
+      contentType: '3',
+      contentCategory: '301',
+    }),
+  {
+    initialData: { list: [] },
+  },
+)
 const products = ref([{}, {}, {}])
+onMounted(async () => {
+  await setData()
+})
 </script>
 
 <template>
   <view class="bg-white flex-grow flex flex-col px-3.5 py-5.5 gap-5.5">
-    <wd-swiper :list="data" autoplay height="128px"></wd-swiper>
+    <wd-swiper :list="data1" autoplay height="128px"></wd-swiper>
     <SectionHeading title="筑巢奖"></SectionHeading>
-    <ElegantInfoCard></ElegantInfoCard>
-    <ElegantInfoCard></ElegantInfoCard>
+    <template v-for="(it, i) in data.list" :key="i">
+      <ElegantInfoCard :options="it"></ElegantInfoCard>
+    </template>
+    <wd-status-tip
+      v-if="!data.list.length"
+      :image="NetImages.NotContent"
+      tip="暂无内容"
+    ></wd-status-tip>
   </view>
 </template>
 

+ 22 - 4
packages/app/src/pages/home/spread/index.vue

@@ -7,13 +7,22 @@ import Card from '@/components/card.vue'
 import { award, camera, wechat } from '../../../core/libs/svgs'
 import InfoCard from '../components/info-card.vue'
 import SectionHeading from '@/components/section-heading.vue'
-import ClassItem from '../components/class-item.vue'
 import ElegantInfoCard from '../components/elegant-info-card.vue'
-import { agent as test } from '@designer-hub/merchant/src/core/libs/svgs'
 import { useRouter } from '../../../core/utils/router'
+import { getContents } from '../../../core/libs/requests'
+import { NetImages } from '../../../core/libs/net-images'
 
 const router = useRouter()
 const current = ref<number>(0)
+const { data, run: setData } = useRequest(
+  () =>
+    getContents({
+      contentType: '3',
+    }),
+  {
+    initialData: { list: [] },
+  },
+)
 
 const swiperList = ref([
   'https://registry.npmmirror.com/wot-design-uni-assets/*/files/redpanda.jpg',
@@ -48,6 +57,9 @@ function handleClick(e) {
 function onChange(e) {
   console.log(e)
 }
+onMounted(async () => {
+  await setData()
+})
 </script>
 
 <template>
@@ -90,9 +102,15 @@ function onChange(e) {
     ></info-card>
     <section-heading title="最新资讯"></section-heading>
     <div class="my-6">
-      <ElegantInfoCard></ElegantInfoCard>
+      <template v-for="(it, i) in data.list" :key="i">
+        <ElegantInfoCard :options="it"></ElegantInfoCard>
+      </template>
+      <wd-status-tip
+        v-if="!data.list.length"
+        :image="NetImages.NotContent"
+        tip="暂无内容"
+      ></wd-status-tip>
     </div>
-    <ElegantInfoCard></ElegantInfoCard>
   </view>
 </template>
 

+ 9 - 6
packages/app/src/pages/home/study-tour/index.vue

@@ -7,15 +7,13 @@
 }
 </route>
 <script lang="ts" setup>
-import AvatarGroupCasual from '@/components/avatar-group-casual/avatar-group-casual.vue'
 import Card from '@/components/card.vue'
 import MomentItem from '@/components/moment-item.vue'
 import SectionHeading from '@/components/section-heading.vue'
-import TiltedButton from '@/components/tilted-button.vue'
 import ClassItem from '../components/class-item.vue'
 import TimeLine from './components/time-line.vue'
-import { getByDictType, getContents } from '../../../core/libs/requests'
-import { DictType } from '../../../core/models/moment'
+import { getContents } from '../../../core/libs/requests'
+import { NetImages } from '../../../core/libs/net-images'
 
 const { data: studyTours, run: setStudyTours } = useRequest(() => getContents({ contentType: '1' }))
 const { data: classmates, run: setClassmates } = useRequest(
@@ -35,7 +33,7 @@ onMounted(async () => {
     <div class="mx--2.5 mb--2.5">
       <TimeLine></TimeLine>
     </div>
-    <card custom-class="p-0!">
+    <!-- <card custom-class="p-0!">
       <view class="relative">
         <wd-img
           custom-class="vertical-bottom"
@@ -93,7 +91,7 @@ onMounted(async () => {
           <tilted-button custom-class="" size="large" color="white">立即报名</tilted-button>
         </view>
       </div>
-    </card>
+    </card> -->
     <card custom-class="">
       <div class="my-7.5 text-black text-xl font-normal font-['PingFang SC'] leading-[10.18px]">
         筑巢荟-设计游学
@@ -114,6 +112,11 @@ onMounted(async () => {
     <template v-for="(it, i) in classmates.list" :key="i">
       <ClassItem :options="it"></ClassItem>
     </template>
+    <wd-status-tip
+      v-if="!classmates.list?.length"
+      :image="NetImages.NotContent"
+      tip="暂无内容"
+    ></wd-status-tip>
     <section-heading custom-class="my-6" title="设计圈"></section-heading>
     <moment-item></moment-item>
   </view>

+ 4 - 0
packages/assets/src/assets/svgs/index.ts

@@ -9,6 +9,8 @@ import notify from "./notify";
 import map from "./map";
 import invertedTrapezoid from "./invertedTrapezoid";
 import trapezium from "./trapezium";
+import strip from "./strip";
+import leaderboardText from "./leaderboardText";
 
 export {
   addBlack,
@@ -22,4 +24,6 @@ export {
   map,
   invertedTrapezoid,
   trapezium,
+  strip,
+  leaderboardText,
 };

+ 2 - 0
packages/assets/src/assets/svgs/leaderboardText.ts

@@ -0,0 +1,2 @@
+import leaderboardText from "./leaderboard_text.svg";
+export default leaderboardText;

File diff suppressed because it is too large
+ 1 - 0
packages/assets/src/assets/svgs/leaderboard_text.svg


+ 3 - 0
packages/assets/src/assets/svgs/strip.svg

@@ -0,0 +1,3 @@
+<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.4722 18.3385C5.19787 18.3385 3.92117 18.3385 2.60498 18.3385C2.60498 15.7472 2.60498 13.1507 2.60498 10.4994C3.90703 10.4994 5.16942 10.4994 6.4722 10.4994C6.4722 13.1131 6.4722 15.7085 6.4722 18.3385ZM12.3803 18.3292C11.1313 18.3292 9.86079 18.3292 8.55964 18.3292C8.55964 13.1047 8.55964 7.89722 8.55964 2.61917C9.84753 2.61917 11.0908 2.61917 12.3803 2.61917C12.3803 7.84267 12.3803 13.064 12.3803 18.3292ZM18.3584 18.3441C17.0774 18.3441 15.8147 18.3441 14.5043 18.3441C14.5043 14.7724 14.5043 11.1931 14.5043 7.56674C15.77 7.56674 17.0444 7.56674 18.3584 7.56674C18.3584 11.1536 18.3584 14.7341 18.3584 18.3441Z" fill="#361A0C"/>
+</svg>

+ 2 - 0
packages/assets/src/assets/svgs/strip.ts

@@ -0,0 +1,2 @@
+import strip from "./strip.svg";
+export default strip;

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