feat: 更换模板

This commit is contained in:
范鹏 2024-12-31 10:58:07 +08:00
parent 3b77a4bfa9
commit 25cf6f1c02
44 changed files with 1182 additions and 271 deletions

View File

@ -3,11 +3,14 @@
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"DirectiveBinding": true,
"EffectScope": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"InjectionKey": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"PropType": true,
"Ref": true,
"VNode": true,
@ -65,6 +68,7 @@
"onUnload": true,
"onUnmounted": true,
"onUpdated": true,
"onWatcherCleanup": true,
"provide": true,
"reactive": true,
"readonly": true,
@ -82,20 +86,15 @@
"useAttrs": true,
"useCssModule": true,
"useCssVars": true,
"useId": true,
"useModel": true,
"useRequest": true,
"useSlots": true,
"useTemplateRef": true,
"useUpload": true,
"useUpload2": true,
"watch": true,
"watchEffect": true,
"watchPostEffect": true,
"watchSyncEffect": true,
"DirectiveBinding": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"onWatcherCleanup": true,
"useId": true,
"useModel": true,
"useTemplateRef": true
"watchSyncEffect": true
}
}

16
env/.env vendored
View File

@ -1,15 +1,15 @@
VITE_APP_TITLE = 'unibest'
VITE_APP_PORT = 9000
VITE_APP_TITLE='星星勤务'
VITE_APP_PORT=9000
VITE_UNI_APPID = 'H57F2ACE4'
VITE_WX_APPID = 'wxa2abb91f64032a2b'
VITE_UNI_APPID='__UNI__58536F2'
VITE_WX_APPID='wxa2abb91f64032a2b'
# h5部署网站的base配置到 manifest.config.ts 里的 h5.router.base
VITE_APP_PUBLIC_BASE=/unibest/
VITE_APP_PUBLIC_BASE=/
VITE_SERVER_BASEURL = 'https://ukw0y1.laf.run'
VITE_UPLOAD_BASEURL = 'https://ukw0y1.laf.run/upload'
VITE_SERVER_BASEURL='https://ukw0y1.laf.run'
VITE_UPLOAD_BASEURL='https://ukw0y1.laf.run/upload'
# h5是否需要配置代理
VITE_APP_PROXY=false
VITE_APP_PROXY_PREFIX = '/api'
VITE_APP_PROXY_PREFIX='/api'

View File

@ -1,6 +1,6 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
NODE_ENV='development'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE = false
VITE_DELETE_CONSOLE=false
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = true
VITE_SHOW_SOURCEMAP=true

6
env/.env.production vendored
View File

@ -1,6 +1,6 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
NODE_ENV='development'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE = true
VITE_DELETE_CONSOLE=true
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = false
VITE_SHOW_SOURCEMAP=false

4
env/.env.test vendored
View File

@ -1,4 +1,4 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
NODE_ENV='development'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE = false
VITE_DELETE_CONSOLE=false

View File

