This commit is contained in:
zoujiandong 2025-03-07 17:11:01 +08:00
commit 5850bdee94
66 changed files with 10952 additions and 0 deletions

2
.env.development Normal file
View File

@ -0,0 +1,2 @@
# VITE_BASE_URL="http://dev.edu.igandan.com/gdxz-h5/"
VITE_BASE_URL="https://dev-caseplatform.igandan.com/api/"

2
.env.production Normal file
View File

@ -0,0 +1,2 @@
# VITE_BASE_URL="http://dev.edu.igandan.com/gdxz-h5/"
VITE_BASE_URL="https://prod-caseplatform.igandan.com/api/"

2
.env.test Normal file
View File

@ -0,0 +1,2 @@
# VITE_BASE_URL="https://twx.igandan.org/"
VITE_BASE_URL="https://dev-caseplatform.igandan.com/api/"

51
.gitignore vendored Normal file
View File

@ -0,0 +1,51 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
# typescript
*.tsbuildinfo
# eslint
.eslintcache
# stylelint
.stylelintcache
dist
dist-ssr
*.local
/dist*
stats.html

0
README.md Normal file
View File

65
auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,65 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const effectScope: typeof import('vue')['effectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue'
}

45
components.d.ts vendored Normal file
View File

@ -0,0 +1,45 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
declare module 'vue' {
export interface GlobalComponents {
Back: typeof import('./src/components/back.vue')['default']
CaseDetail: typeof import('./src/views/caseDetail.vue')['default']
CaseIntro: typeof import('./src/views/caseIntro.vue')['default']
CaseList: typeof import('./src/views/caseList.vue')['default']
CDialog: typeof import('./src/components/c-dialog.vue')['default']
CList: typeof import('./src/components/c-list.vue')['default']
Comment: typeof import('./src/views/comment.vue')['default']
CommentList: typeof import('./src/components/comment-list.vue')['default']
Commentunit: typeof import('./src/components/commentunit.vue')['default']
Expandunit: typeof import('./src/components/expandunit.vue')['default']
Home: typeof import('./src/views/home.vue')['default']
Loading: typeof import('./src/components/loading/loading.vue')['default']
Pageunit: typeof import('./src/views/pageunit.vue')['default']
Question: typeof import('./src/components/question.vue')['default']
Questionend: typeof import('./src/components/questionend.vue')['default']
Reply: typeof import('./src/components/reply.vue')['default']
Result: typeof import('./src/views/result.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SingleQuestion: typeof import('./src/components/singleQuestion.vue')['default']
TopicList: typeof import('./src/components/topicList.vue')['default']
Totop: typeof import('./src/components/totop.vue')['default']
VanCheckbox: typeof import('vant/es')['Checkbox']
VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
VanField: typeof import('vant/es')['Field']
VanImage: typeof import('vant/es')['Image']
VanImagePreview: typeof import('vant/es')['ImagePreview']
VanList: typeof import('vant/es')['List']
VanOverlay: typeof import('vant/es')['Overlay']
VanPullRefresh: typeof import('vant/es')['PullRefresh']
VanRadio: typeof import('vant/es')['Radio']
VanRadioGroup: typeof import('vant/es')['RadioGroup']
VanTab: typeof import('vant/es')['Tab']
VanTabs: typeof import('vant/es')['Tabs']
}
}

119
index.html Normal file
View File

@ -0,0 +1,119 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title><%- title %></title>
<style>
.first-loading-wrp {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 90vh;
min-height: 90vh;
}
.first-loading-wrp>h1 {
font-size: 20px;
}
.first-loading-wrp .loading-wrp {
display: flex;
align-items: center;
justify-content: center;
padding: 98px;
}
.dot {
position: relative;
box-sizing: border-box;
display: inline-block;
width: 64px;
height: 64px;
font-size: 64px;
transform: rotate(45deg);
animation: antRotate 1.2s infinite linear;
}
.dot i {
position: absolute;
display: block;
width: 28px;
height: 28px;
background-color:#43C9C3;
border-radius: 100%;
opacity: 0.5;
transform: scale(0.75);
transform-origin: 50% 50%;
animation: antSpinMove 1s infinite linear alternate;
}
.dot i:nth-child(1) {
top: 0;
left: 0;
}
.dot i:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.dot i:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.dot i:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
@keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@-webkit-keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes antSpinMove {
to {
opacity: 1;
}
}
@-webkit-keyframes antSpinMove {
to {
opacity: 1;
}
};
h2{
font-size: 16px!important;
}
</style>
</head>
<body>
<div id="app">
<div class="first-loading-wrp">
<div class="loading-wrp">
<span class="dot dot-spin">
<i></i>
<i></i>
<i></i>
<i></i>
</span>
</div>
<h2 style="font-size: 16px">努力加载中...</h2>
</div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

14
jsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"allowSyntheticDefaultImports": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": [
"node_modules"
]
}

4380
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

40
package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "wxapp-home",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"test": "vite build --mode=test",
"build": "vite build --mode=production",
"preview": "vite preview"
},
"dependencies": {
"@nutui/nutui": "^4.1.4",
"axios": "^1.5.0",
"dayjs": "^1.11.13",
"jquery": "^3.5.1",
"js-base64": "^3.7.7",
"jweixin-1.6.0": "^1.0.0",
"reset-css": "^5.0.2",
"vant": "^4.9.17",
"vconsole": "^3.15.1",
"vue": "^3.3.4",
"vue-lazyload": "^3.0.0",
"vue-router": "^4.2.4",
"weixin-js-sdk": "^1.6.0"
},
"devDependencies": {
"@types/node": "^20.4.5",
"@vant/auto-import-resolver": "^1.2.1",
"@vitejs/plugin-vue": "^4.2.3",
"rollup-plugin-external-globals": "^0.8.0",
"rollup-plugin-visualizer": "^5.9.2",
"terser": "^5.19.1",
"unplugin-auto-import": "^0.16.7",
"unplugin-vue-components": "^0.25.2",
"vite": "^4.4.5",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.0"
}
}

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

21
src/App.vue Normal file
View File

@ -0,0 +1,21 @@
<template>
<body>
<router-view></router-view>
</body>
</template>
<style>
.nodata{
width:100%;
min-height: 300px;
display: flex;
justify-content: center;
align-items: center;
color:#999;
font-size: 15px;
}
</style>

61
src/api/user.js Normal file
View File

@ -0,0 +1,61 @@
import http from '../utils/http.js'
const login = (data) => {
return http.post("/login", data);
};
const getProjectList = (data) => {
return http.get("/project/page", data);
};
const getCaseList = (data) => {
return http.get("/case/page", data);
};
const getCaseDetail = (id) => {
return http.get("/case/"+id);
};
const getCaseResult = (id) => {
return http.get("/case/question/select/"+id);
};
const getCommentList = (data) => {
return http.get("/comment/page", data);
};
const addComment= (data) => {
return http.post("/comment", data);
};
const like= (id) => {
return http.post("/comment/like/"+id);
};
const cancleLike= (id) => {
return http.del("/comment/like/"+id);
};
const getScore=(data)=>{
return http.get("/user/score",data)
}
const getConfig=(data)=>{
return http.get("/project/platform",data);
}
const completeCase=(id,data)=>{
return http.post("/case/finish/"+id,data,'application/json')
}
const behaviorRecord=(data)=>{
return http.post("/record/user/behavior",data)
}
export default {
login,
getProjectList,
getCaseList,
getCaseDetail,
getCaseResult,
getCommentList,
addComment,
like,
cancleLike,
getScore,
getConfig,
completeCase,
behaviorRecord
}

BIN
src/assets/avastar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
src/assets/banner.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
src/assets/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
src/assets/cai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
src/assets/fuli.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
src/assets/home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
src/assets/newicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
src/assets/noselect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
src/assets/nozan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 B

BIN
src/assets/result.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 KiB

BIN
src/assets/select.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
src/assets/titlebg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
src/assets/top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
src/assets/wave.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
src/assets/zan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

118
src/components/back.vue Normal file
View File

@ -0,0 +1,118 @@
<template>
<div class="home" @click="goHome">
{{ source?'点击返回新医视':'点击返回首页' }}
<!-- <div
v-if="source==3"
class="appbtn"
style="
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 9999;
opacity: 0;
"
>
<wx-open-launch-weapp
:key="freshkey"
style="
width:94px;
height: 30px;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
"
id="launch-btn-miniApp"
appid="wx6e3ba63efef32cc4"
:env-version="envVersion"
path="pages/media_home/index"
>
<div v-is="'script'" type="text/wxtag-template">
<button class="btn" style="width:100%;height:30px;position:absolute;left:0;right:0;top:0;bottom:0;">打开小程序</button>
</div>
</wx-open-launch-weapp>
</div> -->
<!-- <img src="../assets/home.png" @click="goHome" /> -->
<!-- <img src="../assets/home.png" v-else @click="goHome"/> -->
</div>
</template>
<script setup>
import { onMounted, nextTick } from "vue";
import { useRouter, useRoute } from 'vue-router';
import openTag from "../utils/openTag";
import wx from 'jweixin-1.6.0'
const router = useRouter();
const route = useRoute();
let origin=route.query.source?route.query.source:'';
const source=ref(origin);
const freshkey = ref(0);
const goHome = () => {
if(source.value==3){
// try {
// wx.miniProgram.navigateBack();
// } catch (error) {
// wx.miniProgram.switchTab({
// url: '/pages/media_home/index'
// })
// }
wx.miniProgram.switchTab({
url: '/pages/media_home/index',
success(res) {
consoe.log('跳转成功');
},
fail(res) {
consoe.log('跳转失败');
}
});
}else{
router.push({
path: "/home",
});
}
};
onMounted(() => {
openTag();
// nextTick(() => {
// var btn = document.getElementById("launch-btn-miniApp");
// if (btn) {
// btn.addEventListener("launch", function (e) {
// showToast("success");
// freshkey.value++;
// });
// btn.addEventListener("error", function (e) {
// showToast("fail", e.detail);
// });
// }
// });
});
</script>
<style lang='scss' scoped>
.home {
position: fixed;
right: 0;
z-index: 10;
width:27px;
padding:8px 0;
line-height: 17px;
top:50%;
font-size: 14px;
color:#fff;
display: flex;
text-align: center;
align-items: center;
justify-content: center;
border-radius: 5px;
background:#0caf98;
flex-direction: column;
transform: translateY(-50%);
}
</style>

111
src/components/c-dialog.vue Normal file
View File

@ -0,0 +1,111 @@
<template>
<van-overlay :show="show" z-index="9999">
<div class="wrapper">
<div class="box">
<div class="title">温馨提示</div>
<div class="content">{{ message }}</div>
<div class="foot">
<div class="cancle" @click="cancelDialog" v-show="showCancel" ></div>
<div class="ok" @click="closeDialog">{{confirmText}}</div>
</div>
</div>
</div>
</van-overlay>
</template>
<script setup>
import { ref} from 'vue'
const show = ref(false);
const props=defineProps({
message: {
type: String,
default:''
},
showCancel: {
type: Boolean,
default: true,
},
confirmText: {
type: String,
default:'是'
}
});
const emit = defineEmits(['handledConfirm']);
const closeDialog = () => {
show.value = false;
emit('handledConfirm');
};
const cancelDialog = () => {
show.value = false;
emit('handledCancel');
};
const openDialog = () => {
show.value = true;
};
defineExpose({
openDialog
});
</script>
<style lang='scss' scoped>
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
.box {
width: 80%;
box-sizing: border-box;
margin: 0 auto;
padding: 24px;
border-radius: 12px;
background: #fff;
.title {
font-weight: 550;
text-align: center;
font-size: 18px;
color: rgba(0, 0, 0, 0.85);
}
.content {
margin-top: 13px;
text-align: center;
font-size: 14px;
color: rgba(0, 0, 0, 0.65);
line-height: 22px;
}
.foot {
margin-top: 24px;
display: flex;
justify-content: center;
margin-top: 20px;
.cancle {
width: 128px;
height: 44px;
margin-right: 12px;
display: flex;
justify-content: center;
align-items: center;
background: #FFFFFF;
border-radius: 25px;
font-size: 16px;
color: rgba(0, 0, 0, 0.85);
border: 1px solid rgba(0, 0, 0, 0.15);
}
.ok {
width: 128px;
height: 44px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
color: #fff;
background: #43C9C3;
border-radius: 25px;
border: 1px solid 43C9C3;
}
}
}
}
</style>

166
src/components/c-list.vue Normal file
View File

@ -0,0 +1,166 @@
<template>
<van-pull-refresh v-model="pull_loading" @refresh="onRefresh" success-text="刷新成功">
<van-list v-model:loading="loading" :finished="finished" finished-text="" style="min-height: 350px;" ref="clist"
@load="onLoad" class="msglist">
<div class="bar"></div>
<div class="cell" v-for="(item, index) in list" :key="item.case_id" @click="goDetail(item.case_id, item.is_join)">
<div class="namebox">
<img src="../assets/newicon.png" alt="" v-if="item.is_new == 1">
<img src="../assets/fuli.png" alt="" v-if="item.is_welfare == 1">
<div class="name">{{ item.case_name }}</div>
</div>
<div class="btnbox">
<div class="writer">{{ item.case_author }}</div>
<div class="btn" :class="{ finsihed: item.is_join == 1 }">{{ item.is_join == 1 ? '已参与' : '立即参与' }}</div>
</div>
</div>
<div class="nodata" v-show="list.length == 0">
<div class="empty">
<div class="emptydesc">
"暂无相关数据~"
</div>
</div>
</div>
</van-list>
</van-pull-refresh>
</template>
<script setup>
import { ref } from 'vue';
import api from '../api/user.js';
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();
const route = useRoute();
const pull_loading = ref(false);
const page = ref(0);
const project_id = ref(route.query.project_id);
const list = ref([]);
const loading = ref(false);
const finished = ref(false);
const props = defineProps({
is_take_part: {
type: Number,
default: 0
},
});
const getList = async () => {
let { code, data } = await api.getCaseList({
page: page.value,
project_id: project_id.value,
page_size: 10,
is_take_part: props.is_take_part
})
loading.value = false;
if (code == 200) {
pull_loading.value = false;
list.value = list.value.concat(data.data);
if (data.data.length <10) {
finished.value = true;
}
}
}
const onRefresh = () => {
finished.value = false;
loading.value = true;
list.value = [];
page.value = 1;
getList()
}
const goDetail = (id, join) => {
if (join == 1) {
router.push({
path: '/result',
query: {
case_id: id,
project_id: project_id.value
}
})
} else {
router.push({
path: '/caseIntro',
query: {
case_id: id
}
})
}
}
const onLoad = () => {
if(!finished.value) {
page.value++;
getList();
}
};
</script>
<style lang='scss' scoped>
.bar {
width: 100%;
height: 10px;
background: #F5F5F5;
}
.cell {
margin: 0 15px;
border-bottom: 1px solid #E6E6E6;
.btnbox {
margin-top: 18px;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: space-between;
.writer {
font-size: 15px;
color: #666666;
}
.btn {
display: flex;
align-items: center;
height: 27px;
background: #43C9C3;
border-radius: 14px;
padding: 0 6px;
font-size: 12px;
color: #FFFFFF;
}
.finsihed {
background: #B5B5B5;
}
}
.namebox {
margin-top: 15px;
vertical-align: baseline;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
.name {
font-weight: 500;
font-size: 16px;
color: #000000;
display: inline;
}
img {
display: inline-block;
position: relative;
top: 4px;
line-height: 24px;
height: 20px;
}
}
}
</style>

View File

@ -0,0 +1,256 @@
<template>
<div class="cellbox" v-for="(item, index) in childList" :key="item.comment_id">
<commentunit :item="item" :parentIndex="parentIndex" :index="index" :childList="childList" :parent_id="item.comment_id" :root_id="root_id" :case_id="case_id" :project_id="project_id" @freshLike="freshLike" @freshList="freshList" :replyName="replyName"></commentunit>
<expandunit v-if="parentIndex<0 && item.case_comment && item.case_comment.length>0" :parent_id="item.comment_id" :case_id="case_id" :project_id="project_id" :total_amount="item.total_amount" @freshSecComment="freshSecComment" :index="index" :listLength="item.case_comment.length"></expandunit>
</div>
</template>
<script setup>
import { ref } from "vue";
const expand = ref(true);
const props = defineProps({
item:{
type:Object,
default:()=>{}
},
parentIndex:{
type:Number,
default:-1
},
index:{
type:Number,
default:0
},
root_id:{
type:String,
default:''
},
childList:{
type:Array,
default:()=>[]
},
parent_id:{
type:String,
default:''
},
root_id:{
type:String,
default:''
},
replyName:{
type:String,
default:''
},
case_id:{
type:String,
default:''
},
project_id:{
type:String,
default:''
}
})
const emit = defineEmits(['freshLike','handleFreshList','freshSecComment']);
const freshLike = (data) => {
emit('freshLike',data);
};
const freshList = () => {
emit('handleFreshList',true);
};
const freshSecComment = (data) => {
emit('freshSecComment',data);
}
// const list=ref(props.childList);
// const handleShowMore = () => {
// list.value=expand.value?props.childList:props.childList.slice(0,3);
// }
// watch(() =>props.childList, () => {
// if(props.childList.length>=1 && props.parentIndex<0 ) {
// expand.value=false;
// handleShowMore()
// }
// },{
// immediate:true
// });
</script>
<style lang='scss' scoped>
.cellbox {
padding-top: 15px;
}
.active {
border-bottom: 1px solid #E5E5E5;
}
.comemntcell {
display: flex;
.descbox {
margin-left: 12px;
flex: 1;
// border-bottom: 1px solid #E5E5E5;
.depart {
margin-top: 10px;
font-weight: 400;
font-size: 12px;
color: #333333;
}
.hospital {
margin-top: 4px;
font-weight: 400;
font-size: 12px;
color: #333333;
}
.date {
margin-top: 4px;
font-weight: 400;
font-size: 12px;
color: #333333;
}
.comemntcell {
border-bottom: 1px solid #E5E5E5;
.descbox {
border: none;
padding-bottom: 15px;
}
}
.comment {
margin-top: 4px;
display: flex;
.text {
font-size: 12px;
color: #333333;
line-height: 17px;
flex: 1;
}
.reply {
margin-left: 30px;
width: 40px;
height: 20px;
background: #43C9C3;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
color: #FFFFFF;
}
}
.tag {
margin-top: 2px;
font-size: 12px;
color: #FFFFFF;
height: 15px;
background: #43C9C3;
border-radius: 10px;
display: flex;
padding: 0 4px;
width: 52px;
justify-content: center;
align-items: center;
span {
transform: scale(0.9);
}
}
.namebox {
display: flex;
align-items: center;
justify-content: space-between;
.name {
font-size: 17px;
color: #333333;
}
.zanbox {
display: flex;
font-size: 15px;
color: #333333;
align-items: center;
margin-right: 40px;
img {
width: 20px;
height: 20px;
}
}
}
}
}
.talkbox {
margin: 12px 30px 0px 0;
}
.textbox {
border-radius: 5px;
border: .5px solid #43C9C3;
:deep() .van-cell {
background: none;
}
:deep() .van-field__word-limit {
color: #999999;
}
}
.toolbox {
margin-top: 12px;
display: flex;
justify-content: space-between;
.cancel {
height: 38px;
background: #FFFFFF;
border: 1px solid #EBEBEB;
border-radius: 4px;
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
color: #737478;
flex: 1;
}
.ok {
margin-left: 12px;
flex: 1;
border-radius: 4px;
height: 38px;
display: flex;
justify-content: center;
align-items: center;
background: #43C9C3;
border: 1px solid #43C9C3;
font-size: 14px;
color: #fff;
}
}
</style>

