修改环境变量配置,app目录-->uniapp

This commit is contained in:
Jason
2022-09-30 10:23:58 +08:00
parent 9b5b625680
commit 0cc7c8e7b9
251 changed files with 16 additions and 9 deletions

View File

View File

@@ -0,0 +1,65 @@
<template>
<navigator :url="`/pages/news_detail/news_detail?id=${newsId}`">
<view class="news-card flex bg-white px-[20rpx] py-[32rpx]">
<view class="mr-[20rpx]" v-if="item.image">
<u-image :src="item.image" width="240" height="180"></u-image>
</view>
<view class="news-card-content flex flex-col justify-between flex-1">
<view class="news-card-content-title text-lg font-medium">{{ item.title }}</view>
<view class="news-card-content-intro text-gray-400 text-sm mt-[16rpx]">{{
item.intro
}}</view>
<view class="text-muted text-xs w-full flex justify-between mt-[12rpx]">
<view>{{ item.createTime }}</view>
<view class="flex items-center">
<image
src="/static/images/icon/icon_visit.png"
class="w-[30rpx] h-[30rpx]"
></image>
<view class="ml-[10rpx]">{{ item.visit }}</view>
</view>
</view>
</view>
</view>
</navigator>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const props = withDefaults(
defineProps<{
item: any
newsId: number
}>(),
{
item: {},
newsId: ''
}
)
</script>
<style lang="scss" scoped>
.news-card {
border-bottom: 1px solid #f8f8f8;
&-content {
&-title {
-webkit-line-clamp: 2;
overflow: hidden;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
}
&-intro {
-webkit-line-clamp: 1;
overflow: hidden;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
}
}
}
</style>

View File

@@ -0,0 +1,84 @@
<template>
<view
:class="{ active, inactive: !active, tab: true }"
:style="shouldShow ? '' : 'display: none;'"
>
<slot v-if="shouldRender"></slot>
</view>
</template>
<script lang="ts" setup>
import { ref, provide, inject, watch, computed, onMounted, getCurrentInstance } from 'vue'
const props = withDefaults(
defineProps<{
dot?: boolean | string
name?: boolean | string
info?: any
}>(),
{
dot: false,
name: ''
}
)
const active = ref<boolean>(false)
const shouldShow = ref<boolean>(false)
const shouldRender = ref<boolean>(false)
const inited = ref(undefined)
const updateTabs: any = inject('updateTabs')
const handleChange: any = inject('handleChange')
const updateRender = (value) => {
inited.value = inited.value || value
active.value = value
shouldRender.value = inited.value!
shouldShow.value = value
}
const update = () => {
if (updateTabs) {
updateTabs()
}
}
const instance = getCurrentInstance()
console.log(instance)
handleChange(instance?.props, updateRender)
onMounted(() => {
update()
})
const changeData = computed(() => {
const { dot, info } = props
return {
dot,
info
}
})
watch(
() => changeData.value,
() => {
update()
}
)
watch(
() => props.name,
(val) => {
update()
}
)
</script>
<style>
.tab.active {
height: auto;
}
.tab.inactive {
height: 0;
overflow: visible;
}
</style>

View File

@@ -0,0 +1,38 @@
<template>
<u-tabbar
v-model="current"
v-bind="tabbarStyle"
:list="tabbarList"
@change="handleChange"
:hide-tab-bar="false"
></u-tabbar>
</template>
<script lang="ts" setup>
import { useAppStore } from '@/stores/app'
import { navigateTo } from '@/utils/util'
import { computed, ref } from 'vue'
const current = ref()
const appStore = useAppStore()
const tabbarList = computed(() => {
return appStore.getTabbarConfig.map((item: any) => {
const link = JSON.parse(item.link)
return {
iconPath: item.unselected,
selectedIconPath: item.selected,
text: item.name,
link,
pagePath: link.path
}
})
})
const tabbarStyle = computed(() => ({
activeColor: appStore.getStyleConfig.selectedColor,
inactiveColor: appStore.getStyleConfig.defaultColor
}))
const handleChange = (index: number) => {
const selectTab = tabbarList.value[index]
navigateTo(selectTab.link, 'reLaunch')
}
</script>

