Compare commits
No commits in common. "main" and "dev-his-archive" have entirely different histories.
main
...
dev-his-ar
@ -1,27 +0,0 @@
|
||||
<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>
|
||||
@ -1,86 +0,0 @@
|
||||
<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>
|
||||
@ -1,107 +0,0 @@
|
||||
<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>
|
||||
@ -1,99 +0,0 @@
|
||||
<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>
|
||||
@ -1,97 +0,0 @@
|
||||
<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>
|
||||
@ -1,87 +0,0 @@
|
||||
<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>
|
||||
@ -1,98 +0,0 @@
|
||||
<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>
|
||||
@ -1,98 +0,0 @@
|
||||
<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>
|
||||
@ -1,98 +0,0 @@
|
||||
<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>
|
||||
@ -1,98 +0,0 @@
|
||||
<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>
|
||||
@ -1,98 +0,0 @@
|
||||
<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>
|
||||
@ -1,98 +0,0 @@
|
||||
<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>
|
||||
@ -1,98 +0,0 @@
|
||||
<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>
|
||||
@ -1,98 +0,0 @@
|
||||
<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>
|
||||
@ -1,15 +0,0 @@
|
||||
<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>
|
||||
@ -1,261 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
@ -1,156 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* 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;
|
||||
@ -1,195 +0,0 @@
|
||||
// HTML 支持的数学符号
|
||||
function strNumDiscode(str) {
|
||||
str = str.replace(/∀/g, '∀');
|
||||
str = str.replace(/∂/g, '∂');
|
||||
str = str.replace(/∃/g, '∃');
|
||||
str = str.replace(/∅/g, '∅');
|
||||
str = str.replace(/∇/g, '∇');
|
||||
str = str.replace(/∈/g, '∈');
|
||||
str = str.replace(/∉/g, '∉');
|
||||
str = str.replace(/∋/g, '∋');
|
||||
str = str.replace(/∏/g, '∏');
|
||||
str = str.replace(/∑/g, '∑');
|
||||
str = str.replace(/−/g, '−');
|
||||
str = str.replace(/∗/g, '∗');
|
||||
str = str.replace(/√/g, '√');
|
||||
str = str.replace(/∝/g, '∝');
|
||||
str = str.replace(/∞/g, '∞');
|
||||
str = str.replace(/∠/g, '∠');
|
||||
str = str.replace(/∧/g, '∧');
|
||||
str = str.replace(/∨/g, '∨');
|
||||
str = str.replace(/∩/g, '∩');
|
||||
str = str.replace(/∪/g, '∪');
|
||||
str = str.replace(/∫/g, '∫');
|
||||
str = str.replace(/∴/g, '∴');
|
||||
str = str.replace(/∼/g, '∼');
|
||||
str = str.replace(/≅/g, '≅');
|
||||
str = str.replace(/≈/g, '≈');
|
||||
str = str.replace(/≠/g, '≠');
|
||||
str = str.replace(/≤/g, '≤');
|
||||
str = str.replace(/≥/g, '≥');
|
||||
str = str.replace(/⊂/g, '⊂');
|
||||
str = str.replace(/⊃/g, '⊃');
|
||||
str = str.replace(/⊄/g, '⊄');
|
||||
str = str.replace(/⊆/g, '⊆');
|
||||
str = str.replace(/⊇/g, '⊇');
|
||||
str = str.replace(/⊕/g, '⊕');
|
||||
str = str.replace(/⊗/g, '⊗');
|
||||
str = str.replace(/⊥/g, '⊥');
|
||||
str = str.replace(/⋅/g, '⋅');
|
||||
return str;
|
||||
}
|
||||
|
||||
// HTML 支持的希腊字母
|
||||
function strGreeceDiscode(str) {
|
||||
str = str.replace(/Α/g, 'Α');
|
||||
str = str.replace(/Β/g, 'Β');
|
||||
str = str.replace(/Γ/g, 'Γ');
|
||||
str = str.replace(/Δ/g, 'Δ');
|
||||
str = str.replace(/Ε/g, 'Ε');
|
||||
str = str.replace(/Ζ/g, 'Ζ');
|
||||
str = str.replace(/Η/g, 'Η');
|
||||
str = str.replace(/Θ/g, 'Θ');
|
||||
str = str.replace(/Ι/g, 'Ι');
|
||||
str = str.replace(/Κ/g, 'Κ');
|
||||
str = str.replace(/Λ/g, 'Λ');
|
||||
str = str.replace(/Μ/g, 'Μ');
|
||||
str = str.replace(/Ν/g, 'Ν');
|
||||
str = str.replace(/Ξ/g, 'Ν');
|
||||
str = str.replace(/Ο/g, 'Ο');
|
||||
str = str.replace(/Π/g, 'Π');
|
||||
str = str.replace(/Ρ/g, 'Ρ');
|
||||
str = str.replace(/Σ/g, 'Σ');
|
||||
str = str.replace(/Τ/g, 'Τ');
|
||||
str = str.replace(/Υ/g, 'Υ');
|
||||
str = str.replace(/Φ/g, 'Φ');
|
||||
str = str.replace(/Χ/g, 'Χ');
|
||||
str = str.replace(/Ψ/g, 'Ψ');
|
||||
str = str.replace(/Ω/g, 'Ω');
|
||||
|
||||
str = str.replace(/α/g, 'α');
|
||||
str = str.replace(/β/g, 'β');
|
||||
str = str.replace(/γ/g, 'γ');
|
||||
str = str.replace(/δ/g, 'δ');
|
||||
str = str.replace(/ε/g, 'ε');
|
||||
str = str.replace(/ζ/g, 'ζ');
|
||||
str = str.replace(/η/g, 'η');
|
||||
str = str.replace(/θ/g, 'θ');
|
||||
str = str.replace(/ι/g, 'ι');
|
||||
str = str.replace(/κ/g, 'κ');
|
||||
str = str.replace(/λ/g, 'λ');
|
||||
str = str.replace(/μ/g, 'μ');
|
||||
str = str.replace(/ν/g, 'ν');
|
||||
str = str.replace(/ξ/g, 'ξ');
|
||||
str = str.replace(/ο/g, 'ο');
|
||||
str = str.replace(/π/g, 'π');
|
||||
str = str.replace(/ρ/g, 'ρ');
|
||||
str = str.replace(/ς/g, 'ς');
|
||||
str = str.replace(/σ/g, 'σ');
|
||||
str = str.replace(/τ/g, 'τ');
|
||||
str = str.replace(/υ/g, 'υ');
|
||||
str = str.replace(/φ/g, 'φ');
|
||||
str = str.replace(/χ/g, 'χ');
|
||||
str = str.replace(/ψ/g, 'ψ');
|
||||
str = str.replace(/ω/g, 'ω');
|
||||
str = str.replace(/ϑ/g, 'ϑ');
|
||||
str = str.replace(/ϒ/g, 'ϒ');
|
||||
str = str.replace(/ϖ/g, 'ϖ');
|
||||
str = str.replace(/·/g, '·');
|
||||
return str;
|
||||
}
|
||||
|
||||
function strcharacterDiscode(str) {
|
||||
// 加入常用解析
|
||||
str = str.replace(/ /g, ' ');
|
||||
str = str.replace(/ /g, ' ');
|
||||
str = str.replace(/ /g, ' ');
|
||||
str = str.replace(/"/g, "'");
|
||||
str = str.replace(/&/g, '&');
|
||||
str = str.replace(/</g, '<');
|
||||
str = str.replace(/>/g, '>');
|
||||
str = str.replace(/•/g, '•');
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
// HTML 支持的其他实体
|
||||
function strOtherDiscode(str) {
|
||||
str = str.replace(/Œ/g, 'Œ');
|
||||
str = str.replace(/œ/g, 'œ');
|
||||
str = str.replace(/Š/g, 'Š');
|
||||
str = str.replace(/š/g, 'š');
|
||||
str = str.replace(/Ÿ/g, 'Ÿ');
|
||||
str = str.replace(/ƒ/g, 'ƒ');
|
||||
str = str.replace(/ˆ/g, 'ˆ');
|
||||
str = str.replace(/˜/g, '˜');
|
||||
str = str.replace(/ /g, '');
|
||||
str = str.replace(/ /g, '');
|
||||
str = str.replace(/ /g, '');
|
||||
str = str.replace(/‌/g, '');
|
||||
str = str.replace(/‍/g, '');
|
||||
str = str.replace(/‎/g, '');
|
||||
str = str.replace(/‏/g, '');
|
||||
str = str.replace(/–/g, '–');
|
||||
str = str.replace(/—/g, '—');
|
||||
str = str.replace(/‘/g, '‘');
|
||||
str = str.replace(/’/g, '’');
|
||||
str = str.replace(/‚/g, '‚');
|
||||
str = str.replace(/“/g, '“');
|
||||
str = str.replace(/”/g, '”');
|
||||
str = str.replace(/„/g, '„');
|
||||
str = str.replace(/†/g, '†');
|
||||
str = str.replace(/‡/g, '‡');
|
||||
str = str.replace(/•/g, '•');
|
||||
str = str.replace(/…/g, '…');
|
||||
str = str.replace(/‰/g, '‰');
|
||||
str = str.replace(/′/g, '′');
|
||||
str = str.replace(/″/g, '″');
|
||||
str = str.replace(/‹/g, '‹');
|
||||
str = str.replace(/›/g, '›');
|
||||
str = str.replace(/‾/g, '‾');
|
||||
str = str.replace(/€/g, '€');
|
||||
str = str.replace(/™/g, '™');
|
||||
|
||||
str = str.replace(/←/g, '←');
|
||||
str = str.replace(/↑/g, '↑');
|
||||
str = str.replace(/→/g, '→');
|
||||
str = str.replace(/↓/g, '↓');
|
||||
str = str.replace(/↔/g, '↔');
|
||||
str = str.replace(/↵/g, '↵');
|
||||
str = str.replace(/⌈/g, '⌈');
|
||||
str = str.replace(/⌉/g, '⌉');
|
||||
|
||||
str = str.replace(/⌊/g, '⌊');
|
||||
str = str.replace(/⌋/g, '⌋');
|
||||
str = str.replace(/◊/g, '◊');
|
||||
str = str.replace(/♠/g, '♠');
|
||||
str = str.replace(/♣/g, '♣');
|
||||
str = str.replace(/♥/g, '♥');
|
||||
|
||||
str = str.replace(/♦/g, '♦');
|
||||
str = str.replace(/'/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,
|
||||
};
|
||||
@ -1,102 +0,0 @@
|
||||
## 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!`)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -1,232 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@ -1,118 +0,0 @@
|
||||
<!--**
|
||||
* 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>
|
||||
@ -34,9 +34,7 @@ export default function useGuard() {
|
||||
|
||||
async function useLoad(fn) {
|
||||
await promise;
|
||||
if (typeof fn === 'function') {
|
||||
fn(options.value);
|
||||
}
|
||||
fn(options.value);
|
||||
}
|
||||
|
||||
async function triggleShowEvents() {
|
||||
|
||||
628
pages.json
@ -1,348 +1,280 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/home/home",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页",
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/message/message",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/message/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "聊天",
|
||||
"enablePullDownRefresh": false,
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/mine",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/contact",
|
||||
"style": {
|
||||
"navigationBarTitleText": "联系客服",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康柚",
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/redirect-page",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康柚",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/agreement",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康柚",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/common/privacy",
|
||||
"style": {
|
||||
"navigationBarTitleText": "隐私政策",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/common/agreement",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户协议",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/web-view/web-view",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "pages/article",
|
||||
"name": "article",
|
||||
"pages": [
|
||||
{
|
||||
"path": "article-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的宣教",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "article-cate-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康宣教",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "article-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "宣教文章",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "send-article",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择宣教文章",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/survey",
|
||||
"name": "survey",
|
||||
"pages": [
|
||||
{
|
||||
"path": "survey-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的问卷",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "fill",
|
||||
"style": {
|
||||
"navigationBarTitleText": "问卷",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/rate",
|
||||
"name": "rate",
|
||||
"pages": [
|
||||
{
|
||||
"path": "rate-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "服务评价",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "rate-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "服务评价",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/experience-coupon",
|
||||
"name": "experience-coupon",
|
||||
"pages": [
|
||||
{
|
||||
"path": "claim",
|
||||
"style": {
|
||||
"navigationBarTitleText": "领取体验券",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "my-rights",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的权益",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "select-rights-archive",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择档案",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "my-coupons",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的体验券",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "wallet",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的卡包",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/record",
|
||||
"name": "record",
|
||||
"pages": [
|
||||
{
|
||||
"path": "treatment-record",
|
||||
"style": {
|
||||
"navigationBarTitleText": "治疗记录",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "appointment-record",
|
||||
"style": {
|
||||
"navigationBarTitleText": "预约记录",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/archive",
|
||||
"name": "archive",
|
||||
"pages": [
|
||||
{
|
||||
"path": "archive-manage",
|
||||
"style": {
|
||||
"navigationBarTitleText": "档案管理",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "edit-archive",
|
||||
"style": {
|
||||
"navigationBarTitleText": "新增档案",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "archive-result",
|
||||
"style": {
|
||||
"navigationBarTitleText": "团队服务",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/health",
|
||||
"name": "health",
|
||||
"pages": [
|
||||
{
|
||||
"path": "list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康信息",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "record",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康信息",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/library",
|
||||
"name": "library",
|
||||
"pages": [
|
||||
{
|
||||
"path": "diagnosis-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择诊断",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/team",
|
||||
"name": "team",
|
||||
"pages": [
|
||||
{
|
||||
"path": "team-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "团队介绍",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "homepage",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人主页",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "friend",
|
||||
"style": {
|
||||
"navigationBarTitleText": "添加好友",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "business-card",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人名片",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarTitleText": "uni-app",
|
||||
"navigationBarBackgroundColor": "#065bd6",
|
||||
"backgroundColor": "#065bd6"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#666666",
|
||||
"selectedColor": "#007aff",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderStyle": "white",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/home/home",
|
||||
"iconPath": "static/tabbar/home.png",
|
||||
"selectedIconPath": "static/tabbar/home_selected.png",
|
||||
"text": "服务"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/message/message",
|
||||
"iconPath": "static/tabbar/consult.png",
|
||||
"selectedIconPath": "static/tabbar/consult_selected.png",
|
||||
"text": "咨询"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mine/mine",
|
||||
"iconPath": "static/tabbar/mine.png",
|
||||
"selectedIconPath": "static/tabbar/mine_selected.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uniIdRouter": {}
|
||||
}
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/home/home",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页",
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/message/message",
|
||||
"style": {
|
||||
"navigationBarTitleText": "消息",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/message/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "聊天",
|
||||
"enablePullDownRefresh": false,
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/mine",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/contact",
|
||||
"style": {
|
||||
"navigationBarTitleText": "联系客服",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康柚",
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/redirect-page",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康柚",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/agreement",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康柚",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/common/privacy",
|
||||
"style": {
|
||||
"navigationBarTitleText": "隐私政策",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/common/agreement",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户协议",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/web-view/web-view",
|
||||
"style": {
|
||||
"navigationBarTitleText": "",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "pages/article",
|
||||
"name": "article",
|
||||
"pages": [
|
||||
{
|
||||
"path": "article-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的宣教",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "article-cate-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康宣教",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "article-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "宣教文章",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "send-article",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择宣教文章",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/survey",
|
||||
"name": "survey",
|
||||
"pages": [
|
||||
{
|
||||
"path": "survey-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的问卷",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "fill",
|
||||
"style": {
|
||||
"navigationBarTitleText": "问卷",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/rate",
|
||||
"name": "rate",
|
||||
"pages": [
|
||||
{
|
||||
"path": "rate-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "服务评价",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "rate-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "服务评价",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/archive",
|
||||
"name": "archive",
|
||||
"pages": [
|
||||
{
|
||||
"path": "archive-manage",
|
||||
"style": {
|
||||
"navigationBarTitleText": "档案管理",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "edit-archive",
|
||||
"style": {
|
||||
"navigationBarTitleText": "新增档案",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "archive-result",
|
||||
"style": {
|
||||
"navigationBarTitleText": "团队服务",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/health",
|
||||
"name": "health",
|
||||
"pages": [
|
||||
{
|
||||
"path": "list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康信息",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "record",
|
||||
"style": {
|
||||
"navigationBarTitleText": "健康信息",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/library",
|
||||
"name": "library",
|
||||
"pages": [
|
||||
{
|
||||
"path": "diagnosis-list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择诊断",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/team",
|
||||
"name": "team",
|
||||
"pages": [
|
||||
{
|
||||
"path": "team-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "团队介绍",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "homepage",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人主页",
|
||||
"disableScroll": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "friend",
|
||||
"style": {
|
||||
"navigationBarTitleText": "添加好友",
|
||||
"disableScroll": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarTitleText": "uni-app",
|
||||
"navigationBarBackgroundColor": "#065bd6",
|
||||
"backgroundColor": "#065bd6"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#666666",
|
||||
"selectedColor": "#007aff",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderStyle": "white",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/home/home",
|
||||
"iconPath": "static/tabbar/home.png",
|
||||
"selectedIconPath": "static/tabbar/home_selected.png",
|
||||
"text": "服务"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/message/message",
|
||||
"iconPath": "static/tabbar/consult.png",
|
||||
"selectedIconPath": "static/tabbar/consult_selected.png",
|
||||
"text": "咨询"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mine/mine",
|
||||
"iconPath": "static/tabbar/mine.png",
|
||||
"selectedIconPath": "static/tabbar/mine_selected.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uniIdRouter": {}
|
||||
}
|
||||
|
||||
@ -1,301 +0,0 @@
|
||||
<template>
|
||||
<full-page>
|
||||
<view class="claim-page">
|
||||
<view v-if="tip" class="tip">{{ tip }}</view>
|
||||
<view v-else class="tip">加载中...</view>
|
||||
</view>
|
||||
|
||||
<view v-if="posterVisible" class="poster-mask">
|
||||
<view class="poster-dialog">
|
||||
<image class="poster-image" :src="issue?.posterUrl || ''" mode="widthFix" />
|
||||
<view class="poster-content">{{ buildClaimContent() }}</view>
|
||||
<view class="poster-btn" :class="{ disabled: claiming }" @click="acceptCoupon">
|
||||
{{ claiming ? "领取中..." : "接受" }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</full-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import useAccount from "@/store/account";
|
||||
import api from "@/utils/api";
|
||||
import { confirm, hideLoading, loading, toast } from "@/utils/widget";
|
||||
import { normalizeCorpId } from "@/utils/api-base-config";
|
||||
import FullPage from "@/components/full-page.vue";
|
||||
|
||||
const { account } = storeToRefs(useAccount());
|
||||
const { login } = useAccount();
|
||||
|
||||
const corpId = ref("");
|
||||
const issueId = ref("");
|
||||
const memberId = ref("");
|
||||
const issue = ref(null);
|
||||
const tip = ref("");
|
||||
const claiming = ref(false);
|
||||
const autoPrompted = ref(false);
|
||||
const posterVisible = ref(false);
|
||||
|
||||
const projectNameText = computed(() => {
|
||||
const projects = Array.isArray(issue.value?.projectSnapshot) ? issue.value.projectSnapshot : [];
|
||||
if (!projects.length) return "未配置项目";
|
||||
return projects.map((p) => p.projectName || "项目").join("、");
|
||||
});
|
||||
|
||||
function buildClaimContent() {
|
||||
const name = issue.value?.activityName || "体验券";
|
||||
return `您收到一张【${name}】体验券,包含${projectNameText.value},是否存入您的权益卡包?`;
|
||||
}
|
||||
|
||||
async function ensureLogin() {
|
||||
if (!account.value) await login();
|
||||
if (!account.value) {
|
||||
tip.value = "请先登录后再领取体验券";
|
||||
uni.navigateTo({ url: "/pages/login/login" });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function loadIssueDetail() {
|
||||
const res = await api(
|
||||
"getExperienceCouponIssueDetail",
|
||||
{ corpId: corpId.value, issueId: issueId.value },
|
||||
false
|
||||
);
|
||||
if (!res?.success || !res.data) {
|
||||
tip.value = res?.message || "体验券不存在或已失效";
|
||||
return false;
|
||||
}
|
||||
issue.value = res.data;
|
||||
if (!memberId.value && res.data.customerId) {
|
||||
memberId.value = res.data.customerId;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function hasBoundArchive() {
|
||||
const openid = account.value?.openid || "";
|
||||
if (!openid || !corpId.value) return false;
|
||||
|
||||
if (memberId.value) {
|
||||
try {
|
||||
const detail = await api(
|
||||
"getCustomerByCustomerId",
|
||||
{ corpId: corpId.value, customerId: memberId.value },
|
||||
false
|
||||
);
|
||||
const customer = detail?.data || null;
|
||||
if (customer && String(customer.miniAppId || "") === String(openid)) {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("getCustomerByCustomerId failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const countRes = await api(
|
||||
"getWxAppCustomerCount",
|
||||
{ miniAppId: openid, corpId: corpId.value },
|
||||
false
|
||||
);
|
||||
return Number(countRes?.data || 0) > 0;
|
||||
} catch (e) {
|
||||
console.warn("getWxAppCustomerCount failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function goBindArchive() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/archive/archive-manage?corpId=${encodeURIComponent(corpId.value || "")}`,
|
||||
});
|
||||
}
|
||||
|
||||
function isExpiredIssue() {
|
||||
const expireAt = Number(issue.value?.projectExpireTime || 0);
|
||||
if (expireAt && Date.now() > expireAt) return true;
|
||||
const rule = issue.value?.projectValidRuleSnapshot || {};
|
||||
if (rule.type === "fixedDate" && rule.fixedDate && Date.now() > Number(rule.fixedDate)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function acceptCoupon() {
|
||||
if (claiming.value || !issue.value) return;
|
||||
|
||||
claiming.value = true;
|
||||
loading("领取中...");
|
||||
try {
|
||||
const res = await api("claimExperienceCoupon", {
|
||||
corpId: corpId.value,
|
||||
issueId: issueId.value,
|
||||
claimUserId: account.value?.openid || "",
|
||||
});
|
||||
hideLoading();
|
||||
if (!res?.success) {
|
||||
tip.value = res?.message || "领取失败";
|
||||
toast(tip.value);
|
||||
return;
|
||||
}
|
||||
toast("领取成功");
|
||||
posterVisible.value = false;
|
||||
uni.redirectTo({
|
||||
url: `/pages/experience-coupon/my-rights?corpId=${encodeURIComponent(corpId.value)}&customerId=${encodeURIComponent(memberId.value || "")}&name=${encodeURIComponent(issue.value.customerName || "")}`,
|
||||
});
|
||||
} catch (e) {
|
||||
hideLoading();
|
||||
tip.value = e?.message || "领取失败";
|
||||
toast(tip.value);
|
||||
} finally {
|
||||
claiming.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function promptClaim() {
|
||||
if (autoPrompted.value) return;
|
||||
autoPrompted.value = true;
|
||||
|
||||
if (!issue.value) return;
|
||||
if (issue.value.status === "claimed") {
|
||||
tip.value = "该体验券已领取";
|
||||
uni.redirectTo({
|
||||
url: `/pages/experience-coupon/my-rights?corpId=${encodeURIComponent(corpId.value)}&customerId=${encodeURIComponent(memberId.value || "")}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (issue.value.status === "void") {
|
||||
tip.value = "该体验券已作废";
|
||||
return;
|
||||
}
|
||||
if (issue.value.status && issue.value.status !== "issued") {
|
||||
tip.value = "当前状态不可领取";
|
||||
return;
|
||||
}
|
||||
if (isExpiredIssue()) {
|
||||
tip.value = "体验券已过期,无法领取";
|
||||
return;
|
||||
}
|
||||
|
||||
const bound = await hasBoundArchive();
|
||||
if (!bound) {
|
||||
tip.value = "请先绑定档案后再领取体验券";
|
||||
try {
|
||||
await confirm("请先绑定档案后再领取体验券", {
|
||||
title: "提示",
|
||||
confirmText: "去绑定",
|
||||
cancelText: "取消",
|
||||
});
|
||||
goBindArchive();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
tip.value = "";
|
||||
posterVisible.value = true;
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
loading("加载中...");
|
||||
try {
|
||||
const okLogin = await ensureLogin();
|
||||
if (!okLogin) return;
|
||||
const okDetail = await loadIssueDetail();
|
||||
if (!okDetail) return;
|
||||
await promptClaim();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options = {}) => {
|
||||
corpId.value = normalizeCorpId(options.corpId || "");
|
||||
issueId.value = options.issueId || options.id || "";
|
||||
memberId.value = options.memberId || options.customerId || "";
|
||||
if (!corpId.value || !issueId.value) {
|
||||
tip.value = "领取参数不完整";
|
||||
toast(tip.value);
|
||||
return;
|
||||
}
|
||||
bootstrap();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
if (issue.value && issue.value.status === "issued" && tip.value.includes("绑定")) {
|
||||
autoPrompted.value = false;
|
||||
promptClaim();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.claim-page {
|
||||
min-height: 100%;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
background: #f6fafa;
|
||||
}
|
||||
|
||||
.tip {
|
||||
margin-top: 120rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
.poster-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
background: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
.poster-dialog {
|
||||
width: 100%;
|
||||
max-width: 620rpx;
|
||||
overflow: hidden;
|
||||
border-radius: 20rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.poster-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-height: 700rpx;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.poster-content {
|
||||
padding: 28rpx 30rpx 8rpx;
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.poster-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 80rpx;
|
||||
margin: 24rpx 30rpx 30rpx;
|
||||
border-radius: 40rpx;
|
||||
background: #065bd6;
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.poster-btn.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
@ -1,483 +0,0 @@
|
||||
<template>
|
||||
<full-page pageClass="coupons-page" :customScroll="true">
|
||||
<scroll-view
|
||||
class="coupon-scroll"
|
||||
scroll-y="true"
|
||||
refresher-enabled="true"
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="refreshCoupons"
|
||||
>
|
||||
<view class="page-body">
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<view v-else-if="!list.length" class="empty">暂无待领取体验券</view>
|
||||
<view v-else class="coupon-list">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item._id"
|
||||
class="coupon-card"
|
||||
@click="openPoster(item)"
|
||||
>
|
||||
<image
|
||||
v-if="item.posterUrl"
|
||||
class="coupon-poster"
|
||||
:src="item.posterUrl"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view v-else class="coupon-poster coupon-poster--empty">体验券</view>
|
||||
<view class="coupon-body">
|
||||
<view class="coupon-title">{{ item.activityName || "体验券" }}</view>
|
||||
<view class="coupon-info-grid">
|
||||
<view class="coupon-info-row coupon-info-row--projects">
|
||||
<text class="coupon-info-label">项目</text>
|
||||
<view class="coupon-project-list">
|
||||
<view
|
||||
v-for="project in item.projectSnapshot || []"
|
||||
:key="project.projectId"
|
||||
class="coupon-project-item"
|
||||
>
|
||||
<text>{{ project.projectName || "未命名项目" }}</text>
|
||||
<text>×{{ project.usageCount || 1 }}</text>
|
||||
</view>
|
||||
<text v-if="!(item.projectSnapshot || []).length">未配置项目</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="coupon-info-row">
|
||||
<text class="coupon-info-label">项目有效期</text>
|
||||
<text>{{ item.validText }}</text>
|
||||
</view>
|
||||
<view class="coupon-info-row">
|
||||
<text class="coupon-info-label">活动有效期</text>
|
||||
<text>{{ item.activityValidText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="coupon-action">点击查看海报领取</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 海报领取弹窗 -->
|
||||
<view v-if="posterVisible" class="poster-mask" @click="closePoster">
|
||||
<view class="poster-dialog" @click.stop>
|
||||
<image
|
||||
class="poster-image"
|
||||
:src="current?.posterUrl || ''"
|
||||
mode="widthFix"
|
||||
/>
|
||||
<view class="poster-info">
|
||||
<view class="poster-title">{{ current?.activityName || "体验券" }}</view>
|
||||
<view class="poster-desc">{{ current?.projectText }}</view>
|
||||
</view>
|
||||
<view class="poster-btn" :class="{ disabled: claiming }" @click="acceptCoupon">
|
||||
{{ claiming ? "领取中..." : "接受" }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</full-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import { storeToRefs } from "pinia";
|
||||
import useAccount from "@/store/account";
|
||||
import api from "@/utils/api";
|
||||
import { get } from "@/utils/cache";
|
||||
import { toast } from "@/utils/widget";
|
||||
import { normalizeCorpId } from "@/utils/api-base-config";
|
||||
import FullPage from "@/components/full-page.vue";
|
||||
|
||||
const HOME_CURRENT_TEAM_CACHE_KEY = "home-current-team-info";
|
||||
const { account } = storeToRefs(useAccount());
|
||||
const { getTeams } = useAccount();
|
||||
|
||||
const corpId = ref("");
|
||||
const teamId = ref("");
|
||||
const customerId = ref("");
|
||||
const list = ref([]);
|
||||
const loading = ref(false);
|
||||
const refreshing = ref(false);
|
||||
const posterVisible = ref(false);
|
||||
const current = ref(null);
|
||||
const claiming = ref(false);
|
||||
|
||||
function formatDate(ts) {
|
||||
if (!ts) return "";
|
||||
const d = new Date(Number(ts));
|
||||
if (Number.isNaN(d.getTime())) return "";
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
|
||||
function projectText(projects) {
|
||||
const arr = Array.isArray(projects) ? projects : [];
|
||||
if (!arr.length) return "未配置项目";
|
||||
return arr.map((p) => `${p.projectName || "项目"}×${p.usageCount || 1}`).join("、");
|
||||
}
|
||||
|
||||
function buildValidText(item) {
|
||||
const rule = item.projectValidRuleSnapshot || {};
|
||||
if (item.projectExpireTime) return `有效期至 ${formatDate(item.projectExpireTime)}`;
|
||||
if (rule.type === "fixedDate" && rule.fixedDate) {
|
||||
return `有效期至 ${formatDate(rule.fixedDate)}`;
|
||||
}
|
||||
if (rule.type === "daysAfterClaim") {
|
||||
return `领取后 ${rule.days || 0} 天有效`;
|
||||
}
|
||||
return "领取后按活动规则生效";
|
||||
}
|
||||
|
||||
function buildActivityValidText(item) {
|
||||
const start = formatDate(item.activityStartTime) || "-";
|
||||
const end = formatDate(item.activityEndTime) || "-";
|
||||
return `${start} 至 ${end}`;
|
||||
}
|
||||
|
||||
function getExpireAt(item) {
|
||||
if (item.projectExpireTime) return Number(item.projectExpireTime);
|
||||
const rule = item.projectValidRuleSnapshot || {};
|
||||
if (rule.type === "fixedDate" && rule.fixedDate) {
|
||||
return Number(rule.fixedDate);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function isExpired(item) {
|
||||
const expireAt = getExpireAt(item);
|
||||
if (!expireAt) return false;
|
||||
return Date.now() > expireAt;
|
||||
}
|
||||
|
||||
async function resolveContext(options = {}) {
|
||||
corpId.value = normalizeCorpId(options.corpId || corpId.value || "");
|
||||
teamId.value = options.teamId || teamId.value || "";
|
||||
customerId.value = options.customerId || options.memberId || customerId.value || "";
|
||||
|
||||
const cached = get(HOME_CURRENT_TEAM_CACHE_KEY) || {};
|
||||
let teams = [];
|
||||
try {
|
||||
teams = (await getTeams()) || [];
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
const matched =
|
||||
teams.find((t) => t.teamId === (teamId.value || cached.teamId)) ||
|
||||
teams.find((t) => normalizeCorpId(t.corpId) === corpId.value) ||
|
||||
teams[0] ||
|
||||
null;
|
||||
|
||||
if (!corpId.value) corpId.value = normalizeCorpId(matched?.corpId || cached.corpId || "");
|
||||
if (!teamId.value) teamId.value = matched?.teamId || cached.teamId || "";
|
||||
if (!corpId.value) return false;
|
||||
|
||||
if (customerId.value) return true;
|
||||
|
||||
const miniAppId = account.value?.openid || uni.getStorageSync("openid") || "";
|
||||
if (!miniAppId) return false;
|
||||
const res = await api("getMiniAppCustomers", { miniAppId, corpId: corpId.value }, false);
|
||||
const customers = res?.success && Array.isArray(res.data) ? res.data : [];
|
||||
const preferred = customers.find((c) => c.relationship === "本人") || customers[0];
|
||||
customerId.value = preferred?._id || "";
|
||||
return !!customerId.value;
|
||||
}
|
||||
|
||||
async function voidExpired(item) {
|
||||
try {
|
||||
await api(
|
||||
"voidExperienceCouponIssue",
|
||||
{
|
||||
corpId: corpId.value,
|
||||
issueId: item._id,
|
||||
voidReason: "有效期内未领取,自动失效",
|
||||
},
|
||||
false
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn("auto void failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCoupons(options = {}) {
|
||||
const { silent = false } = options;
|
||||
if (!corpId.value || !customerId.value) {
|
||||
list.value = [];
|
||||
return;
|
||||
}
|
||||
if (!silent) loading.value = true;
|
||||
try {
|
||||
const res = await api(
|
||||
"getCustomerExperienceCoupons",
|
||||
{
|
||||
corpId: corpId.value,
|
||||
customerId: customerId.value,
|
||||
status: "issued",
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
},
|
||||
false
|
||||
);
|
||||
if (!res?.success) {
|
||||
toast(res?.message || "加载体验券失败");
|
||||
list.value = [];
|
||||
return;
|
||||
}
|
||||
const raw = res.list || res.data?.list || [];
|
||||
const valid = [];
|
||||
for (const item of raw) {
|
||||
if (isExpired(item)) {
|
||||
voidExpired(item);
|
||||
continue;
|
||||
}
|
||||
valid.push({
|
||||
...item,
|
||||
projectText: projectText(item.projectSnapshot),
|
||||
validText: buildValidText(item),
|
||||
activityValidText: buildActivityValidText(item),
|
||||
});
|
||||
}
|
||||
list.value = valid;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshCoupons() {
|
||||
if (refreshing.value) return;
|
||||
refreshing.value = true;
|
||||
try {
|
||||
await loadCoupons({ silent: true });
|
||||
} finally {
|
||||
refreshing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function openPoster(item) {
|
||||
if (!item?.posterUrl) {
|
||||
toast("该体验券暂无海报");
|
||||
return;
|
||||
}
|
||||
current.value = item;
|
||||
posterVisible.value = true;
|
||||
}
|
||||
|
||||
function closePoster() {
|
||||
if (claiming.value) return;
|
||||
posterVisible.value = false;
|
||||
current.value = null;
|
||||
}
|
||||
|
||||
async function acceptCoupon() {
|
||||
if (!current.value || claiming.value) return;
|
||||
claiming.value = true;
|
||||
try {
|
||||
const res = await api("claimExperienceCoupon", {
|
||||
corpId: corpId.value,
|
||||
issueId: current.value._id,
|
||||
claimUserId: account.value?.openid || "",
|
||||
});
|
||||
if (!res?.success) {
|
||||
toast(res?.message || "领取失败");
|
||||
return;
|
||||
}
|
||||
toast("领取成功");
|
||||
posterVisible.value = false;
|
||||
uni.redirectTo({
|
||||
url: `/pages/experience-coupon/my-rights?corpId=${encodeURIComponent(corpId.value)}&teamId=${encodeURIComponent(teamId.value)}&customerId=${encodeURIComponent(customerId.value)}&name=${encodeURIComponent(current.value.customerName || "")}`,
|
||||
});
|
||||
} catch (e) {
|
||||
toast(e?.message || "领取失败");
|
||||
} finally {
|
||||
claiming.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(async (options = {}) => {
|
||||
uni.setNavigationBarTitle({ title: "我的体验券" });
|
||||
const ok = await resolveContext(options);
|
||||
if (!ok) {
|
||||
toast("请先绑定档案");
|
||||
return;
|
||||
}
|
||||
await loadCoupons();
|
||||
});
|
||||
|
||||
onShow(async () => {
|
||||
if (corpId.value && customerId.value) {
|
||||
await loadCoupons();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.coupons-page :deep(.page-scroll) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.coupon-scroll {
|
||||
height: 100%;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-body {
|
||||
min-height: 100%;
|
||||
padding: 24rpx 30rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.coupon-card {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 8rpx 10rpx 0 rgba(60, 169, 145, 0.06);
|
||||
}
|
||||
|
||||
.coupon-poster {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #f5f7fa;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.coupon-poster--empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #c0c4cc;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.coupon-body {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.coupon-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.coupon-info-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
color: #606266;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.coupon-info-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.coupon-info-label {
|
||||
width: 132rpx;
|
||||
flex-shrink: 0;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.coupon-info-row > text:last-child,
|
||||
.coupon-project-list {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.coupon-project-list {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.coupon-project-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.coupon-project-item text:first-child {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.coupon-action {
|
||||
margin-top: 12rpx;
|
||||
font-size: 22rpx;
|
||||
color: #ff8a00;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.poster-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(0, 0, 0, 0.65);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.poster-dialog {
|
||||
width: 100%;
|
||||
max-width: 620rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.poster-image {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.poster-info {
|
||||
padding: 24rpx 28rpx 8rpx;
|
||||
}
|
||||
|
||||
.poster-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.poster-desc {
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.poster-btn {
|
||||
margin: 24rpx 28rpx 32rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
background: #065bd6;
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.poster-btn.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
@ -1,441 +0,0 @@
|
||||
<template>
|
||||
<full-page pageClass="rights-page" :customScroll="true">
|
||||
<scroll-view
|
||||
class="rights-scroll"
|
||||
scroll-y="true"
|
||||
refresher-enabled="true"
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="refreshRights"
|
||||
>
|
||||
<view class="page-body">
|
||||
<view class="user-card" @click="toSelectPage">
|
||||
<view class="avatar">
|
||||
<uni-icons type="person-filled" size="36" color="#c0c4cc" />
|
||||
</view>
|
||||
<view class="user-main">
|
||||
<view class="user-name">{{ customerName || "未选择档案" }}</view>
|
||||
<view class="user-corp">所属机构:{{ corpName || "-" }}</view>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#c0c4cc" />
|
||||
</view>
|
||||
|
||||
<view class="summary">
|
||||
以下为“<text class="summary-name">{{ customerName || "-" }}</text>”的所有剩余权益
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<view v-else-if="!groups.length" class="empty">暂无权益</view>
|
||||
<view v-else class="group-list">
|
||||
<view v-for="group in groups" :key="group.key" class="group-card">
|
||||
<view class="group-head">
|
||||
<view class="group-badge">{{ group.badge }}</view>
|
||||
<view class="group-title">{{ group.title }}</view>
|
||||
</view>
|
||||
<view class="table-head">
|
||||
<text class="col col-name">项目名称</text>
|
||||
<text class="col col-num">总次数</text>
|
||||
<text class="col col-num">剩余次数</text>
|
||||
<text class="col col-date">有效期至</text>
|
||||
</view>
|
||||
<view v-for="row in group.rows" :key="row._id" class="table-row">
|
||||
<text class="col col-name">{{ row.projectName }}</text>
|
||||
<text class="col col-num">{{ row.usageCount }}</text>
|
||||
<text class="col col-num col-rest">{{ row.restUsageCount }}</text>
|
||||
<text class="col col-date">{{ row.validText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</full-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import { storeToRefs } from "pinia";
|
||||
import useAccount from "@/store/account";
|
||||
import api from "@/utils/api";
|
||||
import { get, remove } from "@/utils/cache";
|
||||
import { toast } from "@/utils/widget";
|
||||
import { normalizeCorpId } from "@/utils/api-base-config";
|
||||
import FullPage from "@/components/full-page.vue";
|
||||
|
||||
const HOME_CURRENT_TEAM_CACHE_KEY = "home-current-team-info";
|
||||
const RIGHTS_SELECTION_CACHE_KEY = "experience-coupon-rights-selection";
|
||||
const { account } = storeToRefs(useAccount());
|
||||
const { getTeams } = useAccount();
|
||||
|
||||
const corpId = ref("");
|
||||
const teamId = ref("");
|
||||
const customerId = ref("");
|
||||
const customerName = ref("");
|
||||
const corpName = ref("");
|
||||
const teams = ref([]);
|
||||
const records = ref([]);
|
||||
const loading = ref(false);
|
||||
const refreshing = ref(false);
|
||||
|
||||
function formatDate(ts) {
|
||||
if (!ts) return "无限期";
|
||||
const d = new Date(Number(ts));
|
||||
if (Number.isNaN(d.getTime())) return "无限期";
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
|
||||
function pickCorpName(team = {}) {
|
||||
return team.licenseHospitalName || team.leaderCorp || team.corpName || "-";
|
||||
}
|
||||
|
||||
const groups = computed(() => {
|
||||
const list = Array.isArray(records.value) ? records.value : [];
|
||||
const valid = list.filter((item) => {
|
||||
const status = String(item.treatmentStatus || "");
|
||||
return !["void", "canceled", "cancel"].includes(status);
|
||||
});
|
||||
|
||||
const packageMap = {};
|
||||
const singleGroups = [];
|
||||
|
||||
valid.forEach((item) => {
|
||||
const row = {
|
||||
_id: item._id,
|
||||
projectName: item.projectName || "-",
|
||||
usageCount: Number(item.usageCount || 0),
|
||||
restUsageCount: Number(item.restUsageCount || 0),
|
||||
validText: formatDate(item.validTime),
|
||||
};
|
||||
|
||||
if (item.packageId) {
|
||||
const key = String(item.packageId);
|
||||
if (!packageMap[key]) {
|
||||
packageMap[key] = {
|
||||
key: `pkg_${key}`,
|
||||
badge: "套",
|
||||
title: `套餐(${item.packageName || "未命名套餐"})`,
|
||||
rows: [],
|
||||
};
|
||||
}
|
||||
packageMap[key].rows.push(row);
|
||||
return;
|
||||
}
|
||||
|
||||
singleGroups.push({
|
||||
key: `single_${item._id}`,
|
||||
badge: "项",
|
||||
title: "单项目",
|
||||
rows: [row],
|
||||
});
|
||||
});
|
||||
|
||||
return [...singleGroups, ...Object.values(packageMap)];
|
||||
});
|
||||
|
||||
function applySelection(selection = {}) {
|
||||
corpId.value = normalizeCorpId(selection.corpId || corpId.value || "");
|
||||
teamId.value = selection.teamId || teamId.value || "";
|
||||
customerId.value = selection.customerId || selection.memberId || customerId.value || "";
|
||||
customerName.value = selection.name || customerName.value || "";
|
||||
if (selection.corpName) {
|
||||
corpName.value = selection.corpName;
|
||||
return;
|
||||
}
|
||||
const matched = teams.value.find(
|
||||
(team) =>
|
||||
normalizeCorpId(team.corpId) === corpId.value &&
|
||||
(!teamId.value || team.teamId === teamId.value)
|
||||
);
|
||||
corpName.value = pickCorpName(matched || {});
|
||||
}
|
||||
|
||||
async function resolveContext(options = {}) {
|
||||
const cached = get(HOME_CURRENT_TEAM_CACHE_KEY) || {};
|
||||
try {
|
||||
teams.value = (await getTeams()) || [];
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
teams.value = [];
|
||||
}
|
||||
|
||||
const incoming = {
|
||||
corpId: options.corpId || corpId.value || cached.corpId || "",
|
||||
teamId: options.teamId || teamId.value || cached.teamId || "",
|
||||
customerId: options.customerId || options.memberId || customerId.value || "",
|
||||
name: options.name ? decodeURIComponent(options.name) : customerName.value,
|
||||
};
|
||||
applySelection(incoming);
|
||||
|
||||
const matched =
|
||||
teams.value.find(
|
||||
(team) =>
|
||||
team.teamId === teamId.value &&
|
||||
normalizeCorpId(team.corpId) === corpId.value
|
||||
) ||
|
||||
teams.value.find((team) => team.teamId === teamId.value) ||
|
||||
teams.value.find((team) => normalizeCorpId(team.corpId) === corpId.value) ||
|
||||
teams.value[0] ||
|
||||
null;
|
||||
|
||||
if (!corpId.value) corpId.value = normalizeCorpId(matched?.corpId || "");
|
||||
if (!teamId.value) teamId.value = matched?.teamId || "";
|
||||
if (!corpName.value) corpName.value = pickCorpName(matched || {});
|
||||
|
||||
if (customerId.value) return true;
|
||||
|
||||
const miniAppId = account.value?.openid || uni.getStorageSync("openid") || "";
|
||||
if (!miniAppId || !corpId.value) return false;
|
||||
|
||||
const res = await api("getMiniAppCustomers", { miniAppId, corpId: corpId.value }, false);
|
||||
const customers = res?.success && Array.isArray(res.data) ? res.data : [];
|
||||
const preferred = customers.find((item) => item.relationship === "本人") || customers[0];
|
||||
customerId.value = preferred?._id || "";
|
||||
customerName.value = preferred?.name || customerName.value || "";
|
||||
return !!customerId.value;
|
||||
}
|
||||
|
||||
async function loadRights(options = {}) {
|
||||
const { silent = false } = options;
|
||||
if (!corpId.value || !customerId.value) {
|
||||
records.value = [];
|
||||
return;
|
||||
}
|
||||
if (!silent) loading.value = true;
|
||||
try {
|
||||
const res = await api(
|
||||
"getTreatmentRecord",
|
||||
{
|
||||
corpId: corpId.value,
|
||||
customerId: customerId.value,
|
||||
page: 1,
|
||||
pageSize: 200,
|
||||
},
|
||||
false
|
||||
);
|
||||
if (!res?.success) {
|
||||
toast(res?.message || "加载权益失败");
|
||||
records.value = [];
|
||||
return;
|
||||
}
|
||||
records.value = res.list || res.data?.list || [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshRights() {
|
||||
if (refreshing.value) return;
|
||||
refreshing.value = true;
|
||||
try {
|
||||
await loadRights({ silent: true });
|
||||
} finally {
|
||||
refreshing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function syncSelectedProfile() {
|
||||
const selected = get(RIGHTS_SELECTION_CACHE_KEY);
|
||||
if (!selected) return false;
|
||||
remove(RIGHTS_SELECTION_CACHE_KEY);
|
||||
applySelection(selected);
|
||||
await loadRights();
|
||||
return true;
|
||||
}
|
||||
|
||||
function toSelectPage() {
|
||||
const params = [
|
||||
`corpId=${encodeURIComponent(corpId.value || "")}`,
|
||||
`teamId=${encodeURIComponent(teamId.value || "")}`,
|
||||
`customerId=${encodeURIComponent(customerId.value || "")}`,
|
||||
`name=${encodeURIComponent(customerName.value || "")}`,
|
||||
].join("&");
|
||||
uni.navigateTo({
|
||||
url: `/pages/experience-coupon/select-rights-archive?${params}`,
|
||||
});
|
||||
}
|
||||
|
||||
onLoad(async (options = {}) => {
|
||||
uni.setNavigationBarTitle({ title: "我的权益" });
|
||||
const ok = await resolveContext(options);
|
||||
if (!ok) {
|
||||
toast("请先绑定档案");
|
||||
return;
|
||||
}
|
||||
await loadRights();
|
||||
});
|
||||
|
||||
onShow(async () => {
|
||||
const changed = await syncSelectedProfile();
|
||||
if (changed) return;
|
||||
if (corpId.value && customerId.value) {
|
||||
await loadRights();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rights-page :deep(.page-scroll) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.rights-scroll {
|
||||
height: 100%;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.page-body {
|
||||
min-height: 100%;
|
||||
padding: 0 0 40rpx;
|
||||
box-sizing: border-box;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 0;
|
||||
padding: 24rpx 30rpx;
|
||||
border-bottom: 1px solid #eceff4;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 50%;
|
||||
background: #f0f2f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-main {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.user-corp {
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.summary {
|
||||
padding: 18rpx 30rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
background: #f0f2f5;
|
||||
border-top: 1px solid #eceff4;
|
||||
border-bottom: 1px solid #eceff4;
|
||||
}
|
||||
|
||||
.summary-name {
|
||||
color: #065bd6;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.group-list {
|
||||
padding-top: 12rpx;
|
||||
}
|
||||
|
||||
.group-card {
|
||||
margin-bottom: 16rpx;
|
||||
background: #fff;
|
||||
border-top: 1px solid #eceff4;
|
||||
border-bottom: 1px solid #eceff4;
|
||||
}
|
||||
|
||||
.group-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 18rpx 24rpx;
|
||||
border-bottom: 1px solid #eceff4;
|
||||
}
|
||||
|
||||
.group-badge {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
background: #2f7df6;
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.table-head,
|
||||
.table-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 18rpx 24rpx;
|
||||
}
|
||||
|
||||
.table-head {
|
||||
color: #666;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
background: #f5f9fc;
|
||||
border-bottom: 1px solid #eceff4;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
color: #333;
|
||||
font-size: 26rpx;
|
||||
border-bottom: 1px solid #f1f3f6;
|
||||
}
|
||||
|
||||
.table-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.col {
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.col-name {
|
||||
flex: 1.35;
|
||||
text-align: left;
|
||||
padding-right: 16rpx;
|
||||
}
|
||||
|
||||
.col-num {
|
||||
flex: 0.7;
|
||||
}
|
||||
|
||||
.col-date {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.col-rest {
|
||||
color: #ff9800;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
@ -1,293 +0,0 @@
|
||||
<template>
|
||||
<full-page pageClass="rights-selector-page" :customScroll="true">
|
||||
<view class="page-body">
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<view v-else-if="!options.length" class="empty">暂无可用档案</view>
|
||||
<view v-else class="archive-list">
|
||||
<view
|
||||
v-for="item in options"
|
||||
:key="item.key"
|
||||
class="archive-card"
|
||||
:class="{ active: item.isCurrent }"
|
||||
@click="selectArchive(item)"
|
||||
>
|
||||
<view class="archive-head">
|
||||
<view class="archive-name-row">
|
||||
<view class="archive-name">{{ item.name || "未命名" }}</view>
|
||||
<view v-if="item.relationship" class="archive-tag">{{ item.relationship }}</view>
|
||||
</view>
|
||||
<view v-if="item.isCurrent" class="archive-current">当前</view>
|
||||
</view>
|
||||
<view class="archive-meta">{{ item.metaText }}</view>
|
||||
<view class="archive-id">证件号:{{ item.idCardText }}</view>
|
||||
<view class="archive-corp">{{ item.corpName }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</full-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { storeToRefs } from "pinia";
|
||||
import useAccount from "@/store/account";
|
||||
import api from "@/utils/api";
|
||||
import { set } from "@/utils/cache";
|
||||
import { toast } from "@/utils/widget";
|
||||
import { normalizeCorpId } from "@/utils/api-base-config";
|
||||
import FullPage from "@/components/full-page.vue";
|
||||
|
||||
const RIGHTS_SELECTION_CACHE_KEY = "experience-coupon-rights-selection";
|
||||
const { account } = storeToRefs(useAccount());
|
||||
const { getTeams } = useAccount();
|
||||
|
||||
const loading = ref(false);
|
||||
const options = ref([]);
|
||||
const currentCorpId = ref("");
|
||||
const currentTeamId = ref("");
|
||||
const currentCustomerId = ref("");
|
||||
const mode = ref("back");
|
||||
const target = ref("");
|
||||
|
||||
function pickCorpName(team = {}) {
|
||||
return team.licenseHospitalName || team.leaderCorp || team.corpName || "-";
|
||||
}
|
||||
|
||||
function maskMobile(mobile = "") {
|
||||
const value = String(mobile || "").trim();
|
||||
if (!value) return "";
|
||||
if (/^\d{11}$/.test(value)) {
|
||||
return value.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function maskIdCard(idCard = "") {
|
||||
const value = String(idCard || "").trim();
|
||||
if (!value) return "--";
|
||||
if (value.length <= 4) return value;
|
||||
return `${"*".repeat(Math.max(value.length - 4, 2))}${value.slice(-4)}`;
|
||||
}
|
||||
|
||||
function buildMetaText(customer = {}) {
|
||||
const parts = [];
|
||||
if (customer.age) parts.push(`${customer.age}岁`);
|
||||
if (customer.mobile) parts.push(maskMobile(customer.mobile));
|
||||
return parts.length ? parts.join(",") : "暂无档案信息";
|
||||
}
|
||||
|
||||
function dedupeByCorp(list = []) {
|
||||
const map = new Map();
|
||||
list.forEach((team) => {
|
||||
const corpId = normalizeCorpId(team.corpId);
|
||||
if (!corpId || map.has(corpId)) return;
|
||||
map.set(corpId, { ...team, corpId });
|
||||
});
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
function buildTargetUrl(item) {
|
||||
const corp = encodeURIComponent(item.corpId || "");
|
||||
const team = encodeURIComponent(item.teamId || "");
|
||||
const customer = encodeURIComponent(item.customerId || "");
|
||||
const name = encodeURIComponent(item.name || "");
|
||||
|
||||
if (target.value === "coupons") {
|
||||
return `/pages/experience-coupon/my-coupons?corpId=${corp}&teamId=${team}&customerId=${customer}`;
|
||||
}
|
||||
if (target.value === "health") {
|
||||
return `/pages/health/list?teamId=${team}&corpId=${corp}&id=${customer}&name=${name}`;
|
||||
}
|
||||
if (target.value === "treatment") {
|
||||
const corpName = encodeURIComponent(item.corpName || "");
|
||||
return `/pages/record/treatment-record?teamId=${team}&corpId=${corp}&customerId=${customer}&name=${name}&corpName=${corpName}`;
|
||||
}
|
||||
if (target.value === "appointment") {
|
||||
const corpName = encodeURIComponent(item.corpName || "");
|
||||
return `/pages/record/appointment-record?teamId=${team}&corpId=${corp}&customerId=${customer}&name=${name}&corpName=${corpName}`;
|
||||
}
|
||||
return `/pages/experience-coupon/my-rights?corpId=${corp}&teamId=${team}&customerId=${customer}&name=${name}`;
|
||||
}
|
||||
|
||||
async function loadArchives() {
|
||||
const miniAppId = account.value?.openid || uni.getStorageSync("openid") || "";
|
||||
if (!miniAppId) {
|
||||
options.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const teams = (await getTeams()) || [];
|
||||
const corpTeams = dedupeByCorp(teams);
|
||||
const responses = await Promise.all(
|
||||
corpTeams.map(async (team) => {
|
||||
try {
|
||||
const res = await api("getMiniAppCustomers", { miniAppId, corpId: team.corpId }, false);
|
||||
const customers = res?.success && Array.isArray(res.data) ? res.data : [];
|
||||
return customers.map((customer) => ({ team, customer }));
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
return [];
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const list = responses
|
||||
.flat()
|
||||
.map(({ team, customer }) => ({
|
||||
key: `${team.corpId}_${customer._id}`,
|
||||
corpId: team.corpId,
|
||||
teamId: team.teamId || "",
|
||||
customerId: customer._id || "",
|
||||
name: customer.name || "",
|
||||
relationship: customer.relationship || "",
|
||||
metaText: buildMetaText(customer),
|
||||
idCardText: maskIdCard(customer.idCard),
|
||||
corpName: pickCorpName(team),
|
||||
}))
|
||||
.filter((item) => item.corpId && item.customerId)
|
||||
.sort((a, b) => {
|
||||
const aScore =
|
||||
(a.corpId === currentCorpId.value ? 4 : 0) +
|
||||
(a.teamId === currentTeamId.value ? 2 : 0) +
|
||||
(a.customerId === currentCustomerId.value ? 8 : 0) +
|
||||
(a.relationship === "本人" ? 1 : 0);
|
||||
const bScore =
|
||||
(b.corpId === currentCorpId.value ? 4 : 0) +
|
||||
(b.teamId === currentTeamId.value ? 2 : 0) +
|
||||
(b.customerId === currentCustomerId.value ? 8 : 0) +
|
||||
(b.relationship === "本人" ? 1 : 0);
|
||||
return bScore - aScore;
|
||||
})
|
||||
.map((item) => ({
|
||||
...item,
|
||||
isCurrent:
|
||||
item.corpId === currentCorpId.value &&
|
||||
item.customerId === currentCustomerId.value,
|
||||
}));
|
||||
|
||||
options.value = list;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function selectArchive(item) {
|
||||
if (!item) return;
|
||||
|
||||
if (mode.value === "target") {
|
||||
uni.navigateTo({
|
||||
url: buildTargetUrl(item),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
set(RIGHTS_SELECTION_CACHE_KEY, {
|
||||
corpId: item.corpId,
|
||||
teamId: item.teamId,
|
||||
customerId: item.customerId,
|
||||
name: item.name,
|
||||
corpName: item.corpName,
|
||||
});
|
||||
uni.navigateBack();
|
||||
}
|
||||
|
||||
onLoad(async (optionsData = {}) => {
|
||||
uni.setNavigationBarTitle({ title: "选择档案" });
|
||||
currentCorpId.value = normalizeCorpId(optionsData.corpId || "");
|
||||
currentTeamId.value = optionsData.teamId || "";
|
||||
currentCustomerId.value = optionsData.customerId || optionsData.memberId || "";
|
||||
mode.value = optionsData.mode === "target" ? "target" : "back";
|
||||
target.value = optionsData.target || "rights";
|
||||
await loadArchives();
|
||||
if (!options.value.length) {
|
||||
toast("暂无可用档案");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-body {
|
||||
min-height: 100%;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
|
||||
.archive-card {
|
||||
background: #fff;
|
||||
border-radius: 18rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
border: 2rpx solid transparent;
|
||||
box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.archive-card.active {
|
||||
border-color: #065bd6;
|
||||
}
|
||||
|
||||
.archive-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.archive-name-row {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.archive-name {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #1f2329;
|
||||
}
|
||||
|
||||
.archive-tag,
|
||||
.archive-current {
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.archive-tag {
|
||||
color: #0f766e;
|
||||
background: rgba(15, 118, 110, 0.12);
|
||||
}
|
||||
|
||||
.archive-current {
|
||||
color: #065bd6;
|
||||
background: rgba(6, 91, 214, 0.1);
|
||||
}
|
||||
|
||||
.archive-meta,
|
||||
.archive-id {
|
||||
font-size: 28rpx;
|
||||
color: #4b5563;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.archive-corp {
|
||||
margin-top: 16rpx;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1px solid #eef2f6;
|
||||
font-size: 28rpx;
|
||||
color: #065bd6;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<view class="redirect-page">跳转中...</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
|
||||
onLoad((options = {}) => {
|
||||
const type = options.type === "coupon" ? "coupon" : "rights";
|
||||
const params = [
|
||||
options.corpId ? `corpId=${encodeURIComponent(options.corpId)}` : "",
|
||||
options.teamId ? `teamId=${encodeURIComponent(options.teamId)}` : "",
|
||||
options.customerId ? `customerId=${encodeURIComponent(options.customerId)}` : "",
|
||||
options.memberId ? `memberId=${encodeURIComponent(options.memberId)}` : "",
|
||||
options.name ? `name=${encodeURIComponent(options.name)}` : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("&");
|
||||
|
||||
const target =
|
||||
type === "coupon"
|
||||
? `/pages/experience-coupon/my-coupons?${params}`
|
||||
: `/pages/experience-coupon/my-rights?${params}`;
|
||||
|
||||
uni.redirectTo({ url: target });
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.redirect-page {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
@ -94,7 +94,7 @@ async function changeTeam({ teamId, corpId, corpUserId, externalUserId, qrid, re
|
||||
qrid,
|
||||
referenceCustomerId: referenceCustomerId || ''
|
||||
});
|
||||
await login('', { forceVerify: true })
|
||||
await login()
|
||||
if (account.value && account.value.mobile) {
|
||||
bindTeam({ corpUserId, externalUserId })
|
||||
} else {
|
||||
@ -147,18 +147,6 @@ onLoad((options) => {
|
||||
if (options.q) {
|
||||
opts.value = JSON.stringify(options)
|
||||
changeTeam(parseInviteOptions(options));
|
||||
} else if (options.type === 'experienceCoupon' && options.issueId && options.corpId) {
|
||||
const params = [
|
||||
`corpId=${encodeURIComponent(options.corpId || "")}`,
|
||||
`issueId=${encodeURIComponent(options.issueId || "")}`,
|
||||
options.memberId ? `memberId=${encodeURIComponent(options.memberId)}` : "",
|
||||
options.couponId ? `couponId=${encodeURIComponent(options.couponId)}` : "",
|
||||
options.unionid ? `unionid=${encodeURIComponent(options.unionid)}` : "",
|
||||
options.externalUserId ? `externalUserId=${encodeURIComponent(options.externalUserId)}` : "",
|
||||
].filter(Boolean).join("&");
|
||||
uni.redirectTo({
|
||||
url: `/pages/experience-coupon/claim?${params}`,
|
||||
});
|
||||
} else if (options.type === 'archive' || (options.teamId && options.corpId)) {
|
||||
changeTeam(options);
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<!-- Blue background area for header -->
|
||||
<view class="header-bg"></view>
|
||||
|
||||
<!-- User Card -->
|
||||
<view class="user-card">
|
||||
<view class="avatar-container">
|
||||
<uni-icons type="person-filled" size="60" color="#cccccc" class="default-avatar"></uni-icons>
|
||||
@ -12,33 +14,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="shortcut-bar">
|
||||
<view class="shortcut-item" @click="openRightsWallet">
|
||||
<view class="shortcut-icon shortcut-icon--rights">
|
||||
<uni-icons type="gift" size="28" color="#065bd6"></uni-icons>
|
||||
</view>
|
||||
<text class="shortcut-title">我的权益</text>
|
||||
</view>
|
||||
<view class="shortcut-item" @click="openCouponWallet">
|
||||
<view class="shortcut-icon shortcut-icon--coupon">
|
||||
<uni-icons type="wallet" size="28" color="#ff8a00"></uni-icons>
|
||||
</view>
|
||||
<text class="shortcut-title">我的体验券</text>
|
||||
</view>
|
||||
<view class="shortcut-item" @click="openTreatmentRecord">
|
||||
<view class="shortcut-icon shortcut-icon--record">
|
||||
<uni-icons type="compose" size="28" color="#19be6b"></uni-icons>
|
||||
</view>
|
||||
<text class="shortcut-title">治疗记录</text>
|
||||
</view>
|
||||
<view class="shortcut-item" @click="openAppointmentRecord">
|
||||
<view class="shortcut-icon shortcut-icon--appointment">
|
||||
<uni-icons type="calendar" size="28" color="#7c3aed"></uni-icons>
|
||||
</view>
|
||||
<text class="shortcut-title">预约记录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Menu List -->
|
||||
<view class="menu-container">
|
||||
<view class="px-15 py-12 flex items-center border-b" @click="toPage('/pages/mine/contact')">
|
||||
<view class="flex-shrink-0 item-icon">
|
||||
@ -77,207 +53,81 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="menu-container">
|
||||
<uni-list>
|
||||
<uni-list-item title="联系客服" link to="/pages/mine/contact" clickable>
|
||||
<template v-slot:header>
|
||||
<view class="item-icon">
|
||||
<uni-icons type="headphones" size="22" color="#000"></uni-icons>
|
||||
</view>
|
||||
</template>
|
||||
</uni-list-item>
|
||||
<uni-list-item title="隐私保护政策" link to="/pages/common/privacy" clickable>
|
||||
<template v-slot:header>
|
||||
<view class="item-icon">
|
||||
<uni-icons type="locked" size="22" color="#000"></uni-icons>
|
||||
</view>
|
||||
</template>
|
||||
</uni-list-item>
|
||||
<uni-list-item title="用户注册协议" link to="/pages/common/agreement" clickable>
|
||||
<template v-slot:header>
|
||||
<view class="item-icon">
|
||||
<uni-icons type="paperclip" size="22" color="#000"></uni-icons>
|
||||
</view>
|
||||
</template>
|
||||
</uni-list-item>
|
||||
<uni-list-item title="退出登录" link @click="handleLogout">
|
||||
<template v-slot:header>
|
||||
<view class="item-icon">
|
||||
<uni-icons type="undo" size="22" color="#000"></uni-icons>
|
||||
</view>
|
||||
</template>
|
||||
</uni-list-item>
|
||||
</uni-list>
|
||||
</view> -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import accountStore from "@/store/account";
|
||||
import api from "@/utils/api";
|
||||
import { get } from "@/utils/cache";
|
||||
import { normalizeCorpId } from "@/utils/api-base-config";
|
||||
import { toast } from "@/utils/widget";
|
||||
|
||||
const HOME_CURRENT_TEAM_CACHE_KEY = "home-current-team-info";
|
||||
|
||||
const store = accountStore();
|
||||
const { account } = storeToRefs(store);
|
||||
const { getTeams, login } = store;
|
||||
|
||||
const context = ref({
|
||||
corpId: "",
|
||||
teamId: "",
|
||||
customerId: "",
|
||||
customerName: "",
|
||||
customers: [],
|
||||
});
|
||||
const resolving = ref(false);
|
||||
|
||||
const maskedPhone = computed(() => {
|
||||
const localAccount = uni.getStorageSync("account");
|
||||
const mobile = localAccount?.mobile || account.value?.mobile || "";
|
||||
if (!mobile) return "";
|
||||
return mobile.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
|
||||
const account = uni.getStorageSync('account');
|
||||
const mobile = account?.mobile || store.account?.mobile || '';
|
||||
if (!mobile) return '';
|
||||
return mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
|
||||
});
|
||||
|
||||
const handleLogout = () => {
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "确定要退出登录吗?",
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: function (res) {
|
||||
if (res.confirm) {
|
||||
uni.removeStorageSync("account");
|
||||
uni.removeStorageSync("openid");
|
||||
// Clear login info
|
||||
uni.removeStorageSync('account');
|
||||
uni.removeStorageSync('openid');
|
||||
store.account = null;
|
||||
|
||||
// Redirect to login or home
|
||||
uni.reLaunch({
|
||||
url: "/pages/login/login",
|
||||
url: '/pages/login/login'
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function pickPreferredCustomer(list = []) {
|
||||
if (!Array.isArray(list) || !list.length) return null;
|
||||
return list.find((item) => item.relationship === "本人") || list[0] || null;
|
||||
}
|
||||
|
||||
async function ensureLogin() {
|
||||
if (account.value?.openid) return true;
|
||||
const local = uni.getStorageSync("account");
|
||||
if (local?.openid) {
|
||||
store.account = local;
|
||||
return true;
|
||||
}
|
||||
const res = await login();
|
||||
return !!(res && res.openid);
|
||||
}
|
||||
|
||||
async function resolveTeam() {
|
||||
const cached = get(HOME_CURRENT_TEAM_CACHE_KEY) || {};
|
||||
let teams = [];
|
||||
try {
|
||||
teams = (await getTeams()) || [];
|
||||
} catch (e) {
|
||||
console.warn("getTeams failed", e);
|
||||
}
|
||||
const cachedCorpId = normalizeCorpId(cached.corpId || "");
|
||||
const matched =
|
||||
teams.find(
|
||||
(item) =>
|
||||
item.teamId === cached.teamId &&
|
||||
(!cachedCorpId || normalizeCorpId(item.corpId || "") === cachedCorpId)
|
||||
) ||
|
||||
teams.find((item) => item.teamId === cached.teamId) ||
|
||||
teams[0] ||
|
||||
null;
|
||||
|
||||
return {
|
||||
corpId: normalizeCorpId(matched?.corpId || cached.corpId || ""),
|
||||
teamId: matched?.teamId || cached.teamId || "",
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveContext({ force = false } = {}) {
|
||||
if (resolving.value) return context.value;
|
||||
if (!force && context.value.corpId && context.value.customerId) {
|
||||
return context.value;
|
||||
}
|
||||
|
||||
resolving.value = true;
|
||||
try {
|
||||
const ok = await ensureLogin();
|
||||
if (!ok) {
|
||||
toast("请先登录");
|
||||
return null;
|
||||
}
|
||||
|
||||
const team = await resolveTeam();
|
||||
if (!team.corpId) {
|
||||
toast("请先加入服务团队");
|
||||
return null;
|
||||
}
|
||||
|
||||
const miniAppId = account.value?.openid || uni.getStorageSync("openid") || "";
|
||||
const res = await api(
|
||||
"getMiniAppCustomers",
|
||||
{ miniAppId, corpId: team.corpId },
|
||||
false
|
||||
);
|
||||
const customers = res?.success && Array.isArray(res.data) ? res.data : [];
|
||||
const preferred = pickPreferredCustomer(customers);
|
||||
|
||||
context.value = {
|
||||
corpId: team.corpId,
|
||||
teamId: team.teamId,
|
||||
customerId: preferred?._id || "",
|
||||
customerName: preferred
|
||||
? `${preferred.name || ""}${preferred.relationship ? ` ${preferred.relationship}` : ""}`.trim()
|
||||
: "",
|
||||
customers,
|
||||
};
|
||||
return context.value;
|
||||
} finally {
|
||||
resolving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function goBindArchive(ctx) {
|
||||
const corpId = ctx?.corpId || "";
|
||||
const teamId = ctx?.teamId || "";
|
||||
uni.navigateTo({
|
||||
url: `/pages/archive/archive-manage?corpId=${encodeURIComponent(corpId)}&teamId=${encodeURIComponent(teamId)}`,
|
||||
});
|
||||
}
|
||||
|
||||
function openArchiveSelector(target, ctx) {
|
||||
const params = [
|
||||
`mode=target`,
|
||||
`target=${encodeURIComponent(target)}`,
|
||||
`corpId=${encodeURIComponent(ctx?.corpId || "")}`,
|
||||
`teamId=${encodeURIComponent(ctx?.teamId || "")}`,
|
||||
`customerId=${encodeURIComponent(ctx?.customerId || "")}`,
|
||||
`name=${encodeURIComponent(ctx?.customerName || "")}`,
|
||||
].join("&");
|
||||
uni.navigateTo({
|
||||
url: `/pages/experience-coupon/select-rights-archive?${params}`,
|
||||
});
|
||||
}
|
||||
|
||||
async function openWithArchive(target) {
|
||||
const ctx = await resolveContext({ force: true });
|
||||
if (!ctx) return;
|
||||
|
||||
if (!ctx.customerId) {
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "请先绑定档案后再查看",
|
||||
confirmText: "去绑定",
|
||||
success: (res) => {
|
||||
if (res.confirm) goBindArchive(ctx);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
openArchiveSelector(target, ctx);
|
||||
}
|
||||
|
||||
async function openRightsWallet() {
|
||||
await openWithArchive("rights");
|
||||
}
|
||||
|
||||
async function openCouponWallet() {
|
||||
await openWithArchive("coupons");
|
||||
}
|
||||
|
||||
async function openTreatmentRecord() {
|
||||
await openWithArchive("treatment");
|
||||
}
|
||||
|
||||
async function openAppointmentRecord() {
|
||||
await openWithArchive("appointment");
|
||||
}
|
||||
|
||||
function toPage(url) {
|
||||
uni.navigateTo({ url });
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
});
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
resolveContext({ force: true });
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@ -334,67 +184,14 @@ page {
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-bar {
|
||||
margin: 0 15px 15px;
|
||||
padding: 12px 0 10px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.shortcut-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.shortcut-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-bottom: 6px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.shortcut-icon--rights {
|
||||
background: #eef5ff;
|
||||
}
|
||||
|
||||
.shortcut-icon--coupon {
|
||||
background: #fff6e8;
|
||||
}
|
||||
|
||||
.shortcut-icon--record {
|
||||
background: #ecfff5;
|
||||
}
|
||||
|
||||
.shortcut-icon--appointment {
|
||||
background: #f3edff;
|
||||
}
|
||||
|
||||
.shortcut-title {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
background-color: #fff;
|
||||
margin: 0 15px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
width: 24px;
|
||||
margin-right: 12px;
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -1,390 +0,0 @@
|
||||
<template>
|
||||
<full-page pageClass="record-page" :customScroll="true">
|
||||
<scroll-view
|
||||
class="record-scroll"
|
||||
scroll-y="true"
|
||||
refresher-enabled="true"
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="refreshRecords"
|
||||
>
|
||||
<view class="page-body">
|
||||
<view class="profile-card" @click="switchArchive">
|
||||
<view class="avatar-wrap">
|
||||
<view class="avatar-placeholder">{{ avatarText }}</view>
|
||||
</view>
|
||||
<view class="profile-main">
|
||||
<view class="profile-title">{{ customerName || "请选择档案" }}</view>
|
||||
<view class="profile-sub">所属机构:{{ corpName || "-" }}</view>
|
||||
</view>
|
||||
<view class="profile-arrow">></view>
|
||||
</view>
|
||||
|
||||
<picker mode="date" fields="month" :value="selectedMonth" @change="handleMonthChange">
|
||||
<view class="month-bar">
|
||||
<text>{{ monthText }}</text>
|
||||
<text class="month-arrow">▼</text>
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<view v-else-if="!records.length" class="empty">暂无预约记录</view>
|
||||
<view v-else class="record-list">
|
||||
<view v-for="item in records" :key="item._id" class="record-card">
|
||||
<view class="record-status">{{ statusText(item) }}</view>
|
||||
<view class="record-row">
|
||||
<text class="row-label">【预约类型】</text>
|
||||
<text class="row-value strong">{{ appointmentTypeText(item) }}</text>
|
||||
</view>
|
||||
<view class="record-row">
|
||||
<text class="row-label">【{{ practitionerLabel(item) }}】</text>
|
||||
<text class="row-value strong">{{ practitionerText(item) }}</text>
|
||||
</view>
|
||||
<view class="record-row">
|
||||
<text class="row-label">【预约日期】</text>
|
||||
<text class="row-value">{{ appointmentDateText(item) }}</text>
|
||||
</view>
|
||||
<view class="record-row">
|
||||
<text class="row-label">【预约时间】</text>
|
||||
<text class="row-value">{{ item.timeRange || "-" }}</text>
|
||||
</view>
|
||||
<view class="record-row">
|
||||
<text class="row-label">【预约项目】</text>
|
||||
<text class="row-value">{{ projectText(item) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</full-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import api from "@/utils/api";
|
||||
import { normalizeCorpId } from "@/utils/api-base-config";
|
||||
import { toast } from "@/utils/widget";
|
||||
import FullPage from "@/components/full-page.vue";
|
||||
|
||||
const corpId = ref("");
|
||||
const teamId = ref("");
|
||||
const customerId = ref("");
|
||||
const customerName = ref("");
|
||||
const corpName = ref("");
|
||||
const selectedMonth = ref(formatMonth(Date.now()));
|
||||
const records = ref([]);
|
||||
const staffNameMap = ref({});
|
||||
const loading = ref(false);
|
||||
const refreshing = ref(false);
|
||||
|
||||
const statusMap = {
|
||||
pending: { label: "未到院" },
|
||||
confirmed: { label: "已到院" },
|
||||
no_show: { label: "爽约" },
|
||||
};
|
||||
|
||||
const avatarText = computed(() => (customerName.value || "档案").slice(0, 1));
|
||||
const monthText = computed(() => selectedMonth.value.replace("-", "年") + "月");
|
||||
|
||||
function pad(value) {
|
||||
return String(value).padStart(2, "0");
|
||||
}
|
||||
|
||||
function toDate(ts) {
|
||||
if (!ts) return null;
|
||||
const d = new Date(Number(ts));
|
||||
return Number.isNaN(d.getTime()) ? null : d;
|
||||
}
|
||||
|
||||
function formatMonth(ts) {
|
||||
const d = toDate(ts) || new Date();
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}`;
|
||||
}
|
||||
|
||||
function formatDate(ts) {
|
||||
const d = toDate(ts);
|
||||
if (!d) return "";
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
||||
}
|
||||
|
||||
function monthRange(month) {
|
||||
const [year, monthIndex] = String(month || "").split("-").map(Number);
|
||||
const start = new Date(year, monthIndex - 1, 1);
|
||||
const end = new Date(year, monthIndex, 0);
|
||||
return {
|
||||
start: `${start.getFullYear()}-${pad(start.getMonth() + 1)}-01`,
|
||||
end: `${end.getFullYear()}-${pad(end.getMonth() + 1)}-${pad(end.getDate())}`,
|
||||
};
|
||||
}
|
||||
|
||||
function appointmentDateText(item) {
|
||||
return item?.appointmentDateStr || formatDate(item?.appointmentDate || item?.startTime || item?.registrationTime) || "-";
|
||||
}
|
||||
|
||||
function statusText(item) {
|
||||
return statusMap[item?.status || "pending"]?.label || "未到院";
|
||||
}
|
||||
|
||||
function appointmentTypeText(item) {
|
||||
if (item?.appointmentType === "schedule") return item?.scheduleTtile || "排班";
|
||||
if (item?.appointmentType === "medical") return "复诊预约";
|
||||
return "治疗预约";
|
||||
}
|
||||
|
||||
function practitionerLabel(item) {
|
||||
return item?.appointmentType === "medical" ? "医生" : "治疗师";
|
||||
}
|
||||
|
||||
function practitionerText(item) {
|
||||
const id = item?.therapistUserId || "";
|
||||
if (item?.therapistName) return item.therapistName;
|
||||
if (id === "none-therapist" || id === "none-doctor") return "未指定人员";
|
||||
return staffNameMap.value[id] || id || "未指定人员";
|
||||
}
|
||||
|
||||
function projectText(item) {
|
||||
const projects = item?.treatmentProject;
|
||||
if (Array.isArray(projects)) {
|
||||
const names = projects
|
||||
.map((project) => {
|
||||
if (!project) return "";
|
||||
if (typeof project === "string") return project;
|
||||
return project.projectName || project.name || project.title || "";
|
||||
})
|
||||
.filter(Boolean);
|
||||
return names.length ? names.join("、") : "无";
|
||||
}
|
||||
if (typeof projects === "string" && projects) return projects;
|
||||
return "无";
|
||||
}
|
||||
|
||||
function switchArchive() {
|
||||
const corp = encodeURIComponent(corpId.value || "");
|
||||
const team = encodeURIComponent(teamId.value || "");
|
||||
const customer = encodeURIComponent(customerId.value || "");
|
||||
uni.navigateTo({
|
||||
url: `/pages/experience-coupon/select-rights-archive?mode=target&target=appointment&corpId=${corp}&teamId=${team}&customerId=${customer}`,
|
||||
});
|
||||
}
|
||||
|
||||
async function loadStaffNameMap() {
|
||||
if (!corpId.value) {
|
||||
staffNameMap.value = {};
|
||||
return;
|
||||
}
|
||||
const res = await api("getAllCorpMemberIncludeDeleted", { corpId: corpId.value }, false);
|
||||
const list = res?.success && Array.isArray(res.data) ? res.data : [];
|
||||
staffNameMap.value = list.reduce((map, staff) => {
|
||||
if (staff?.userid) {
|
||||
map[staff.userid] = staff.anotherName || staff.name || staff.userid;
|
||||
}
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
async function loadRecords(options = {}) {
|
||||
const { silent = false } = options;
|
||||
if (!corpId.value || !customerId.value) {
|
||||
records.value = [];
|
||||
return;
|
||||
}
|
||||
if (!silent) loading.value = true;
|
||||
try {
|
||||
const range = monthRange(selectedMonth.value);
|
||||
const res = await api(
|
||||
"getAppointmentRegistration",
|
||||
{
|
||||
corpId: corpId.value,
|
||||
customerId: customerId.value,
|
||||
appointmentDateStart: range.start,
|
||||
appointmentDateEnd: range.end,
|
||||
page: 1,
|
||||
pageSize: 200,
|
||||
sortBy: "appointmentDate",
|
||||
sortOrder: "desc",
|
||||
},
|
||||
false
|
||||
);
|
||||
if (!res?.success) {
|
||||
toast(res?.message || "加载预约记录失败");
|
||||
records.value = [];
|
||||
return;
|
||||
}
|
||||
records.value = (Array.isArray(res.data) ? res.data : []).sort((a, b) => {
|
||||
const aTime = Number(a.appointmentDate || a.startTime || a.registrationTime || 0);
|
||||
const bTime = Number(b.appointmentDate || b.startTime || b.registrationTime || 0);
|
||||
return bTime - aTime;
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshRecords() {
|
||||
if (refreshing.value) return;
|
||||
refreshing.value = true;
|
||||
try {
|
||||
await loadStaffNameMap();
|
||||
await loadRecords({ silent: true });
|
||||
} finally {
|
||||
refreshing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMonthChange(event) {
|
||||
selectedMonth.value = event?.detail?.value || selectedMonth.value;
|
||||
await loadRecords();
|
||||
}
|
||||
|
||||
onLoad(async (options = {}) => {
|
||||
uni.setNavigationBarTitle({ title: "预约记录" });
|
||||
corpId.value = normalizeCorpId(options.corpId || "");
|
||||
teamId.value = options.teamId || "";
|
||||
customerId.value = options.customerId || options.id || "";
|
||||
customerName.value = options.name ? decodeURIComponent(options.name) : "";
|
||||
corpName.value = options.corpName ? decodeURIComponent(options.corpName) : "";
|
||||
if (options.month) selectedMonth.value = options.month;
|
||||
await loadStaffNameMap();
|
||||
await loadRecords();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
if (corpId.value && customerId.value) loadRecords();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.record-page :deep(.page-scroll) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.record-scroll {
|
||||
height: 100%;
|
||||
background: #e9edf3;
|
||||
}
|
||||
|
||||
.page-body {
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
background: #e9edf3;
|
||||
border-top: 8rpx solid #0b63d8;
|
||||
}
|
||||
.profile-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #d7dce5;
|
||||
}
|
||||
.avatar-wrap {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background: #d2d5da;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.avatar-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.profile-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.profile-title {
|
||||
font-size: 31rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
.profile-sub {
|
||||
margin-top: 4rpx;
|
||||
font-size: 24rpx;
|
||||
color: #9aa0a8;
|
||||
line-height: 34rpx;
|
||||
}
|
||||
.profile-arrow {
|
||||
flex-shrink: 0;
|
||||
color: #b4b7bf;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
.month-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
height: 70rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #e2e6ec;
|
||||
color: #333;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
border-bottom: 1rpx solid #d4d9e2;
|
||||
}
|
||||
.month-arrow {
|
||||
font-size: 18rpx;
|
||||
color: #333;
|
||||
}
|
||||
.record-list {
|
||||
padding: 0rpx 10rpx 12rpx 10rpx;
|
||||
}
|
||||
.record-card {
|
||||
position: relative;
|
||||
padding: 22rpx 24rpx 22rpx 34rpx;
|
||||
background: #fff;
|
||||
border-top: 14rpx solid #e9edf3;
|
||||
border-bottom: 1rpx solid #d7dce5;
|
||||
}
|
||||
.record-card::before {
|
||||
display: none;
|
||||
}
|
||||
.record-status {
|
||||
position: absolute;
|
||||
top: 24rpx;
|
||||
right: 24rpx;
|
||||
color: #333;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.record-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 0;
|
||||
padding: 7rpx 112rpx 7rpx 0;
|
||||
font-size: 27rpx;
|
||||
line-height: 38rpx;
|
||||
}
|
||||
.record-row:first-of-type {
|
||||
padding-top: 0;
|
||||
}
|
||||
.row-label {
|
||||
flex-shrink: 0;
|
||||
color: #6b7280;
|
||||
font-weight: 700;
|
||||
}
|
||||
.row-value {
|
||||
min-width: 0;
|
||||
text-align: left;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
.strong {
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
}
|
||||
.empty {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
@ -1,364 +0,0 @@
|
||||
<template>
|
||||
<full-page pageClass="record-page" :customScroll="true">
|
||||
<scroll-view class="record-scroll" scroll-y="true" refresher-enabled="true" :refresher-triggered="refreshing"
|
||||
@refresherrefresh="refreshRecords">
|
||||
<view class="page-body">
|
||||
<view class="profile-card" @click="switchArchive">
|
||||
<view class="avatar-wrap">
|
||||
<view class="avatar-placeholder">{{ avatarText }}</view>
|
||||
</view>
|
||||
<view class="profile-main">
|
||||
<view class="profile-title">{{ customerName || "请选择档案" }}</view>
|
||||
<view class="profile-sub">所属机构:{{ corpName || "-" }}</view>
|
||||
</view>
|
||||
<view class="profile-arrow">></view>
|
||||
</view>
|
||||
|
||||
<picker mode="date" fields="month" :value="selectedMonth" @change="handleMonthChange">
|
||||
<view class="month-bar">
|
||||
<text>{{ monthText }}</text>
|
||||
<text class="month-arrow">▼</text>
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<view v-else-if="!records.length" class="empty">暂无治疗记录</view>
|
||||
<view v-else class="record-list">
|
||||
<view v-for="item in records" :key="item._id" class="record-card">
|
||||
<view class="record-status">已治疗</view>
|
||||
<view class="record-row">
|
||||
<text class="row-label">【项目名称】</text>
|
||||
<text class="row-value strong">{{ item.projectName || "未命名项目" }}</text>
|
||||
</view>
|
||||
<view class="record-row">
|
||||
<text class="row-label">【治疗时间】</text>
|
||||
<text class="row-value">{{ formatDate(item.treatmentTime) }}</text>
|
||||
</view>
|
||||
<view class="record-row">
|
||||
<text class="row-label">【治疗数量】</text>
|
||||
<text class="row-value">{{ item.deductUsageCount || 0 }}</text>
|
||||
</view>
|
||||
<view class="record-row">
|
||||
<text class="row-label">【治疗科室】</text>
|
||||
<text class="row-value">{{ item.treatmentDeptName || "-" }}</text>
|
||||
</view>
|
||||
<view class="record-row">
|
||||
<text class="row-label">【治疗医生】</text>
|
||||
<text class="row-value strong">{{ item.treatmentDoctorName || item.doctorName ||
|
||||
staffText(item.treatmentDoctorUserId) }}</text>
|
||||
</view>
|
||||
<view v-if="assistantText(item)" class="record-row">
|
||||
<text class="row-label">【配台】</text>
|
||||
<text class="row-value">{{ assistantText(item) }}</text>
|
||||
</view>
|
||||
<view v-if="item.treatmentArea" class="record-row">
|
||||
<text class="row-label">【治疗部位】</text>
|
||||
<text class="row-value">{{ item.treatmentArea }}</text>
|
||||
</view>
|
||||
<view v-if="item.treatmentRemark" class="record-row">
|
||||
<text class="row-label">【治疗备注】</text>
|
||||
<text class="row-value">{{ item.treatmentRemark }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</full-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import api from "@/utils/api";
|
||||
import { normalizeCorpId } from "@/utils/api-base-config";
|
||||
import { toast } from "@/utils/widget";
|
||||
import FullPage from "@/components/full-page.vue";
|
||||
|
||||
const corpId = ref("");
|
||||
const teamId = ref("");
|
||||
const customerId = ref("");
|
||||
const customerName = ref("");
|
||||
const corpName = ref("");
|
||||
const selectedMonth = ref(formatMonth(Date.now()));
|
||||
const records = ref([]);
|
||||
const staffNameMap = ref({});
|
||||
const loading = ref(false);
|
||||
const refreshing = ref(false);
|
||||
|
||||
const avatarText = computed(() => (customerName.value || "档案").slice(0, 1));
|
||||
const monthText = computed(() => selectedMonth.value.replace("-", "年") + "月");
|
||||
|
||||
function pad(value) {
|
||||
return String(value).padStart(2, "0");
|
||||
}
|
||||
|
||||
function toDate(ts) {
|
||||
if (!ts) return null;
|
||||
const d = new Date(Number(ts));
|
||||
return Number.isNaN(d.getTime()) ? null : d;
|
||||
}
|
||||
|
||||
function formatMonth(ts) {
|
||||
const d = toDate(ts) || new Date();
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}`;
|
||||
}
|
||||
|
||||
function monthRange(month) {
|
||||
const [year, monthIndex] = String(month || "").split("-").map(Number);
|
||||
const start = new Date(year, monthIndex - 1, 1);
|
||||
const end = new Date(year, monthIndex, 0);
|
||||
return [
|
||||
`${start.getFullYear()}-${pad(start.getMonth() + 1)}-01`,
|
||||
`${end.getFullYear()}-${pad(end.getMonth() + 1)}-${pad(end.getDate())}`,
|
||||
];
|
||||
}
|
||||
|
||||
function formatDate(ts) {
|
||||
const d = toDate(ts);
|
||||
if (!d) return "-";
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
||||
}
|
||||
|
||||
|
||||
function staffText(userid) {
|
||||
if (!userid) return "-";
|
||||
return staffNameMap.value[userid] || userid;
|
||||
}
|
||||
|
||||
function assistantText(item) {
|
||||
const assistants = item?.assistantDoctors;
|
||||
if (!Array.isArray(assistants) || !assistants.length) return "";
|
||||
return assistants.map((staff) => staffText(staff?.userid || staff?.userId || staff)).filter(Boolean).join("、");
|
||||
}
|
||||
|
||||
async function loadStaffNameMap() {
|
||||
if (!corpId.value) {
|
||||
staffNameMap.value = {};
|
||||
return;
|
||||
}
|
||||
const res = await api("getAllCorpMemberIncludeDeleted", { corpId: corpId.value }, false);
|
||||
const list = res?.success && Array.isArray(res.data) ? res.data : [];
|
||||
staffNameMap.value = list.reduce((map, staff) => {
|
||||
if (staff?.userid) {
|
||||
map[staff.userid] = staff.anotherName || staff.name || staff.userid;
|
||||
}
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function switchArchive() {
|
||||
const corp = encodeURIComponent(corpId.value || "");
|
||||
const team = encodeURIComponent(teamId.value || "");
|
||||
const customer = encodeURIComponent(customerId.value || "");
|
||||
uni.navigateTo({
|
||||
url: `/pages/experience-coupon/select-rights-archive?mode=target&target=treatment&corpId=${corp}&teamId=${team}&customerId=${customer}`,
|
||||
});
|
||||
}
|
||||
|
||||
async function loadRecords(options = {}) {
|
||||
const { silent = false } = options;
|
||||
if (!corpId.value || !customerId.value) {
|
||||
records.value = [];
|
||||
return;
|
||||
}
|
||||
if (!silent) loading.value = true;
|
||||
try {
|
||||
const res = await api(
|
||||
"getDeductRecord",
|
||||
{
|
||||
corpId: corpId.value,
|
||||
customerId: customerId.value,
|
||||
deductStatus: ["deducted"],
|
||||
treatmentTimeDates: monthRange(selectedMonth.value),
|
||||
page: 1,
|
||||
pageSize: 200,
|
||||
},
|
||||
false
|
||||
);
|
||||
if (!res?.success) {
|
||||
toast(res?.message || "加载治疗记录失败");
|
||||
records.value = [];
|
||||
return;
|
||||
}
|
||||
const list = res.data?.list || res.list || [];
|
||||
records.value = Array.isArray(list)
|
||||
? list.sort((a, b) => Number(b.treatmentTime || b.createTime || 0) - Number(a.treatmentTime || a.createTime || 0))
|
||||
: [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshRecords() {
|
||||
if (refreshing.value) return;
|
||||
refreshing.value = true;
|
||||
try {
|
||||
await loadStaffNameMap();
|
||||
await loadRecords({ silent: true });
|
||||
} finally {
|
||||
refreshing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMonthChange(event) {
|
||||
selectedMonth.value = event?.detail?.value || selectedMonth.value;
|
||||
await loadRecords();
|
||||
}
|
||||
|
||||
onLoad(async (options = {}) => {
|
||||
uni.setNavigationBarTitle({ title: "治疗记录" });
|
||||
corpId.value = normalizeCorpId(options.corpId || "");
|
||||
teamId.value = options.teamId || "";
|
||||
customerId.value = options.customerId || options.id || "";
|
||||
customerName.value = options.name ? decodeURIComponent(options.name) : "";
|
||||
corpName.value = options.corpName ? decodeURIComponent(options.corpName) : "";
|
||||
if (options.month) selectedMonth.value = options.month;
|
||||
await loadStaffNameMap();
|
||||
await loadRecords();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
if (corpId.value && customerId.value) loadRecords();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.record-page :deep(.page-scroll) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.record-scroll {
|
||||
height: 100%;
|
||||
background: #e9edf3;
|
||||
}
|
||||
|
||||
.page-body {
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
background: #e9edf3;
|
||||
border-top: 8rpx solid #0b63d8;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #d7dce5;
|
||||
}
|
||||
|
||||
.avatar-wrap {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background: #d2d5da;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.profile-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.profile-title {
|
||||
font-size: 31rpx;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
|
||||
.profile-sub {
|
||||
margin-top: 4rpx;
|
||||
font-size: 24rpx;
|
||||
color: #9aa0a8;
|
||||
line-height: 34rpx;
|
||||
}
|
||||
|
||||
.profile-arrow {
|
||||
flex-shrink: 0;
|
||||
color: #b4b7bf;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.month-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
height: 70rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #e2e6ec;
|
||||
color: #333;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
border-bottom: 1rpx solid #d4d9e2;
|
||||
}
|
||||
|
||||
.month-arrow {
|
||||
font-size: 18rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
padding: 0rpx 10rpx 12rpx 10rpx;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
position: relative;
|
||||
padding: 22rpx 24rpx 22rpx 34rpx;
|
||||
background: #fff;
|
||||
border-top: 14rpx solid #e9edf3;
|
||||
border-bottom: 1rpx solid #d7dce5;
|
||||
}
|
||||
|
||||
.record-status {
|
||||
position: absolute;
|
||||
top: 24rpx;
|
||||
right: 24rpx;
|
||||
color: #333;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.record-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 7rpx 112rpx 7rpx 0;
|
||||
font-size: 27rpx;
|
||||
line-height: 38rpx;
|
||||
}
|
||||
|
||||
.row-label {
|
||||
flex-shrink: 0;
|
||||
color: #6b7280;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.row-value {
|
||||
min-width: 0;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
@ -1,312 +0,0 @@
|
||||
<template>
|
||||
<view v-if="card" class="business-card" @click="handleClick">
|
||||
<image class="card-bg" :src="cardBg"></image>
|
||||
<view class="card-header">
|
||||
<view class="avatar">
|
||||
<image :src="card.avatar || '/static/card/avatar.png'" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="info">
|
||||
<text class="name">{{ card.externalName || card.anotherName || '' }}</text>
|
||||
<text v-if="card.hospitalInfo" class="hospital">{{ card.hospitalInfo.name || '' }}</text>
|
||||
<view class="title-tags">
|
||||
<text class="title-tag department" v-if="card.deptName">{{ card.deptName }}</text>
|
||||
<text class="title-tag position" v-if="card.title">{{ card.title }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="contact-section">
|
||||
<view class="contact-info">
|
||||
<!-- <view class="contact-item" v-if="card.externalContact">
|
||||
<image class="contact-icon" src="/static/card/address.png" mode="aspectFit"></image>
|
||||
<text class="contact-text">{{ accountInfo.address }}</text>
|
||||
</view> -->
|
||||
<view v-if="card.externalContact" class="contact-item">
|
||||
<image class="contact-icon" src="/static/card/phone.png" mode="aspectFit"></image>
|
||||
<text class="contact-text">{{ card.externalContact || '' }}</text>
|
||||
</view>
|
||||
<view v-if="card.externalEmail" class="contact-item">
|
||||
<image class="contact-icon" src="/static/card/email.png" mode="aspectFit"></image>
|
||||
<text class="contact-text">{{ card.externalEmail || '' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
// import { useCardStore } from "@/stores/card";
|
||||
// import { useAccountStore } from "@/stores/account";
|
||||
// import { getDeptStaffDepartments } from "@/api/dept";
|
||||
|
||||
const props = defineProps({
|
||||
card: {
|
||||
type: [Object, null],
|
||||
default: null
|
||||
},
|
||||
// 点击事件处理函数
|
||||
onClick: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
// 背景图片路径
|
||||
cardBg: {
|
||||
type: String,
|
||||
default: "/static/card/card-bg2.png",
|
||||
},
|
||||
// 显示模式:'doctor' 医生模式(使用 cardStore 的可见性设置),'patient' 患者模式(直接显示)
|
||||
mode: {
|
||||
type: String,
|
||||
default: "doctor",
|
||||
validator: (value) => ["doctor", "patient"].includes(value),
|
||||
},
|
||||
// 自定义账户信息(如果提供,则使用自定义数据而不是 store)
|
||||
customAccountInfo: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
// 自定义机构信息(如果提供,则使用自定义数据而不是 store)
|
||||
customInstitutionInfo: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
// 自定义医生名片状态(如果提供,则使用自定义数据而不是 store)
|
||||
customDoctorCardStatus: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["click"]);
|
||||
|
||||
const cardStore = ref({});
|
||||
const accountStore = ref({});
|
||||
|
||||
// 获取账户信息(优先使用自定义数据)
|
||||
const accountInfo = computed(() => {
|
||||
if (props.customAccountInfo) {
|
||||
return props.customAccountInfo;
|
||||
}
|
||||
return accountStore.getAccountInfo || {};
|
||||
});
|
||||
|
||||
// 部门显示名称(优先使用接口 getDeptStaffDepartments 返回的结果)
|
||||
const deptDisplayName = ref("");
|
||||
|
||||
const displayDeptName = computed(() => {
|
||||
// 优先展示接口返回的部门字符串,否则回退账户里的 deptName
|
||||
return deptDisplayName.value || accountInfo.value.deptName || "";
|
||||
});
|
||||
|
||||
// 获取机构信息(优先使用自定义数据)
|
||||
const institutionInfo = computed(() => {
|
||||
if (props.customInstitutionInfo) {
|
||||
return props.customInstitutionInfo;
|
||||
}
|
||||
return accountStore.getInstitutionInfo || {};
|
||||
});
|
||||
|
||||
// 获取医生名片状态(优先使用自定义数据)
|
||||
const doctorCardStatus = computed(() => {
|
||||
if (props.customDoctorCardStatus) {
|
||||
return props.customDoctorCardStatus;
|
||||
}
|
||||
return accountStore.getDoctorCardStatus || { hasDoctorCard: false };
|
||||
});
|
||||
|
||||
// 根据模式决定是否显示部门名称
|
||||
const shouldShowDeptName = computed(() => {
|
||||
if (props.mode === "patient") {
|
||||
return true; // 患者模式直接显示
|
||||
}
|
||||
return cardStore.deptNameVisible; // 医生模式使用 cardStore 设置
|
||||
});
|
||||
|
||||
// 根据模式决定是否显示职位
|
||||
const shouldShowPosition = computed(() => {
|
||||
if (props.mode === "patient") {
|
||||
return true; // 患者模式直接显示
|
||||
}
|
||||
return cardStore.positionVisible; // 医生模式使用 cardStore 设置
|
||||
});
|
||||
|
||||
// 根据模式决定是否显示电话
|
||||
const shouldShowPhone = computed(() => {
|
||||
if (props.mode === "patient") {
|
||||
return true; // 患者模式直接显示
|
||||
}
|
||||
return cardStore.phoneVisible; // 医生模式使用 cardStore 设置
|
||||
});
|
||||
|
||||
// 根据模式决定是否显示邮箱
|
||||
const shouldShowEmail = computed(() => {
|
||||
if (props.mode === "patient") {
|
||||
return true; // 患者模式直接显示
|
||||
}
|
||||
return cardStore.emailVisible; // 医生模式使用 cardStore 设置
|
||||
});
|
||||
|
||||
// 根据模式决定是否显示地址
|
||||
const shouldShowAddress = computed(() => {
|
||||
if (props.mode === "patient") {
|
||||
return true; // 患者模式直接显示
|
||||
}
|
||||
return cardStore.addressVisible; // 医生模式使用 cardStore 设置
|
||||
});
|
||||
|
||||
// 处理点击事件
|
||||
const handleClick = () => {
|
||||
if (props.onClick) {
|
||||
props.onClick();
|
||||
}
|
||||
emit("click");
|
||||
};
|
||||
|
||||
// 组件挂载后,通过工号 staffId 调用 getDeptStaffDepartments 获取部门显示信息
|
||||
onShow(async () => {
|
||||
try {
|
||||
const staffId = accountInfo.value.staffId;
|
||||
if (!staffId) return;
|
||||
|
||||
const res = await getDeptStaffDepartments({ staffId });
|
||||
if (res && res.success && Array.isArray(res.data) && res.data.length > 0) {
|
||||
// 后端返回的是部门名称数组,例如 ["人力资源部/人事培训科/人事培训科"]
|
||||
deptDisplayName.value = res.data.join("、");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取部门显示信息失败:", error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 名片展示区域 */
|
||||
.business-card {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
border-bottom-left-radius: 16rpx;
|
||||
border-bottom-right-radius: 16rpx;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.card-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
position: relative;
|
||||
padding: 36rpx 40rpx 10rpx 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 36rpx;
|
||||
}
|
||||
|
||||
.contact-section {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 0 40rpx 56rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
display: flex;
|
||||
border-radius: 50%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 60rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
flex-shrink: 0;
|
||||
margin-right: 30rpx;
|
||||
align-self: center;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: block;
|
||||
font-size: 36rpx;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6rpx;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.hospital {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 8rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.title-tags {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.title-tag {
|
||||
font-size: 24rpx;
|
||||
font-weight: 400;
|
||||
padding: 0 16rpx;
|
||||
flex-shrink: 0;
|
||||
border-radius: 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title-tag.department {
|
||||
color: #ff8b00;
|
||||
border: 1rpx solid #ff8b00;
|
||||
}
|
||||
|
||||
.title-tag.position {
|
||||
color: #de4f27;
|
||||
border: 0.5px solid #de4f27;
|
||||
}
|
||||
|
||||
.contact-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
align-items: flex-start;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.contact-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.contact-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.contact-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@ -1,97 +0,0 @@
|
||||
<template>
|
||||
<uni-popup ref="popup" type="bottom" :mask-click="false">
|
||||
<view class="bg-white rounded overflow-hidden">
|
||||
<view class="flex items-center justify-between px-15 py-12 border-b">
|
||||
<view class="text-lg font-semibold text-dark">全周期管理</view>
|
||||
<uni-icons type="closeempty" :size="24" color="#999" @click="close"></uni-icons>
|
||||
</view>
|
||||
<scroll-view scroll-y="true" class="popup-content-scroll">
|
||||
<view class="px-15 py-12">
|
||||
<view v-for="team in teams" :key="team.teamId" class="flex items-center p-10 mb-10 rounded-sm bg-gray"
|
||||
@click="enterTeam(team)">
|
||||
<view class="flex-grow w-0 mr-5 text-base leading-normal text-dark">
|
||||
{{ team.name }}
|
||||
</view>
|
||||
<view class="flex-shrink-0 text-base text-primary">进入</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
</view>
|
||||
</uni-popup>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { storeToRefs } from "pinia";
|
||||
import useAccount from "@/store/account";
|
||||
import api from "@/utils/api";
|
||||
import { set } from "@/utils/cache";
|
||||
import { toast } from "@/utils/widget";
|
||||
|
||||
const emits = defineEmits(['close', 'confirm'])
|
||||
const props = defineProps({
|
||||
teams: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
const env = __VITE_ENV__;
|
||||
const appid = env.MP_WX_APP_ID;
|
||||
const popup = ref()
|
||||
const { account } = storeToRefs(useAccount());
|
||||
|
||||
|
||||
function close() {
|
||||
emits('close')
|
||||
}
|
||||
|
||||
async function enterTeam(team) {
|
||||
if (account.value && account.value.mobile) {
|
||||
const res = await api('bindWxappWithTeam', { appid, corpId: team.corpId, teamId: team.teamId, openid: account.value.openid });
|
||||
if (!res || !res.success) {
|
||||
return toast("关联团队失败");
|
||||
}
|
||||
set('home-invite-team-info', {
|
||||
teamId: team.teamId,
|
||||
corpId: team.corpId,
|
||||
});
|
||||
uni.switchTab({
|
||||
url: "/pages/home/home",
|
||||
});
|
||||
}else {
|
||||
uni.redirectTo({
|
||||
url: `/pages/login/redirect-page?corpId=${team.corpId}&teamId=${team.teamId}`,
|
||||
})
|
||||
}
|
||||
// close()
|
||||
}
|
||||
|
||||
watch(() => props.visible, n => {
|
||||
if (n) {
|
||||
popup.value && popup.value.open();
|
||||
} else {
|
||||
popup.value && popup.value.close()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.min-w-60 {
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
.popup-content-scroll {
|
||||
max-height: 50vh;
|
||||
}
|
||||
</style>
|
||||
@ -96,6 +96,7 @@ const teammate = computed(() => {
|
||||
|
||||
const friendlyMember = computed(() => {
|
||||
const friendlyMembers = team.value && Array.isArray(team.value.friendlyMembers) ? team.value.friendlyMembers : [];
|
||||
debugger
|
||||
return friendlyMembers.reduce((data, item) => {
|
||||
data[item] = true;
|
||||
return data
|
||||
@ -106,13 +107,8 @@ function toFriend(userid) {
|
||||
uni.navigateTo({ url: `/pages/team/friend?corpId=${corpId.value}&userid=${userid}` })
|
||||
}
|
||||
|
||||
async function toHomePage(userid) {
|
||||
const res = await api('getCorpBusinessCardStatus', { corpId: corpId.value });
|
||||
if (res && res.data) {
|
||||
uni.navigateTo({ url: `/pages/team/business-card?corpId=${corpId.value}&userid=${userid}` })
|
||||
} else {
|
||||
uni.navigateTo({ url: `/pages/team/homepage?corpId=${corpId.value}&userid=${userid}&showQrcode=${friendlyMember.value[userid] ? 'YES' : ''}` })
|
||||
}
|
||||
function toHomePage(userid) {
|
||||
uni.navigateTo({ url: `/pages/team/homepage?corpId=${corpId.value}&userid=${userid}&showQrcode=${friendlyMember.value[userid] ? 'YES' : ''}` })
|
||||
}
|
||||
|
||||
async function getTeam() {
|
||||
|
||||
@ -12,34 +12,6 @@ export default [
|
||||
path: 'pages/login/redirect-page',
|
||||
meta: { title: '柚健康' },
|
||||
},
|
||||
{
|
||||
path: 'pages/experience-coupon/claim',
|
||||
meta: { title: '领取体验券', login: true }
|
||||
},
|
||||
{
|
||||
path: 'pages/experience-coupon/my-rights',
|
||||
meta: { title: '我的权益', login: true }
|
||||
},
|
||||
{
|
||||
path: 'pages/experience-coupon/select-rights-archive',
|
||||
meta: { title: '选择档案', login: true }
|
||||
},
|
||||
{
|
||||
path: 'pages/experience-coupon/my-coupons',
|
||||
meta: { title: '我的体验券', login: true }
|
||||
},
|
||||
{
|
||||
path: 'pages/experience-coupon/wallet',
|
||||
meta: { title: '我的卡包', login: true }
|
||||
},
|
||||
{
|
||||
path: 'pages/record/treatment-record',
|
||||
meta: { title: '治疗记录', login: true }
|
||||
},
|
||||
{
|
||||
path: 'pages/record/appointment-record',
|
||||
meta: { title: '预约记录', login: true }
|
||||
},
|
||||
{
|
||||
path: 'pages/archive/archive-manage',
|
||||
meta: { title: '档案管理', login: true }
|
||||
@ -84,10 +56,6 @@ export default [
|
||||
path: 'pages/team/friend',
|
||||
meta: { title: '添加好友', login: true }
|
||||
},
|
||||
{
|
||||
path: 'pages/team/business-card',
|
||||
meta: { title: '电子名片', login: true }
|
||||
},
|
||||
{
|
||||
path: 'pages/web-view/web-view',
|
||||
meta: { title: '' }
|
||||
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 724 B |
|
Before Width: | Height: | Size: 339 B |
@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1781663573063" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11885" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M838.954667 234.666667H170.666667c-3.626667 0-7.168 0.448-10.56 1.322666l323.690666 323.669334a21.333333 21.333333 0 0 0 30.165334 0L838.954667 234.666667z m46.144 14.186666l-260.693334 260.693334 262.933334 262.912c5.44-7.168 8.661333-16.106667 8.661333-25.792V277.333333c0-10.944-4.117333-20.906667-10.88-28.48zM843.861333 789.333333l-249.6-249.621333-50.133333 50.133333a64 64 0 0 1-90.517333 0l-50.112-50.133333L156.373333 786.88c4.48 1.578667 9.28 2.453333 14.314667 2.453333h673.194667zM128.661333 754.218667L373.333333 509.525333 129.578667 265.813333A42.709333 42.709333 0 0 0 128 277.333333v469.333334c0 2.56 0.213333 5.098667 0.661333 7.552zM170.666667 192h682.666666a85.333333 85.333333 0 0 1 85.333334 85.333333v469.333334a85.333333 85.333333 0 0 1-85.333334 85.333333H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333333V277.333333a85.333333 85.333333 0 0 1 85.333334-85.333333z" fill="#ffffff" p-id="11886"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 368 B |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 494 B |
|
Before Width: | Height: | Size: 5.8 KiB |
@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1781663513850" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10787" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M731.9 631.3c15.5-30.6 46.8-71.9-20-96.3-66.8-24.4-74.7 1-90.8 32.6-11.2 22.1-65.1 1.6-113.7-22.4-48.6-24-97.5-54.2-86.3-76.3 16.1-31.6 32-53-27.8-91.1-59.9-38.1-74.8 11.3-90.3 41.9-18 35.3 40.4 125.4 171.6 190.2 131.1 64.6 239.3 56.6 257.3 21.4z m0 0" p-id="10788" fill="#ffffff"></path><path d="M511 959c-60.4 0-119-11.8-174.2-35.2-53.3-22.5-101.2-54.8-142.3-95.9-41.1-41.1-73.4-89-95.9-142.3-23.3-55.2-35.2-113.8-35.2-174.2 0-60.4 11.8-119 35.2-174.2 22.5-53.3 54.8-101.2 95.9-142.3 41.1-41.1 89-73.4 142.3-95.9C391.9 75.7 450.6 63.8 511 63.8c60.4 0 119 11.8 174.2 35.2 53.3 22.5 101.2 54.8 142.3 95.9 41.1 41.1 73.4 89 95.9 142.3 23.3 55.2 35.2 113.8 35.2 174.2 0 60.4-11.8 119-35.2 174.2-22.5 53.3-54.8 101.2-95.9 142.3-41.1 41.1-89 73.4-142.3 95.9C630 947.1 571.4 959 511 959z m0-851.3c-222.6 0-403.7 181.1-403.7 403.7S288.4 915.1 511 915.1 914.7 734 914.7 511.4 733.6 107.7 511 107.7z" fill="#ffffff" p-id="10789"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 627 B After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 23 KiB |
@ -24,9 +24,9 @@ export default defineStore("accountStore", () => {
|
||||
});
|
||||
const teamsPromise = ref(null);
|
||||
|
||||
async function login(phoneCode = '', options = {}) {
|
||||
async function login(phoneCode = '') {
|
||||
if (loading.value) return;
|
||||
if (account.value && account.value.mobile && !options.forceVerify) {
|
||||
if (account.value && account.value.mobile) {
|
||||
return account.value
|
||||
}
|
||||
loading.value = true;
|
||||
@ -54,17 +54,11 @@ export default defineStore("accountStore", () => {
|
||||
|
||||
// initIMAfterLogin(openid.value)
|
||||
return normalizedAccount
|
||||
} else {
|
||||
account.value = null;
|
||||
openid.value = '';
|
||||
uni.removeStorageSync('account');
|
||||
uni.removeStorageSync('openid');
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!options.silent) toast('登录失败,请重新登录');
|
||||
toast('登录失败,请重新登录');
|
||||
} catch (e) {
|
||||
if (!options.silent) toast('登录失败,请重新登录');
|
||||
toast('登录失败,请重新登录');
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
@ -160,9 +154,9 @@ export default defineStore("accountStore", () => {
|
||||
const unionid = account.value?.unionid;
|
||||
const openid = account.value?.openid;
|
||||
if (!(normalizedCorpId && unionid && openid)) {
|
||||
externalUserId.value = '';
|
||||
return
|
||||
};
|
||||
externalUserId.value = '';
|
||||
return
|
||||
};
|
||||
const res = await api('getUnionidToExternalUserid', { unionid, openid, corpId: normalizedCorpId }, false);
|
||||
const id = res && res.success && typeof res.data === 'string' && res.data.trim() ? res.data.trim() : '';
|
||||
externalUserId.value = id;
|
||||
|
||||
@ -4,8 +4,6 @@ export const API_CONTEXT_CACHE_KEY = "ykt_team_api_context";
|
||||
|
||||
export const CORP_API_BASE_URL_MAP = {
|
||||
wwa54dfba0b5441ef1: "https://crm.gykqyy.com/ykt/",
|
||||
wpLgjyawAAxKtBH5GVT5DVhLefg246Ag: "https://hlwyy.6thhosp.com:4443/",
|
||||
|
||||
};
|
||||
|
||||
export const CORP_ID_ALIAS_MAP = {
|
||||
|
||||
16
utils/api.js
@ -13,14 +13,9 @@ const urlsConfig = {
|
||||
bindWxappWithTeam: 'bindWxappWithTeam',
|
||||
getWxappRelateTeams: 'getWxappRelateTeams',
|
||||
getTeamMemberAvatarsAndName: "getTeamMemberAvatarsAndName",
|
||||
getAllCorpMemberIncludeDeleted: "getAllCorpMemberIncludeDeleted",
|
||||
getMiniAppHomeStats: "getMiniAppHomeStats",
|
||||
getResponsiblePerson: 'getTeamResponsiblePerson',
|
||||
relateWxappTeamByExternalUserId: 'relateWxappTeamByExternalUserId',
|
||||
getBusinessCard: 'getBusinessCard',
|
||||
recordBusinessCardBehavior: 'recordBusinessCardBehavior',
|
||||
getJoinedTeams: 'getJoinedTeams',
|
||||
getCorpBusinessCardStatus: 'getCorpBusinessCardStatus',
|
||||
relateWxappTeamByExternalUserId: 'relateWxappTeamByExternalUserId'
|
||||
},
|
||||
|
||||
knowledgeBase: {
|
||||
@ -55,18 +50,9 @@ const urlsConfig = {
|
||||
addMedicalRecord: 'addMedicalRecord',
|
||||
authCustomerToTeam: 'authCustomerToTeam', // 授权客户到团队
|
||||
bindMiniAppArchive: "bindMiniAppArchive",
|
||||
claimExperienceCoupon: "claimExperienceCoupon",
|
||||
voidExperienceCouponIssue: "voidExperienceCouponIssue",
|
||||
getCustomerByCustomerId: 'getCustomerByCustomerId',
|
||||
getCustomerExperienceCoupons: "getCustomerExperienceCoupons",
|
||||
getExperienceCouponIssueDetail: "getExperienceCouponIssueDetail",
|
||||
getExperienceCouponIssueList: "getExperienceCouponIssueList",
|
||||
getMiniAppCustomers: 'getMiniAppCustomers',
|
||||
getTeamCustomers: 'getTeamCustomers',
|
||||
getTreatmentRecord: "getTreatmentRecord",
|
||||
getDeductRecord: "getDeductRecord",
|
||||
getAppointmentRecord: "getAppointmentRecord",
|
||||
getAppointmentRegistration: "getAppointmentRegistration",
|
||||
getUnbindMiniAppCustomers: 'getUnbindMiniAppCustomers',
|
||||
getCustomerMedicalRecord: 'getCustomerMedicalRecord',
|
||||
getMedicalRecordById: 'getMedicalRecordById',
|
||||
|
||||