@ -90,7 +90,7 @@
"pinia-plugin-persistedstate": "3.2.1",
"qs": "6.5.3",
"vue": "3.4.21",
"wot-design-uni": "^1.4.0",
"wot-design-uni": "1.4.0",
"z-paging": "^2.8.4"
},
"devDependencies": {

10
pnpm-lock.yaml generated
View File

@ -72,8 +72,8 @@ importers:
specifier: 3.4.21
version: 3.4.21(typescript@5.7.2)
wot-design-uni:
specifier: ^1.4.0
version: 1.4.0(vue@3.4.21(typescript@5.7.2))
specifier: 1.5.1
version: 1.5.1(vue@3.4.21(typescript@5.7.2))
z-paging:
specifier: ^2.8.4
version: 2.8.4
@ -5456,8 +5456,8 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
wot-design-uni@1.4.0:
resolution: {integrity: sha512-h6sjrgfg7mP0nJwGTiHn/iTLPFyRswMpQV6MKhWN3skTnXe+FAyndvtXofpXp+uLkTXefGfw5BPI0uuCMD874w==}
wot-design-uni@1.5.1:
resolution: {integrity: sha512-jiDRuF8r7+xAldc4Dp+2T1VnqDnOoMWsXu6aRpDg2QG7ZH+/bJl7W8H8hHbKFgA9lu1By/HCDmpG7JC45nYtBw==}
engines: {HBuilderX: ^3.8.7}
peerDependencies:
vue: '>=3.2.47'
@ -12236,7 +12236,7 @@ snapshots:
word-wrap@1.2.5: {}
wot-design-uni@1.4.0(vue@3.4.21(typescript@5.7.2)):
wot-design-uni@1.5.1(vue@3.4.21(typescript@5.7.2)):
dependencies:
vue: 3.4.21(typescript@5.7.2)

View File

@ -30,29 +30,4 @@
height: 100%;
vertical-align: middle;
}
// 使 unocss: text-ellipsis
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
//
.ellipsis-2 {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
//
.ellipsis-3 {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
</style>

View File

@ -0,0 +1,73 @@
<template>
<view>
<wd-tabbar
v-model="index"
fixed
safeAreaInsetBottom
active-color="#255cf7"
inactive-color="#0d0f10"
placeholder
@change="change"
>
<wd-tabbar-item
v-for="(item, index) in list"
:key="index"
:is-dot="item.isDot || false"
:title="item.title"
:value="item.value || 0"
:icon="item.icon"
></wd-tabbar-item>
</wd-tabbar>
</view>
</template>
<script setup lang="ts">
const index = ref<number>(0)
defineOptions({
options: {
styleIsolation: 'shared'
}
})
const props = defineProps({
defaultIndex: {
type: Number,
default: 0
},
list: {
type: Array<TabBarItem>,
required: true
}
})
const emits = defineEmits(['change'])
onMounted(() => {
index.value = props.defaultIndex
})
const change = (index: number) => {
emits('change', index)
}
</script>
<style lang="scss" scoped>
:deep(.wd-icon) {
width: 24px;
height: 24px;
margin-bottom: 2px;
}
:deep(.wd-tabbar-item__body-title) {
font-size: 10px;
}
:deep(.wd-badge__content) {
top: 5px !important;
}
:deep(.wd-tabbar) {
background: #f4f5fa;
}
</style>

View File

@ -0,0 +1,95 @@
<template>
<view>
<wd-popup v-model="show" custom-style="border-radius:24rpx;" :close-on-click-modal="false">
<view class="w-75">
<view v-if="title" class="font-bold text-4 text-center pt-4">{{ title }}</view>
<view class="text-3.5 w-60 pl-7.5 py-4 custom-color-black">
<slot v-if="$slots.default"></slot>
<view class="break-all" v-else>{{ content }}</view>
</view>
<view class="flex flex-items-center text-center border-[#ebebeb] border-t-1 border-t-solid">
<view
v-if="showCancel"
class="w-1/2 py-3 text-4 font-bold border-r-1 border-r-solid border-[#ebebeb]"
:style="{ color: cancelColor }"
@click="cancel"
>
{{ cancelText }}
</view>
<view
class="w-1/2 py-3 text-4 font-bold"
:class="[showCancel ? 'w-1/2' : 'w-full']"
:style="{ color: confirmColor }"
@click="confirm"
>
{{ confirmText }}
</view>
</view>
</view>
</wd-popup>
</view>
</template>
<script setup lang="ts">
const show = ref(false)
const props = defineProps({
title: {
type: String,
default: null
},
content: {
type: String,
default: null
},
cancelText: {
type: String,
default: '取消'
},
confirmText: {
type: String,
default: '确定'
},
showCancel: {
type: Boolean,
default: true
},
cancelColor: {
type: String,
default: '#0d0f10'
},
confirmColor: {
type: String,
default: '#255cf7'
},
controlModal: {
type: Boolean,
default: false
}
})
const showModal = () => {
show.value = true
}
const hideModal = () => {
show.value = false
}
const emits = defineEmits(['cancel', 'confirm'])
const cancel = async () => {
emits('cancel')
show.value = props.controlModal
}
const confirm = async () => {
emits('confirm')
show.value = props.controlModal
}
defineExpose({
showModal,
hideModal
})
</script>

View File

@ -0,0 +1,75 @@
<template>
<view>
<view v-if="systemInfo" :style="{ marginTop: systemInfo.safeAreaInsets?.top + 'px' }">
<view
v-if="mini"
class="h-12 pt-1 pos-relative flex flex-justify-between flex-items-center text-4 font-bold custom-color-black"
>
<view class="ml-4 line-clamp-1 max-w-60 break-all">
{{ title }}
</view>
<view class="mr-3 border-1 border-solid border-[#ebebeb] w-25 h-7 rounded-4">
<view class="flex flex-items-center flex-justify-around px-2 h-7">
<image class="w-6 h-6" src="/static/images/icon_nav_more.png"></image>
<view class="h-4 w-0.25 bg-[#ebebeb]"></view>
<image class="w-6 h-6" src="/static/images/icon_nav_close.png"></image>
</view>
</view>
</view>
<view
v-else
class="h-12 pt-1 pos-relative flex flex-justify-center flex-items-center text-4 font-bold custom-color-black"
>
<image
v-if="haveBack"
:src="backUrl"
class="pos-absolute left-3 h-5 w-5"
@click="back"
></image>
<view class="flex flex-items-center">
<view class="line-clamp-1 max-w-60 break-all">
{{ title }}
</view>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { useBasicStore } from '@/store'
const $basic = useBasicStore()
const systemInfo = ref(null)
defineProps({
mini: {
type: Boolean,
default: false
},
title: {
type: String,
default: null
},
haveBack: {
type: Boolean,
default: true
},
backUrl: {
type: String,
default: '/static/images/icon_back.png'
}
})
const emits = defineEmits(['back'])
const back = () => {
uni.navigateBack()
emits('back')
}
onMounted(async () => {
systemInfo.value = await $basic.getSystemInfo()
})
</script>

View File

@ -0,0 +1,3 @@
export const phoneRegExp = /^1[3-9]\d{9}$/
export const passwordRegExp = /^(?![0-9]+$)(?![a-zA-Z]+$)(?![^0-9a-zA-Z]+$).{8,20}$/

View File

View File

@ -1,6 +1,6 @@
{
"name": "unibest",
"appid": "H57F2ACE4",
"name": "星星勤务",
"appid": "__UNI__58536F2",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
@ -105,7 +105,7 @@
"vueVersion": "3",
"h5": {
"router": {
"base": "/unibest/"
"base": "/"
}
}
}
}

View File

@ -51,7 +51,7 @@
},
"pages": [
{
"path": "pages/index/index",
"path": "pages/home/home",
"type": "home",
"style": {
"navigationStyle": "custom",
@ -59,12 +59,53 @@
}
},
{
"path": "pages/about/about",
"path": "pages/code/code",
"type": "page",
"style": {
"navigationBarTitleText": "关于"
"navigationStyle": "custom",
"disableScroll": true
}
},
{
"path": "pages/get-code/get-code",
"type": "page",
"style": {
"navigationStyle": "custom",
"disableScroll": true
}
},
{
"path": "pages/home/CustomTab",
"type": "page"
},
{
"path": "pages/login/login",
"type": "page",
"style": {
"navigationStyle": "custom",
"disableScroll": true
}
},
{
"path": "pages/mine/mine",
"type": "page"
},
{
"path": "pages/notification/notification",
"type": "page"
},
{
"path": "pages/reset-password/reset-password",
"type": "page",
"style": {
"navigationStyle": "custom",
"disableScroll": true
}
},
{
"path": "pages/workbench/workbench",
"type": "page"
}
],
"subPackages": []
}
}