View File

@@ -0,0 +1,437 @@
<template>
<view class="tabs">
<u-sticky :enable="isFixed" :bg-color="stickyBgColor" :offset-top="top" :h5-nav-height="0">
<view
:id="id"
:style="{
background: bgColor
}"
>
<scroll-view
:style="{ height: height + 'rpx' }"
scroll-x
class="scroll-view"
:scroll-left="scrollLeft"
scroll-with-animation
>
<view class="scroll-box" :class="{ 'tabs-scorll-flex': !isScroll }">
<view
class="tab-item line1"
:id="'tab-item-' + index"
v-for="(item, index) in list"
:key="index"
@tap="clickTab(index)"
:style="[tabItemStyle(index)]"
>
<u-badge
:count="item[count] || item['dot'] || 0"
:offset="offset"
size="mini"
></u-badge>
{{ item[name] || item['name'] }}
</view>
<view v-if="showBar" class="tab-bar" :style="[tabBarStyle]"></view>
</view>
</scroll-view>
</view>
</u-sticky>
<view
class="tab-content"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchcancel="onTouchEnd"
@touchend="onTouchEnd"
>
<!-- <view class="tab-track" :class="{'tab-animated': animated}" :style="[trackStyle]"> -->
<view>
<slot></slot>
</view>
<!-- </view> -->
</view>
</view>
</template>
<script lang="ts" setup>
import { getRect } from '@/utils/util'
import {
ref,
reactive,
computed,
watch,
provide,
nextTick,
onMounted,
getCurrentInstance
} from 'vue'
import { useTouch } from '@/hooks/useTouch'
// Touch 钩子
const { touch, resetTouchStatus, touchStart, touchMove } = useTouch()
const emit = defineEmits<{
(event: 'change', value: number): void
}>()
const props = withDefaults(
defineProps<{
isScroll?: boolean // 导航菜单是否需要滚动如只有2或者3个的时候就不需要滚动了此时使用flex平分tab的宽度
current?: number | string // 当前活动tab的索引
height?: number | string // 导航栏的高度和行高
fontSize?: number | string // 字体大小
duration?: number | string // 过渡动画时长, 单位ms
activeColor?: number | string // 选中项的主题颜色
inactiveColor?: number | string // 未选中项的颜色
barWidth?: number | string // 菜单底部移动的bar的宽度单位rpx
barHeight?: number // 移动bar的高度
gutter?: number | string // 单个tab的左或有内边距左右相同
bgColor?: number | string // 导航栏的背景颜色
name?: string // 读取传入的数组对象的属性(tab名称)
count?: string // 读取传入的数组对象的属性(徽标数)
offset?: number[] // 徽标数位置偏移
bold?: boolean // 活动tab字体是否加粗
activeItemStyle?: any // 当前活动tab item的样式
showBar?: boolean // 是否显示底部的滑块
barStyle?: any // 底部滑块的自定义样式
itemWidth?: string // 标签的宽度
isFixed?: boolean // 吸顶是否固定
top?: number | string // 吸顶顶部距离
stickyBgColor?: string // 吸顶颜色
swipeable: boolean // 是否允许滑动切换
// animated: boolean // 切换动画
}>(),
{
isScroll: true,
current: 0,
height: 80,
fontSize: 28,
duration: 0.3,
activeColor: '#2073F4',
inactiveColor: '#333',
barWidth: 40,
barHeight: 4,
gutter: 30,
bgColor: '#FFFFFF',
name: 'name',
count: 'count',
offset: [5, 20],
bold: true,
activeItemStyle: {},
showBar: true,
barStyle: {},
itemWidth: 'auto',
isFixed: false,
top: 0,
stickyBgColor: '#FFFFFF',
swipeable: true
// animated: true
}
)
const list = ref<any>([])
const childrens = ref<any>([])
const scrollLeft = ref<number>(0) // 滚动scroll-view的左边滚动距离
const tabQueryInfo = ref<any>([]) // 存放对tab菜单查询后的节点信息
const componentWidth = ref<number>(0) // 屏幕宽度单位为px
const scrollBarLeft = ref<number>(0) // 移动bar需要通过translateX()移动的距离
const parentLeft = ref<number>(0) // 父元素(tabs组件)到屏幕左边的距离
const id = ref<string>('cu-tab') // id值
const currentIndex = ref<any>(props.current)
const barFirstTimeMove = ref<boolean>(true) // 滑块第一次移动时(页面刚生成时),无需动画,否则给人怪异的感觉
const swiping = ref<boolean>(false)
//@ts-ignore
const ctx = getCurrentInstance()
// 监听tab的变化重新计算tab菜单的布局信息因为实际使用中菜单可能是通过
// 后台获取的如新闻app顶部的菜单获取返回需要一定时间所以list变化时重新获取布局信息
watch(
() => list.value,
async (n, o) => {
// list变动时重制内部索引否则可能导致超出数组边界的情况
if (!barFirstTimeMove.value && n.length !== o.length) {
currentIndex.value = 0
}
// 用$nextTick等待视图更新完毕后再计算tab的局部信息否则可能因为tab还没生成就获取就会有问题
await nextTick()
init()
}
)
watch(
() => props.current,
(nVal, oVal) => {
// 视图更新后再执行移动操作、
nextTick(() => {
currentIndex.value = nVal
scrollByIndex()
})
},
{ immediate: true }
)
// 移动bar的样式
const tabBarStyle = computed(() => {
const style = {
width: props.barWidth + 'rpx',
transform: `translate(${scrollBarLeft.value}px, -100%)`,
// 滑块在页面渲染后第一次滑动时,无需动画效果
'transition-duration': `${barFirstTimeMove.value ? 0 : props.duration}s`,
'background-color': props.activeColor,
height: props.barHeight + 'rpx',
opacity: barFirstTimeMove.value ? 0 : 1,
// 设置一个很大的值,它会自动取能用的最大值,不用高度的一半,是因为高度可能是单数,会有小数出现
'border-radius': `${props.barHeight / 2}px`
}
Object.assign(style, props.barStyle)
return style
})
// tab的样式
const tabItemStyle = computed(() => {
return (index) => {
let style: any = {
height: props.height + 'rpx',
'line-height': props.height + 'rpx',
'font-size': props.fontSize + 'rpx',
padding: props.isScroll ? `0 ${props.gutter}rpx` : '',
flex: props.isScroll ? 'auto' : '1',
width: `${props.itemWidth}rpx`
}
// 字体加粗
if (index == currentIndex.value && props.bold) style.fontWeight = 'bold'
if (index == currentIndex.value) {
style.color = props.activeColor
// 给选中的tab item添加外部自定义的样式
style = Object.assign(style, props.activeItemStyle)
} else {
style.color = props.inactiveColor
}
return style
}
})
// const trackStyle = computed(() => {
// if (!props.animated) return ''
// return {
// left: -100 * currentIndex.value + '%',
// 'transition-duration': props.duration + 's',
// '-webkit-transition-duration': props.duration + 's',
// }
// })
const updateTabs = () => {
list.value = childrens.value.map((item) => {
const { name, dot, active, inited } = item.event
const { updateRender } = item
return {
name,
dot,
active,
inited,
updateRender
}
})
// nextTick(() => {
// init()
// })
}
// 设置一个init方法方便多处调用
const init = async () => {
// 获取tabs组件的尺寸信息
const tabRect = await getRect('#' + id.value, false, ctx)
// tabs组件距离屏幕左边的宽度
parentLeft.value = tabRect.left
// tabs组件的宽度
componentWidth.value = tabRect.width
getTabRect()
}
// 点击某一个tab菜单
const clickTab = (index) => {
// 点击当前活动tab不触发事件
if (index == currentIndex.value) return
nextTick(() => {
currentIndex.value = index
scrollByIndex()
})
// 发送事件给父组件
emit('change', index)
}
// 查询tab的布局信息
const getTabRect = () => {
// 创建节点查询
const query: any = uni.createSelectorQuery().in(ctx)
// 历遍所有tab这里是执行了查询最终使用exec()会一次性返回查询的数组结果
for (let i = 0; i < list.value.length; i++) {
// 只要size和rect两个参数
query.select(`#tab-item-${i}`).fields({
size: true,
rect: true
})
}
// 执行查询,一次性获取多个结果
query.exec((res) => {
tabQueryInfo.value = res
// 初始化滚动条和移动bar的位置
scrollByIndex()
})
}
// 滚动scroll-view让活动的tab处于屏幕的中间位置
const scrollByIndex = () => {
// 当前活动tab的布局信息有tab菜单的width和left(为元素左边界到父元素左边界的距离)等信息
const tabInfo = tabQueryInfo.value[currentIndex.value]
if (!tabInfo) return
// 活动tab的宽度
const tabWidth = tabInfo.width
// 活动item的左边到tabs组件左边的距离用item的left减去tabs的left
const offsetLeft = tabInfo.left - parentLeft.value
// 将活动的tabs-item移动到屏幕正中间实际上是对scroll-view的移动
const scrollLefts = offsetLeft - (componentWidth.value - tabWidth) / 2
scrollLeft.value = scrollLefts < 0 ? 0 : scrollLefts
// 当前活动item的中点点到左边的距离减去滑块宽度的一半即可得到滑块所需的移动距离
const left = tabInfo.left + tabInfo.width / 2 - parentLeft.value
// 计算当前活跃item到组件左边的距离
scrollBarLeft.value = left - uni.upx2px(props.barWidth) / 2
// 第一次移动滑块的时候barFirstTimeMove为true放到延时中将其设置false
// 延时是因为scrollBarLeft作用于computed计算时需要一个过程需否则导致出错
if (barFirstTimeMove.value == true) {
setTimeout(() => {
barFirstTimeMove.value = false
}, 100)
}
// 更新子组件的显示
childrens.value.forEach((item, ind) => {
const active = ind === currentIndex.value
if (active !== item.event.active || !item.event.inited) {
item.updateRender(active)
}
})
}
// 子组件调用此函数而产生的事件通信
const handleChange = (event, updateRender) => {
childrens.value.push({ event: event, updateRender })
}
// 手指触摸
const onTouchStart = (event) => {
if (!props.swipeable) return
swiping.value = true
touchStart(event)
}
// 手指滑动
const onTouchMove = (event) => {
if (!props.swipeable || !swiping.value) return
touchMove(event)
}
// 手指滑动结束
const onTouchEnd = () => {
if (!props.swipeable || !swiping.value) return
const minSwipeDistance = 50
if (touch.direction === 'horizontal' && touch.offsetX >= minSwipeDistance) {
let index,
len = list.value.length,
curIndex = currentIndex.value
if (touch.deltaX <= 0) {
curIndex >= len - 1 ? (index = 0) : (index = curIndex + 1)
} else {
curIndex <= 0 ? (index = len - 1) : (index = curIndex - 1)
}
nextTick(() => {
currentIndex.value = index
scrollByIndex()
})
// 发送事件给父组件
emit('change', index)
}
swiping.value = false
}
onMounted(() => {
updateTabs()
})
provide('handleChange', handleChange)
provide('updateTabs', updateTabs)
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
::-webkit-scrollbar,
::-webkit-scrollbar,
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.scroll-box {
height: 100%;
position: relative;
/* #ifdef MP-TOUTIAO */
white-space: nowrap;
/* #endif */
}
.tab-fixed {
position: sticky;
top: 0;
width: 100%;
}
/* #ifdef H5 */
// 通过样式穿透隐藏H5下scroll-view下的滚动条
scroll-view ::v-deep ::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.scroll-view {
width: 100%;
white-space: nowrap;
position: relative;
}
.tab-item {
position: relative;
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
text-align: center;
transition-property: background-color, color;
}
.tab-bar {
position: absolute;
bottom: 6rpx;
}
.tabs-scorll-flex {
display: flex;
justify-content: space-between;
}
// .tab-content {
// overflow: hidden;
// .tab-track {
// position: relative;
// width: 100%;
// height: 100%;
// }
// .tab-animated {
// display: flex;
// transition-property: left;
// }
// }
</style>

