Merge branch 'develop' of https://gitee.com/likeadmin/likeadmin_java into develop

This commit is contained in:
TinyAnts 2022-09-08 09:56:21 +08:00
commit ce877ff7f1
54 changed files with 481 additions and 737 deletions

View File

@ -157,6 +157,7 @@ const setData = async () => {
getData()
feedback.msgSuccess('保存成功')
}
getData()
</script>
<style lang="scss" scoped>
.decoration-tabbar {

12
app/src/api/account.ts Normal file
View File

@ -0,0 +1,12 @@
import { client } from '@/utils/client'
import request from '@/utils/request'
// 登录
export function login(data: Record<string, any>) {
return request.post({ url: '/login/check', data: { ...data, client } })
}
//注册
export function register(data: Record<string, any>) {
return request.post({ url: '/login/register', data: { ...data, client } })
}

6
app/src/api/app.ts Normal file
View File

@ -0,0 +1,6 @@
import request from '@/utils/request'
//注册
export function smsSend(data: Record<string, any>) {
return request.post({ url: '/sms/send', data: data })
}

6
app/src/api/shop.ts Normal file
View File

@ -0,0 +1,6 @@
import request from '@/utils/request'
//首页数据
export function getIndex() {
return request.get({ url: '/index' })
}

View File

@ -0,0 +1,27 @@
<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>

View File

@ -1,75 +0,0 @@
<template>
<div>
<el-form label-width="70px">
<el-form-item label="是否启用">
<el-radio-group v-model="content.enabled">
<el-radio :label="1">开启</el-radio>
<el-radio :label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="图片设置">
<div class="flex-1">
<div class="form-tips">最多添加5张建议图片尺寸750px*400px</div>
<del-wrap
v-for="(item, index) in content.data"
:key="index"
@close="handleDelete(index)"
class="max-w-[400px]"
>
<div class="bg-fill-light flex items-center w-full p-4 mt-4">
<material-picker v-model="item.image" upload-class="bg-body" />
<div class="ml-3 flex-1">
<el-form-item label="图片名称">
<el-input v-model="item.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item class="mt-[18px]" label="图片链接">
<link-picker v-model="item.link" />
</el-form-item>
</div>
</div>
</del-wrap>
</div>
</el-form-item>
<el-form-item v-if="content.data?.length < limit">
<el-button type="primary" @click="handleAdd">添加图片</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import feedback from '@/utils/feedback'
import type { PropType } from 'vue'
import type options from './options'
const limit = 5
type OptionsType = ReturnType<typeof options>
const props = defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
const handleAdd = () => {
if (props.content.data?.length < limit) {
props.content.data.push({
image: '',
name: '',
link: {}
})
} else {
feedback.msgError(`最多添加${limit}张图片`)
}
}
const handleDelete = (index: number) => {
if (props.content.data?.length <= 1) {
return feedback.msgError('最少保留一张图片')
}
props.content.data.splice(index, 1)
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,31 +0,0 @@
<template>
<div class="banner">
<div class="banner-image">
<image-contain width="100%" height="200px" :src="getImage" fit="contain" />
</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import type options from './options'
type OptionsType = ReturnType<typeof options>
const props = defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
const getImage = computed(() => {
const { data } = props.content
if (Array.isArray(data)) {
return data[0] ? data[0].image : ''
}
return ''
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,8 +0,0 @@
import attr from './attr.vue'
import content from './content.vue'
import options from './options'
export default {
attr,
content,
options
}

View File

@ -1,15 +0,0 @@
export default () => ({
title: '首页轮播图',
name: 'banner',
content: {
enabled: 1,
data: [
{
image: '',
name: '',
link: {}
}
]
},
styles: {}
})

View File

@ -1,38 +0,0 @@
<template>
<div>
<el-form label-width="90px">
<el-form-item label="客服标题">
<el-input class="w-[400px]" v-model="content.title" />
</el-form-item>
<el-form-item label="服务时间">
<el-input class="w-[400px]" v-model="content.time" />
</el-form-item>
<el-form-item label="联系电话">
<el-input class="w-[400px]" v-model="content.mobile" />
</el-form-item>
<el-form-item label="客服二维码">
<div>
<material-picker v-model="content.qrcode" />
<div class="form-tips">建议图片尺寸200*200像素图片格式jpgpngjpeg</div>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import type options from './options'
type OptionsType = ReturnType<typeof options>
defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,38 +0,0 @@
<template>
<div class="customer-service">
<image-contain width="140px" height="140px" :src="content.qrcode" alt="" />
<div class="text-[15px] mt-[7px] font-medium">{{ content.title }}</div>
<div class="text-[#666] mt-[20px]">服务时间{{ content.time }}</div>
<div class="text-[#666] mt-[7px]">客服电话{{ content.mobile }}</div>
<div
class="text-white text-[16px] rounded-[42px] bg-[#4173FF] w-full h-[42px] flex justify-center items-center mt-[50px]"
>
保存二维码图片
</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import type options from './options'
type OptionsType = ReturnType<typeof options>
defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
</script>
<style lang="scss" scoped>
.customer-service {
margin: 10px 18px;
border-radius: 10px;
padding: 50px 55px 80px;
background: #fff;
@apply flex flex-col justify-center items-center;
}
</style>

View File

@ -1,8 +0,0 @@
import attr from './attr.vue'
import content from './content.vue'
import options from './options'
export default {
attr,
content,
options
}

View File

@ -1,11 +0,0 @@
export default () => ({
title: '客服设置',
name: 'customer-service',
content: {
title: '添加客服二维码',
time: '',
mobile: '',
qrcode: ''
},
styles: {}
})

View File

@ -1,14 +0,0 @@
const widgets: Record<string, any> = import.meta.glob('./**/index.ts', { eager: true })
interface Widget {
attr: any
content: any
options: any
}
console.log(widgets)
const exportWidgets: Record<string, Widget> = {}
Object.keys(widgets).forEach((key) => {
const widgetName = key.replace(/^\.\/([\w-]+).*/gi, '$1')
exportWidgets[widgetName] = widgets[key]?.default
})
export default exportWidgets

View File

@ -1,38 +0,0 @@
<template>
<div>
<el-form label-width="70px">
<el-form-item label="排版样式">
<el-radio-group v-model="content.style">
<el-radio :label="1">横排</el-radio>
<el-radio :label="2">竖排</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="标题名称">
<el-input class="w-[400px]" v-model="content.title" />
</el-form-item>
<el-form-item label="菜单设置">
<div class="flex-1">
<AddNav v-model="content.data" />
</div>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import type options from './options'
import AddNav from '../../add-nav.vue'
type OptionsType = ReturnType<typeof options>
defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,58 +0,0 @@
<template>
<div class="my-service">
<div v-if="content.title" class="title px-[15px] py-[10px]">
<div>{{ content.title }}</div>
</div>
<div v-if="content.style == 1" class="flex flex-wrap pt-[20px] pb-[10px]">
<div
v-for="(item, index) in content.data"
:key="index"
class="flex flex-col items-center w-1/4 mb-[15px]"
>
<image-contain width="26px" height="26px" :src="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-b border-[#e5e5e5] h-[50px] px-[12px]"
>
<image-contain width="24px" height="24px" :src="item.image" alt="" />
<div class="ml-[10px] flex-1">{{ item.name }}</div>
<div>
<icon name="el-icon-ArrowRight" />
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import type options from './options'
type OptionsType = ReturnType<typeof options>
defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
</script>
<style lang="scss" scoped>
.my-service {
margin: 10px 10px 0;
background-color: #fff;
border-radius: 7px;
.title {
border-bottom: 1px solid #e5e5e5;
font-size: 16px;
font-weight: 500;
}
}
</style>

View File

@ -1,8 +0,0 @@
import attr from './attr.vue'
import content from './content.vue'
import options from './options'
export default {
attr,
content,
options
}

View File

@ -1,16 +0,0 @@
export default () => ({
title: '我的服务',
name: 'my-service',
content: {
style: 1,
title: '我的服务',
data: [
{
image: '',
name: '',
link: {}
}
]
},
styles: {}
})

View File

@ -1,36 +0,0 @@
<template>
<div>
<el-form label-width="70px">
<el-form-item label="是否启用">
<el-radio-group v-model="content.enabled">
<el-radio :label="1">开启</el-radio>
<el-radio :label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单设置">
<div class="flex-1">
<div class="form-tips mb-4">最多可添加10个建议图片尺寸100px*100px</div>
<AddNav v-model="content.data" />
</div>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import type options from './options'
import AddNav from '../../add-nav.vue'
type OptionsType = ReturnType<typeof options>
defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,35 +0,0 @@
<template>
<div class="nav pt-[15px] pb-[8px]">
<div class="flex flex-wrap">
<div
v-for="(item, index) in content.data"
:key="index"
class="flex flex-col items-center w-1/5 mb-[15px]"
>
<image-contain width="41px" height="41px" :src="item.image" alt="" />
<div class="mt-[7px]">{{ item.name }}</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import type options from './options'
type OptionsType = ReturnType<typeof options>
const props = defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
</script>
<style lang="scss" scoped>
.nav {
background-color: #fff;
}
</style>

View File

@ -1,8 +0,0 @@
import attr from './attr.vue'
import content from './content.vue'
import options from './options'
export default {
attr,
content,
options
}

View File

@ -1,15 +0,0 @@
export default () => ({
title: '导航菜单',
name: 'nav',
content: {
enabled: 1,
data: [
{
image: '',
name: '导航',
link: {}
}
]
},
styles: {}
})

View File

@ -1,20 +0,0 @@
<template>
<div></div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import type options from './options'
type OptionsType = ReturnType<typeof options>
const props = defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,23 +0,0 @@
<template>
<div class="search">
<div class="search-con flex items-center px-[15px]">
<icon name="el-icon-Search" :size="17" />
<span class="ml-[5px]">请输入关键词搜索</span>
</div>
</div>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped>
.search {
background-color: #fff;
padding: 7px 12px;
.search-con {
height: 100%;
height: 36px;
border-radius: 36px;
background: #f4f4f4;
color: #999999;
}
}
</style>

View File

@ -1,8 +0,0 @@
import attr from './attr.vue'
import content from './content.vue'
import options from './options'
export default {
attr,
content,
options
}

View File

@ -1,7 +0,0 @@
export default () => ({
title: '搜索',
name: 'search',
disabled: 1,
content: {},
styles: {}
})

View File

@ -1,75 +0,0 @@
<template>
<div>
<el-form label-width="70px">
<el-form-item label="是否启用">
<el-radio-group v-model="content.enabled">
<el-radio :label="1">开启</el-radio>
<el-radio :label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="图片设置">
<div class="flex-1">
<div class="form-tips">最多添加5张建议图片尺寸750px*200px</div>
<del-wrap
v-for="(item, index) in content.data"
:key="index"
@close="handleDelete(index)"
class="max-w-[400px]"
>
<div class="bg-fill-light flex items-center w-full p-4 mt-4">
<material-picker v-model="item.image" upload-class="bg-body" />
<div class="ml-3 flex-1">
<el-form-item label="图片名称">
<el-input v-model="item.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item class="mt-[18px]" label="图片链接">
<link-picker v-model="item.link" />
</el-form-item>
</div>
</div>
</del-wrap>
</div>
</el-form-item>
<el-form-item v-if="content.data?.length < limit">
<el-button type="primary" @click="handleAdd">添加图片</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import feedback from '@/utils/feedback'
import type { PropType } from 'vue'
import type options from './options'
const limit = 5
type OptionsType = ReturnType<typeof options>
const props = defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
const handleAdd = () => {
if (props.content.data?.length < limit) {
props.content.data.push({
image: '',
name: '',
link: {}
})
} else {
feedback.msgError(`最多添加${limit}张图片`)
}
}
const handleDelete = (index: number) => {
if (props.content.data?.length <= 1) {
return feedback.msgError('最少保留一张图片')
}
props.content.data.splice(index, 1)
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,31 +0,0 @@
<template>
<div class="banner mx-[10px] mt-[10px]">
<div class="banner-image">
<image-contain width="100%" height="100px" :src="getImage" fit="contain" />
</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import type options from './options'
type OptionsType = ReturnType<typeof options>
const props = defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
const getImage = computed(() => {
const { data } = props.content
if (Array.isArray(data)) {
return data[0] ? data[0].image : ''
}
return ''
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,8 +0,0 @@
import attr from './attr.vue'
import content from './content.vue'
import options from './options'
export default {
attr,
content,
options
}

View File

@ -1,15 +0,0 @@
export default () => ({
title: '个人中心广告图',
name: 'user-banner',
content: {
enabled: 1,
data: [
{
image: '',
name: '',
link: {}
}
]
},
styles: {}
})

View File

@ -1,20 +0,0 @@
<template>
<div></div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue'
import type options from './options'
type OptionsType = ReturnType<typeof options>
const props = defineProps({
content: {
type: Object as PropType<OptionsType['content']>,
default: () => ({})
},
styles: {
type: Object as PropType<OptionsType['styles']>,
default: () => ({})
}
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,23 +0,0 @@
<template>
<div class="user-info flex items-center px-[25px]">
<img src="./images/default_avatar.png" class="w-[60px] h-[60px]" alt="" />
<div class="text-white text-[18px] ml-[10px]">未登录</div>
</div>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped>
.user-info {
background: url(./images/my_topbg.png);
height: 115px;
background-position: bottom;
background-size: 100% auto;
.search-con {
height: 100%;
height: 36px;
border-radius: 36px;
background: #f4f4f4;
color: #999999;
}
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

View File

@ -1,8 +0,0 @@
import attr from './attr.vue'
import content from './content.vue'
import options from './options'
export default {
attr,
content,
options
}

View File

@ -1,7 +0,0 @@
export default () => ({
title: '用户信息',
name: 'user-info',
disabled: 1,
content: {},
styles: {}
})

View File

@ -0,0 +1,29 @@
<template>
<view class="banner h-[400rpx] bg-white">
<swiper
class="swiper h-full"
indicator-dots
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" />
</swiper-item>
</swiper>
</view>
</template>
<script setup lang="ts">
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
}
})
</script>
<style></style>

View File

@ -0,0 +1,29 @@
<template>
<view class="nav pt-[30rpx] pb-[16rpx] bg-white">
<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="" />
<view class="mt-[14rpx]">{{ item.name }}</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
}
})
</script>
<style></style>

View File

@ -0,0 +1,9 @@
<template>
<view class="search px-[24rpx] py-[14rpx] bg-white">
<u-search placeholder="请输入关键子" disabled :show-action="false"></u-search>
</view>
</template>
<script setup lang="ts"></script>
<style></style>

View File

@ -3,3 +3,19 @@ export enum ThemeEnum {
LIGHT = 'light',
DARK = 'dark'
}
// 客户端
export enum ClientEnum {
MP_WEIXIN = 1, // 微信-小程序
OA_WEIXIN = 2, // 微信-公众号
H5 = 3, // H5
IOS = 5, //苹果
ANDROID = 6 //安卓
}
export enum SMSEnum {
LOGIN = 101,
BIND_MOBILE = 102,
CHANGE_MOBILE = 103,
FIND_PASSWORD = 104
}

View File

@ -23,6 +23,12 @@
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/register/register",
"style": {
"navigationBarTitleText": "注册"
}
}
],
"globalStyle": {
@ -53,7 +59,8 @@
},
"easycom": {
"custom": {
"^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)": "z-paging/components/z-paging$1/z-paging$1.vue"
"^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)": "z-paging/components/z-paging$1/z-paging$1.vue",
"^w-(.*)": "@/components/widgets/$1/$1.vue"
}
}
}

View File

@ -1,11 +1,22 @@
<template>
<view class="content">
<navigator url="/pages/login/login" hover-class="navigator-hover">
<button type="default">跳转到新页面</button>
</navigator>
<view class="index">
<decoration :pages="state.pages" />
<view class="article"> </view>
</view>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import { getIndex } from '@/api/shop'
import { reactive } from 'vue'
const state = reactive({
pages: []
})
const getData = async () => {
const data = await getIndex()
state.pages = JSON.parse(data.pages)
console.log(state.pages)
}
getData()
</script>
<style></style>

View File

@ -1,46 +1,176 @@
<template>
<view class="login">
<view
class="bg-white login min-h-full flex flex-col items-center px-[40rpx] pt-[80rpx] box-border"
>
<view>
<u-image src="" mode="widthFix" height="160" width="160" />
</view>
<view class="mt-4">这里是商城名称</view>
<view class="w-full mt-[60rpx]">
<u-form :model="formData" borderBottom>
<u-form-item borderBottom>
<u-icon class="mr-2" :size="36" name="/static/images/icon_user.png" />
<u-input
v-model="formData.mobile"
:border="false"
placeholder="请输入账号/手机号码"
/>
</u-form-item>
<u-form-item borderBottom>
<u-icon class="mr-2" :size="36" name="/static/images/icon_password.png" />
<u-input placeholder="请输入密码" :border="false" />
<view
class="border-l border-solid border-0 border-light pl-3 text-muted leading-4"
>
忘记密码
</view>
</u-form-item>
<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-input
class="flex-1"
v-model="formData.username"
:border="false"
placeholder="请输入账号/手机号码"
/>
</u-form-item>
<u-form-item borderBottom>
<u-icon class="mr-2" :size="36" name="/static/images/icon_password.png" />
<u-input
class="flex-1"
v-model="formData.password"
type="password"
placeholder="请输入密码"
:border="false"
/>
<view
class="border-l border-solid border-0 border-light pl-3 text-muted leading-4 ml-3"
>
忘记密码
</view>
</u-form-item>
</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-input
class="flex-1"
v-model="formData.mobile"
:border="false"
placeholder="请输入手机号码"
/>
</u-form-item>
<u-form-item borderBottom>
<u-icon class="mr-2" :size="36" name="/static/images/icon_code.png" />
<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秒"
/>
{{ codeTips }}
</view>
</u-form-item>
</template>
</u-form>
<view class="mt-[60rpx]">
<u-checkbox v-model="isCheckAgreement" shape="circle">
<view class="text-xs flex">
已阅读并同意
<navigator class="text-primary">服务协议</navigator>
<navigator class="text-primary">隐私协议</navigator>
</view>
</u-checkbox>
</view>
<view class="mt-[40rpx]">
<u-button type="primary" shape="circle" @click="accountLogin(scene)">
</u-button>
</view>
<view class="text-content flex justify-between mt-[40rpx]">
<view v-if="scene == LoginTypeEnum.MOBILE" @click="scene = LoginTypeEnum.ACCOUNT">
账号密码登录
</view>
<view v-if="scene == LoginTypeEnum.ACCOUNT" @click="scene = LoginTypeEnum.MOBILE">
短信验证码登录
</view>
<navigator url="/pages/register/register">注册账号</navigator>
</view>
<view class="mt-[80rpx]">
<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" />
<div class="text-sm mt-[10px]">微信登录</div>
</div>
</div>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import { login } from '@/api/account'
import { smsSend } from '@/api/app'
import { SMSEnum } from '@/enums/appEnums'
import { reactive, ref, shallowRef } from 'vue'
enum LoginTypeEnum {
MOBILE = 'mobile',
ACCOUNT = 'account',
MNP = 'mnp'
}
const uCodeRef = shallowRef()
const scene = ref(LoginTypeEnum.ACCOUNT)
const codeTips = ref('')
const isCheckAgreement = ref(false)
const formData = reactive({
username: '',
password: '',
code: '',
mobile: ''
})
const codeChange = (text: string) => {
codeTips.value = text
}
const sendSms = async () => {
if (!formData.mobile) return uni.$u.toast('请输入手机号码')
if (uCodeRef.value?.canGetCode) {
await smsSend({
scene: SMSEnum.LOGIN,
mobile: formData.mobile
})
uni.$u.toast('发送成功')
uCodeRef.value?.start()
}
}
const accountLogin = async (scene: LoginTypeEnum, code?: string) => {
if (!isCheckAgreement.value) return uni.$u.toast('请勾选已阅读并同意《服务协议》和《隐私协议》')
if (scene == LoginTypeEnum.ACCOUNT) {
if (!formData.username) return uni.$u.toast('请输入账号/手机号码')
if (!formData.password) return uni.$u.toast('请输入密码')
}
if (scene == LoginTypeEnum.MOBILE) {
if (!formData.mobile) return uni.$u.toast('请输入手机号码')
if (!formData.code) return uni.$u.toast('请输入验证码')
}
const params = {
...formData,
scene
}
if (code) params.code = code
await login(params)
uni.$u.toast('登录成功')
uni.navigateBack()
}
const wxLogin = async () => {
const data: any = await uni.login({
provider: 'weixin'
})
console.log(data)
accountLogin(LoginTypeEnum.MNP, data.code)
}
</script>
<style lang="scss">
page {
height: 100%;
}
.login {
@apply min-h-full flex flex-col items-center px-[40rpx] pt-[80rpx] box-border;
}
</style>

View File

@ -0,0 +1,76 @@
<template>
<view class="register 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>
<u-input
class="flex-1"
v-model="accountData.username"
:border="false"
placeholder="请输入账号"
/>
</u-form-item>
<u-form-item label="设置密码" borderBottom>
<u-input
class="flex-1"
type="password"
v-model="accountData.password"
placeholder="请输入字母+数字组合的密码"
:border="false"
/>
</u-form-item>
<u-form-item label="确认密码" borderBottom>
<u-input
class="flex-1"
type="password"
v-model="accountData.password2"
placeholder="请确认密码"
:border="false"
/>
</u-form-item>
</u-form>
<view class="mt-[60rpx]">
<u-checkbox v-model="isCheckAgreement" shape="circle">
<view class="text-xs flex">
已阅读并同意
<navigator class="text-primary">服务协议</navigator>
<navigator class="text-primary">隐私协议</navigator>
</view>
</u-checkbox>
</view>
<view class="mt-[40rpx]">
<u-button type="primary" shape="circle" @click="accountRegister"> 注册 </u-button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { register } from '@/api/account'
import { reactive, ref } from 'vue'
const isCheckAgreement = ref(false)
const accountData = reactive({
username: '',
password: '',
password2: ''
})
const accountRegister = async () => {
if (!isCheckAgreement.value) return uni.$u.toast('请勾选已阅读并同意《服务协议》和《隐私协议》')
if (!accountData.username) return uni.$u.toast('请输入账号')
if (!accountData.password) return uni.$u.toast('请输入密码')
if (!accountData.password2) return uni.$u.toast('请输入确认密码')
if (accountData.password != accountData.password2) return uni.$u.toast('两次输入的密码不一致')
await register(accountData)
uni.$u.toast('组册成功')
uni.navigateBack()
}
</script>
<style lang="scss">
page {
height: 100%;
}
</style>

View File

@ -0,0 +1,6 @@
import { App } from 'vue'
import { createPinia } from 'pinia'
const pinia = createPinia()
export default (app: App) => {
app.use(pinia)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

8
app/src/stores/app.ts Normal file
View File

@ -0,0 +1,8 @@
import { defineStore } from 'pinia'
export const useAppStore = defineStore({
id: 'userStore',
state: () => ({}),
getters: {},
actions: {}
})

12
app/src/stores/user.ts Normal file
View File

@ -0,0 +1,12 @@
import { getClient } from '@/utils/client'
import { defineStore } from 'pinia'
export const useAppStore = defineStore({
id: 'appStore',
// convert to a function
state: () => ({
client: getClient()
}),
getters: {},
actions: {}
})

View File

@ -1,3 +1,3 @@
// .input-placeholder {
// color: $u-info !important;
// }
page {
background-color: $u-bg-color;
}

View File

@ -429,7 +429,7 @@
.u-form-item {
@include vue-flex;
// align-items: flex-start;
padding: 10rpx 0;
padding: 15rpx 0;
font-size: 28rpx;
color: $u-main-color;
box-sizing: border-box;
@ -485,10 +485,8 @@
&__slot {
flex: 1;
/* #ifndef MP */
@include vue-flex;
align-items: center;
/* #endif */
}
&__icon {

57
app/src/utils/client.ts Normal file
View File

@ -0,0 +1,57 @@
import { ClientEnum } from '@/enums/appEnums'
/**
* @description
* @return { Boolean }
*/
export const isWeixinClient = () => {
// #ifdef H5
return /MicroMessenger/i.test(navigator.userAgent)
// #endif
}
/**
* @description
* @return { Object }
*/
export const getClient = () => {
//@ts-ignore
return handleClientEvent({
// 微信小程序
MP_WEIXIN: () => ClientEnum['MP_WEIXIN'],
// 微信公众号
OA_WEIXIN: () => ClientEnum['OA_WEIXIN'],
// H5
H5: () => ClientEnum['H5'],
// APP
IOS: () => ClientEnum['IOS'],
ANDROID: () => ClientEnum['ANDROID'],
// 其它
OTHER: () => null
})
}
// 根据端处理事件
//@ts-ignore
export const handleClientEvent = ({ MP_WEIXIN, OA_WEIXIN, H5, IOS, ANDROID, OTHER }) => {
// #ifdef MP-WEIXIN
return MP_WEIXIN()
// #endif
// #ifdef H5
return isWeixinClient() ? OA_WEIXIN() : H5()
// #endif
// #ifdef APP-PLUS
const system = uni.getSystemInfoSync()
if (system.platform == 'ios') {
return IOS()
} else {
return ANDROID()
}
// #endif
return OTHER()
}
export const client = getClient()

View File

@ -32,11 +32,11 @@ const requestHooks: RequestHooks = {
if (!isTransformResponse) {
return response.data
}
console.log(response.data)
const { code, data, msg } = response.data as any
switch (code) {
case RequestCodeEnum.SUCCESS:
return data
case RequestCodeEnum.PARAMS_TYPE_ERROR:
case RequestCodeEnum.PARAMS_VALID_ERROR:
case RequestCodeEnum.REQUEST_METHOD_ERROR:
@ -47,7 +47,8 @@ const requestHooks: RequestHooks = {
case RequestCodeEnum.NO_PERMISSTION:
case RequestCodeEnum.FAILED:
case RequestCodeEnum.SYSTEM_ERROR:
return Promise.reject(data)
uni.$u.toast(msg)
return Promise.reject(msg)
case RequestCodeEnum.TOKEN_INVALID:
case RequestCodeEnum.TOKEN_EMPTY:
@ -63,13 +64,13 @@ const defaultOptions: HttpRequestOptions = {
requestOptions: {
timeout: 10 * 1000
},
baseUrl: 'https://likeadmin-java.yixiangonline.com',
baseUrl: import.meta.env.VITE_APP_BASE_URL,
//是否返回默认的响应
isReturnDefaultResponse: false,
// 需要对返回数据进行处理
isTransformResponse: true,
// 接口拼接地址
urlPrefix: '/api',
urlPrefix: 'api',
// 忽略重复请求
ignoreCancel: false,
// 是否携带token