View File

@ -1,36 +0,0 @@
<route lang="json5">
{
style: {
navigationBarTitleText: '关于'
}
}
</route>
<template>
<view
class="bg-white overflow-hidden pt-2 px-4"
:style="{ marginTop: safeAreaInsets?.top + 'px' }"
>
<view class="text-center text-3xl mt-8">
鸽友们好我是
<text class="text-red-500">菲鸽</text>
</view>
<RequestComp />
<UploadComp />
</view>
</template>
<script lang="ts" setup>
import RequestComp from './components/request.vue'
import UploadComp from './components/upload.vue'
//
const { safeAreaInsets } = uni.getSystemInfoSync()
</script>
<style lang="scss" scoped>
.test-css {
// mt-4=>1rem=>16px;
margin-top: 16px;
}
</style>

View File

@ -1,56 +0,0 @@
<route lang="json5">
{
layout: 'demo',
style: {
navigationBarTitleText: '请求'
}
}
</route>
<template>
<view class="p-6 text-center">
<view class="my-2">使用的是 laf 云后台</view>
<view class="text-green-400">我的推荐码可以获得佣金</view>
<!-- #ifdef H5 -->
<view class="my-2">
<a class="my-2" :href="recommendUrl" target="_blank">{{ recommendUrl }}</a>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="my-2 text-left text-sm">{{ recommendUrl }}</view>
<!-- #endif -->
<!-- http://localhost:9000/#/pages/index/request -->
<wd-button @click="run" class="my-6">发送请求</wd-button>
<view class="h-12">
<view v-if="loading">loading...</view>
<block v-else>
<view class="text-xl">请求数据如下</view>
<view class="text-green leading-8">{{ JSON.stringify(data) }}</view>
</block>
</view>
<wd-button type="error" @click="reset" class="my-6" :disabled="!data">重置数据</wd-button>
</view>
</template>
<script lang="ts" setup>
import { getFooAPI, postFooAPI, IFooItem } from '@/service/index/foo'
const recommendUrl = ref('http://laf.run/signup?code=ohaOgIX')
// const initialData = {
// name: 'initialData',
// id: '1234',
// }
const initialData = undefined
// Service
const { loading, error, data, run } = useRequest<IFooItem>(() => getFooAPI('菲鸽'), {
immediate: true,
initialData
})
const reset = () => {
data.value = initialData
}
</script>

View File

