Compare commits

..

No commits in common. "main" and "dev-his-archive" have entirely different histories.

63 changed files with 350 additions and 7151 deletions

View File

@ -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>

View File

@ -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) {
// widthwidth
results.imageWidth = windowWidth;
results.imageheight = windowWidth * (originalHeight / originalWidth);
} else {
//
results.imageWidth = originalWidth;
results.imageheight = originalHeight;
}
return results;
},
},
};
</script>

View File

@ -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 currentTargetdataset
if (!href) return;
let parent = this.$parent;
while(!parent.preview || typeof parent.preview !== 'function') {// TODO
parent = parent.$parent;
}
parent.navigate(href, e);
},
},
};
</script>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

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

View File

@ -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!`)
}
}
}
```

View File

@ -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;
}

View File

@ -1,118 +0,0 @@
<!--**
* forked fromhttps://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>

View File

@ -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() {

View File

@ -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": {}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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);
}

View File

@ -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>

View File

@ -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">&gt;</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>

View File

@ -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">&gt;</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>

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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>

View File

@ -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() {

View File

@ -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: '' }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 627 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -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;

View File

@ -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 = {

View File

@ -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',