View File

@ -0,0 +1,417 @@
<template>
<div class="comemntcell">
<van-image
round
width="49"
height="49"
:src="avatar"
/>
<div class="descbox">
<div class="namebox">
<div class="name">{{ formatName(item.user_name) }}</div>
<div
class="zanbox"
@click="toggleLike(item.comment_id, item.is_like, index)"
>
<img src="../assets/nozan.png" alt="" v-if="!item.is_like" />
<img src="../assets/zan.png" alt="" v-else />
<div class="num" v-if="item.like_num > 0">{{ formatNumber(item.like_num) }}</div>
</div>
</div>
<div class="depart">{{ item.department_name }}</div>
<div class="hospital">{{ item.hospital_name }}</div>
<div class="date">{{ formateDate(item.created_at) }}</div>
<div class="tag" v-if="item.is_high_quality == 1">
<span>优质解答</span>
</div>
<div class="comment">
<div class="text">
{{ item.content }}
</div>
<div class="reply" @click="openConmment(item.level, item.user_name)">
{{ item.level == 1 ? "回复" : "评论" }}
</div>
</div>
<!-- <reply ref="replyRef" :parentIndex="parentIndex" :parent_id='item.comment_id' :root_id="''" :case_id="case_id" :project_id="project_id"></reply> -->
<div class="talkbox" v-show="showReply">
<div class="textbox">
<van-field
:autosize="{ minHeight: 120 }"
v-model="content"
rows="2"
autosize
type="textarea"
maxlength="200"
placeholder="请输入评论内容"
show-word-limit
/>
</div>
<div class="toolbox">
<div class="cancel" @click="showReply = false">取消</div>
<button class="ok" @click="handleAdd" :disabled="isLock">提交</button>
</div>
</div>
<div class="sondiv">
<comment-list
v-if="item.case_comment && item.case_comment.length > 0"
:childList="item.case_comment"
:parentIndex="index"
@freshLike="freshLike"
:root_id="parent_id"
:replyName="item.user_name"
:case_id="case_id"
:project_id="project_id"
@freshSecComment="freshSecComment"
@handleFreshList="handleFreshList"
></comment-list>
</div>
</div>
</div>
</template>
<script setup>
import { showToast } from "vant";
import { ref } from "vue";
import api from "../api/user.js";
import dayjs from "dayjs";
import avatar from "../assets/doctor_avatar.png";
const replyRef = ref(null);
const showReply = ref(false);
const content = ref("");
const level = ref(2);
const writer = ref("");
const isLock = ref(false);
const props = defineProps({
item: {
type: Object,
default: () => {},
},
parent_id: {
type: String,
default: "",
},
root_id: {
type: String,
default: "",
},
replyName: {
type: String,
default: "",
},
index: {
type: Number,
default: 0,
},
childList: {
type: Array,
default: () => [],
},
project_id: {
type: String,
default: "",
},
case_id: {
type: String,
default: "",
},
parentIndex: {
type: Number,
default: -1,
},
});
const emit = defineEmits([
"freshLike",
"freshList",
"freshSecComment",
"handleFreshList",
]);
const formateDate = (val) => {
return dayjs(val).format("YYYY-MM-DD HH:mm");
};
/**
* 显示回复框的处理函数
*
* 该函数用于设置 showReply 的值为 true从而显示回复框
*/
const handleReply = () => {
replyRef.value[0].openReply();
};
const handleLike = async (id, index) => {
const { code } = await api.like(id);
if (code == 200) {
showToast("点赞成功");
emit("freshLike", {
parentIndex: props.parentIndex,
index: index,
likeNum: 1,
});
}
};
const freshLike = (data) => {
emit("freshLike", data);
};
const freshSecComment = (data) => {
emit("freshSecComment", data);
};
const handleCancleLike = async (id, index) => {
const { code } = await api.cancleLike(id);
if (code == 200) {
showToast("取消点赞成功");
emit("freshLike", {
parentIndex: props.parentIndex,
index: index,
likeNum: -1,
});
}
};
const toggleLike = (id, flag, index) => {
console.log(id, flag, props.parentIndex, index);
if (flag) {
handleCancleLike(id, index);
} else {
handleLike(id, index);
}
};
const formatNumber = (value) => {
if (value > 9999) {
return (value / 10000).toFixed(1) + "w";
} else {
return value;
}
};
const formatName=(val)=>{
if(!val) return '医生'
return val.slice(0,1)+'医生'
}
const openConmment = (flag, name) => {
showReply.value = true;
if (flag == 2 || flag == 3) {
content.value = "@" + formatName(name) + "";
writer.value = "@" + formatName(name) + "";
level.value = 3;
} else {
content.value = "";
writer.value = "";
level.value = 2;
}
};
const handleAdd = async () => {
if (content.value == "" || content.value == writer.value) {
showToast("请输入内容");
return false;
}
showReply.value = false;
isLock.value = true;
const { code, data } = await api.addComment({
case_id: props.case_id,
project_id: props.project_id,
content: content.value,
level: level.value,
root_id: level.value == 2 ? props.parent_id : props.root_id,
parent_id: props.parent_id,
});
isLock.value = false;
if (code == 200) {
emit("freshList");
content.value = "";
}
};
const handleFreshList = () => {
emit("freshList");
};
</script>
<style lang='scss' scoped>
.sondiv {
margin-top: 15px;
:deep() .cellbox {
padding-top: 15px;
}
}
.cellbox {
padding-top: 15px;
}
.active {
border-bottom: 1px solid #e5e5e5;
}
.comemntcell {
display: flex;
.descbox {
margin-left: 12px;
flex: 1;
// border-bottom: 1px solid #E5E5E5;
.depart {
margin-top: 10px;
font-weight: 400;
font-size: 14px;
color: #333333;
}
.hospital {
margin-top: 5px;
font-weight: 400;
font-size: 14px;
color: #333333;
}
.date {
margin-top: 5px;
font-weight: 400;
font-size: 14px;
color: #333333;
}
.comemntcell {
border-bottom: 1px solid #e5e5e5;
.descbox {
border: none;
padding-bottom: 15px;
}
}
.comment {
margin-top: 5px;
display: flex;
.text {
font-size: 14px;
color: #333333;
line-height: 17px;
flex: 1;
white-space: pre-wrap;
word-wrap: break-word;
word-break: break-all;
}
.reply {
margin-left: 30px;
width: 50px;
height: 25px;
background: #43c9c3;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
color: #ffffff;
}
}
.tag {
margin-top: 2px;
font-size: 12px;
color: #ffffff;
height: 15px;
background: #43c9c3;
border-radius: 10px;
display: flex;
padding: 0 4px;
width: 52px;
justify-content: center;
align-items: center;
span {
transform: scale(0.9);
}
}
.namebox {
display: flex;
align-items: center;
justify-content: space-between;
.name {
font-size: 16px;
color: #333333;
}
.zanbox {
display: flex;
font-size: 15px;
justify-content: flex-end;
color: #333333;
align-items: center;
margin-right: 40px;
img {
width: 20px;
height: 20px;
}
}
}
}
}
.expand {
width: 240px;
height: 19px;
margin: 12px auto 12px;
background: #43c9c3;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
font-size: 10px;
color: #ffffff;
}
.talkbox {
margin: 12px 30px 0px 0;
}
.textbox {
border-radius: 5px;
border: 0.5px solid #43c9c3;
:deep() .van-cell {
background: none;
}
:deep() .van-field__word-limit {
color: #999999;
}
}
.toolbox {
margin-top: 12px;
display: flex;
justify-content: space-between;
.cancel {
height: 38px;
background: #ffffff;
border: 1px solid #ebebeb;
border-radius: 4px;
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
color: #737478;
flex: 1;
}
.ok {
margin-left: 12px;
flex: 1;
border-radius: 4px;
height: 38px;
display: flex;
justify-content: center;
align-items: center;
background: #43c9c3;
border: 1px solid #43c9c3;
font-size: 14px;
color: #fff;
}
}
</style>

View File

@ -0,0 +1,82 @@
<template>
<div class="expand" @click="showMore" v-if="restNum>0">
展示剩余{{restNum}}条回复
</div>
</template>
<script setup>
import { ref } from 'vue';
import api from '../api/user.js';
const page=ref(1);
const props = defineProps({
parent_id:{
type:String,
default:'',
},
index:{
type:Number,
default:0
},
case_id:{
type:String,
default:''
},
project_id:{
type:String,
default:''
},
listLength:{
type:Number,
default:0
},
total_amount:{
type:Number,
default:0
}
})
const restNum=ref(props.total_amount);
const emit = defineEmits(['freshSecComment']);
const showMore=()=>{
page.value++;
getList(props.parent_id)
}
const getList = async (root_id='') => {
const { code, data } = await api.getCommentList({
case_id:props.case_id,
project_id:props.project_id,
page:page.value,
page_size:5,
root_id:root_id
})
if (code == 200) {
if(data.data.length>0){
emit('freshSecComment',{
commentIndex:props.index,
commentData:data.data
});
nextTick(()=>{
restNum.value=data.total-props.listLength;
})
}
//console.log(data);
}
}
</script>
<style lang='scss' scoped>
.expand {
width: 240px;
height: 19px;
margin: 12px auto 12px;
background: #43C9C3;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
font-size: 10px;
color: #FFFFFF;
}
</style>

View File

@ -0,0 +1,28 @@
import { createApp } from "vue"
// 导入写好的Loading.vue文件
import Loading from "./loading.vue"
export default {
loading: null,
// 每当这个插件被添加到应用程序中时,如果它是一个对象,就会调用 install 方法。如果它是一个 function则函数本身将被调用。在这两种情况下——它都会收到两个参数由 Vue 的 createApp 生成的 app 对象和用户传入的选项。
install(app) {
if (this.loading) {
// 防止多次载入
app.config.globalProperties.$loading = this.loading
return
}
// 创建Loading实例用于挂载
let instance = createApp(Loading)
// 创建div元素装载Loading对象
let div = document.createElement("div")
div.setAttribute("id","maskbox")
let body = document.body
// 导入body中
body.appendChild(div);
this.loading = instance.mount(div)
// 挂载vue身上
app.config.globalProperties.$loading = this.loading;
}
}

View File

@ -0,0 +1,194 @@
<template>
<div class="maskbox" v-if="loading">
<div class="mask"></div>
<div class="sk-fading-circle">
<div class="sk-circle1 sk-circle"></div>
<div class="sk-circle2 sk-circle"></div>
<div class="sk-circle3 sk-circle"></div>
<div class="sk-circle4 sk-circle"></div>
<div class="sk-circle5 sk-circle"></div>
<div class="sk-circle6 sk-circle"></div>
<div class="sk-circle7 sk-circle"></div>
<div class="sk-circle8 sk-circle"></div>
<div class="sk-circle9 sk-circle"></div>
<div class="sk-circle10 sk-circle"></div>
<div class="sk-circle11 sk-circle"></div>
<div class="sk-circle12 sk-circle"></div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const loading = ref(false)
const show = () => {
loading.value = true
}
const hide = () => {
loading.value = false
}
defineExpose({
show,
hide
})
</script>
<style lang="scss">
.maskbox{
top:0;
bottom:0;
width:100%;
height:100%;
z-index:9990;
position: fixed;
display: flex;
justify-content: center;
align-items: center;
.mask{
top:0;
bottom:0;
position: absolute;
width:100%;
height:100%;
opacity: 0.4;
background-color: #000;
}
}
.sk-fading-circle {
margin: 100px auto;
width: 40px;
height: 40px;
position: relative;
}
.sk-fading-circle .sk-circle {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
.sk-fading-circle .sk-circle:before {
content: '';
display: block;
margin: 0 auto;
width: 15%;
height: 15%;
background-color: #fff;
border-radius: 100%;
-webkit-animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;
animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;
}
.sk-fading-circle .sk-circle2 {
-webkit-transform: rotate(30deg);
-ms-transform: rotate(30deg);
transform: rotate(30deg);
}
.sk-fading-circle .sk-circle3 {
-webkit-transform: rotate(60deg);
-ms-transform: rotate(60deg);
transform: rotate(60deg);
}
.sk-fading-circle .sk-circle4 {
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.sk-fading-circle .sk-circle5 {
-webkit-transform: rotate(120deg);
-ms-transform: rotate(120deg);
transform: rotate(120deg);
}
.sk-fading-circle .sk-circle6 {
-webkit-transform: rotate(150deg);
-ms-transform: rotate(150deg);
transform: rotate(150deg);
}
.sk-fading-circle .sk-circle7 {
-webkit-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
}
.sk-fading-circle .sk-circle8 {
-webkit-transform: rotate(210deg);
-ms-transform: rotate(210deg);
transform: rotate(210deg);
}
.sk-fading-circle .sk-circle9 {
-webkit-transform: rotate(240deg);
-ms-transform: rotate(240deg);
transform: rotate(240deg);
}
.sk-fading-circle .sk-circle10 {
-webkit-transform: rotate(270deg);
-ms-transform: rotate(270deg);
transform: rotate(270deg);
}
.sk-fading-circle .sk-circle11 {
-webkit-transform: rotate(300deg);
-ms-transform: rotate(300deg);
transform: rotate(300deg);
}
.sk-fading-circle .sk-circle12 {
-webkit-transform: rotate(330deg);
-ms-transform: rotate(330deg);
transform: rotate(330deg);
}
.sk-fading-circle .sk-circle2:before {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}
.sk-fading-circle .sk-circle3:before {
-webkit-animation-delay: -1s;
animation-delay: -1s;
}
.sk-fading-circle .sk-circle4:before {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}
.sk-fading-circle .sk-circle5:before {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}
.sk-fading-circle .sk-circle6:before {
-webkit-animation-delay: -0.7s;
animation-delay: -0.7s;
}
.sk-fading-circle .sk-circle7:before {
-webkit-animation-delay: -0.6s;
animation-delay: -0.6s;
}
.sk-fading-circle .sk-circle8:before {
-webkit-animation-delay: -0.5s;
animation-delay: -0.5s;
}
.sk-fading-circle .sk-circle9:before {
-webkit-animation-delay: -0.4s;
animation-delay: -0.4s;
}
.sk-fading-circle .sk-circle10:before {
-webkit-animation-delay: -0.3s;
animation-delay: -0.3s;
}
.sk-fading-circle .sk-circle11:before {
-webkit-animation-delay: -0.2s;
animation-delay: -0.2s;
}
.sk-fading-circle .sk-circle12:before {
-webkit-animation-delay: -0.1s;
animation-delay: -0.1s;
}
@-webkit-keyframes sk-circleFadeDelay {
0%, 39%, 100% { opacity: 0; }
40% { opacity: 1; }
}
@keyframes sk-circleFadeDelay {
0%, 39%, 100% { opacity: 0; }
40% { opacity: 1; }
}
</style>

207
src/components/question.vue Normal file
View File

@ -0,0 +1,207 @@
<template>
<div class="qbox">
<div class="title">{{question.question_name}}{{question.question_type==1?'单选':question.question_type==2?'多选':question.question_type==3?'问答':'判断' }}</div>
<div class="tips" v-if="question.question_type==2">:本题可选择多个选项!</div>
<div class="tips" v-else-if="question.question_type==1">:本题最多可选择1个选项!</div>
<div class="options" v-if="question.question_type==2">
<van-checkbox-group v-model="manychecked" @change="changeCheck" :disabled="disabled">
<div class="cell" v-for="(item,index) in question.case_item_question_option" :key="item.option_id">
<van-checkbox :name="zimu[index]">
{{ item.option_value }}
<template #icon="props">
<img class="img-icon" :src="props.checked ? activeIcon : inactiveIcon" />
</template>
</van-checkbox>
</div>
<!-- <div class="cell">
<van-checkbox name="b">
复选框 b
<template #icon="props">
<img class="img-icon" :src="props.checked ? activeIcon : inactiveIcon" />
</template>
</van-checkbox>
</div> -->
</van-checkbox-group>
</div>
<div class="options" v-else-if="question.question_type==1" >
<van-radio-group v-model="singlechecked" @change="changeRadio" :disabled="disabled">
<div class="cell" v-for="(item,index) in question.case_item_question_option" :key="item.option_id">
<van-radio :name="zimu[index]">
{{ item.option_value }}
<template #icon="props">
<img class="img-icon" :src="props.checked ? activeIcon : inactiveIcon" />
</template>
</van-radio>
</div>
</van-radio-group>
</div>
<div class="options_con" v-else-if="question.question_type == 3">
<van-field @update:model-value="changeContent" :autosize="{ minHeight: 120 }" v-model="content" rows="2" autosize type="textarea"
placeholder="请输入内容" show-word-limit />
</div>
<div class="options" v-else-if="question.question_type == 4">
<van-radio-group v-model="singlechecked" @change="changeRadio" :disabled="disabled">
<div class="cell" v-for="(item,index) in question.case_item_question_option" :key="item.option_id">
<van-radio :name="zimu[index]">
{{ item.option_value }}
<template #icon="props">
<img class="img-icon" :src="props.checked ? activeIcon : inactiveIcon" />
</template>
</van-radio>
</div>
</van-radio-group>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import activeIcon from "@/assets/select.png";
import inactiveIcon from "@/assets/noselect.png";
const emit = defineEmits(['checkAnswer']);
const manychecked = ref([]);
const singlechecked = ref('');
const content = ref('')
const zimu=['A','B','C','D','E','F','G','H','I','J'];
const props=defineProps({
question: {
type: Object,
default: () => ({}),
},
disabled:{
type:Boolean,
default:false
},
pageIndex:{
type:Number,
default:0
},
questionIndex:{
type:Number,
default:0
},
});
const changeRadio = (val) => {
let optionObj=props.question.case_item_question_option.find((item,index)=>zimu[index]==val);
emit('checkAnswer',{
pageIndex:props.pageIndex,
answer:val,
questionId:props.question.question_id,
error_tips:props.question.error_tips,
is_right_next:props.question.is_right_next,
option_id:optionObj?optionObj.option_id:'',
questionIndex:props.questionIndex,
question_type:props.question.question_type,
question_name:props.question.question_name,
question_answer:props.question.question_answer
})
};
const changeContent = (val) => {
emit('checkAnswer',{
pageIndex:props.pageIndex,
answer:val,
error_tips:props.question.error_tips,
questionId:props.question.question_id,
is_right_next:props.question.is_right_next,
questionIndex:props.questionIndex,
question_type:props.question.question_type,
question_name:props.question.question_name,
question_answer:props.question.question_answer
})
};
const changeCheck = (val) => {
let sortAnswer=val.sort();
let option_id='';
for(let i=0;i<sortAnswer.length;i++){
let optionObj=props.question.case_item_question_option.find((item,index)=>zimu[index]==sortAnswer[i]);
if(option_id){
if(optionObj.option_id){
option_id+=','+optionObj.option_id;
}
}else{
if(optionObj.option_id){
option_id=optionObj.option_id;
}
}
}
//let optionObj=props.question.case_item_question_option.find((item,index)=>zimu[index]==val);
emit('checkAnswer',{
pageIndex:props.pageIndex,
answer:sortAnswer,
option_id:option_id,
is_right_next:props.question.is_right_next,
error_tips:props.question.error_tips,
questionId:props.question.question_id,
questionIndex:props.questionIndex,
question_type:props.question.question_type,
question_name:props.question.question_name,
question_answer:props.question.question_answer
})
};
</script>
<style lang='scss' scoped>
.options_con {
border-radius: 5px;
border: .5px solid #B8B8B8;
margin-top: 15px;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.08);
border-radius: 10px;
padding: 15px 0px;
padding-bottom: 8px;
:deep() .van-cell {
background: none;
}
:deep() .van-field__word-limit {
color: #999999;
}
}
.qbox {
margin-bottom: 15px;
.title {
font-weight: bold;
font-size: 16px;
color: #333333;
}
.tips {
margin-top: 8px;
font-weight: 500;
font-size: 12px;
color: #999999;
}
.cell {
border-radius: 10px;
border: 1px solid #B8B8B8;
padding: 12px;
background: #fff;
margin-bottom: 12px;
}
.active{
border: 1px solid #43C9C3;
}
.cell:last-child {
margin-bottom: 0;
}
.options {
margin-top: 15px;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.08);
border-radius: 10px;
padding: 15px 12px;
}
.img-icon{
width: 19px;
height: 19px;
}
}
</style>

