hookehuyr

fix

Showing 60 changed files with 2 additions and 5515 deletions
......@@ -8,7 +8,7 @@ VITE_PROXY_TARGET = http://voice.onwall.cn
VITE_PROXY_PREFIX = /srv/
# 打包输出文件夹名称
VITE_OUTDIR = voice
VITE_OUTDIR = map
# 是否开启调试
VITE_CONSOLE = 0
......
......@@ -9,63 +9,12 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
'图形验证码': typeof import('./src/components/图形验证码.vue')['default']
Agreement: typeof import('./src/components/DonateFlower/agreement.vue')['default']
AuditVideoCard: typeof import('./src/components/AuditVideoCard/index.vue')['default']
Banner: typeof import('./src/components/MuiVideo/banner.vue')['default']
BookCard: typeof import('./src/components/BookCard/index.vue')['default']
BVideoCard: typeof import('./src/components/BVideoCard/index.vue')['default']
CommentBox: typeof import('./src/components/CommentBox/index.vue')['default']
CommentList: typeof import('./src/components/CommentList/index.vue')['default']
DonateBar: typeof import('./src/components/DonateBar/index.vue')['default']
DonateBook: typeof import('./src/components/DonateBook/index.vue')['default']
DonateCert: typeof import('./src/components/DonateCert/index.vue')['default']
DonateFlower: typeof import('./src/components/DonateFlower/index.vue')['default']
FlowerIcon: typeof import('./src/components/FlowerIcon/index.vue')['default']
ImageSliderVerify: typeof import('./src/components/ImageSliderVerify/index.vue')['default']
LocalismBox: typeof import('./src/components/LocalismBox/index.vue')['default']
LoginBox: typeof import('./src/components/LoginBox/index.vue')['default']
MuiVideo: typeof import('./src/components/MuiVideo/index.vue')['default']
MyButton: typeof import('./src/components/MyButton/index.vue')['default']
NoticeOverlay: typeof import('./src/components/NoticeOverlay/index.vue')['default']
NoticeOverlayModule: typeof import('./src/components/NoticeOverlayModule/index.vue')['default']
RankingItem: typeof import('./src/components/RankingItem/index.vue')['default']
RightSideList: typeof import('./src/components/RightSideList/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ShortcutFixed: typeof import('./src/components/ShortcutFixed/index.vue')['default']
Status: typeof import('./src/components/MuiVideo/status.vue')['default']
Template: typeof import('./src/components/template/index.vue')['default']
Test: typeof import('./src/components/LoginBox/test.vue')['default']
VanActionSheet: typeof import('vant/es')['ActionSheet']
VanButton: typeof import('vant/es')['Button']
VanCellGroup: typeof import('vant/es')['CellGroup']
VanCheckbox: typeof import('vant/es')['Checkbox']
VanCol: typeof import('vant/es')['Col']
VanConfigProvider: typeof import('vant/es')['ConfigProvider']
VanDialog: typeof import('vant/es')['Dialog']
VanEmpty: typeof import('vant/es')['Empty']
VanField: typeof import('vant/es')['Field']
VanForm: typeof import('vant/es')['Form']
VanIcon: typeof import('vant/es')['Icon']
VanImage: typeof import('vant/es')['Image']
VanList: typeof import('vant/es')['List']
VanLoading: typeof import('vant/es')['Loading']
VanNumberKeyboard: typeof import('vant/es')['NumberKeyboard']
VanOverlay: typeof import('vant/es')['Overlay']
VanPicker: typeof import('vant/es')['Picker']
VanPopup: typeof import('vant/es')['Popup']
VanRow: typeof import('vant/es')['Row']
VanStepper: typeof import('vant/es')['Stepper']
VanSticky: typeof import('vant/es')['Sticky']
VanSwipe: typeof import('vant/es')['Swipe']
VanSwipeItem: typeof import('vant/es')['SwipeItem']
VanTab: typeof import('vant/es')['Tab']
VanTabs: typeof import('vant/es')['Tabs']
VanTag: typeof import('vant/es')['Tag']
VanUploader: typeof import('vant/es')['Uploader']
VideoBar: typeof import('./src/components/MuiVideo/videoBar.vue')['default']
VideoCard: typeof import('./src/components/VideoCard/index.vue')['default']
VideoDetail: typeof import('./src/components/VideoDetail/index.vue')['default']
}
}
......
<template>
<div class="video-wrapper" style="position: relative;">
<div v-if="mp && detail.showStatus" class="status">
<van-image round width="6rem" height="6rem" style="vertical-align: bottom;" :src="status_icon" />
</div>
<div :id="'mui-player-' + item.id" class="video-div" />
<div v-if="mp">
<div v-if="item.status === 'ENABLE'" class="normal-module">
<div class="video-bar">
<van-row>
<van-col span="12" @click="setComment">
<van-image round width="2rem" height="2rem" style="vertical-align: middle;"
:src="item.avatar ? item.avatar : icon_avatar" />&nbsp;&nbsp;
<span style="font-size: 1.05rem;vertical-align: middle;">{{ item.name }}</span>
</van-col>
<van-col span="12">
<div style="padding: 0.25rem; padding-top: 0.75rem; text-align: right;">
<span @click="setComment">
<van-icon :name="icon_liuyan" size="1.2rem" style="vertical-align: bottom;" />
{{ item.comment_num }}
</span>
&nbsp;&nbsp;&nbsp;
<span @click="handleAction('like', detail.id)">
<van-icon v-if="!detail.is_like" :name="icon_dianzan1" size="1.2rem"
style="vertical-align: bottom;" />
<van-icon v-else :name="icon_dianzan2" size="1.2rem" style="vertical-align: bottom;" />
{{ detail.like_num }}
</span>
</div>
</van-col>
</van-row>
</div>
<div style="color: #999999; padding: 0px 1rem 0.5rem;" @click="goTo">
{{ item.book_name }} | {{ item.localism_type }}
</div>
</div>
<div v-else class="audit-module" style="margin-top: 1rem;">
<div style="color: #222222; padding: 0px 1rem 0.5rem;">{{ item.book_name }} | {{ item.localism_type }}</div>
<div v-if="item.status === 'DISABLE'" class="van-hairline--top" style="padding: 1rem; font-size: 0.85rem;">
<p style="color: #999999; margin-bottom: 0.25rem;">老师留言:</p>
<p>{{ item.check_note }}</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
/**
* 视频组件通用模块
*/
import icon_dianzan1 from '@images/icon-dianzan01@2x.png'
import icon_dianzan2 from '@images/icon-dianzan02@2x.png'
import icon_liuyan from '@images/icon-liuyan@2x.png'
import icon_avatar from '@images/que-touxiang@2x.png'
import icon_refuse from '@images/icon-jujue@2x.png'
import icon_apply from '@images/icon-shenhe@2x.png'
import icon_enable from '@images/icon-tongguo@2x.png'
import 'mui-player/dist/mui-player.min.css'
import MuiPlayer from 'mui-player'
import _ from 'lodash';
import { DEFAULT_COVER } from '@/constant'
</script>
<script>
import mixin from 'common/mixin';
export default {
mixins: [mixin.likeFn],
props: ['item'],
data() {
return {
detail: {},
mp: ''
}
},
computed: {
// eslint-disable-next-line vue/return-in-computed-property
status_icon () {
switch (this.item.status) {
case 'ENABLE':
return icon_enable
case 'DISABLE':
return icon_refuse
case 'APPLY':
return icon_apply
}
}
},
created() {
},
mounted() {
// TAG: 视频组件控制
setTimeout(() => {
var mp = new MuiPlayer({
container: '#mui-player-' + this.item.id,
title: this.item.title,
src: this.item.video,
poster: this.item.cover ? this.item.cover : DEFAULT_COVER,
// poster: DEFAULT_COVER,
autoFit: false,
videoAttribute: [ // 声明启用同层播放, 不让会自动全屏播放
{ attrKey: 'webkit-playsinline', attrValue: 'webkit-playsinline' },
{ attrKey: 'playsinline', attrValue: 'playsinline' },
{ attrKey: 'x5-video-player-type', attrValue: 'h5-page' },
]
})
this.mp = mp; // 渲染速度问题,只有视频控件渲染完成后显示
this.detail = _.cloneDeep(this.item);
this.detail.showStatus = true;
var video = mp.video();
// 监听原生video事件,审核状态标签显示控制
var _this = this;
video && video.addEventListener('play', function () {
_this.detail.showStatus = false;
});
video && video.addEventListener('pause', function () {
_this.detail.showStatus = true;
});
}, 500);
// 配置16:9高度比
const width = document.getElementById('mui-player-' + this.item.id).clientWidth;
const height = (width * 9) / 16;
document.getElementById('mui-player-' + this.item.id).height = height;
},
methods: {
goTo () { // 跳转作品详情页
this.$router.push({
path: '/client/videoDetail',
query: {
prod_id: this.item.id,
type: this.item.type, // 特殊标识,判断入口 为keepAlive使用
perf_id: this.item.perf_id,
book_id: this.item.book_id
}
});
},
setComment() {
this.$router.push({
path: '/client/videoDetail/comment',
query: {
prod_id: this.item.id,
book_id: this.item.book_id
}
});
}
}
}
</script>
<style lang="less" scoped>
.video-wrapper {
margin: 1rem;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.13);
.status {
position: absolute;
top: 0;
right: 0;
z-index: 999;
}
.video-div {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.video-bar {
color: #713610;
padding: 1rem;
padding-bottom: 0.5rem;
}
}
</style>
<template>
<div class="video-wrapper" style="position: relative;">
<div v-if="status === 'PROCESS' && detail.showStatus" class="status">
<van-image v-if="item.status === 'ENABLE'" round width="6rem" height="6rem" style="vertical-align: bottom;"
:src="icon_enable" />
<van-image v-if="item.status === 'DISABLE'" round width="6rem" height="6rem" style="vertical-align: bottom;"
:src="icon_refuse" />
<van-image v-if="item.status === 'APPLY'" round width="6rem" height="6rem" style="vertical-align: bottom;"
:src="icon_apply" />
</div>
<div :id="'mui-player-' + item.id" class="video-div" />
<div class="control-bar">
<div>
<div class="video-bar">
<!-- <van-row align="center">
<van-col span="8">
<van-image round width="2rem" height="2rem" style="vertical-align: middle;" :src="item.avatar" />&nbsp;
<span style="font-size: 1.05rem; vertical-align: middle;">{{ item.name }}</span>
</van-col>
<van-col span="16">
<div style="color: #999999; text-align: right;">
{{ item.book_name }} |
<span style="color: #11D2B1;" @click="setLocalism">{{ item.localism_type }}</span>
</div>
</van-col>
</van-row> -->
<div>
<van-image round width="2rem" height="2rem" style="vertical-align: middle;" :src="item.avatar" />&nbsp;
<span style="font-size: 1.05rem; vertical-align: middle;">{{ item.name }}</span>
</div>
<div style="color: #999999; margin-top: 1rem;">
{{ item.book_name }} |
<span style="color: #11D2B1;" @click="setLocalism">{{ item.localism_type }}</span>
</div>
</div>
</div>
<div class="book-intro" style="margin-top: 1rem;">
<!-- <div :id="'book-intro' + item.id" :class="{ 'van-multi-ellipsis--l3': isToggle }">{{ item.note }}</div> -->
<div v-if="hasToggle">
<div v-if="isToggle" class="book-toggle-icon" @click="onToggle(false)">
展开&nbsp;
<van-icon style="vertical-align: middle;" size="0.9rem" :name="icon_down" />
</div>
<div v-else class="book-toggle-icon" @click="onToggle(true)">
折叠&nbsp;
<van-icon style="vertical-align: middle;" size="0.9rem" :name="icon_up" />
</div>
</div>
</div>
<div v-if="status === 'PROCESS'">
<div v-if="item.status === 'DISABLE'" class="van-hairline--top" style="padding: 1rem; font-size: 0.85rem;">
<p style="color: #999999; margin-bottom: 0.25rem;">老师留言:</p>
<p>{{ item.check_note }}</p>
</div>
</div>
<div v-if="status === 'PENDING'" class="van-hairline--top book-handle">
<van-row>
<van-col offset="3" style="padding: 1rem;" @click="onRefuse()">
<div class="disagree-btn">
<van-icon name="close" />&nbsp;不通过
</div>
</van-col>
<van-col style="padding: 1rem;" @click="onPass('pass')">
<div class="agree-btn" style="">
<van-icon name="passed" />&nbsp;通过
</div>
</van-col>
</van-row>
</div>
</div>
</div>
<van-overlay :show="showNotice" z-index="1000">
<div class="wrapper" @click.stop>
<div class="block">
<div style="position: absolute; top: -2rem; right: 1rem; font-size: 1.5rem;">
<van-icon name="close" color="#FFFFFF" @click="closeNotice" />
</div>
<div class="van-hairline--bottom"
style="color: #222222; font-size: 1.25rem; font-weight: bold; text-align: center; padding-bottom: 1rem;">
作品不通过
</div>
<div>
<van-field v-model="message" rows="2" autosize label="" type="textarea" maxlength="200"
placeholder="请填写您对小朋友的温馨鼓励" show-word-limit @focus="focusInput" @blur="blurInput" />
</div>
<div style="margin-top: 3rem;">
<my-button type="primary" @on-click="handleAudit('disable')">确定</my-button>
</div>
</div>
</div>
</van-overlay>
<van-dialog v-model:show="show" title="温馨提示" show-cancel-button :confirmButtonColor="styleColor.baseColor"
@confirm="handleAudit('enable')" @cancel="onCancel">
<div style="padding: 1rem; text-align: center;">是否确认审核通过该视频 ?</div>
</van-dialog>
<localism-box :id="item.id" :title="localism_title" :show-localism="showLocalism"
:localism="item.localism_type" @on-close="closeLocalism" @on-submit="submitLocalism" />
</template>
<script setup>
import { icon_up, icon_down, icon_refuse, icon_apply, icon_enable } from '@/utils/generateIcons'
import MyButton from '@/components/MyButton/index.vue'
import LocalismBox from '@/components/LocalismBox/index.vue'
import { checkProdAPI } from '@/api/B/audit'
import { ref, onMounted } from 'vue'
import 'mui-player/dist/mui-player.min.css'
import MuiPlayer from 'mui-player'
import _ from 'lodash';
import tools from '@/common/tool'
import { styleColor } from '@/constant.js';
import { Toast } from 'vant';
import { DEFAULT_COVER } from '@/constant'
const props = defineProps({
item: Object,
status: String,
});
const emit = defineEmits(['on-click']);
// 判断是否显示简介的展开图标
const hasToggle = ref(false); // 判断是否有展开文字,默认没有
const isToggle = ref(true); // 判断展开状态,默认展开
const onToggle = (v) => { // 展开/折叠
isToggle.value = v
}
onMounted(() => {
// setTimeout(() => {
// // 判断是否显示简介的展开图标
// hasToggle.value = tools.hasEllipsis(`book-intro${props.item.id}`);
// }, 500);
})
const localism_title = ref('')
// 审核视频通过/不通过弹框
const showNotice = ref(false);
const show = ref(false);
const message = ref('');
const trigger_type = ref(''); // 点击入口判断,pass为点击通过按钮进入,空值为直接点击语言栏
const onPass = (type) => { // 通过审核
showLocalism.value = true;
localism_title.value = '请确认您的方言类别是否正确'
trigger_type.value = type;
}
const onCancel = () => {
show.value = false;
}
const closeNotice = () => {
showNotice.value = false;
}
const onRefuse = () => { // 不通过审核
showNotice.value = true;
}
const handleAudit = async (status) => {
const { code } = await checkProdAPI({ prod_id: props.item.id, status, check_note: message.value, })
if (code === 1) {
Toast.success('操作成功');
message.value = '';
showNotice.value = false;
show.value = false;
emit('on-click', props.item.id);
}
}
const focusInput = () => {
}
const blurInput = () => {
}
// 修改语言种类
const showLocalism = ref(false);
const setLocalism = () => {
trigger_type.value = ''
localism_title.value = '设置方言类别'
showLocalism.value = true;
}
const closeLocalism = () => {
showLocalism.value = false;
}
const submitLocalism = (localism) => {
// 接口保存成功后,静态修改语言类型
props.item.localism_type = localism;
showLocalism.value = false;
// 通过按钮点击进入,需要走通过审核流程。
if (trigger_type.value) {
show.value = true;
}
}
</script>
<script>
export default {
data() {
return {
detail: {},
mp: '',
}
},
created() {
},
mounted() {
setTimeout(() => {
var mp = new MuiPlayer({
container: '#mui-player-' + this.item.id,
title: this.item.title,
src: this.item.video,
poster: this.item.cover ? this.item.cover : DEFAULT_COVER,
// poster: DEFAULT_COVER,
autoFit: false,
videoAttribute: [ // 声明启用同层播放, 不让会自动全屏播放
{ attrKey: 'webkit-playsinline', attrValue: 'webkit-playsinline' },
{ attrKey: 'playsinline', attrValue: 'playsinline' },
{ attrKey: 'x5-video-player-type', attrValue: 'h5-page' },
]
})
this.mp = mp;
this.detail = _.cloneDeep(this.item);
this.detail.showStatus = true;
var video = mp.video();
// 监听原生video事件,审核状态标签显示控制
var _this = this;
video && video.addEventListener('play', function (event) {
_this.detail.showStatus = false;
});
video && video.addEventListener('pause', function (event) {
_this.detail.showStatus = true;
});
}, 500);
},
methods: {
}
}
</script>
<style lang="less" scoped>
.video-wrapper {
margin: 1rem;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.13);
.status {
position: absolute;
top: 0;
right: 0;
z-index: 999;
}
.video-div {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
height: 13rem !important;
}
.video-bar {
color: #713610;
padding: 1rem;
padding-bottom: 0.5rem;
}
.book-intro {
padding: 1rem;
padding-top: 0;
.book-post {
color: #222222;
font-size: 1.25rem;
font-weight: bold;
}
#book-intro {
color: #333333;
margin-top: 0.25rem;
}
.book-toggle-icon {
text-align: right;
color: #713610;
font-size: 1rem;
margin-top: 0.5rem;
}
}
.book-handle {
padding: 0 1rem;
.disagree-btn {
background: #B4B4B3;
border-radius: 15px;
color: #FFFFFF;
padding: 0.25rem 0.8rem;
}
.agree-btn {
background: @base-color;
border-radius: 15px;
color: @base-font-color;
padding: 0.25rem 1.5rem;
}
}
}
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: auto;
text-align: center;
}
.block {
width: 80%;
// height: 25rem;
background-color: #fff;
border-radius: 10px;
padding: 1rem;
position: relative;
margin-top: 1rem;
margin-bottom: 5rem;
}
</style>
<template>
<div class="book-item van-hairline--bottom" @click="handle">
<van-row>
<van-col span="8">
<van-image width="7rem" height="7rem" :src="item.cover" fit="contain" style="text-align: center;">
<template #loading>
<van-loading type="spinner" size="20" />
</template>
</van-image>
</van-col>
<van-col class="wrapper" span="16">
<p class="title van-multi-ellipsis--l2">{{ item.name }}</p>
<div v-if="type === USER_ROLE.CLIENT" class="van-multi-ellipsis--l2 content">{{ item.note }}</div>
<div class="sub">
<van-icon :name="icon_video" />&nbsp;&nbsp;<span>{{ item.prod_num }}个作品</span>
</div>
<div v-if="type === USER_ROLE.BUSINESS" class="upload" @click="onUpload(item)">上传视频</div>
</van-col>
</van-row>
</div>
<van-overlay :show="show" z-index="9999">
<div class="overlay-wrapper" @click.stop>
<van-loading size="24px">跳转中...</van-loading>
</div>
</van-overlay>
</template>
<script setup>
import icon_video from '@images/video.png'
import { ref } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
import { JSJ_FORM_B, USER_ROLE } from '@/constant'
const props = defineProps({
item: Object,
type: String,
user_id: Number
})
const emit = defineEmits(['on-click']);
const handle = () => {
if (props.type === USER_ROLE.CLIENT) { // 类型是客户端时,才能查看
emit('on-click', props.item)
}
}
// 上传视频
const show = ref(false);
const onUpload = (v) => {
show.value = true;
// x_field_1是金数据表单传入的参数,老师上传的格式为:user_id-book_id-perf_id,perf_id 为 0
let str = `${props.user_id}-${v.id}-0`;
location.href = `${JSJ_FORM_B}?x_field_1=${str}`;
// BUG: 关闭loading临时处理
setTimeout(() => {
show.value = false;
}, 2000);
}
onBeforeRouteLeave(() => {
})
</script>
<script>
export default {
data() {
return {
}
},
mounted() {
},
methods: {
}
}
</script>
<style lang="less" scoped>
.book-item {
// margin: 1rem;
padding: 1rem;
.wrapper {
padding-left: 1rem;
.title {
font-size: 1.15rem;
color: #222222;
font-weight: bold;
margin: 0.5rem 0;
}
.content {
font-size: 0.85rem;
color: #999999;
margin: 0.5rem 0;
line-height: 1.75;
}
.sub {
font-size: 0.85rem;
color: #999999;
margin-top: 0.5rem;
}
.upload {
color: @base-font-color;
background-color: @base-color;
border-radius: 15px;
width: 4rem;
padding: 0.25rem 0.5rem;
font-size: 0.8rem;
text-align: center;
margin-top: 1rem;
}
}
}
.overlay-wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: auto;
text-align: center;
}
</style>
<template>
<van-popup v-model:show="show" :close-on-click-overlay="false" position="bottom" :style="{ height: '40%' }">
<div class="van-hairline--bottom">
<van-row>
<van-col span="4" />
<van-col span="16" style="color: #222222; text-align: center; line-height: 3;">
<span v-if="type === 'comment'">留言</span>
<span v-else>回复</span>
</van-col>
<van-col span="4" @click="closeBtn">
<div style="padding: 1rem;">
<van-icon :name="icon_y" size="1.25rem" />
</div>
</van-col>
</van-row>
</div>
<div style="background-color: #F7F7F7; font-size: 0.9rem;">
<p style="color: #777777; padding: 1rem;">您的留言评论将会展示在页面中,请谨慎发表留言评论</p>
<van-row>
<van-col span="16">
<p v-if="type === 'comment'" style="padding: 1rem; padding-top: 0;">请写下你友善的留言:</p>
<p v-else style="padding: 1rem; padding-top: 0;">回复<span style="color: #0B3A72;">@{{ replayUser }}:</span></p>
</van-col>
<van-col span="8">
<div class="button-primary-comment" @click="submitComment">发送</div>
</van-col>
</van-row>
</div>
<div>
<van-cell-group inset>
<van-field v-model="message" rows="2" autosize label="" type="textarea" maxlength="200"
:placeholder="type === 'comment' ? '请输入留言内容' : '请输入回复内容'" show-word-limit @focus="focusInput"
@blur="blurInput" />
</van-cell-group>
</div>
</van-popup>
</template>
<script setup>
import icon_y from '@images/y.png'
import { Toast } from 'vant';
import { ref, watch } from 'vue'
const props = defineProps({
showPopup: Boolean,
type: String,
replayUser: String
})
const emit = defineEmits(['on-close', 'on-submit']);
const show = ref(false)
const message = ref('')
const closeBtn = () => {
emit('on-close', false)
show.value = false;
}
const submitComment = () => {
if (message.value) {
show.value = false;
emit('on-submit', message.value);
message.value = '';
} else {
Toast.fail('留言不能为空');
}
}
// 监听弹出框
watch(() => props.showPopup, (v) => {
show.value = v
})
// 优化处理输入框弹出遮挡问题
let interval = ''
const focusInput = () => {
window.addEventListener("resize", function () {
if (
document.activeElement.tagName === "INPUT" ||
document.activeElement.tagName === "TEXTAREA"
) {
interval = window.setTimeout(function () {
if ("scrollIntoView" in document.activeElement) {
document.activeElement.scrollIntoView();
} else {
document.activeElement.scrollIntoViewIfNeeded();
}
}, 0);
}
});
}
const blurInput = () => {
clearInterval(interval);
}
</script>
<style lang="less" scoped>
.comment-wrapper {
color: #999999;
padding: 1rem;
line-height: 1.75;
.reply-wrapper {
background: #F7F7F7;
border-radius: 10px;
padding: 0.5rem;
margin-top: 0.5rem;
color: #0B3A72;
.content {
color: #222222;
}
}
}
.button-primary-comment {
width: 5rem;
height: auto;
text-align: center;
padding: 0.2rem;
// margin: 0.25rem;
font-size: 0.9rem;
background: @base-color;
border-radius: 24px;
border: 1px solid @base-color;
color: @base-font-color;
font-weight: bold;
margin-left: 1rem;
margin-bottom: 1rem;
}
.text {
text-align: center;
padding: 0.7rem;
margin: 0.8rem;
font-size: 1rem;
font-weight: bold;
border-radius: 24px;
// border: 1px solid F7F7F7;
color: #713610;
background-color: @base-color;
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.06);
}
</style>
<template>
<van-popup v-model:show="show" :close-on-click-overlay="false" round position="bottom" :style="{ height: '70%' }">
<div style="position: relative;">
<div class="van-hairline--bottom"
style="position: fixed; width: 100%; background-color: white; border-radius: 10px; z-index: 999;">
<van-row>
<van-col span="4" @click="onReload">
<div style="padding: 1rem; text-align: center;">
<!-- <van-icon :name="icon_x" size="1.25rem" /> -->
</div>
</van-col>
<van-col span="16" style="color: #222222; text-align: center; line-height: 3;">
<span>{{ listTotal }}条回复</span>
</van-col>
<van-col span="4" @click="closeBtn">
<div style="padding: 1rem;">
<van-icon :name="icon_y" size="1.25rem" />
</div>
</van-col>
</van-row>
</div>
<div style="height: 4rem;"></div>
<van-list v-model:loading="loading" :finished="finished" :finished-text="finishedTextStatus ? '没有更多了' : ''"
@load="onLoad" :immediate-check="false">
<template v-for="(item, key) in replyList" :key="key">
<div class="comment-wrapper">
<van-row style="font-size: 0.9rem;">
<van-col span="4">
<van-image round width="3rem" height="3rem" :src="item.avatar ? item.avatar : icon_avatar" />
</van-col>
<van-col span="14">
<p>{{ item.name }}</p>
<p>{{ item.kg_name }}</p>
</van-col>
<van-col span="6" style="text-align: right;">
<p @click="setComment(item, 'reply')" style="color: #333333;">回复</p>
<p>{{ item.comment_time }}</p>
</van-col>
</van-row>
<van-row>
<van-col offset="4">
<span style="color: #222222;">{{ item.note }}</span>
</van-col>
</van-row>
</div>
</template>
</van-list>
<van-empty v-if="emptyStatus" class="custom-image" :image="no_image" description="暂无回复" />
</div>
<comment-box :showPopup="showCommentBoxPopup" :type="commentType" :replayUser="replayUser"
@on-submit="submitCommentBox" @on-close="closeCommentBox"></comment-box>
</van-popup>
<!-- 写评论时,如果没有实名认证提示弹框 -->
<notice-overlay-module :show="showNotice" :type="userInfo.can_upload" @on-submit="onSubmit" @on-close="onClose" />
</template>
<script setup>
import CommentBox from '@/components/CommentBox/index.vue'
import NoticeOverlayModule from '@/components/NoticeOverlayModule/index.vue'
import no_image from '@images/que-shuju@2x.png'
import icon_y from '@images/y.png'
import icon_avatar from '@images/que-touxiang@2x.png'
import { useRoute, useRouter } from 'vue-router'
import axios from '@/utils/axios';
import _ from 'lodash'
import { Toast } from 'vant';
import { ref, watch } from 'vue'
// 获取是否实名认证
import { useDefaultPerf } from '@/composables';
import { USER_STATUS } from '@/constant'
import { useGo } from '@/hooks/useGo'
const go = useGo();
const $router = useRouter();
const $route = useRoute();
const { userInfo } = useDefaultPerf($route.query.book_id);
const props = defineProps({
showPopup: Boolean,
data: Object
})
const emit = defineEmits(['on-close']);
/******** 留言框相关操作 START *******/
// 回复评论控件
const showCommentBoxPopup = ref(false);
const commentType = ref('comment'); // 类型 comment 为评论/类型 reply 为回复
/**
* 回复/评论 功能
* @param {*} v 单行评论数据
* @param {*} type 类型 comment 为评论/类型 reply 为回复
*/
const commentId = ref('')
const replayUser = ref('')
const setComment = (v, type) => { //
if (userInfo.value.can_upload === USER_STATUS.PASS) {
showCommentBoxPopup.value = true;
commentType.value = type;
replayUser.value = v.name;
commentId.value = props.data.id;
} else {
closeBtn();
showNotice.value = true;
}
}
/**
* 提交留言回调
* @param {*} note 留言内容
*/
const submitCommentBox = (note) => {
let url = '';
let data = {}
// 判断是留言还是回复 动态调整接口名称
if (commentType.value === 'comment') {
url = 'add_comment';
data = {
prod_id: $route.query.prod_id,
note
}
} else {
url = 'add_reply';
data = {
comment_id: commentId.value,
note
}
}
axios.post(`/srv/?a=${url}`, data)
.then(res => {
showCommentBoxPopup.value = false;
if (res.data.code === 1) {
Toast.success('发布成功')
onReload()
} else {
console.warn(res);
if (!res.data.show) return false;
Toast({
icon: 'close',
message: res.data.msg
});
}
})
.catch(err => {
console.error(err);
})
}
const closeCommentBox = (v) => { // 关闭留言框
showCommentBoxPopup.value = v;
}
/******** 留言框相关操作 START *******/
const showNotice = ref(false)
const onClose = () => { // 关闭提示框回调
showNotice.value = false;
}
// 跳转个人中心
const onSubmit = () => {
setTimeout(() => {
showNotice.value = false;
}, 1000);
if (userInfo.value.can_upload === USER_STATUS.NON_VERIFIED) { // 未实名认证
go('/me/verifyUser', { back_url: $route.fullPath })
} else if (userInfo.value.can_upload === USER_STATUS.NON_DEFAULT_CHILD) { // 没有默认儿童
go('/me/handleUser', { perf_id: '', kg_id: '', kg_name: '', type: 'ADD', back_url: $route.fullPath })
}
}
const show = ref(false);
const listTotal = ref(0)
const replyList = ref([])
const loading = ref(false)
const finished = ref(false)
const limit = ref(10)
const offset = ref(0)
// 因为不能让空图标提前出来的写法
const finishedTextStatus = ref(false);
const emptyStatus = ref(false);
const onLoad = () => {
// 异步更新数据
axios.get('/srv/?a=reply_list', {
params: {
comment_id: props.data.id,
limit: limit.value,
offset: offset.value
}
})
.then(res => {
if (res.data.code === 1) {
listTotal.value = res.data.data.total;
replyList.value = _.concat(replyList.value, res.data.data.replylist);
replyList.value = _.uniqBy(replyList.value, 'id');
offset.value = replyList.value.length;
loading.value = false;
// 数据全部加载完成
if (!res.data.data.replylist.length) {
// 加载状态结束
finished.value = true;
}
if (!replyList.value.length) {
finishedTextStatus.value = false;
emptyStatus.value = true;
} else {
emptyStatus.value = false;
}
} else {
console.warn(res);
if (!res.data.show) return false;
Toast({
icon: 'close',
message: res.data.msg
});
}
})
.catch(err => {
console.error(err);
})
}
const onReload = () => {
replyList.value = [];
offset.value = 0;
onLoad();
}
// const onClose = () => {
// show.value = false;
// }
// 监听弹出框
watch(() => props.showPopup, (v) => {
show.value = v;
onReload()
});
const closeBtn = () => {
emit('on-close', { comment_id: props.data.id, total: listTotal.value })
show.value = false;
}
</script>
<style lang="less" scoped>
.comment-wrapper {
color: #999999;
padding: 1rem;
line-height: 1.75;
.reply-wrapper {
background: #F7F7F7;
border-radius: 10px;
padding: 0.5rem;
margin-top: 0.5rem;
color: #0B3A72;
.content {
color: #222222;
}
}
}
</style>
<!--
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-05-31 18:32:38
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-07-01 17:58:30
* @FilePath: /tswj/src/components/DonateBar/index.vue
* @Description: 爱心助力底部固定栏
-->
<template>
<div class="fix-btn">
<div class="text" @click="showDonate=true">
<slot />
</div>
</div>
<donate-flower
:show-popup="showDonate"
:item="donateInfo"
@on-close="showDonate=false"
/>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { DonateFlower } from '@/utils/generateModules'
import { prepareDonateAPI } from '@/api/C/donate.js'
const props = defineProps({
perfId: Number,
kgId: {
type: String,
default: ''
}
})
const donateInfo = ref({})
const showDonate = ref(false);
onMounted(async () => {
const { data } = await prepareDonateAPI({ perf_id: props.perfId, kg_id: props.kgId });
donateInfo.value = data;
})
</script>
<style lang="less" scoped>
.fix-btn {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: white;
box-shadow: 0px -2px 4px 0px rgba(0, 0, 0, 0.07);
.text {
text-align: center;
padding: 0.7rem;
margin: 1.2rem;
font-size: 1rem;
font-weight: bold;
border-radius: 24px;
// border: 1px solid F7F7F7;
color: @base-font-color;
background-color: @base-color;
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.06);
}
}
</style>
<template>
<van-popup v-model:show="show" :close-on-click-overlay="false" round position="bottom"
:style="{ height: popupHeight }">
<div class="donate-wrapper">
<div class="donate-header">
<van-row>
<van-col>
<div class="donate-book">
<van-image width="80" height="80" :src="item.avatar ? item.avatar : icon_avatar" />
</div>
</van-col>
<van-col>
<div class="donate-detail">
<p class="text">{{ item.name }}</p>
<p class="price">¥{{ item.price }}</p>
</div>
</van-col>
</van-row>
</div>
<div class="donate-name">
<van-row>
<van-col span="4" style="line-height: 2;">捐赠人<span style="color: red;">*</span></van-col>
<van-col class="donate-input" span="18">
<input v-model="item.perf_name" type="text" style="border: 0; padding: 0.25rem;" />
</van-col>
</van-row>
</div>
<div class="van-hairline--bottom donate-tips">
<p style="color: #999999;">
<van-icon name="warning-o" />&nbsp;&nbsp;您捐献的书籍将用于多民族语言发展项目
</p>
</div>
<div class="donate-number">
<van-row>
<van-col span="12"> 数量 </van-col>
<van-col span="12" style="text-align: right;">
<van-stepper v-model="donate_number" min="1" max="100" integer input-width="40px" button-size="32px" />
</van-col>
</van-row>
</div>
<div class="donate-handle">
<div class="donate-cancel">
<my-button type="plain" @on-click="cancelDonate">取消</my-button>
</div>
<div class="donate-imd">
<my-button type="primary" @on-click="donateBook">立即捐赠</my-button>
</div>
</div>
</div>
</van-popup>
</template>
<script setup>
import Cookies from 'js-cookie'
import icon_avatar from '@images/que-touxiang@2x.png'
import MyButton from '@/components/MyButton/index.vue'
import { ref, reactive, onMounted, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from '@/utils/axios';
import $ from 'jquery'
import { Toast, Dialog } from 'vant';
const $route = useRoute();
const $router = useRouter();
const props = defineProps({
item: Object,
showPopup: Boolean
})
const emit = defineEmits(['on-close']);
let donate_number = ref(1)
const donateBook = () => {
// 爱心捐书接口
axios.post('/srv/?a=add_donate', {
book_id: props.item.book_id,
qty: donate_number.value,
donate_name: props.item.perf_name,
})
.then(res => {
if (res.data.code === 1) {
console.warn(res.data.data);
closeBtn();
// 交易成功跳转回调页面
// TEMP: 临时传参 donate_id
$router.push({
path: '/client/wechatpayCallback',
query: {
donate_id: res.data.data.id
}
})
} else {
console.warn(res);
if (!res.data.show) return false;
Toast({
icon: 'close',
message: res.data.msg
});
}
})
.catch(err => {
console.error(err);
})
}
const show = ref(false);
let popupHeight = ref('60%');
watch(() => props.showPopup, (v) => {
// 如果没有默认儿童需要用户确认一次
if (v && !props.item.perf_id) {
Dialog.confirm({
title: '温馨提示',
message: '默认儿童为空, 是否继续捐书!',
confirmButtonColor: '#713610'
})
.then(() => {
show.value = v;
// DOM调整后,把弹出框高度设定到适合高度,配合不同分辨率效果
nextTick(() => {
let height = $('.donate-wrapper').height();
popupHeight.value = height + 10 + 'px';
})
})
.catch(() => {
// 取消按钮回调
closeBtn();
});
return false;
}
show.value = v;
// DOM调整后,把弹出框高度设定到适合高度,配合不同分辨率效果
nextTick(() => {
let height = $('.donate-wrapper').height();
popupHeight.value = height + 10 + 'px';
})
})
const closeBtn = () => {
emit('on-close', false)
show.value = false;
}
const cancelDonate = () => {
closeBtn();
}
onMounted(() => {
})
</script>
<script>
import mixin from 'common/mixin';
export default {
mixins: [mixin.init],
data() {
return {
}
},
mounted() {
},
methods: {
}
}
</script>
<style lang="less" scoped>
.donate-wrapper {
.donate-header {
padding: 1rem;
.donate-book {
border: 1px solid #ECECEC;
border-radius: 5px;
padding: 0.1rem;
}
.donate-detail {
font-size: 1.25rem;
padding: 1rem;
.text {
color: #292929;
font-weight: bold;
}
.price {
color: #EE332E;
}
}
}
.donate-name {
padding: 1rem;
.donate-input {
border: 1px solid #B9B9B9;
border-radius: 5px;
padding: 0.2rem;
margin-left: 1rem;
}
}
.donate-tips {
padding: 1rem;
padding-top: 0;
}
.donate-number {
padding: 1rem;
}
.donate-handle {
overflow: auto;
.donate-cancel {
width: 49%;
float: left;
}
.donate-imd {
width: 50%;
float: left;
}
}
}
.button {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
padding: 0 0.5rem;
}
</style>
<template>
<div class="donate-certificate">
<div class="header-bg">
<van-image width="100" height="100" fit="contain" :src="icon_cert" style="margin-top: 15vh;" />
</div>
<div class="title">
<van-row align="center">
<van-col>
<span style="font-size: 0.8rem; color: #272727;">亲爱的捐赠者</span>
</van-col>
<van-col>
<div style="color: #713610; padding: 1rem;" class="bg-gradient">{{ item.name }}</div>
</van-col>
<van-col>:</van-col>
</van-row>
</div>
<div class="content">
感谢您对“童声无界”多民族儿童共读活动的关注及捐赠。上海市儿童基金会已收到您的捐款。您的爱心将用于边疆地区少数民族儿童阅读能力的培育。共读一本书传递一份爱,初心为爱童声无界。感谢您对公益事业的支持。
</div>
<div class="price">
<div>爱心捐赠</div>
<div>{{ item.amt }}&nbsp;元</div>
</div>
<div class="organizer">
<div class="wrapper">
<p>上海市儿童基金会</p>
<p>上海初心为爱公益基金会</p>
<div style="position: absolute; width: 50%; height: 100%; top: 0; left: 0;">
<van-image height="100%" fit="contain" :src="icon_stamp01" />
</div>
<div style="position: absolute; width: 50%; height: 100%; top: 0; right: 0;">
<van-image height="100%" fit="contain" :src="icon_stamp02" />
</div>
</div>
</div>
<!-- <div class="date">{{ item.donate_date }}</div> -->
<div class="text">
证书编号:{{ item.cert_code }}
</div>
<div style="height: 3rem;" />
<div class="wrapper-border">
<div class="top-bg">
<van-image :src="donate_top" style="width: 100%; height: 10rem;" />
</div>
<div class="center-bg">
<img :src="donate_center" :style="styleObject">
</div>
<div class="bottom-bg">
<van-image :src="donate_bottom" style="width: 100%;" />
</div>
<div style="height: 3rem;" />
</div>
</div>
</template>
<script setup>
import icon_cert from '@images/zhengshu@2x.png'
import icon_stamp01 from '@images/stamp01.png'
import icon_stamp02 from '@images/stamp02.png'
import donate_top from '@images/donate_top.png'
import donate_center from '@images/donate_center.png'
import donate_bottom from '@images/donate_bottom.png'
import $ from 'jquery'
</script>
<script>
import mixin from 'common/mixin';
export default {
mixins: [mixin.init],
props: ['item'],
data() {
return {
name: '',
price: '',
datetime: '',
styleObject: {}
}
},
mounted() {
// 动态计算背景图高度
const wrapper_height = $('.donate-certificate').height()
const top_height = $('.top-bg').height()
const bottom_height = $('.bottom-bg').height()
const center_height = (wrapper_height - top_height - bottom_height).toFixed();
this.styleObject = {
width: '100%',
height: center_height + 'px'
}
},
methods: {
}
}
</script>
<style lang="less" scoped>
.donate-certificate {
background-image: url('http://gyzs.onwall.cn/zhengshu-banner.png');
background-color: white;
background-repeat: no-repeat;
background-size: contain;
width: 100%;
position: relative;
border-radius: 10px;
// height: 85vh;
box-shadow: 0px 0px 7px 0px rgba(3, 155, 178, 0.14);
.header-bg {
text-align: center;
}
.title {
padding: 0 2rem;
}
.content {
font-size: 0.85rem;
padding: 2rem;
line-height: 1.75;
text-indent: 2rem;
}
.price {
margin-left: 4rem;
div:first-child {
display: inline-block;
font-size: 0.85rem;
vertical-align: middle;
margin-right: 0.5rem;
}
div:last-child {
display: inline-block;
font-size: 1.25rem;
color: red;
vertical-align: middle;
}
}
.organizer {
margin: 1rem;
overflow: hidden;
margin-right: 2rem;
padding-bottom: 1rem;
.wrapper {
float: right;
font-size: 0.8rem;
text-align: center;
line-height: 2;
position: relative;
color: #000;
padding: 0.5rem 0;
}
}
.date {
color: #272727;
font-size: 0.85rem;
text-align: right;
padding-right: 2rem;
}
.text {
color: #713610;
padding-left: 10%;
font-size: 0.9rem;
position: relative;
bottom: 1rem;
margin-top: 1rem;
}
.wrapper-border {
position: absolute;
// border: 0.5px solid #11D2B1;
height: 95%;
top: 0;
width: 93%;
margin: 3%;
// border-radius: 1.5rem;
// overflow: hidden;
.top-bg {
margin: 0;
padding: 0;
font-size: 0;
position: absolute;
top: 0;
width: 100%;
}
.center-bg {
margin: 0;
padding: 0;
font-size: 0;
position: absolute;
top: 10%;
width: 100%;
}
.bottom-bg {
margin: 0;
padding: 0;
font-size: 0;
position: absolute;
bottom: 0;
width: 100%;
}
}
.bg-gradient {
// 文字下划线
background: linear-gradient(#EAEAEA, #EAEAEA) no-repeat;
/*调整下划线的宽度占百分之百 高度是3px */
// background-size: 100% 3px;
/* 调整下划线的起始位置 左侧是0 上边是1.15em */
// background-position: 0 1rem;
background-size: 100% 1px;
background-position: 0 2.5rem;
}
}
</style>
/*
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-06-02 11:23:16
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-09 17:05:08
* @FilePath: /tswj/src/components/DonateFlower/agreement.js
* @Description:
*/
const str = '上海XX益基金会'
const html = `
<div style="text-align: center; font-weight: bold; margin-bottom: 1rem; font-size: 1.25rem; color: #11D2B1;">
捐赠协议
</div>
<div style="text-align: justify; color: #231815; font-size: 1.05rem; line-height: 1.5;">
感谢您对中国公益慈善事业的关心与支持!${str}面向所有具有公开募捐资质的慈善组织提供服务。${str}始终秉持着最高的合规性要求,因此,只要您点击“同意”或“接受”,您的行为就已表示您无条件接受并遵守“本网络捐赠条款和条件”以及${str}不时公布的“其他公开规则”。
</div>
<div style="margin: 1rem 0;">
<div style="margin-bottom: 0.5rem;">1.资格规定</div>
<div style="line-height: 1.75;">
您声明您是符合中华人民共和国法律规定的具有完全民事行为能力的自然人。
</div>
</div>
<div style="margin: 1rem 0;">
<div style="margin-bottom: 0.5rem;">2.捐赠财产</div>
<div style="margin-bottom: 0.5rem;line-height: 1.75;">您同意依照《公益事业捐赠法》的相关规定,自愿无偿地通过网络向${str}平台上具有公开募捐资质的慈善组织捐赠财产用于公益事业。</div>
<div style="margin-bottom: 0.5rem;line-height: 1.75;">您声明您用于捐赠的财产是您合法持有并有权处分的财产。</div>
<div style="margin-bottom: 0.5rem;line-height: 1.75;">您声明您已经了解《公益事业捐赠法》、《合同法》中关干财产捐赠的相关规定,明确同意不会撤销或部分撤销对${str}平台上具有公开募捐资质的慈善组织作出的捐赠。</div>
<div style="margin-bottom: 0.5rem;line-height: 1.75;">您声明您知晓并同意,${str}平台上具有公开募捐资质的慈善组织可以为公益事业之目的合理审慎地自主决定捐赠财产的实际受助对象、以及具体使用的领域、金额、时间。</div>
</div>
<div style="margin: 1rem 0;">
<div style="margin-bottom: 0.5rem;">3.争议解决和法律适用</div>
<div style="margin-bottom: 0.5rem;line-height: 1.75;">除非另有明确约定,否则,本网络捐款条款和条件适用中华人民共和国法律,并排除一切冲突法原则的适用。</div>
<div style="line-height: 1.75;">
如果各方无法通过协商解决争端,您和${str}平台上具有公开募捐资质的慈善组织,也即善款接收方,均有权向有管辖权的人民法院提起诉讼以解决争议,由此产生的诉讼费、律师费、公证费等由败诉方承担。<br/>
</div>
</div>
<div style="margin: 1rem 0;">
<div style="margin-bottom: 0.5rem;">4.其他</div>
本网络捐款条款和条件构成您与${str}之间就本网络捐款条款和条件约定事项的完整和唯一的协议,并取代就本网络捐款条款和条件事项达成的口头或书面协议。如本网络捐款条款和条件内容与${str}其他公开规则内容相冲突,则以本网络捐款条款和条件内容为准。
</div>
<div style="margin: 1rem 0; line-height: 1.75;">
如果本网络捐款条款和条件条文因任何原因被认定是违法、无效或者丧失执行力,该条文将从本网络捐款条款和条件中删除,而其余条款的效力则不受影响。<br/>
</div>
<div style="margin: 1rem 0; line-height: 1.75;">
${str}未能执行本网络捐款条款和条件中的任何条款的行为不应被解释为放弃当前或未来对该条款的权利,也不会影响${str}日后要求执行该条款的权利。${str}明确提出放弃本网络捐款条款和条件中的规定、条件或要求不构成放弃追究未来与此规定、条件或要求相一致的责任。
</div>
</div>
<div style="height: 5rem;"></div>
`
export default html;
<!--
* @Date: 2022-06-17 17:17:13
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-17 20:39:34
* @FilePath: /tswj/src/components/DonateFlower/agreement.vue
* @Description: 文件描述
-->
<template>
<div class="agreement-page">
<div class="title">捐赠协议</div>
<div class="sub">
感谢您对中国公益慈善事业的关心与支持!上海市儿童基金会面向所有具有公开募捐资质的慈善组织提供服务。上海市儿童基金会始终秉持着最高的合规性要求,因此,只要您点击“同意”或“接受”,您的行为就已表示您无条件接受并遵守“本网络捐赠条款和条件”以及上海市儿童基金会不时公布的“其他公开规则”。
</div>
<div class="content">
<div class="title-content">1. 资格规定</div>
<div class="text-content">
您声明您是符合中华人民共和国法律规定的具有完全民事行为能力的自然人。
</div>
</div>
<div class="content">
<div class="title-content">2. 捐赠财产</div>
<div class="text-content">您同意依照《公益事业捐赠法》的相关规定,自愿无偿地通过网络向上海市儿童基金会平台上具有公开募捐资质的慈善组织捐赠财产用于公益事业。</div>
<div class="text-content">您声明您用于捐赠的财产是您合法持有并有权处分的财产。</div>
<div class="text-content">
您声明您已经了解《公益事业捐赠法》、《民法典》中关干财产捐赠的相关规定,明确同意不会撤销或部分撤销对上海市儿童基金会平台上具有公开募捐资质的慈善组织作出的捐赠。
</div>
<div class="text-content">
您声明您知晓并同意,上海市儿童基金会平台上具有公开募捐资质的慈善组织可以为公益事业之目的合理审慎地自主决定捐赠财产的实际受助对象、以及具体使用的领域、金额、时间。
</div>
</div>
<div class="content">
<div class="title-content">3. 争议解决和法律适用</div>
<div class="text-content">除非另有明确约定,否则,本网络捐款条款和条件适用中华人民共和国法律,并排除一切冲突法原则的适用。</div>
<div class="text-content">
如果各方无法通过协商解决争端,您和上海市儿童基金会平台上具有公开募捐资质的慈善组织,也即善款接收方,均有权向有管辖权的人民法院提起诉讼以解决争议,由此产生的诉讼费、律师费、公证费等由败诉方承担。
</div>
</div>
<div class="content">
<div class="title-content">4. 其他</div>
<div class="text-content">
本网络捐款条款和条件构成您与上海市儿童基金会之间就本网络捐款条款和条件约定事项的完整和唯一的协议,并取代就本网络捐款条款和条件事项达成的口头或书面协议。如本网络捐款条款和条件内容与上海市儿童基金会其他公开规则内容相冲突,则以本网络捐款条款和条件内容为准。
</div>
</div>
<div class="content">
如果本网络捐款条款和条件条文因任何原因被认定是违法、无效或者丧失执行力,该条文将从本网络捐款条款和条件中删除,而其余条款的效力则不受影响。
</div>
<div class="content">
上海市儿童基金会未能执行本网络捐款条款和条件中的任何条款的行为不应被解释为放弃当前或未来对该条款的权利,也不会影响上海市儿童基金会日后要求执行该条款的权利。上海市儿童基金会明确提出放弃本网络捐款条款和条件中的规定、条件或要求不构成放弃追究未来与此规定、条件或要求相一致的责任。
</div>
<div style="height: 5rem;" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Cookies, $, _, axios, storeToRefs, mainStore, Toast, useTitle } from '@/utils/generatePackage.js'
//import { } from '@/utils/generateModules.js'
//import { } from '@/utils/generateIcons.js'
//import { } from '@/composables'
const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);
</script>
<style lang="less" scoped>
.agreement-page {
padding: 1rem;
.title {
text-align: center;
font-weight: bold;
margin-bottom: 1rem;
font-size: 1.25rem;
color: #11D2B1;
}
.sub {
text-align: justify;
color: #231815;
font-size: 1.05rem;
line-height: 1.5;
}
.content {
margin: 1rem 0;
line-height: 1.75;
.title-content {
margin-bottom: 0.5rem;
}
.text-content {
margin-bottom: 0.5rem;
line-height: 1.75;
}
}
}
</style>
<!--
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-05-31 22:09:58
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-02-08 13:45:09
* @FilePath: /tswj/src/components/DonateFlower/index.vue
* @Description: 捐花组件
-->
<template>
<van-popup v-model:show="show" :close-on-click-overlay="false" round position="bottom"
:style="{ height: popupHeight, zIndex: 3000 }">
<div class="donate-wrapper">
<div class="donate-title">
您所有捐助的爱心
<van-icon :name="icon_flower" />
都将用于多民族语言发展
</div>
<div class="shortcut-choose-wrapper">
<van-row gutter="10">
<van-col v-for="(v, index) in defaultOptions" :key="index" span="8">
<div :class="['base-item', donate_number === +index ? 'checked-item' : 'uncheck-item']"
@click="selectDonate(+index)">
{{ v }}
</div>
</van-col>
</van-row>
</div>
<!-- <div style="background: #FFFFFF; border-radius: 3px; border: 1px solid #713610; padding: 0.5rem 1rem; margin: 0.5rem 1rem; text-align: center;">更多花花</div> -->
<div class="donate-number">
<van-row v-if="more_donate">
<van-col span="18" style="text-align: right;">
<van-field v-model="donate_number" style="border: 1px solid #11D2B1; padding: 0.4rem 1rem;" type="digit"
input-align="center" label="" placeholder="请输入花花数量" />
</van-col>
<van-col span="6" style="line-height: 2.5;">&nbsp;朵花花</van-col>
</van-row>
<div v-else style="border: 1px solid #11D2B1; padding: 0.4rem 1rem;" @click="more_donate = true">更多花花</div>
</div>
<div v-if="item.kg_id" class="donate-name">
<van-row>
<van-col span="4" style="line-height: 2;">幼儿园</van-col>
<van-col span="18">
<div class="donate-text">{{ item.kg_name }}</div>
</van-col>
</van-row>
</div>
<div v-if="item.perf_id" class="donate-name">
<van-row>
<van-col span="4" style="line-height: 2;">助力人</van-col>
<van-col span="18">
<div class="donate-text">{{ item.perf_name }}</div>
</van-col>
</van-row>
</div>
<div class="donate-name">
<van-row>
<van-col span="4" style="line-height: 2;">捐赠人<span style="color: red;">*</span></van-col>
<van-col class="donate-input" span="18">
<input v-model="item.donate_name" type="text" style="border: 0; padding: 0.25rem;">
</van-col>
</van-row>
</div>
<van-row class="agree-wrapper">
<van-col span="12" offset="8">
<div class="agree-content">
<div class="btn">
<van-checkbox v-model="agreed" checked-color="#ee0a24">同意&nbsp;</van-checkbox>
</div>
<div class="text">
<span @click="showDA=true">捐赠协议</span>
</div>
</div>
</van-col>
</van-row>
<div class="donate-handle">
<div class="donate-cancel">
<my-button type="plain" @on-click="cancelDonate">取消</my-button>
</div>
<div class="donate-imd">
<my-button type="primary" @on-click="donateFlower">确定</my-button>
</div>
</div>
</div>
</van-popup>
<div class="popup-wrapper">
<van-popup v-model:show="showDA" position="bottom" :style="{ height: '100%', zIndex: 4000 }">
<agreement />
<div class="bottom-btn" @click="showDA=false">
<div class="text">关闭</div>
</div>
</van-popup>
</div>
</template>
<script setup>
import { icon_flower } from '@/utils/generateIcons'
import MyButton from '@/components/MyButton/index.vue'
import agreement from './agreement.vue'
import { ref, watch, nextTick } from 'vue'
import { $, Toast } from '@/utils/generatePackage'
import { addDonateAPI } from '@/api/C/donate.js'
// import { wxJsAPI } from '@/api/wx/config'
// import { wxPayAPI } from '@/api/wx/pay'
// import wx from 'weixin-js-sdk'
const props = defineProps({
item: Object,
showPopup: Boolean
})
const emit = defineEmits(['on-close']);
let donate_number = ref(1);
const donateFlower = () => {
if (!agreed.value) {
Toast.fail('请先查看捐赠协议,勾选同意!');
return false;
}
if (!props.item.donate_name) {
Toast.fail('捐赠人姓名不能为空!');
return false;
}
// 业务逻辑调整,有值就传值显示
let params = {
qty: donate_number.value,
donate_name: props.item.donate_name,
perf_id: props.item.perf_id,
kg_id: props.item.kg_id,
};
addDonate(params);
}
const addDonate = async (params) => {
const { data } = await addDonateAPI(params);
if (data.id) {
closeBtn();
// TAG: 微信支付
wechatPay(data.id);
}
}
const show = ref(false);
let popupHeight = ref('100%');
watch(() => props.showPopup, (v) => {
// 现在新需求只要钱,多余步骤先屏蔽掉。
// // 如果没有默认儿童需要用户确认一次
// if (v && !props.item.perf_id) {
// Dialog.confirm({
// title: '温馨提示',
// message: '默认儿童为空, 是否继续助力!',
// confirmButtonColor: '#713610'
// })
// .then(() => {
// show.value = v;
// // DOM调整后,把弹出框高度设定到适合高度,配合不同分辨率效果
// nextTick(() => {
// let height = $('.donate-wrapper').height();
// popupHeight.value = height + 10 + 'px';
// })
// })
// .catch(() => {
// // 取消按钮回调
// closeBtn();
// });
// return false;
// }
show.value = v;
// DOM调整后,把弹出框高度设定到适合高度,配合不同分辨率效果
nextTick(() => {
let height = $('.donate-wrapper').height();
popupHeight.value = height + 10 + 'px';
})
})
const closeBtn = () => {
emit('on-close', false)
show.value = false;
}
const cancelDonate = () => {
closeBtn();
}
// TAG: 微信支付
const wechatPay = (id) => {
const base = 'http://voice.onwall.cn/WxpayAPI/voice_pay/jsapi_voice.php'
location.href = `${base}?i=${id}`
}
const more_donate = ref(false);
// 选择捐赠数量
const selectDonate = (index) => {
donate_number.value = index
more_donate.value = false
}
const defaultOptions = ref({
1: '1朵花花',
3: '3朵花花',
5: '5朵花花',
});
// 捐赠协议
const agreed = ref(false);
// 显示捐赠协议弹框
const showDA = ref(false);
</script>
<style lang="less" scoped>
.donate-wrapper {
.donate-title {
padding: 1rem 1rem 0 1rem;
color: #999999;
text-align: center;
}
.shortcut-choose-wrapper {
padding: 1rem;
.base-item {
text-align: center;
padding: 0.5rem 0;
}
.checked-item {
color: @base-font-color;
background: @base-color;
border-radius: 3px;
border: 1px solid @base-color;
}
.uncheck-item {
color: @base-color;
background: @base-font-color;
border-radius: 3px;
border: 1px solid @base-color;
}
}
.donate-name {
padding: 1rem;
.donate-text {
background: #F4F4F4;
border-radius: 3px;
padding: 0.5rem;
margin-left: 5%;
width: 95%;
}
.donate-input {
border: 1px solid #B9B9B9;
border-radius: 5px;
padding: 0.2rem;
margin-left: 1rem;
}
}
.agree-wrapper {
margin: 1rem 0;
.agree-content {
display: flex;
align-items: center;
box-sizing: content-box;
.btn {
display: flex;
flex-direction: column;
justify-content: center;
}
.text {
display: flex;
flex-direction: column;
justify-content: center;
span {
text-decoration: underline;
color: #11D2B1;
}
}
}
}
.donate-number {
padding: 1rem;
padding-top: 0;
text-align: center;
color: #11D2B1;
}
.donate-handle {
overflow: auto;
.donate-cancel {
width: 49%;
float: left;
}
.donate-imd {
width: 50%;
float: left;
}
}
}
.button {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
padding: 0 0.5rem;
}
.bottom-btn {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: white;
box-shadow: 0px -2px 4px 0px rgba(0, 0, 0, 0.07);
.text {
text-align: center;
padding: 0.7rem;
margin: 0.8rem;
font-size: 1rem;
font-weight: bold;
border-radius: 24px;
// border: 1px solid F7F7F7;
color: @base-font-color;
background-color: @base-color;
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.06);
}
}
.popup-wrapper {
.agreementHtml {
padding: 1rem;
}
}
</style>
<!--
* @Date: 2022-06-25 03:29:05
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-25 15:50:08
* @FilePath: /tswj/src/components/FlowerIcon/index.vue
* @Description: 文件描述
-->
<template>
<div :class="[type === 'center' ? 'global-center' : 'flower']">
<van-icon :name="icon_flower" color="#c5c5c5" size="1.25rem" :style="{ verticalAlign: align }" />
<span :style="{ color, fontSize }">&nbsp;{{ qty }}</span>
</div>
</template>
<script setup>
import { icon_flower } from '@/utils/generateIcons.js'
const props = defineProps({
qty: {
type: Number,
default: 0
},
color: String,
align: String,
type: String,
fontSize: String
})
</script>
<style lang="less" scoped>
.flower {
text-align: center;
position: absolute;
top: 40%;
right: 0.5rem;
color: #713610;
}
</style>
<template>
<!-- 滑块验证功能 -->
<div class="sliderModel" v-if="visible">
<div class="cont">
<div class="title">滑块验证</div>
<div class="slider-refresh">
<span @click="handleRefresh">刷新</span>
<span @click="handleClose">关闭</span>
</div>
<!-- canvas 图片 -->
<div class="imgWrap">
<canvas ref="sliderBlock" class="slider-block"></canvas>
<canvas ref="codeImg" class="code-img"></canvas>
</div>
<!-- 滑块 -->
<div class="sliderBox">
<div class="sliderF">
<div class="sliderS" @touchstart.prevent="handleTouch">
<div class="btn">&gt;&gt;</div>
</div>
</div>
<div class="bgC">
{{ tips }}
<div class="bgC_left"></div>
</div>
</div>
</div>
</div>
</template>
<script>
import img1 from './images/1.jpg'
import img2 from './images/2.jpg'
import img3 from './images/3.jpg'
import img4 from './images/4.jpg'
import img5 from './images/5.jpg'
import img6 from './images/6.jpg'
export default {
props: {
isShow: {
type: Boolean,
default: false
},
options: {
// 传入的参数不影响组件
type: Object,
default: () => ({})
},
imgList: { // 背景图片
type: Array,
default: () => {
return [img1, img2, img3, img4, img5, img6]
}
}
},
data() {
return {
// 滑块x轴数据
slider: {
mx: 0,
bx: 0
},
tips: '',
visible: false,
mainDom: '',
blockDom: ''
}
},
watch: {
isShow: {
handler(newVal) {
this.visible = newVal
if (newVal === true) {
this.tips = '拖动左边滑块完成上方拼图'
this.$nextTick(() => {
this.getDom()
this.canvasInit()
})
}
},
immediate: true
}
},
methods: {
// 获取 dom
getDom() {
this.mainDom = this.$refs.codeImg
this.blockDom = this.$refs.sliderBlock
},
handleClose() {
this.$emit('on-close', false)
},
// 刷新
handleRefresh() {
this.canvasInit()
},
// 移动端事件
handleTouch(e) {
const ev = e || window.event
const dom = ev.target // dom元素
const downCoordinate = {
x: ev.touches[0].pageX,
y: ev.touches[0].pageY
}
// 正确的滑块数据
const checkx = Number(this.slider.mx) - 0
// x轴数据
let x = 0
const move = (moveEV) => {
x = moveEV.touches[0].pageX - downCoordinate.x
// //y = moveEV.y - downCoordinate.y;
if (x >= 251 || x <= 0) return false
dom.style.left = x + 'px'
// dom.style.top = y + "px";
this.blockDom.style.left = x + 'px'
}
const up = () => {
document.removeEventListener('touchmove', move)
document.removeEventListener('touchend', up)
dom.style.left = ''
// console.log(x, checkx)
const max = checkx - 5
const min = checkx - 15
// 允许正负误差1
if ((max >= x && x >= min) || x === checkx) {
this.tips = '验证成功'
this.$emit('done', this.options.type)
} else {
this.tips = '验证失败,请重试'
this.blockDom.style.left = 0
this.canvasInit()
}
}
document.addEventListener('touchmove', move)
document.addEventListener('touchend', up)
},
// 拼图验证码初始化
canvasInit() {
// 生成指定区间的随机数
const random = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min)
}
// x: 254, y: 109
const mx = random(127, 230)
const bx = random(10, 128)
const y = random(10, 99)
this.slider = { mx, bx }
this.draw(mx, bx, y)
},
draw(mx = 200, bx = 20, y = 50) {
const bg = this.mainDom.getContext('2d')
const block = this.blockDom.getContext('2d')
const width = this.mainDom.width
const height = this.mainDom.height
// 重新赋值,让canvas进行重新绘制
this.blockDom.height = height
this.mainDom.height = height
this.mainDom.width = width
// 随机背景图片
const randomImg = () => {
const num = Math.floor(Math.random() * this.imgList.length)
return this.imgList[num]
}
const imgsrc = randomImg()
const img = document.createElement('img')
img.style.objectFit = 'scale-down'
img.src = imgsrc
img.onload = () => {
bg.drawImage(img, 0, 0, width, height)
block.drawImage(img, 0, 0, width, height)
const ImageData = block.getImageData(mx, y, width, height)
block.putImageData(ImageData, 0, y)
}
const mainxy = { x: mx, y: y, r: 9 }
const blockxy = { x: mx, y: y, r: 9 }
this.drawBlock(bg, mainxy, 'fill')
this.drawBlock(block, blockxy, 'clip')
},
// 绘制拼图
drawBlock(ctx, xy = { x: 254, y: 109, r: 9 }, type) {
const x = xy.x
const y = xy.y
const r = xy.r
const w = 40
const PI = Math.PI
// 绘制
ctx.beginPath()
// left
// ctx.moveTo(x, y)
// top
ctx.arc(x + (w + 5) / 2, y, r, -PI, 0.15, true)
ctx.lineTo(x + w + 5, y)
// right
ctx.arc(x + w + 5, y + w / 2, r, 1.5 * PI, 0.5 * PI, false)
ctx.lineTo(x + w + 5, y + w)
// bottom
ctx.arc(x + (w + 5) / 2, y + w, r, 0, PI, false)
ctx.lineTo(x, y + w)
ctx.arc(x, y + w / 2, r, 0.5 * PI, 1.5 * PI, true)
ctx.lineTo(x, y)
// 修饰,没有会看不出效果
ctx.lineWidth = 1
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'
ctx.stroke()
ctx[type]()
ctx.globalCompositeOperation = 'xor'
}
}
}
</script>
<style scoped lang="less">
.sliderModel {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.title {
width: 100%;
height: 60px;
font-size: 18px;
color: #333;
display: flex;
align-items: center;
justify-content: center;
}
.cont {
position: relative;
background: #fff;
width: 300px;
border-radius: 8px;
overflow: hidden;
padding-bottom: 10px;
}
.imgWrap {
position: relative;
width: 280px;
height: 150px;
margin: 0 auto;
overflow: hidden;
.code-img,
.slider-block {
border-radius: 8px;
height: inherit;
}
.code-img {
width: 280px;
margin: 0 auto;
}
.slider-block {
position: absolute;
z-index: 4000;
left: 0;
}
}
.slider-refresh {
font-size: 14px;
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
color: green;
span {
padding: 0 2px;
}
}
.img {
display: block;
width: 100%;
height: 100%;
}
.sliderOver {
position: absolute;
left: 0;
top: 0;
width: 50px;
height: 50px;
background: #ddd;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.3);
}
.smartImg {
position: absolute;
z-index: 2;
left: 0;
top: 0;
width: 50px;
height: 50px;
overflow: hidden;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
.simg {
position: absolute;
display: block;
width: 280px;
height: 150px;
}
.sliderBox {
width: 280px;
margin: 15px auto 0;
height: 36px;
position: relative;
}
.sliderF {
width: 100%;
height: 100%;
z-index: 3;
}
.sliderS {
cursor: pointer;
position: absolute;
left: 0;
top: 0;
z-index: 2;
height: 36px;
width: 36px;
border-radius: 36px;
display: flex;
justify-content: center;
align-items: center;
}
.icon {
width: 20px;
height: 20px;
}
.bgC {
position: absolute;
z-index: 1;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 100%;
height: 30px;
border-radius: 30px;
line-height: 30px;
font-size: 14px;
color: #999999;
box-shadow: inset 0 0 4px #ccc;
text-align: center;
overflow: hidden;
}
.bgC_left {
position: absolute;
left: 0px;
top: 50%;
transform: translateY(-50%);
width: 0;
height: 28px;
border-top-left-radius: 28px;
border-bottom-left-radius: 28px;
line-height: 28px;
font-size: 14px;
background-color: #eee;
box-shadow: inset 0 0 4px #ccc;
text-align: center;
}
.showMessage {
text-align: center;
font-size: 14px;
height: 30px;
line-height: 30px;
}
#closeBtn {
position: fixed;
z-index: 10;
bottom: 10px;
left: 50%;
}
.btn {
width: 36px;
height: 36px;
position: absolute;
border: 1px solid #ccc;
cursor: move;
font-family: "宋体";
text-align: center;
line-height: 36px;
background-color: #fff;
user-select: none;
color: #666;
font-size: 16px;
}
</style>
<!--
* @Date: 2022-06-20 11:35:50
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-21 19:10:41
* @FilePath: /tswj/src/components/LocalismBox/index.vue
* @Description: 调整作品方言弹框组件
-->
<template>
<van-overlay :show="show" z-index="1000" :lock-scroll="false">
<div class="wrapper" @click.stop>
<div class="block">
<div class="close">
<van-icon name="close" color="#FFFFFF" @click="handleClose" />
</div>
<div class="localism-title">{{ title }}</div>
<div id="localism-list" class="localism-list">
<div v-for="(item, index) in localismList" :id="item.id" :key="index"
:class="[item.checked ? 'checked' : 'unchecked', 'van-hairline--top-bottom', 'localism-item']"
@click="setLocalism(item.id)">
{{ item.localism }}
</div>
<div v-show="show_localism" id="localism-bottom" class="van-hairline--top-bottom localism-item">
<input v-model="localism_name" placeholder="请输入新增的方言名称" style="border: 0; text-align: center;"
@blur="onBlur">
</div>
</div>
<div class="bar">
<div class="button">
<my-button type="plain" @on-click="addLocalism">新增方言</my-button>
</div>
<div class="button">
<my-button type="primary" @on-click="handleSubmit">确定</my-button>
</div>
</div>
</div>
</div>
</van-overlay>
</template>
<script setup>
import { ref, watch, nextTick } from 'vue'
import MyButton from '@/components/MyButton/index.vue'
import { localismListModiAPI, addLocalismAPI, modifyProdLocalismAPI } from '@/api/B/localism'
import { Toast, Dialog } from '@/utils/generatePackage.js'
const props = defineProps({
showLocalism: Boolean,
id: Number,
localism: String,
title: String,
})
const emit = defineEmits(['on-close', 'on-submit']);
/**
* 滚动到指定位置
* @param {*} id
*/
const scrollToDom = (id) => {
nextTick(() => {
document.getElementById(id)?.scrollIntoView({ block: 'center' });
})
}
// 处理语言列表数据
let localismList = ref([]);
const raw_id = ref('')
const getLocalismList = async () => {
const { data } = await localismListModiAPI();
localismList.value = data.map((item) => ({ id: item, localism: item, checked: false }))
localismList.value.forEach((item) => {
if (item.localism === props.localism) {
item.checked = true;
raw_id.value = item.id
}
});
scrollToDom(props.localism);
}
let show = ref(false);
watch(() => props.showLocalism, (v) => {
show.value = v;
if (v) {
getLocalismList()
}
})
// 点击行选择方言
const setLocalism = (v) => {
localismList.value.forEach((item) => {
item.checked = false;
});
localismList.value.forEach((item) => {
if (item.id === v) {
item.checked = true;
}
});
}
// 新增方言
const show_localism = ref(false)
const localism_name = ref('')
const addLocalism = () => { // 滚动到底部,显示新增输入框
show_localism.value = true;
const id = localismList.value[localismList.value.length - 1]['id'];
scrollToDom(id);
}
const onBlur = () => { // 失焦保存录入方言
if (!localism_name.value) return false;
Dialog.confirm({
title: '温馨提示',
message: `是否确认新增${localism_name.value}?`,
confirmButtonColor: '#11D2B1'
})
.then(async () => {
const { code } = await addLocalismAPI({ localism_name: localism_name.value });
if (code) {
Toast.success('新增成功!');
localismList.value.forEach((item) => {
item.checked = false;
});
localismList.value.push({ id: localism_name.value, localism: localism_name.value, checked: true });
scrollToDom(localism_name.value);
}
show_localism.value = false;
localism_name.value = '';
})
.catch(() => {
// on cancel
});
}
const clearAll = () => {
show.value = false
show_localism.value = false
localismList.value = []
localism_name.value = ''
}
const handleClose = () => { // 关闭提示框回调
clearAll();
emit('on-close', false)
}
const handleSubmit = () => { // 提交选择方言
const localism = localismList.value.filter((item) => item.checked === true);
// 原始和提交不一致请求接口提交
if (raw_id.value !== localism[0].id) {
Dialog.confirm({
title: '温馨提示',
message: `是否确认设置方言为${localism[0].id}?`,
confirmButtonColor: '#11D2B1'
})
.then(async () => {
const { code } = await modifyProdLocalismAPI({ prod_id: props.id, localism_name: localism[0].id });
if (code) {
Toast.success('更新成功!');
clearAll();
emit('on-submit', localism[0].id);
}
})
.catch(() => {
// on cancel
});
} else {
clearAll();
emit('on-submit', raw_id.value);
}
}
</script>
<style lang="less" scoped>
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: auto;
text-align: center;
}
.block {
width: 80%;
// height: 25rem;
background-color: #fff;
border-radius: 10px;
padding: 1rem 0;
position: relative;
margin-top: 1rem;
margin-bottom: 5rem;
.close {
position: absolute;
top: -2rem;
right: 1rem;
font-size: 1.5rem;
}
}
.localism-title {
font-size: 1.05rem;
}
.localism-list {
margin-top: 2rem;
height: 20rem;
overflow: scroll;
}
.bar {
display: flex;
align-items: center;
box-sizing: content-box;
background-color: white;
padding: 0.7rem;
.button {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
padding: 0 0.5rem;
}
}
.localism-item {
padding: 1rem;
text-align: center;
}
.checked {
color: #11d2b1;
}
.unchecked {
color: gray;
}
input::-webkit-input-placeholder {
color: #B0B0B0;
}
</style>
<template>
<div class="login-section">
<van-config-provider :theme-vars="themeVars">
<van-form ref="form" @submit="onSubmit">
<van-cell-group inset style="border: 1px solid #EAEAEA;">
<van-field v-if="use_widget" v-model="phone" name="phone" label="手机号" placeholder="手机号" readonly clickable
:rules="[{ validator, message: '请输入正确手机号' }]"
@touchstart.stop="showKeyboard" />
<van-field v-else v-model="phone" name="phone" label="手机号" placeholder="手机号"
:rules="[{ validator, message: '请输入正确手机号' }]" />
<van-field v-model="code" center clearable name="code" type="digit" label="短信验证码" placeholder="请输入短信验证码"
:formatter="formatter" :rules="[{ required: true, message: '请填写验证码' }]">
<template #button>
<van-button @click="sendCode" v-if="countDown.current.value.total === limit" size="small" type="primary"
:disabled="disabled">
<span>发送验证码</span>
</van-button>
<van-button v-else size="small" type="primary" :disabled="disabled">
<span>{{ countDown.current.value.seconds }} 秒重新发送</span>
</van-button>
</template>
</van-field>
</van-cell-group>
</van-form>
</van-config-provider>
</div>
<van-number-keyboard v-model="phone" :show="keyboard_show" :maxlength="11" @blur="onBlur" />
<!-- 图片滑块验证 -->
<image-slider-verify :isShow="sliderShow" @done="handleConfirm" @on-close="handleClose">
</image-slider-verify>
</template>
<script setup>
import ImageSliderVerify from '@/components/ImageSliderVerify/index.vue'
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useCountDown } from '@vant/use';
import { wxInfo } from '@/utils/tools';
import { styleColor } from '@/constant.js';
import { Cookies, $, _, axios, storeToRefs, mainStore, Toast, useTitle } from '@/utils/generatePackage.js'
//import { } from '@/utils/generateModules.js'
//import { } from '@/utils/generateIcons.js'
//import { } from '@/composables'
const $route = useRoute();
const $router = useRouter();
const emit = defineEmits(['on-submit'])
const form = ref(null);
const submit = () => {
let valid = form.value.validate();
valid
.then(() => {
form.value.submit();
})
.catch(error => {
console.error(error);
Toast({
message: '请检查后再次提交',
icon: 'cross',
});
})
}
defineExpose({
submit
})
const themeVars = {
buttonPrimaryBackground: styleColor.baseColor,
buttonPrimaryBorderColor: styleColor.baseColor,
buttonPrimaryColor: styleColor.baseFontColor,
CellVerticalPadding: '14px'
};
const onSubmit = () => {
emit('on-submit', {
phone: phone.value,
code: code.value,
})
}
// 判断是否显示控件
let use_widget = ref(true);
/**
* 手机号码校验
* 函数返回 true 表示校验通过,false 表示不通过
* @param {*} val
*/
const validator = (val) => {
let flag = false;
// 简单判断手机号位数
if (/1\d{10}/.test(val) && phone.value.length === 11) {
disabled.value = false;
flag = true;
} else {
disabled.value = true;
flag = false;
}
return flag
};
const phone = ref('');
const code = ref('');
// TAG: 开发环境测试数据
if (import.meta.env.DEV) {
phone.value = import.meta.env.VITE_ID
code.value = import.meta.env.VITE_PIN
}
onMounted(() => {
/**
* 判断微信环境看是否弹出控件框
* 桌面微信直接输入
* 其他环境弹出输入框
*/
if (wxInfo().isiOS || wxInfo().isAndroid) {
use_widget.value = true;
} else {
use_widget.value = false;
}
// 判断微信授权状态,进入页面时未授权需要授权跳转
if (!Cookies.get('PHPSESSID')) {
$router.replace({
path: '/auth',
query: {
href: location.hash,
userType: 'b'
}
});
}
})
// 手机号输入控件控制
const keyboard_show = ref(false);
const showKeyboard = () => { // 弹出数字弹框
keyboard_show.value = true;
};
const onBlur = () => { // 数字键盘失焦回调
keyboard_show.value = false;
if (phone.value.length === 11) {
disabled.value = false;
}
};
// 设置发送短信倒计时
// TAG: vant 自带倒计时函数
const limit = ref(60000); // 配置倒计时秒数
const countDown = useCountDown({
// 倒计时 24 小时
time: limit.value,
onFinish: () => {
countDown.reset();
}
});
const sendCode = () => { // 发送验证码
countDown.start();
axios.post('/srv/?a=bind_phone&t=get_code', {
phone: phone.value
})
.then(res => {
if (res.data.code === 1) {
Toast.success('发送成功');
} else {
console.warn(res.data);
if (!res.data.show) return false;
Toast({
message: res.data.msg,
icon: 'close',
});
}
})
.catch(err => {
console.error(err);
})
};
const disabled = ref(true);
// 过滤输入的数字 只能四位
const formatter = (value) => value.substring(0, 4);
// 滑块验证成功后回调
const sliderShow = ref(false);
const handleConfirm = (val) => {
sliderShow.value = false
console.warn('验证成功');
}
const handleClose = () => {
sliderShow.value = false
}
const imageVerify = () => {
sliderShow.value = true;
}
</script>
<style lang="less" scoped>
</style>
<template>
<login-box ref="form" @on-submit="onSubmit"></login-box>
<div class="btn" @click="submit">
登&nbsp;录
</div>
</template>
<script setup>
import LoginBox from '@/components/LoginBox'
import { onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import {
Cookies,
$,
_,
axios,
storeToRefs,
mainStore,
Toast,
useTitle,
} from '@/utils/generatePackage.js';
//import { } from '@/utils/generateModules.js'
//import { } from '@/utils/generateIcons.js'
//import { } from '@/composables'
const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);
const form = ref(null);
const submit = () => {
form.value.submit();
}
const onSubmit = (values) => {
axios.post('/srv/?a=b_login', {
phone: values.phone,
pin: values.code,
})
.then(res => {
if (res.data.code === 1) {
$router.push({
path: '/business/index'
});
} else {
console.warn(res.data);
Toast({
message: res.data.msg,
icon: 'close',
});
}
})
.catch(err => {
console.error(err);
})
};
</script>
<style
lang="less"
scoped>
</style>
<template>
<div
@click="goToDetail(item, $router)"
class="banner">
{{ item.kg_name }} | {{ item.localism_type }}
</div>
</template>
<script setup>
import { goToDetail } from './methods'
const props = defineProps({
item: Object,
});
</script>
<style lang="less" scoped>
.banner {
color: #999999;
padding: 0.5rem 1rem
}
</style>
<!--
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-05-23 13:42:35
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-24 12:19:30
* @FilePath: /tswj/src/components/MuiVideo/index.vue
* @Description:
-->
<template>
<div class="video-wrapper">
<div v-if="type === 'video'">
<div :id="'mui-player-' + item.id" class="video-div" />
</div>
<div v-else>
<status :item="item" />
<div :id="'mui-player-' + item.id" class="video-div" />
<video-bar :item="item" :bar-type="1" />
<banner v-if="type === 'bookDetail'" :item="item" />
</div>
</div>
</template>
<script setup>
/**
* 视频组件通用模块
*/
import 'mui-player/dist/mui-player.min.css';
import MuiPlayer from 'mui-player';
import { onMounted } from 'vue';
import banner from './banner';
import videoBar from './videoBar';
import status from './status';
import { useEventListener } from '@/composables';
// 视频基础属性
const props = defineProps({
item: Object,
type: String,
});
// 视频播放事件回调
const emit = defineEmits(['on-play']);
onMounted(() => {
const mp = new MuiPlayer({
container: '#mui-player-' + props.item.id,
title: props.item.title,
src: props.item.video,
poster: props.item.cover,
autoFit: false,
videoAttribute: [
// 声明启用同层播放, 不让会自动全屏播放
{ attrKey: 'webkit-playsinline', attrValue: 'webkit-playsinline' },
{ attrKey: 'playsinline', attrValue: 'playsinline' },
{ attrKey: 'x5-video-player-type', attrValue: 'h5-page' },
],
});
const video = mp.video();
// 监听原生video事件
useEventListener(video, 'play', (event) => {
emit('on-play', {
event,
props: props.item,
});
});
// 配置16:9高度比
const width = document.getElementById('mui-player-' + props.item.id).clientWidth;
const height = (width * 9) / 16;
document.getElementById('mui-player-' + props.item.id).height = height;
});
</script>
<style lang="less" scoped>
.video-wrapper {
position: relative;
margin: 1rem 0;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.13);
.video-div {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.video-bar {
color: #713610;
padding: 1rem;
padding-bottom: 0.5rem;
}
}
</style>
/*
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-05-23 14:33:37
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-02 13:14:23
* @FilePath: /tswj/src/components/MuiVideo/methods.js
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { prodActionAPI } from '@/api/C/prod.js'
import { Toast } from 'vant';
export const goToDetail = ({ id, book_id, type, perf_id }, $router) => {
$router.push({
path: '/client/videoDetail',
query: {
prod_id: id,
book_id,
type, // 特殊标识,判断入口 为keepAlive使用
perf_id
}
});
}
export const setComment = ({ id, book_id }, $router) => {
$router.push({
path: '/client/videoDetail/comment',
query: {
prod_id: id,
book_id
}
});
}
export const prodAction = async (action_type, prod_id) => {
const { msg } = await prodActionAPI({ action_type, prod_id });
if (msg === `${action_type}-add-OK`) { // 动作操作成功
if (action_type === 'favor') {
Toast('收藏成功');
}
if (action_type === 'like') {
Toast('点赞成功');
}
} else { // 取消操作
if (action_type !== 'play') {
Toast('取消成功');
}
}
return true;
}
export default [
{
"id": 315040,
"localism_type": "普通话",
"cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_74_1653095780.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:3kZFL-LK1CP0k8Iiewr6d4_kCas=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzc0XzE2NTMwOTU3ODAucG5nKiIsIkUiOjE5Njg2NjUzMTB9",
"video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_20_1653095775.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:2gmHTr1opoFCeuYjRPvxabflLZM=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzIwXzE2NTMwOTU3NzUubXA0KiIsIkUiOjE5Njg2NjUzMTB9",
"note": "Z",
"status": "apply",
"check_note": null,
"kg_id": 314048,
"kg_name": "上海市杨浦区科技幼儿园",
"perf_id": 314880,
"name": "王申羽",
"avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
"book_id": 314483,
"book_name": "《它们从哪里来》",
"comment_num": 0,
"like_num": 0,
"favor_num": 0,
"play_num": 0,
"is_like": 0
},
{
"id": 315041,
"localism_type": "沪语",
"cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_81_1653095788.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:km1-cl6r3X9BwYekqZHyo4kzbWo=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzgxXzE2NTMwOTU3ODgucG5nKiIsIkUiOjE5Njg2NjUzMTB9",
"video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_80_1653095786.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:9kK2Nf96oLV0_t5dKXLnEC0QBLM=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzgwXzE2NTMwOTU3ODYubXA0KiIsIkUiOjE5Njg2NjUzMTB9",
"note": "Z",
"status": "apply",
"check_note": null,
"kg_id": 314048,
"kg_name": "上海市杨浦区科技幼儿园",
"perf_id": 314880,
"name": "王申羽",
"avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
"book_id": 314483,
"book_name": "《它们从哪里来》",
"comment_num": 0,
"like_num": 0,
"favor_num": 0,
"play_num": 0,
"is_like": 0
},
{
"id": 315036,
"localism_type": "普通话",
"cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_74_1653095579.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:TbYtglmAQrmsCpKulfIkdjrYsjc=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzc0XzE2NTMwOTU1NzkucG5nKiIsIkUiOjE5Njg2NjUxMjd9",
"video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_20_1653095576.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:CIcNhT-g-lmC1mv253Nnoz7EVKQ=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzIwXzE2NTMwOTU1NzYubXA0KiIsIkUiOjE5Njg2NjUxMjd9",
"note": "Z",
"status": "apply",
"check_note": null,
"kg_id": 314048,
"kg_name": "上海市杨浦区科技幼儿园",
"perf_id": 314880,
"name": "王申羽",
"avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
"book_id": 314483,
"book_name": "《它们从哪里来》",
"comment_num": 0,
"like_num": 0,
"favor_num": 0,
"play_num": 0,
"is_like": 0
},
{
"id": 315037,
"localism_type": "沪语",
"cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_81_1653095586.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:MaeVQ6AUQNzcWKDaMExdr7pFpZU=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzgxXzE2NTMwOTU1ODYucG5nKiIsIkUiOjE5Njg2NjUxMjd9",
"video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_80_1653095584.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:Ox6WCAeG4ImThDZaoEc2v3mrcb8=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzgwXzE2NTMwOTU1ODQubXA0KiIsIkUiOjE5Njg2NjUxMjd9",
"note": "Z",
"status": "apply",
"check_note": null,
"kg_id": 314048,
"kg_name": "上海市杨浦区科技幼儿园",
"perf_id": 314880,
"name": "王申羽",
"avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
"book_id": 314483,
"book_name": "《它们从哪里来》",
"comment_num": 0,
"like_num": 0,
"favor_num": 0,
"play_num": 0,
"is_like": 0
},
{
"id": 315033,
"localism_type": "沪语",
"cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_81_1653095145.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:dzCQntH7aBmPGYyxvmiOTwgr8WY=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzgxXzE2NTMwOTUxNDUucG5nKiIsIkUiOjE5Njg2NjQ2Njh9",
"video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_80_1653095142.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:IygI0aLGuFR_DEgRtmsbNjAhVS0=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzgwXzE2NTMwOTUxNDIubXA0KiIsIkUiOjE5Njg2NjQ2Njh9",
"note": "2",
"status": "apply",
"check_note": null,
"kg_id": 314048,
"kg_name": "上海市杨浦区科技幼儿园",
"perf_id": 314880,
"name": "王申羽",
"avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
"book_id": 314483,
"book_name": "《它们从哪里来》",
"comment_num": 0,
"like_num": 0,
"favor_num": 0,
"play_num": 0,
"is_like": 0
},
{
"id": 315032,
"localism_type": "普通话",
"cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_74_1653095136.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:PLQaeVUgpZ-rgvD37DE0Yq9W5-o=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzc0XzE2NTMwOTUxMzYucG5nKiIsIkUiOjE5Njg2NjQ2Njh9",
"video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_20_1653095134.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:1fnWx_k8UBYJJ_XjunwCp93N7GI=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzIwXzE2NTMwOTUxMzQubXA0KiIsIkUiOjE5Njg2NjQ2Njh9",
"note": "1",
"status": "apply",
"check_note": null,
"kg_id": 314048,
"kg_name": "上海市杨浦区科技幼儿园",
"perf_id": 314880,
"name": "王申羽",
"avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
"book_id": 314483,
"book_name": "《它们从哪里来》",
"comment_num": 0,
"like_num": 0,
"favor_num": 0,
"play_num": 0,
"is_like": 0
},
{
"id": 315024,
"localism_type": "普通话",
"cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_74_1653094501.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:0fDZL_C8youKy-IftSFwyr3c36g=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzc0XzE2NTMwOTQ1MDEucG5nKiIsIkUiOjE5Njg2NjQwNDF9",
"video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_20_1653094499.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:BHD2B4D_CDzu9Tw48SvPAgfuJ90=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzIwXzE2NTMwOTQ0OTkubXA0KiIsIkUiOjE5Njg2NjQwNDF9",
"note": "1",
"status": "apply",
"check_note": null,
"kg_id": 314048,
"kg_name": "上海市杨浦区科技幼儿园",
"perf_id": 314880,
"name": "王申羽",
"avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
"book_id": 314483,
"book_name": "《它们从哪里来》",
"comment_num": 0,
"like_num": 0,
"favor_num": 0,
"play_num": 0,
"is_like": 0
},
{
"id": 315025,
"localism_type": "沪语",
"cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_81_1653094509.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:qSjAFNlU3o6SduJzStxg49A8SK0=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzgxXzE2NTMwOTQ1MDkucG5nKiIsIkUiOjE5Njg2NjQwNDF9",
"video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_80_1653094506.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:2Khn4cIo2mJo6Ty6qRvLB0Uus48=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzgwXzE2NTMwOTQ1MDYubXA0KiIsIkUiOjE5Njg2NjQwNDF9",
"note": "2",
"status": "apply",
"check_note": null,
"kg_id": 314048,
"kg_name": "上海市杨浦区科技幼儿园",
"perf_id": 314880,
"name": "王申羽",
"avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
"book_id": 314483,
"book_name": "《它们从哪里来》",
"comment_num": 0,
"like_num": 0,
"favor_num": 0,
"play_num": 0,
"is_like": 0
},
{
"id": 315021,
"localism_type": "沪语",
"cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_81_1653094433.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:AfRlQR8HGWV0y9kAeEAKd7jhj6I=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzgxXzE2NTMwOTQ0MzMucG5nKiIsIkUiOjE5Njg2NjM5NTV9",
"video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_80_1653094431.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:PjpJx-lC2kILJPBlpPJuJMqslrE=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzgwXzE2NTMwOTQ0MzEubXA0KiIsIkUiOjE5Njg2NjM5NTV9",
"note": "Z",
"status": "apply",
"check_note": null,
"kg_id": 314048,
"kg_name": "上海市杨浦区科技幼儿园",
"perf_id": 314880,
"name": "王申羽",
"avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
"book_id": 314483,
"book_name": "《它们从哪里来》",
"comment_num": 0,
"like_num": 0,
"favor_num": 0,
"play_num": 0,
"is_like": 0
},
{
"id": 315020,
"localism_type": "普通话",
"cover": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FgYwXBnKzvOcLNoojqrpwYGsppQI_field_74_1653094425.png?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:9BOVLskMMLE-pbqmb5rK_5qncNQ=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GZ1l3WEJuS3p2T2NMTm9vanFycHdZR3NwcFFJX2ZpZWxkXzc0XzE2NTMwOTQ0MjUucG5nKiIsIkUiOjE5Njg2NjM5NTV9",
"video": "https://gd-pri.jinshujufiles.com/en/NAGn1D/FjyggcxvTB9HJ6yFBv4i7-HDuW8F_field_20_1653094423.mp4?token=7NK_Z1IEoKaIY6I9RXzO4b9uQPwuwdvnlGbzHZmF:uEz_Ke-BuliSvPQkvdwOAe42Hyk=:eyJTIjoiZ2QtcHJpLmppbnNodWp1ZmlsZXMuY29tL2VuL05BR24xRC9GanlnZ2N4dlRCOUhKNnlGQnY0aTctSER1VzhGX2ZpZWxkXzIwXzE2NTMwOTQ0MjMubXA0KiIsIkUiOjE5Njg2NjM5NTV9",
"note": "Z",
"status": "apply",
"check_note": null,
"kg_id": 314048,
"kg_name": "上海市杨浦区科技幼儿园",
"perf_id": 314880,
"name": "王申羽",
"avatar": "https://cdn.ipadbiz.cn/ipadbiz/Njg3ZjY5Yzk2NGJhOWQ2NGNjODJmMzBhYWM2NmE3NDJhOTI2NTljNA.jpeg",
"book_id": 314483,
"book_name": "《它们从哪里来》",
"comment_num": 0,
"like_num": 0,
"favor_num": 0,
"play_num": 0,
"is_like": 0
}
]
<template>
<div class="status">
<van-image v-if="item.status === 'enable'" round width="6rem" height="6rem" style="vertical-align: bottom;" :src="icon_enable" />
<van-image v-if="item.status === 'disable'" round width="6rem" height="6rem" style="vertical-align: bottom;" :src="icon_refuse" />
<van-image v-if="item.status === 'apply'" round width="6rem" height="6rem" style="vertical-align: bottom;" :src="icon_apply" />
</div>
</template>
<script setup>
import icon_refuse from '@images/icon-jujue@2x.png'
import icon_apply from '@images/icon-shenhe@2x.png'
import icon_enable from '@images/icon-tongguo@2x.png'
const props = defineProps({
item: Object,
});
</script>
<style lang="less" scoped>
.status {
position: absolute;
top: 0;
right: 0;
z-index: 999;
}
</style>
<!--
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-05-23 18:00:39
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-02 17:41:38
* @FilePath: /tswj/src/components/MuiVideo/test.vue
* @Description: 视频播放通用组件演示组件
* @Description: type: video 为纯视频播放框,bookDetail为定制模式
-->
<template>
<mui-video
v-for="(item, index) in mock"
:key="index"
:item="item"
type="video"
@on-play="onPlay"
/>
</template>
<script setup>
import mock from '@/components/MuiVideo/mock';
import MuiVideo from '@/components/MuiVideo/index';
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import {
Cookies,
$,
_,
axios,
storeToRefs,
mainStore,
Toast,
useTitle,
} from '@/utils/generatePackage.js';
//import { } from '@/utils/generateModules.js'
//import { } from '@/utils/generateIcons.js'
//import { } from '@/composables'
const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);
const onPlay = ({ event, props }) => {
console.warn(event);
console.warn(props);
};
</script>
<style
lang="less"
scoped></style>
<!--
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-05-23 14:30:57
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-07 15:26:03
* @FilePath: /tswj/src/components/MuiVideo/videoBar.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<div v-if="barType === 1" class="video-bar">
<van-row>
<van-col span="12" @click="goToDetail(item, $router)">
<van-image round width="2rem" height="2rem" style="vertical-align: middle;"
:src="item.avatar ? item.avatar : icon_avatar" />&nbsp;
<span style="font-size: 1.05rem;vertical-align: middle;">{{ item.name }}</span>
</van-col>
<van-col span="12">
<div style="padding: 0.25rem; padding-top: 0.75rem; text-align: right;">
<span @click="setComment(item, $router)">
<van-icon :name="icon_liuyan" size="1.2rem" style="vertical-align: bottom;" />
{{ item.comment_num }}
</span>
&nbsp;&nbsp;&nbsp;
<span @click="handleAction('like', detail.id)">
<van-icon v-if="!detail.is_like" :name="icon_dianzan1" size="1.2rem" style="vertical-align: bottom;" />
<van-icon v-else :name="icon_dianzan2" size="1.2rem" style="vertical-align: bottom;" />
{{ detail.like_num }}
</span>
</div>
</van-col>
</van-row>
</div>
<div v-if="barType === 2" class="video-bar">
<van-row style="text-align: center;">
<van-col span="7">
<span style="color: #777777;">
{{ item.play_num }}次播放
</span>
</van-col>
<van-col span="1" style="color: #EEEDED;">
|
</van-col>
<van-col span="8">
<span @click="handleAction('favor', detail.id)">
<van-icon v-if="!detail.is_favor" :name="icon_shoucang1" size="1.2rem" style="vertical-align: bottom;" />
<van-icon v-else :name="icon_shoucang2" size="1.2rem" style="vertical-align: bottom;" />
{{ detail.favor_num }}
</span>
</van-col>
<van-col span="1" style="color: #EEEDED;">
|
</van-col>
<van-col span="7">
<span @click="handleAction('like', detail.id)">
<van-icon v-if="!detail.is_like" :name="icon_dianzan1" size="1.2rem" style="vertical-align: bottom;" />
<van-icon v-else :name="icon_dianzan2" size="1.2rem" style="vertical-align: bottom;" />
{{ detail.like_num }}
</span>
</van-col>
</van-row>
</div>
</template>
<script setup>
import { icon_shoucang1, icon_shoucang2, icon_dianzan1, icon_dianzan2, icon_liuyan, icon_avatar } from '@/utils/generateIcons.js'
import { ref } from 'vue';
import { goToDetail, setComment, prodAction } from './methods';
import _ from 'lodash';
import { prodInfoAPI } from '@/api/C/prod.js'
const props = defineProps({
item: Object,
barType: Number
});
const detail = ref({});
detail.value = _.cloneDeep(props.item);
const handleAction = async (type, id) => {
if (await prodAction(type, id)) {
getProductDetail(type, id)
}
}
// 查询作品详情
async function getProductDetail(action_type, prod_id) {
const { data } = await prodInfoAPI({ prod_id });
// 更新详情显示
detail.value[`is_${action_type}`] = data[`is_${action_type}`];
detail.value[`${action_type}_num`] = data[`${action_type}_num`];
}
</script>
<style lang="less" scoped>
.video-bar {
color: #713610;
padding: 1rem;
padding-bottom: 0.5rem;
}
</style>
<!--
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-04-21 10:04:58
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-18 19:28:22
* @FilePath: /tswj/src/components/MyButton/index.vue
* @Description:
-->
<template>
<div v-if="type === 'primary'" class="button-primary" @click="handle">
<slot />
</div>
<div v-if="type === 'plain'" class="button-plain" @click="handle">
<slot />
</div>
<div v-if="type === 'custom'" :style="customStyle" class="button-custom" @click="handle">
<slot />
</div>
</template>
<script setup>
import { onMounted } from 'vue'
const props = defineProps({
type: String,
customStyle: Object,
disable: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['on-click']);
const handle = () => {
emit('on-click', '')
}
onMounted(() => {
})
</script>
<script>
export default {
data () {
return {
}
},
mounted () {
},
methods: {
}
}
</script>
<style lang="less" scoped>
.button-primary {
width: auto;
height: auto;
text-align: center;
padding: 0.6rem;
margin: 0.5rem;
font-size: 1rem;
background: #colors[base-color];
border-radius: 24px;
border: 1px solid #colors[base-color];
color: #colors[base-font-color];
font-weight: bold;
}
.button-plain {
width: auto;
height: auto;
text-align: center;
padding: 0.6rem;
margin: 0.5rem;
font-size: 1rem;
background: @base-font-color;
border-radius: 24px;
border: 1px solid @base-color;
color: @base-color;
font-weight: bold;
}
.button-custom {
width: auto;
height: auto;
text-align: center;
padding: 0.6rem;
font-size: 1rem;
border-radius: 24px;
border: 1px solid;
}
</style>
<template>
<van-overlay :show="showNotice" z-index="1000">
<div class="wrapper" @click.stop>
<div class="block">
<div style="position: absolute; top: -2rem; right: 1rem; font-size: 1.5rem;">
<van-icon name="close" color="#FFFFFF" @click="handleClose" />
</div>
<div>
<van-image width="100" height="100" :src="icon_notice" />
<p style="margin: 1rem; font-size: 1.15rem; font-weight: bold; color: #222222;">温馨提示</p>
</div>
<slot></slot>
<div style="margin: 3rem 0;">
<my-button @on-click="handleSubmit" type="primary">{{ text }}</my-button>
</div>
</div>
</div>
</van-overlay>
</template>
<script setup>
import MyButton from '@/components/MyButton/index.vue'
import icon_notice from '@images/que-tishi@2x.png'
import { ref, reactive, onMounted } from 'vue'
const props = defineProps({
showNotice: Boolean,
text: String
})
const emit = defineEmits(['on-close', 'on-submit']);
let show = props.showNotice;
const handleClose = () => { // 关闭提示框回调
show = false
emit('on-close', false)
}
// 底部按钮
const handleSubmit = () => {
emit('on-submit', false)
}
onMounted(() => {
})
</script>
<script>
export default {
data() {
return {
}
},
mounted() {
},
methods: {
}
}
</script>
<style lang="less" scoped>
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: auto;
text-align: center;
}
.block {
width: 80%;
// height: 25rem;
background-color: #fff;
border-radius: 10px;
padding: 1rem;
position: relative;
margin-top: 1rem;
margin-bottom: 5rem;
}
</style>
\ No newline at end of file
<!--
* @Date: 2022-06-30 17:48:46
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-30 18:11:08
* @FilePath: /tswj/src/components/NoticeOverlayTest/index.vue
* @Description: 文件描述
-->
<template>
<van-overlay :show="showNotice" z-index="1000">
<div class="wrapper" @click.stop>
<div class="block">
<div style="position: absolute; top: -2rem; right: 1rem; font-size: 1.5rem;">
<van-icon name="close" color="#FFFFFF" @click="handleClose" />
</div>
<div>
<van-image width="100" height="100" :src="icon_notice" />
<p style="margin: 1rem; font-size: 1.15rem; font-weight: bold; color: #222222;">温馨提示</p>
</div>
<div style="color: #333333;">
<div v-html="noticeHtml" />
</div>
<div style="margin: 3rem 0;">
<my-button type="primary" @on-click="handleSubmit">{{ noticeText }}</my-button>
</div>
</div>
</div>
</van-overlay>
</template>
<script setup>
import MyButton from '@/components/MyButton/index.vue'
import icon_notice from '@images/que-tishi@2x.png'
import { USER_STATUS } from '@/constant'
const props = defineProps({
show: Boolean,
type: {
type: Number,
default: -1,
}
})
const emit = defineEmits(['on-close', 'on-submit']);
const handleClose = () => { // 关闭提示框回调
showNotice.value = false
emit('on-close', false)
}
// 底部按钮
const handleSubmit = () => {
emit('on-submit', false)
}
const noticeText = ref('')
const noticeHtml = ref('')
const showNotice = ref(false)
// 监听弹出框
watch(() => props.show, (v) => {
showNotice.value = v;
if (props.type === USER_STATUS.NON_VERIFIED) {
noticeText.value = '前往认证'
noticeHtml.value = `
<p>您还没有实名认证</p>
<p>请前往个人中心进行实名认证</p>
`
} else if (props.type === USER_STATUS.NON_DEFAULT_CHILD) {
noticeText.value = '前往新增'
noticeHtml.value = `
<p>您还没有新增儿童</p>
<p>请前往个人中心进行新增</p>
`
}
})
</script>
<style lang="less" scoped>
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: auto;
text-align: center;
}
.block {
width: 80%;
// height: 25rem;
background-color: #fff;
border-radius: 10px;
padding: 1rem;
position: relative;
margin-top: 1rem;
margin-bottom: 5rem;
}
</style>
<!--
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-05-30 10:20:34
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-25 20:13:49
* @FilePath: /tswj/src/components/RankingItem/index.vue
-->
<template>
<div class="wrapper">
<van-row>
<van-col span="2" class="rank">
<div :class="[{ 'text': indexKey >= 3 }, { 'avatar': indexKey < 3 }]">
<van-icon v-if="indexKey < 3" :name="iconRanking(indexKey)" size="1.75rem" />
<span v-else>{{ indexKey + 1 }}&nbsp;</span>
</div>
</van-col>
<van-col span="18" class="content-wrapper">
<div :class="['height3rem', 'kg-name']" @click="go('/client/chooseBook', { kg_id: rankInfo.id })">
<van-row align="center" justify="center" style="position: relative; top: 50%; transform: translateY(-50%);">
<van-col span="4">
<van-image round width="3rem" height="3rem" :src="rankInfo.logo ? rankInfo.logo : icon_logo"
style="vertical-align: text-bottom;" />
</van-col>
<van-col span="20">
<div v-if="rankInfo.multi_name" style="margin-left: 0.5rem;">
<p>{{ rankInfo.multi_name[0] }}<br>{{ rankInfo.multi_name[1] }}</p>
</div>
<p v-else style="margin-left: 0.5rem;">
{{ rankInfo.name }}
</p>
</van-col>
</van-row>
</div>
</van-col>
<van-col>
<flower-icon type="right" :qty="rankInfo.qty" align="text-bottom"
@click="go('/client/donateList', { kg_id: rankInfo.id })" />
</van-col>
</van-row>
</div>
</template>
<script setup>
import { icon_logo, icon_ranking1, icon_ranking2, icon_ranking3 } from '@/utils/generateIcons.js'
import { ref } from 'vue'
import _ from 'lodash'
import FlowerIcon from '@/components/FlowerIcon'
import { useGo } from '@/hooks/useGo'
const go = useGo()
const iconRanking = (index) => {
switch (index) {
case 0:
return icon_ranking1
case 1:
return icon_ranking2
case 2:
return icon_ranking3
default:
return 0
}
}
const onFlowerClick = (v) => {
if (v) {
go('/client/donateList', { kg_id: rankInfo.value.id })
}
}
// eslint-disable-next-line no-unused-vars
const props = defineProps({
item: {
type: Object,
default(rawProps) {
return rawProps
}
},
indexKey: {
type: Number,
default(rawProps) {
return rawProps
}
}
})
const emit = defineEmits(['on-icon-click']);
const handle = () => {
emit('on-icon-click', '')
}
const rankInfo = ref('');
rankInfo.value = _.cloneDeep(props.item);
// 有空格分割name
if (rankInfo.value.name.indexOf(' ') > -1) {
rankInfo.value.multi_name = rankInfo.value.name.split(' ');
}
</script>
<style lang="less" scoped>
.wrapper {
margin: 1rem 0;
background-color: #FFF;
position: relative;
.rank {
position: relative;
.avatar {
position: absolute;
top: 0;
left: 20%;
}
.text {
position: absolute;
top: 0.5rem;
left: 40%;
color: #84909F;
}
}
.content-wrapper {
padding: 1rem 0.5rem 1rem 0;
}
.flower {
text-align: center;
position: absolute;
top: 40%;
right: 0.5rem;
color: #713610;
}
.kg-name {
position: relative;
height: 3rem;
}
.height3rem {
height: 3rem;
}
.height6rem {
height: 6rem;
}
}
</style>
<template>
<div class="wrapper">
<div class="w-image">
<van-image class="van-hairline--surround avatar" round :src="avatar ? avatar : icon_avatar">
<template #error>加载失败</template>
</van-image>
</div>
<div class="text-wrapper">
<van-row align="center" justify="center" class="center-content">
<van-col span="20" style="color: #713610;">
<slot />
</van-col>
<van-col span="4" style="text-align: center;" @click="handle">
<van-icon name="arrow" color="#c5c5c5" size="1.25rem" />
</van-col>
</van-row>
</div>
</div>
</template>
<script setup>
import icon_avatar from '@images/que-logo@2x.png'
// eslint-disable-next-line no-unused-vars
const props = defineProps({
avatar: {
type: String,
default: ''
}
})
const emit = defineEmits(['on-icon-click']);
const handle = () => {
emit('on-icon-click', '')
}
</script>
<style lang="less" scoped>
.wrapper {
position: relative;
background-color: #fff;
margin: 2rem;
margin-right: 0;
margin-left: 4rem;
height: 4rem;
box-shadow: 0px 0px 4px 0px rgba(73, 156, 255, 0.2);
.w-image {
width: 4rem;
height: 4rem;
border-radius: 50%;
background-color: #FFF;
position: absolute;
left: -2rem;
.avatar {
position: absolute;
left: 0.1rem;
top: 0.1rem;
width: 3.8rem;
height: 3.8rem;
}
}
.text-wrapper {
height: 100%;
text-align: left;
// line-height: 4rem;
padding-left: 3rem;
.center-content{
position: relative; top: 50%; transform: translateY(-50%);
}
}
}
</style>
<!--
* @Date: 2022-05-11 11:19:14
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-24 13:19:43
* @FilePath: /tswj/src/components/ShortcutFixed/index.vue
* @Description: 文件描述
-->
<template>
<div class="shortcut-page" :style="customStyle">
<van-image v-if="isHome" class="icon-box" :src="icon_home" @click="toHome" />
<van-image v-if="isMe" class="icon-box" :src="icon_me" @click="toMe" />
<van-image v-if="isRank" class="icon-box" :src="icon_rank" @click="toRank" />
</div>
</template>
<script setup>
import Cookies from 'js-cookie'
import { icon_me, icon_home, icon_rank } from '@/utils/generateIcons.js'
</script>
<script>
// FIXME: VUE2写法
export default {
props: ['type', 'item', 'customStyle'],
data() {
return {
userType: Cookies.get('userType') ? Cookies.get('userType') : ''
}
},
computed: {
isHome() {
return this.item.indexOf('home') !== -1 ? true : false
},
isMe() {
return this.item.indexOf('me') !== -1 ? true : false
},
isRank() {
return this.item.indexOf('rank') !== -1 ? true : false
},
},
mounted() {
},
methods: {
toHome() {
// 返回首页
if (this.type === 'B') { // 服务端判断
this.$router.push({
path: '/business/index'
});
} else {
// C 端返回首页需要判断是否,访客或客户
switch (this.userType) {
case 'visitor':
this.$router.push({
path: '/client/chooseBook'
});
break;
case 'client':
this.$router.push({
path: '/client/chooseSchool'
});
break;
default:
this.$router.push({
path: '/client/index'
});
break;
}
}
},
toMe() {
if (this.type === 'B') { // 服务端判断
this.$router.push({
path: '/business/me'
});
} else {
this.$router.push({
path: '/me/index'
});
}
},
toRank () {
this.$router.push({
path: '/client/rankList',
query: {
kg_id: this.$route.query.kg_id
}
});
}
}
}
</script>
<style lang="less" scoped>
.shortcut-page {
position: fixed;
bottom: 18rem;
right: 1rem;
overflow: auto;
z-index: 999;
width: 3rem;
}
.icon-box {
width: 3rem;
height: 3rem;
margin-bottom: 0.5rem;
}
</style>
<template>
<div class="video-wrapper">
<div :id="'mui-player-' + item.id" class="video-div" />
<div class="normal-module">
<div class="video-bar">
<van-row>
<van-col span="15" @click="setComment">
<van-image round width="2rem" height="2rem" class="avatar" :src="item.avatar ? item.avatar : icon_avatar" />
<span class="text">{{ item.name }}</span>
</van-col>
<van-col span="9">
<div style="padding: 0.25rem; padding-top: 0.5rem; text-align: right;">
<span @click="setComment">
<van-icon :name="icon_liuyan" size="1.2rem" style="vertical-align: bottom;" />
{{ item.comment_num }}
</span>
&nbsp;&nbsp;&nbsp;
<span @click="handleAction('like', detail.id)">
<van-icon :name="!detail.is_like ? icon_dianzan1 : icon_dianzan2" size="1.2rem"
style="vertical-align: bottom;" />
{{ detail.like_num }}
</span>
</div>
</van-col>
</van-row>
</div>
<slot name="bookDetailSub" />
</div>
<div class="audit-module" />
</div>
</template>
<script setup>
/**
* 视频组件通用模块
*/
import { icon_dianzan1, icon_dianzan2, icon_liuyan, icon_avatar } from '@/utils/generateIcons.js'
import { ref, onMounted } from 'vue'
import { _, Toast } from '@/utils/generatePackage.js'
import { useRouter } from 'vue-router'
import 'mui-player/dist/mui-player.min.css'
import MuiPlayer from 'mui-player'
import { prodActionAPI, prodInfoAPI } from '@/api/C/prod.js'
import { useDebounce } from '@/hooks/useDebounce.js'
import { DEFAULT_COVER } from '@/constant'
const $router = useRouter();
const props = defineProps({
item: {
type: Object,
default(rawProps) {
return rawProps
}
}
})
// 作品用户操作
/**
* 两个请求顺序执行,处理直接写在请求函数里面似乎有点问题,get请求数据似乎无法顺利渲染显示
*/
// TAG:防抖处理
const handleAction = useDebounce(async (action_type, prod_id) => {
const { msg } = await prodActionAPI({ action_type, prod_id });
if (msg === `${action_type}-add-OK`) { // 动作操作成功
if (action_type === 'favor') {
Toast('收藏成功');
}
if (action_type === 'like') {
Toast('点赞成功');
}
} else { // 取消操作,播放动作不提示
if (action_type !== 'play') {
Toast('取消成功');
}
}
getProductDetail(action_type, prod_id); // 更新信息
});
// 查询作品详情
const getProductDetail = async (action_type, prod_id) => {
const { data } = await prodInfoAPI({ prod_id });
// 更新详情显示
detail.value[`is_${action_type}`] = data[`is_${action_type}`];
detail.value[`${action_type}_num`] = data[`${action_type}_num`];
}
let detail = ref({});
onMounted(() => {
const mp = new MuiPlayer({
container: '#mui-player-' + props.item.id,
title: props.item.title,
src: props.item.video,
poster: props.item.cover ? props.item.cover : DEFAULT_COVER,
// poster: DEFAULT_COVER,
autoFit: false,
videoAttribute: [ // 声明启用同层播放, 不让会自动全屏播放
{ attrKey: 'webkit-playsinline', attrValue: 'webkit-playsinline' },
{ attrKey: 'playsinline', attrValue: 'playsinline' },
{ attrKey: 'x5-video-player-type', attrValue: 'h5-page' },
]
})
detail.value = _.cloneDeep(props.item);
const video = mp.video();
// 监听原生video事件
video && video.addEventListener('play', function () {
handleAction('play', props.item.id)
});
// 配置16:9高度比
const width = document.getElementById('mui-player-' + props.item.id).clientWidth;
const height = (width * 9) / 16;
document.getElementById('mui-player-' + props.item.id).height = height;
});
const goTo = () => { // 跳转作品详情页
$router.push({
path: '/client/videoDetail',
query: {
prod_id: props.item.id,
book_id: props.item.book_id,
type: props.item.type, // 特殊标识,判断入口 为keepAlive使用
perf_id: props.item.perf_id
}
});
}
const setComment = () => {
$router.push({
path: '/client/videoDetail/comment',
query: {
prod_id: props.item.id,
book_id: props.item.book_id,
type: props.item.type, // 特殊标识,判断入口 为keepAlive使用
}
});
}
</script>
<style lang="less" scoped>
.video-wrapper {
margin: 1rem;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.13);
.video-div {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.video-bar {
color: #713610;
padding: 1rem;
padding-bottom: 0.5rem;
.avatar {
vertical-align: middle;
}
.text {
font-size: 1.05rem;vertical-align: middle; display: inline-block; padding-left: 1rem;
}
}
}
</style>
<template>
<div class="video-wrapper">
<div class="video-div" :id="'mui-player-' + item.id"></div>
<div class="video-bar">
<van-row style="text-align: center;">
<van-col span="7">
<span style="color: #777777;">
{{ item.play_num }}次播放
</span>
</van-col>
<van-col span="1" style="color: #EEEDED;">
|
</van-col>
<van-col span="8">
<span @click="handleAction('favor', detail.id)">
<van-icon v-if="!detail.is_favor" :name="icon_shoucang1" size="1.2rem" style="vertical-align: bottom;" />
<van-icon v-else :name="icon_shoucang2" size="1.2rem" style="vertical-align: bottom;" />
{{ detail.favor_num }}
</span>
</van-col>
<van-col span="1" style="color: #EEEDED;">
|
</van-col>
<van-col span="7">
<span @click="handleAction('like', detail.id)">
<van-icon v-if="!detail.is_like" :name="icon_dianzan1" size="1.2rem" style="vertical-align: bottom;" />
<van-icon v-else :name="icon_dianzan2" size="1.2rem" style="vertical-align: bottom;" />
{{ detail.like_num }}
</span>
</van-col>
</van-row>
</div>
</div>
</template>
<script setup>
/**
* 该组件是放在作品详情页头部
*/
import icon_dianzan1 from '@images/icon-dianzan01@2x.png'
import icon_dianzan2 from '@images/icon-dianzan02@2x.png'
import icon_shoucang1 from '@images/icon-shoucang01@2x.png'
import icon_shoucang2 from '@images/icon-shoucang02@2x.png'
import 'mui-player/dist/mui-player.min.css'
import MuiPlayer from 'mui-player'
import _ from 'lodash';
import { DEFAULT_COVER } from '@/constant'
</script>
<script>
// FIXME: VUE2写法
import mixin from 'common/mixin';
export default {
mixins: [mixin.likeFn],
props: ['item'],
data() {
return {
detail: {
mp: ''
}
}
},
created() {
},
mounted() {
setTimeout(() => {
var mp = new MuiPlayer({
container: '#mui-player-' + this.item.id,
title: this.item.title,
src: this.item.video,
poster: this.item.cover ? this.item.cover : DEFAULT_COVER,
// poster: DEFAULT_COVER,
autoFit: false,
videoAttribute: [ // 声明启用同层播放, 不让会自动全屏播放
{attrKey:'webkit-playsinline',attrValue:'webkit-playsinline'},
{attrKey:'playsinline',attrValue:'playsinline'},
{attrKey:'x5-video-player-type',attrValue:'h5-page'},
]
})
this.detail = _.cloneDeep(this.item);
// 传回数据
this.$emit('on-click', this.detail);
this.mp = mp;
var video = mp.video();
// 监听原生video事件
var _this = this;
video && video.addEventListener('play', function (event) {
_this.handleAction('play', _this.item.id)
});
}, 500)
// 配置16:9高度比
const width = document.getElementById('mui-player-' + this.item.id).clientWidth;
const height = (width * 9) / 16;
document.getElementById('mui-player-' + this.item.id).height = height;
},
methods: {
}
}
</script>
<style lang="less" scoped>
.video-wrapper {
.video-div {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.video-bar {
color: #713610;
padding: 1rem;
padding-bottom: 0.5rem;
}
}
</style>
<template>
<div></div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
const props = defineProps({
type: String
})
onMounted(() => {
})
</script>
<script>
export default {
data () {
return {
}
},
mounted () {
},
methods: {
}
}
</script>
<style lang="less" scoped>
</style>
\ No newline at end of file
// ts script setup 写法
<template>
<div class="img-verify">
<canvas ref="verify" :width="state.width" :height="state.height" @click="handleDraw" />
</div>
</template>
<script setup lang="ts">
import { reactive, onMounted, ref } from 'vue'
const verify = ref({} as HTMLCanvasElement)
const state = reactive({
pool: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', // 字符串
width: 120,
height: 40,
imgCode: ''
})
onMounted(() => {
// 初始化绘制图片验证码
state.imgCode = draw()
})
// 点击图片重新绘制
const handleDraw = () => {
state.imgCode = draw()
}
// 随机数
const randomNum = (min: number, max: number) => {
return parseInt((Math.random() * (max - min) + min + '') as string)
}
// 随机颜色
const randomColor = (min: number, max: number) => {
const r = randomNum(min, max)
const g = randomNum(min, max)
const b = randomNum(min, max)
return `rgb(${r},${g},${b})`
}
// 绘制图片
const draw = () => {
// 3.填充背景颜色,背景颜色要浅一点
const ctx = verify.value.getContext('2d') as any
// 填充颜色
ctx.fillStyle = randomColor(180, 230)
// 填充的位置
ctx.fillRect(0, 0, state.width, state.height)
// 定义paramText
let imgCode = ''
// 4.随机产生字符串,并且随机旋转
for (let i = 0; i < 4; i++) {
// 随机的四个字
const text = state.pool[randomNum(0, state.pool.length)]
imgCode += text
// 随机的字体大小
const fontSize = randomNum(18, 40)
// 字体随机的旋转角度
const deg = randomNum(-30, 30)
/*
* 绘制文字并让四个文字在不同的位置显示的思路 :
* 1、定义字体
* 2、定义对齐方式
* 3、填充不同的颜色
* 4、保存当前的状态(以防止以上的状态受影响)
* 5、平移translate()
* 6、旋转 rotate()
* 7、填充文字
* 8、restore出栈
* */
ctx.font = fontSize + 'px Simhei'
ctx.textBaseline = 'top'
ctx.fillStyle = randomColor(80, 150)
/*
* save() 方法把当前状态的一份拷贝压入到一个保存图像状态的栈中。
* 这就允许您临时地改变图像状态,
* 然后,通过调用 restore() 来恢复以前的值。
* save是入栈,restore是出栈。
* 用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。 restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
*
* */
ctx.save()
ctx.translate(30 * i + 15, 15)
ctx.rotate((deg * Math.PI) / 180)
// fillText() 方法在画布上绘制填色的文本。文本的默认颜色是黑色。
// 请使用 font 属性来定义字体和字号,并使用 fillStyle 属性以另一种颜色/渐变来渲染文本。
// context.fillText(text,x,y,maxWidth);
ctx.fillText(text, -15 + 5, -15)
ctx.restore()
}
// 5.随机产生5条干扰线,干扰线的颜色要浅一点
for (let i = 0; i < 5; i++) {
ctx.beginPath()
ctx.moveTo(randomNum(0, state.width), randomNum(0, state.height))
ctx.lineTo(randomNum(0, state.width), randomNum(0, state.height))
ctx.strokeStyle = randomColor(180, 230)
ctx.closePath()
ctx.stroke()
}
// 6.随机产生40个干扰的小点
for (let i = 0; i < 40; i++) {
ctx.beginPath()
ctx.arc(randomNum(0, state.width), randomNum(0, state.height), 1, 0, 2 * Math.PI)
ctx.closePath()
ctx.fillStyle = randomColor(150, 200)
ctx.fill()
}
return imgCode
}
</script>
<style type="text/css">
.img-verify canvas {
cursor: pointer;
}
</style>
/*
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-05-17 12:13:13
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-12 22:50:29
* @FilePath: /tswj/src/composables/index.js
* @Description:
*/
import { onMounted, onUnmounted } from 'vue'
import { useVideoList } from '@/composables/useVideoList.js'
import { useDefaultPerf } from '@/composables/useDefaultPerf.js'
import { useBookList, useAsyncBookList } from '@/composables/useBookList.js'
import { useShortcutBar } from '@/composables/useShortcutBar.js'
import { useScrollTop } from '@/composables/useScrollTop.js'
import { useMessageList } from '@/composables/useMessageList.ts'
export {
useVideoList,
useDefaultPerf,
useBookList,
useAsyncBookList,
useShortcutBar,
useScrollTop,
useMessageList,
}
/**
* 添加和清除 DOM 事件监听器
* @param {*} target
* @param {*} event
* @param {*} callback
*/
export function useEventListener(target, event, callback) {
onMounted(() => target?.addEventListener(event, callback))
onUnmounted(() => target?.removeEventListener(event, callback))
}
/*
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-05-07 17:46:54
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-12 22:55:58
* @FilePath: /tswj/src/composables/useBookList.js
* @Description:
*/
import { ref } from 'vue'
import axios from '@/utils/axios';
import { Toast } from 'vant';
import { useRoute } from 'vue-router';
import { kgBookListAPI } from '@/api/C/kg'
export const useBookList = () => {
const $route = useRoute();
const emptyStatus = ref(false);
// tslint:disable-next-line: variable-name
const kg_id = $route.query.kg_id ? $route.query.kg_id : '';
const kgInfo = ref({
id: '',
logo: '',
name: '',
multi_name: '',
book_list: []
});
if (kg_id) { // 从学校列表进入
axios.get('/srv/?a=kg_book_list', {
params: {
kg_id
}
})
.then(res => {
if (res.data.code === 1) {
kgInfo.value = res.data.data;
kgInfo.value.book_list.forEach(item => {
item.show = true; // 默认显示所有,给搜索功能留的hook
});
// 有空格分割name
if (kgInfo.value.name.indexOf(' ') > -1) {
kgInfo.value.multi_name = kgInfo.value.name.split(' ');
}
if (!kgInfo.value.book_list.length) {
emptyStatus.value = true;
}
} else {
// tslint:disable-next-line: no-console
console.warn(res);
if (!res.data.show) return false;
Toast({
icon: 'close',
message: res.data.msg
});
}
})
.catch(err => {
// tslint:disable-next-line: no-console
console.error(err);
})
} else { // 从访客进入
axios.get('/srv/?a=book_list')
.then(res => {
if (res.data.code === 1) {
res.data.data.forEach(item => {
item.show = true; // 默认显示所有,给搜索功能留的hook
});
kgInfo.value = {
book_list: res.data.data
}
if (!kgInfo.value.book_list.length) {
emptyStatus.value = true;
}
} else {
// tslint:disable-next-line: no-console
console.warn(res);
if (!res.data.show) return false;
Toast({
icon: 'close',
message: res.data.msg
});
}
})
.catch(err => {
// tslint:disable-next-line: no-console
console.error(err);
})
}
return {
kg_id,
kgInfo,
emptyStatus
}
}
// !!废弃方法
// 前端使用方式过于复杂
export const useAsyncBookList = async () => {
const $route = useRoute();
const emptyStatus = ref(false);
// tslint:disable-next-line: variable-name
const kg_id = $route.query.kg_id ? $route.query.kg_id : '';
const kgInfo = ref({
id: '',
logo: '',
name: '',
multi_name: [],
book_list: []
});
const { data } = await kgBookListAPI({ kg_id });
if (data) kgInfo.value = data;
kgInfo.value.book_list.forEach(item => {
item.show = true; // 默认显示所有,给搜索功能留的hook
});
// 有空格分割name
if (kgInfo.value.name.indexOf(' ') > -1) {
kgInfo.value.multi_name = kgInfo.value.name.split(' ');
}
if (!kgInfo.value.book_list.length) {
emptyStatus.value = true;
}
return {
kg_id,
kgInfo,
emptyStatus
}
}
/*
* @Author: hookehuyr hookehuyr@gmail.com
* @Date: 2022-05-18 14:31:26
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-05-30 10:18:19
* @FilePath: /tswj/src/composables/useDefaultPerf.js
* @Description:
*/
import { ref } from 'vue'
import axios from '@/utils/axios';
import { Toast } from 'vant';
/**
* @description: 获取默认儿童信息
* @param {*} bookId
* @param {*} params
* @param {*} avatar
* @param {*} name
* @param {*} price
* @param {*} user_id
* @param {*} perf_id
* @param {*} perf_name
* @param {*} can_upload
* @param {*} can_upload
* @param {*} can_upload
* @param {*} message
* @return {*}
*/
export const useDefaultPerf = (bookId) => {
// 金数据准备数据
const userInfo = ref({})
axios.get('/srv/?a=default_perf', {
params: {
book_id: bookId
}
})
.then(res => {
if (res.data.code === 1) {
userInfo.value = {
book_id: res.data.data.book_id,
avatar: res.data.data.book_cover,
name: res.data.data.book_name,
price: res.data.data.book_price,
user_id: res.data.data.user_id,
perf_id: res.data.data.perf_id,
perf_name: res.data.data.perf_name,
can_upload: res.data.data.can_upload, // can_upload :1=可上传,-1=用户没有实名,-2=用户没有儿童表演者
// can_upload: -1, // can_upload :1=可上传,-1=用户没有实名,-2=用户没有儿童表演者
}
} else {
console.warn(res);
if (!res.data.show) return false;
Toast({
icon: 'close',
message: res.data.msg
});
}
})
.catch(err => {
console.error(err);
});
return {
userInfo
}
}
/*
* @Date: 2022-06-22 00:07:42
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-30 13:45:54
* @FilePath: /tswj/src/composables/useLogin.js
* @Description: 文件描述
*/
import { bLoginAPI } from '@/api/B/login'
import { useRouter } from 'vue-router'
import { wxInfo } from '@/utils/tools';
import { ref } from 'vue'
import { useCountDown } from '@vant/use';
import { smsAPI } from '@/api/common'
import { Toast } from 'vant'
export const useLogin = () => {
const phone = ref('');
const code = ref('')
const refForm = ref(null);
const validateForm = () => {
const valid = refForm.value.validate();
valid
.then(() => {
refForm.value.submit();
})
.catch(error => {
console.error(error);
Toast({
message: '请检查后再次提交',
icon: 'cross',
});
})
}
/**
* 判断微信环境看是否弹出控件框
* 桌面微信直接输入
* 其他环境弹出输入框
*/
let use_widget = ref(true);
use_widget.value = !!(wxInfo().isiOS || wxInfo().isAndroid);
/**
* 手机号码校验
* 函数返回 true 表示校验通过,false 表示不通过
* @param {*} val
*/
const sms_disabled = ref(true);
const phoneValidator = (val) => {
let flag = false;
// 简单判断手机号位数
if (/1\d{10}/.test(val) && phone.value.length === 11) {
sms_disabled.value = false;
flag = true;
} else {
sms_disabled.value = true;
flag = false;
}
return flag
};
/**
* 手机号数字弹框
*/
const keyboard_show = ref(false);
const refPhone = ref(null)
const showKeyboard = () => { // 弹出数字弹框
keyboard_show.value = true;
};
const keyboardBlur = () => { // 数字键盘失焦回调
keyboard_show.value = false;
refPhone.value.validate();
};
// 设置发送短信倒计时
// TAG: vant 自带倒计时函数
const limitSeconds = ref(60000); // 配置倒计时秒数
const countDown = useCountDown({ // 配置倒计时
time: limitSeconds.value,
onFinish: () => {
countDown.reset();
}
});
const sendCode = async () => { // 发送验证码
countDown.start();
// 验证码接口
const { code } = await smsAPI({ phone: phone.value });
if (code) {
Toast.success('发送成功');
}
};
// 过滤输入的数字 只能四位
const smsFormatter = (value) => value.substring(0, 4);
/**
* 用户登录
* @param {*} phone
* @param {*} pin
*/
const $router = useRouter();
const onSubmit = async (values) => {
const { code } = await bLoginAPI({ phone: values.phone, pin: values.code })
if (code) {
$router.push({
path: '/business/index'
});
}
};
return {
phone,
code,
onSubmit,
use_widget,
sms_disabled,
phoneValidator,
keyboardBlur,
keyboard_show,
showKeyboard,
refPhone,
refForm,
validateForm,
smsFormatter,
limitSeconds,
countDown,
sendCode,
}
}
/*
* @Date: 2022-06-12 22:51:21
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-13 01:01:33
* @FilePath: /tswj/src/composables/useMessageList.ts
* @Description: 测试处理列表查询功能中的重复代码问题
* 感觉复用意义不大,这么写反而很麻烦
*/
import { Ref, ref } from 'vue'
import { myCommentAPI } from '@/api/C/me'
import _ from 'lodash'
import { commentListType } from '@/@types/message/interface';
import {
loading,
finished,
limit,
offset,
finishedTextStatus,
emptyStatus,
} from '@/composables/variable';
// scrollList的类型是个麻烦,其他地方调用时类型不一定一样
function fn(data: [], scrollList: Ref<commentListType[]>) {
// function fn(data: [], scrollList: any) {
// 数据全部加载完成
if (!data.length) {
// 加载状态结束
finished.value = true;
}
scrollList.value = [...scrollList.value, ...data];
offset.value = scrollList.value.length;
loading.value = false;
// 隐藏loading标识,空列表图标
if (!scrollList.value.length) {
finishedTextStatus.value = false;
emptyStatus.value = true;
} else {
emptyStatus.value = false;
}
return {
scrollList,
finished,
loading,
finishedTextStatus,
emptyStatus,
};
}
export const useMessageList = () => {
// 我的留言接口联调
const scrollList = ref<commentListType[]>([]);
let obj = {
scrollList,
finished,
loading,
finishedTextStatus,
emptyStatus,
};
const onLoad = async () => {
const { data } = await myCommentAPI({ limit: limit.value, offset: offset.value });
data.forEach((item: { target_name: string | null | undefined; c_action: string; c_name: string; }) => {
let arr = _.split(item.target_name, '@'); // 分割评论的动作和姓名
item.c_action = arr[0]; // 评论动作
item.c_name = arr[1]; // 评论姓名
});
// fn把重复代码抽离
obj = fn(data, scrollList);
}
return {
onLoad,
scrollList: obj.scrollList,
loading: obj.loading,
finished: obj.finished,
finishedTextStatus: obj.finishedTextStatus,
emptyStatus: obj.emptyStatus,
};
}
import { $, _, storeToRefs, mainStore } from '@/utils/generatePackage'
/**
* 页面滚动恢复
* @returns
*/
export const useScrollTop = () => {
const store = mainStore();
const resetScrollTop = (key) => {
// 嵌套滚动,执行两个,先滚外面再滚里面
_.times(2, () => {
$("html,body").animate({ "scrollTop": String(storeToRefs(store)[key].value) + 'px' });
});
}
return {
resetScrollTop,
store
}
}
/*
* @Date: 2022-06-13 17:42:32
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-18 21:14:42
* @FilePath: /tswj/src/composables/useShare.js
* @Description: 文件描述
*/
import wx from 'weixin-js-sdk';
// import { Toast } from 'vant';
/**
* @description: 微信分享功能
* @param {*} title 标题
* @param {*} desc 描述
* @param {*} imgUrl 图标
* @return {*}
*/
export const sharePage = ({title = '童声无界', desc = '共读一本书,传递一份爱。', imgUrl = 'http://voice.onwall.cn/f/voice/images/weixin_logo.jpg'}) => {
const shareData = {
title, // 分享标题
desc, // 分享描述
link: location.origin + location.pathname + location.hash, // 分享链接,该链接域名或路径必须与当前页面对应的公众号 JS 安全域名一致
imgUrl, // 分享图标
success: function () {
// console.warn('设置成功');
}
}
// 分享好友(微信好友或qq好友)
wx.updateAppMessageShareData(shareData);
// 分享到朋友圈或qq空间
wx.updateTimelineShareData(shareData);
// 分享到腾讯微博
wx.onMenuShareWeibo(shareData);
// // 获取“分享给朋友”按钮点击状态及自定义分享内容接口(即将废弃)
// wx.onMenuShareAppMessage(shareData);
// // 获取“分享到朋友圈”按钮点击状态及自定义分享内容接口(即将废弃)
// wx.onMenuShareTimeline(shareData);
// // 获取“分享到QQ”按钮点击状态及自定义分享内容接口(即将废弃)
// wx.onMenuShareQQ(shareData);
}
import { useRoute } from 'vue-router';
import { ref } from 'vue';
import { Cookies } from '@/utils/generatePackage'
export const useShortcutBar = (item) => {
const $route = useRoute();
const path = $route.path;
const isClient = Cookies.get('userType') === 'client' ? true : false; // 判断C端入口位置,访客/客户
// const case1 = ['home', 'me', 'rank'];
// const case2 = ['home'];
// const case3 = ['me'];
const shortcutItem = ref([]);
// 配置快捷跳转条
if (item) {
shortcutItem.value = item;
} else {
if (path === '/client/chooseBook') { // 爱心捐书页面
if (isClient) {
shortcutItem.value = ['home', 'me', 'rank']
} else {
shortcutItem.value = ['me']
}
}
if (path === '/client/donateCertificate') {
shortcutItem.value = ['home']
}
if (path === '/business/index') {
shortcutItem.value = ['me']
}
}
return {
shortcutItem
}
}
import { ref } from 'vue'
import axios from '@/utils/axios';
import _ from 'lodash'
import { Toast } from 'vant';
export const useUnwatchList = () => {
// 绑定页面数据
const prod_list = ref([]);
const limit = ref(10)
const offset = ref(0)
// 处理书籍下作品列表
const loading = ref(false);
const finished = ref(false);
// 因为不能让空图标提前出来的写法
const finishedTextStatus = ref(false);
const emptyStatus = ref(false);
/**
* 向下滚动查询数据
*/
const onLoad = () => {
// 异步更新数据
axios.get('/srv/?a=my_unplay', {
params: {
limit: limit.value,
offset: offset.value
}
})
.then(res => {
if (res.data.code === 1) {
prod_list.value = _.concat(prod_list.value, res.data.data.prod);
prod_list.value = _.uniqBy(prod_list.value, 'id');
_.each(prod_list.value, (item) => {
item.type = 'read-only' // 特殊标识,判断入口 为keepAlive使用
})
offset.value = prod_list.value.length;
loading.value = false;
// 数据全部加载完成
if (!res.data.data.prod.length) {
// 加载状态结束
finished.value = true;
}
// 空数据提示
if (!prod_list.value.length) {
finishedTextStatus.value = false;
emptyStatus.value = true;
}
} else {
// tslint:disable-next-line: no-console
console.warn(res);
if (!res.data.show) return false;
Toast({
icon: 'close',
message: res.data.msg
});
}
})
.catch(err => {
// tslint:disable-next-line: no-console
console.error(err);
})
};
return {
onLoad,
prod_list,
finished,
loading,
finishedTextStatus,
emptyStatus,
}
}
/*
* @Date: 2022-05-10 12:15:14
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-29 23:36:29
* @FilePath: /tswj/src/composables/useUpload.js
* @Description: 图片上传模块
*/
import { v4 as uuidv4 } from 'uuid';
import { ref, reactive } from 'vue'
import { qiniuTokenAPI, qiniuUploadAPI, saveFileAPI } from '@/api/common'
export const useUpload = () => {
let lock_btn = ref(false); // 保存按钮锁
let fileList = ref([]);
let upload_image = reactive({ src: '' });
const afterRead = async (res) => {
lock_btn.value = true; // 上传开始, 不能保存
let affix = uuidv4();
// 此时可以自行将文件上传至服务器
let dataURL = res.content;
let base64url = dataURL.slice(dataURL.indexOf(',') + 1); // 截取前缀的base64 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnoAAAJeCAYAA.......
// 获取七牛token
const { token, key, code } = await qiniuTokenAPI({ filename: `${affix}_${res.file.name}`, file: base64url });
if (code) {
const config = {
headers: {
'Content-Type': 'application/octet-stream',
'Authorization': 'UpToken ' + token, // UpToken后必须有一个 ' '(空格)
}
}
// 上传七牛服务器
const { filekey, hash, image_info } = await qiniuUploadAPI('http://upload.qiniup.com/putb64/-1/key/' + key, base64url, config)
if (filekey) {
// 保存图片
const { data } = await saveFileAPI({ filekey, hash, format: image_info.format, height: image_info.height, width: image_info.width });
upload_image.src = data.src;
lock_btn.value = false; // 头像上传完成, 打开锁
}
}
};
const beforeDelete = () => { // 删除图片回调
upload_image.src = '';
return true;
}
return {
lock_btn,
fileList,
upload_image,
afterRead,
beforeDelete
}
}
import { ref } from 'vue'
import axios from '@/utils/axios';
import { Toast } from 'vant';
export const idCard = () => {
/**
* 检查用户是否已实名认证 (实名认证后可以上传作品,留言)
*/
const can_use = ref(false)
axios.get('/srv/?a=can_upload')
.then(res => {
if (res.data.code === 1) {
can_use.value = res.data.data.can_upload ? true : false;
} else {
console.warn(res);
if (!res.data.show) return false;
Toast({
icon: 'close',
message: res.data.msg
});
}
})
.catch(err => {
console.error(err);
})
return {
can_use
}
}
/*
* @Date: 2022-05-05 18:07:16
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-10-01 21:13:07
* @FilePath: /tswj/src/composables/useVideoList.js
* @Description: 文件描述
*/
import { ref } from 'vue'
import axios from '@/utils/axios';
import _ from 'lodash'
import { Toast } from 'vant';
import { useRoute } from 'vue-router'
import { bookInfoAPI } from '@/api/C/book'
import { flowFn } from '@/hooks/useFlowFn'
export const useVideoList = () => {
const $route = useRoute();
// 切换视频语言
const checkMandarin = ref(true); // 普通话选项卡
const checkLocalism = ref(false); // 方言选项卡
const chooseLanguage = ref({ text: '普通话', val: '普通话' }); // 默认选中普通话
/**
* 切换视频语言回调
* @param {*} type
*/
const toggleLanguage = (type) => {
if (type === 'mandarin') {
checkMandarin.value = true;
checkLocalism.value = false;
} else {
checkMandarin.value = false;
checkLocalism.value = true;
}
// 修改默认语言绑定数据
if (checkLocalism.value) {
// tslint:disable-next-line:no-string-literal
chooseLanguage.value = { text: columns.value[0]['text'], val: columns.value[0]['val'] }
} else {
chooseLanguage.value = { text: '普通话', val: '普通话' };
}
// 切换语言需要更新列表数据
onReload()
}
// 方言选择项
let columns = ref([
{ text: '所有方言', val: '所有方言' },
])
//
axios.get('/srv/?a=localism_list&book_id=' + $route.query.id)
.then(res => {
if (res.data.code === 1) {
let arr = [];
_.each(res.data.data, item => {
arr.push({
text: item,
val: item
})
});
columns.value = _.concat(columns.value, arr);
} else {
// tslint:disable-next-line: no-console
console.warn(res);
if (!res.data.show) return false;
Toast({
icon: 'close',
message: res.data.msg
});
}
})
.catch(err => {
// tslint:disable-next-line: no-console
console.error(err);
})
const showPicker = ref(false);
/**
* 选择方言确认后回调
* @param {*} param
*/
const onConfirm = ({ selectedOptions }) => {
showPicker.value = false;
chooseLanguage.value = {
text: selectedOptions[0].text,
val: selectedOptions[0].val
}
onReload()
};
// 绑定页面数据
const bookInfo = ref('');
// tslint:disable-next-line: variable-name
const prod_list = ref([]);
const limit = ref(10)
const offset = ref(0)
// 处理书籍下作品列表
const loading = ref(false);
const finished = ref(false);
// 因为不能让空图标提前出来的写法
const finishedTextStatus = ref(false);
const emptyStatus = ref(false);
/**
* 向下滚动查询数据
*/
const onLoad = async () => {
// 异步更新数据
const { data, code } = await bookInfoAPI({ book_id: $route.query.id, kg_id: $route.query.kg_id ? $route.query.kg_id : 0, localism_type: chooseLanguage.value.text, limit: limit.value, offset: offset.value })
if (code === 1) {
bookInfo.value = data;
flowFn(data.prod_list, prod_list, offset, loading, finished, finishedTextStatus, emptyStatus);
}
};
/**
* 重载刷新程序
*/
const onReload = () => {
offset.value = 0
prod_list.value = []
loading.value = true;
finished.value = false;
onLoad()
}
return {
toggleLanguage,
onLoad,
columns,
prod_list,
finished,
loading,
bookInfo,
showPicker,
checkLocalism,
checkMandarin,
onConfirm,
chooseLanguage,
finishedTextStatus,
emptyStatus,
}
}
/*
* @Date: 2022-06-12 23:54:24
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2022-06-13 00:50:29
* @FilePath: /tswj/src/composables/variable.js
* @Description: 文件描述
*/
import { ref } from 'vue';
export const loading = ref(false);
export const finished = ref(false);
export const limit = ref(5);
export const offset = ref(0)
export const finishedTextStatus = ref(false);
export const emptyStatus = ref(false);
<!--
* @Date: 2023-05-19 14:54:27
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2023-05-29 17:21:49
* @LastEditTime: 2023-05-29 17:29:12
* @FilePath: /map-demo/src/views/index.vue
* @Description: 文件描述
-->
......