This commit is contained in:
Your Name
2026-03-01 14:17:59 +08:00
parent 65aa12f146
commit a07e844c47
6071 changed files with 777651 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
<template>
<view class="p-[30rpx]">
<u-parse :html="agreementContent"></u-parse>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getPolicy } from '@/api/app'
let agreementType = ref('') // 协议类型
const agreementContent = ref('') // 协议内容
const getData = async (type) => {
const res = await getPolicy({ type })
agreementContent.value = res.content
uni.setNavigationBarTitle({
title: String(res.title)
})
}
onLoad((options: any) => {
if (options.type) {
agreementType = options.type
getData(agreementType)
}
})
</script>
<style lang="scss" scoped></style>
+30
View File
@@ -0,0 +1,30 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view class="as-us flex flex-1 flex-col items-center">
<image :src="appStore.getWebsiteConfig.shop_logo" mode="" class="img"></image>
<view class="text-content mt-[20rpx]">当前版本{{ appStore.config.version }}</view>
</view>
</template>
<script setup lang="ts">
import { useAppStore } from '@/stores/app'
const appStore = useAppStore()
</script>
<style lang="scss" scoped>
.as-us {
.img {
width: 160rpx;
height: 160rpx;
border-radius: 20rpx;
margin-top: 96rpx;
}
}
</style>
@@ -0,0 +1,112 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view class="bg-white min-h-full flex flex-col items-center px-[40rpx] pt-[40rpx] box-border">
<view class="w-full">
<view class="text-2xl font-medium mb-[60rpx]">绑定手机号</view>
<view
class="px-[18rpx] border border-solid border-lightc border-light rounded-[10rpx] h-[100rpx] items-center flex"
>
<u-input
class="flex-1"
v-model="formData.mobile"
:border="false"
placeholder="请输入手机号码"
/>
</view>
<view
class="px-[18rpx] border border-solid border-lightc border-light rounded-[10rpx] h-[100rpx] items-center flex mt-[40rpx]"
>
<u-input
class="flex-1"
v-model="formData.code"
placeholder="请输入验证码"
:border="false"
/>
<view
class="border-l border-solid border-0 border-light pl-3 leading-4 ml-3 w-[180rpx]"
@click="sendSms"
>
<u-verification-code
ref="uCodeRef"
:seconds="60"
@change="codeChange"
change-text="x秒"
/>
<text :class="formData.mobile ? 'text-primary' : 'text-muted'">
{{ codeTips }}
</text>
</view>
</view>
<view class="mt-[40rpx]">
<u-button
type="primary"
hover-class="none"
:customStyle="{
height: '100rpx',
opacity:
formData.mobile && formData.code
? '1'
: '0.5'
}"
@click="handleConfirm"
>
确定
</u-button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { userBindMobile } from '@/api/user'
import { smsSend } from '@/api/app'
import { SMSEnum } from '@/enums/appEnums'
import { reactive, ref, shallowRef } from 'vue'
import { useUserStore } from '@/stores/user'
const uCodeRef = shallowRef()
const codeTips = ref('')
const userStore = useUserStore()
const codeChange = (text: string) => {
codeTips.value = text
}
const formData = reactive({
type: 'bind',
mobile: '',
code: ''
})
const sendSms = async () => {
if (!formData.mobile) return uni.$u.toast('请输入手机号码')
if (uCodeRef.value?.canGetCode) {
await smsSend({
scene: SMSEnum.BIND_MOBILE,
mobile: formData.mobile
})
uni.$u.toast('发送成功')
uCodeRef.value?.start()
}
}
const handleConfirm = async () => {
if (!formData.mobile) return uni.$u.toast('请输入手机号码')
if (!formData.code) return uni.$u.toast('请输入验证码')
await userBindMobile(formData, { token: userStore.temToken })
uni.$u.toast('绑定成功')
userStore.login(userStore.temToken!)
uni.navigateBack()
}
</script>
<style lang="scss">
page {
height: 100%;
}
</style>
@@ -0,0 +1,90 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view
class="register bg-white min-h-full flex flex-col items-center px-[40rpx] pt-[100rpx] box-border"
>
<view class="w-full">
<view class="text-2xl font-medium mb-[60rpx]">
{{ type == 'set' ? '设置登录密码' : '修改登录密码' }}
</view>
<u-form borderBottom :label-width="150">
<u-form-item label="原密码" borderBottom v-if="type != 'set'">
<u-input
class="flex-1"
type="password"
v-model="formData.old_password"
:border="false"
placeholder="请输入原来的密码"
/>
</u-form-item>
<u-form-item label="新密码" borderBottom>
<u-input
class="flex-1"
type="password"
v-model="formData.password"
placeholder="6-20位数字+字母或符号组合"
:border="false"
/>
</u-form-item>
<u-form-item label="确认密码" borderBottom>
<u-input
class="flex-1"
type="password"
v-model="formData.password_confirm"
placeholder="再次输入新密码"
:border="false"
/>
</u-form-item>
</u-form>
<view class="mt-[100rpx]">
<u-button type="primary" shape="circle" @click="handleConfirm"> 确定 </u-button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { userChangePwd } from '@/api/user'
import { onLoad } from '@dcloudio/uni-app'
import { reactive, ref } from 'vue'
const type = ref('')
const formData = reactive<any>({
password: '',
password_confirm: ''
})
const handleConfirm = async () => {
if (!formData.old_password && type.value != 'set') return uni.$u.toast('请输入原来的密码')
if (!formData.password) return uni.$u.toast('请输入密码')
if (!formData.password_confirm) return uni.$u.toast('请输入确认密码')
if (formData.password != formData.password_confirm) return uni.$u.toast('两次输入的密码不一致')
await userChangePwd(formData)
uni.$u.toast('操作成功')
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
onLoad((options) => {
type.value = options.type || ''
if (type.value == 'set') {
uni.setNavigationBarTitle({
title: '设置登录密码'
})
}
})
</script>
<style lang="scss">
page {
height: 100%;
}
</style>
@@ -0,0 +1,70 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<z-paging
ref="paging"
v-model="collectData"
@query="queryList"
:fixed="false"
height="100%"
use-page-scroll
>
<u-swipe-action
:show="item.show"
:index="index"
v-for="(item, index) in collectData"
:key="item.id"
@click="handleCollect"
:options="options"
btn-width="120"
>
<news-card :item="item" :newsId="item.article_id"></news-card>
</u-swipe-action>
</z-paging>
</template>
<script lang="ts" setup>
import { ref, reactive, shallowRef } from 'vue'
import { getCollect, cancelCollect } from '@/api/news'
const paging = shallowRef()
const options = reactive([
{
text: '取消收藏',
style: {
color: '#FFFFFF',
backgroundColor: '#FF2C3C'
}
}
])
const collectData: any = ref([])
const queryList = async (pageNo, pageSize) => {
const { lists } = await getCollect()
lists.forEach((item: any) => {
item.show = false
})
collectData.value = lists
paging.value.complete(lists)
}
const handleCollect = async (index: number): Promise<void> => {
try {
const article_id: number = collectData.value[index].article_id
await cancelCollect({ id: article_id })
uni.$u.toast('已取消收藏')
paging.value.reload()
} catch (err) {
//TODO handle the exception
console.log('取消收藏报错=>', err)
}
}
</script>
<style scoped></style>
@@ -0,0 +1,34 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view class="customer-service">
<view v-for="(item, index) in state.pages" :key="index">
<template v-if="item.name == 'customer-service'">
<w-customer-service :content="item.content" :styles="item.styles" />
</template>
</view>
</view>
</template>
<script setup lang="ts">
import { getDecorate } from '@/api/shop'
import { reactive } from 'vue'
const state = reactive<{
pages: any[]
}>({
pages: []
})
const getData = async () => {
const data = await getDecorate({ id: 3 })
state.pages = JSON.parse(data.data)
}
getData()
</script>
<style></style>
+7
View File
@@ -0,0 +1,7 @@
<template>
<div></div>
</template>
<script setup lang="ts"></script>
<style></style>
+119
View File
@@ -0,0 +1,119 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view
class="register bg-white min-h-full flex flex-col items-center px-[40rpx] pt-[100rpx] box-border"
>
<view class="w-full">
<view class="text-2xl font-medium mb-[60rpx]">忘记登录密码</view>
<u-form borderBottom :label-width="150">
<u-form-item label="手机号" borderBottom>
<u-input
class="flex-1"
v-model="formData.mobile"
:border="false"
placeholder="请输入手机号码"
/>
</u-form-item>
<u-form-item label="验证码" borderBottom>
<u-input
class="flex-1"
v-model="formData.code"
placeholder="请输入验证码"
:border="false"
/>
<view
class="border-l border-solid border-0 border-light pl-3 text-muted leading-4 ml-3 w-[180rpx]"
@click="sendSms"
>
<u-verification-code
ref="uCodeRef"
:seconds="60"
@change="codeChange"
change-text="x秒"
/>
<text :class="formData.mobile ? 'text-primary' : 'text-muted'">
{{ codeTips }}
</text>
</view>
</u-form-item>
<u-form-item label="新密码" borderBottom>
<u-input
class="flex-1"
type="password"
v-model="formData.password"
placeholder="6-20位数字+字母或符号组合"
:border="false"
/>
</u-form-item>
<u-form-item label="确认密码" borderBottom>
<u-input
class="flex-1"
type="password"
v-model="formData.password_confirm"
placeholder="再次输入新密码"
:border="false"
/>
</u-form-item>
</u-form>
<view class="mt-[100rpx]">
<u-button type="primary" shape="circle" @click="handleConfirm"> 确定 </u-button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { smsSend } from '@/api/app'
import { forgotPassword } from '@/api/user'
import { SMSEnum } from '@/enums/appEnums'
import { reactive, ref, shallowRef } from 'vue'
const uCodeRef = shallowRef()
const codeTips = ref('')
const formData = reactive({
mobile: '',
code: '',
password: '',
password_confirm: ''
})
const codeChange = (text: string) => {
codeTips.value = text
}
const sendSms = async () => {
if (!formData.mobile) return
if (uCodeRef.value?.canGetCode) {
await smsSend({
scene: SMSEnum.FIND_PASSWORD,
mobile: formData.mobile
})
uni.$u.toast('发送成功')
uCodeRef.value?.start()
}
}
const handleConfirm = async () => {
if (!formData.mobile) return uni.$u.toast('请输入手机号码')
if (!formData.password) return uni.$u.toast('请输入密码')
if (!formData.password_confirm) return uni.$u.toast('请输入确认密码')
if (formData.password != formData.password_confirm) return uni.$u.toast('两次输入的密码不一致')
await forgotPassword(formData)
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
</script>
<style lang="scss">
page {
height: 100%;
}
</style>
@@ -0,0 +1,158 @@
<template>
<!-- modal:隐私授权弹窗-->
<view v-if="show" class="modal-box" @tap.stop>
<view class="dialog" @tap.stop>
<view class="title">隐私政策提示</view>
<view class="content">
欢迎使用{{
appStore.getWebsiteConfig.shop_name
}}小程序请您在使用前点击
<text
class="text-[#243245]"
hover-class="hover"
@click="openContract"
>
{{ name }}
</text>
并仔细阅读如您同意全部内容请点击同意开始使用我们的服务
</view>
<view class="btn-box">
<button
class="btn disagree"
hover-class="hover"
@click="disagreePrivacy"
>
不同意
</button>
<button
class="btn bg-primary text-white"
hover-class="hover"
id="agree-btn"
open-type="agreePrivacyAuthorization"
@agreeprivacyauthorization="agreePrivacy"
>
同意
</button>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useAppStore } from '@/stores/app'
const appStore = useAppStore()
const name = ref<string>('')
const show = ref<boolean>(false)
interface PrivacyRes {
errMsg: string
privacyContractName: string
needAuthorization: boolean
}
if (wx.getPrivacySetting) {
wx.getPrivacySetting({
success(res: PrivacyRes) {
console.log(res)
name.value = res.privacyContractName
show.value = res.needAuthorization
}
})
}
const openContract = () => {
wx.openPrivacyContract({
success: () => {},
fail: () => {}
})
}
const disagreeHandle = () => {
// 用户点击拒绝后
show.value = false
}
const disagreePrivacy = () => {
uni.$u.toast('同意隐私政策后可继续使用')
// wx.exitMiniProgram()
}
const agreePrivacy = () => {
show.value = false
}
</script>
<style scoped>
.modal-box {
height: 100vh;
width: 100vw;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 99999;
}
.modal-box .dialog {
box-sizing: border-box;
position: absolute;
bottom: 0;
width: 100%;
padding: 40rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
background: #ffffff;
border-radius: 20rpx 20rpx 0 0;
}
.modal-box .title {
text-align: center;
color: #333;
font-weight: bold;
font-size: 34rpx;
}
.modal-box .content {
display: block;
font-size: 28rpx;
color: #666;
margin-top: 20rpx;
text-align: justify;
line-height: 1.6;
padding: 10rpx 20rpx;
}
.modal-box .btn-box {
margin-top: 50rpx;
padding: 0 30rpx;
padding-bottom: 30rpx;
display: flex;
text-align: center;
}
.modal-box .btn::after {
border: none;
display: none;
}
.modal-box .btn-box .btn {
width: 50%;
height: 76rpx;
line-height: 76rpx;
margin: 0 10rpx;
padding: 0;
align-items: center;
justify-content: center;
border-radius: 60px;
font-size: 28rpx;
font-weight: 500;
}
.modal-box .disagree {
color: #0f0f0f;
background: #f5f5f5;
}
</style>
+183
View File
@@ -0,0 +1,183 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view class="index" :style="pageStyle">
<!-- 组件 -->
<template
v-for="(item, index) in state.pages"
:key="index"
>
<template v-if="item.name == 'search'">
<w-search
:pageMeta="state.meta"
:content="item.content"
:styles="item.styles"
:percent="percent"
:isLargeScreen="isLargeScreen"
/>
</template>
<template v-if="item.name == 'banner'">
<w-banner
:content="item.content"
:styles="item.styles"
:isLargeScreen="isLargeScreen"
@change="handleBanner"
/>
</template>
<template v-if="item.name == 'nav'">
<w-nav :content="item.content" :styles="item.styles"/>
</template>
<template v-if="item.name == 'middle-banner'">
<w-middle-banner :content="item.content" :styles="item.styles" />
</template>
</template>
<view class="article" v-if="state.article.length">
<view
class="flex items-center article-title mx-[20rpx] my-[30rpx] text-lg font-medium"
>
最新资讯
</view>
<news-card
v-for="item in state.article"
:key="item.id"
:news-id="item.id"
:item="item"
/>
</view>
<!-- #ifdef H5 -->
<view class="text-center py-4 mb-12">
<router-navigate
class="mx-1 text-xs text-[#495770]"
:to="{
path: '/pages/webview/webview',
query: {
url: item.value
}
}"
v-for="item in appStore.getCopyrightConfig"
:key="item.key"
>
{{ item.key }}
</router-navigate>
</view>
<!-- #endif -->
<!-- 返回顶部按钮 -->
<u-back-top
:scroll-top="scrollTop"
:top="100"
:customStyle="{
backgroundColor: '#FFF',
color: '#000',
boxShadow: '0px 3px 6px rgba(0, 0, 0, 0.1)'
}"
>
</u-back-top>
<!-- #ifdef MP -->
<!-- 微信小程序隐私弹窗 -->
<MpPrivacyPopup></MpPrivacyPopup>
<!-- #endif -->
<tabbar/>
</view>
</template>
<script setup lang="ts">
import {getIndex} from '@/api/shop'
import {onLoad, onPageScroll} from "@dcloudio/uni-app";
import {computed, reactive, ref} from 'vue'
import {useAppStore} from '@/stores/app'
// #ifdef MP
import MpPrivacyPopup from './component/mp-privacy-popup.vue'
// #endif
const appStore = useAppStore()
const state = reactive<{
pages: any[]
meta: any[]
article: any[]
bannerImage: string
}>({
pages: [],
meta: [],
article: [],
bannerImage: ''
})
const scrollTop = ref<number>(0)
const percent = ref<number>(0)
// 是否联动背景图
const isLinkage = computed(() => {
return state.pages.find((item: any) => item.name === 'banner')?.content.bg_style === 1
})
// 是否大屏banner
const isLargeScreen = computed(() => {
return state.pages.find((item: any) => item.name === 'banner')?.content.style === 2
})
// 根页面样式
const pageStyle = computed(() => {
const {bg_type, bg_color, bg_image} = state.meta[0]?.content ?? {}
if (!isLinkage.value) {
return bg_type == 1 ?
{'background-color': bg_color} :
{'background-image': `url(${bg_image})`}
}
else return {'background-image': `url(${state.bannerImage})`}
})
const handleBanner = (url: string) => {
state.bannerImage = url
}
const getData = async () => {
const data = await getIndex()
state.pages = JSON.parse(data?.page?.data)
state.meta = JSON.parse(data?.page?.meta)
state.article = data.article
uni.setNavigationBarTitle({
title: state.meta[0].content.title
})
}
onPageScroll((event: any) => {
scrollTop.value = event.scrollTop
const top = uni.upx2px(100)
percent.value = event.scrollTop / top > 1 ? 1 : event.scrollTop / top
})
onLoad(() => { getData() })
</script>
<style lang="scss" scoped>
.index {
position: relative;
background-repeat: no-repeat;
background-size: 100% auto;
overflow: hidden;
width: 100%;
transition: all 1s;
min-height: calc(100vh - env(safe-area-inset-bottom));
}
.article-title {
&::before {
content: '';
width: 8rpx;
height: 34rpx;
display: block;
margin-right: 10rpx;
@apply bg-primary;
}
}
</style>
+480
View File
@@ -0,0 +1,480 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar :front-color="$theme.navColor" :background-color="$theme.navBgColor" />
<!-- #endif -->
</page-meta>
<view
class="bg-white login min-h-full flex flex-col items-center px-[40rpx] pt-[120rpx] box-border"
>
<view>
<image
:src="appStore.getWebsiteConfig.shop_logo"
mode="widthFix"
class="w-[160rpx] h-[160rpx] rounded-full"
/>
</view>
<view class="w-full mt-[140rpx] pb-[60rpx]">
<block v-if="!phoneLogin">
<!-- #ifdef MP-WEIXIN || H5 -->
<view v-if="isOpenOtherAuth && isWeixin && inWxAuth">
<u-button
type="primary"
@click="wxLogin"
:customStyle="{ height: '100rpx' }"
hover-class="none"
>
用户一键登录
</u-button>
</view>
<!-- #endif -->
<view class="mt-[40rpx]">
<u-button
@click="phoneLogin = !phoneLogin"
:customStyle="{ height: '100rpx' }"
hover-class="none"
>
手机号登录
</u-button>
</view>
</block>
<block v-if="phoneLogin">
<!-- 密码登录 -->
<template
v-if="
formData.scene == LoginWayEnum.ACCOUNT &&
includeLoginWay(LoginWayEnum.ACCOUNT)
"
>
<view
class="px-[18rpx] border border-solid border-lightc border-light rounded-[10rpx] h-[100rpx] items-center flex"
>
<u-input
class="flex-1"
v-model="formData.account"
:border="false"
placeholder="输入账号"
/>
</view>
<view
class="px-[18rpx] py-[10rpx] border border-solid border-light rounded-[10rpx] flex h-[100rpx] items-center mt-[40rpx]"
>
<u-input
class="flex-1"
v-model="formData.password"
type="password"
placeholder="输入密码"
:border="false"
/>
<navigator url="/pages/forget_pwd/forget_pwd" hover-class="none">
<view
class="border-l border-solid border-0 border-light pl-3 text-muted leading-4 ml-3"
>
忘记密码?
</view>
</navigator>
</view>
</template>
<!-- 验证码登录 -->
<template
v-if="
formData.scene == LoginWayEnum.MOBILE &&
includeLoginWay(LoginWayEnum.MOBILE)
"
>
<view
class="px-[18rpx] border border-solid border-lightc border-light rounded-[10rpx] h-[100rpx] items-center flex"
>
<u-input
class="flex-1"
v-model="formData.account"
:border="false"
placeholder="请输入手机号码"
/>
</view>
<view
class="px-[18rpx] border border-solid border-lightc border-light rounded-[10rpx] h-[100rpx] items-center flex mt-[40rpx]"
>
<u-input
class="flex-1"
v-model="formData.code"
placeholder="请输入验证码"
:border="false"
/>
<view
class="border-l border-solid border-0 border-light pl-3 leading-4 ml-3 w-[180rpx]"
@click="sendSms"
>
<u-verification-code
ref="uCodeRef"
:seconds="60"
@change="codeChange"
change-text="x秒"
/>
<text :class="formData.account ? 'text-primary' : 'text-muted'">
{{ codeTips }}
</text>
</view>
</view>
</template>
</block>
<view class="mt-[40rpx]" v-if="isOpenAgreement">
<u-checkbox v-model="isCheckAgreement" shape="circle">
<view class="text-xs flex">
已阅读并同意
<view @click.stop>
<navigator
class="text-primary"
hover-class="none"
url="/pages/agreement/agreement?type=service"
>
《服务协议》
</navigator>
</view>
<view @click.stop>
<navigator
class="text-primary"
hover-class="none"
url="/pages/agreement/agreement?type=privacy"
>
《隐私协议》
</navigator>
</view>
</view>
</u-checkbox>
</view>
<block v-if="phoneLogin">
<view class="mt-[60rpx]">
<u-button
type="primary"
@click="handleLogin(formData.scene)"
:customStyle="{
height: '100rpx',
opacity: DisableStyle ? '1' : '0.5'
}"
hover-class="none"
>
登录
</u-button>
</view>
<view class="flex justify-between mt-[40rpx]">
<view
>已有账号,使用
<span
class="text-primary"
@click="changeLoginWay(LoginWayEnum.ACCOUNT)"
v-if="
formData.scene == LoginWayEnum.MOBILE &&
includeLoginWay(LoginWayEnum.ACCOUNT)
"
>密码登录</span
>
<span
class="text-primary"
@click="changeLoginWay(LoginWayEnum.MOBILE)"
v-if="
formData.scene == LoginWayEnum.ACCOUNT &&
includeLoginWay(LoginWayEnum.MOBILE)
"
>验证码登录</span
>
</view>
<navigator url="/pages/register/register" hover-class="none"
>注册账号</navigator
>
</view>
</block>
</view>
<!-- 协议弹框 -->
<u-modal
v-model="showModel"
show-cancel-button
:show-title="false"
confirm-color="var(--color-primary)"
@confirm=";(isCheckAgreement = true), (showModel = false)"
@cancel="showModel = false"
>
<view class="text-center px-[70rpx] py-[60rpx]">
<view> 请先阅读并同意 </view>
<view class="flex justify-center">
<navigator data-theme="" url="/pages/agreement/agreement?type=service">
<view class="text-primary">《服务协议》</view>
</navigator>
<navigator url="/pages/agreement/agreement?type=privacy">
<view class="text-primary">《隐私协议》</view>
</navigator>
</view>
</view>
</u-modal>
<!-- #ifdef MP-WEIXIN -->
<mplogin-popup
v-model:show="showLoginPopup"
:logo="websiteConfig.shop_logo"
:title="websiteConfig.shop_name"
@update="handleUpdateUser"
/>
<!-- #endif -->
</view>
</template>
<script setup lang="ts">
import { login, mnpLogin, updateUser, OALogin } from '@/api/account'
import { smsSend } from '@/api/app'
import { SMSEnum } from '@/enums/appEnums'
import { BACK_URL } from '@/enums/constantEnums'
import { useLockFn } from '@/hooks/useLockFn'
import { useAppStore } from '@/stores/app'
import { useUserStore } from '@/stores/user'
import { useRouter, useRoute } from 'uniapp-router-next'
import cache from '@/utils/cache'
import { isWeixinClient } from '@/utils/client'
// #ifdef H5
import wechatOa, { UrlScene } from '@/utils/wechat'
// #endif
import { onLoad, onShow } from '@dcloudio/uni-app'
import { computed, reactive, ref, shallowRef, watch } from 'vue'
enum LoginWayEnum {
ACCOUNT = 1,
MOBILE = 2
}
const isWeixin = ref(true)
// #ifdef H5
isWeixin.value = isWeixinClient()
// #endif
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const appStore = useAppStore()
const showModel = ref(false)
const uCodeRef = shallowRef()
const codeTips = ref('')
const showLoginPopup = ref(false)
const isCheckAgreement = ref(false)
const formData = reactive({
scene: 1,
account: '',
password: '',
code: ''
})
const phoneLogin = ref(false)
const loginData = ref()
const codeChange = (text: string) => {
codeTips.value = text
}
const websiteConfig = computed(() => appStore.getWebsiteConfig)
const sendSms = async () => {
if (!formData.account) return
if (uCodeRef.value?.canGetCode) {
await smsSend({
scene: SMSEnum.LOGIN,
mobile: formData.account
})
uni.$u.toast('发送成功')
uCodeRef.value?.start()
}
}
const changeLoginWay = (way: LoginWayEnum) => {
formData.scene = way
}
const includeLoginWay = (way: LoginWayEnum) => {
return appStore.getLoginConfig.login_way?.includes(String(way))
}
const inWxAuth = computed(() => {
return appStore.getLoginConfig.wechat_auth
})
const isOpenAgreement = computed(() => appStore.getLoginConfig.login_agreement == 1)
const isOpenOtherAuth = computed(() => appStore.getLoginConfig.third_auth == 1)
const isForceBindMobile = computed(() => appStore.getLoginConfig.coerce_mobile == 1)
const loginFun = async () => {
if (!isCheckAgreement.value && isOpenAgreement.value) return (showModel.value = true)
if (formData.scene == LoginWayEnum.ACCOUNT) {
if (!formData.account) return uni.$u.toast('请输入账号/手机号码')
if (!formData.password) return uni.$u.toast('请输入密码')
}
if (formData.scene == LoginWayEnum.MOBILE) {
if (!formData.account) return uni.$u.toast('请输入手机号码')
if (!formData.code) return uni.$u.toast('请输入验证码')
}
uni.showLoading({
title: '请稍后...'
})
try {
const data = await login(formData)
loginHandle(data)
} catch (error: any) {
uni.hideLoading()
uni.$u.toast(error)
}
}
const loginHandle = async (data: any) => {
const { token, mobile } = data
if (!mobile && isForceBindMobile.value) {
userStore.temToken = token
router.navigateTo('/pages/bind_mobile/bind_mobile')
uni.hideLoading()
return
}
userStore.login(data.token)
await userStore.getUser()
uni.$u.toast('登录成功')
uni.hideLoading()
const pages = getCurrentPages()
if (pages.length > 1) {
const prevPage = pages[pages.length - 2]
await router.navigateBack()
// @ts-ignore
const { onLoad, options } = prevPage
// 刷新上一个页面
onLoad && onLoad(options)
} else if (cache.get(BACK_URL)) {
try {
router.redirectTo(cache.get(BACK_URL))
} catch (error) {
router.switchTab(cache.get(BACK_URL))
}
} else {
router.reLaunch('/pages/index/index')
}
cache.remove(BACK_URL)
}
const { lockFn: handleLogin } = useLockFn(loginFun)
const oaLogin = async (options: any = { getUrl: true }) => {
const { code, getUrl } = options
if (getUrl) {
await wechatOa.getUrl(UrlScene.LOGIN)
} else {
const data = await OALogin({
code
})
return data
}
return Promise.reject()
}
const wxLogin = async () => {
if (!isCheckAgreement.value && isOpenAgreement.value) {
showModel.value = true
console.log(showModel.value)
return
}
// #ifdef MP-WEIXIN
uni.showLoading({
title: '请稍后...'
})
try {
const { code }: any = await uni.login({
provider: 'weixin'
})
const data = await mnpLogin({
code: code
})
loginData.value = data
if (data.is_new_user) {
uni.hideLoading()
userStore.temToken = data.token
showLoginPopup.value = true
return
}
loginHandle(data)
} catch (error: any) {
uni.hideLoading()
uni.$u.toast(error)
}
// #endif
// #ifdef H5
if (isWeixin.value) {
oaLogin()
}
// #endif
}
const handleUpdateUser = async (value: any) => {
await updateUser(value, { token: userStore.temToken })
showLoginPopup.value = false
loginHandle(loginData.value)
}
watch(
() => appStore.getLoginConfig,
(value) => {
if (value.login_way) {
formData.scene = value.login_way[0]
}
},
{
immediate: true
}
)
const DisableStyle = computed(() => {
if (formData.scene == 1 && formData.account && formData.password) {
return true
} else if (formData.scene == 2 && formData.account && formData.code) {
return true
} else {
return false
}
})
const removeWxQuery = () => {
const options = route.query
if (options.code && options.state) {
delete options.code
delete options.state
router.redirectTo({ path: route.path, query: options })
}
}
onLoad(async () => {
//#ifdef H5
const options = wechatOa.getAuthData()
try {
if (options.code && options.scene === UrlScene.LOGIN) {
uni.showLoading({
title: '请稍后...'
})
const data = await oaLogin(options)
if (data) {
loginData.value = data
loginHandle(loginData.value)
}
}
} catch (error) {
removeWxQuery()
} finally {
uni.hideLoading()
//清除保存的授权数据
wechatOa.setAuthData()
}
//#endif
})
</script>
<style lang="scss">
page {
height: 100%;
}
</style>
@@ -0,0 +1,65 @@
<template>
<z-paging
auto-show-back-to-top
:auto="i == index"
ref="paging"
v-model="dataList"
:data-key="i"
@query="queryList"
:fixed="false"
height="100%"
>
<block v-for="(newsItem, newsIndex) in dataList" :key="newsIndex">
<news-card :item="newsItem" :newsId="newsItem.id"></news-card>
</block>
</z-paging>
</template>
<script lang="ts" setup>
import { ref, watch, nextTick, shallowRef } from 'vue'
import { getArticleList } from '@/api/news'
const props = withDefaults(
defineProps<{
cid: number
i: number
index: number
}>(),
{
cid: 0
}
)
const paging = shallowRef<any>(null)
const dataList = ref([])
const isFirst = ref<boolean>(true)
watch(
() => props.index,
async () => {
await nextTick()
if (props.i == props.index && isFirst.value) {
isFirst.value = false
paging.value?.reload()
}
},
{ immediate: true }
)
const queryList = async (page_no, page_size) => {
try {
const { lists } = await getArticleList({
cid: props.cid,
page_no,
page_size
})
paging.value.complete(lists)
} catch (e) {
console.log('报错=>', e)
//TODO handle the exception
paging.value.complete(false)
}
}
</script>
<style scoped></style>
+85
View File
@@ -0,0 +1,85 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<!-- #ifndef H5 -->
<u-sticky
h5-nav-height="0"
bg-color="transparent"
>
<u-navbar
:is-back="false"
:is-fixed="false"
title="资讯"
:border-bottom="false"
:title-bold="true"
:title-color="$theme.navColor"
:background="{ background: $theme.navBgColor }"
>
</u-navbar>
</u-sticky>
<!-- #endif -->
<view class="news">
<!-- 搜索 -->
<navigator class="news-search px-[24rpx] py-[14rpx] bg-white" url="/pages/search/search">
<u-search placeholder="请输入关键词搜索" disabled :show-action="false"></u-search>
</navigator>
<!-- 内容 -->
<tabs
:current="current"
@change="handleChange"
height="80"
bar-width="60"
:barStyle="{ bottom: '0' }"
>
<tab v-for="(item, i) in tabList" :key="i" :name="item.name">
<view class="news-list pt-[20rpx]">
<news-list :cid="item.id" :i="i" :index="current"></news-list>
</view>
</tab>
</tabs>
<tabbar />
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from 'vue'
import { onLoad, onShow, onReady } from '@dcloudio/uni-app'
import NewsList from './component/news-list.vue'
import { getArticleCate } from '@/api/news'
const tabList = ref<any>([])
const current = ref<number>(0)
const handleChange = (index: number) => {
console.log(index)
current.value = Number(index)
}
const getData = async () => {
const data = await getArticleCate()
tabList.value = [{ name: '全部', id: '' }].concat(data)
}
onLoad((options) => {
getData()
})
</script>
<style lang="scss">
.news {
&-search {
margin-bottom: 2rpx;
}
&-list {
height: calc(100vh - 272rpx - env(safe-area-inset-bottom));
}
}
</style>
@@ -0,0 +1,108 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view class="news-detail bg-white">
<!-- 标题信心 -->
<view class="news-detail-header py-[20rpx] px-[30rpx]">
<view class="text-3xl font-medium">{{ newsData.title }}</view>
<view class="flex mt-[20rpx] text-xs">
<view class="mr-[40rpx]" v-if="newsData.author">作者: {{ newsData.author }}</view>
<view class="text-muted mr-[40rpx] flex-1">{{ newsData.create_time }}</view>
<view class="flex items-center text-muted flex-none">
<image
src="/static/images/icon/icon_visit.png"
class="w-[30rpx] h-[30rpx]"
></image>
<view class="ml-[10rpx]">{{ newsData.click }}</view>
</view>
</view>
</view>
<!-- 咨询内容 -->
<view class="news-detail-section bg-white p-[24rpx]">
<!-- 摘要 -->
<view class="summary p-[20rpx] text-base" v-if="newsData.abstract">
<text class="font-medium">摘要: </text> {{ newsData.abstract }}
</view>
<!-- 内容 -->
<view class="mt-[20rpx]">
<u-parse :html="newsData.content"></u-parse>
</view>
</view>
<view class="panel-btn flex items-center px-[34rpx]" @click="handleAddCollect(newsData.id)">
<u-icon
:name="newsData.collect ? 'star-fill' : 'star'"
size="40"
:color="newsData.collect ? '#F7BA47' : '#333'"
></u-icon>
<text class="ml-[10rpx]">收藏</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getArticleDetail, addCollect, cancelCollect } from '@/api/news'
const newsData = ref<any>({})
let newsId = ''
const getData = async (id) => {
newsData.value = await getArticleDetail({ id })
}
const handleAddCollect = async (id: number) => {
try {
if (newsData.value.collect) {
await cancelCollect({ id })
uni.$u.toast('已取消收藏')
} else {
await addCollect({ id })
uni.$u.toast('收藏成功')
}
getData(newsId)
} catch (e) {
//TODO handle the exception
}
}
onLoad((options: any) => {
newsId = options.id
getData(newsId)
})
</script>
<style lang="scss">
.news-detail {
height: 100%;
&-header {
border-bottom: 2rpx solid #f8f8f8;
}
&-section {
.summary {
border-radius: 12rpx;
background-color: #f7f7f7;
}
}
.panel-btn {
position: fixed;
right: 30rpx;
height: 80rpx;
bottom: 80rpx;
border-radius: 40rpx;
background: rgba(255, 255, 255, 0.95);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.16);
}
}
</style>
@@ -0,0 +1,168 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<!-- 页面状态 -->
<page-status :status="status">
<template #error>
<u-empty text="订单不存在" mode="order"></u-empty>
</template>
<template #default>
<view class="payment-result p-[20rpx]">
<view class="result bg-white p-[20rpx] rounded-md">
<view class="flex flex-col items-center my-[40rpx]">
<!-- 支付状态图片 -->
<u-image
class="status-image"
:src="paymentStatus['image']"
width="100"
height="100"
shape="circle"
/>
<!-- 支付状态文字 -->
<text class="text-2xl font-medium mt-[20rpx]">{{
paymentStatus['text']
}}
</text>
<view class="text-3xl font-medium mt-[20rpx]">
¥ {{ orderInfo.order.order_amount }}
</view>
</view>
<!-- 支付信息 -->
<view class="result-info">
<view class="result-info__item">
<text>订单编号</text>
<text>{{ orderInfo.order.order_sn }}</text>
</view>
<view class="result-info__item">
<text>付款时间</text>
<text>{{ orderInfo.order.pay_time }}</text>
</view>
<view class="result-info__item">
<text>支付方式</text>
<template v-if="orderInfo.pay_status">
<text>{{ orderInfo.order.pay_way || '-' }}</text>
</template>
<template v-else>
<text>未支付</text>
</template>
</view>
</view>
</view>
<view class="mt-[40rpx]">
<view class="mb-[20rpx]">
<u-button
v-if="pageOptions.from == 'recharge'"
type="primary"
shape="circle"
hover-class="none"
@click="goOrder"
>
继续充值
</u-button>
</view>
<view class="mb-[20rpx]">
<u-button
type="primary"
plain
shape="circle"
hover-class="none"
@click="goHome"
>
返回首页
</u-button>
</view>
</view>
</view>
</template>
</page-status>
</template>
<script lang="ts" setup>
import {getPayResult} from '@/api/pay'
import {PageStatusEnum} from '@/enums/appEnums'
import {onLoad} from '@dcloudio/uni-app'
import {computed, reactive, ref} from 'vue'
import {useRouter} from "uniapp-router-next";
const router = useRouter()
const mapStatus = {
succeed: {
text: '支付成功',
image: '/static/images/payment/icon_succeed.png'
},
waiting: {
text: '等待支付',
image: '/static/images/payment/icon_waiting.png'
}
}
const status = ref(PageStatusEnum['LOADING'])
const pageOptions = ref({
id: '',
from: ''
})
const orderInfo = reactive<any>({
order: {}
})
const paymentStatus = computed(() => {
const status = !!orderInfo.pay_status
return mapStatus[status ? 'succeed' : 'waiting']
})
const initPageData = () => {
return new Promise((resolve, reject) => {
getPayResult({
order_id: pageOptions.value.id,
from: pageOptions.value.from
})
.then((data) => {
Object.assign(orderInfo, data)
resolve(data)
})
.catch((err) => {
reject(err)
})
})
}
const goHome = () => {
router.reLaunch('/pages/index/index')
}
const goOrder = () => {
switch (pageOptions.value.from) {
case 'recharge':
router.navigateBack()
break
}
}
onLoad(async (options: any) => {
try {
if (!options.id) throw new Error('订单不存在')
pageOptions.value = options
await initPageData()
status.value = PageStatusEnum['NORMAL']
} catch (err) {
console.log(err)
status.value = PageStatusEnum['ERROR']
}
})
</script>
<style lang="scss" scoped>
.result-info {
.result-info__item {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
}
</style>
+150
View File
@@ -0,0 +1,150 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view
class="register bg-white min-h-full flex flex-col items-center px-[40rpx] pt-[40rpx] box-border"
>
<view class="w-full">
<view class="text-2xl font-medium mb-[60rpx]">注册新账号</view>
<view
class="px-[18rpx] border border-solid border-lightc border-light rounded-[10rpx] h-[100rpx] items-center flex"
>
<u-input
class="flex-1"
v-model="formData.account"
:border="false"
placeholder="请输入账号"
/>
</view>
<view
class="px-[18rpx] border border-solid border-lightc border-light rounded-[10rpx] h-[100rpx] items-center flex mt-[40rpx]"
>
<u-input
class="flex-1"
type="password"
v-model="formData.password"
placeholder="请输入密码"
:border="false"
/>
</view>
<view
class="px-[18rpx] border border-solid border-lightc border-light rounded-[10rpx] h-[100rpx] items-center flex mt-[40rpx]"
>
<u-input
class="flex-1"
type="password"
v-model="formData.password_confirm"
placeholder="请再次输入密码"
:border="false"
/>
</view>
<view class="mt-[40rpx]" v-if="isOpenAgreement">
<u-checkbox v-model="isCheckAgreement" shape="circle">
<view class="text-xs flex">
已阅读并同意
<view @click.stop>
<router-navigate
class="text-primary"
hover-class="none"
to="/pages/agreement/agreement?type=service"
>
服务协议
</router-navigate>
</view>
<view @click.stop>
<router-navigate
class="text-primary"
hover-class="none"
to="/pages/agreement/agreement?type=privacy"
>
隐私协议
</router-navigate>
</view>
</view>
</u-checkbox>
</view>
<view class="mt-[60rpx]">
<u-button
type="primary"
hover-class="none"
@click="accountRegister"
:customStyle="{
height: '100rpx',
opacity:
formData.account && formData.password && formData.password_confirm
? '1'
: '0.5'
}"
>
注册
</u-button>
</view>
</view>
</view>
<!-- 协议弹框 -->
<u-modal
v-model="showModel"
show-cancel-button
:show-title="false"
@confirm=";(isCheckAgreement = true), (showModel = false)"
@cancel="showModel = false"
confirm-color="var(--color-primary)"
>
<view class="text-center px-[70rpx] py-[60rpx]">
<view> 请先阅读并同意</view>
<view class="flex justify-center">
<router-navigate data-theme="" to="/pages/agreement/agreement?type=service">
<view class="text-primary">服务协议</view>
</router-navigate>
<router-navigate to="/pages/agreement/agreement?type=privacy">
<view class="text-primary">隐私协议</view>
</router-navigate>
</view>
</view>
</u-modal>
</template>
<script setup lang="ts">
import {register} from '@/api/account'
import {useAppStore} from '@/stores/app'
import {computed, reactive, ref} from 'vue'
const isCheckAgreement = ref(false)
const appStore = useAppStore()
const isOpenAgreement = computed(() => appStore.getLoginConfig.login_agreement == 1)
const formData = reactive({
account: '',
password: '',
password_confirm: ''
})
const showModel = ref(false)
const accountRegister = async () => {
if (!formData.account) return uni.$u.toast('请输入账号')
if (!formData.password) return uni.$u.toast('请输入密码')
if (!formData.password_confirm) return uni.$u.toast('请输入确认密码')
if (!isCheckAgreement.value && isOpenAgreement.value) return (showModel.value = true)
if (formData.password != formData.password_confirm) return uni.$u.toast('两次输入的密码不一致')
await register(formData)
// uni.navigateBack()
setTimeout(function () {
uni.navigateBack()
}, 1000)
}
</script>
<style lang="scss">
page {
height: 100%;
}
</style>
@@ -0,0 +1,87 @@
<template>
<view class="suggest bg-white">
<!-- 热门搜索 -->
<view class="hot" v-if="hot_search.status == 1 && searchData.length">
<view class="font-medium pl-[24rpx] pt-[26rpx] pb-[6rpx] text-lg">热门搜索</view>
<view class="w-full px-[24rpx]">
<block v-for="(hotItem, index) in searchData" :key="index">
<view
class="keyword truncate max-w-full"
@click="handleHistoreSearch(hotItem.name)"
>
{{ hotItem.name }}
</view>
</block>
</view>
</view>
<view
class="mx-[24rpx] my-[40rpx] border-b border-solid border-0 border-light"
v-if="hot_search.status == 1 && searchData.length && his_search.length"
></view>
<!-- 历史搜索 -->
<view class="history" v-if="his_search.length">
<view class="flex justify-between px-[24rpx] pb-[6rpx] pt-[26rpx]">
<view class="text-lg font-medium">历史搜索</view>
<view class="text-xs text-muted" @click="() => emit('clear')">清空</view>
</view>
<view class="w-full px-[24rpx]">
<block v-for="(hisItem, index) in his_search" :key="index">
<view class="keyword truncate" @click="handleHistoreSearch(hisItem)">{{
hisItem
}}</view>
</block>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
const emit = defineEmits<{
(event: 'search', value: string): void
(event: 'clear', value: void): void
}>()
const props = withDefaults(
defineProps<{
hot_search?: {
data: any[]
status: number
}
his_search?: string[]
}>(),
{
hot_search: () => ({
data: [],
status: 0
}),
his_search: () => []
}
)
const searchData = computed(() => {
return props.hot_search.data.filter((item) => item.name)
})
const handleHistoreSearch = (text: string) => {
emit('search', text)
}
</script>
<style lang="scss" scoped>
.suggest {
height: 100%;
.keyword {
display: inline-block;
margin: 24rpx 16rpx 0 0;
padding: 8rpx 24rpx;
border-radius: 26rpx;
background-color: #f4f4f4;
}
}
</style>
+142
View File
@@ -0,0 +1,142 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view class="search">
<!-- 搜索框 -->
<view class="px-[24rpx] py-[14rpx] bg-white">
<u-search
v-model="keyword"
placeholder="请输入关键词搜索"
height="72"
@search="handleSearch"
@custom="handleSearch"
@clear="search.searching = false"
></u-search>
</view>
<!-- 搜索 -->
<view class="search-content">
<!-- -->
<suggest
v-show="!search.searching"
@search="handleSearch"
@clear="handleClear"
:hot_search="search.hot_search"
:his_search="search.his_search"
></suggest>
<!-- -->
<view class="search-content-s pt-[20rpx]" v-show="search.searching">
<z-paging
ref="paging"
v-model="search.result"
@query="queryList"
:fixed="false"
height="100%"
>
<block v-for="item in search.result" :key="item.id">
<news-card :item="item" :newsId="item.id"></news-card>
</block>
</z-paging>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, shallowRef } from 'vue'
import Suggest from './component/suggest.vue'
import { HISTORY } from '@/enums/constantEnums'
import { getHotSearch } from '@/api/shop'
import cache from '@/utils/cache'
import { getArticleList } from '@/api/news'
interface Search {
hot_search: {
data: any[]
status: number
}
his_search: string[]
result: any
searching: boolean
}
const search = reactive<Search>({
hot_search: {
data: [],
status: 1
},
his_search: [],
result: [],
searching: false
})
const keyword = ref<string>('')
const paging = shallowRef()
const handleSearch = (value: string) => {
keyword.value = value
if (keyword.value) {
if (!search.his_search.includes(keyword.value)) {
search.his_search.unshift(keyword.value)
cache.set(HISTORY, search.his_search)
}
}
paging.value.reload()
search.searching = true
}
const getHotSearchFunc = async () => {
try {
search.hot_search = await getHotSearch()
} catch (e) {
//TODO handle the exception
console.log('获取热门搜索失败=>', e)
}
}
const handleClear = async (): Promise<void> => {
const resModel: any = await uni.showModal({
title: '温馨提示',
content: '是否清空历史记录?'
})
if (resModel.confirm) {
cache.set(HISTORY, '')
search.his_search = []
}
}
const queryList = async (page_no: number, page_size: number) => {
try {
const { lists } = await getArticleList({
keyword: keyword.value,
page_no,
page_size
})
paging.value.complete(lists)
} catch (e) {
console.log('报错=>', e)
//TODO handle the exception
paging.value.complete(false)
}
}
getHotSearchFunc()
search.his_search = cache.get(HISTORY) || []
</script>
<style lang="scss" scoped>
.search {
&-content {
height: calc(100vh - 46px - env(safe-area-inset-bottom));
&-s {
height: 100%;
}
}
}
</style>
+61
View File
@@ -0,0 +1,61 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view class="user">
<view v-for="(item, index) in state.pages" :key="index">
<template v-if="item.name == 'user-info'">
<w-user-info
:pageMeta="state.meta"
:content="item.content"
:styles="item.styles"
:user="userInfo"
:is-login="isLogin"
/>
</template>
<template v-if="item.name == 'my-service'">
<w-my-service :content="item.content" :styles="item.styles" />
</template>
<template v-if="item.name == 'user-banner'">
<w-user-banner :content="item.content" :styles="item.styles" />
</template>
</view>
<tabbar />
</view>
</template>
<script setup lang="ts">
import { getDecorate } from '@/api/shop'
import { useUserStore } from '@/stores/user'
import { onShow } from '@dcloudio/uni-app'
import { storeToRefs } from 'pinia'
import { reactive } from 'vue'
const state = reactive<{
meta: any[]
pages: any[]
}>({
meta: [],
pages: []
})
const getData = async () => {
const data = await getDecorate({ id: 2 })
state.meta = JSON.parse(data.meta)
state.pages = JSON.parse(data.data)
uni.setNavigationBarTitle({
title: state.meta[0].content.title
})
}
const userStore = useUserStore()
const { userInfo, isLogin } = storeToRefs(userStore)
onShow(() => {
userStore.getUser()
})
getData()
</script>
<style></style>
+365
View File
@@ -0,0 +1,365 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<!-- Main Start -->
<!-- 头部修改头像 -->
<view class="header bg-white pt-[30rpx]">
<view class="flex justify-center pb-5">
<avatar-upload
:modelValue="userInfo?.avatar"
file-key="url"
:round="true"
@update:modelValue="handleAvatarChange"
>
</avatar-upload>
</view>
</view>
<!-- 用户ID -->
<view
class="item text-nr flex justify-between"
@click=";(showUserName = true), (newUsername = userInfo?.username)"
>
<view class="label">账号</view>
<view class="content">{{ userInfo?.account }}</view>
<u-icon name="arrow-right" size="22" color="#666"></u-icon>
</view>
<!-- 昵称 -->
<view
class="item text-nr flex justify-between"
@click=";(showNickName = true), (newNickname = userInfo?.nickname)"
>
<view class="label">昵称</view>
<view class="content">{{ userInfo?.nickname }}</view>
<u-icon name="arrow-right" size="22" color="#666"></u-icon>
</view>
<!-- 性别 -->
<view class="item text-nr flex justify-between" @click="changeSex">
<view class="label">性别</view>
<view class="content">{{ userInfo?.sex }}</view>
<u-icon name="arrow-right" size="22" color="#666"></u-icon>
</view>
<!-- 手机号 -->
<view class="item text-nr flex justify-between">
<view class="label">手机号</view>
<view class="content">{{
userInfo?.mobile == '' ? '未绑定手机号' : userInfo?.mobile
}}</view>
<!-- #ifdef MP-WEIXIN -->
<u-button
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
type="primary"
shape="circle"
size="mini"
:plain="true"
>
{{ userInfo?.mobile == '' ? '绑定手机号' : '更换手机号' }}
</u-button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<u-button
@click="showMobilePop = true"
size="mini"
type="primary"
shape="circle"
:plain="true"
>
{{ userInfo?.mobile == '' ? '绑定手机号' : '更换手机号' }}
</u-button>
<!-- #endif -->
</view>
<!-- 注册时间 -->
<view class="item text-nr flex justify-between">
<view class="label">注册时间</view>
<view class="content">{{ userInfo?.create_time }}</view>
</view>
<!-- 昵称修改组件 -->
<u-popup
v-model="showNickName"
:closeable="true"
mode="center"
:maskCloseAble="false"
border-radius="20"
>
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
<form @submit="changeNameConfirm">
<view class="mb-[70rpx] text-xl text-center">修改昵称</view>
<u-form-item borderBottom>
<input
class="nr h-[60rpx] w-full"
:value="userInfo.nickname"
name="nickname"
type="nickname"
placeholder="请输入昵称"
/>
</u-form-item>
<view class="mt-[80rpx]">
<button
class="bg-primary text-white w-full h-[80rpx] !text-lg !leading-[80rpx] rounded-full"
form-type="submit"
size="mini"
hover-class="none"
>
确定
</button>
</view>
</form>
</view>
</u-popup>
<!-- 账号修改组件 -->
<u-popup v-model="showUserName" :closeable="true" mode="center" border-radius="20">
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
<view class="mb-[70rpx] text-xl text-center">修改账号</view>
<u-form-item borderBottom>
<u-input
class="flex-1"
v-model="newUsername"
placeholder="请输入账号"
:border="false"
/>
</u-form-item>
<view class="mt-[80rpx]">
<u-button @click="changeUserNameConfirm" type="primary" shape="circle">
确定
</u-button>
</view>
</view>
</u-popup>
<!-- 性别修改组件 -->
<u-picker
mode="selector"
v-model="showPicker"
confirm-color="#4173FF"
:default-selector="[0]"
:range="sexList"
@confirm="changeSexConfirm"
/>
<!-- 账号修改组件 -->
<u-popup v-model="showMobilePop" :closeable="true" mode="center" border-radius="20">
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
<view class="mb-[70rpx] text-xl text-center">{{ userInfo?.mobile == '' ? '绑定手机号' : '更换手机号' }}</view>
<u-form-item borderBottom>
<u-input
class="flex-1"
v-model="newMobile"
placeholder="请输入新的手机号码"
:border="false"
/>
</u-form-item>
<u-form-item borderBottom>
<u-input
class="flex-1"
v-model="mobileCode"
placeholder="请输入验证码"
:border="false"
/>
<view
class="border-l border-solid border-0 border-light pl-3 text-muted leading-4 ml-3 w-[180rpx]"
@click="sendSms"
>
<u-verification-code
ref="uCodeRef"
:seconds="60"
@change="codeChange"
change-text="x秒"
/>
{{ codeTips }}
</view>
</u-form-item>
<view class="mt-[80rpx]">
<u-button @click="changeCodeMobile" type="primary" shape="circle"> 确定 </u-button>
</view>
</view>
</u-popup>
</template>
<script lang="ts" setup>
import { ref, shallowRef } from 'vue'
import { onShow, onUnload } from '@dcloudio/uni-app'
import { getUserInfo, userEdit, userBindMobile, userMnpMobile } from '@/api/user'
import { smsSend } from '@/api/app'
import { FieldType, SMSEnum } from '@/enums/appEnums'
// 用户信息
const userInfo = ref<any>({})
// 用户信息的枚举
const fieldType = ref(FieldType.NONE)
//选择性别数据
const sexList = ref<Array<string> | null>(['男', '女'])
//显示昵称弹窗
const showNickName = ref<boolean | null>(false)
//显示账户弹窗
const showUserName = ref<boolean | null>(false)
//显示性别选择弹窗
const showPicker = ref<boolean | null>(false)
// 显示手机号验证码调整弹窗 非小程序才需要
const showMobilePop = ref<boolean | null>(false)
//新昵称
const newNickname = ref<string>('')
//新账号
const newUsername = ref<string>('')
//新的手机号码
const newMobile = ref<string>('')
//修改手机验证码
const mobileCode = ref<string>('')
const codeTips = ref('')
const uCodeRef = shallowRef()
// 获取用户信息
const getUser = async (): Promise<void> => {
userInfo.value = await getUserInfo()
}
// 获取验证码显示字段
const codeChange = (text: string) => {
codeTips.value = text
}
// 发送验证码
const sendSms = async () => {
if (!newMobile.value) return uni.$u.toast('请输入新的手机号码')
if (uCodeRef.value?.canGetCode) {
await smsSend({
scene: userInfo.value.mobile ? SMSEnum.CHANGE_MOBILE : SMSEnum.BIND_MOBILE,
mobile: newMobile.value
})
uni.$u.toast('发送成功')
uCodeRef.value?.start()
}
}
const handleAvatarChange = (value) => {
fieldType.value = FieldType.AVATAR
setUserInfoFun(value)
}
// 验证码修改手机号-非微信小程序
const changeCodeMobile = async () => {
await userBindMobile({
type: userInfo.value.mobile ? 'change' : 'bind',
mobile: newMobile.value,
code: mobileCode.value
})
uni.$u.toast('操作成功')
showMobilePop.value = false
getUser()
}
// 修改用户信息
const setUserInfoFun = async (value: string): Promise<void> => {
await userEdit({
field: fieldType.value,
value: value
})
uni.$u.toast('操作成功')
getUser()
}
// 显示修改用户性别弹窗
const changeSex = () => {
showPicker.value = true
fieldType.value = FieldType.SEX
}
// 修改用户性别
const changeSexConfirm = (value) => {
setUserInfoFun(value[0] + 1)
showPicker.value = false
}
// 修改用户账号
const changeUserNameConfirm = () => {
if (newUsername.value == '') return uni.$u.toast('账号不能为空')
if (newUsername.value.length > 10) return uni.$u.toast('账号长度不得超过十位数')
fieldType.value = FieldType.USERNAME
setUserInfoFun(newUsername.value)
showUserName.value = false
}
// 修改用户昵称
const changeNameConfirm = async (e: any) => {
newNickname.value = e.detail.value.nickname
if (newNickname.value == '') return uni.$u.toast('昵称不能为空')
if (newNickname.value.length > 10) return uni.$u.toast('昵称长度不得超过十位数')
fieldType.value = FieldType.NICKNAME
await setUserInfoFun(newNickname.value)
showNickName.value = false
}
// 微信小程序 绑定||修改用户手机号
const getPhoneNumber = async (e): Promise<void> => {
const { encryptedData, iv, code } = e.detail
const data = {
code,
encrypted_data: encryptedData,
iv
}
if (encryptedData) {
await userMnpMobile({
...data
})
uni.$u.toast('操作成功')
getUser()
}
}
const goPage = (url: string) => {
uni.navigateTo({
url: url
})
}
onShow(async () => {
getUser()
})
onUnload(() => {})
</script>
<style lang="scss">
.header {
width: 100%;
image {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
}
}
.item {
margin-top: 2rpx;
padding: 30rpx;
background-color: #ffffff;
.label {
width: 150rpx;
}
.content {
flex: 1;
width: 80%;
}
}
</style>
+248
View File
@@ -0,0 +1,248 @@
<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view class="user-set">
<navigator :url="`/pages/user_data/user_data`">
<view class="item flex bg-white mt-[20rpx]">
<u-avatar :src="userInfo.avatar" shape="square" :size="100"></u-avatar>
<view class="ml-[20rpx] flex flex-1 justify-between items-center">
<view>
<view class="mb-[15rpx] text-xl font-medium">{{ userInfo.nickname }}</view>
<view class="text-content text-xs">账号{{ userInfo.account }}</view>
</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</view>
</navigator>
<view
class="item bg-white mt-[20rpx] btn-border flex flex-1 justify-between"
@click="handlePwd"
>
<view class="">登录密码</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
<!-- #ifdef H5 || MP-WEIXIN -->
<view
v-if="isWeixin"
class="item bg-white flex flex-1 justify-between"
@click="bindWechatLock"
>
<view class="">绑定微信</view>
<view class="flex justify-between">
<view class="text-muted mr-[20rpx]">
{{ userInfo.is_auth ? '已绑定' : '未绑定' }}
</view>
<u-icon v-if="userInfo.is_auth == 0" name="arrow-right" color="#666"></u-icon>
</view>
</view>
<!-- #endif -->
<navigator :url="`/pages/agreement/agreement?type=${AgreementEnum.PRIVACY}`">
<view class="item bg-white mt-[20rpx] btn-border flex flex-1 justify-between">
<view class="">隐私政策</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</navigator>
<navigator :url="`/pages/agreement/agreement?type=${AgreementEnum.SERVICE}`">
<view class="item bg-white btn-border flex flex-1 justify-between">
<view class="">服务协议</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</navigator>
<navigator url="/pages/as_us/as_us">
<view class="item bg-white flex flex-1 justify-between">
<view class="">关于我们</view>
<view class="flex justify-between">
<view class="text-muted mr-[20rpx]">
{{ appStore.config.version }}
</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</view>
</navigator>
<view class="mt-[60rpx] mx-[26rpx]">
<u-button type="primary" shape="circle" @click="showLogout = true"> 退出登录</u-button>
</view>
<u-action-sheet
:list="list"
v-model="show"
@click="handleClick"
:safe-area-inset-bottom="true"
></u-action-sheet>
<u-popup
class="pay-popup"
v-model="showLogout"
round
mode="center"
borderRadius="10"
:maskCloseAble="false"
>
<view class="content bg-white w-[560rpx] p-[40rpx]">
<view class="text-2xl font-medium text-center"> 温馨提示</view>
<view class="pt-[30rpx] pb-[40rpx]">
<view> 是否清除当前登录信息退出登录</view>
</view>
<view class="flex">
<view class="flex-1 mr-[20rpx]">
<u-button
shape="circle"
type="primary"
plain
size="medium"
hover-class="none"
:customStyle="{ width: '100%' }"
@click="showLogout = false"
>
取消
</u-button>
</view>
<view class="flex-1">
<u-button
shape="circle"
type="primary"
size="medium"
hover-class="none"
:customStyle="{ width: '100%' }"
@click="logoutHandle"
>
确认
</u-button>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script setup lang="ts">
import {onLoad, onShow} from '@dcloudio/uni-app'
import {computed, ref} from 'vue'
import {useAppStore} from '@/stores/app'
import {useUserStore} from '@/stores/user'
import {AgreementEnum} from '@/enums/agreementEnums'
import {isWeixinClient} from '@/utils/client'
import {mnpAuthBind, oaAuthBind} from '@/api/account'
import {useLockFn} from '@/hooks/useLockFn'
import {useRouter} from "uniapp-router-next";
// #ifdef H5
import wechatOa from '@/utils/wechat'
// #endif
const router = useRouter()
const appStore = useAppStore()
const userStore = useUserStore()
const userInfo = computed(() => userStore.userInfo)
const list = ref([
{
text: '修改密码'
},
{
text: '忘记密码'
}
])
const isWeixin = ref(true)
// #ifdef H5
isWeixin.value = isWeixinClient()
// #endif
const show = ref(false)
const showLogout = ref(false)
// 修改/忘记密码
const handleClick = (index: number) => {
switch (index) {
case 0:
router.navigateTo('/pages/change_password/change_password')
break
case 1:
router.navigateTo('/pages/forget_pwd/forget_pwd')
break
}
}
const handlePwd = () => {
if (!userInfo.value.has_password)
return router.navigateTo('/pages/change_password/change_password?type=set')
show.value = true
}
// 退出登录
const logoutHandle = () => {
userStore.logout()
router.redirectTo('/pages/login/login')
}
const bindWechat = async () => {
if (userInfo.value.is_auth) return
try {
uni.showLoading({
title: '请稍后...'
})
// #ifdef MP-WEIXIN
const {code}: any = await uni.login({
provider: 'weixin'
})
await mnpAuthBind({
code: code
})
//#endif
// #ifdef H5
if (isWeixin.value) {
wechatOa.getUrl()
}
// #endif
await userStore.getUser()
uni.hideLoading()
} catch (e) {
uni.hideLoading()
uni.$u.toast(e)
}
}
const {lockFn: bindWechatLock} = useLockFn(bindWechat)
onShow(() => {
userStore.getUser()
})
onLoad(async (options) => {
// #ifdef H5
const {code} = options
if (!isWeixin.value) return
if (code) {
uni.showLoading({
title: '请稍后...'
})
try {
await oaAuthBind({code})
await userStore.getUser()
} catch (error) {
}
//用于清空code
router.redirectTo('/pages/user_set/user_set')
}
// #endif
})
</script>
<style lang="scss" scoped>
.user-set {
.item {
padding: 30rpx;
}
.btn-border {
border-bottom: 2rpx solid #f8f8f8;
}
}
</style>
+16
View File
@@ -0,0 +1,16 @@
<template>
<web-view :src="url" />
</template>
<script setup lang="ts">
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
const url = ref('')
onLoad((options) => {
url.value = decodeURIComponent(options.url!)
})
</script>
<style></style>