@ -1,30 +0,0 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '上传-状态一体化'
}
}
</route>
<template>
<view class="p-4 text-center">
<wd-button @click="run">选择图片并上传</wd-button>
<view v-if="loading" class="text-blue h-10">上传...</view>
<template v-else>
<view class="m-2">上传后返回的接口数据</view>
<view class="m-2">{{ data }}</view>
<view class="h-80 w-full">
<image v-if="data" :src="data || data" mode="scaleToFill" />
</view>
</template>
</view>
</template>
<script lang="ts" setup>
const { loading, data, run } = useUpload({ user: '菲鸽' })
</script>
<style lang="scss" scoped>
//
</style>

100
src/pages/code/code.vue Normal file
View File

@ -0,0 +1,100 @@
<route lang="json5">
{
style: {
navigationStyle: 'custom',
disableScroll: true
}
}
</route>
<template>
<view>
<TopNavigation></TopNavigation>
<view class="text-6 ml-4 pt-7 custom-color-black font-bold">请输入验证码</view>
<view class="text-3.5 ml-4 mt-2 mb-4 custom-color-grey">
<view>已发送验证码至{{ maskedPhone }}</view>
<view v-if="countDown > 0">
如未收到请
<text class="text-red">{{ countDown }}s</text>
后重新获取
</view>
<view v-else class="custom-color-blue" @click="reset">重新获取</view>
</view>
<view class="pt-8">
<wd-password-input
v-model="code"
:gutter="10"
:mask="false"
:focused="showKeyboard"
@focus="showKeyboard = true"
/>
<wd-number-keyboard
v-model="code"
v-model:visible="showKeyboard"
:maxlength="6"
@blur="showKeyboard = false"
/>
</view>
</view>
</template>
<script setup lang="ts">
const phone = ref<string>('')
const code = ref<string>('')
const countDown = ref<number>(60)
const showKeyboard = ref<boolean>(true)
const type = ref<string>('')
onLoad(async options => {
phone.value = options.phone
type.value = options.type
countDownStart()
})
watch(code, newVal => {
if (newVal.length === 6) {
//
if (type.value === 'login') {
console.log('login')
} else if (type.value === 'reset-password') {
uni.navigateTo({
url: `/pages/reset-password/reset-password`
})
}
}
})
const reset = () => {
countDownStart()
}
const maskedPhone = computed(() => {
if (phone.value.length === 11) {
return phone.value.slice(0, 3) + '****' + phone.value.slice(7)
}
return phone.value
})
const countDownStart = () => {
countDown.value = 60
const timestamp = new Date().getTime()
const timer = setInterval(() => {
if (countDown.value > 0) {
countDown.value = 60 - Math.floor((new Date().getTime() - timestamp) / 1000)
} else {
clearInterval(timer)
}
}, 1000)
}
</script>
<style scoped lang="scss">
:deep(.wd-password-input__item) {
border: #b8b8b8 solid 2rpx;
border-radius: 12rpx;
}
</style>

View File

@ -0,0 +1,59 @@
<route lang="json5">
{
style: {
navigationStyle: 'custom',
disableScroll: true
}
}
</route>
<template>
<view>
<TopNavigation></TopNavigation>
<view class="text-6 ml-4 pt-7 custom-color-black font-bold">请输入手机号</view>
<view class="p-5">
<wd-input
v-model="phone"
:maxlength="11"
clearable
placeholder="请输入手机号码"
placeholderClass="text-4 custom-color-grey"
size="large"
type="number"
@change="phoneChange"
/>
</view>
</view>
<view class="p-5">
<wd-button :block="true" :disabled="!phonePass" :round="false" size="large" @click="codeLogin">
获取验证码
</wd-button>
</view>
</template>
<script setup lang="ts">
import { phoneRegExp } from '@/constants/regular-expressions'
const phone = ref<string>('')
const phonePass = computed(() => phoneRegExp.test(phone.value))
onLoad(options => {
if (options.phone) {
phone.value = options.phone
}
})
const codeLogin = () => {
if (!phonePass.value) {
return
}
uni.navigateTo({
url: `/pages/code/code?phone=${phone.value}&type=reset-password`
})
}
const phoneChange = (value: string) => {
phone.value = value
}
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,44 @@
<template>
<view class="m-2 p-2 bg-white rounded-2">
<wd-tabs v-model="tab" auto-line-width swipeable animated>
<wd-tab v-for="(item, index) in list" :key="index" :title="item.title" :name="index">
<view class="flex flex-wrap">
<view
v-for="subItem in item.list"
:key="subItem.id"
class="text-center my-2 w-21.4375"
@click="clickItem(subItem)"
>
<image :src="subItem.icon" class="w-7 h-7 m-a"></image>
<view class="text-3 mt-2">{{ subItem.title }}</view>
</view>
</view>
</wd-tab>
</wd-tabs>
</view>
</template>
<script setup lang="ts">
const tab = ref<number>(0)
const props = defineProps({
index: {
type: Number,
default: 0
},
list: {
type: Array<HomeTab>,
required: true
}
})
const emits = defineEmits(['clickItem'])
onMounted(() => {
tab.value = props.index
})
const clickItem = (item: HomeTabItem) => {
emits('clickItem', item)
}
</script>