View File

@@ -0,0 +1,48 @@
<template>
<view
class="banner h-[340rpx] bg-white 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"
@click="handleClick(item.link)"
>
<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'
import { navigateTo } from '@/utils/util'
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
}
})
const { getImageUrl } = useAppStore()
const handleClick = (link: any) => {
navigateTo(link)
}
</script>
<style></style>

View File

@@ -0,0 +1,56 @@
<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(getImageUrl(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>

View File

@@ -0,0 +1,56 @@
<template>
<div class="my-service bg-white mx-[20rpx] mt-[20rpx] rounded-lg">
<div
v-if="content.title"
class="title px-[30rpx] py-[20rpx] font-medium text-xl 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]"
@click="handleClick(item.link)"
>
<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]"
@click="handleClick(item.link)"
>
<u-image width="48" height="48" :src="getImageUrl(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'
import { navigateTo } from '@/utils/util'
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
}
})
const { getImageUrl } = useAppStore()
const handleClick = (link: any) => {
navigateTo(link)
}
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,38 @@
<template>
<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]"
@click="handleClick(item.link)"
>
<u-image width="41px" height="41px" :src="getImageUrl(item.image)" alt="" />
<view class="mt-[14rpx]">{{ item.name }}</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { useAppStore } from '@/stores/app'
import { navigateTo } from '@/utils/util'
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
}
})
const handleClick = (link: any) => {
navigateTo(link)
}
const { getImageUrl } = useAppStore()
</script>
<style></style>

