登录,个人中心,联系客服
@@ -1,14 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
||||
import request from './utils/request'
|
||||
import { onLaunch } from '@dcloudio/uni-app'
|
||||
import { useAppStore } from './stores/app'
|
||||
import { useUserStore } from './stores/user'
|
||||
const { getConfig } = useAppStore()
|
||||
const { getUser } = useUserStore()
|
||||
console.log(useUserStore())
|
||||
onLaunch(() => {
|
||||
console.log('App Launch')
|
||||
})
|
||||
onShow(() => {
|
||||
console.log('App Show')
|
||||
})
|
||||
onHide(() => {
|
||||
console.log('App Hide')
|
||||
getConfig()
|
||||
getUser()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
//注册
|
||||
export function smsSend(data: Record<string, any>) {
|
||||
//发送短信
|
||||
export function smsSend(data: any) {
|
||||
return request.post({ url: '/sms/send', data: data })
|
||||
}
|
||||
|
||||
export function getConfig() {
|
||||
return request.get({ url: '/config' })
|
||||
}
|
||||
|
||||
@@ -4,3 +4,8 @@ import request from '@/utils/request'
|
||||
export function getIndex() {
|
||||
return request.get({ url: '/index' })
|
||||
}
|
||||
|
||||
// 装修页面
|
||||
export function getDecorate(data: any) {
|
||||
return request.get({ url: '/decorate', data })
|
||||
}
|
||||
|
||||
5
app/src/api/user.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getUserCenter() {
|
||||
return request.get({ url: '/user/center' })
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<template>
|
||||
<view>
|
||||
<view v-for="(item, index) in pages" :key="index">
|
||||
<template v-if="item.name == 'search'">
|
||||
<w-search :content="item.content" :styles="item.styles" />
|
||||
</template>
|
||||
<template v-if="item.name == 'banner'">
|
||||
<w-banner :content="item.content" :styles="item.styles" />
|
||||
</template>
|
||||
<template v-if="item.name == 'nav'">
|
||||
<w-nav :content="item.content" :styles="item.styles" />
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
|
||||
defineProps({
|
||||
pages: {
|
||||
type: Array as PropType<any[]>
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,19 +1,29 @@
|
||||
<template>
|
||||
<view class="banner h-[400rpx] bg-white">
|
||||
<view
|
||||
class="banner h-[340rpx] bg-white translate-y-0"
|
||||
v-if="content.data.length && content.enabled"
|
||||
>
|
||||
<swiper
|
||||
class="swiper h-full"
|
||||
indicator-dots
|
||||
:indicator-dots="content.data.length > 1"
|
||||
indicator-active-color="#4173ff"
|
||||
:autoplay="true"
|
||||
>
|
||||
<swiper-item v-for="(item, index) in content.data" :key="index">
|
||||
<u-image mode="aspectFit" width="100%" height="100%" :src="item.image" />
|
||||
<u-image
|
||||
mode="aspectFit"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:src="getImageUrl(item.image)"
|
||||
/>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
@@ -24,6 +34,7 @@ const props = defineProps({
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const { getImageUrl } = useAppStore()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<view
|
||||
class="customer-service bg-white flex flex-col justify-center items-center mx-[36rpx] mt-[20rpx] rounded-lg px-[110rpx] pt-[100rpx] pb-[160rpx]"
|
||||
>
|
||||
<u-image width="280" height="280" :src="getImageUrl(content.qrcode)" />
|
||||
<view v-if="content.title" class="text-lg mt-[14rpx] font-medium">{{ content.title }}</view>
|
||||
<view v-if="content.time" class="text-content mt-[40rpx]"
|
||||
>服务时间:{{ content.time }}</view
|
||||
>
|
||||
<view v-if="content.mobile" class="text-content mt-[14rpx] flex flex-wrap">
|
||||
客服电话:{{ content.mobile }}
|
||||
<!-- #ifdef H5 -->
|
||||
<a class="ml-[10rpx] phone text-muted underline" :href="'tel:' + content.mobile">
|
||||
拨打
|
||||
</a>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 -->
|
||||
<view class="ml-[10rpx] phone text-muted underline" @click="handleCall">拨打</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="mt-[100rpx] w-full">
|
||||
<u-button type="primary" shape="circle" @click="saveImageToPhotosAlbum(content.qrcode)">
|
||||
保存二维码图片
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { saveImageToPhotosAlbum } from '@/utils/file'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const { getImageUrl } = useAppStore()
|
||||
|
||||
const handleCall = () => {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: String(props.content.mobile)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
50
app/src/components/widgets/my-service/my-service.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div class="my-service bg-white mx-[20rpx] mt-[20rpx] rounded-lg">
|
||||
<div
|
||||
v-if="content.title"
|
||||
class="title px-[30rpx] py-[20rpx] border-light border-solid border-0 border-b"
|
||||
>
|
||||
<div>{{ content.title }}</div>
|
||||
</div>
|
||||
<div v-if="content.style == 1" class="flex flex-wrap pt-[40rpx] pb-[20rpx]">
|
||||
<div
|
||||
v-for="(item, index) in content.data"
|
||||
:key="index"
|
||||
class="flex flex-col items-center w-1/4 mb-[15px]"
|
||||
>
|
||||
<u-image width="52" height="52" :src="getImageUrl(item.image)" alt="" />
|
||||
<div class="mt-[7px]">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="content.style == 2">
|
||||
<div
|
||||
v-for="(item, index) in content.data"
|
||||
:key="index"
|
||||
class="flex items-center border-light border-solid border-0 border-b h-[100rpx] px-[24rpx]"
|
||||
>
|
||||
<u-image width="48" height="48" :src="item.image" alt="" />
|
||||
<div class="ml-[20rpx] flex-1">{{ item.name }}</div>
|
||||
<div class="text-muted">
|
||||
<u-icon name="arrow-right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const { getImageUrl } = useAppStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<view class="nav pt-[30rpx] pb-[16rpx] bg-white">
|
||||
<view class="nav pt-[30rpx] pb-[16rpx] bg-white" v-if="content.data.length && content.enabled">
|
||||
<view class="nav-item flex flex-wrap">
|
||||
<view
|
||||
v-for="(item, index) in content.data"
|
||||
:key="index"
|
||||
class="flex flex-col items-center w-1/5 mb-[30rpx]"
|
||||
>
|
||||
<u-image width="41px" height="41px" :src="item.image" alt="" />
|
||||
<u-image width="41px" height="41px" :src="getImageUrl(item.image)" alt="" />
|
||||
<view class="mt-[14rpx]">{{ item.name }}</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -14,6 +14,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
@@ -24,6 +26,8 @@ const props = defineProps({
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const { getImageUrl } = useAppStore()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
||||
41
app/src/components/widgets/user-banner/user-banner.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<view
|
||||
class="banner h-[200rpx] mx-[20rpx] mt-[20rpx] translate-y-0"
|
||||
v-if="content.data.length && content.enabled"
|
||||
>
|
||||
<swiper
|
||||
class="swiper h-full"
|
||||
:indicator-dots="content.data.length > 1"
|
||||
indicator-active-color="#4173ff"
|
||||
:autoplay="true"
|
||||
>
|
||||
<swiper-item v-for="(item, index) in content.data" :key="index">
|
||||
<u-image
|
||||
mode="aspectFit"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:src="getImageUrl(item.image)"
|
||||
:border-radius="14"
|
||||
/>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/stores/app'
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const { getImageUrl } = useAppStore()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
45
app/src/components/widgets/user-info/user-info.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="user-info flex items-center px-[50rpx]">
|
||||
<navigator
|
||||
v-if="isLogin"
|
||||
class="flex items-center"
|
||||
hover-class="none"
|
||||
url="/pages/login/login"
|
||||
>
|
||||
<u-avatar :src="user.avatar" :size="120"></u-avatar>
|
||||
<div class="text-white text-3xl ml-[20rpx]">{{ user.nickname }}</div>
|
||||
</navigator>
|
||||
<navigator v-else class="flex items-center" hover-class="none" url="/pages/login/login">
|
||||
<u-avatar src="/static/images/user/default_avatar.png" :size="120"></u-avatar>
|
||||
<div class="text-white text-3xl ml-[20rpx]">未登录</div>
|
||||
</navigator>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
user: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
isLogin: {
|
||||
type: Boolean
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-info {
|
||||
background: url(/static/images/user/my_topbg.png);
|
||||
height: 115px;
|
||||
background-position: bottom;
|
||||
background-size: 100% auto;
|
||||
}
|
||||
</style>
|
||||
21
app/src/hooks/useLockFn.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export function useLockFn(fn: (...args: any[]) => Promise<any>) {
|
||||
const isLock = ref(false)
|
||||
const lockFn = async (...args: any[]) => {
|
||||
if (isLock.value) return
|
||||
isLock.value = true
|
||||
try {
|
||||
const res = await fn(...args)
|
||||
isLock.value = false
|
||||
return res
|
||||
} catch (e) {
|
||||
isLock.value = false
|
||||
throw e
|
||||
}
|
||||
}
|
||||
return {
|
||||
isLock,
|
||||
lockFn
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import { createSSRApp } from 'vue'
|
||||
// import { createPinia } from 'pinia'
|
||||
import App from './App.vue'
|
||||
import plugins from './plugins'
|
||||
import './styles/index.scss'
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
console.log(app.config.globalProperties)
|
||||
app.use(plugins)
|
||||
return {
|
||||
app
|
||||
|
||||
@@ -29,6 +29,12 @@
|
||||
"style": {
|
||||
"navigationBarTitleText": "注册"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/customer_service/customer_service",
|
||||
"style": {
|
||||
"navigationBarTitleText": "联系客服"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
26
app/src/pages/customer_service/customer_service.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<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.pages)
|
||||
}
|
||||
getData()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,14 +1,26 @@
|
||||
<template>
|
||||
<view class="index">
|
||||
<decoration :pages="state.pages" />
|
||||
<view v-for="(item, index) in state.pages" :key="index">
|
||||
<template v-if="item.name == 'search'">
|
||||
<w-search :content="item.content" :styles="item.styles" />
|
||||
</template>
|
||||
<template v-if="item.name == 'banner'">
|
||||
<w-banner :content="item.content" :styles="item.styles" />
|
||||
</template>
|
||||
<template v-if="item.name == 'nav'">
|
||||
<w-nav :content="item.content" :styles="item.styles" />
|
||||
</template>
|
||||
</view>
|
||||
<view class="article"> </view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getIndex } from '@/api/shop'
|
||||
import { reactive } from 'vue'
|
||||
const state = reactive({
|
||||
import { reactive, ref } from 'vue'
|
||||
const state = reactive<{
|
||||
pages: any[]
|
||||
}>({
|
||||
pages: []
|
||||
})
|
||||
const getData = async () => {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<u-form borderBottom>
|
||||
<template v-if="scene == LoginTypeEnum.ACCOUNT">
|
||||
<u-form-item borderBottom>
|
||||
<u-icon class="mr-2" :size="36" name="/static/images/icon_user.png" />
|
||||
<u-icon class="mr-2" :size="36" name="/static/images/icon/icon_user.png" />
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.username"
|
||||
@@ -19,7 +19,11 @@
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item borderBottom>
|
||||
<u-icon class="mr-2" :size="36" name="/static/images/icon_password.png" />
|
||||
<u-icon
|
||||
class="mr-2"
|
||||
:size="36"
|
||||
name="/static/images/icon/icon_password.png"
|
||||
/>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.password"
|
||||
@@ -36,7 +40,11 @@
|
||||
</template>
|
||||
<template v-if="scene == LoginTypeEnum.MOBILE">
|
||||
<u-form-item borderBottom>
|
||||
<u-icon class="mr-2" :size="36" name="/static/images/icon_mobile.png" />
|
||||
<u-icon
|
||||
class="mr-2"
|
||||
:size="36"
|
||||
name="/static/images/icon/icon_mobile.png"
|
||||
/>
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.mobile"
|
||||
@@ -45,7 +53,7 @@
|
||||
/>
|
||||
</u-form-item>
|
||||
<u-form-item borderBottom>
|
||||
<u-icon class="mr-2" :size="36" name="/static/images/icon_code.png" />
|
||||
<u-icon class="mr-2" :size="36" name="/static/images/icon/icon_code.png" />
|
||||
<u-input
|
||||
class="flex-1"
|
||||
v-model="formData.code"
|
||||
@@ -77,7 +85,7 @@
|
||||
</u-checkbox>
|
||||
</view>
|
||||
<view class="mt-[40rpx]">
|
||||
<u-button type="primary" shape="circle" @click="accountLogin(scene)">
|
||||
<u-button type="primary" shape="circle" @click="handleLogin(scene)">
|
||||
登 录
|
||||
</u-button>
|
||||
</view>
|
||||
@@ -95,7 +103,7 @@
|
||||
<u-divider>第三方登录</u-divider>
|
||||
<div class="flex justify-center mt-[40rpx]">
|
||||
<div class="flex flex-col items-center" @click="wxLogin">
|
||||
<u-icon name="/static/images/icon_wx.png" size="80" />
|
||||
<u-icon name="/static/images/icon/icon_wx.png" size="80" />
|
||||
<div class="text-sm mt-[10px]">微信登录</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,7 +116,9 @@
|
||||
import { login } from '@/api/account'
|
||||
import { smsSend } from '@/api/app'
|
||||
import { SMSEnum } from '@/enums/appEnums'
|
||||
import { reactive, ref, shallowRef } from 'vue'
|
||||
import { useLockFn } from '@/hooks/useLockFn'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { reactive, ref, shallowRef, watch } from 'vue'
|
||||
enum LoginTypeEnum {
|
||||
MOBILE = 'mobile',
|
||||
ACCOUNT = 'account',
|
||||
@@ -118,6 +128,8 @@ const uCodeRef = shallowRef()
|
||||
const scene = ref(LoginTypeEnum.ACCOUNT)
|
||||
const codeTips = ref('')
|
||||
const isCheckAgreement = ref(false)
|
||||
|
||||
const userStore = useUserStore()
|
||||
const formData = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
@@ -140,7 +152,8 @@ const sendSms = async () => {
|
||||
uCodeRef.value?.start()
|
||||
}
|
||||
}
|
||||
const accountLogin = async (scene: LoginTypeEnum, code?: string) => {
|
||||
|
||||
const loginFun = async (scene: LoginTypeEnum, code?: string) => {
|
||||
if (!isCheckAgreement.value) return uni.$u.toast('请勾选已阅读并同意《服务协议》和《隐私协议》')
|
||||
if (scene == LoginTypeEnum.ACCOUNT) {
|
||||
if (!formData.username) return uni.$u.toast('请输入账号/手机号码')
|
||||
@@ -155,17 +168,29 @@ const accountLogin = async (scene: LoginTypeEnum, code?: string) => {
|
||||
scene
|
||||
}
|
||||
if (code) params.code = code
|
||||
await login(params)
|
||||
uni.$u.toast('登录成功')
|
||||
uni.navigateBack()
|
||||
uni.showLoading({
|
||||
title: '请稍后...'
|
||||
})
|
||||
try {
|
||||
const data = await login(params)
|
||||
userStore.login(data.token)
|
||||
await userStore.getUser()
|
||||
uni.$u.toast('登录成功')
|
||||
uni.hideLoading()
|
||||
uni.navigateBack()
|
||||
} catch (error: any) {
|
||||
uni.hideLoading()
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const { isLock, lockFn: handleLogin } = useLockFn(loginFun)
|
||||
|
||||
const wxLogin = async () => {
|
||||
const data: any = await uni.login({
|
||||
provider: 'weixin'
|
||||
})
|
||||
console.log(data)
|
||||
accountLogin(LoginTypeEnum.MNP, data.code)
|
||||
handleLogin(LoginTypeEnum.MNP, data.code)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<template>
|
||||
<view class="register min-h-full flex flex-col items-center px-[40rpx] pt-[40rpx] box-border">
|
||||
<view
|
||||
class="register bg-white min-h-full flex flex-col items-center px-[40rpx] pt-[40rpx] box-border"
|
||||
>
|
||||
<view class="w-full">
|
||||
<u-form borderBottom :label-width="150">
|
||||
<u-form-item label="创建账号" borderBottom>
|
||||
|
||||
@@ -1,7 +1,45 @@
|
||||
<template>
|
||||
<view class="content">个人设置</view>
|
||||
<view class="user">
|
||||
<view v-for="(item, index) in state.pages" :key="index">
|
||||
<template v-if="item.name == 'user-info'">
|
||||
<w-user-info
|
||||
: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>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<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<{
|
||||
pages: any[]
|
||||
}>({
|
||||
pages: []
|
||||
})
|
||||
const getData = async () => {
|
||||
const data = await getDecorate({ id: 2 })
|
||||
state.pages = JSON.parse(data.pages)
|
||||
}
|
||||
const userStore = useUserStore()
|
||||
const { userInfo, isLogin } = storeToRefs(userStore)
|
||||
onShow(() => {
|
||||
userStore.getUser()
|
||||
})
|
||||
getData()
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 679 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
BIN
app/src/static/images/user/default_avatar.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
app/src/static/images/user/my_topbg.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -1,8 +1,22 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { getConfig } from '@/api/app'
|
||||
|
||||
interface AppSate {
|
||||
config: Record<string, any>
|
||||
}
|
||||
export const useAppStore = defineStore({
|
||||
id: 'userStore',
|
||||
state: () => ({}),
|
||||
id: 'appStore',
|
||||
state: (): AppSate => ({
|
||||
config: {}
|
||||
}),
|
||||
getters: {},
|
||||
actions: {}
|
||||
actions: {
|
||||
getImageUrl(url: string) {
|
||||
return url ? `${this.config.domain}${url}` : ''
|
||||
},
|
||||
async getConfig() {
|
||||
const data = await getConfig()
|
||||
this.config = data
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,12 +1,34 @@
|
||||
import { getClient } from '@/utils/client'
|
||||
import { getUserCenter } from '@/api/user'
|
||||
import { TOKEN_KEY } from '@/enums/cacheEnums'
|
||||
import cache from '@/utils/cache'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useAppStore = defineStore({
|
||||
id: 'appStore',
|
||||
// convert to a function
|
||||
state: () => ({
|
||||
client: getClient()
|
||||
interface UserSate {
|
||||
userInfo: Record<string, any>
|
||||
token: string | null
|
||||
}
|
||||
export const useUserStore = defineStore({
|
||||
id: 'userStore',
|
||||
state: (): UserSate => ({
|
||||
userInfo: {},
|
||||
token: cache.get(TOKEN_KEY) || null
|
||||
}),
|
||||
getters: {},
|
||||
actions: {}
|
||||
getters: {
|
||||
isLogin: (state) => !!state.token
|
||||
},
|
||||
actions: {
|
||||
async getUser() {
|
||||
const data = await getUserCenter()
|
||||
this.userInfo = data
|
||||
},
|
||||
login(token: string) {
|
||||
this.token = token
|
||||
cache.set(TOKEN_KEY, token)
|
||||
},
|
||||
logout() {
|
||||
this.token = ''
|
||||
this.userInfo = {}
|
||||
cache.remove(TOKEN_KEY)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -12,7 +12,7 @@ const cache = {
|
||||
data = JSON.stringify(data)
|
||||
}
|
||||
try {
|
||||
window.localStorage.setItem(key, data)
|
||||
uni.setStorageSync(key, data)
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
@@ -20,13 +20,13 @@ const cache = {
|
||||
get(key: string) {
|
||||
key = this.getKey(key)
|
||||
try {
|
||||
const data = window.localStorage.getItem(key)
|
||||
const data = uni.getStorageSync(key)
|
||||
if (!data) {
|
||||
return null
|
||||
}
|
||||
const { value, expire } = JSON.parse(data)
|
||||
if (expire && expire < this.time()) {
|
||||
window.localStorage.removeItem(key)
|
||||
uni.removeStorageSync(key)
|
||||
return null
|
||||
}
|
||||
return value
|
||||
@@ -40,7 +40,7 @@ const cache = {
|
||||
},
|
||||
remove(key: string) {
|
||||
key = this.getKey(key)
|
||||
window.localStorage.removeItem(key)
|
||||
uni.removeStorageSync(key)
|
||||
},
|
||||
getKey(key: string) {
|
||||
return this.key + key
|
||||
|
||||
21
app/src/utils/file.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export async function saveImageToPhotosAlbum(url: string) {
|
||||
if (!url) return uni.$u.$toast('图片不存在')
|
||||
//#ifdef H5
|
||||
uni.$u.$toast('长按图片保存')
|
||||
//#endif
|
||||
try {
|
||||
const res: any = await uni.downloadFile({ url, timeout: 10000 })
|
||||
await uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath
|
||||
})
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
} catch (error: any) {
|
||||
uni.showToast({
|
||||
title: error.errMsg || '保存失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||