254
src/pages/home/home.vue Normal file
View File

@ -0,0 +1,254 @@
<route lang="json5" type="home">
{
style: {
navigationStyle: 'custom',
navigationBarTitleText: '首页'
}
}
</route>
<template>
<view class="custom-bg-default">
<TopNavigation :title="appName"></TopNavigation>
<TopNavigation mini :title="appName"></TopNavigation>
<button class="mt-12" @click="toLogin">去登录</button>
<CustomTab class="mt-10" :list="featuresList" :index="2" @clickItem="clickItem"></CustomTab>
<view class="pt-10">
<CustomTabBar :list="list" :default-index="1" @change="change"></CustomTabBar>
</view>
</view>
</template>
<script lang="ts" setup>
import { useUserStore } from '@/store'
import CustomTab from '@/pages/home/CustomTab.vue'
const $user = useUserStore()
const appName = import.meta.env.VITE_APP_TITLE
const list = ref<Array<TabBarItem>>([
{
title: '首页',
isDot: true,
icon: 'home'
},
{
title: '我的',
value: 0,
icon: '/static/tabbar/home.png'
},
{
title: '最大值',
value: 200,
icon: '/static/tabbar/home.png'
},
{
title: '客服',
value: 3,
icon: '/static/tabbar/home.png'
}
])
const featuresList = ref<Array<HomeTab>>([
{
title: '视频',
list: [
{
id: 0,
title: '视频中心',
icon: '/static/logo.png'
},
{
id: 1,
title: '视频中心',
icon: '/static/logo.png'
},
{
id: 2,
title: '视频中心',
icon: '/static/logo.png'
},
{
id: 3,
title: '视频中心',
icon: '/static/logo.png'
},
{
id: 4,
title: '视频中心',
icon: '/static/logo.png'
}
]
},
{
title: '音频',
list: [
{
id: 0,
title: '音频中心',
icon: '/static/logo.png'
},
{
id: 1,
title: '音频中心',
icon: '/static/logo.png'
},
{
id: 2,
title: '音频中心',
icon: '/static/logo.png'
}
]
},
{
title: '图文',
list: [
{
id: 0,
title: '图文中心',
icon: '/static/logo.png'
},
{
id: 1,
title: '图文中心',
icon: '/static/logo.png'
},
{
id: 2,
title: '图文中心',
icon: '/static/logo.png'
}
]
},
{
title: '直播',
list: [
{
id: 0,
title: '直播中心',
icon: '/static/logo.png'
},
{
id: 1,
title: '直播中心',
icon: '/static/logo.png'
},
{
id: 2,
title: '直播中心',
icon: '/static/logo.png'
}
]
},
{
title: '其他',
list: [
{
id: 0,
title: '其他中心',
icon: '/static/logo.png'
},
{
id: 1,
title: '其他中心',
icon: '/static/logo.png'
},
{
id: 2,
title: '其他中心',
icon: '/static/logo.png'
}
]
},
{
title: '其他',
list: [
{
id: 0,
title: '其他中心',
icon: '/static/logo.png'
},
{
id: 1,
title: '其他中心',
icon: '/static/logo.png'
},
{
id: 2,
title: '其他中心',
icon: '/static/logo.png'
}
]
},
{
title: '其他',
list: [
{
id: 0,
title: '其他中心',
icon: '/static/logo.png'
},
{
id: 1,
title: '其他中心',
icon: '/static/logo.png'
},
{
id: 2,
title: '其他中心',
icon: '/static/logo.png'
}
]
},
{
title: '其他',
list: [
{
id: 0,
title: '其他中心',
icon: '/static/logo.png'
},
{
id: 1,
title: '其他中心',
icon: '/static/logo.png'
},
{
id: 2,
title: '其他中心',
icon: '/static/logo.png'
}
]
}
])
// uni API
onLoad(() => {
$user.setUserInfo({ nickname: '1', avatar: '1', token: '1' })
console.log($user.userInfo.nickname)
})
const change = data => {
console.log(data.value)
if (data.value === 3) {
list.value[data.value].value = 0
}
}
const toLogin = () => {
uni.navigateTo({
url: '/pages/login/login'
})
}
const clickItem = item => {
console.log(item)
}
</script>
<style lang="scss">
page {
background: #f3f5fa;
}
</style>

View File