View File

@@ -0,0 +1,13 @@
<template>
<navigator
url="/pages/search/search"
class="search px-[24rpx] py-[14rpx] bg-white"
hover-class="none"
>
<u-search placeholder="请输入关键词搜索" disabled :show-action="false"></u-search>
</navigator>
</template>
<script setup lang="ts"></script>
<style></style>

View File

@@ -0,0 +1,49 @@
<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"
@click="handleClick(item.limk)"
>
<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'
import { navigateTo } from '@/utils/util'
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
}
})
const handleClick = (link: any) => {
navigateTo(link)
}
const { getImageUrl } = useAppStore()
</script>
<style></style>

View File

@@ -0,0 +1,60 @@
<template>
<view class="user-info flex px-[50rpx] justify-between py-[50rpx]">
<view
v-if="isLogin"
class="flex items-center"
@click="navigateTo('/pages/user_data/user_data')"
>
<u-avatar :src="user.avatar" :size="120"></u-avatar>
<view class="text-white ml-[20rpx]">
<view class="text-2xl">{{ user.nickname }}</view>
<view class="text-xs mt-[18rpx]" @click.stop="copy(user.username)">
账号{{ user.username }}
</view>
</view>
</view>
<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>
<view class="text-white text-3xl ml-[20rpx]">未登录</view>
</navigator>
<navigator v-if="isLogin" hover-class="none" url="/pages/user_set/user_set">
<u-icon name="setting" color="#fff" :size="48"></u-icon>
</navigator>
</view>
</template>
<script lang="ts" setup>
import { useCopy } from '@/hooks/useCopy'
const props = defineProps({
content: {
type: Object,
default: () => ({})
},
styles: {
type: Object,
default: () => ({})
},
user: {
type: Object,
default: () => ({})
},
isLogin: {
type: Boolean
}
})
const { copy } = useCopy()
const navigateTo = (url: string) => {
uni.navigateTo({
url
})
}
</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>