View File

@ -0,0 +1,163 @@
<template>
<div class="qbox" v-if="question.question_type != 3">
<div class="title">
{{ question.question_name }}{{ question.question_type == 1 ? '单选' : question.question_type == 2 ? '多选' :question.question_type == 3 ? '问答' : '判断'}}</div>
<div class="tips" v-if="question.question_type == 2">:本题可选择多个选项!</div>
<div class="tips" v-else-if="question.question_type == 1">:本题最多可选择1个选项!</div>
<div class="options" v-if="question.question_type == 1 || question.question_type == 2 || question.question_type == 4">
<div class="cell" v-for="(item, index) in question.case_item_question_option" :key="item.option_id" v-if="question.case_item_question_option">
<div class="title">{{ item.option_value }}</div>
<div class="progressbox">
<div class="progress">
<div class="inner" :style="{ width: 220 / 100 * item.proportion + 'px' }"></div>
</div>
<div class="numbox">
<div class="num">{{ item.select_num }}</div>
<div class="precent">{{ item.proportion }}%</div>
</div>
</div>
</div>
</div>
<div class="options_con" v-if="question.question_type == 3">
<van-field readonly :autosize="{ minHeight: 120 }" v-model="question.question_answer" rows="2" autosize type="textarea"
placeholder="请输入内容" show-word-limit />
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
const emit = defineEmits(['checkAnswer']);
const zimu = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
const props = defineProps({
question: {
type: Object,
default: () => ({}),
},
disabled: {
type: Boolean,
default: false
},
pageIndex: {
type: Number,
default: 0
},
questionIndex: {
type: Number,
default: 0
},
});
</script>
<style lang='scss' scoped>
.options_con {
border-radius: 5px;
border: .5px solid #efefef;
margin-top: 15px;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.08);
border-radius: 10px;
padding: 15px 0px;
padding-bottom: 8px;
:deep() .van-cell {
background: none;
}
:deep() .van-field__word-limit {
color: #999999;
}
}
.qbox {
margin-bottom: 15px;
.title {
font-weight: bold;
font-size: 16px;
color: #333333;
}
.tips {
margin-top: 8px;
font-weight: 500;
font-size: 12px;
color: #999999;
}
.cell {
border-bottom: 1px solid #efefef;
background: #fff;
margin-bottom: 12px;
.title {
margin: 0 10px;
font-weight: 500;
font-size: 14px;
color: #1F1F1F;
}
.numbox {
display: flex;
font-size: 12px;
color: #999999;
.precent {
margin-left: 5px;
}
}
.progressbox {
justify-content: space-between;
align-items: center;
margin: 8px 10px 15px;
margin-bottom: 15px;
display: flex;
border-radius: 5px;
.progress {
margin-right: 10px;
width: 220px;
overflow: hidden;
height: 5px;
background: #EFF3FF;
border-radius: 5px;
}
.inner {
width: 80%;
height: 5px;
border-radius: 5px;
background: #43C9C3;
}
}
}
.active {
border: 1px solid #43C9C3;
}
.cell:last-child {
margin-bottom: 0;
border: none;
}
.options {
margin-top: 15px;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.08);
border-radius: 10px;
padding: 15px 0px;
padding-bottom: 8px;
}
.img-icon {
width: 19px;
height: 19px;
}
}
</style>

103
src/components/reply.vue Normal file
View File

@ -0,0 +1,103 @@
<template>
<div class="talkbox " v-show="showReply">
<div class="textbox">
<van-field :autosize="{ minHeight: 120 }" v-model="content" rows="2" autosize type="textarea"
maxlength="200" placeholder="请输入评论内容" show-word-limit />
</div>
<div class="toolbox">
<div class="cancel" @click="showReply = false">取消</div>
<div class="ok" @click="handleSubmit">提交</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const showReply=ref(false);
const content=ref('')
const props = defineProps({
root_id: {
type: String,
default: ''
},
project_id:{
type: String,
default: ''
},
case_id:{
type: String,
default: ''
},
parent_id: {
type: String,
default: ''
},
});
const handleSubmit=()=>{
showReply.value=false;
};
const openReply=()=>{
showReply.value=true;
};
const closeReply=()=>{
showReply.value=false;
};
defineExpose({
openReply,
closeReply
})
</script>
<style lang='scss' scoped>
.talkbox {
margin: 12px 30px 0px 0;
}
.textbox {
border-radius: 5px;
border: .5px solid #43C9C3;
:deep() .van-cell {
background: none;
}
:deep() .van-field__word-limit {
color: #999999;
}
}
.toolbox {
margin-top: 12px;
display: flex;
justify-content: space-between;
.cancel {
height: 38px;
background: #FFFFFF;
border: 1px solid #EBEBEB;
border-radius: 4px;
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
color: #737478;
flex: 1;
}
.ok {
margin-left: 12px;
flex: 1;
border-radius: 4px;
height: 38px;
display: flex;
justify-content: center;
align-items: center;
background: #43C9C3;
border: 1px solid #43C9C3;
font-size: 14px;
color: #fff;
}
}
</style>

View File

@ -0,0 +1,76 @@
<template>
<div class="qbox">
<div class="title">结合患者目前情况下一步该如何治疗?</div>
<div class="tips">:本题可选择多个选项!</div>
<div class="options">
<van-radio-group v-model="checked">
<div class="cell">
<van-radio name="a">
复选框 a
<template #icon="props">
<img class="img-icon" :src="props.checked ? activeIcon : inactiveIcon" />
</template>
</van-radio>
</div>
<div class="cell">
<van-radio name="b">
复选框 b
<template #icon="props">
<img class="img-icon" :src="props.checked ? activeIcon : inactiveIcon" />
</template>
</van-radio>
</div>
</van-radio-group>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import activeIcon from "@/assets/select.png";
import inactiveIcon from "@/assets/noselect.png";
const checked = ref('');
</script>
<style lang='scss' scoped>
.qbox {
.title {
font-weight: bold;
font-size: 16px;
color: #333333;
}
.tips {
margin-top: 8px;
font-weight: 500;
font-size: 12px;
color: #999999;
}
.cell {
border-radius: 10px;
border: 1px solid #B8B8B8;
padding: 12px;
background: #fff;
margin-bottom: 12px;
}
.active{
border: 1px solid #43C9C3;
}
.cell:last-child {
margin-bottom: 0;
}
.options {
margin-top: 15px;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.08);
border-radius: 10px;
padding: 15px 12px;
}
.img-icon{
width: 19px;
height: 19px;
}
}
</style>

View File