@ -1,56 +0,0 @@
<!-- 使用 type="home" 属性设置首页其他页面不需要设置默认为page推荐使用json5更强大且允许注释 -->
<route lang="json5" type="home">
{
style: {
navigationStyle: 'custom',
navigationBarTitleText: '首页'
}
}
</route>
<template>
<view
class="bg-white overflow-hidden pt-2 px-4"
:style="{ marginTop: safeAreaInsets?.top + 'px' }"
>
<view class="mt-12">
<image src="/static/logo.svg" alt="" class="w-28 h-28 block mx-auto" />
</view>
<view class="text-center text-4xl main-title-color mt-4">unibest</view>
<view class="text-center text-2xl mt-2 mb-8">最好用的 uniapp 开发模板</view>
<view class="text-justify max-w-100 m-auto text-4 indent mb-2">{{ description }}</view>
<view class="text-center mt-8">
当前平台是
<text class="text-green-500">{{ PLATFORM.platform }}</text>
</view>
<view class="text-center mt-4">
模板分支是
<text class="text-green-500">base</text>
</view>
</view>
</template>
<script lang="ts" setup>
import PLATFORM from '@/utils/platform'
defineOptions({
name: 'Home'
})
//
const { safeAreaInsets } = uni.getSystemInfoSync()
const author = ref('菲鸽')
const description = ref(
'unibest 是一个集成了多种工具和技术的 uniapp 开发模板,由 uniapp + Vue3 + Ts + Vite4 + UnoCss + UniUI + VSCode 构建,模板具有代码提示、自动格式化、统一配置、代码片段等功能,并内置了许多常用的基本组件和基本功能,让你编写 uniapp 拥有 best 体验。'
)
// uni API
onLoad(() => {
console.log(author)
})
</script>
<style>
.main-title-color {
color: #d14328;
}
</style>

242
src/pages/login/login.vue Normal file
View File

