骑手端app代码仓库创建
This commit is contained in:
196
sheep/components/countDown/index.vue
Normal file
196
sheep/components/countDown/index.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<!-- TODO 霖:是不是怎么复用 s-count-down 组件 -->
|
||||
<template>
|
||||
<view class="time" :style="justifyLeft">
|
||||
<text class="" v-if="tipText">{{ tipText }}</text>
|
||||
<text
|
||||
class="styleAll p6"
|
||||
v-if="isDay === true"
|
||||
:style="{ background: bgColor.bgColor, color: bgColor.Color }"
|
||||
>{{ day }}{{ bgColor.isDay ? '天' : '' }}</text
|
||||
>
|
||||
<text
|
||||
class="timeTxt"
|
||||
v-if="dayText"
|
||||
:style="{ width: bgColor.timeTxtwidth, color: bgColor.bgColor }"
|
||||
>{{ dayText }}</text
|
||||
>
|
||||
<text
|
||||
class="styleAll"
|
||||
:class="isCol ? 'timeCol' : ''"
|
||||
:style="{ background: bgColor.bgColor, color: bgColor.Color, width: bgColor.width }"
|
||||
>{{ hour }}</text
|
||||
>
|
||||
<text
|
||||
class="timeTxt"
|
||||
v-if="hourText"
|
||||
:class="isCol ? 'whit' : ''"
|
||||
:style="{ width: bgColor.timeTxtwidth, color: bgColor.bgColor }"
|
||||
>{{ hourText }}</text
|
||||
>
|
||||
<text
|
||||
class="styleAll"
|
||||
:class="isCol ? 'timeCol' : ''"
|
||||
:style="{ background: bgColor.bgColor, color: bgColor.Color, width: bgColor.width }"
|
||||
>{{ minute }}</text
|
||||
>
|
||||
<text
|
||||
class="timeTxt"
|
||||
v-if="minuteText"
|
||||
:class="isCol ? 'whit' : ''"
|
||||
:style="{ width: bgColor.timeTxtwidth, color: bgColor.bgColor }"
|
||||
>{{ minuteText }}</text
|
||||
>
|
||||
<text
|
||||
class="styleAll"
|
||||
:class="isCol ? 'timeCol' : ''"
|
||||
:style="{ background: bgColor.bgColor, color: bgColor.Color, width: bgColor.width }"
|
||||
>{{ second }}</text
|
||||
>
|
||||
<text class="timeTxt" v-if="secondText">{{ secondText }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'countDown',
|
||||
props: {
|
||||
justifyLeft: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
//距离开始提示文字
|
||||
tipText: {
|
||||
type: String,
|
||||
default: '倒计时',
|
||||
},
|
||||
dayText: {
|
||||
type: String,
|
||||
default: '天',
|
||||
},
|
||||
hourText: {
|
||||
type: String,
|
||||
default: '时',
|
||||
},
|
||||
minuteText: {
|
||||
type: String,
|
||||
default: '分',
|
||||
},
|
||||
secondText: {
|
||||
type: String,
|
||||
default: '秒',
|
||||
},
|
||||
datatime: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
isDay: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isCol: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
bgColor: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
day: '00',
|
||||
hour: '00',
|
||||
minute: '00',
|
||||
second: '00',
|
||||
};
|
||||
},
|
||||
created: function () {
|
||||
this.show_time();
|
||||
},
|
||||
mounted: function () {},
|
||||
methods: {
|
||||
show_time: function () {
|
||||
let that = this;
|
||||
|
||||
function runTime() {
|
||||
//时间函数
|
||||
let intDiff = that.datatime - Date.parse(new Date()) / 1000; //获取数据中的时间戳的时间差;
|
||||
let day = 0,
|
||||
hour = 0,
|
||||
minute = 0,
|
||||
second = 0;
|
||||
if (intDiff > 0) {
|
||||
//转换时间
|
||||
if (that.isDay === true) {
|
||||
day = Math.floor(intDiff / (60 * 60 * 24));
|
||||
} else {
|
||||
day = 0;
|
||||
}
|
||||
hour = Math.floor(intDiff / (60 * 60)) - day * 24;
|
||||
minute = Math.floor(intDiff / 60) - day * 24 * 60 - hour * 60;
|
||||
second = Math.floor(intDiff) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
|
||||
if (hour <= 9) hour = '0' + hour;
|
||||
if (minute <= 9) minute = '0' + minute;
|
||||
if (second <= 9) second = '0' + second;
|
||||
that.day = day;
|
||||
that.hour = hour;
|
||||
that.minute = minute;
|
||||
that.second = second;
|
||||
} else {
|
||||
that.day = '00';
|
||||
that.hour = '00';
|
||||
that.minute = '00';
|
||||
that.second = '00';
|
||||
}
|
||||
}
|
||||
runTime();
|
||||
setInterval(runTime, 1000);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.p6 {
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
.styleAll {
|
||||
/* color: #fff; */
|
||||
font-size: 24rpx;
|
||||
height: 36rpx;
|
||||
line-height: 36rpx;
|
||||
border-radius: 6rpx;
|
||||
text-align: center;
|
||||
/* padding: 0 6rpx; */
|
||||
}
|
||||
.timeTxt {
|
||||
text-align: center;
|
||||
/* width: 16rpx; */
|
||||
height: 36rpx;
|
||||
line-height: 36rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
.whit {
|
||||
color: #fff !important;
|
||||
}
|
||||
.time {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: #fc4141;
|
||||
margin: 0 4rpx;
|
||||
}
|
||||
|
||||
.timeCol {
|
||||
/* width: 40rpx;
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
text-align:center;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
font-size: 24rpx; */
|
||||
color: #e93323;
|
||||
}
|
||||
</style>
|
||||
108
sheep/components/s-auth-modal/components/account-login.vue
Normal file
108
sheep/components/s-auth-modal/components/account-login.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<!-- 账号密码登录 accountLogin -->
|
||||
<template>
|
||||
<view>
|
||||
<!-- 标题栏 -->
|
||||
<view class="head-box ss-m-b-60">
|
||||
<view class="ss-m-b-20">
|
||||
<!-- <view class="head-title-active head-title-line" @tap="showAuthModal('smsLogin')">
|
||||
短信登录
|
||||
</view> -->
|
||||
<view class="head-title head-title-animation">登录账号</view>
|
||||
</view>
|
||||
<view class="head-subtitle">如无账号请联系平台开通</view>
|
||||
<!-- <view class="head-subtitle">如果未设置过密码,请点击忘记密码</view> -->
|
||||
</view>
|
||||
|
||||
<!-- 表单项 -->
|
||||
<uni-forms
|
||||
ref="accountLoginRef"
|
||||
v-model="state.model"
|
||||
:rules="state.rules"
|
||||
validateTrigger="bind"
|
||||
labelWidth="140"
|
||||
labelAlign="center"
|
||||
>
|
||||
<uni-forms-item name="mobile" label="账号">
|
||||
<uni-easyinput placeholder="请输入手机号" v-model="state.model.mobile" :inputBorder="false">
|
||||
<!-- <template v-slot:right>
|
||||
<button class="ss-reset-button forgot-btn" @tap="showAuthModal('resetPassword')">
|
||||
忘记密码
|
||||
</button>
|
||||
</template> -->
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item name="password" label="密码">
|
||||
<uni-easyinput
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
v-model="state.model.password"
|
||||
:inputBorder="false"
|
||||
>
|
||||
<template v-slot:right>
|
||||
<button class="ss-reset-button login-btn-start" @tap="accountLoginSubmit">登录</button>
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, unref } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { mobile, password } from '@/sheep/validate/form';
|
||||
import { showAuthModal, closeAuthModal } from '@/sheep/hooks/useModal';
|
||||
import AuthUtil from '@/sheep/api/member/auth';
|
||||
|
||||
const accountLoginRef = ref(null);
|
||||
|
||||
const emits = defineEmits(['onConfirm']);
|
||||
|
||||
const props = defineProps({
|
||||
agreeStatus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
// 数据
|
||||
const state = reactive({
|
||||
model: {
|
||||
mobile: '', // 账号
|
||||
password: '', // 密码
|
||||
},
|
||||
rules: {
|
||||
mobile,
|
||||
password,
|
||||
},
|
||||
});
|
||||
|
||||
// 账号登录
|
||||
async function accountLoginSubmit() {
|
||||
// 表单验证
|
||||
const validate = await unref(accountLoginRef)
|
||||
.validate()
|
||||
.catch((error) => {
|
||||
console.log('error: ', error);
|
||||
});
|
||||
if (!validate) return;
|
||||
|
||||
// 同意协议
|
||||
if (!props.agreeStatus) {
|
||||
emits('onConfirm', true)
|
||||
sheep.$helper.toast('请勾选同意');
|
||||
return;
|
||||
}
|
||||
|
||||
// 提交数据
|
||||
const { code, data } = await AuthUtil.login(state.model);
|
||||
if (code === 0) {
|
||||
closeAuthModal();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../index.scss';
|
||||
</style>
|
||||
127
sheep/components/s-auth-modal/components/change-mobile.vue
Normal file
127
sheep/components/s-auth-modal/components/change-mobile.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<!-- 绑定/更换手机号 changeMobile -->
|
||||
<template>
|
||||
<view>
|
||||
<!-- 标题栏 -->
|
||||
<view class="head-box ss-m-b-60">
|
||||
<view class="head-title ss-m-b-20">
|
||||
{{ userInfo.mobile ? '更换手机号' : '绑定手机号' }}
|
||||
</view>
|
||||
<view class="head-subtitle">为了您的账号安全,请使用本人手机号码</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单项 -->
|
||||
<uni-forms
|
||||
ref="changeMobileRef"
|
||||
v-model="state.model"
|
||||
:rules="state.rules"
|
||||
validateTrigger="bind"
|
||||
labelWidth="140"
|
||||
labelAlign="center"
|
||||
>
|
||||
<uni-forms-item name="mobile" label="手机号">
|
||||
<uni-easyinput
|
||||
placeholder="请输入手机号"
|
||||
v-model="state.model.mobile"
|
||||
:inputBorder="false"
|
||||
type="number"
|
||||
>
|
||||
<template v-slot:right>
|
||||
<button
|
||||
class="ss-reset-button code-btn-start"
|
||||
:disabled="state.isMobileEnd"
|
||||
:class="{ 'code-btn-end': state.isMobileEnd }"
|
||||
@tap="getSmsCode('changeMobile', state.model.mobile)"
|
||||
>
|
||||
{{ getSmsTimer('changeMobile') }}
|
||||
</button>
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item name="code" label="验证码">
|
||||
<uni-easyinput
|
||||
placeholder="请输入验证码"
|
||||
v-model="state.model.code"
|
||||
:inputBorder="false"
|
||||
type="number"
|
||||
maxlength="4"
|
||||
>
|
||||
<template v-slot:right>
|
||||
<button class="ss-reset-button login-btn-start" @tap="changeMobileSubmit">
|
||||
确认
|
||||
</button>
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
|
||||
<!-- 微信独有:读取手机号 -->
|
||||
<button
|
||||
v-if="'WechatMiniProgram' === sheep.$platform.name"
|
||||
class="ss-reset-button type-btn"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="getPhoneNumber"
|
||||
>
|
||||
使用微信手机号
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, reactive, unref } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { code, mobile } from '@/sheep/validate/form';
|
||||
import { closeAuthModal, getSmsCode, getSmsTimer } from '@/sheep/hooks/useModal';
|
||||
import UserApi from '@/sheep/api/member/user';
|
||||
|
||||
const changeMobileRef = ref(null);
|
||||
const userInfo = computed(() => sheep.$store('user').userInfo);
|
||||
|
||||
// 数据
|
||||
const state = reactive({
|
||||
isMobileEnd: false, // 手机号输入完毕
|
||||
model: {
|
||||
mobile: '', // 手机号
|
||||
code: '', // 验证码
|
||||
},
|
||||
rules: {
|
||||
code,
|
||||
mobile,
|
||||
},
|
||||
});
|
||||
|
||||
// 绑定手机号
|
||||
async function changeMobileSubmit() {
|
||||
const validate = await unref(changeMobileRef)
|
||||
.validate()
|
||||
.catch((error) => {
|
||||
console.log('error: ', error);
|
||||
});
|
||||
if (!validate) {
|
||||
return;
|
||||
}
|
||||
// 提交更新请求
|
||||
const { code } = await UserApi.updateUserMobile(state.model);
|
||||
if (code !== 0) {
|
||||
return;
|
||||
}
|
||||
sheep.$store('user').getInfo();
|
||||
closeAuthModal();
|
||||
}
|
||||
|
||||
// 使用微信手机号
|
||||
async function getPhoneNumber(e) {
|
||||
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
|
||||
return;
|
||||
}
|
||||
const result = await sheep.$platform.useProvider().bindUserPhoneNumber(e.detail);
|
||||
if (result) {
|
||||
sheep.$store('user').getInfo();
|
||||
closeAuthModal();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../index.scss';
|
||||
</style>
|
||||
113
sheep/components/s-auth-modal/components/change-password.vue
Normal file
113
sheep/components/s-auth-modal/components/change-password.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<!-- 修改密码(登录时) -->
|
||||
<template>
|
||||
<view>
|
||||
<!-- 标题栏 -->
|
||||
<view class="head-box ss-m-b-60">
|
||||
<view class="head-title ss-m-b-20 head-title-animation text-bold">修改密码</view>
|
||||
<view class="head-subtitle">您目前的登录密码为初始密码,为了您的账号安全,请对您的密码进行修改。</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单项 -->
|
||||
<uni-forms
|
||||
ref="changePasswordRef"
|
||||
v-model="state.model"
|
||||
:rules="state.rules"
|
||||
validateTrigger="bind"
|
||||
labelWidth="140"
|
||||
labelAlign="center"
|
||||
>
|
||||
<uni-forms-item name="reNewPassword" label="手机号">
|
||||
<uni-easyinput
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
v-model="state.model.mobile"
|
||||
:inputBorder="false"
|
||||
maxlength="11"
|
||||
>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item name="reNewPassword" label="新密码">
|
||||
<uni-easyinput
|
||||
type="password"
|
||||
placeholder="请输入新密码"
|
||||
v-model="state.model.newPassword"
|
||||
:inputBorder="false"
|
||||
maxlength="16"
|
||||
>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item name="reNewPassword" label="确认密码">
|
||||
<uni-easyinput
|
||||
type="password"
|
||||
placeholder="请重新输入新密码进行确认"
|
||||
v-model="state.model.ackPassword"
|
||||
:inputBorder="false"
|
||||
maxlength="16"
|
||||
>
|
||||
<template v-slot:right>
|
||||
<button class="ss-reset-button login-btn-start" @tap="changePasswordSubmit">
|
||||
确认
|
||||
</button>
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
|
||||
<button class="ss-reset-button type-btn" @tap="closeAuthModal">
|
||||
取消修改
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, unref } from 'vue';
|
||||
import { ackPassword, newPassword, mobile } from '@/sheep/validate/form';
|
||||
import { closeAuthModal, getSmsCode, getSmsTimer } from '@/sheep/hooks/useModal';
|
||||
import UserApi from '@/sheep/api/member/user';
|
||||
|
||||
const changePasswordRef = ref(null);
|
||||
|
||||
// 数据
|
||||
const state = reactive({
|
||||
model: {
|
||||
mobile: '', // 手机号
|
||||
newPassword: '', // 密码
|
||||
ackPassword: '', // 密码
|
||||
},
|
||||
rules: {
|
||||
mobile,
|
||||
newPassword,
|
||||
ackPassword
|
||||
},
|
||||
});
|
||||
|
||||
// 更改密码
|
||||
async function changePasswordSubmit() {
|
||||
// 参数校验
|
||||
const validate = await unref(changePasswordRef)
|
||||
.validate()
|
||||
.catch((error) => {
|
||||
console.log('error: ', error);
|
||||
});
|
||||
if (!validate) {
|
||||
return;
|
||||
}
|
||||
// 发起请求
|
||||
const { code } = await UserApi.updateUserPasswordReset(state.model);
|
||||
if (code !== 0) {
|
||||
return;
|
||||
}
|
||||
// 成功后,只需要关闭弹窗
|
||||
closeAuthModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../index.scss';
|
||||
|
||||
.text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
152
sheep/components/s-auth-modal/components/mp-authorization.vue
Normal file
152
sheep/components/s-auth-modal/components/mp-authorization.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<!-- 微信授权信息 mpAuthorization -->
|
||||
<template>
|
||||
<view>
|
||||
<!-- 标题栏 -->
|
||||
<view class="head-box ss-m-b-60 ss-flex-col">
|
||||
<view class="ss-flex ss-m-b-20">
|
||||
<view class="head-title ss-m-r-40 head-title-animation">授权信息</view>
|
||||
</view>
|
||||
<view class="head-subtitle">完善您的头像、昵称、手机号</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单项 -->
|
||||
<uni-forms
|
||||
ref="accountLoginRef"
|
||||
v-model="state.model"
|
||||
:rules="state.rules"
|
||||
validateTrigger="bind"
|
||||
labelWidth="140"
|
||||
labelAlign="center"
|
||||
>
|
||||
<!-- 获取头像昵称:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/userProfile.html -->
|
||||
<uni-forms-item name="avatar" label="头像">
|
||||
<button
|
||||
class="ss-reset-button avatar-btn"
|
||||
open-type="chooseAvatar"
|
||||
@chooseavatar="onChooseAvatar"
|
||||
>
|
||||
<image
|
||||
class="avatar-img"
|
||||
:src="sheep.$url.cdn(state.model.avatar)"
|
||||
mode="aspectFill"
|
||||
@tap="sheep.$router.go('/pages/user/info')"
|
||||
/>
|
||||
<text class="cicon-forward" />
|
||||
</button>
|
||||
</uni-forms-item>
|
||||
<uni-forms-item name="nickname" label="昵称">
|
||||
<uni-easyinput
|
||||
type="nickname"
|
||||
placeholder="请输入昵称"
|
||||
v-model="state.model.nickname"
|
||||
:inputBorder="false"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
<view class="foot-box">
|
||||
<button class="ss-reset-button authorization-btn" @tap="onConfirm"> 确认授权 </button>
|
||||
</view>
|
||||
</uni-forms>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, reactive } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { closeAuthModal } from '@/sheep/hooks/useModal';
|
||||
import FileApi from '@/sheep/api/infra/file';
|
||||
import UserApi from '@/sheep/api/member/user';
|
||||
|
||||
const props = defineProps({
|
||||
agreeStatus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const userInfo = computed(() => sheep.$store('user').userInfo);
|
||||
|
||||
const accountLoginRef = ref(null);
|
||||
|
||||
// 数据
|
||||
const state = reactive({
|
||||
model: {
|
||||
nickname: userInfo.value.nickname,
|
||||
avatar: userInfo.value.avatar,
|
||||
},
|
||||
rules: {},
|
||||
disabledStyle: {
|
||||
color: '#999',
|
||||
disableColor: '#fff',
|
||||
},
|
||||
});
|
||||
|
||||
// 选择头像(来自微信)
|
||||
function onChooseAvatar(e) {
|
||||
const tempUrl = e.detail.avatarUrl || '';
|
||||
uploadAvatar(tempUrl);
|
||||
}
|
||||
|
||||
// 选择头像(来自文件系统)
|
||||
async function uploadAvatar(tempUrl) {
|
||||
if (!tempUrl) {
|
||||
return;
|
||||
}
|
||||
let { data } = await FileApi.uploadFile(tempUrl);
|
||||
state.model.avatar = data;
|
||||
}
|
||||
|
||||
// 确认授权
|
||||
async function onConfirm() {
|
||||
const { model } = state;
|
||||
const { nickname, avatar } = model;
|
||||
if (!nickname) {
|
||||
sheep.$helper.toast('请输入昵称');
|
||||
return;
|
||||
}
|
||||
if (!avatar) {
|
||||
sheep.$helper.toast('请选择头像');
|
||||
return;
|
||||
}
|
||||
// 发起更新
|
||||
const { code } = await UserApi.updateUser({
|
||||
avatar: state.model.avatar,
|
||||
nickname: state.model.nickname,
|
||||
});
|
||||
// 更新成功
|
||||
if (code === 0) {
|
||||
sheep.$helper.toast('授权成功');
|
||||
await sheep.$store('user').getInfo();
|
||||
closeAuthModal();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../index.scss';
|
||||
|
||||
.foot-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.authorization-btn {
|
||||
width: 686rpx;
|
||||
height: 80rpx;
|
||||
background-color: var(--ui-BG-Main);
|
||||
border-radius: 40rpx;
|
||||
color: #fff;
|
||||
}
|
||||
.avatar-img {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
}
|
||||
.cicon-forward {
|
||||
font-size: 30rpx;
|
||||
color: #595959;
|
||||
}
|
||||
.avatar-btn {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
119
sheep/components/s-auth-modal/components/reset-password.vue
Normal file
119
sheep/components/s-auth-modal/components/reset-password.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<!-- 重置密码(未登录时) -->
|
||||
<template>
|
||||
<view>
|
||||
<!-- 标题栏 -->
|
||||
<view class="head-box ss-m-b-60">
|
||||
<view class="head-title ss-m-b-20">重置密码</view>
|
||||
<view class="head-subtitle">为了您的账号安全,设置密码前请先进行安全验证</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单项 -->
|
||||
<uni-forms
|
||||
ref="resetPasswordRef"
|
||||
v-model="state.model"
|
||||
:rules="state.rules"
|
||||
validateTrigger="bind"
|
||||
labelWidth="140"
|
||||
labelAlign="center"
|
||||
>
|
||||
<uni-forms-item name="mobile" label="手机号">
|
||||
<uni-easyinput
|
||||
placeholder="请输入手机号"
|
||||
v-model="state.model.mobile"
|
||||
type="number"
|
||||
:inputBorder="false"
|
||||
>
|
||||
<template v-slot:right>
|
||||
<button
|
||||
class="ss-reset-button code-btn code-btn-start"
|
||||
:disabled="state.isMobileEnd"
|
||||
:class="{ 'code-btn-end': state.isMobileEnd }"
|
||||
@tap="getSmsCode('resetPassword', state.model.mobile)"
|
||||
>
|
||||
{{ getSmsTimer('resetPassword') }}
|
||||
</button>
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item name="code" label="验证码">
|
||||
<uni-easyinput
|
||||
placeholder="请输入验证码"
|
||||
v-model="state.model.code"
|
||||
type="number"
|
||||
maxlength="4"
|
||||
:inputBorder="false"
|
||||
/>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item name="password" label="密码">
|
||||
<uni-easyinput
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
v-model="state.model.password"
|
||||
:inputBorder="false"
|
||||
>
|
||||
<template v-slot:right>
|
||||
<button class="ss-reset-button login-btn-start" @tap="resetPasswordSubmit">
|
||||
确认
|
||||
</button>
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
|
||||
<button v-if="!isLogin" class="ss-reset-button type-btn" @tap="showAuthModal('accountLogin')">
|
||||
返回登录
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, reactive, unref } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { code, mobile, password } from '@/sheep/validate/form';
|
||||
import { showAuthModal, closeAuthModal, getSmsCode, getSmsTimer } from '@/sheep/hooks/useModal';
|
||||
import UserApi from '@/sheep/api/member/user';
|
||||
|
||||
const resetPasswordRef = ref(null);
|
||||
const isLogin = computed(() => sheep.$store('user').isLogin);
|
||||
|
||||
// 数据
|
||||
const state = reactive({
|
||||
isMobileEnd: false, // 手机号输入完毕
|
||||
model: {
|
||||
mobile: '', // 手机号
|
||||
code: '', // 验证码
|
||||
password: '', // 密码
|
||||
},
|
||||
rules: {
|
||||
code,
|
||||
mobile,
|
||||
password,
|
||||
},
|
||||
});
|
||||
|
||||
// 重置密码
|
||||
const resetPasswordSubmit = async () => {
|
||||
// 参数校验
|
||||
const validate = await unref(resetPasswordRef)
|
||||
.validate()
|
||||
.catch((error) => {
|
||||
console.log('error: ', error);
|
||||
});
|
||||
if (!validate) {
|
||||
return;
|
||||
}
|
||||
// 发起请求
|
||||
const { code } = await UserApi.resetUserPassword(state.model);
|
||||
if (code !== 0) {
|
||||
return;
|
||||
}
|
||||
// 成功后,用户重新登录
|
||||
showAuthModal('accountLogin')
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../index.scss';
|
||||
</style>
|
||||
119
sheep/components/s-auth-modal/components/sms-login.vue
Normal file
119
sheep/components/s-auth-modal/components/sms-login.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<!-- 短信登录 - smsLogin -->
|
||||
<template>
|
||||
<view>
|
||||
<!-- 标题栏 -->
|
||||
<view class="head-box ss-m-b-60">
|
||||
<view class="ss-flex ss-m-b-20">
|
||||
<view class="head-title head-title-line head-title-animation">短信登录</view>
|
||||
<view class="head-title-active ss-m-r-40" @tap="showAuthModal('accountLogin')">
|
||||
账号登录
|
||||
</view>
|
||||
</view>
|
||||
<view class="head-subtitle">未注册的手机号,验证后自动注册账号</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单项 -->
|
||||
<uni-forms
|
||||
ref="smsLoginRef"
|
||||
v-model="state.model"
|
||||
:rules="state.rules"
|
||||
validateTrigger="bind"
|
||||
labelWidth="140"
|
||||
labelAlign="center"
|
||||
>
|
||||
<uni-forms-item name="mobile" label="手机号">
|
||||
<uni-easyinput
|
||||
placeholder="请输入手机号"
|
||||
v-model="state.model.mobile"
|
||||
:inputBorder="false"
|
||||
type="number"
|
||||
>
|
||||
<template v-slot:right>
|
||||
<button
|
||||
class="ss-reset-button code-btn code-btn-start"
|
||||
:disabled="state.isMobileEnd"
|
||||
:class="{ 'code-btn-end': state.isMobileEnd }"
|
||||
@tap="getSmsCode('smsLogin', state.model.mobile)"
|
||||
>
|
||||
{{ getSmsTimer('smsLogin') }}
|
||||
</button>
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
|
||||
<uni-forms-item name="code" label="验证码">
|
||||
<uni-easyinput
|
||||
placeholder="请输入验证码"
|
||||
v-model="state.model.code"
|
||||
:inputBorder="false"
|
||||
type="number"
|
||||
maxlength="4"
|
||||
>
|
||||
<template v-slot:right>
|
||||
<button class="ss-reset-button login-btn-start" @tap="smsLoginSubmit"> 登录 </button>
|
||||
</template>
|
||||
</uni-easyinput>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, unref } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { code, mobile } from '@/sheep/validate/form';
|
||||
import { showAuthModal, closeAuthModal, getSmsCode, getSmsTimer } from '@/sheep/hooks/useModal';
|
||||
import AuthUtil from '@/sheep/api/member/auth';
|
||||
|
||||
const smsLoginRef = ref(null);
|
||||
|
||||
const emits = defineEmits(['onConfirm']);
|
||||
|
||||
const props = defineProps({
|
||||
agreeStatus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
// 数据
|
||||
const state = reactive({
|
||||
isMobileEnd: false, // 手机号输入完毕
|
||||
codeText: '获取验证码',
|
||||
model: {
|
||||
mobile: '', // 手机号
|
||||
code: '', // 验证码
|
||||
},
|
||||
rules: {
|
||||
code,
|
||||
mobile,
|
||||
},
|
||||
});
|
||||
|
||||
// 短信登录
|
||||
async function smsLoginSubmit() {
|
||||
// 参数校验
|
||||
const validate = await unref(smsLoginRef)
|
||||
.validate()
|
||||
.catch((error) => {
|
||||
console.log('error: ', error);
|
||||
});
|
||||
if (!validate) {
|
||||
return;
|
||||
}
|
||||
if (!props.agreeStatus) {
|
||||
emits('onConfirm', true)
|
||||
sheep.$helper.toast('请勾选同意');
|
||||
return;
|
||||
}
|
||||
// 提交数据
|
||||
const { code } = await AuthUtil.smsLogin(state.model);
|
||||
if (code === 0) {
|
||||
closeAuthModal();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../index.scss';
|
||||
</style>
|
||||
156
sheep/components/s-auth-modal/index.scss
Normal file
156
sheep/components/s-auth-modal/index.scss
Normal file
@@ -0,0 +1,156 @@
|
||||
@keyframes title-animation {
|
||||
0% {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
100% {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.login-wrap {
|
||||
padding: 50rpx 34rpx;
|
||||
min-height: 500rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
}
|
||||
|
||||
.head-box {
|
||||
.head-title {
|
||||
min-width: 160rpx;
|
||||
font-size: 36rpx;
|
||||
// font-weight: bold;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
.head-title-active {
|
||||
width: 160rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #999;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
.head-title-animation {
|
||||
text-align: center;
|
||||
animation-name: title-animation;
|
||||
animation-duration: 0.1s;
|
||||
animation-timing-function: ease-out;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
.head-title-line {
|
||||
position: relative;
|
||||
&::before {
|
||||
content: '';
|
||||
width: 1rpx;
|
||||
height: 34rpx;
|
||||
background-color: #e4e7ed;
|
||||
position: absolute;
|
||||
left: -30rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
.head-subtitle {
|
||||
// font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
// color: #afb6c0;
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
// text-align: left;
|
||||
text-align: center;
|
||||
// display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
// .code-btn[disabled] {
|
||||
// background-color: #fff;
|
||||
// }
|
||||
.code-btn-start {
|
||||
width: 160rpx;
|
||||
height: 56rpx;
|
||||
line-height: normal;
|
||||
border: 2rpx solid var(--ui-BG-Main);
|
||||
border-radius: 28rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
color: var(--ui-BG-Main);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.forgot-btn {
|
||||
width: 160rpx;
|
||||
line-height: 56rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.login-btn-start {
|
||||
width: 158rpx;
|
||||
height: 56rpx;
|
||||
line-height: normal;
|
||||
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
|
||||
border-radius: 28rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.type-btn {
|
||||
padding: 20rpx;
|
||||
margin: 40rpx auto;
|
||||
width: 200rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.auto-login-box {
|
||||
width: 100%;
|
||||
.auto-login-btn {
|
||||
width: 68rpx;
|
||||
height: 68rpx;
|
||||
border-radius: 50%;
|
||||
margin: 0 30rpx;
|
||||
}
|
||||
.auto-login-img {
|
||||
width: 68rpx;
|
||||
height: 68rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.agreement-box {
|
||||
margin: 80rpx auto 0;
|
||||
.protocol-check {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
.agreement-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #999999;
|
||||
.tcp-text {
|
||||
color: var(--ui-BG-Main);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修改密码
|
||||
.editPwd-btn-box {
|
||||
.save-btn {
|
||||
width: 690rpx;
|
||||
line-height: 70rpx;
|
||||
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
|
||||
border-radius: 35rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
.forgot-btn {
|
||||
width: 690rpx;
|
||||
line-height: 70rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
251
sheep/components/s-auth-modal/s-auth-modal.vue
Normal file
251
sheep/components/s-auth-modal/s-auth-modal.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<!-- 规格弹窗 -->
|
||||
<su-popup :show="authType !== ''" round="10" :showClose="true" @close="closeAuthModal">
|
||||
<view class="login-wrap main-blue">
|
||||
<!-- 1. 账号密码登录 accountLogin -->
|
||||
<account-login
|
||||
v-if="authType === 'accountLogin'"
|
||||
:agreeStatus="state.protocol"
|
||||
@onConfirm="onConfirm"
|
||||
/>
|
||||
|
||||
<!-- 2. 短信登录 smsLogin -->
|
||||
<!-- <sms-login
|
||||
v-if="authType === 'smsLogin'"
|
||||
:agreeStatus="state.protocol"
|
||||
@onConfirm="onConfirm"
|
||||
/> -->
|
||||
|
||||
<!-- 3. 忘记密码 resetPassword-->
|
||||
<reset-password v-if="authType === 'resetPassword'" />
|
||||
|
||||
<!-- 4. 绑定手机号 changeMobile -->
|
||||
<change-mobile v-if="authType === 'changeMobile'" />
|
||||
|
||||
<!-- 5. 修改密码 changePassword-->
|
||||
<changePassword v-if="authType === 'changePassword'" />
|
||||
|
||||
<!-- 6. 微信小程序授权 -->
|
||||
<mp-authorization v-if="authType === 'mpAuthorization'" />
|
||||
|
||||
<!-- 7. 第三方登录 -->
|
||||
<view
|
||||
v-if="['accountLogin', 'smsLogin'].includes(authType)"
|
||||
class="auto-login-box ss-flex ss-flex-col ss-row-center ss-col-center"
|
||||
>
|
||||
<!-- 7.1 微信小程序的快捷登录 -->
|
||||
<view v-if="sheep.$platform.name === 'WechatMiniProgram'" class="ss-flex register-box">
|
||||
<!-- <view class="register-title">还没有账号?</view> -->
|
||||
<view class="register-title">已经拥有账号可以,</view>
|
||||
<button
|
||||
class="ss-reset-button login-btn"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="getPhoneNumber"
|
||||
>
|
||||
快捷登录
|
||||
</button>
|
||||
<view class="circle"></view>
|
||||
</view>
|
||||
|
||||
<!-- 7.2 微信的公众号、App、小程序的登录,基于 openid + code -->
|
||||
<!-- <button
|
||||
v-if="
|
||||
['WechatOfficialAccount', 'WechatMiniProgram', 'App'].includes(sheep.$platform.name) &&
|
||||
sheep.$platform.isWechatInstalled
|
||||
"
|
||||
@tap="thirdLogin('wechat')"
|
||||
class="ss-reset-button auto-login-btn"
|
||||
>
|
||||
<image
|
||||
class="auto-login-img"
|
||||
:src="sheep.$url.static('/static/img/shop/platform/wechat.png')"
|
||||
/>
|
||||
</button> -->
|
||||
|
||||
<!-- 7.3 iOS 登录 TODO 芋艿:等后面搞 App 再弄 -->
|
||||
<!-- <button
|
||||
v-if="sheep.$platform.os === 'ios' && sheep.$platform.name === 'App'"
|
||||
@tap="thirdLogin('apple')"
|
||||
class="ss-reset-button auto-login-btn"
|
||||
>
|
||||
<image
|
||||
class="auto-login-img"
|
||||
:src="sheep.$url.static('/static/img/shop/platform/apple.png')"
|
||||
/>
|
||||
</button> -->
|
||||
</view>
|
||||
|
||||
<!-- 用户协议的勾选 -->
|
||||
<view
|
||||
v-if="['accountLogin', 'smsLogin'].includes(authType)"
|
||||
class="agreement-box ss-flex ss-row-center"
|
||||
:class="{ shake: currentProtocol }"
|
||||
>
|
||||
<label class="radio ss-flex ss-col-center" @tap="onChange">
|
||||
<radio
|
||||
:checked="state.protocol"
|
||||
color="var(--ui-BG-Main)"
|
||||
style="transform: scale(0.8)"
|
||||
@tap.stop="onChange"
|
||||
/>
|
||||
<view class="agreement-text ss-flex ss-col-center ss-m-l-8">
|
||||
我已阅读并同意
|
||||
<view class="tcp-text" @tap.stop="onProtocol('用户协议')"> 《用户协议》 </view>
|
||||
<view class="agreement-text">与</view>
|
||||
<view class="tcp-text" @tap.stop="onProtocol('隐私协议')"> 《隐私协议》 </view>
|
||||
</view>
|
||||
</label>
|
||||
</view>
|
||||
<view class="safe-box" />
|
||||
</view>
|
||||
</su-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import accountLogin from './components/account-login.vue';
|
||||
import smsLogin from './components/sms-login.vue';
|
||||
import resetPassword from './components/reset-password.vue';
|
||||
import changeMobile from './components/change-mobile.vue';
|
||||
import changePassword from './components/change-password.vue';
|
||||
import mpAuthorization from './components/mp-authorization.vue';
|
||||
import { closeAuthModal, showAuthModal } from '@/sheep/hooks/useModal';
|
||||
|
||||
const modalStore = sheep.$store('modal');
|
||||
// 授权弹窗类型
|
||||
const authType = computed(() => modalStore.auth);
|
||||
// const authType = "accountLogin";
|
||||
|
||||
const state = reactive({
|
||||
protocol: false,
|
||||
});
|
||||
|
||||
const currentProtocol = ref(false);
|
||||
|
||||
// 勾选协议
|
||||
function onChange() {
|
||||
state.protocol = !state.protocol;
|
||||
}
|
||||
|
||||
// 查看协议
|
||||
function onProtocol(title) {
|
||||
closeAuthModal();
|
||||
sheep.$router.go('/pages/public/richtext', {
|
||||
title,
|
||||
});
|
||||
}
|
||||
|
||||
// 点击登录 / 注册事件
|
||||
function onConfirm(e) {
|
||||
currentProtocol.value = e;
|
||||
setTimeout(() => {
|
||||
currentProtocol.value = false;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 第三方授权登陆(微信小程序、Apple)
|
||||
const thirdLogin = async (provider) => {
|
||||
if (!state.protocol) {
|
||||
currentProtocol.value = true;
|
||||
setTimeout(() => {
|
||||
currentProtocol.value = false;
|
||||
}, 1000);
|
||||
sheep.$helper.toast('请勾选同意');
|
||||
return;
|
||||
}
|
||||
const loginRes = await sheep.$platform.useProvider(provider).login();
|
||||
if (loginRes) {
|
||||
const userInfo = await sheep.$store('user').getInfo();
|
||||
closeAuthModal();
|
||||
// 如果用户已经有头像和昵称,不需要再次授权
|
||||
if (userInfo.avatar && userInfo.nickname) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 触发小程序授权信息弹框
|
||||
// #ifdef MP-WEIXIN
|
||||
showAuthModal('mpAuthorization');
|
||||
// #endif
|
||||
}
|
||||
};
|
||||
|
||||
// 微信小程序的“手机号快速验证”:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html
|
||||
const getPhoneNumber = async (e) => {
|
||||
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
|
||||
sheep.$helper.toast('快捷登录失败');
|
||||
return;
|
||||
}
|
||||
console.log("e", e);
|
||||
let result = await sheep.$platform.useProvider().mobileLogin(e.detail);
|
||||
console.log("result", result);
|
||||
if (result) {
|
||||
closeAuthModal();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './index.scss';
|
||||
|
||||
.shake {
|
||||
animation: shake 0.05s linear 4 alternate;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
from {
|
||||
transform: translateX(-10rpx);
|
||||
}
|
||||
to {
|
||||
transform: translateX(10rpx);
|
||||
}
|
||||
}
|
||||
|
||||
.register-box {
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
.register-btn {
|
||||
color: #999999;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
.register-title {
|
||||
color: #999999;
|
||||
font-size: 30rpx;
|
||||
font-weight: 400;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
.or-title {
|
||||
margin: 0 16rpx;
|
||||
color: #999999;
|
||||
font-size: 30rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
.login-btn {
|
||||
color: var(--ui-BG-Main);
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
.circle {
|
||||
position: absolute;
|
||||
right: 0rpx;
|
||||
top: 18rpx;
|
||||
width: 8rpx;
|
||||
height: 8rpx;
|
||||
border-radius: 8rpx;
|
||||
background: var(--ui-BG-Main);
|
||||
}
|
||||
}
|
||||
.safe-box {
|
||||
height: calc(constant(safe-area-inset-bottom) / 5 * 3);
|
||||
height: calc(env(safe-area-inset-bottom) / 5 * 3);
|
||||
}
|
||||
|
||||
.tcp-text {
|
||||
color: var(--ui-BG-Main);
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
color: $dark-9;
|
||||
}
|
||||
</style>
|
||||
69
sheep/components/s-custom-navbar/components/navbar-item.vue
Normal file
69
sheep/components/s-custom-navbar/components/navbar-item.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<!-- 顶部导航栏 - 单元格 -->
|
||||
<template>
|
||||
<view class="ss-flex ss-col-center">
|
||||
<!-- 类型一: 文字 -->
|
||||
<view
|
||||
v-if="data.type === 'text'"
|
||||
class="nav-title inline"
|
||||
:style="[{ color: data.textColor, width: width }]"
|
||||
>
|
||||
{{ data.text }}
|
||||
</view>
|
||||
<!-- 类型二: 图片 -->
|
||||
<view
|
||||
v-if="data.type === 'image'"
|
||||
:style="[{ width: width }]"
|
||||
class="menu-icon-wrap ss-flex ss-row-center ss-col-center"
|
||||
@tap="sheep.$router.go(data.url)"
|
||||
>
|
||||
<image class="nav-image radius-img" v-if="data.imgUrl == imgSrc" :src="userInfo.avatar?userInfo.avatar:defautAvatar" mode="aspectFit"></image>
|
||||
<image class="nav-image" v-else :src="sheep.$url.cdn(data.imgUrl)" mode="aspectFit"></image>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import sheep from '@/sheep';
|
||||
import { computed } from 'vue';
|
||||
|
||||
// 接收参数
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '1px',
|
||||
},
|
||||
});
|
||||
|
||||
const imgSrc = 'http://api.jnmall.zq-hightech.com/admin-api/infra/file/29/get/e2f8b02bae129322f99ed06226543a55a8c13226fa688017f1454508a974d7bb.png'
|
||||
const defautAvatar = 'http://api.jnmall.zq-hightech.com/admin-api/infra/file/29/get/e2f8b02bae129322f99ed06226543a55a8c13226fa688017f1454508a974d7bb.png'
|
||||
const userInfo = computed(() => sheep.$store('user').userInfo);
|
||||
|
||||
|
||||
const height = computed(() => sheep.$platform.capsule.height);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nav-title {
|
||||
font-size: 36rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menu-icon-wrap {
|
||||
.nav-image {
|
||||
// height: 24px;
|
||||
height: 65rpx;
|
||||
width: 65rpx;
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.radius-img {
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
314
sheep/components/s-custom-navbar/components/navbar.vue
Normal file
314
sheep/components/s-custom-navbar/components/navbar.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<su-fixed
|
||||
:noFixed="props.noFixed"
|
||||
:alway="props.alway"
|
||||
:bgStyles="props.bgStyles"
|
||||
:val="0"
|
||||
:index="props.zIndex"
|
||||
noNav
|
||||
:bg="props.bg"
|
||||
:ui="props.ui"
|
||||
:opacity="props.opacity"
|
||||
:placeholder="props.placeholder"
|
||||
:sticky="props.sticky"
|
||||
>
|
||||
<su-status-bar />
|
||||
<!--
|
||||
:class="[{ 'border-bottom': !props.opacity && props.bg != 'bg-none' }]"
|
||||
-->
|
||||
<view class="ui-navbar-box">
|
||||
<view
|
||||
class="ui-bar"
|
||||
:class="
|
||||
props.status == '' ? `text-a` : props.status == 'light' ? 'text-white' : 'text-black'
|
||||
"
|
||||
:style="[{ height: sys_navBar - sys_statusBar + 'px' }]"
|
||||
>
|
||||
<slot name="item"></slot>
|
||||
<view class="right">
|
||||
<!-- #ifdef MP -->
|
||||
<view :style="[state.capsuleStyle]"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</su-fixed>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 标题栏 - 基础组件navbar
|
||||
*
|
||||
* @param {Number} zIndex = 100 - 层级
|
||||
* @param {Boolean} back = true - 是否返回上一页
|
||||
* @param {String} backtext = '' - 返回文本
|
||||
* @param {String} bg = 'bg-white' - 公共Class
|
||||
* @param {String} status = '' - 状态栏颜色
|
||||
* @param {Boolean} alway = true - 是否常驻
|
||||
* @param {Boolean} opacity = false - 是否开启透明渐变
|
||||
* @param {Boolean} opacityBg = false - 开启滑动渐变后,返回按钮是否添加背景
|
||||
* @param {Boolean} noFixed = false - 是否浮动
|
||||
* @param {String} ui = '' - 公共Class
|
||||
* @param {Boolean} capsule = false - 是否开启胶囊返回
|
||||
* @param {Boolean} stopBack = false - 是否禁用返回
|
||||
* @param {Boolean} placeholder = true - 是否开启占位
|
||||
* @param {Object} bgStyles = {} - 背景样式
|
||||
*
|
||||
*/
|
||||
|
||||
import { computed, reactive, onBeforeMount } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
// 本地数据
|
||||
const state = reactive({
|
||||
statusCur: '',
|
||||
capsuleStyle: {},
|
||||
capsuleBack: {},
|
||||
});
|
||||
|
||||
const sys_statusBar = sheep.$platform.device.statusBarHeight;
|
||||
const sys_navBar = sheep.$platform.navbar;
|
||||
|
||||
const props = defineProps({
|
||||
sticky: Boolean,
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
back: {
|
||||
//是否返回上一页
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
backtext: {
|
||||
//返回文本
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
bg: {
|
||||
type: String,
|
||||
default: 'bg-white',
|
||||
},
|
||||
status: {
|
||||
//状态栏颜色 可以选择light dark/其他字符串视为黑色
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 常驻
|
||||
alway: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
opacity: {
|
||||
//是否开启滑动渐变
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
opacityBg: {
|
||||
//开启滑动渐变后 返回按钮是否添加背景
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
noFixed: {
|
||||
//是否浮动
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
ui: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
capsule: {
|
||||
//是否开启胶囊返回
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
stopBack: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: [Boolean],
|
||||
default: true,
|
||||
},
|
||||
bgStyles: {
|
||||
type: Object,
|
||||
default() {},
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['navback']);
|
||||
|
||||
onBeforeMount(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
// 返回
|
||||
const onNavback = () => {
|
||||
sheep.$router.back();
|
||||
};
|
||||
|
||||
// 初始化
|
||||
const init = () => {
|
||||
state.capsuleStyle = {
|
||||
width: sheep.$platform.capsule.width + 'px',
|
||||
height: sheep.$platform.capsule.height + 'px',
|
||||
margin: '0 ' + (sheep.$platform.device.windowWidth - sheep.$platform.capsule.right) + 'px',
|
||||
};
|
||||
|
||||
state.capsuleBack = state.capsuleStyle;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ui-navbar-box {
|
||||
background-color: transparent;
|
||||
width: 100%;
|
||||
|
||||
.ui-bar {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.left {
|
||||
@include flex-bar;
|
||||
|
||||
.back {
|
||||
@include flex-bar;
|
||||
|
||||
.back-icon {
|
||||
@include flex-center;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
margin: 0 10rpx;
|
||||
font-size: 46rpx !important;
|
||||
|
||||
&.opacityIcon {
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(127, 127, 127, 0.5);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 200%;
|
||||
width: 200%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-radius: inherit;
|
||||
transform: scale(0.5);
|
||||
transform-origin: 0 0;
|
||||
opacity: 0.1;
|
||||
border: 1px solid currentColor;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* #ifdef MP-ALIPAY */
|
||||
._icon-back {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.capsule {
|
||||
@include flex-bar;
|
||||
border-radius: 100px;
|
||||
position: relative;
|
||||
|
||||
&.dark {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
&.light {
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 60%;
|
||||
width: 1px;
|
||||
left: 50%;
|
||||
top: 20%;
|
||||
background-color: currentColor;
|
||||
opacity: 0.1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 200%;
|
||||
width: 200%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-radius: inherit;
|
||||
transform: scale(0.5);
|
||||
transform-origin: 0 0;
|
||||
opacity: 0.1;
|
||||
border: 1px solid currentColor;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.capsule-back,
|
||||
.capsule-home {
|
||||
@include flex-center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&.isFristPage {
|
||||
.capsule-back,
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
@include flex-bar;
|
||||
|
||||
.right-content {
|
||||
@include flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
@include flex-center;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
|
||||
.image {
|
||||
display: block;
|
||||
height: 36px;
|
||||
max-width: calc(100vw - 200px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui-bar-bg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
207
sheep/components/s-custom-navbar/s-custom-navbar.vue
Normal file
207
sheep/components/s-custom-navbar/s-custom-navbar.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<!-- 顶部导航栏 -->
|
||||
<template>
|
||||
<navbar
|
||||
:alway="isAlways"
|
||||
:back="false"
|
||||
bg=""
|
||||
:placeholder="isPlaceholder"
|
||||
:bgStyles="bgStyles"
|
||||
:opacity="isOpacity"
|
||||
:sticky="sticky"
|
||||
>
|
||||
<template #item>
|
||||
<view class="nav-box">
|
||||
<view class="nav-icon" v-if="showLeftButton">
|
||||
<view class="icon-box ss-flex" :class="{ 'inner-icon-box': data.styleType === 'inner' }">
|
||||
<view class="icon-button icon-button-left ss-flex ss-row-center" @tap="onClickLeft">
|
||||
<text class="sicon-back" v-if="hasHistory" />
|
||||
<text class="sicon-home" v-else />
|
||||
</view>
|
||||
<view class="line"></view>
|
||||
<view class="icon-button icon-button-right ss-flex ss-row-center" @tap="onClickRight">
|
||||
<text class="sicon-more" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="nav-item"
|
||||
v-for="(item, index) in navList"
|
||||
:key="index"
|
||||
:style="[parseImgStyle(item)]"
|
||||
:class="[{ 'ss-flex ss-col-center ss-row-center': item.type !== 'search' }]"
|
||||
>
|
||||
<navbar-item :data="item" :width="parseImgStyle(item).width" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</navbar>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 装修组件 - 自定义标题栏
|
||||
*
|
||||
*
|
||||
* @property {Number | String} alwaysShow = [0,1] - 是否常驻
|
||||
* @property {Number | String} styleType = [inner] - 是否沉浸式
|
||||
* @property {String | Number} type - 标题背景模式
|
||||
* @property {String} color - 页面背景色
|
||||
* @property {String} src - 页面背景图片
|
||||
*/
|
||||
import { computed, unref } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import Navbar from './components/navbar.vue';
|
||||
import NavbarItem from './components/navbar-item.vue';
|
||||
import { showMenuTools } from '@/sheep/hooks/useModal';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
showLeftButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const hasHistory = sheep.$router.hasHistory();
|
||||
const sticky = computed(() => {
|
||||
if (props.data.styleType === 'inner') {
|
||||
if (props.data.alwaysShow) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (props.data.styleType === 'normal') {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
const navList = computed(() => {
|
||||
// #ifdef MP
|
||||
return props.data.mpCells || [];
|
||||
// #endif
|
||||
return props.data.otherCells || [];
|
||||
});
|
||||
// 页面宽度
|
||||
const windowWidth = sheep.$platform.device.windowWidth;
|
||||
// 单元格宽度
|
||||
const cell = computed(() => {
|
||||
if (unref(navList).length) {
|
||||
// 默认宽度为8个格子,微信公众号右上角有胶囊按钮所以是6个格子
|
||||
let cell = (windowWidth - 90) / 8;
|
||||
// #ifdef MP
|
||||
cell = (windowWidth - 80 - unref(sheep.$platform.capsule).width) / 6;
|
||||
// #endif
|
||||
return cell;
|
||||
}
|
||||
});
|
||||
// 解析位置
|
||||
const parseImgStyle = (item) => {
|
||||
let obj = {
|
||||
width: item.width * cell.value + (item.width - 1) * 10 + 'px',
|
||||
left: item.left * cell.value + (item.left + 1) * 10 + 'px',
|
||||
'border-radius': item.borderRadius + 'px',
|
||||
};
|
||||
return obj;
|
||||
};
|
||||
const isAlways = computed(() =>
|
||||
props.data.styleType === 'inner' ? Boolean(props.data.alwaysShow) : true,
|
||||
);
|
||||
const isOpacity = computed(() =>
|
||||
props.data.styleType === 'normal'
|
||||
? false
|
||||
: props.showLeftButton
|
||||
? false
|
||||
: props.data.styleType === 'inner',
|
||||
);
|
||||
const isPlaceholder = computed(() => props.data.styleType === 'normal');
|
||||
const bgStyles = computed(() => {
|
||||
return {
|
||||
background:
|
||||
props.data.bgType === 'img' && props.data.bgImg
|
||||
? `url(${sheep.$url.cdn(props.data.bgImg)}) no-repeat top center / 100% 100%`
|
||||
: props.data.bgColor,
|
||||
};
|
||||
});
|
||||
|
||||
// 左侧按钮:返回上一页或首页
|
||||
function onClickLeft() {
|
||||
if (hasHistory) {
|
||||
sheep.$router.back();
|
||||
} else {
|
||||
sheep.$router.go('/pages/index/index');
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧按钮:打开快捷菜单
|
||||
function onClickRight() {
|
||||
showMenuTools();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nav-box {
|
||||
width: 750rpx;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
.nav-item {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
left: 20rpx;
|
||||
|
||||
.inner-icon-box {
|
||||
border: 1px solid rgba(#fff, 0.4);
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
background: #ffffff;
|
||||
box-shadow: 0px 0px 4rpx rgba(51, 51, 51, 0.08), 0px 4rpx 6rpx 2rpx rgba(102, 102, 102, 0.12);
|
||||
border-radius: 30rpx;
|
||||
width: 134rpx;
|
||||
height: 56rpx;
|
||||
margin-left: 8rpx;
|
||||
|
||||
.line {
|
||||
width: 2rpx;
|
||||
height: 24rpx;
|
||||
background: #e5e5e7;
|
||||
}
|
||||
|
||||
.sicon-back {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.sicon-home {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.sicon-more {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
width: 67rpx;
|
||||
height: 56rpx;
|
||||
|
||||
&-left:hover {
|
||||
background: rgba(0, 0, 0, 0.16);
|
||||
border-radius: 30rpx 0px 0px 30rpx;
|
||||
}
|
||||
|
||||
&-right:hover {
|
||||
background: rgba(0, 0, 0, 0.16);
|
||||
border-radius: 0px 30rpx 30rpx 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
93
sheep/components/s-empty/s-empty.vue
Normal file
93
sheep/components/s-empty/s-empty.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<view
|
||||
class="ss-flex-col ss-col-center ss-row-center empty-box"
|
||||
:style="[{ paddingTop: paddingTop + 'rpx' }]"
|
||||
>
|
||||
<view class=""><image class="empty-icon" :src="icon" mode="widthFix"></image></view>
|
||||
<view class="empty-text ss-m-t-28 ss-m-b-40">
|
||||
<text v-if="text !== ''">{{ text }}</text>
|
||||
</view>
|
||||
<button class="ss-reset-button empty-btn" v-if="showAction" @tap="clickAction">
|
||||
{{ actionText }}
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import sheep from '@/sheep';
|
||||
/**
|
||||
* 容器组件 - 装修组件的样式容器
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
// 图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 描述
|
||||
text: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 是否显示button
|
||||
showAction: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// button 文字
|
||||
actionText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 链接
|
||||
actionUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 间距
|
||||
paddingTop: {
|
||||
type: String,
|
||||
default: '260',
|
||||
},
|
||||
//主题色
|
||||
buttonColor: {
|
||||
type: String,
|
||||
default: 'var(--ui-BG-Main)',
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(['clickAction']);
|
||||
|
||||
function clickAction() {
|
||||
if (props.actionUrl !== '') {
|
||||
sheep.$router.go(props.actionUrl);
|
||||
}
|
||||
emits('clickAction');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.empty-box {
|
||||
width: 100%;
|
||||
}
|
||||
.empty-icon {
|
||||
width: 240rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.empty-btn {
|
||||
width: 320rpx;
|
||||
height: 70rpx;
|
||||
border: 2rpx solid v-bind('buttonColor');
|
||||
border-radius: 35rpx;
|
||||
font-weight: 500;
|
||||
color: v-bind('buttonColor');
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
250
sheep/components/s-layout/s-layout.vue
Normal file
250
sheep/components/s-layout/s-layout.vue
Normal file
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<view
|
||||
class="page-app"
|
||||
:class="['theme-' + sys.mode, 'main-' + sys.theme, 'font-' + sys.fontSize]"
|
||||
>
|
||||
<view class="page-main" :style="[bgMain]">
|
||||
<!-- 顶部导航栏-情况1:默认通用顶部导航栏 -->
|
||||
<su-navbar
|
||||
v-if="navbar === 'normal'"
|
||||
:title="title"
|
||||
statusBar
|
||||
:color="color"
|
||||
:tools="tools"
|
||||
:opacityBgUi="opacityBgUi"
|
||||
@search="(e) => emits('search', e)"
|
||||
:defaultSearch="defaultSearch"
|
||||
/>
|
||||
|
||||
<!-- 顶部导航栏-情况2:装修组件导航栏-标准 -->
|
||||
<s-custom-navbar
|
||||
v-else-if="navbar === 'custom' && navbarMode === 'normal'"
|
||||
:data="navbarStyle"
|
||||
:showLeftButton="showLeftButton"
|
||||
/>
|
||||
<view class="page-body" :style="[bgBody]">
|
||||
<!-- 顶部导航栏-情况3:沉浸式头部 -->
|
||||
<su-inner-navbar v-if="navbar === 'inner'" :title="title" />
|
||||
<view
|
||||
v-if="navbar === 'inner'"
|
||||
:style="[{ paddingTop: sheep.$platform.navbar + 'px' }]"
|
||||
></view>
|
||||
|
||||
<!-- 顶部导航栏-情况4:装修组件导航栏-沉浸式 -->
|
||||
<s-custom-navbar
|
||||
v-if="navbar === 'custom' && navbarMode === 'inner'"
|
||||
:data="navbarStyle"
|
||||
:showLeftButton="showLeftButton"
|
||||
/>
|
||||
|
||||
<!-- 页面内容插槽 -->
|
||||
<slot />
|
||||
|
||||
<!-- 底部导航 -->
|
||||
<s-tabbar v-if="tabbar !== ''" :path="tabbar" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="page-modal">
|
||||
<!-- 全局授权弹窗 -->
|
||||
<s-auth-modal />
|
||||
<!-- 全局分享弹窗 -->
|
||||
<s-share-modal :shareInfo="shareInfo" />
|
||||
<!-- 全局快捷入口 -->
|
||||
<s-menu-tools />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 模板组件 - 提供页面公共组件,属性,方法
|
||||
*/
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
// #ifdef MP-WEIXIN
|
||||
import { onShareAppMessage } from '@dcloudio/uni-app';
|
||||
// #endif
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
navbar: {
|
||||
type: String,
|
||||
default: 'normal',
|
||||
},
|
||||
opacityBgUi: {
|
||||
type: String,
|
||||
default: 'bg-white',
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tools: {
|
||||
type: String,
|
||||
default: 'title',
|
||||
},
|
||||
keyword: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
navbarStyle: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
styleType: '',
|
||||
type: '',
|
||||
color: '',
|
||||
src: '',
|
||||
list: [],
|
||||
alwaysShow: 0,
|
||||
}),
|
||||
},
|
||||
bgStyle: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
src: '',
|
||||
color: 'var(--ui-BG-1)',
|
||||
}),
|
||||
},
|
||||
tabbar: {
|
||||
type: [String, Boolean],
|
||||
default: '',
|
||||
},
|
||||
onShareAppMessage: {
|
||||
type: [Boolean, Object],
|
||||
default: true,
|
||||
},
|
||||
leftWidth: {
|
||||
type: [Number, String],
|
||||
default: 100,
|
||||
},
|
||||
rightWidth: {
|
||||
type: [Number, String],
|
||||
default: 100,
|
||||
},
|
||||
defaultSearch: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
//展示返回按钮
|
||||
showLeftButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(['search']);
|
||||
|
||||
const sysStore = sheep.$store('sys');
|
||||
const userStore = sheep.$store('user');
|
||||
const appStore = sheep.$store('app');
|
||||
const modalStore = sheep.$store('modal');
|
||||
const sys = computed(() => sysStore);
|
||||
|
||||
// 导航栏模式(因为有自定义导航栏 需要计算)
|
||||
const navbarMode = computed(() => {
|
||||
if (props.navbar === 'normal' || props.navbarStyle.styleType === 'normal') {
|
||||
return 'normal';
|
||||
}
|
||||
return 'inner';
|
||||
});
|
||||
|
||||
// 背景1
|
||||
const bgMain = computed(() => {
|
||||
if (navbarMode.value === 'inner') {
|
||||
return {
|
||||
background: `${props.bgStyle.backgroundColor} url(${sheep.$url.cdn(
|
||||
props.bgStyle.backgroundImage,
|
||||
)}) no-repeat top center / 100% auto`,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
// 背景2
|
||||
const bgBody = computed(() => {
|
||||
if (navbarMode.value === 'normal') {
|
||||
// return {
|
||||
// background: `${props.bgStyle.backgroundColor} url(${sheep.$url.cdn(
|
||||
// props.bgStyle.backgroundImage,
|
||||
// )}) no-repeat top center / 100% auto`,
|
||||
// };
|
||||
return {
|
||||
background: `linear-gradient(to bottom, ${props.bgStyle.backgroundColor} 20%, #fafafa 50%)`,
|
||||
// background: `linear-gradient( 180deg, #00B85B 0%, rgba(2,189,94,0.74) 66%, #07CC68 100%)`,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
// 分享信息
|
||||
const shareInfo = computed(() => {
|
||||
if (props.onShareAppMessage === true) {
|
||||
return sheep.$platform.share.getShareInfo();
|
||||
} else {
|
||||
if (!isEmpty(props.onShareAppMessage)) {
|
||||
sheep.$platform.share.updateShareInfo(props.onShareAppMessage);
|
||||
return props.onShareAppMessage;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序分享
|
||||
onShareAppMessage(() => {
|
||||
return {
|
||||
title: shareInfo.value.title,
|
||||
path: shareInfo.value.path,
|
||||
imageUrl: shareInfo.value.image,
|
||||
};
|
||||
});
|
||||
// #endif
|
||||
|
||||
onShow(() => {
|
||||
if (!isEmpty(shareInfo.value)) {
|
||||
sheep.$platform.share.updateShareInfo(shareInfo.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-app {
|
||||
position: relative;
|
||||
color: var(--ui-TC);
|
||||
background-color: var(--ui-BG-1) !important;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
.page-main {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.page-body {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.page-img {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
118
sheep/components/s-menu-tools/s-menu-tools.vue
Normal file
118
sheep/components/s-menu-tools/s-menu-tools.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<!-- 全局 - 快捷入口 -->
|
||||
<template>
|
||||
<su-popup :show="show" type="top" round="20" backgroundColor="#F0F0F0" @close="closeMenuTools">
|
||||
<su-status-bar />
|
||||
<view class="tools-wrap ss-m-x-30 ss-m-b-16">
|
||||
<view class="title ss-m-b-34 ss-p-t-20">快捷菜单</view>
|
||||
<view class="container-list ss-flex ss-flex-wrap">
|
||||
<view class="list-item ss-m-b-24" v-for="item in list" :key="item.title">
|
||||
<view class="ss-flex-col ss-col-center">
|
||||
<button
|
||||
class="ss-reset-button list-image ss-flex ss-row-center ss-col-center"
|
||||
@tap="onClick(item)"
|
||||
>
|
||||
<image v-if="show" :src="sheep.$url.static(item.icon)" class="list-icon" />
|
||||
</button>
|
||||
<view class="list-title ss-m-t-20">{{ item.title }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</su-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, computed } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { showMenuTools, closeMenuTools } from '@/sheep/hooks/useModal';
|
||||
|
||||
const show = computed(() => sheep.$store('modal').menu);
|
||||
|
||||
function onClick(item) {
|
||||
closeMenuTools();
|
||||
if (item.url) sheep.$router.go(item.url);
|
||||
}
|
||||
|
||||
const list = [
|
||||
{
|
||||
url: '/pages/index/index',
|
||||
icon: '/static/img/shop/tools/home.png',
|
||||
title: '首页',
|
||||
},
|
||||
{
|
||||
url: '/pages/index/search',
|
||||
icon: '/static/img/shop/tools/search.png',
|
||||
title: '搜索',
|
||||
},
|
||||
{
|
||||
url: '/pages/index/user',
|
||||
icon: '/static/img/shop/tools/user.png',
|
||||
title: '个人中心',
|
||||
},
|
||||
{
|
||||
url: '/pages/index/cart',
|
||||
icon: '/static/img/shop/tools/cart.png',
|
||||
title: '购物车',
|
||||
},
|
||||
{
|
||||
url: '/pages/user/goods-log',
|
||||
icon: '/static/img/shop/tools/browse.png',
|
||||
title: '浏览记录',
|
||||
},
|
||||
{
|
||||
url: '/pages/user/goods-collect',
|
||||
icon: '/static/img/shop/tools/collect.png',
|
||||
title: '我的收藏',
|
||||
},
|
||||
{
|
||||
url: '/pages/chat/index',
|
||||
icon: '/static/img/shop/tools/service.png',
|
||||
title: '客服',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tools-wrap {
|
||||
// background: #F0F0F0;
|
||||
// box-shadow: 0px 0px 28rpx 7rpx rgba(0, 0, 0, 0.13);
|
||||
// opacity: 0.98;
|
||||
// border-radius: 0 0 20rpx 20rpx;
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
width: calc(25vw - 20rpx);
|
||||
|
||||
.list-image {
|
||||
width: 104rpx;
|
||||
height: 104rpx;
|
||||
border-radius: 52rpx;
|
||||
background: var(--ui-BG);
|
||||
|
||||
.list-icon {
|
||||
width: 54rpx;
|
||||
height: 54rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.list-title {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uni-popup {
|
||||
top: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.button-hover) {
|
||||
background: #fafafa !important;
|
||||
}
|
||||
</style>
|
||||
168
sheep/components/s-share-modal/canvas-poster/index.vue
Normal file
168
sheep/components/s-share-modal/canvas-poster/index.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<!-- 海报弹窗 -->
|
||||
<template>
|
||||
<su-popup :show="show" round="10" @close="onClosePoster" type="center" class="popup-box">
|
||||
<view class="ss-flex-col ss-col-center ss-row-center">
|
||||
<image
|
||||
v-if="!!painterImageUrl"
|
||||
class="poster-img"
|
||||
:src="painterImageUrl"
|
||||
:style="{
|
||||
height: poster.css.height+ 'px',
|
||||
width: poster.css.width + 'px',
|
||||
}"
|
||||
:show-menu-by-longpress="true"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
class="poster-btn-box ss-m-t-20 ss-flex ss-row-between ss-col-center"
|
||||
v-if="!!painterImageUrl"
|
||||
>
|
||||
<button class="cancel-btn ss-reset-button" @tap="onClosePoster">取消</button>
|
||||
<button class="save-btn ss-reset-button ui-BG-Main" @tap="onSavePoster">
|
||||
{{
|
||||
['wechatOfficialAccount', 'H5'].includes(sheep.$platform.name)
|
||||
? '长按图片保存'
|
||||
: '保存图片'
|
||||
}}
|
||||
</button>
|
||||
</view>
|
||||
<!-- 海报画板:默认隐藏只用来生成海报。生成方式为主动调用 -->
|
||||
<l-painter
|
||||
isCanvasToTempFilePath
|
||||
pathType="url"
|
||||
@success="setPainterImageUrl"
|
||||
hidden
|
||||
ref="painterRef"
|
||||
/>
|
||||
</su-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 海报生成和展示
|
||||
* 提示:小程序码默认跳转首页,由首页进行 spm 参数解析后跳转到对应的分享页面
|
||||
* @description 用于生成分享海报,如:分享商品海报。
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=2389
|
||||
* @property {Boolean} show 弹出层控制
|
||||
* @property {Object} shareInfo 分享信息
|
||||
*/
|
||||
import { reactive, ref, unref } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import { getPosterData } from '@/sheep/components/s-share-modal/canvas-poster/poster';
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
shareInfo: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const poster = reactive({
|
||||
css: {
|
||||
// 根节点若无尺寸,自动获取父级节点
|
||||
width: sheep.$platform.device.windowWidth * 0.9,
|
||||
height: 600,
|
||||
},
|
||||
views: [],
|
||||
});
|
||||
|
||||
const emits = defineEmits(['success', 'close']);
|
||||
|
||||
const onClosePoster = () => {
|
||||
emits('close');
|
||||
};
|
||||
|
||||
const painterRef = ref(); // 海报画板
|
||||
const painterImageUrl = ref(); // 海报 url
|
||||
// 渲染海报
|
||||
const renderPoster = async () => {
|
||||
await painterRef.value.render(unref(poster));
|
||||
};
|
||||
// 获得生成的图片
|
||||
const setPainterImageUrl = (path) => {
|
||||
painterImageUrl.value = path;
|
||||
};
|
||||
// 保存海报图片
|
||||
const onSavePoster = () => {
|
||||
if (['WechatOfficialAccount', 'H5'].includes(sheep.$platform.name)) {
|
||||
sheep.$helper.toast('请长按图片保存');
|
||||
return;
|
||||
}
|
||||
|
||||
// 非H5 保存到相册
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: painterImageUrl.value,
|
||||
success: (res) => {
|
||||
onClosePoster();
|
||||
sheep.$helper.toast('保存成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
sheep.$helper.toast('保存失败');
|
||||
console.log('图片保存失败:', err);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 获得海报数据
|
||||
async function getPoster() {
|
||||
painterImageUrl.value = undefined
|
||||
poster.views = await getPosterData({
|
||||
width: poster.css.width,
|
||||
shareInfo: props.shareInfo,
|
||||
});
|
||||
await renderPoster();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getPoster,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.popup-box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.poster-title {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
// 分享海报
|
||||
.poster-btn-box {
|
||||
width: 600rpx;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: -80rpx;
|
||||
|
||||
.cancel-btn {
|
||||
width: 240rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
background: $white;
|
||||
border-radius: 35rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $dark-9;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
width: 240rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
border-radius: 35rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.poster-img {
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
</style>
|
||||
125
sheep/components/s-share-modal/canvas-poster/poster/goods.js
Normal file
125
sheep/components/s-share-modal/canvas-poster/poster/goods.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import sheep from '@/sheep';
|
||||
import { formatImageUrlProtocol, getWxaQrcode } from './index';
|
||||
|
||||
const goods = async (poster) => {
|
||||
const width = poster.width;
|
||||
const userInfo = sheep.$store('user').userInfo;
|
||||
const wxa_qrcode = await getWxaQrcode(poster.shareInfo.path, poster.shareInfo.query);
|
||||
return [
|
||||
{
|
||||
type: 'image',
|
||||
src: formatImageUrlProtocol(sheep.$url.cdn(sheep.$store('app').platform.share.posterInfo.goods_bg)),
|
||||
css: {
|
||||
width,
|
||||
position: 'fixed',
|
||||
'object-fit': 'contain',
|
||||
top: '0',
|
||||
left: '0',
|
||||
zIndex: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: userInfo.nickname,
|
||||
css: {
|
||||
color: '#333',
|
||||
fontSize: 16,
|
||||
fontFamily: 'sans-serif',
|
||||
position: 'fixed',
|
||||
top: width * 0.06,
|
||||
left: width * 0.22,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
src: formatImageUrlProtocol(sheep.$url.cdn(userInfo.avatar)),
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.04,
|
||||
top: width * 0.04,
|
||||
width: width * 0.14,
|
||||
height: width * 0.14,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
src: formatImageUrlProtocol(poster.shareInfo.poster.image),
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.03,
|
||||
top: width * 0.21,
|
||||
width: width * 0.94,
|
||||
height: width * 0.94,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: poster.shareInfo.poster.title,
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.04,
|
||||
top: width * 1.18,
|
||||
color: '#333',
|
||||
fontSize: 14,
|
||||
lineHeight: 15,
|
||||
maxWidth: width * 0.91,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '¥' + poster.shareInfo.poster.price,
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.04,
|
||||
top: width * 1.31,
|
||||
fontSize: 20,
|
||||
fontFamily: 'OPPOSANS',
|
||||
color: '#333',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text:
|
||||
poster.shareInfo.poster.original_price > 0
|
||||
? '¥' + poster.shareInfo.poster.original_price
|
||||
: '',
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.3,
|
||||
top: width * 1.33,
|
||||
color: '#999',
|
||||
fontSize: 10,
|
||||
fontFamily: 'OPPOSANS',
|
||||
textDecoration: 'line-through',
|
||||
},
|
||||
},
|
||||
// #ifndef MP-WEIXIN
|
||||
{
|
||||
type: 'qrcode',
|
||||
text: poster.shareInfo.link,
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.75,
|
||||
top: width * 1.3,
|
||||
width: width * 0.2,
|
||||
height: width * 0.2,
|
||||
},
|
||||
},
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
{
|
||||
type: 'image',
|
||||
src: wxa_qrcode,
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.75,
|
||||
top: width * 1.3,
|
||||
width: width * 0.2,
|
||||
height: width * 0.2,
|
||||
},
|
||||
},
|
||||
// #endif
|
||||
];
|
||||
};
|
||||
|
||||
export default goods;
|
||||
122
sheep/components/s-share-modal/canvas-poster/poster/groupon.js
Normal file
122
sheep/components/s-share-modal/canvas-poster/poster/groupon.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import sheep from '@/sheep';
|
||||
import { formatImageUrlProtocol, getWxaQrcode } from './index';
|
||||
|
||||
const groupon = async (poster) => {
|
||||
const width = poster.width;
|
||||
const userInfo = sheep.$store('user').userInfo;
|
||||
const wxa_qrcode = await getWxaQrcode(poster.shareInfo.path, poster.shareInfo.query);
|
||||
return [
|
||||
{
|
||||
type: 'image',
|
||||
src: formatImageUrlProtocol(sheep.$url.cdn(sheep.$store('app').platform.share.posterInfo.groupon_bg)),
|
||||
css: {
|
||||
width,
|
||||
position: 'fixed',
|
||||
'object-fit': 'contain',
|
||||
top: '0',
|
||||
left: '0',
|
||||
zIndex: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: userInfo.nickname,
|
||||
css: {
|
||||
color: '#333',
|
||||
fontSize: 16,
|
||||
fontFamily: 'sans-serif',
|
||||
position: 'fixed',
|
||||
top: width * 0.06,
|
||||
left: width * 0.22,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
src: formatImageUrlProtocol(sheep.$url.cdn(userInfo.avatar)),
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.04,
|
||||
top: width * 0.04,
|
||||
width: width * 0.14,
|
||||
height: width * 0.14,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
src: formatImageUrlProtocol(poster.shareInfo.poster.image),
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.03,
|
||||
top: width * 0.21,
|
||||
width: width * 0.94,
|
||||
height: width * 0.94,
|
||||
borderRadius: 10,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: poster.shareInfo.poster.title,
|
||||
css: {
|
||||
color: '#333',
|
||||
fontSize: 14,
|
||||
position: 'fixed',
|
||||
top: width * 1.18,
|
||||
left: width * 0.04,
|
||||
maxWidth: width * 0.91,
|
||||
lineHeight: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '¥' + poster.shareInfo.poster.price,
|
||||
css: {
|
||||
color: '#ff0000',
|
||||
fontSize: 20,
|
||||
fontFamily: 'OPPOSANS',
|
||||
position: 'fixed',
|
||||
top: width * 1.3,
|
||||
left: width * 0.04,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: '2人团',
|
||||
css: {
|
||||
color: '#fff',
|
||||
fontSize: 12,
|
||||
fontFamily: 'OPPOSANS',
|
||||
position: 'fixed',
|
||||
left: width * 0.84,
|
||||
top: width * 1.3,
|
||||
},
|
||||
},
|
||||
// #ifndef MP-WEIXIN
|
||||
{
|
||||
type: 'qrcode',
|
||||
text: poster.shareInfo.link,
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.5,
|
||||
top: width * 1.3,
|
||||
width: width * 0.2,
|
||||
height: width * 0.2,
|
||||
},
|
||||
},
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
{
|
||||
type: 'image',
|
||||
src: wxa_qrcode,
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.75,
|
||||
top: width * 1.3,
|
||||
width: width * 0.2,
|
||||
height: width * 0.2,
|
||||
},
|
||||
},
|
||||
// #endif
|
||||
];
|
||||
};
|
||||
|
||||
export default groupon;
|
||||
39
sheep/components/s-share-modal/canvas-poster/poster/index.js
Normal file
39
sheep/components/s-share-modal/canvas-poster/poster/index.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import user from './user';
|
||||
import goods from './goods';
|
||||
import groupon from './groupon';
|
||||
import SocialApi from '@/sheep/api/member/social';
|
||||
|
||||
export function getPosterData(options) {
|
||||
switch (options.shareInfo.poster.type) {
|
||||
case 'user':
|
||||
return user(options);
|
||||
case 'goods':
|
||||
return goods(options);
|
||||
case 'groupon':
|
||||
return groupon(options);
|
||||
}
|
||||
}
|
||||
|
||||
export function formatImageUrlProtocol(url) {
|
||||
// #ifdef H5
|
||||
// H5平台 https协议下需要转换
|
||||
if (window.location.protocol === 'https:' && url.indexOf('http:') === 0) {
|
||||
url = url.replace('http:', 'https:');
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 小程序平台 需要强制转换为https协议
|
||||
if (url.indexOf('http:') === 0) {
|
||||
url = url.replace('http:', 'https:');
|
||||
}
|
||||
// #endif
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
// 获得微信小程序码 (Base64 image)
|
||||
export async function getWxaQrcode(path, query) {
|
||||
const res = await SocialApi.getWxaQrcode(path, query);
|
||||
return 'data:image/png;base64,' + res.data;
|
||||
}
|
||||
74
sheep/components/s-share-modal/canvas-poster/poster/user.js
Normal file
74
sheep/components/s-share-modal/canvas-poster/poster/user.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import sheep from '@/sheep';
|
||||
import { formatImageUrlProtocol, getWxaQrcode } from './index';
|
||||
|
||||
const user = async (poster) => {
|
||||
const width = poster.width;
|
||||
const userInfo = sheep.$store('user').userInfo;
|
||||
const wxa_qrcode = await getWxaQrcode(poster.shareInfo.path, poster.shareInfo.query);
|
||||
return [
|
||||
{
|
||||
type: 'image',
|
||||
src: formatImageUrlProtocol(sheep.$url.cdn(sheep.$store('app').platform.share.posterInfo.user_bg)),
|
||||
css: {
|
||||
width,
|
||||
position: 'fixed',
|
||||
'object-fit': 'contain',
|
||||
top: '0',
|
||||
left: '0',
|
||||
zIndex: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: userInfo.nickname,
|
||||
css: {
|
||||
color: '#333',
|
||||
fontSize: 14,
|
||||
textAlign: 'center',
|
||||
fontFamily: 'sans-serif',
|
||||
position: 'fixed',
|
||||
top: width * 0.4,
|
||||
left: width / 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
src: formatImageUrlProtocol(sheep.$url.cdn(userInfo.avatar)),
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.4,
|
||||
top: width * 0.16,
|
||||
width: width * 0.2,
|
||||
height: width * 0.2,
|
||||
},
|
||||
},
|
||||
// #ifndef MP-WEIXIN
|
||||
{
|
||||
type: 'qrcode',
|
||||
text: poster.shareInfo.link,
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.35,
|
||||
top: width * 0.84,
|
||||
width: width * 0.3,
|
||||
height: width * 0.3,
|
||||
},
|
||||
},
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
{
|
||||
type: 'image',
|
||||
src: wxa_qrcode,
|
||||
css: {
|
||||
position: 'fixed',
|
||||
left: width * 0.35,
|
||||
top: width * 0.84,
|
||||
width: width * 0.3,
|
||||
height: width * 0.3,
|
||||
},
|
||||
},
|
||||
// #endif
|
||||
];
|
||||
};
|
||||
|
||||
export default user;
|
||||
196
sheep/components/s-share-modal/s-share-modal.vue
Normal file
196
sheep/components/s-share-modal/s-share-modal.vue
Normal file
@@ -0,0 +1,196 @@
|
||||
<!-- 全局分享弹框 -->
|
||||
<template>
|
||||
<view>
|
||||
<su-popup :show="state.showShareGuide" :showClose="false" @close="onCloseGuide" />
|
||||
<view v-if="state.showShareGuide" class="guide-wrap">
|
||||
<image class="guide-image" :src="sheep.$url.static('/static/img/shop/share/share_guide.png')" />
|
||||
</view>
|
||||
|
||||
<su-popup :show="show" round="10" :showClose="false" @close="closeShareModal">
|
||||
<!-- 分享 tools -->
|
||||
<view class="share-box">
|
||||
<view class="share-list-box ss-flex">
|
||||
<!-- 操作 ①:发送给微信好友 -->
|
||||
<button
|
||||
v-if="shareConfig.methods.includes('forward')"
|
||||
class="share-item share-btn ss-flex-col ss-col-center"
|
||||
open-type="share"
|
||||
@tap="onShareByForward"
|
||||
>
|
||||
<image class="share-img" :src="sheep.$url.static('/static/img/shop/share/share_wx.png')" mode="" />
|
||||
<text class="share-title">微信好友</text>
|
||||
</button>
|
||||
|
||||
<!-- 操作 ②:生成海报图片 -->
|
||||
<button
|
||||
v-if="shareConfig.methods.includes('poster')"
|
||||
class="share-item share-btn ss-flex-col ss-col-center"
|
||||
@tap="onShareByPoster"
|
||||
>
|
||||
<image
|
||||
class="share-img"
|
||||
:src="sheep.$url.static('/static/img/shop/share/share_poster.png')"
|
||||
mode=""
|
||||
/>
|
||||
<text class="share-title">生成海报</text>
|
||||
</button>
|
||||
|
||||
<!-- 操作 ③:生成链接 -->
|
||||
<button
|
||||
v-if="shareConfig.methods.includes('link')"
|
||||
class="share-item share-btn ss-flex-col ss-col-center"
|
||||
@tap="onShareByCopyLink"
|
||||
>
|
||||
<image class="share-img" :src="sheep.$url.static('/static/img/shop/share/share_link.png')" mode="" />
|
||||
<text class="share-title">复制链接</text>
|
||||
</button>
|
||||
</view>
|
||||
<view class="share-foot ss-flex ss-row-center ss-col-center" @tap="closeShareModal">
|
||||
取消
|
||||
</view>
|
||||
</view>
|
||||
</su-popup>
|
||||
|
||||
<!-- 分享海报,对应操作 ② -->
|
||||
<canvas-poster
|
||||
ref="SharePosterRef"
|
||||
:show="state.showPosterModal"
|
||||
:shareInfo="shareInfo"
|
||||
@close="state.showPosterModal = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
/**
|
||||
* 分享弹窗
|
||||
*/
|
||||
import { ref, unref, reactive, computed } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
import canvasPoster from './canvas-poster/index.vue';
|
||||
import { closeShareModal, showAuthModal } from '@/sheep/hooks/useModal';
|
||||
|
||||
const show = computed(() => sheep.$store('modal').share);
|
||||
const shareConfig = computed(() => sheep.$store('app').platform.share);
|
||||
const SharePosterRef = ref('');
|
||||
|
||||
const props = defineProps({
|
||||
shareInfo: {
|
||||
type: Object,
|
||||
default() {},
|
||||
},
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
showShareGuide: false, // H5 的指引
|
||||
showPosterModal: false, // 海报弹窗
|
||||
});
|
||||
|
||||
// 操作 ②:生成海报分享
|
||||
const onShareByPoster = () => {
|
||||
closeShareModal();
|
||||
if (!sheep.$store('user').isLogin) {
|
||||
showAuthModal();
|
||||
return;
|
||||
}
|
||||
console.log(props.shareInfo);
|
||||
unref(SharePosterRef).getPoster();
|
||||
state.showPosterModal = true;
|
||||
};
|
||||
|
||||
// 操作 ①:直接转发分享
|
||||
const onShareByForward = () => {
|
||||
closeShareModal();
|
||||
|
||||
// #ifdef H5
|
||||
if (['WechatOfficialAccount', 'H5'].includes(sheep.$platform.name)) {
|
||||
state.showShareGuide = true;
|
||||
return;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
uni.share({
|
||||
provider: 'weixin',
|
||||
scene: 'WXSceneSession',
|
||||
type: 0,
|
||||
href: props.shareInfo.link,
|
||||
title: props.shareInfo.title,
|
||||
summary: props.shareInfo.desc,
|
||||
imageUrl: props.shareInfo.image,
|
||||
success: (res) => {
|
||||
console.log('success:' + JSON.stringify(res));
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('fail:' + JSON.stringify(err));
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
};
|
||||
|
||||
// 操作 ③:复制链接分享
|
||||
const onShareByCopyLink = () => {
|
||||
sheep.$helper.copyText(props.shareInfo.link);
|
||||
closeShareModal();
|
||||
};
|
||||
|
||||
function onCloseGuide() {
|
||||
state.showShareGuide = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.guide-image {
|
||||
right: 30rpx;
|
||||
top: 0;
|
||||
position: fixed;
|
||||
width: 580rpx;
|
||||
height: 430rpx;
|
||||
z-index: 10080;
|
||||
}
|
||||
|
||||
// 分享tool
|
||||
.share-box {
|
||||
background: $white;
|
||||
width: 750rpx;
|
||||
border-radius: 30rpx 30rpx 0 0;
|
||||
padding-top: 30rpx;
|
||||
|
||||
.share-foot {
|
||||
font-size: 24rpx;
|
||||
color: $gray-b;
|
||||
height: 80rpx;
|
||||
border-top: 1rpx solid $gray-e;
|
||||
}
|
||||
|
||||
.share-list-box {
|
||||
.share-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.share-item {
|
||||
flex: 1;
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
.share-img {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
background: $gray-f;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.share-title {
|
||||
font-size: 24rpx;
|
||||
color: $dark-6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
90
sheep/components/s-tabbar/s-tabbar.vue
Normal file
90
sheep/components/s-tabbar/s-tabbar.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<view class="u-page__item" v-if="tabbar?.items?.length > 0">
|
||||
<su-tabbar
|
||||
:value="path"
|
||||
:fixed="true"
|
||||
:placeholder="true"
|
||||
:safeAreaInsetBottom="true"
|
||||
:inactiveColor="tabbar.style.color"
|
||||
:activeColor="tabbar.style.activeColor"
|
||||
:midTabBar="tabbar.mode === 2"
|
||||
:customStyle="tabbarStyle"
|
||||
>
|
||||
<su-tabbar-item
|
||||
v-for="(item, index) in tabbar.items"
|
||||
:key="item.text"
|
||||
:text="item.text"
|
||||
:name="item.url"
|
||||
:isCenter="getTabbarCenter(index)"
|
||||
:centerImage="sheep.$url.cdn(item.iconUrl)"
|
||||
@tap="sheep.$router.go(item.url)"
|
||||
>
|
||||
<template v-slot:active-icon>
|
||||
<image class="u-page__item__slot-icon" :src="sheep.$url.cdn(item.activeIconUrl)"></image>
|
||||
</template>
|
||||
<template v-slot:inactive-icon>
|
||||
<image class="u-page__item__slot-icon" :src="sheep.$url.cdn(item.iconUrl)"></image>
|
||||
</template>
|
||||
</su-tabbar-item>
|
||||
</su-tabbar>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, unref } from 'vue';
|
||||
import sheep from '@/sheep';
|
||||
|
||||
const tabbar = computed(() => {
|
||||
return sheep.$store('app').template.basic?.tabbar;
|
||||
});
|
||||
|
||||
const tabbarStyle = computed(() => {
|
||||
const backgroundStyle = tabbar.value.style;
|
||||
if (backgroundStyle.bgType === 'color') {
|
||||
return { background: backgroundStyle.bgColor };
|
||||
}
|
||||
if (backgroundStyle.bgType === 'img')
|
||||
return {
|
||||
background: `url(${sheep.$url.cdn(
|
||||
backgroundStyle.bgImg,
|
||||
)}) no-repeat top center / 100% auto`,
|
||||
};
|
||||
});
|
||||
|
||||
const getTabbarCenter = (index) => {
|
||||
if (unref(tabbar).mode !== 2) return false;
|
||||
return unref(tabbar).items % 2 > 0
|
||||
? Math.ceil(unref(tabbar).items.length / 2) === index + 1
|
||||
: false;
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
path: String,
|
||||
default: '',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.u-page {
|
||||
padding: 0;
|
||||
|
||||
&__item {
|
||||
&__title {
|
||||
color: var(--textSize);
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
font-size: 15px;
|
||||
|
||||
&__slot-title {
|
||||
color: var(--textSize);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&__slot-icon {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user