@ -0,0 +1,892 @@
<template>
<van-pull-refresh v-model="pull_loading" @refresh="onRefresh" success-text="刷新成功">
<van-list
v-model="loading"
:finished="finished"
finished-text=""
ref="list"
@load="onLoad"
class="msglist"
>
<div
class="msgcell"
v-for="(item, index) in list"
:key="item.gandanhome_id"
>
<div class="namebox">
<div class="left">
<div class="leftcon">
<img
v-lazy="img_host + item.patient_photo"
alt=""
class="avastar head"
@click="goPage(item.patient_uuid)"
/>
<div class="info">
<div class="name_level">
<div class="name">{{ item.patient_name }}</div>
<img
v-lazy="imgcell.url"
alt=""
class="level"
v-for="imgcell in item.tags"
:key="imgcell.url"
v-if="type != 'mypage'"
/>
<div
class="settop"
v-if="type == 'mypage'|| type =='patient'"
v-show="item.top_flag == 1"
>
<span>置顶</span>
</div>
</div>
<div class="time">{{ item.update_date | fromTimeNow }}</div>
</div>
</div>
</div>
<div
class="more"
v-if="type == 'mypage'"
@click="openMore(index, item.gandanhome_id, item.top_flag)"
>
...
</div>
</div>
<div class="title" @click="goDetail(item.gandanhome_id)">
{{ item.title }}
</div>
<!-- <div @click.stop="goDetail(item.gandanhome_id)"> -->
<clamp
:htmlContent="item.content"
:topics="item.topics"
:id="item.gandanhome_id"
></clamp>
<!-- </div> -->
<div
class="imgbox"
@click="goDetail(item.gandanhome_id)"
v-if="item.imgs.length <= 6 && item.imgs.length != 4"
>
<div class="imgrow" v-for="itemcell in transforPages(item.imgs, 3)">
<div class="imgcell" v-for="imgcell in itemcell">
<van-image
v-if="imgcell.status==1"
lazy-load
fit="cover"
radius="8px"
:src="imgcell.url"
width="110px"
height="110px"
/>
<van-image
v-else
lazy-load
fit="cover"
radius="8px"
:src="require('../assets/weigui.png')"
width="110px"
height="110px"
/>
</div>
</div>
</div>
<div class="imgbox" v-else-if="item.imgs.length == 4">
<div
class="imgrow tworow"
v-for="itemcell in transforPages(item.imgs, 2)"
@click="goDetail(item.gandanhome_id)"
>
<div class="imgcell" v-for="imgcell in itemcell">
<van-image
lazy-load
v-if="imgcell.status==1"
radius="8px"
:src="imgcell.url"
width="110px"
height="110px"
/>
<van-image
lazy-load
v-else
radius="8px"
:src="require('../assets/weigui.png')"
width="110px"
height="110px"
/>
</div>
</div>
</div>
<div class="imgbox" v-else>
<div class="swiperbox">
<van-swipe :height="142" indicator-color="#3CC7C0">
<van-swipe-item
v-for="(imgcell, index) in item.imgs"
:key="index"
@click="goDetail(item.gandanhome_id)"
>
<div class="swipebg" >
<img v-lazy="imgcell.url" alt="" class="swiperImg" v-if="imgcell.status==1"/>
<img src="../assets/weigui.png" alt="" class="swiperImg" v-else>
</div>
<!-- <img v-lazy="imgcell.url" alt="" class="swipebg" v-if="imgcell.status==1"/>
<img src="../assets/weigui.png" alt="" class="swipebg" v-else> -->
<img v-lazy="imgcell.url" alt="" class="swiperImg" v-if="imgcell.status==1"/>
<img src="../assets/weigui.png" alt="" class="swiperImg" v-else>
</van-swipe-item>
</van-swipe>
</div>
</div>
<div class="tool" @click="goDetail(item.gandanhome_id)">
<div class="address">
{{ item.ip_show == 1 ? item.ip_address_province : "" }}
</div>
<div class="zan_comment">
<div
class="zan"
@click.stop="
toggleLike(item.isLike, index, item.gandanhome_id, item.likeNum)
"
>
<img src="../assets/zan.png" alt="" v-if="item.isLike == 1" />
<img src="../assets/unzan.png" alt="" v-else />
<div class="zancount" v-if="item.likeNum >0">{{ item.likeNum }}</div>
</div>
<!-- <div class="collectbox" v-if="type=='mypage' || type=='mycollect'" @click.stop="toggleCollect(item.is_collect,index,item.gandanhome_id)">
<img src="../assets/collect.png" alt="" v-if="item.is_collect == 1"/>
<img src="../assets/uncollect.png" alt="" v-else />
</div> -->
<div class="comment">
<img src="../assets/msg_comment.png" alt="" />
<div class="zancount" v-if="item.commonNum >0">{{ item.commonNum }}</div>
</div>
</div>
</div>
</div>
<div class="nodata" v-show="list.length == 0 ">
<div class="empty">
<img src="../assets/tie_empty.png" alt="" />
<div class="emptydesc">
{{
type == "searchBytitle"
? "暂未搜到相关帖子~"
: type == "homeFollow"
? "关注的人暂无发布哦~"
: type == "homeFriend"
? "好友暂无发布哦~"
: "暂无相关帖子~"
}}
</div>
</div>
</div>
<van-popup
v-model:show="showBottom"
round
position="bottom"
:style="{ height: '236px' }"
>
<div class="dealcell" @click="toggleTop">
{{ top_flag == 0 ? "置顶" : "取消置顶" }}
</div>
<div class="dealcell" @click="goPublic">编辑</div>
<div class="dealcell" @click="confirmDel">删除</div>
<div class="ba"></div>
<div class="dealcell" @click="showBottom = false">取消</div>
</van-popup>
</van-list>
</van-pull-refresh>
</template>
<script>
import img_host from "@/utils/imgHost";
import clamp from "./clamp";
import moment from "moment";
import { Dialog } from "vant";
import { throttle } from "../utils/debounce";
import cookie from "../utils/cookie";
moment.locale("zh-cn");
export default {
components: {
clamp,
},
filters: {
fromTimeNow(val) {
return moment(val).fromNow();
},
},
mounted(){
this.$toast.allowMultiple();
},
data() {
return {
img_host: img_host,
finished: false,
loading: false,
pull_loading: false,
page: 0,
list: [],
top_flag: 0,
showBottom: false,
dealIndex: 0,
};
},
props: {
keyWord: {
type: String,
default: "",
},
patient_uuid: {
type: String,
default: "",
},
type: {
type: String,
default: "",
},
topic_id:{
type: String,
default: "",
},
api: {
type: Function,
},
},
watch: {
keyWord(newVal, oldVal) {
if (newVal) {
if(this.type == "searchBytitle"){
this.freshList();
}
}
},
},
methods: {
closeBottom(){
this.showBottom = false;
},
goPage(uuid) {
let cur_uuid = cookie.readCookie("patient_uuid");
if (uuid == cur_uuid) {
this.$router.push({
path: "/mypage",
});
} else {
this.$router.push({
path: "/patientpage",
query: {
patient_uuid: uuid,
},
});
}
},
confirmDel() {
this.showBottom = false;
Dialog.confirm({
title: "温馨提示",
className: "mydialog",
message: "是否删除该帖子?",
})
.then(() => {
this.handelDelTopic();
})
.catch(() => {
// on cancel
});
},
openMore(index, gandanhome_id, top_flag) {
this.top_flag = top_flag;
this.gandanhome_id = gandanhome_id;
this.dealIndex = index;
this.showBottom = true;
},
toggleTop() {
this.top_flag == 0 ? this.handleTop() : this.handleCancelTop();
this.showBottom = false;
},
goPublic() {
this.showBottom = false;
this.$router.push({
path: "/publish",
query: {
gandanhome_id: this.gandanhome_id,
},
});
},
handelDelTopic() {
this.$api
.delTopic({
gandanhome_id: this.gandanhome_id,
})
.then((rep) => {
let result = rep.data;
if (result.code == 200) {
this.list.splice(this.dealIndex, 1);
this.$toast({
duration: 1000,
message: "删除成功",
});
} else {
this.$toast({
duration: 1000,
message: rep.data.message,
});
}
});
},
handleTop() {
this.$api
.setTop({
gandanhome_id: this.gandanhome_id,
})
.then((rep) => {
let result = rep.data;
if (result.code == 200) {
this.$set(this.list[this.dealIndex], "top_flag", 1);
this.$toast({
duration: 1000,
message: "置顶成功",
});
this.freshList();
} else {
this.$toast({
duration: 1000,
message: rep.data.message,
});
}
});
},
handleCompleteTask(id) {
this.$api
.addGrowRecord({
task_unique_id: id,
})
.then((rep) => {
var result = rep.data;
if (result.code == 200) {
if(result.data){
this.$toast({
duration: 2000,
message:'成长值+1',
});
}
}
})
.catch((error) => {
console.log(error);
});
},
handleCancelTop() {
this.$api
.cancelSetTop({
gandanhome_id: this.gandanhome_id,
})
.then((rep) => {
let result = rep.data;
if (result.code == 200) {
this.$set(this.list[this.dealIndex], "top_flag", 0);
this.$toast({
duration: 1000,
message: "已取消置顶",
});
this.freshList();
} else {
this.$toast({
duration: 1000,
message: rep.data.message,
});
}
});
},
onLoad() {
if (this.type == "searchBytitle") {
let str = this.keyWord.replace(/\s+/g, "");
if (str == "") {
this.page = 1;
this.list = [];
this.loading = false;
this.finished = false;
// this.$toast({
// duration: 1000,
// message: '',
// });
return false;
}
}
this.page++;
this.handleList();
},
goDetail(id) {
this.$router.push({
path: "talkDetail",
query: {
id: id,
},
});
},
onRefresh(){
this.freshList()
},
freshList: throttle(function () {
if (this.type == "searchBytitle") {
if (this.keyWord.replace(/\s+/g, "") == "") {
this.page = 1;
this.list = [];
this.loading = false;
this.finished = false;
// this.$toast({
// duration: 1000,
// message: '',
// });
return false;
}
}
this.page = 1;
this.pull_loading=false
this.list = [];
this.loading = false;
this.finished = false;
this.handleList();
}, 500),
handleList() {
let jsonData = null;
if (this.type == "searchBytitle") {
if (this.keyWord.replace(/\s+/g, "") == "") {
this.page = 1;
this.list = [];
this.loading = false;
this.finished = false;
// this.$toast({
// duration: 1000,
// message: '',
// });
return false;
}
jsonData = {
type: 1,
keyWord: this.keyWord,
page: this.page,
page_size: 10,
};
}else if(this.type =='ralationTopic'){
jsonData = {
topic_id: this.topic_id,
page: this.page,
page_size: 10,
};
} else if (this.type == "patient") {
jsonData = {
patient_uuid: this.patient_uuid,
page: this.page,
page_size: 10,
};
} else {
jsonData = {
page: this.page,
page_size: 10,
};
}
this.api(jsonData).then((rep) => {
var result = rep.data;
this.loading = false;
if (result.code == 200) {
this.list = this.list.concat(result.data.list);
if (result.data.list.length < 10) {
this.finished = true;
}
if (this.type == "homeFollow" && this.list.length > 0) {
this.$emit("getMyFollow", true);
}
} else {
this.$toast({
duration: 1000,
message: rep.data.message,
});
}
});
},
transforPages(arr, num) {
const pages = [];
arr.forEach((item, index) => {
const page = Math.floor(index / num);
if (!pages[page]) {
pages[page] = [];
}
pages[page].push(item);
});
return pages;
},
handleCancelCollect(index, gandanhome_id) {
this.$api
.cancelCollect({
gandanhome_id: gandanhome_id,
})
.then((rep) => {
let result = rep.data;
if (result.code == 200) {
this.$set(this.list[index], "is_collect", 0);
this.$toast({
duration: 1000,
message: "已取消收藏",
});
} else {
this.$toast({
duration: 1000,
message: rep.data.message,
});
}
});
},
toggleCollect(iscollect, index, gandanhome_id) {
iscollect == 0
? this.handleCollect(index, gandanhome_id)
: this.handleCancelCollect(index, gandanhome_id);
},
handleCollect(index, gandanhome_id) {
this.$api
.collect({
gandanhome_id: gandanhome_id,
})
.then((rep) => {
let result = rep.data;
if (result.code == 200) {
this.$set(this.list[index], "is_collect", 1);
this.$toast({
duration: 1000,
message: "收藏成功",
});
this.handleCompleteTask('task_add_collect')
} else {
this.$toast({
duration: 1000,
message: rep.data.message,
});
}
});
},
toggleLike(isLike, index, gandanhome_id, likeNum) {
isLike == 0
? this.handleLike(gandanhome_id, index, likeNum)
: this.handleCancelLike(gandanhome_id, index, likeNum);
},
handleCancelLike(gandanhome_id, index, likeNum) {
this.$api
.cancelLike({
gandanhome_id: gandanhome_id,
})
.then((rep) => {
let result = rep.data;
if (result.code == 200) {
this.$set(this.list[index], "isLike", 0);
this.$set(this.list[index], "likeNum", likeNum - 1);
this.$toast({
duration: 1000,
message: "已取消点赞",
});
} else {
this.$toast({
duration: 1000,
message: rep.data.message,
});
}
});
},
handleLike(gandanhome_id, index, likeNum) {
this.$api
.like({
gandanhome_id: gandanhome_id,
})
.then((rep) => {
let result = rep.data;
if (result.code == 200) {
this.$set(this.list[index], "isLike", 1);
this.$set(this.list[index], "likeNum", likeNum + 1);
this.$toast({
duration: 1000,
message: "点赞成功",
});
this.handleCompleteTask('task_add_collect')
} else {
this.$toast({
duration: 1000,
message: rep.data.message,
});
}
});
},
},
};
</script>
<style scoped>
.info .time {
font-size: 14px;
color: #666;
}
.swiperbox {
margin-top: 10px;
border-radius: 4px;
overflow: hidden;
}
.swiperbox .van-swipe {
width: 100%;
}
.wraper >>> .van-swipe__indicator {
width: 9px;
height: 9px;
opacity: 1;
background-color: #fff;
}
.swiperbox>>> .van-swipe-item{
display: flex;
justify-content: center;
overflow: hidden;
}
.swiperImg {
margin:0 auto;
width:auto;
opacity: 1;
position: relative;
height:142px;
object-fit: contain;
}
.swipebg{
top:0;
height:142px;
position:absolute;
width: 100%;
opacity: 0.5;
object-fit: contain;
}
.swipebg .swiperImg{
width:100%;
height:auto;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.settop {
width: 35px;
height: 15px;
font-size: 12px;
text-align: center;
line-height: 15px;
margin-left: 5px;
background: linear-gradient(318deg, #3cc7c0 0%, #3cc7c0 0%, #6ef0e9 100%);
border-radius: 8px;
color: #fff;
}
.settop span {
font-size: 12px;
display: block;
transform: scale(0.8);
}
.ba {
width: 100%;
height: 8px;
background: #f3f3f3;
}
.dealcell {
display: flex;
align-items: center;
height: 56px;
font-size: 16px;
color: rgba(0, 0, 0, 0.9);
justify-content: center;
border-bottom: 1px solid #efefef;
}
.dealcell:last-child {
border-bottom: none;
}
.more {
font-size: 16px;
color: #000;
}
.head[lazy="error"] {
background: url("../assets/head.png") no-repeat center center;
background-size: cover;
}
.imgbox {
width: 100%;
}
.imgbox.tworow {
width: 66.66%;
}
.imgbox .imgrow {
margin-top: 10px;
display: flex;
/* justify-content: space-between; */
flex-wrap: wrap;
}
.imgcell {
flex: 1;
max-width: 33.33%;
position: relative;
}
.imgbox .imgcell:nth-child(2n) {
text-align: center;
}
.imgbox .imgcell:nth-child(3n) {
text-align: right;
}
.tulist {
padding-top: 15px;
height: calc(100vh - 180px);
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
.tool {
margin-top: 12px;
display: flex;
align-items: center;
justify-content: space-between;
}
.address {
font-size: 12px;
color: #999999;
}
.zan_comment {
min-width: 75px;
display: flex;
font-size: 14px;
color: #333333;
justify-content: space-between;
}
.zan {
display: flex;
align-items: center;
}
.collectbox {
margin: 0 20px;
}
.zan img,
.comment img,
.collectbox img {
width: 20px;
height: 20px;
}
.comment {
align-items: center;
display: flex;
}
.msgcell {
background: #fff;
padding: 8px 15px;
border-bottom: 5px solid #f4f4f4;
}
.namebox {
width:100%;
display: flex;
justify-content: space-between;
}
.namebox .info {
display: flex;
flex: 1;
height: 40px;
justify-content: space-between;
margin-right: 10px;
margin-left: 6px;
flex-direction: column;
}
.namebox .left {
display: flex;
flex: 1;
align-items: center;
}
.msgcell .leftcon {
display: flex;
align-items: center;
}
.msgcell .avastar {
width: 43px;
height: 43px;
border-radius: 50%;
}
.name_level {
display: flex;
align-items: center;
}
.name_level .name {
white-space: nowrap;
font-size: 18px;
color: #000000;
}
.level {
margin-left: 5px;
margin-right: 4px;
height: 15px;
}
.level:first-child {
margin-left: 4px;
}
.listbox {
margin-top: 10px;
}
.listbox .cell {
margin: 0 15px;
padding: 13px 10px;
background: #fff;
display: flex;
align-items: center;
border-radius: 4px;
justify-content: space-between;
border-bottom: 1px solid #efefef;
}
.listbox .cell .head {
width: 50px;
height: 50px;
border-radius: 50%;
}
.fllow {
width: 68px;
height: 25px;
display: flex;
color: #999;
font-size: 12px;
align-items: center;
justify-content: center;
background: #ffffff;
border-radius: 25px;
border: 1px solid #999999;
}
.listbox .cell .name {
font-size: 16px;
color: #333333;
margin-left: 24px;
}
.listbox .cell:last-child {
border-bottom: none;
}
.listbox .cell .left {
display: flex;
align-items: center;
}
.msglist .title {
line-height: 23px;
margin-top: 12px;
font-size: 17px;
color: #000000;
}
.msglist .msgdetail {
margin-top: 4px;
font-size: 15px;
line-height: 22px;
color: #666666;
}
.msglist .msgdetail span {
color: #3cc7c0;
}
.msglist {
background: transparent;
}
</style>

68
src/components/totop.vue Normal file
View File

@ -0,0 +1,68 @@
<template>
<!-- <div> -->
<div class="goTop" v-show="goTopShow" @click="goTop">
<img src="../assets/top.png" alt="">
</div>
<!-- </div> -->
</template>
<script setup>
import { ref, watch,onMounted,onUnmounted } from "vue"
const scrollTop = ref(0);
const goTopShow = ref(false);
watch(scrollTop, () => {
if (scrollTop.value > 250) {
goTopShow.value = true;
} else {
goTopShow.value = false;
}
})
const handleScroll = () => {
scrollTop.value = document.documentElement.scrollTop || document.body.scrollTop;
if (scrollTop.value > 250) {
goTopShow.value = true;
}else{
goTopShow.value = false;
}
};
const goTop = () => {
let timer = setInterval(() => {
let ispeed = Math.floor(-scrollTop.value / 5)
document.body.scrollTop = scrollTop.value + ispeed
document.documentElement.scrollTop = scrollTop.value + ispeed
if (scrollTop.value === 0) {
clearInterval(timer)
}
}, 20)
};
onMounted(() => {
window.addEventListener("scroll", handleScroll);
});
onUnmounted(() => {
window.removeEventListener("scroll", handleScroll);
});
</script>
<style scoped>
.goTop {
position: fixed;
width: 69px;
height: 30px;
right:0px;
bottom:135px;
cursor: pointer;
z-index:999;
overflow: hidden;
border-radius: 15px 0 0 15px;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.12);
}
.goTop img {
width: 100%;
}
</style>

33
src/main.js Normal file
View File

@ -0,0 +1,33 @@
import { createApp } from 'vue'
import 'reset-css'
import 'vant/lib/index.css';
import App from './App.vue'
import router from "@/router/index.js"
import Loading from "@/components/loading/index"
import { Image as VanImage } from 'vant';
import {Toast,ImagePreview,Lazyload,Overlay, List,Tab, Tabs,PullRefresh,CountDown,Checkbox, CheckboxGroup, RadioGroup, Radio,Field } from 'vant';
import VConsole from 'vconsole';
if(import.meta.env.MODE!=='production') {
const vConsole = new VConsole();
};
const app = createApp(App);
app.config.compilerOptions.isCustomElement = (tag) => { return tag.startsWith('wx-open-launch-weapp') }
app.use(router);
app.use(VanImage);
app.use(List);
app.use(Tab);
app.use(Tabs);
app.use(PullRefresh);
app.use(CountDown);
app.use(Checkbox);
app.use(CheckboxGroup)
app.use(Radio);
app.use(RadioGroup);
app.use(Field);
app.use(Overlay);
app.use(Toast);
app.use(Lazyload );
app.use(ImagePreview)
app.use(Loading)
app.mount('#app')

149
src/router/index.js Normal file
View File

@ -0,0 +1,149 @@
import {
createWebHashHistory,
createRouter,
createWebHistory
} from 'vue-router';
import api from '../api/user.js';
import cookie from '@/utils/cookie.js';
import host from "../utils/host.js"
const routes = [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
name: 'home',
component: () => import('@/views/home.vue')
},
{
path: '/caseList',
name: 'caseList',
component: () => import('@/views/caseList.vue')
},
{
path: '/caseIntro',
name: 'caseIntro',
component: () => import('@/views/caseIntro.vue')
},
{
path: '/caseDetail',
name: 'caseDetail',
component: () => import('@/views/caseDetail.vue')
},
{
path: '/comment',
name: 'comment',
component: () => import('@/views/comment.vue')
},
{
path: '/result',
name: 'result',
component: () => import('@/views/result.vue')
},
];
const router = createRouter({
// createWebHashHistory URL 带井号
// createWebHistory URL 去井号
history:createWebHistory('web'),
routes: routes,
});
router.beforeEach(async(to, from, next) => {
if(to.path!=='/caseIntro' || to.path!=='/result'){
document.title = '互动病例';
}
let source=to.query.source
// 权限验证逻辑
if (source == 3) {
if(to.path!=='/result'){
localStorage.setItem('token_other','')
}
let token=localStorage.getItem('token_other');
if(!token){
const {data,code}=await api.login({
source:3,
platform_key:to.query.platform_key,
user_iden:to.query.user_iden,
mobile_encryption:to.query.mobile_encryption,
doctor_name:to.query.doctor_name,
hospital_name:to.query.hospital_name,
department_name:to.query.department_name,
address:to.query.address,
title:to.query.title,
share_user_id:to.query.share_user_id,
is_white:to.query.is_white,
project_id:to.query.project_id
})
if(code==200){
localStorage.setItem('token_other',data.token);
localStorage.setItem('userInfo',JSON.stringify(data));
localStorage.setItem('source',3);
next();
}
}else{
next();
}
} else {
//localStorage.setItem('token_gandan','eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTg5NDI3NjIyOTg5NzM5MjEyOCIsInBsYXRmb3JtX2lkIjoiMSIsImV4cCI6MTc0MTMyNzk2OSwibmJmIjoxNzQxMDY4NzY5LCJpYXQiOjE3NDEwNjg3Njl9.xItE3sfBb438X5vZUOWptzahKKUutu0QPFrXo0pi53Y');
let token='';
let token_other=localStorage.getItem('token_other');
let token_gandan=localStorage.getItem('token_gandan');
if(source==3){
if(token_other){
token=token_other;
}
}else{
if(token_gandan){
token=token_gandan;
}
}
if(!token){
let unionid='';
let userInfo=cookie.readCookie('wechat_user_info');
let videoToken=cookie.readCookie('video_token');
if(userInfo){
let json=JSON.parse(userInfo);
unionid=json.unionid;
const {data,code}=await api.login({
source:2,
platform_key:123456,
unionid:unionid?unionid:'',
user_iden:to.query.uuid?to.query.uuid:'',
})
if(code==200){
localStorage.setItem('token_gandan',data.token);
localStorage.setItem('userInfo',JSON.stringify(data));
localStorage.setItem('source','');
next();
}
}else if(videoToken){
const {data,code}=await api.login({
source:2,
platform_key:123456,
unionid:unionid?unionid:'',
user_iden:to.query.uuid?to.query.uuid:'',
token:videoToken
})
if(code==200){
localStorage.setItem('token_gandan',data.token);
localStorage.setItem('userInfo',JSON.stringify(data));
localStorage.setItem('source','');
next();
}
}else{
let url='https://dev-wx.igandan.com';
if(host.indexOf('//dev-caseplatform.igandan.com')==-1){
url='https://wx.igandan.com'
}
window.location.href = url+"/hcp/Signup2020online_tologin?back_url="+host+"/web/home"
}
}else{
next();
}
}
});
export default router;

5
src/style.css Normal file
View File

@ -0,0 +1,5 @@
body{
background:#fff;
}

249
src/utils/HCPUtils.js Normal file
View File

@ -0,0 +1,249 @@
/**
* HCP 整合js
*/
//'use strict';
import $ from 'jquery';
import {Base64} from 'js-base64';
var localStorage_name = "watchlive_current_user"
localStorage.setItem("watch_live_localStorage_name",localStorage_name);
window.finsihed=null;
var current_user = {
id:"",
name:'',
avatar:''
};
function getUrlParam(url, paramName) {
const reg = new RegExp('(^|&)' + paramName + '=([^&]*)(&|$)', 'i');
const result = url.slice(url.indexOf('?') + 1).match(reg);
if (result !== null) {
return decodeURIComponent(result[2]);
}
return null;
}
function init_current_user(){
console.log("init_current_user")
current_user = JSON.parse(localStorage.getItem(localStorage_name));
if(current_user == undefined || current_user == null){
current_user =
{
id: timestamp(),
name: Base64.encode("游客"+timestamp()),
avatar: "https://doc.igandan.com/app/html/img/2016/20160714132557.png"
}
}
return current_user;
}
var timestamp = function(){
return new Date().getTime();
}
const HCP = {
options: {
privilege_async: false, //获取权限时ajax同步异步 ajax 默认是true异步false同步。)
user_info_async: false, //获取用户信息时ajax同步异步 ajax 默认是true异步false同步。)
add_sources_async: true, //获取权限时ajax同步异步 ajax 默认是true异步false同步。)
},
current_user: init_current_user(),//当前用户信息
privilege: {},//权限信息
//获取属性值
getOption: function (name){
return HCP.options[name]
},
//设置属性值
setOption: function(name,value){
return HCP.options[name] = value
},
//检查HCP权限
getHCPPrivilege: async function(gdxz_unionid,from,project_id){
let baseUrl = '';
var path=location.href;
if (path.indexOf("//wx.igandan.com") > 1 || path.indexOf("oss")>1) {
baseUrl='https://wx.igandan.com'
} else {
baseUrl='https://dev-wx.igandan.com'
}
await $.ajax({
url: baseUrl+"/hcp/getHCPPrivilege",
//url:'https://www.fastmock.site/mock/ebb1956b65d6078940cacb295a06be27/mock/mock',
async: HCP.options.privilege_async,
data: {"unionid":gdxz_unionid, "from": from, "project_id":project_id},
success:function(res){// 获取用户信息,后端可首先通过cookie,session等判断,没有信息则通过code获取
//alert(JSON.stringify(res))
console.log(res);
if (res.code == 60001) {
window.finsihed=true;
//alert("getHCPPrivilege");
console.log("授权成功")
//console.log(HCP.privilege.HCP_INFO);
localStorage.setItem("userName", Base64.decode(res.HCP_INFO.nickname));
localStorage.setItem("uuid", res.HCP_INFO.user_uuid);
localStorage.setItem("avatar", res.HCP_INFO.avatar);
} else if (res.code == 60002) {
Dialog.alert({
title: '提示信息',
message: '您暂时没有权限,无法浏览,请关闭页面',
}).then(() => {
// on close
});
} else {
//alert("222");
//直接走登录页面
HCP.gologin();
}
}
})
},
//添加HCP 访问记录
addSources: function(openid,sourcesId,sourcesTitle, sourcesType,userid,userType){
$.ajax({
url: "/hcp/addSources",
async: HCP.options.add_sources_async,
data: {
openid:openid,
sourcesId:sourcesId,
sourcesTitle:sourcesTitle,
sourcesType:sourcesType,
userid:userid,
userType:userType
},
success:function(res){
//...
}
})
},
//转到login页面
gologin: function (){
let baseUrl = '';
var path=location.href;
if (path.indexOf("//wx.igandan.com") > 1 || path.indexOf("oss")>1) {
baseUrl='https://wx.igandan.com'
} else {
baseUrl='https://dev-wx.igandan.com'
}
var path=location.href.split('#')[0];
window.location.href =baseUrl+"/hcp/Signup2020online_tologin?back_url="+HCP.htmlDecodeByRegExp(path);
},
//反转义
htmlDecodeByRegExp: function (str){
var s = "";
if(str.length == 0) return "";
s = str.replace(/&amp;/g,"&");
s = s.replace(/&lt;/g,"<");
s = s.replace(/&gt;/g,">");
s = s.replace(/&nbsp;/g," ");
s = s.replace(/'/g,"\'");
s = s.replace(/"/g,"\"");
return s;
} ,
//判断字符串是否为空
isNull: function (str) {
    if (typeof (str) == "undefined" || str == "undefined" || str == null || str.replace(" ") === "")
        return true;
    else
        return false;
},
//获取cookie
getCookie: function (name){
var arr;
var reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
if (arr = document.cookie.match(reg))
return unescape(arr[2]);
else
return null;
// var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");
// if(arr=document.cookie.match(reg))
// return unescape(arr[2]);
// else
// return null;
},
//设置cookie
setCookie: function (cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires;
},
//是否为微信
is_weixin: function (){
var ua = navigator.userAgent.toLowerCase();
if(ua.match(/MicroMessenger/i)=="micromessenger") {
return true;
} else {
return false;
}
},
//获得时间戳
timestamp: function(){
return new Date().getTime();
},
//获取微信用户信息
getUserInfo: function (){
let code =getUrlParam("code") ;// 截取url上的code ,可能没有,则返回''空字符串!HCP.getCookie("gdxz_unionid")?'':
var path=location.href; //.split('#')[0];
var url="";
var appid="";
if (path.indexOf("//wx.igandan.com") > 1) {
url="https://wx.igandan.com/";
appid="wxa4132ef4701ac5e4";
} else {
url="https://dev-wx.igandan.com/";
appid="wx68affaa9d23528f8";
}
// if(path.indexOf(".igandan.org")>1){
// url="https://twx.igandan.org/";
// appid="wx68affaa9d23528f8";
// }else{
// url="https://wx.igandan.com/";
// appid="wxa4132ef4701ac5e4";
// }
const ishttps = 'https:' == document.location.protocol ? true : false;
if(!ishttps){
url = url.toString().replace("https","http")
};
$.ajax({
url: url+"AuthorizeURL/getUserInfo",
data: {appid:appid, code: code, scope: "snsapi_userinfo"},
async: HCP.options.user_info_async,
success:function(res){// 获取用户信息,后端可首先通过cookie,session等判断,没有信息则通过code获取
if(res.code == 200 ){
var user_info = JSON.parse(res.user_info.json)
current_user = {
id: user_info.openid,
name: Base64.encode(user_info.nickname),
avatar: user_info.headimgurl,
unionid: user_info.unionid,
};
HCP.current_user = current_user;
HCP.setCookie("gdxz_unionid", user_info.unionid, 1);
localStorage.setItem(localStorage_name,JSON.stringify(current_user));
//alert("即将重新加载")
window.location.reload();
//HCP.getHCPPrivilege(user_info.unionid, "wechat", "")
// var currentHref=sessionStorage.getItem("currentHref");
// if(currentHref){
// window.location.href=currentHref;
// }
}else if(res.code == 202){
// 这个redirectUrl用 当前页路径或者tof.fullPath(将要进入的路径)
//var myurl="https://" + window.location.host +"/excellencourse/index.htm?"+window.location.href.split("?")[1];
var redirectUrl =window.location.href;
//sessionStorage.setItem("currentHref",redirectUrl)
redirectUrl = encodeURIComponent(redirectUrl);
var oauth2_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
var appid = res.appid;
window.location.href = oauth2_url.replace("APPID",appid).replace("REDIRECT_URI",redirectUrl);
//window.location.reload();
}
}
})
}
}
export default HCP

243
src/utils/axios.js Normal file
View File

@ -0,0 +1,243 @@
import axios from "axios";
import { showToast,showDialog } from 'vant';
import host from "./host"
import wx from 'jweixin-1.6.0'
// 1. 创建axios实例
const instance = axios.create({
// 接口
baseURL: import.meta.env.VITE_BASE_URL,
// 超时时间
timeout: 100000,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
}
});
// 2.请求拦截
instance.interceptors.request.use(
config => {
let token='';
let token_gandan = localStorage.getItem('token_gandan');
let token_other = localStorage.getItem('token_other');
const params = new URLSearchParams(window.location.search);
let source=params.get('source');
if(source==3){
if(token_other){
token=token_other;
}
}else{
if(token_gandan){
token=token_gandan;
}
}
if (token) {
config.headers['Authorization'] ='Bearer '+token
}
return config;
},
error => {
// 请求发生错误,抛出异常
Promise.reject(error);
}
);
// 3.响应拦截
instance.interceptors.response.use(
res => {
const params = new URLSearchParams(window.location.search);
let source=params.get('source');
if (res.data && res.data.code === 200 ) {
//let status=res.data.code;
return res.data;
}else{
let status=res.data.code;
switch (status) {
case 201:
localStorage.clear();
showToast("该账户已被禁用");
break;
case -1:
if(res.data.data.source==3){
showDialog({
width: '80%',
title: '提示',
message: '跳转失败,请返回重试',
}).then(() => {
wx.miniProgram.navigateBack({
fail:function(res) {
wx.miniProgram.switchTab({
url: '/pages/media_home/index'
})
}
});
// wx.miniProgram.switchTab({
// url: '/pages/media_home/index',
// success(res) {
// consoe.log('跳转成功');
// },
// fail(res) {
// consoe.log('跳转失败');
// }
// });
});
}else{
showToast(res.data.message);
}
return res.data;
break;
case -2:
localStorage.clear();
//showToast('该账户未注册');
let url='https://dev-wx.igandan.com';
if(host.indexOf('//dev-caseplatform.igandan.com')==-1){
url='https://wx.igandan.com'
}
window.location.href = url+"/hcp/Signup2020online_tologin?back_url="+host+"/web/home"
break;
case 400:
showToast("请求错误");
break;
case 401:
showToast("未授权,请重新登录");
if(source==3){
localStorage.clear();
wx.miniProgram.navigateBack({
fail:function(res) {
wx.miniProgram.switchTab({
url: '/pages/media_home/index'
})
}
});
// wx.miniProgram.switchTab({
// url: '/pages/media_home/index',
// success(res) {
// consoe.log('跳转成功');
// },
// fail(res) {
// consoe.log('跳转失败');
// }
// });
}else{
localStorage.clear();
window.location.reload();
}
break;
case 403:
showToast("拒绝访问");
break;
case 404:
showToast("请求错误,未找到相应的资源");
break;
case 405:
showToast("未授权,请重新登录");
if(source==3){
localStorage.clear();
wx.miniProgram.navigateBack({
fail:function(res) {
wx.miniProgram.switchTab({
url: '/pages/media_home/index'
})
}
});
// wx.miniProgram.switchTab({
// url: '/pages/media_home/index',
// success(res) {
// consoe.log('跳转成功');
// },
// fail(res) {
// consoe.log('跳转失败');
// }
// });
}else{
localStorage.clear();
window.location.reload();
}
break;
case 408:
showToast("请求超时");
break;
case 500:
showToast("服务器内部错误");
break;
case 501:
showToast("网络未实现");
break;
case 502:
showToast("网络错误");
break;
case 503:
showToast("服务不可用");
break;
case 504:
showToast("网络超时");
break;
case 505:
showToast("HTTP版本不支持该请求");
break;
default:
return res.data;
//ElMessage.error("请求失败");
}
//return res;
}
},
error => {
if (error && error.response) {
const status = error.response.status
switch (status) {
case 201:
localStorage.clear()
showToast("该账户已被禁用");
break;
case 400:
showToast("请求错误");
break;
case 401:
localStorage.clear()
window.location.reload();
showToast("未授权,请重新登录");
break;
case 403:
showToast("拒绝访问");
break;
case 404:
showToast("请求错误,未找到相应的资源");
break;
case 408:
showToast("请求超时");
break;
case 500:
showToast("服务器内部错误");
break;
case 501:
showToast("网络未实现");
break;
case 502:
showToast("网络错误");
break;
case 503:
showToast("服务不可用");
break;
case 504:
showToast("网络超时");
break;
case 505:
showToast("HTTP版本不支持该请求");
break;
default:
showToast("请求失败");
}
} else {
if (JSON.stringify(error).includes("timeout")) {
showToast("服务器响应超时,请刷新页面");
}
showToast("连接服务器失败");
}
return Promise.reject(error);
}
);
// 4.导出 axios 实例
export default instance;

33
src/utils/cookie.js Normal file
View File

@ -0,0 +1,33 @@
const cookie = {
//写cookies
setCookie: function(name, value) {
//let days = 0.5;
let exp = new Date();
exp.setTime(exp.getTime() + 15*60*1000)
// exp.setTime(exp.getTime() + days*24*60*60*1000)
document.cookie = name + '=' + escape (value) + ';expires=' + exp.toGMTString()
},
//读取cookies
readCookie: function (name) {
let arr = null
let reg = new RegExp('(^| )'+name+'=([^;]*)(;|$)')
if (document.cookie && (arr = document.cookie.match(reg))) {
return unescape(arr[2])
} else {
return null;
}
},
//删除cookies
delCookie: function (name) {
let cval = this.readCookie(name);
var domain = '.igandan.com';
if (cval!=null) {
document.cookie = name + '=;expires=' + (new Date(0)).toGMTString()+";path=/;domain="+domain
}
}
}
export default cookie

60
src/utils/getUrlParam.js Normal file
View File

@ -0,0 +1,60 @@
/*
* @Author: Administrator
* @Date: 2017-10-27 11:30:07
* @Last Modified by: Administrator
* @Last Modified time: 2017-10-27 11:30:36
*/
var UrlParm = function() { // url参数
var data, index;
(function init() {
data = [];
index = {};
var u = window.location.search.substr(1);
if (u != '') {
var parms = decodeURIComponent(u).split('&');
for (var i = 0, len = parms.length; i < len; i++) {
if (parms[i] != '') {
var p = parms[i].split("=");
if (p.length == 1 || (p.length == 2 && p[1] == '')) {// p | p=
data.push(['']);
index[p[0]] = data.length - 1;
} else if (typeof(p[0]) == 'undefined' || p[0] == '') { // =c | =
data[0] = [p[1]];
} else if (typeof(index[p[0]]) == 'undefined') { // c=aaa
data.push([p[1]]);
index[p[0]] = data.length - 1;
} else {// c=aaa
data[index[p[0]]].push(p[1]);
}
}
}
}
})();
return {
// 获得参数,类似request.getParameter()
parm : function(o) { // o: 参数名或者参数次序
try {
return (typeof(o) == 'number' ? data[o][0] : data[index[o]][0]);
} catch (e) {
}
},
//获得参数组, 类似request.getParameterValues()
parmValues : function(o) { // o: 参数名或者参数次序
try {
return (typeof(o) == 'number' ? data[o] : data[index[o]]);
} catch (e) {}
},
//是否含有parmName参数
hasParm : function(parmName) {
return typeof(parmName) == 'string' ? typeof(index[parmName]) != 'undefined' : false;
},
// 获得参数Map ,类似request.getParameterMap()
parmMap : function() {
var map = {};
try {
for (var p in index) { map[p] = data[index[p]]; }
} catch (e) {}
return map;
}
}
}();

35
src/utils/hcpRight.js Normal file
View File

@ -0,0 +1,35 @@
import HCP from "./HCPUtils.js"
import host from "./host.js"
var gdxz_unionid = HCP.getCookie("gdxz_unionid");
var from;
const hcpRight = async function () {
if (HCP.is_weixin()) {
from = "wechat";
if (HCP.isNull(gdxz_unionid)) {
//await HCP.getUserInfo();
window.location.href = "https://dev-wx.igandan.com/hcp/Signup2020online_tologin?back_url=https://dev-caseplatform.igandan.com/web/home"
// var redirectUrl = encodeURIComponent(window.location.href);
// let url='https://dev-wx.igandan.com';
// if(host.indexOf('//dev-caseplatform.igandan.com')==-1){
// url='https://wx.igandan.com'
// }
// window.location.href = url+"/AuthorizeURL/grant?back_url=" + redirectUrl;
}else{
await HCP.getHCPPrivilege(gdxz_unionid, from, "")
}
} else {
gdxz_unionid = "";
from = "gdxzAPP";
HCP.getHCPPrivilege(gdxz_unionid, from, "");
};
};
export default hcpRight;

9
src/utils/host.js Normal file
View File

@ -0,0 +1,9 @@
let host='';
let path=window.location.href;
if (path.indexOf("//prod-caseplatform.igandan.com") > 1 ) {
host = "https://prod-caseplatform.igandan.com";
} else {
host = "https://dev-caseplatform.igandan.com";
}
export default host;

53
src/utils/http.js Normal file
View File

@ -0,0 +1,53 @@
import instance from "./axios"
const post = (url, data,header='application/x-www-form-urlencoded') => {
return new Promise((resolve, reject) => {
instance.post(url, data,{
headers: {
"Content-Type": header,
},
}).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
}
const get = (url, data={}) => {
return new Promise((resolve, reject) => {
instance.get(url, { params: data }).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
}
const put = (url, data,header='application/x-www-form-urlencoded') => {
return new Promise((resolve, reject) => {
instance.put(url, data,{
headers: {
"Content-Type": header,
},
}).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
}
const del = (url, data,header='application/x-www-form-urlencoded') => {
return new Promise((resolve, reject) => {
instance.delete(url,data, {headers: {
"Content-Type": header,
}}).then(res => {
resolve(res)
}).catch(err => {
reject(err)
})
})
}
export default {
post, get, put, del
}

45
src/utils/openTag.js Normal file
View File

@ -0,0 +1,45 @@
import wx from 'weixin-js-sdk'
import axios from 'axios'
function openTag(){
var path = location.href.split('#')[0];
var url = "";
var appid = "";
if (path.indexOf("//dev") > 1) {
url = "https://dev-app.igandan.com/app/manager/getSignature4bing";
appid = "wx68affaa9d23528f8";
} else {
url = "https://app.igandan.com/app/manager/getSignature4bing";
appid = "wxa4132ef4701ac5e4";
}
axios.get(url, {
params: {
path: encodeURIComponent(window.location.href.split('#')[0]),
appid: appid
}
}).then(json => {
wx.config({
debug: false,
appId: appid,
timestamp: json.data.timestamp,
nonceStr: json.data.nonceStr,
signature: json.data.signature,
jsApiList: ['wx-open-launch-weapp'],
openTagList: ['wx-open-launch-weapp']
});
}).catch((e) => {
console.log('获取数据失败');
});
wx.checkJsApi({
jsApiList: ['wx-open-launch-weapp'], // 需要检测的JS接口列表所有JS接口列表见附录2,
success: function (res) {
console.log(res)
}
});
wx.ready(function () {
console.log('准备好了~~~');
});
}
export default openTag;

9
src/utils/version.js Normal file
View File

@ -0,0 +1,9 @@
let version='';
//let appid = "wx68affaa9d23528f8";
if (import.meta.env.MODE ==='production') {
version = "release";
} else {
version = "trial";
}
export default version;

View File

@ -0,0 +1,94 @@
import wx from 'weixin-js-sdk'
import axios from 'axios'
let share = {
title: "",
desc: "",
link: "",
imgUrl: "",
init: function () {
var path = location.href.split('#')[0];
var url = "";
var appid = "";
if (path.indexOf("//wx.igandan.com") > 1 || path.indexOf("oss")>1) {
url = "https://app.igandan.com/app/manager/getSignature4bing";
appid = "wxa4132ef4701ac5e4";
} else {
url = "https://dev-app.igandan.com/app/manager/getSignature4bing";
appid = "wx68affaa9d23528f8";
}
axios.get(url, {
params: {
path: encodeURIComponent(window.location.href.split('#')[0]),
appid: appid
}
}).then(json => {
wx.config({
debug: false,
appId: appid,
timestamp: json.data.timestamp,
nonceStr: json.data.nonceStr,
signature: json.data.signature,
jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData', 'onMenuShareTimeline', 'onMenuShareAppMessage','chooseImage'],
openTagList: ['wx-open-launch-app','chooseImage']
});
}).catch((e) => {
console.log('获取数据失败');
});
wx.checkJsApi({
jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData', 'onMenuShareTimeline', 'onMenuShareAppMessage'], // 需要检测的JS接口列表所有JS接口列表见附录2,
success: function (res) {
console.log(res)
}
});
wx.ready(function () {
wx.updateAppMessageShareData({
title: share.title,
desc: share.desc,
link: share.link,
imgUrl: share.imgUrl,
success: function () {}
})
wx.updateTimelineShareData({
title: share.title,
desc: share.desc,
link: share.link,
imgUrl: share.imgUrl,
success: function () {}
})
wx.onMenuShareTimeline({
title: share.title,
desc: share.desc,
link: share.link,
imgUrl: share.imgUrl,
success: function () {}
})
wx.onMenuShareAppMessage({
title: share.title,
desc: share.desc,
link: share.link,
imgUrl: share.imgUrl,
success: function () {}
})
});
}
}
function WXSHARE(title, desc, link, imgUrl) {
share.title = title;
share.desc = desc;
share.link = link;
if (imgUrl == undefined || imgUrl == "") {
imgUrl = "https://doc.igandan.com/app/html/img/2016/20160714132557.png";
}
share.imgUrl = imgUrl;
share.init();
};
export {
WXSHARE
}

422
src/views/caseDetail.vue Normal file
View File

@ -0,0 +1,422 @@
<template>
<div class="main" :id="'pagemain'+pageIndex" v-show="step == pageIndex" >
<img src="../assets/bg.png" alt="" class="bg" />
<div class="dealbox">
<div class="prev" @click="switchPage(pageIndex - 1)">上一步</div>
</div>
<div class="content">
<div class="pagebox">
<div class="page">
<!-- <div class="card" v-html="pageItem.content"></div> -->
<div
class="card"
v-for="(item, index) in pageItem.case_item_model"
:key="index"
>
<div class="titlebox">
<img src="../assets/titlebg.png" alt="" />
<div class="title">{{ item.model_name }}</div>
</div>
<div class="descbox">
<div class="namebox" v-html="item.content"></div>
<!-- <div class="namebox" >
<div class="row">
<div class="left"> ·姓别</div>
<div class="right">
**</div>
</div>
<div class="row">
<div class="left"> ·姓别</div>
<div class="right">
症状:自诉1月前因长期熬夜出现软困乏力无腹痛无恶心呕吐纳差胸闷胸痛无尿频尿痛尿黄无发热无胸闷气紧无胸痛心悸无呼吸困难无皮肤黄染及皮下出血等病后饮食稍差睡眠尚可精神尚可大小便正常
</div>
</div>
</div>-->
</div>
</div>
</div>
<question
v-for="(item, index) in pageItem.case_item_question"
:key="'case' + pageIndex + 'qestion' + item.question_id"
:question="item"
@checkAnswer="checkAnswer"
:pageIndex="pageIndex"
:questionIndex="index"
v-if="pageItem.case_item_question"
></question>
</div>
</div>
<div class="dealbox">
<div class="next" @click="switchPage(pageIndex - 1)">上一步</div>
<div class="prev" @click="goNext(pageIndex + 1)">下一步</div>
</div>
<back></back>
<c-dialog
:message="dialog_message"
ref="cdialog"
:showCancel="false"
></c-dialog>
</div>
<van-image-preview ref="ImagePreview" v-model:show="showImg" closeable :images="imgList" @change="onChange">
</van-image-preview>
</template>
<script setup>
import { ref, reactive } from "vue";
import api from "../api/user.js";
import dayjs from "dayjs";
//import { showImagePreview } from 'vant';
const case_id = ref(null);
const imgList = ref([]);
const position = ref(0);
const showImg = ref(false);
const ImagePreview = ref(null);
const props = defineProps({
pageIndex: {
type: Number,
default: 0,
},
case_id: {
type: String,
default: "",
},
step: {
type: Number,
default: 0,
},
pageNum: {
type: Number,
default: 0,
},
start_time: {
type: String,
default:'',
},
pageItem: {
type: Object,
default: () => ({}),
},
});
const unitAnswer = {
pageIndex: null,
answer: [],
};
const cdialog = ref(null);
const dialog_message = ref("");
watch(
() => props.pageItem,
(newVal) => {
if (newVal.case_item_question && newVal.case_item_question.length > 0) {
for (let i = 0; i < newVal.case_item_question.length; i++) {
unitAnswer.answer[i] = "";
}
}
},
{
immediate: true,
deep: true,
}
);
const emit = defineEmits(["switchPage", "getAllAnswer"]);
const switchPage = (index) => {
emit("switchPage", index);
};
const behaviorRecord = (step = "", start_time = "", end_time = "") => {
api
.behaviorRecord({
case_id: props.case_id,
step: step,
start_time: start_time,
end_time: end_time,
})
.then((res) => {});
};
const checkAnswer = (data) => {
//
unitAnswer.pageIndex = data.pageIndex;
unitAnswer.answer[data.questionIndex] = {
answer: data.answer,
question_type: data.question_type,
question_id: data.questionId,
question_name: data.question_name,
question_answer: data.question_answer,
option_id: data.option_id,
error_tips: data.error_tips,
is_right_next: data.is_right_next,
};
// console.log(unitAnswer);
// allAnswer[data.pageIndex]=unitAnswer.answer;
// console.log(allAnswer);
};
const fomatObj = (obj) => {
let str = "";
for (let key in obj) {
if (str) {
str += `${obj[key]}`;
} else {
str = `${obj[key]}`;
}
}
return str;
};
const passNext = () => {
let answer = unitAnswer.answer;
console.log(answer);
let answerForamt = answer.map((item) => {
if (item.question_type == 2) {
return {
answer: fomatObj(item.answer),
question_type: item.question_type,
question_id: item.question_id,
question_name: item.question_name,
question_answer: item.question_answer,
option_id: item.option_id,
error_tips: item.error_tips,
is_right_next: item.is_right_next,
};
} else {
return { ...item };
}
});
console.log(answerForamt);
if (answerForamt.length == 0 && props.pageItem.case_item_question) {
dialog_message.value = "请回答本页所有问题后再提交";
cdialog.value.openDialog();
//showToast('')
return false;
}
for (let i = 0; i < answerForamt.length; i++) {
if (!answerForamt[i].answer) {
dialog_message.value = "请回答本页第" + (i + 1) + "个问题后再提交";
cdialog.value.openDialog();
//showToast(''+(i+1)+'')
return false;
}
if (answerForamt[i].question_type == 2) {
if (answerForamt[i].answer.length < 2) {
dialog_message.value = answerForamt[i].question_name + "至少选择两项";
cdialog.value.openDialog();
//showToast(''+(i+1)+'')
return false;
}
}
if (
answerForamt[i].answer !== answerForamt[i].question_answer &&
answerForamt[i].is_right_next == 1 &&
answerForamt[i].question_type != 3
) {
//dialog_message.value=props.pageItem.error_tips;
dialog_message.value = answerForamt[i].error_tips;
cdialog.value.openDialog();
return false;
}
}
// alert(unitAnswer.pageIndex);
// allAnswer[unitAnswer.pageIndex]=answerForamt;
// console.log('allAnswer');
// console.log(allAnswer);
emit("getAllAnswer", {
pageIndex: unitAnswer.pageIndex,
answer: answerForamt,
});
return true;
};
const reportQuestion = (start, end) => {
let answer = unitAnswer.answer;
for (let i = 0; i < answer.length; i++) {
behaviorRecord(answer[i].question_name, start, end);
}
};
const goNext = (index) => {
if (passNext()) {
if (index < props.pageNum) {
let nextPageTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
localStorage.setItem("entryPage" + index, nextPageTime);
if (index == 1) {
let start = dayjs(props.start_time).format("YYYY-MM-DD HH:mm:ss");
behaviorRecord("第2页", start, nextPageTime);
reportQuestion(start, nextPageTime);
} else {
let prevPageTime = localStorage.getItem("entryPage" + (index - 1));
behaviorRecord(`${index + 2}`, prevPageTime, nextPageTime);
reportQuestion(prevPageTime, nextPageTime);
}
emit("switchPage", index);
} else {
let nextPageTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
localStorage.setItem("entryPage" + index, nextPageTime);
localStorage.setItem("lastPage", nextPageTime);
if (index == 1 && props.pageNum == 1) {
let start = dayjs(props.start_time).format("YYYY-MM-DD HH:mm:ss");
behaviorRecord("第2页", start, nextPageTime);
reportQuestion(start, nextPageTime);
} else {
let prevPageTime = localStorage.getItem("entryPage" + (index - 1));
behaviorRecord(`${index + 2}`, prevPageTime, nextPageTime);
reportQuestion(prevPageTime, nextPageTime);
}
emit("switchPage", index);
}
}
};
const showPreview=(index)=> {
//if(showImg.value)return;
showImg.value=true;
position.value=index;
setTimeout(()=>{
ImagePreview.value.swipeTo(index)
})
};
const handleClick = (e) => {
let src=e.target.currentSrc;
let index =imgList.value.findIndex((item)=>{
if(item==src) return true;
});
showPreview(index)
};
onMounted(async () => {
let imgArr = [];
await nextTick(() => {
const nodeList = document.querySelectorAll("#pagemain"+props.pageIndex+" .namebox img");
console.log(nodeList);
nodeList.forEach(function (node) {
imgArr.push(node.src);
node.removeEventListener("click",(e)=>{
handleClick(e)
});
node.addEventListener("click", (e)=>{
handleClick(e)
});
});
imgList.value = [...new Set(imgArr)];
});
});
const onChange=(newIndex)=>{
position.value = newIndex || 0
}
</script>
<style lang='scss' scoped>
.main {
width: 100%;
position: relative;
overflow: hidden;
.dealbox {
margin: 12px 15px 10px;
padding-bottom: 15px;
display: flex;
justify-content: space-between;
}
.prev,
.next {
width: 73px;
font-size: 14px;
color: #43c9c3;
height: 34px;
display: flex;
justify-content: center;
align-items: center;
background: #ffffff;
border-radius: 5px;
border: 1px solid #43c9c3;
}
.count {
:deep() .van-count-down {
color: #fff;
}
right: 15px;
top: 18px;
z-index: 9;
display: flex;
align-items: center;
position: absolute;
height: 31px;
padding: 0 8px;
background: #43c9c3;
border-radius: 8px;
}
.bg {
width: 100%;
position: absolute;
z-index: -1;
}
.content {
margin: 20px 15px;
position: relative;
.page {
background: #fff;
min-height: 100px;
margin-bottom: 20px;
border-radius: 10px;
border: 1px solid #43c9c3;
}
}
}
.card {
padding: 15px;
:deep() img {
width: 100%;
}
.descbox {
margin-top: 13px;
background: #f6feff;
border-radius: 8px;
padding: 12px 10px;
}
.titlebox {
display: flex;
width: 100%;
align-items: center;
.title {
font-size: 18px;
color: #000000;
font-weight: bold;
}
img {
width: 16px;
height: 14px;
}
}
}
.namebox {
.row {
display: flex;
align-items: first baseline;
margin-bottom: 5px;
.left {
font-weight: 500;
font-size: 16px;
color: #43c9c3;
flex-shrink: 0;
}
.right {
font-size: 16px;
color: #333333;
line-height: 23px;
}
}
}
</style>

289
src/views/caseIntro.vue Normal file
View File

@ -0,0 +1,289 @@
<template>
<div class="count">
{{ countTime }}
</div>
<div class="main" v-show="showIntro && step==-1">
<img src="../assets/bg.png" alt="" class="bg">
<div class="content">
<img src="../assets/wave.png" alt="" class="top">
<img src="../assets/wave.png" alt="" class="xia">
<div class="title">
{{ caseObj.case_name }}
</div>
<div class="con" v-html="(casePage[0].case_item_model)[0].content" v-if="casePage.length > 0 && casePage[0].case_item_model">
</div>
</div>
<div class="bottom">
<div class="button" @click="handleStart" >马上开始</div>
</div>
</div>
<caseDetail v-for="(item, index) in pageList" :pageIndex="index" :step="step" @switchPage="switchPage" :pageItem="item" :pageNum="pageList.length" @getAllAnswer="getAllAnswer" :start_time="start_time" :case_id="case_id"></caseDetail>
<comment v-show="!showIntro && step==pageList.length" ref="commentRef" :countTime="countTime" @initPage="initPage" :start_time="entry_time" :allAnswer="allAnswer" :is_welfare="is_welfare"></comment>
<totop></totop>
<back></back>
</template>
<script setup>
import { ref ,reactive,getCurrentInstance} from "vue";
const step = ref(-1);
const key = ref(0);
const showIntro = ref(true);
const countTime = ref('00:00');
const start_time = ref('');
const time = ref(0);
const timer = ref(null);
const casePage = ref([]);
const pageList = ref([]);
const entry_time=ref('');
const commentRef = ref(null);
import dayjs from 'dayjs'
import api from '../api/user.js';
import { useRouter, useRoute } from 'vue-router';
import { WXSHARE } from "../utils/wxshare-1.6.0";
import host from "../utils/host"
const router = useRouter();
const route = useRoute();
const caseObj = reactive({});
const allAnswer = reactive({});
const { proxy } = getCurrentInstance();
const is_welfare = ref(0);
let origin=route.query.source?route.query.source:''
const source = ref(origin);
const case_id = ref(route.query.case_id);
const getDetail = async () => {
const { code, data } = await api.getCaseDetail(case_id.value);
proxy.$loading.hide();
if (code == 200) {
document.title = data.case_name;
if(data.is_join==1){
if(source.value){
router.push({
path:'/result',
query:{
case_id:data.case_id,
project_id:data.project_id,
source:source.value
}
})
}else{
router.push({
path:'/result',
query:{
case_id:data.case_id,
project_id:data.project_id
}
})
}
}
is_welfare.value=data.is_welfare;
console.log('is_welfare:'+data.is_welfare)
Object.assign(caseObj, data)
casePage.value = data.case_item;
pageList.value = [];
for (let i = 1; i < data.case_item.length; i++) {
pageList.value.push(data.case_item[i]);
}
let link=host+'/web/caseIntro?case_id='+case_id.value;
WXSHARE(data.case_name,'“佳动例”等你来挑战,精彩福利享不停',link,'');
}
}
const behaviorRecord=(step='',start_time='',end_time='')=>{
api.behaviorRecord({
case_id:case_id.value,
step:step,
start_time:start_time,
end_time:end_time
}).then(res=>{
})
}
const addZero = (num) => {
return num < 10 ? '0' + num : '' + num;
}
const getAllAnswer=(data)=>{
//Object.assign(allAnswer,data)
allAnswer[data.pageIndex]=data.answer;
commentRef.value.formatAnswer(allAnswer)
}
const switchPage = (index) => {
if(index<0){
showIntro.value=true;
step.value=-1;
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
}else{
step.value=index;
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
}
if(index==pageList.value.length){
//alert(caseObj.project_id);
commentRef.value.init(caseObj.case_id,caseObj.project_id)
}
}
const handleCount = () => {
let minute = 0;
let second = 0;
if (timer.value) {
clearInterval(timer.value)
}
timer.value = setInterval(() => {
time.value = time.value + 1;
if (time.value >= 60) {
minute = Math.floor(time.value / 60);
second = time.value % 60;
if (minute >= 99 && second >= 59) {
clearInterval(timer.value)
}
countTime.value = `${addZero(minute)}:${addZero(second)}`;
} else {
countTime.value = `00:${addZero(time.value)}`;
}
}, 1000);
};
const initPage=()=>{
// step.value =-1;
// showIntro.value=true;
step.value = 0;
showIntro.value = false;
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
if(caseObj.case_item.length<=1) {
commentRef.value.init(caseObj.case_id,caseObj.project_id)
}
}
const handleStart = () => {
step.value = 0;
showIntro.value = false;
start_time.value =dayjs().format('YYYY-MM-DD HH:mm:ss');
behaviorRecord('点击开始',entry_time.value,dayjs(start_time.value).format('YYYY-MM-DD HH:mm:ss'));
behaviorRecord('第1页',entry_time.value,dayjs(start_time.value).format('YYYY-MM-DD HH:mm:ss'));
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
if(caseObj.case_item.length<=1) {
commentRef.value.init(caseObj.case_id,caseObj.project_id)
}
}
onMounted(async() => {
proxy.$loading.show();
entry_time.value=dayjs().format('YYYY-MM-DD HH:mm:ss')
getDetail();
behaviorRecord('进入问卷',entry_time.value,entry_time.value);
handleCount();
// await setTimeout(() => {
// const nodeList = document.querySelectorAll(".pagemain .namebox img");
// console.log(nodeList);
// nodeList.forEach(function (node) {
// imgList.value.push(node.src);
// node.onClick=null;
// // node.removeEventListener("click",(e)=>{
// // handleClick(e)
// // });
// node.addEventListener("click", (e)=>{
// handleClick(e)
// });
// });
// });
})
onBeforeMount(() => {
clearInterval(timer.value)
})
</script>
<style lang='scss' scoped>
.count {
color:#fff;
right: 15px;
top: 12px;
z-index: 9;
display: flex;
align-items: center;
position: fixed;
height: 31px;
padding: 0 8px;
background: #43C9C3;
border-radius: 8px;
}
.main {
width: 100%;
position: relative;
overflow: hidden;
.bg {
width: 100%;
position: absolute;
z-index: -1;
}
.content {
background: #fff;
position: relative;
margin: 20px 15px;
min-height: 300px;
margin-bottom: 80px;
border-left: 1px solid #43C9C3;
border-right: 1px solid #43C9C3;
.con {
margin: 0 10px;
:deep() img {
width:100%;
}
}
.title {
padding: 20px 0;
font-weight: 500;
margin: 0 30px;
text-align: center;
font-size: 20px;
color: #000000;
}
.top {
width: 100%;
position: absolute;
top: -9px;
}
.xia {
width: 100%;
position: absolute;
transform: rotate(180deg);
bottom: -9px;
}
}
.bottom {
position: fixed;
height: 49px;
bottom: 10px;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
.button {
width: 250px;
height: 40px;
background: #43C9C3;
border-radius: 20px;
font-weight: 500;
font-size: 17px;
display: flex;
justify-content: center;
align-items: center;
color: #FFFFFF;
}
}
}
</style>

95
src/views/caseList.vue Normal file
View File

@ -0,0 +1,95 @@
<template>
<div class="main">
<div class="banner">
<van-image width="100%" height="211" fit="cover" :src="img_url" />
<div class="casenum" v-if="case_num>0">
病例数{{ case_num }}
</div>
</div>
<div class="content">
<van-tabs color="#0984FD" title-inactive-color="#666666" v-model:active="activeName" sticky offset-top="0">
<van-tab title="未参与" :name="2">
<div class="tabcon">
<c-list :is_take_part="2" ></c-list>
</div>
</van-tab>
<van-tab title="已参与" :name="1">
<c-list :is_take_part="1" ></c-list>
</van-tab>
<van-tab title="全部" :name="0">
<c-list :is_take_part="0" ></c-list>
</van-tab>
</van-tabs>
</div>
</div>
</template>
<script setup>
import { ref} from "vue";
import { useRoute } from 'vue-router';
import { WXSHARE } from "../utils/wxshare-1.6.0";
import host from "../utils/host"
const route = useRoute();
//0: 1: 2:
const activeName = ref(2);
const img_url=ref(route.query.img_url)
const case_num=ref(route.query.case_num)
onMounted(() => {
let link=host+'/web/home';
WXSHARE('“佳动例”等你来挑战,精彩福利享不停',"肝胆相照-肝胆病在线公共服务平台",link,'');
})
</script>
<style lang='scss' scoped>
.main {
display: flex;
flex-direction: column;
height: 100vh;
.banner{
position: relative;
.casenum {
position: absolute;
bottom: 10px;
right: 10px;
z-index: 3;
font-weight: 400;
font-size: 16px;
color: #FFFFFF;
}
}
.row {
height: 2000px;
margin: 0 15px;
.cell {
margin-top: 12px;
border-radius: 8px;
overflow: hidden;
position: relative;
.casenum {
position: absolute;
bottom: 10px;
right: 10px;
z-index: 3;
font-weight: 400;
font-size: 16px;
color: #FFFFFF;
}
.mark {
position: absolute;
top: 10px;
left: 10px;
z-index: 3;
display: flex;
img {
height: 21px;
}
}
}
}
}
</style>

494
src/views/comment.vue Normal file
View File

@ -0,0 +1,494 @@
<template>
<div class="main commentmain">
<img src="../assets/bg.png" alt="" class="bg">
<div class="content">
<div class="title">{{ !isResult?'互动讨论区':'留言' }}<img src="../assets/cai.png" alt="" srcset="" class="cai"></div>
<div class="tips" v-if="!isResult">感谢您的参与请在文末点击完成提交</div>
<div class="conmmentbox">
<div class="commentwrapercell">
<comment-list :childList="list" @freshLike="freshLike" :case_id="case_id" :project_id="project_id" @freshSecComment="freshSecComment" @handleFreshList="handleFreshList"></comment-list>
</div>
</div>
<div class="loadmore" v-if="list.length<total" @click="loadMore">加载更多留言{{total-list.length}} </div>
<div class="add" v-if="isResult" @click="showLiuyan=true">添加留言</div>
<div class="bottom" v-if="!isResult">
<div class="textbox">
<van-field :autosize="{ minHeight: 150 }" v-model="message" rows="2" autosize type="textarea"
maxlength="200" :placeholder="placeholder" show-word-limit />
</div>
<button class="button" @click="handleSubmit">完成</button>
</div>
<div class="bottom" v-if="showLiuyan">
<div class="textbox">
<van-field :autosize="{ minHeight: 150 }" v-model="message" rows="2" autosize type="textarea"
maxlength="200" placeholder="病例有疑问?方案有争议?欢迎在此留言、提问、探讨~ " show-word-limit />
</div>
<button class="button" :disabled="isLock" @click="handleAdd">完成</button>
</div>
</div>
<c-dialog :message="dialog_message" ref="cdialog" @handledConfirm="handledConfirm" @handledCancel="handledCancel"></c-dialog>
</div>
</template>
<script setup>
import { showToast } from "vant";
import { ref,getCurrentInstance } from "vue";
import dayjs from 'dayjs'
import api from '../api/user.js';
import { useRouter, useRoute } from 'vue-router';
const route = useRoute();
const router = useRouter();
const { proxy } = getCurrentInstance();
let share_user_iden_temp=route.query.share_user_iden?route.query.share_user_iden:'';
const share_user_iden=ref(share_user_iden_temp);
const showLiuyan=ref(false);
const isResult=ref(route.path.includes('result')?true:false);
const read_duration=ref(0);
const case_id = ref(route.query.case_id)
const isLock=ref(false);
const project_id=ref(null);
const message = ref("");
const cdialog=ref(null);
const list=ref([]);
const page=ref(1);
const total=ref(0);
const dialogType=ref(0); //1 3min 23min 33min
const dialog_message=ref('');
const placeholder=ref('');
const complete_read=ref(0);//
const complete_read_time=ref(0);//
const first_high_quality=ref(0);//
const is_white_user=ref(0);
let origin=route.query.source?route.query.source:''
const source = ref(origin);
//const is_welfare=ref(0);
let answer=ref([])
const props=defineProps({
countTime:{
type:String,
default:'0'
},
allAnswer:{
type:Object,
default:{}
},
is_welfare:{
type:Number,
default:0
},
start_time:{
type:String,
default:''
}
});
const formatAnswer=(data)=>{
answer.value=[];
for(let i in data){
data[i].forEach(item => {
answer.value.push(item)
})
}
console.log(answer.value)
}
const emit=defineEmits(['initPage']);
// watch(()=>props.allAnswer,(newVal)=>{
// answer.value=[];
// console.log(1111111)
// console.log(props.allAnswer)
// for(let i in newVal){
// console.log(333)
// console.log(newVal[i])
// // newVal[i].forEach(item => {
// // answer.value.push(item)
// // })
// }
// console.log(answer.value)
// },{
// immediate:true,
// deep:true
// })
const handleFreshList=(data)=>{
list.value=[];
getList();
}
const getList = async (root_id='') => {
const { code, data } = await api.getCommentList({
case_id:case_id.value,
project_id:project_id.value,
page:page.value,
page_size:10,
root_id:root_id
})
if (code == 200) {
list.value = list.value.concat(data.data);
total.value=data.total;
}
}
const handledConfirm=()=>{
if(dialogType.value==1){
emit('initPage');
}else if(dialogType.value==2){
}else if(dialogType.value==3){
emit('initPage');
}
}
const behaviorRecord=(step='',start_time='',end_time='')=>{
api.behaviorRecord({
case_id:case_id.value,
step:step,
start_time:start_time,
end_time:end_time
}).then(res=>{
})
}
const changeDialog=(type)=>{
let unit='积分';
if(source.value){
unit='e豆'
};
if(type==1){
dialogType.value=1;
dialog_message.value='阅读时长满足'+read_duration.value/60+'分钟可获得'+complete_read.value+unit+'、留言入选优质点评有机会获得'+first_high_quality.value+unit+',是否继续学习?';
cdialog.value.openDialog();
}else if(type==2){
dialogType.value=2;
dialog_message.value='留言入选优质点评,有机会获得'+first_high_quality.value+unit+',是否留言?'
cdialog.value.openDialog();
}else if(type==3){
dialogType.value=3;
dialog_message.value='阅读时长满足'+read_duration.value/60+'分钟可获得'+complete_read.value+unit+',是否继续学习?'
cdialog.value.openDialog();
}else{
dialogType.value=4;
}
}
const loadMore=()=>{
page.value++;
getList();
}
const init= async(caseId,projectId)=>{
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
case_id.value=caseId;
project_id.value=projectId;
if(!project_id.value){
const { code, data } = await api.getCaseDetail(case_id.value);
if(code==200){
project_id.value=data.project_id;
}
}
handleGetConfig();
getList();
}
const freshLike=(data)=>{
console.log(data);
if(data.parentIndex<0){
let cuurent= list.value[data.index]
cuurent.like_num+=data.likeNum;
if(data.likeNum>0){
cuurent.is_like=true;
}else{
cuurent.is_like=false;
}
}else{
let cuurent=list.value[data.parentIndex].case_comment[data.index];
console.log(cuurent);
cuurent.like_num+=data.likeNum;
if(data.likeNum>0){
cuurent.is_like=true;
}else{
cuurent.is_like=false;
}
}
}
const freshSecComment=(data)=>{
console.log('freshSecComment')
console.log(data);
list.value[data.commentIndex].case_comment=list.value[data.commentIndex].case_comment.concat(data.commentData)
}
const handleSubmit=()=>{
let countS=props.countTime.split(':');
let current=Number(countS[0])*60+Number(countS[1]);
console.log('props.is_welfare:'+props.is_welfare)
if(props.is_welfare==1 && is_white_user.value==1){
if(message.value==='' && current<read_duration.value){
changeDialog(1)
}else if(message.value==='' && current>=read_duration.value){
changeDialog(2)
}else if(message.value && current<read_duration.value){
changeDialog(3)
}else{
handleComplete()
}
}else{
handleComplete()
}
}
const handleAdd=async()=>{
if(message.value==''){
showToast('请输入内容');
return false
}
isLock.value=true;
const {code,data}=await api.addComment({
case_id:case_id.value,
project_id:project_id.value,
content:message.value,
level:1,
root_id:'',
parent_id:''
})
isLock.value=false;
if(code==200){
handleFreshList()
message.value='';
showToast('留言成功')
}
}
const handledCancel=()=>{
handleComplete();
};
const handleComplete= async()=>{
proxy.$loading.show();
const {code,data}=await api.completeCase(case_id.value,{
project_id:project_id.value,
content:message.value,
share_user_iden:share_user_iden.value,
start_read:dayjs(props.start_time).format('YYYY-MM-DD HH:mm:ss'),
end_read:dayjs().format('YYYY-MM-DD HH:mm:ss'),
answer:answer.value
})
if(code==200){
let last=localStorage.getItem('lastPage')
behaviorRecord('完成',last,dayjs().format('YYYY-MM-DD HH:mm:ss'));
if(source.value){
router.push({
path:'/result',
query:{
case_id:case_id.value,
project_id:project_id.value,
source:source.value
}
});
}else{
router.push({
path:'/result',
query:{
case_id:case_id.value,
project_id:project_id.value
}
});
}
}
proxy.$loading.hide();
}
const handleGetConfig=async()=>{
const {code,data}=await api.getConfig({
project_id:project_id.value,
});
if(code==200){
read_duration.value=data.read_duration;
complete_read.value=data.complete_read;
complete_read_time.value=data.complete_read_time;
first_high_quality.value=data.first_high_quality;
is_white_user.value=data.is_white_user;
if(data.is_white_user==1){
let txt='获取奖励';
if(source.value){
txt='获取e豆'
}
placeholder.value=`欢迎就本病例提出您的讨论话题、思考与诊疗分享,如病例疑难点、诊疗经验、用药心得等。(病例阅读时长≥${data.read_duration/60}分钟、留言入选精选留言均有机会${txt})`
}else{
placeholder.value=`欢迎就本病例提出您的讨论话题、思考与诊疗分享,如病例疑难点、诊疗经验、用药心得等。`
}
// if(data.read_duration==0){
// changeDialog(1)
// }
}
}
//defineExpose({freshLike})
defineExpose({init,formatAnswer})
</script>
<style lang='scss' scoped>
.commentmain{
:deep(.van-field__control::placeholder){
color:#666;
}
}
.main {
width: 100%;
position: relative;
overflow: hidden;
.add {
margin: 25px auto;
width: 250px;
height: 40px;
background: #43C9C3;
border-radius: 23px;
display: flex;
font-size: 14px;
color: #FFFFFF;
justify-content: center;
align-items: center;
}
.loadmore {
margin-top: 15px;
height: 50px;
display: flex;
font-size: 15px;
color: #FFFFFF;
justify-content: center;
align-items: center;
background: #0CAF98;
}
.conmmentbox {
margin-top: 40px;
}
.expand {
width: 240px;
height: 19px;
margin: 12px auto 12px;
background: #43C9C3;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
font-size: 10px;
color: #FFFFFF;
}
.active {
border-bottom: 1px solid #E5E5E5;
}
.textbox {
border-radius: 5px;
border: .5px solid #43C9C3;
:deep() .van-cell {
background: none;
}
:deep() .van-field__word-limit {
color: #999999;
}
}
.bg {
width: 100%;
position: absolute;
z-index: -1;
}
.content {
position: relative;
margin: 20px 15px;
min-height: 120px;
margin-bottom: 20px;
.count {
:deep() .van-count-down {
color: #fff;
}
right:0;
top:-10px;
z-index:9;
display: flex;
align-items: center;
position: absolute;
height: 31px;
padding:0 8px;
background: #43C9C3;
border-radius: 8px;
}
.title {
padding: 20px 0 10px;
position: relative;
font-weight: 600;
font-size: 23px;
color: #000000;
.cai{
width: 50px;
height: 25px;
bottom:4px;
left:-4px;
z-index:-1;
position: absolute;
}
}
.tips {
font-weight: 400;
font-size: 17px;
color: #666666;
}
}
.bottom {
margin-top: 65px;
width: 100%;
overflow: hidden;
.button {
outline: none;
width: 250px;
height: 40px;
margin: 34px auto 0;
background: #43C9C3;
border-radius: 20px;
font-weight: 500;
font-size: 17px;
border: none;
display: flex;
justify-content: center;
align-items: center;
color: #FFFFFF;
}
}
}
</style>

108
src/views/home.vue Normal file
View File

@ -0,0 +1,108 @@
<template>
<div class="main">
<div class="box" >
<van-list v-model:loading="loading" :finished="finished" finished-text="" @load="onLoad" >
<div class="row" v-for="(item, index) in list" :key="item.project_id" @click="goCase(item.project_id,item.project_image,item.case_count)">
<div class="cell">
<div class="mark">
<img src="../assets/newicon.png" alt="" v-if="item.is_recently_update==1">
<img src="../assets/fuli.png" alt="" v-if="item.is_welfare==1">
</div>
<div class="casenum">
病例数{{ item.case_count }}
</div>
<van-image lazy-load radius="8" width="100%" height="194" fit="cover" :src="item.project_image" />
</div>
</div>
</van-list>
</div>
<div class="nodata" v-if="list.length == 0">
暂无数据
</div>
</div>
</template>
<script setup>
import api from '../api/user.js';
import { ref } from "vue";
import { useRouter, useRoute } from 'vue-router';
import { WXSHARE } from "../utils/wxshare-1.6.0";
import host from "../utils/host"
const router = useRouter();
const page = ref(0);
const list = ref([]);
const loading = ref(false);
const finished = ref(false);
const goCase = (id,img,num) => {
router.push({
path: 'caseList',
query: { project_id: id,img_url:img,case_num: num}
})
}
const getList = async () => {
const {data,code}=await api.getProjectList({
page:page.value,
page_size:10,
})
loading.value = false;
if (code == 200) {
console.log(data);
list.value = list.value.concat(data.data);
if (data.data.length <10) {
finished.value = true;
}
}
}
const onLoad = () => {
if(!finished.value) {
page.value++;
getList();
}
};
onMounted(() => {
let link=host+'/web/home';
WXSHARE('“佳动例”等你来挑战,精彩福利享不停',"肝胆相照-肝胆病在线公共服务平台",link,'');
})
</script>
<style lang='scss' scoped>
.main {
.row {
margin: 0 15px;
.cell {
margin-top: 12px;
border-radius: 8px;
overflow: hidden;
position: relative;
.casenum {
position: absolute;
bottom: 10px;
right: 10px;
z-index: 3;
font-weight: 400;
font-size: 16px;
color: #FFFFFF;
}
.mark {
position: absolute;
top: 10px;
left: 10px;
z-index: 3;
display: flex;
img {
height: 21px;
}
}
}
}
}
</style>

308
src/views/pageunit.vue Normal file
View File

@ -0,0 +1,308 @@
<template>
<div class="main" :id="'resultmain'+pageIndex" >
<!-- <img src="../assets/bg.png" alt="" class="bg">
<div class="dealbox">
<div class="prev" @click="switchPage(pageIndex-1)">上一步</div>
</div> -->
<div class="content">
<div class="pagebox">
<div class="page">
<div class="card" v-for="(item,index) in pageItem.case_item_model" :key="index">
<div class="titlebox">
<img src="../assets/titlebg.png" alt="">
<div class="title">{{ item.model_name }} </div>
</div>
<div class="descbox">
<div class="namebox" v-html="item.content"></div>
</div>
</div>
<!-- <div class="card" v-html="pageItem.content"></div> -->
<!-- <div class="card">
<div class="titlebox">
<img src="../assets/titlebg.png" alt="">
<div class="title">基础信息 </div>
</div>
<div class="descbox">
<div class="namebox">
<div class="row">
<div class="left"> ·姓别</div>
<div class="right">
**</div>
</div>
<div class="row">
<div class="left"> ·姓别</div>
<div class="right">
症状:自诉1月前因长期熬夜出现软困乏力无腹痛无恶心呕吐纳差胸闷胸痛无尿频尿痛尿黄无发热无胸闷气紧无胸痛心悸无呼吸困难无皮肤黄染及皮下出血等病后饮食稍差睡眠尚可精神尚可大小便正常
</div>
</div>
</div>
</div>
</div> -->
</div>
<questionend v-for="(item,index) in pageItem.case_item_question" :key="'case'+pageIndex+'qestion'+item.question_id" :question="item" @checkAnswer="checkAnswer" :pageIndex="pageIndex" :questionIndex="index" v-if="pageItem.case_item_question"></questionend>
</div>
</div>
<!-- <div class="dealbox">
<div class="next" @click="switchPage(pageIndex-1)">上一步</div>
<div class="prev" @click="goNext(pageIndex+1)" >下一步</div>
</div> -->
<!-- <back></back> -->
</div>
<van-image-preview ref="ImagePreview" v-model:show="showImg" closeable :images="imgList" @change="onChange">
</van-image-preview>
</template>
<script setup>
import { ref } from "vue";
//import { showImagePreview } from 'vant';
//const isResult=ref(route.path.includes('result')?true:false);
const props=defineProps({
pageIndex: {
type: Number,
default:0
},
imgList:{
type: Array,
default: () => []
},
step:{
type: Number,
default:0
},
pageNum: {
type: Number,
default: 0
},
pageItem: {
type: Object,
default: () => ({})
}
});
const unitAnswer=reactive({
pageIndex:null,
answer:[],
});
const showImg = ref(false);
const position = ref(0);
const ImagePreview = ref(null);
watch(()=>props.pageItem,(newVal)=>{
if(newVal.case_item_question.length>0) {
for(let i=0;i<newVal.case_item_question.length;i++){
unitAnswer.answer[i]=''
}
}
},{
immediate:true,
deep:true
})
const emit=defineEmits(['switchPage','changeImg']);
const switchPage=(index)=>{
emit('switchPage',index)
}
const checkAnswer=(data)=>{
console.log(data);
unitAnswer.pageIndex=data.questionIndex;
unitAnswer.answer[data.questionIndex]={answer:data.answer,questin_type:data.question_type,question_id:data.question_id};
}
const passNext=()=>{
let answer=unitAnswer.answer;
console.log(answer);
if(answer.length==0) {
showToast('请回答本页所有问题后再提交')
return
};
for(let i=0;i<answer.length;i++){
if(!answer[i].answer){
showToast('请回答本页第'+(i+1)+'个问题后再提交')
return
}
if(answer[i].questin_type==2){
if(answer[i].answer.length<2){
showToast('本页第'+(i+1)+'个问题是多选')
return;
}
}
}
}
const goNext=(index)=>{
if(index<props.pageNum){
emit('switchPage',index)
}else{
emit('switchPage',index)
passNext()
}
}
const showPreview=(index)=> {
showImg.value=true;
position.value=index;
setTimeout(()=>{
ImagePreview.value.swipeTo(index)
})
};
const handleClick = (e) => {
let src=e.target.currentSrc;
console.log(src);
console.log(props.imgList);
let index =props.imgList.findIndex((item)=>{
if(item==src) return true;
});
showPreview(index)
};
const onChange=(newIndex)=>{
position.value = newIndex || 0
}
onMounted(async()=>{
let imgArr=[];
nextTick(() => {
const nodeList = document.querySelectorAll("#resultmain"+props.pageIndex+" .namebox img");
//console.log(nodeList)
nodeList.forEach(function (node) {
imgArr.push(node.src);
node.removeEventListener("click",()=>{
handleClick()
});
node.addEventListener("click", (e)=>{
handleClick(e)
});
});
let temp= [...new Set(imgArr)];
emit('changeImg',temp)
});
})
</script>
<style lang='scss' scoped>
.main {
width: 100%;
position: relative;
overflow: hidden;
.dealbox{
margin:12px 15px 10px;
display: flex;
justify-content: space-between;
}
.prev,.next {
width: 73px;
font-size: 14px;
color: #43C9C3;
height: 34px;
display: flex;
justify-content: center;
align-items: center;
background: #FFFFFF;
border-radius: 5px;
border: 1px solid #43C9C3;
}
.count {
:deep() .van-count-down {
color: #fff;
}
right:15px;
top:18px;
z-index:9;
display: flex;
align-items: center;
position: absolute;
height: 31px;
padding:0 8px;
background: #43C9C3;
border-radius: 8px;
}
.bg {
width: 100%;
position: absolute;
z-index: -1;
}
.content {
margin: 20px 15px;
position: relative;
.page {
background: #fff;
min-height: 180px;
margin-bottom: 20px;
border-radius: 10px;
border: 1px solid #43C9C3;
}
}
}
.card {
padding: 15px;
:deep() img{
width:100%;
}
.descbox {
margin-top: 13px;
background: #F6FEFF;
border-radius: 8px;
padding: 12px 10px;
}
.titlebox {
display: flex;
width: 100%;
align-items: center;
.title {
font-size: 18px;
color: #000000;
font-weight: bold;
}
img {
width: 16px;
height: 14px;
}
}
}
.namebox {
.row {
display: flex;
align-items: first baseline;
margin-bottom: 5px;
.left {
font-weight: 500;
font-size: 16px;
color: #43C9C3;
flex-shrink: 0;
}
.right {
font-size: 16px;
color: #333333;
line-height: 23px;
}
}
}
</style>

432
src/views/result.vue Normal file
View File

@ -0,0 +1,432 @@
<template>
<div class="main">
<div class="bannerbox">
<img src="../assets/banner.jpg" alt="" class="banner">
<div class="thank" :class="{'on':!scoreObj.describe}">感谢您完成阅读!</div>
<div class="score" v-if="scoreObj.describe">您已获得:<span>{{ scoreObj.total_score }}</span>{{source?'e豆':'积分'}}</div>
<div class="tips" v-if="scoreObj.describe">({{scoreObj.describe}})</div>
</div>
<comment ref="commentRef"></comment>
<div class="pagebox">
<pageunit v-for="(item, index) in pageList" :pageIndex="index" :pageItem="item" :imgList="imgList" @changeImg="changeImg"></pageunit>
</div>
<totop></totop>
<back></back>
</div>
<!-- <van-image-preview ref="ImagePreview" v-model:show="showImg" closeable :images="imgList" @change="onChange">
</van-image-preview> -->
</template>
<script setup>
import { ref,reactive } from "vue";
import api from '../api/user.js';
import { useRouter, useRoute } from 'vue-router';
import { WXSHARE } from "../utils/wxshare-1.6.0";
import host from "../utils/host"
const scoreObj = reactive({});
const route = useRoute();
const case_id = ref(route.query.case_id)
const pageList = ref([]);
const commentRef = ref(null);
const project_id = ref(route.query.project_id);
const is_welfare=ref(0);
const imgList = ref([]);
let origin=route.query.source?route.query.source:''
const source = ref(origin);
const getDetail = async () => {
const { code, data } = await api.getCaseDetail(case_id.value)
if (code == 200) {
document.title = data.case_name;
let link=host+'/web/caseIntro?case_id='+case_id.value;
WXSHARE(data.case_name,'“佳动例”等你来挑战,精彩福利享不停',link,'');
// Object.assign(caseObj, data)
// casePage.value = data.case_item;
pageList.value = [];
for (let i = 1; i < data.case_item.length; i++) {
pageList.value.push(data.case_item[i]);
}
}
}
const getScore=()=>{
api.getScore({
case_id:case_id.value
}).then(res=>{
if(res.code==200){
if(res.data){
Object.assign(scoreObj, res.data)
}
}
})
}
const changeImg=(data)=>{
console.log(data);
imgList.value=imgList.value.concat(data);
}
onMounted(async() => {
commentRef.value.init(case_id.value, project_id.value);
getScore();
getDetail()
});
</script>
<style lang='scss' scoped>
.main {
width: 100%;
overflow: hidden;
.banner {
width: 100%;
}
.bannerbox {
position: relative;
.thank {
position: absolute;
top: 21.69%;
// right: 18.53%;
white-space: nowrap;
left:50%;
transform: translateX(-50%);
font-weight: 600;
font-size: 23px;
color: #0CAF98;
line-height: 30px;
}
.thank.on{
top: 40.10%;
}
.score {
position: absolute;
top: 50.10%;
//right: 26.13%;
left:50%;
transform: translateX(-50%);
font-weight: 550;
display: flex;
white-space: nowrap;
align-items: first baseline;
font-size: 16px;
color: #1F1F1F;
span{
font-size: 21px;
font-weight: 550;
color:#FF173D
}
}
.tips{
position: absolute;
white-space: nowrap;
top: 72.43%;
// right: 9.7%;
left:50%;
transform: translateX(-50%);
font-weight: 500;
font-size: 12px;
color: #1F1F1F;
}
}
.add {
margin: 25px auto;
width: 250px;
height: 40px;
background: #43C9C3;
border-radius: 23px;
display: flex;
font-size: 14px;
color: #FFFFFF;
justify-content: center;
align-items: center;
}
.loadmore {
margin-top: 15px;
height: 50px;
display: flex;
font-size: 15px;
color: #FFFFFF;
justify-content: center;
align-items: center;
background: #0CAF98;
}
.conmmentbox {
margin-top: 40px;
}
.talkbox {
margin: 12px 30px 0px 0;
}
.toolbox {
margin-top: 12px;
display: flex;
justify-content: space-between;
.cancel {
height: 38px;
background: #FFFFFF;
border: 1px solid #EBEBEB;
border-radius: 4px;
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
color: #737478;
flex: 1;
}
.ok {
margin-left: 12px;
flex: 1;
border-radius: 4px;
height: 38px;
display: flex;
justify-content: center;
align-items: center;
background: #43C9C3;
border: 1px solid #43C9C3;
font-size: 14px;
color: #fff;
}
}
.expand {
width: 240px;
height: 19px;
margin: 12px auto 12px;
background: #43C9C3;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
font-size: 10px;
color: #FFFFFF;
}
.active {
border-bottom: 1px solid #E5E5E5;
}
.comemntcell {
display: flex;
.descbox {
margin-left: 12px;
flex: 1;
// border-bottom: 1px solid #E5E5E5;
.depart {
margin-top: 10px;
font-weight: 400;
font-size: 12px;
color: #333333;
}
.hospital {
margin-top: 4px;
font-weight: 400;
font-size: 12px;
color: #333333;
}
.date {
margin-top: 4px;
font-weight: 400;
font-size: 12px;
color: #333333;
}
.comemntcell {
border-bottom: 1px solid #E5E5E5;
.descbox {
border: none;
padding-bottom: 15px;
}
}
.comment {
margin-top: 4px;
display: flex;
.text {
font-size: 12px;
color: #333333;
line-height: 17px;
flex: 1;
}
.reply {
margin-left: 30px;
width: 40px;
height: 20px;
background: #43C9C3;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
color: #FFFFFF;
}
}
.tag {
margin-top: 2px;
font-size: 12px;
color: #FFFFFF;
height: 15px;
background: #43C9C3;
border-radius: 10px;
display: flex;
padding: 0 4px;
width: 52px;
justify-content: center;
align-items: center;
span {
transform: scale(0.9);
}
}
.namebox {
display: flex;
align-items: center;
justify-content: space-between;
.name {
font-size: 17px;
color: #333333;
}
.zanbox {
display: flex;
font-size: 15px;
color: #333333;
align-items: center;
margin-right: 40px;
img {
width: 20px;
height: 20px;
}
}
}
}
}
.bg {
width: 100%;
position: relative;
z-index: -1;
}
.content {
padding: 0 15px;
background: url('../assets/bg.png') no-repeat 0 0;
background-size: 100% auto;
margin: 0px 0px;
min-height: 300px;
margin-bottom: 20px;
.count {
:deep() .van-count-down {
color: #fff;
}
right:0;
top:-10px;
z-index:9;
display: flex;
align-items: center;
position: absolute;
height: 31px;
padding:0 8px;
background: #43C9C3;
border-radius: 8px;
}
.title {
position: relative;
background-size: 50px 15px;
padding: 20px 0 10px;
font-weight: 600;
font-size: 23px;
color: #000000;
.cai{
width: 50px;
height: 25px;
bottom:4px;
left:-4px;
z-index:-1;
position: absolute;
}
}
.tips {
font-weight: 400;
font-size: 17px;
color: #666666;
}
}
.textbox {
border-radius: 5px;
border: .5px solid #43C9C3;
:deep() .van-cell {
background: none;
}
:deep() .van-field__word-limit {
color: #999999;
}
}
.bottom {
margin-top: 65px;
width: 100%;
overflow: hidden;
.button {
width: 250px;
height: 40px;
margin: 34px auto 0;
background: #43C9C3;
border-radius: 20px;
font-weight: 500;
font-size: 17px;
display: flex;
justify-content: center;
align-items: center;
color: #FFFFFF;
}
}
}
</style>

103
vite.config.js Normal file
View File

@ -0,0 +1,103 @@
import {
join
} from 'path'
import {
defineConfig
} from 'vite';
import vue from '@vitejs/plugin-vue';
import {
VuetifyResolver
} from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';
import AutoImport from 'unplugin-auto-import/vite';
import viteCompression from 'vite-plugin-compression'
import { visualizer } from 'rollup-plugin-visualizer' //查看项目的依赖
import { createHtmlPlugin } from 'vite-plugin-html'
import { VantResolver } from '@vant/auto-import-resolver';
export default defineConfig(({ command }) => {
return {
base: command === 'build' ? './' : '/web',
plugins: [
vue(),
AutoImport({
// 自动导入 Vue 相关函数ref, reactive, toRef 等
imports: ['vue'],
resolvers: [VantResolver()],
}),
visualizer({
open: false
}),
// 将下面的添加到plugin下
createHtmlPlugin({
minify: true,
inject: {
data: {
title: '互动病例',
}
}
}),
Components({
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
dirs: ['src/components', 'src/views'],
resolvers: [VantResolver(),VuetifyResolver()],
}),
],
build: {
assetsInlineLimit: 4096, // 图片转 base64 编码的阈值
minify: 'terser',
plugins: [
viteCompression({
threshold: 1024000 // 对大于 1mb 的文件进行压缩
}),
],
// rollup 配置
rollupOptions: {
output: {
chunkFileNames: 'static/js/[name]-[hash].js', // 引入文件名的名称
entryFileNames: 'static/js/[name]-[hash].js', // 包的入口文件名称
assetFileNames: 'static/[ext]/[name]-[hash].[ext]', // 资源文件像 字体,图片等
manualChunks(id) {
// 如果不同模块使用的插件基本相同那就尽可能打包在同一个文件中减少http请求如果不同模块使用不同插件明显那就分成不同模块打包。这是一个矛盾体。
// 这里使用的是最小化拆分包。如果是前者可以直接选择返回'vendor'。
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString(); //让打开那个页面加载那个页面的js ,让之间的关联足够小
// return 'vendor' 如果不同模块使用的插件基本相同那就尽可能打包在同一个文件中减少http请求;
}
}
}
},
terserOptions: {
compress: {
//生产环境时移除console
drop_console: false,
drop_debugger: true,
},
},
},
resolve: {
alias: {
'@': join(__dirname, 'src'),
}
},
server: {
host: true,
port: 1798,
//secure: false,
proxy: {
// '/api': {
//   target: 'https://vue3.go-admin.dev',
//   changeOrigin: true,             //开启跨域
//   rewrite: (path) => path.replace(/^\/api/, '')
// },
'/api': {
target: 'https://prod-vote.igandan.com',
changeOrigin: true, //开启跨域
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},
publicDir: '/public'
}
});

BIN
正式前台dist.zip Normal file

Binary file not shown.