@ -0,0 +1,242 @@
<route lang="json5">
{
style: {
navigationStyle: 'custom',
disableScroll: true
}
}
</route>
<template>
<view class="bg-white">
<TopNavigation v-if="systemInfo && systemInfo?.uniPlatform === 'app'"></TopNavigation>
<swiper
v-if="systemInfo"
:circular="true"
:current="index"
:disable-touch="true"
:style="{
height: 'calc(100vh - ' + (systemInfo.safeAreaInsets?.top + 48) + 'px)'
}"
>
<swiper-item v-for="item in 4" :key="item">
<view
:class="[systemInfo && systemInfo?.uniPlatform === 'app' ? 'pt-5' : 'pt-20']"
class="pos-relative"
>
<view class="text-6 ml-4 mt-2 custom-color-black font-bold">欢迎使用{{ appName }}</view>
<view class="text-3 ml-4 mt-2 mb-4 custom-color-grey">
未注册手机号验证号将创建{{ appName }}通行证
</view>
<view class="p-5">
<wd-input
v-model="phone"
:maxlength="11"
clearable
placeholder="请输入手机号码"
placeholderClass="text-4 custom-color-grey"
size="large"
type="number"
@change="phoneChange"
/>
<wd-input
v-if="index % 2 === 1"
v-model="password"
:maxlength="20"
class="mt-6"
clearable
placeholder="请输入密码"
placeholderClass="text-4 custom-color-grey"
show-password
size="large"
@change="passwordChange"
/>
</view>
<view v-if="index % 2 === 0" class="p-5">
<wd-button
:block="true"
:disabled="!phonePass"
:round="false"
size="large"
@click="codeLogin"
>
获取验证码
</wd-button>
</view>
<view v-else class="p-5">
<wd-button
@click="passwordLogin"
:block="true"
:disabled="!(passwordPass && phonePass)"
size="large"
:round="false"
>
登录
</wd-button>
</view>
<view
class="pt-2 pb-1 pl-5 pr-5 flex flex-items-center flex-justify-center custom-color-grey"
>
<view v-if="index % 2 === 0" @click="index = index + 1">密码登录</view>
<view v-if="index % 2 === 1" @click="index === 3 ? (index = 0) : (index = index + 1)">
验证码登录
</view>
<view v-if="index % 2 === 1" class="mx-4">|</view>
<view v-if="index % 2 === 1" @click="resetPassword">忘记密码</view>
</view>
<view class="pos-fixed bottom-1 pb-safe">
<view v-if="systemInfo" class="text-center text-3">
<view>{{ systemInfo?.uniPlatform === 'app' ? '第三方登录方式' : '扫码登录' }}</view>
<image
v-if="systemInfo?.uniPlatform === 'app'"
class="h-10 w-10 mt-2"
mode="aspectFit"
src="/static/images/icon_wechat.png"
></image>
<image
v-if="systemInfo?.uniPlatform !== 'app'"
class="h-10 w-10 mt-2"
mode="aspectFit"
src="/static/images/icon_scan_code.png"
></image>
</view>
<view class="flex p-5">
<view class="pt-0.8">
<wd-checkbox
:modelValue="consentAgreement"
class="flex-shrink-0"
shape="circle"
size="larger"
@click="changeConsentAgreement"
></wd-checkbox>
</view>
<view class="ml-1.5 text-3">
<text>
登录后即同意
<text class="custom-color-blue">天翼账号认证服务条款</text>
<text class="custom-color-blue">{{ appName }}登录政策</text>
<text class="custom-color-blue">{{ appName }}服务协议</text>
<text class="custom-color-blue">{{ appName }}隐私政策</text>
并授权{{ appName }}获取本机号码及{{ appName }}通行证账号信息进行统一管理
</text>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
</view>
<Modal
ref="agreementModal"
:title="`欢迎使用${appName}`"
confirm-text="同意并继续"
@confirm="confirm"
>
<text>
登录后即同意
<text class="custom-color-blue">天翼账号认证服务条款</text>
<text class="custom-color-blue">{{ appName }}登录政策</text>
<text class="custom-color-blue">{{ appName }}服务协议</text>
<text class="custom-color-blue">{{ appName }}隐私政策</text>
</text>
</Modal>
</template>
<script lang="ts" setup>
import { useBasicStore } from '@/store'
import { passwordRegExp, phoneRegExp } from '@/constants/regular-expressions'
const $basic = useBasicStore()
const appName = import.meta.env.VITE_APP_TITLE
const phone = ref<string>('')
const password = ref<string>('')
const consentAgreement = ref<boolean>(false)
const index = ref<number>(0)
const systemInfo = ref<GetSystemInfoResult>(null)
const phonePass = computed(() => phoneRegExp.test(phone.value))
const passwordPass = computed(() => passwordRegExp.test(password.value))
const agreementModal = ref(null)
const type = ref('code')
onMounted(async () => {
systemInfo.value = await $basic.getSystemInfo()
})
const passwordLogin = () => {
if (!phonePass.value || !passwordPass.value) {
return
}
if (!consentAgreement.value) {
type.value = 'password'
agreementModal.value.showModal()
return
}
console.log('登录')
}
const codeLogin = () => {
if (!phonePass.value) {
return
}
if (!consentAgreement.value) {
type.value = 'code'
agreementModal.value.showModal()
return
}
uni.navigateTo({
url: `/pages/code/code?phone=${phone.value}&type=login`
})
}
const resetPassword = () => {
if (!consentAgreement.value) {
type.value = 'reset'
agreementModal.value.showModal()
return
}
let params = ''
if (phonePass.value) {
params = `?phone=${phone.value}`
}
uni.navigateTo({
url: `/pages/get-code/get-code${params}`
})
}
const confirm = () => {
consentAgreement.value = true
if (type.value === 'code') {
codeLogin()
} else if (type.value === 'password') {
passwordLogin()
} else if (type.value === 'reset') {
resetPassword()
}
}
const phoneChange = (value: string) => {
phone.value = value
}
const passwordChange = (value: string) => {
password.value = value
}
const changeConsentAgreement = () => {
consentAgreement.value = !consentAgreement.value
}
</script>

7
src/pages/mine/mine.vue Normal file
View File

@ -0,0 +1,7 @@
<template>
<view></view>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,7 @@
<template>
<view></view>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,74 @@
<route lang="json5">
{
style: {
navigationStyle: 'custom',
disableScroll: true
}
}
</route>
<template>
<view>
<TopNavigation></TopNavigation>
<view class="text-6 ml-4 pt-7 custom-color-black font-bold">请输入手机号</view>
<view class="p-5">
<wd-input
v-model="password"
:maxlength="20"
class="mt-6"
clearable
placeholder="请输入新密码"
placeholderClass="text-4 custom-color-grey"
show-password
size="large"
@change="passwordChange"
/>
<wd-input
v-model="confirmPassword"
:maxlength="20"
class="mt-6"
clearable
placeholder="再次输入新密码"
placeholderClass="text-4 custom-color-grey"
show-password
size="large"
@change="confirmPasswordChange"
/>
</view>
<view class="bg-[#eeeff4] p-2 mx-5 rounded-2">
<view class="p-2">
<view class="text-3 color-[#525458]">密码为8-20至少含数字/字母/字符中的2种</view>
</view>
</view>
<view class="p-5">
<wd-button
:block="true"
:disabled="!(passwordPass && confirmPasswordPass && password === confirmPassword)"
:round="false"
size="large"
@click="codeLogin"
>
提交
</wd-button>
</view>
</view>
</template>
<script setup lang="ts">
import { passwordRegExp } from '@/constants/regular-expressions'
const password = ref<string>('')
const confirmPassword = ref<string>('')
const passwordPass = computed(() => passwordRegExp.test(password.value))
const confirmPasswordPass = computed(() => password.value === confirmPassword.value)
const passwordChange = (value: string) => {
password.value = value
}
const confirmPasswordChange = (value: string) => {
confirmPassword.value = value
}
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,7 @@
<template>
<view></view>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss"></style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
src/static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

22
src/store/basic.ts Normal file
View File

@ -0,0 +1,22 @@
import { defineStore } from 'pinia'
import GetSystemInfoResult = UniNamespace.GetSystemInfoResult
export const useBasicStore = defineStore('basic', () => {
// 系统信息
let systemInfo: GetSystemInfoResult = uni.getSystemInfoSync()
// 获取系统信息
const getSystemInfo = (): Promise<GetSystemInfoResult> => {
return new Promise(resolve => {
if (systemInfo.uniPlatform) {
resolve(systemInfo)
} else {
systemInfo = uni.getSystemInfoSync()
resolve(systemInfo)
}
})
}
return {
getSystemInfo
}
})

View File

@ -15,3 +15,4 @@ export default store
// 模块统一导出
export * from './user'
export * from './basic'

View File

@ -79,12 +79,10 @@ declare global {
const useCssVars: typeof import('vue')['useCssVars']
const useId: typeof import('vue')['useId']
const useModel: typeof import('vue')['useModel']
const useNavbarWeixin: (typeof import('../hooks/useNavbarWeixin'))['default']
const useRequest: typeof import('../hooks/useRequest')['default']
const useSlots: typeof import('vue')['useSlots']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const useUpload: typeof import('../hooks/useUpload')['default']
const useUpload2: typeof import('../hooks/useUpload2')['default']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']

View File

@ -4,8 +4,15 @@
// Generated by vite-plugin-uni-pages
interface NavigateToOptions {
url: "/pages/index/index" |
"/pages/about/about";
url: "/pages/home/home" |
"/pages/code/code" |
"/pages/get-code/get-code" |
"/pages/home/CustomTab" |
"/pages/login/login" |
"/pages/mine/mine" |
"/pages/notification/notification" |
"/pages/reset-password/reset-password" |
"/pages/workbench/workbench";
}
interface RedirectToOptions extends NavigateToOptions {}

View File

@ -1,8 +1,8 @@
// 全局要用的类型放到这里
type IResData<T> = {
code: number
msg: string
errorCode: number
errorMsg: string
data: T
}
@ -18,12 +18,24 @@ type IUniUploadFileOptions = {
type IUserInfo = {
nickname?: string
avatar?: string
/** 微信的 openid非微信没有这个字段 */
openid?: string
token?: string
}
enum TestEnum {
A = 'a',
B = 'b'
type TabBarItem = {
icon: string
title: string
isDot?: boolean
value?: number
}
type HomeTabItem = {
icon: string
title: string
id: number
}
type HomeTab = {
title: string
list: HomeTabItem[]
}

View File

@ -13,20 +13,20 @@ export const http = <T>(options: CustomRequestOptions) => {
success(res) {
// 状态码 2xx参考 axios 的设计
if (res.statusCode >= 200 && res.statusCode < 300) {
// 2.1 提取核心数据 res.data
resolve(res.data as IResData<T>)
} else if (res.statusCode === 401) {
// 401错误 -> 清理用户信息,跳转到登录页
// userStore.clearUserInfo()
// uni.navigateTo({ url: '/pages/login/login' })
reject(res)
if (res.data.errorCode === 403) {
// 401错误 -> 清理用户信息,跳转到登录页
// userStore.clearUserInfo()
// uni.navigateTo({ url: '/pages/login/login' })
reject(res)
} else {
resolve(res.data as IResData<T>)
}
} else {
// 其他错误 -> 根据后端错误信息轻提示
!options.hideErrorToast &&
uni.showToast({
icon: 'none',
title: (res.data as IResData<T>).msg || '请求错误'
})
uni.showToast({
icon: 'none',
title: '网络错误,请重试'
})
reject(res)
}
},
@ -34,7 +34,7 @@ export const http = <T>(options: CustomRequestOptions) => {
fail(err) {
uni.showToast({
icon: 'none',
title: '网络错误,换个网络试试'
title: '网络错误,请重试'
})
reject(err)
}

View File

@ -1,9 +1,3 @@
/*
* @Author:
* @Date: 2024-03-28 19:13:55
* @Last Modified by: 菲鸽
* @Last Modified time: 2024-03-28 19:24:55
*/
export const platform = __UNI_PLATFORM__
export const isH5 = __UNI_PLATFORM__ === 'h5'
export const isApp = __UNI_PLATFORM__ === 'app'