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

This commit is contained in:
windy
2022-09-09 18:45:13 +08:00
20 changed files with 1543 additions and 1476 deletions

View File

@@ -12,7 +12,7 @@ export function getDecorate(data: any) {
/**
* @description 热门搜索
* @return { Promise }
* @return { Promise }
*/
export function getHotSearch() {
return request.get({ url: '/hotSearch' })
@@ -21,8 +21,8 @@ export function getHotSearch() {
/**
* @description 搜索
* @param { string } keyword 关键词
* @return { Promise }
* @return { Promise }
*/
export function getSearch(data: { keyword: string, pageNo: number, pageSize: number }) {
export function getSearch(data: { keyword: string; pageNo: number; pageSize: number }) {
return request.get({ url: '/search', data })
}

View File

@@ -1,57 +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>
<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: ''
})
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>
.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

@@ -1,79 +1,83 @@
<template>
<view :class="{ active, inactive: !active, tab: true }" :style="shouldShow ? '' : 'display: none;'">
<slot v-if="shouldRender"></slot>
</view>
</template>
<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
} > (), {
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 { ctx } = getCurrentInstance()
handleChange(ctx, updateRender)
onMounted(() => {
update()
})
const changeData = computed(() => {
const {
dot,
info,
} = props;
return {
dot,
info,
}
})
watch(() => changeData.value, () => {
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 { ctx } = getCurrentInstance()
handleChange(ctx, updateRender)
onMounted(() => {
update()
})
const changeData = computed(() => {
const { dot, info } = props
return {
dot,
info
}
})
watch(
() => changeData.value,
() => {
update()
})
watch(() => props.name, (val) => {
}
)
watch(
() => props.name,
(val) => {
update()
})
</script>
<style>
.tab.active {
height: auto;
}
.tab.inactive {
height: 0;
overflow: visible;
}
}
)
</script>
<style>
.tab.active {
height: auto;
}
.tab.inactive {
height: 0;
overflow: visible;
}
</style>

View File

@@ -1,71 +1,106 @@
<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 // 是否允许滑动切换
<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,
@@ -88,318 +123,315 @@
isFixed: false,
top: 0,
stickyBgColor: '#FFFFFF',
swipeable: true,
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) => {
}
)
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(() => {
let 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()
// })
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`
}
// 设置一个init方法方便多处调用
const init = async () => {
// 获取tabs组件的尺寸信息
let 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 = () => {
// 创建节点查询
let 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(为元素左边界到父元素左边界的距离)等信息
let tabInfo = tabQueryInfo.value[currentIndex.value];
if (!tabInfo) return;
// 活动tab的宽度
let tabWidth = tabInfo.width;
// 活动item的左边到tabs组件左边的距离用item的left减去tabs的left
let offsetLeft = tabInfo.left - parentLeft.value;
// 将活动的tabs-item移动到屏幕正中间实际上是对scroll-view的移动
let scrollLefts = offsetLeft - (componentWidth.value - tabWidth) / 2;
scrollLeft.value = scrollLefts < 0 ? 0 : scrollLefts;
// 当前活动item的中点点到左边的距离减去滑块宽度的一半即可得到滑块所需的移动距离
let 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)
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`
}
// 更新子组件的显示
childrens.value.forEach((item, ind) => {
let 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);
// 字体加粗
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
}
swiping.value = false;
return style
}
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;
// }
// }
})
// 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

@@ -3,4 +3,3 @@ export enum AgreementEnum {
PRIVACY = 'privacy',
SERVICE = 'service'
}

View File

@@ -21,14 +21,14 @@ export enum SMSEnum {
}
export enum SearchTypeEnum {
HISTORY = 'history'
HISTORY = 'history'
}
// 用户资料
export enum FieldType {
NONE = '',
AVATAR = 'avatar',
USERNAME = 'username',
AVATAR = 'avatar',
USERNAME = 'username',
NICKNAME = 'nickname',
SEX = 'sex',
}
SEX = 'sex'
}

View File

@@ -4,4 +4,4 @@
export const TOKEN_KEY = 'token'
// 搜索历史记录
export const HISTORY = 'history'
export const HISTORY = 'history'

View File

@@ -1,13 +1,13 @@
import { reactive } from "vue"
import { reactive } from 'vue'
/**
* @description 触碰屏幕钩子函数
* @return { Function } 暴露钩子
*/
export function useTouch () {
export function useTouch() {
// 最小移动距离
const MIN_DISTANCE = 10;
const MIN_DISTANCE = 10
const touch = reactive({
direction: '',
deltaX: 0,
@@ -22,48 +22,47 @@ export function useTouch () {
*/
const getDirection = (x: number, y: number) => {
if (x > y && x > MIN_DISTANCE) {
return 'horizontal';
return 'horizontal'
}
if (y > x && y > MIN_DISTANCE) {
return 'vertical';
return 'vertical'
}
return '';
return ''
}
/**
* @description 重置参数
*/
const resetTouchStatus = () => {
touch.direction = '';
touch.deltaX = 0;
touch.deltaY = 0;
touch.offsetX = 0;
touch.offsetY = 0;
touch.direction = ''
touch.deltaX = 0
touch.deltaY = 0
touch.offsetX = 0
touch.offsetY = 0
}
/**
* @description 触发
*/
const touchStart = (event: any) => {
resetTouchStatus();
const events = event.touches[0];
touch.startX = events.clientX;
touch.startY = events.clientY;
resetTouchStatus()
const events = event.touches[0]
touch.startX = events.clientX
touch.startY = events.clientY
}
/**
* @description 移动
*/
const touchMove = (event: any) => {
const events = event.touches[0];
touch.deltaX = events.clientX - touch.startX;
touch.deltaY = events.clientY - touch.startY;
touch.offsetX = Math.abs(touch.deltaX);
touch.offsetY = Math.abs(touch.deltaY);
touch.direction =
touch.direction || getDirection(touch.offsetX, touch.offsetY);
const events = event.touches[0]
touch.deltaX = events.clientX - touch.startX
touch.deltaY = events.clientY - touch.startY
touch.offsetX = Math.abs(touch.deltaX)
touch.offsetY = Math.abs(touch.deltaY)
touch.direction = touch.direction || getDirection(touch.offsetX, touch.offsetY)
}
return {
touch,
resetTouchStatus,

View File

@@ -5,6 +5,7 @@ import './router'
import './styles/index.scss'
export function createApp() {
const app = createSSRApp(App)
app.use(plugins)
return {
app

View File

@@ -1,36 +1,35 @@
<template>
<view class="">
<u-parse :html="agreementContent"></u-parse>
</view>
</template>
<template>
<view class="">
<u-parse :html="agreementContent"></u-parse>
</view>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { onLoad } from "@dcloudio/uni-app";
import { AgreementEnum } from '@/enums/agreementEnums'
import { getPolicy } from '@/api/app'
let agreementType = ref('') // 协议类型
let agreementContent = ref('') // 协议内容
const getData = async (type) => {
let res = await getPolicy({ type })
console.log(res, 'res')
agreementContent.value = res.content
uni.setNavigationBarTitle({
title: res.name
})
}
onLoad((options: any) => {
if(options.type) {
agreementType = options.type
getData(agreementType)
}
})
</script>
<style lang="scss" scoped>
</style>
import { reactive, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { AgreementEnum } from '@/enums/agreementEnums'
import { getPolicy } from '@/api/app'
let agreementType = ref('') // 协议类型
const agreementContent = ref('') // 协议内容
const getData = async (type) => {
const res = await getPolicy({ type })
console.log(res, 'res')
agreementContent.value = res.content
uni.setNavigationBarTitle({
title: res.name
})
}
onLoad((options: any) => {
if (options.type) {
agreementType = options.type
getData(agreementType)
}
})
</script>
<style lang="scss" scoped></style>

View File

@@ -1,22 +1,22 @@
<template>
<view class="as-us flex flex-1 flex-col items-center">
<image :src="appStore.config.website.logo" mode="" class="img"></image>
<view class="text-content mt-[20rpx]">当前版本{{ appStore.config.version }}</view>
</view>
</template>
<template>
<view class="as-us flex flex-1 flex-col items-center">
<image :src="appStore.config.website.logo" mode="" class="img"></image>
<view class="text-content mt-[20rpx]">当前版本{{ appStore.config.version }}</view>
</view>
</template>
<script setup lang="ts">
import { useAppStore } from '@/stores/app'
const appStore = useAppStore()
</script>
<style lang="scss" scoped>
.as-us {
.img {
width: 160rpx;
height: 160rpx;
border-radius: 20rpx;
margin-top: 96rpx;
}
}
import { useAppStore } from '@/stores/app'
const appStore = useAppStore()
</script>
<style lang="scss" scoped>
.as-us {
.img {
width: 160rpx;
height: 160rpx;
border-radius: 20rpx;
margin-top: 96rpx;
}
}
</style>

View File

@@ -1,50 +1,61 @@
<template>
<z-paging ref="paging" v-model="collectData" @query="queryList" :fixed="false" height="100%"
use-page-scroll>
<u-swipe-action :show="item.show" :index="index" v-for="(item, index) in collectData" :key="item.id"
@click="handleCollect" :options="options" btn-width="120">
<news-card :item="item" :newsId="item.articleId"></news-card>
</u-swipe-action>
</z-paging>
</template>
<script lang="ts" setup>
import { ref, reactive, shallowRef } from 'vue';
import { getCollect, cancelCollect } from "@/api/news"
const paging = shallowRef()
const options = reactive([{
text: '取消收藏',
style: {
color: '#FFFFFF',
backgroundColor: '#FF2C3C'
}
}])
const collectData: any = ref([])
const queryList = async (pageNo, pageSize) => {
const { lists } = await getCollect()
lists.forEach(item => {
item.show = false
})
collectData.value = lists
paging.value.complete(lists);
}
const handleCollect = async (index: number): Promise<void> => {
try{
const articleId: number = collectData.value[index].articleId
await cancelCollect({ articleId })
paging.value.reload()
}catch(err){
//TODO handle the exception
console.log('取消收藏报错=>', err)
}
}
</script>
<style scoped>
</style>
<template>
<z-paging
ref="paging"
v-model="collectData"
@query="queryList"
:fixed="false"
height="100%"
use-page-scroll
>
<u-swipe-action
:show="item.show"
:index="index"
v-for="(item, index) in collectData"
:key="item.id"
@click="handleCollect"
:options="options"
btn-width="120"
>
<news-card :item="item" :newsId="item.articleId"></news-card>
</u-swipe-action>
</z-paging>
</template>
<script lang="ts" setup>
import { ref, reactive, shallowRef } from 'vue'
import { getCollect, cancelCollect } from '@/api/news'
const paging = shallowRef()
const options = reactive([
{
text: '取消收藏',
style: {
color: '#FFFFFF',
backgroundColor: '#FF2C3C'
}
}
])
const collectData: any = ref([])
const queryList = async (pageNo, pageSize) => {
const { lists } = await getCollect()
lists.forEach((item) => {
item.show = false
})
collectData.value = lists
paging.value.complete(lists)
}
const handleCollect = async (index: number): Promise<void> => {
try {
const articleId: number = collectData.value[index].articleId
await cancelCollect({ articleId })
paging.value.reload()
} catch (err) {
//TODO handle the exception
console.log('取消收藏报错=>', err)
}
}
</script>
<style scoped></style>

View File

@@ -1,50 +1,65 @@
<template>
<z-paging auto-show-back-to-top :auto="i==index" ref="paging" v-model="dataList" :data-key="i" @query="queryList" :fixed="false" height="100%">
<block v-for="(newsItem,newsIndex) in dataList" :key="newsIndex">
<news-card :item="newsItem" :newsId="newsItem.id"></news-card>
</block>
</z-paging>
</template>
<script lang="ts" setup>
import { ref, watch, nextTick } from 'vue';
import { getArticleList } from "@/api/news"
const props = withDefaults(defineProps < {
cid: number,
i: number,
index: number
} > (), {
cid: 0
})
const paging = ref(null)
const dataList = ref([])
const isFirst = ref<boolean>(true)
watch(() => props.index, async () => {
await nextTick()
if( props.i == props.index && isFirst.value ) {
isFirst.value = false;
paging.value?.reload();
}
}, { immediate: true })
const queryList = async (pageNo, pageSize) => {
try{
const { lists } = await getArticleList({
cid: props.cid,
pageNo, pageSize
})
paging.value.complete(lists);
}catch(e){
console.log('报错=>',e)
//TODO handle the exception
paging.value.complete(false);
}
}
</script>
<style scoped>
</style>
<template>
<z-paging
auto-show-back-to-top
:auto="i == index"
ref="paging"
v-model="dataList"
:data-key="i"
@query="queryList"
:fixed="false"
height="100%"
>
<block v-for="(newsItem, newsIndex) in dataList" :key="newsIndex">
<news-card :item="newsItem" :newsId="newsItem.id"></news-card>
</block>
</z-paging>
</template>
<script lang="ts" setup>
import { ref, watch, nextTick } from 'vue'
import { getArticleList } from '@/api/news'
const props = withDefaults(
defineProps<{
cid: number
i: number
index: number
}>(),
{
cid: 0
}
)
const paging = ref(null)
const dataList = ref([])
const isFirst = ref<boolean>(true)
watch(
() => props.index,
async () => {
await nextTick()
if (props.i == props.index && isFirst.value) {
isFirst.value = false
paging.value?.reload()
}
},
{ immediate: true }
)
const queryList = async (pageNo, pageSize) => {
try {
const { lists } = await getArticleList({
cid: props.cid,
pageNo,
pageSize
})
paging.value.complete(lists)
} catch (e) {
console.log('报错=>', e)
//TODO handle the exception
paging.value.complete(false)
}
}
</script>
<style scoped></style>

View File

@@ -1,63 +1,58 @@
<template>
<view class="news" >
<!-- 搜索 -->
<navigator class="news-search px-[24rpx] py-[14rpx] bg-white" url="/pages/search/search">
<u-search placeholder="请输入关键词搜索" disabled :show-action="false"></u-search>
</navigator>
<!-- 内容 -->
<tabs
:current="current"
@change="handleChange"
height="80"
bar-width="60"
:barStyle="{'bottom': '0'}"
>
<tab
v-for="(item, i) in tabList"
:key="i"
:name="item.name"
>
<view class="news">
<!-- 搜索 -->
<navigator class="news-search px-[24rpx] py-[14rpx] bg-white" url="/pages/search/search">
<u-search placeholder="请输入关键词搜索" disabled :show-action="false"></u-search>
</navigator>
<!-- 内容 -->
<tabs
:current="current"
@change="handleChange"
height="80"
bar-width="60"
:barStyle="{ bottom: '0' }"
>
<tab v-for="(item, i) in tabList" :key="i" :name="item.name">
<view class="news-list pt-[20rpx]">
<news-list :cid="item.id" :i="i" :index="current"></news-list>
<news-list :cid="item.id" :i="i" :index="current"></news-list>
</view>
</tab>
</tabs>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from "vue"
import { onLoad, onShow, onReady } from "@dcloudio/uni-app";
import NewsList from "./component/news-list.vue"
import { getArticleCate } from "@/api/news"
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from 'vue'
import { onLoad, onShow, onReady } from '@dcloudio/uni-app'
import NewsList from './component/news-list.vue'
import { getArticleCate } from '@/api/news'
const tabList = ref<any>([])
const current = ref<number>(0)
const handleChange = (index: number) => {
console.log(index)
current.value = Number(index)
}
const getData = async () => {
tabList.value = await getArticleCate()
}
onLoad((options) => {
getData()
})
</script>
const tabList = ref< any >([])
const current = ref<number>(0)
const handleChange = (index: number) => {
console.log(index)
current.value = Number(index)
}
const getData = async () => {
tabList.value = await getArticleCate()
}
onLoad((options) => {
getData()
})
</script>
<style lang="scss">
.news {
&-search {
margin-bottom: 2rpx;
}
&-list {
height: calc(100vh - 86px);
}
}
.news {
&-search {
margin-bottom: 2rpx;
}
&-list {
height: calc(100vh - 86px);
}
}
</style>

View File

@@ -1,95 +1,96 @@
<template>
<view class="news-detail bg-white" >
<!-- 标题信心 -->
<view class="news-detail-header py-[20rpx] px-[30rpx]">
<view class="text-3xl font-medium">{{ newsData.title }}</view>
<view class="flex mt-[20rpx] text-xs">
<view class="mr-[40rpx]" v-if="newsData.author">作者: {{ newsData.author }}</view>
<view class="text-muted mr-[40rpx]">{{ newsData.createTime }}</view>
<view class="flex items-center text-muted ">
<image src="/static/images/icon/icon_visit.png" class="w-[30rpx] h-[30rpx]"></image>
<view class="ml-[10rpx]">{{ newsData.visit }}</view>
</view>
</view>
</view>
<!-- 咨询内容 -->
<view class="news-detail-section bg-white p-[20rpx]">
<!-- 摘要 -->
<view class="summary p-[20rpx] text-base" v-if="newsData.summary">
摘要: {{ newsData.summary }}
</view>
<!-- 封面 -->
<view class="mt-[20rpx]" v-if="newsData.image">
<image class="w-full" :src="newsData.image" mode="widthFix"></image>
</view>
<!-- 内容 -->
<view class="mt-[20rpx]">
<u-parse :html="newsData.content"></u-parse>
</view>
</view>
<view class="panel-btn flex items-center px-[34rpx]" @click="handleAddCollect(newsData.id)">
<u-icon :name="newsData.collect ? 'star-fill' : 'star'" size="36"></u-icon>
<text class="ml-[10rpx]">收藏</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from "vue"
import { onLoad, onShow, onReady } from "@dcloudio/uni-app";
import { getArticleDetail, addCollect, cancelCollect } from "@/api/news"
<view class="news-detail bg-white">
<!-- 标题信心 -->
<view class="news-detail-header py-[20rpx] px-[30rpx]">
<view class="text-3xl font-medium">{{ newsData.title }}</view>
<view class="flex mt-[20rpx] text-xs">
<view class="mr-[40rpx]" v-if="newsData.author">作者: {{ newsData.author }}</view>
<view class="text-muted mr-[40rpx]">{{ newsData.createTime }}</view>
<view class="flex items-center text-muted">
<image
src="/static/images/icon/icon_visit.png"
class="w-[30rpx] h-[30rpx]"
></image>
<view class="ml-[10rpx]">{{ newsData.visit }}</view>
</view>
</view>
</view>
<!-- 咨询内容 -->
<view class="news-detail-section bg-white p-[20rpx]">
<!-- 摘要 -->
<view class="summary p-[20rpx] text-base" v-if="newsData.summary">
摘要: {{ newsData.summary }}
</view>
<!-- 封面 -->
<view class="mt-[20rpx]" v-if="newsData.image">
<image class="w-full" :src="newsData.image" mode="widthFix"></image>
</view>
<!-- 内容 -->
<view class="mt-[20rpx]">
<u-parse :html="newsData.content"></u-parse>
</view>
</view>
<view class="panel-btn flex items-center px-[34rpx]" @click="handleAddCollect(newsData.id)">
<u-icon :name="newsData.collect ? 'star-fill' : 'star'" size="36"></u-icon>
<text class="ml-[10rpx]">收藏</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, computed } from 'vue'
import { onLoad, onShow, onReady } from '@dcloudio/uni-app'
import { getArticleDetail, addCollect, cancelCollect } from '@/api/news'
const newsData = ref<any>({})
let newsId = ''
const getData = async (id) => {
newsData.value = await getArticleDetail({ id })
}
const handleAddCollect = async (articleId: number) => {
try {
if (newsData.value.collect) {
await cancelCollect({ articleId })
} else await addCollect({ articleId })
getData(newsId)
} catch (e) {
//TODO handle the exception
}
}
onLoad((options: any) => {
newsId = options.id
getData(newsId)
})
</script>
const newsData = ref< any >({})
let newsId = ''
const getData = async (id) => {
newsData.value = await getArticleDetail({ id })
}
const handleAddCollect = async (articleId: number) => {
try{
if( newsData.value.collect ) {
await cancelCollect({ articleId })
} else await addCollect({ articleId })
getData(newsId)
}catch(e){
//TODO handle the exception
}
}
onLoad((options: any) => {
newsId = options.id
getData(newsId)
})
</script>
<style lang="scss">
.news-detail {
height: 100%;
&-header {
border-bottom: 2rpx solid #F8F8F8;
}
&-section {
.summary {
border-radius: 12rpx;
background-color: #F7F7F7;
}
}
.panel-btn {
position: fixed;
right: 30rpx;
height: 80rpx;
bottom: 80rpx;
border-radius: 40rpx;
background: rgba(255, 255, 255, 0.95);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.16);
}
}
.news-detail {
height: 100%;
&-header {
border-bottom: 2rpx solid #f8f8f8;
}
&-section {
.summary {
border-radius: 12rpx;
background-color: #f7f7f7;
}
}
.panel-btn {
position: fixed;
right: 30rpx;
height: 80rpx;
bottom: 80rpx;
border-radius: 40rpx;
background: rgba(255, 255, 255, 0.95);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.16);
}
}
</style>

View File

@@ -1,67 +1,70 @@
<template>
<view class="suggest bg-white">
<!-- 热门搜索 -->
<view class="hot" v-if="hot_search.length">
<view class="text-base font-medium pl-[24rpx] pt-[26rpx] pb-[6rpx]">热门搜索</view>
<view class="w-full pl-[24rpx] pr-[8rpx]">
<block v-for="hotItem in hot_search">
<view class="keyword" @click="handleHistoreSearch(hotItem)">{{ hotItem }}</view>
</block>
</view>
</view>
<view class="mx-[24rpx] my-[40rpx] border-b border-solid border-0 border-light" v-if="hot_search.length && his_search.length"></view>
<!-- 历史搜索 -->
<view class="history" v-if="his_search.length">
<view class="flex justify-between px-[24rpx] pb-[6rpx]">
<view class="text-base font-medium">历史搜索</view>
<view class="text-xs text-muted" @click="() => emit('clear')">清空</view>
</view>
<view class="w-full pl-[24rpx] pr-[8rpx]">
<block v-for="hisItem in his_search">
<view class="keyword" @click="handleHistoreSearch(hisItem)">{{ hisItem }}</view>
</block>
</view>
</view>
</view>
<view class="suggest bg-white">
<!-- 热门搜索 -->
<view class="hot" v-if="hot_search.length">
<view class="text-base font-medium pl-[24rpx] pt-[26rpx] pb-[6rpx]">热门搜索</view>
<view class="w-full pl-[24rpx] pr-[8rpx]">
<block v-for="hotItem in hot_search">
<view class="keyword" @click="handleHistoreSearch(hotItem)">{{ hotItem }}</view>
</block>
</view>
</view>
<view
class="mx-[24rpx] my-[40rpx] border-b border-solid border-0 border-light"
v-if="hot_search.length && his_search.length"
></view>
<!-- 历史搜索 -->
<view class="history" v-if="his_search.length">
<view class="flex justify-between px-[24rpx] pb-[6rpx]">
<view class="text-base font-medium">历史搜索</view>
<view class="text-xs text-muted" @click="() => emit('clear')">清空</view>
</view>
<view class="w-full pl-[24rpx] pr-[8rpx]">
<block v-for="hisItem in his_search">
<view class="keyword" @click="handleHistoreSearch(hisItem)">{{ hisItem }}</view>
</block>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, nextTick } from "vue"
const emit = defineEmits<{
(event: 'search', value: string): void
(event: 'clear', value: void): void
}>()
const props = withDefaults(defineProps < {
hot_search?: string[],
his_search?: string[]
} > (), {
<script lang="ts" setup>
import { ref, reactive, nextTick } from 'vue'
const emit = defineEmits<{
(event: 'search', value: string): void
(event: 'clear', value: void): void
}>()
const props = withDefaults(
defineProps<{
hot_search?: string[]
his_search?: string[]
}>(),
{
hot_search: [],
his_search: []
})
const handleHistoreSearch = (text: string) => {
emit('search', text)
}
his_search: []
}
)
const handleHistoreSearch = (text: string) => {
emit('search', text)
}
</script>
<style lang="scss" scoped>
.suggest {
height: 100%;
.keyword {
display: inline-block;
margin: 24rpx 16rpx 0 0;
padding: 8rpx 24rpx;
border-radius: 26rpx;
background-color: #F4F4F4;
}
}
</style>
.suggest {
height: 100%;
.keyword {
display: inline-block;
margin: 24rpx 16rpx 0 0;
padding: 8rpx 24rpx;
border-radius: 26rpx;
background-color: #f4f4f4;
}
}
</style>

View File

@@ -1,127 +1,127 @@
<template>
<view class="search">
<!-- 搜索框 -->
<view class="px-[24rpx] py-[14rpx] bg-white">
<u-search
v-model="keyword"
placeholder="请输入关键词搜索"
height="72"
@search="handleSearch"
@custom="handleSearch"
@clear="search.searching = false"
></u-search>
</view>
<!-- 搜索 -->
<view class="search-content">
<!-- -->
<suggest
v-show="!search.searching"
@search="handleSearch"
@clear="handleClear"
:hot_search="search.hot_search"
:his_search="search.his_search"
></suggest>
<!-- -->
<view class="search-content-s pt-[20rpx]" v-show="search.searching">
<z-paging
ref="paging"
v-model="search.result"
@query="queryList"
:fixed="false"
height="100%"
>
<block v-for="(item, index) in search.result" :key="item.id">
<news-card :item="item" :newsId="item.id"></news-card>
</block>
</z-paging>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, shallowRef } from 'vue';
import Suggest from "./component/suggest.vue"
import { HISTORY } from "@/enums/cacheEnums"
import { getHotSearch, getSearch } from "@/api/shop"
import cache from "@/utils/cache"
interface Search {
hot_search: string[]
his_search:string[]
result: any
searching: boolean
}
const search = reactive<Search>({
hot_search: [],
his_search: [],
result: [],
searching: false
})
const keyword = ref<string>('')
const paging = shallowRef()
<view class="search">
<!-- 搜索框 -->
<view class="px-[24rpx] py-[14rpx] bg-white">
<u-search
v-model="keyword"
placeholder="请输入关键词搜索"
height="72"
@search="handleSearch"
@custom="handleSearch"
@clear="search.searching = false"
></u-search>
</view>
const handleSearch = (value: string) => {
keyword.value = value
if ( keyword.value ) {
if ( !search.his_search.includes(keyword.value) ) {
search.his_search.unshift(keyword.value)
cache.set(HISTORY, search.his_search)
}
}
paging.value.reload()
search.searching = true
}
const getHotSearchFunc = async () => {
try{
search.hot_search = await getHotSearch()
}catch(e){
//TODO handle the exception
console.log('获取热门搜索失败=>', e)
}
}
const handleClear = async (): Promise<void> => {
const resModel: any = await uni.showModal({
title: '温馨提示',
content: '是否清空历史记录?'
})
if ( resModel.confirm ) {
cache.set(HISTORY, '')
search.his_search = []
}
}
const queryList = async (pageNo, pageSize) => {
try{
const { lists } = await getSearch({
keyword: keyword.value,
pageNo, pageSize
})
paging.value.complete(lists);
}catch(e){
console.log('报错=>',e)
//TODO handle the exception
paging.value.complete(false);
}
}
getHotSearchFunc()
search.his_search = cache.get(HISTORY) || new Array()
</script>
<style lang="scss" scoped>
.search {
&-content {
height: calc(100vh - 46px - env(safe-area-inset-bottom));
&-s {
height: 100%;
}
}
}
<!-- 搜索 -->
<view class="search-content">
<!-- -->
<suggest
v-show="!search.searching"
@search="handleSearch"
@clear="handleClear"
:hot_search="search.hot_search"
:his_search="search.his_search"
></suggest>
<!-- -->
<view class="search-content-s pt-[20rpx]" v-show="search.searching">
<z-paging
ref="paging"
v-model="search.result"
@query="queryList"
:fixed="false"
height="100%"
>
<block v-for="(item, index) in search.result" :key="item.id">
<news-card :item="item" :newsId="item.id"></news-card>
</block>
</z-paging>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive, shallowRef } from 'vue'
import Suggest from './component/suggest.vue'
import { HISTORY } from '@/enums/cacheEnums'
import { getHotSearch, getSearch } from '@/api/shop'
import cache from '@/utils/cache'
interface Search {
hot_search: string[]
his_search: string[]
result: any
searching: boolean
}
const search = reactive<Search>({
hot_search: [],
his_search: [],
result: [],
searching: false
})
const keyword = ref<string>('')
const paging = shallowRef()
const handleSearch = (value: string) => {
keyword.value = value
if (keyword.value) {
if (!search.his_search.includes(keyword.value)) {
search.his_search.unshift(keyword.value)
cache.set(HISTORY, search.his_search)
}
}
paging.value.reload()
search.searching = true
}
const getHotSearchFunc = async () => {
try {
search.hot_search = await getHotSearch()
} catch (e) {
//TODO handle the exception
console.log('获取热门搜索失败=>', e)
}
}
const handleClear = async (): Promise<void> => {
const resModel: any = await uni.showModal({
title: '温馨提示',
content: '是否清空历史记录?'
})
if (resModel.confirm) {
cache.set(HISTORY, '')
search.his_search = []
}
}
const queryList = async (pageNo, pageSize) => {
try {
const { lists } = await getSearch({
keyword: keyword.value,
pageNo,
pageSize
})
paging.value.complete(lists)
} catch (e) {
console.log('报错=>', e)
//TODO handle the exception
paging.value.complete(false)
}
}
getHotSearchFunc()
search.his_search = cache.get(HISTORY) || []
</script>
<style lang="scss" scoped>
.search {
&-content {
height: calc(100vh - 46px - env(safe-area-inset-bottom));
&-s {
height: 100%;
}
}
}
</style>

View File

@@ -1,350 +1,350 @@
<template>
<!-- Main Start -->
<!-- 头部修改头像 -->
<view class="header bg-white pt-[30rpx]">
<view class="flex justify-center">
<image @click="uploaderAvatar" :src="userInfo?.avatar"></image>
</view>
<view class="mt-[20rpx] text-center text-muted text-xs" @click="uploaderAvatar"
>点击修改头像</view
>
</view>
<!-- 用户ID -->
<view
class="item text-nr flex justify-between"
@click=";(showUserName = true), (newUsername = userInfo?.username)"
>
<view class="label">账号</view>
<view class="content">{{ userInfo?.username }}</view>
<u-icon name="arrow-right" size="22"></u-icon>
</view>
<!-- 昵称 -->
<view
class="item text-nr flex justify-between"
@click=";(showNickName = true), (newNickname = userInfo?.nickname)"
>
<view class="label">昵称</view>
<view class="content">{{ userInfo?.nickname }}</view>
<u-icon name="arrow-right" size="22"></u-icon>
</view>
<!-- 性别 -->
<view class="item text-nr flex justify-between" @click="changeSex">
<view class="label">性别</view>
<view class="content">{{ userInfo?.sex }}</view>
<u-icon name="arrow-right" size="22"></u-icon>
</view>
<!-- 手机号 -->
<view class="item text-nr flex justify-between">
<view class="label">手机号</view>
<view class="content">{{
userInfo?.mobile == '' ? '未绑定手机号' : userInfo?.mobile
}}</view>
<!-- #ifdef MP-WEIXIN -->
<u-button
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
size="mini"
type="primary"
shape="circle"
:plain="true"
>
{{ userInfo?.mobile == '' ? '绑定手机号' : '更换手机号' }}
</u-button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<u-button @click="" size="mini" type="primary" shape="circle" :plain="true">
{{ userInfo?.mobile == '' ? '绑定手机号' : '更换手机号' }}
</u-button>
<!-- #endif -->
</view>
<!-- 注册时间 -->
<view class="item text-nr flex justify-between">
<view class="label">注册时间</view>
<view class="content">{{ userInfo?.createTime }}</view>
</view>
<!-- 昵称修改组件 -->
<u-popup v-model="showNickName" :closeable="true" mode="center" border-radius="20">
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
<view class="mb-[70rpx] text-xl text-center">修改昵称</view>
<u-field v-model="newNickname" label="新昵称" placeholder="请输入新的昵称"> </u-field>
<view class="mt-[80rpx]">
<u-button @click="changeNameConfirm" type="primary" shape="circle"> 确定 </u-button>
</view>
</view>
</u-popup>
<!-- 账号修改组件 -->
<u-popup v-model="showUserName" :closeable="true" mode="center" border-radius="20">
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
<view class="mb-[70rpx] text-xl text-center">修改账号</view>
<u-field v-model="newUsername" label="新账号" placeholder="请输入新的账号"> </u-field>
<view class="mt-[80rpx]">
<u-button @click="changeUserNameConfirm" type="primary" shape="circle">
确定
</u-button>
</view>
</view>
</u-popup>
<!-- 性别修改组件 -->
<u-picker
mode="selector"
v-model="showPicker"
confirm-color="#4173FF"
:default-selector="[0]"
:range="sexList"
@confirm="changeSexConfirm"
/>
<!-- 账号修改组件 -->
<u-popup v-model="showMobilePop" :closeable="true" mode="center" border-radius="20">
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
<view class="mb-[70rpx] text-xl text-center">修改手机号码</view>
<u-form-item borderBottom>
<u-input
class="flex-1"
v-model="newMobile"
placeholder="请输入新的手机号码"
:border="false"
/>
</u-form-item>
<u-form-item borderBottom>
<u-input
class="flex-1"
v-model="mobileCode"
placeholder="请输入验证码"
:border="false"
/>
<view
class="border-l border-solid border-0 border-light pl-3 text-muted leading-4 ml-3 w-[180rpx]"
@click="sendSms"
>
<u-verification-code
ref="uCodeRef"
:seconds="60"
@change="codeChange"
change-text="x秒"
/>
{{ codeTips }}
</view>
</u-form-item>
<view class="mt-[80rpx]">
<u-button @click="changeCodeMobile" type="primary" shape="circle"> 确定 </u-button>
</view>
</view>
</u-popup>
</template>
<script lang="ts" setup>
import { ref, shallowRef } from 'vue'
import { onShow, onUnload } from '@dcloudio/uni-app'
import { getUserInfo, userEdit, userBindMobile, userMnpMobile } from '@/api/user.ts'
import { smsSend } from '@/api/app'
import { FieldType, SMSEnum } from '@/enums/appEnums'
import { uploadFile } from '@/utils/util.ts'
// 用户信息
const userInfo = ref<any | null>(null)
// 用户信息的枚举
const fieldType = ref(FieldType.NONE)
//选择性别数据
const sexList = ref<Array<string> | null>(['男', '女'])
//显示昵称弹窗
const showNickName = ref<boolean | null>(false)
//显示账户弹窗
const showUserName = ref<boolean | null>(false)
//显示性别选择弹窗
const showPicker = ref<boolean | null>(false)
// 显示手机号验证码调整弹窗 非小程序才需要
const showMobilePop = ref<boolean | null>(false)
//新昵称
const newNickname = ref<string>('')
//新账号
const newUsername = ref<string>('')
//新的手机号码
const newMobile = ref<string>('')
//修改手机验证码
const mobileCode = ref<string>('')
const codeTips = ref('')
const uCodeRef = shallowRef()
// 获取用户信息
const getUser = async (): Promise<void> => {
userInfo.value = await getUserInfo()
}
// 获取验证码显示字段
const codeChange = (text: string) => {
codeTips.value = text
}
// 发送验证码
const sendSms = async () => {
if (!newMobile.value) return uni.$u.toast('请输入新的手机号码')
if (uCodeRef.value?.canGetCode) {
await smsSend({
scene: userInfo.value.mobile ? SMSEnum.CHANGE_MOBILE : SMSEnum.BIND_MOBILE,
mobile: newMobile.value
})
uni.$u.toast('发送成功')
uCodeRef.value?.start()
}
}
// 验证码修改手机号-非微信小程序
const changeCodeMobile = async () => {
await userBindMobile({
type: userInfo.value.mobile ? 'change' : 'bind',
mobile: newMobile.value,
code: mobileCode.value
})
getUser()
}
// 修改用户信息
const setUserInfoFun = async (value: string): Promise<void> => {
await userEdit({
field: fieldType.value,
value: value
})
getUser()
}
// 上传头像
const uploaderAvatar = () => {
fieldType.value = FieldType.AVATAR
uni.navigateTo({
url: '/uni_modules/vk-uview-ui/components/u-avatar-cropper/u-avatar-cropper?destWidth=300&rectWidth=200&fileType=jpg'
})
}
// 显示修改用户性别弹窗
const changeSex = () => {
showPicker.value = true
fieldType.value = FieldType.SEX
}
// 修改用户性别
const changeSexConfirm = (value) => {
setUserInfoFun(value[0] + 1)
showPicker.value = false
}
// 修改用户账号
const changeUserNameConfirm = () => {
if (newUsername.value == '') return uni.$u.toast('账号不能为空')
if (newUsername.value.length > 10) return uni.$u.toast('账号长度不得超过十位数')
fieldType.value = FieldType.USERNAME
setUserInfoFun(newUsername.value)
showUserName.value = false
}
// 修改用户昵称
const changeNameConfirm = () => {
if (newNickname.value == '') return uni.$u.toast('昵称不能为空')
if (newNickname.value.length > 10) return uni.$u.toast('昵称长度不得超过十位数')
showNickName.value = false
fieldType.value = FieldType.NICKNAME
setUserInfoFun(newNickname.value)
}
// 微信小程序 绑定||修改用户手机号
const getPhoneNumber = async (e): Promise<void> => {
fieldType.value = FieldType.MOBILE
const { encryptedData, iv, code } = e.detail
const data = {
code,
encrypted_data: encryptedData,
iv
}
if (encryptedData) {
await userMnpMobile({
...data
})
getUser()
}
}
const goPage = (url: string) => {
uni.navigateTo({
url: url
})
}
// 监听从裁剪页发布的事件,获得裁剪结果
uni.$on('uAvatarCropper', (path) => {
uni.showLoading({
title: '正在上传中...',
mask: true
})
uploadFile(path)
.then((res) => {
uni.hideLoading()
setUserInfoFun(res.url)
})
.catch(() => {
uni.hideLoading()
uni.$u.toast('上传失败')
})
})
onShow(async () => {
getUser()
})
onUnload(() => {
uni.$off('uAvatarCropper')
})
</script>
<style lang="scss">
.header {
width: 100%;
height: 240rpx;
// border-radius: 14rpx;
image {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
}
}
.item {
margin-top: 2rpx;
padding: 30rpx 20rpx;
// border-radius: 14rpx;
background-color: #ffffff;
.label {
width: 150rpx;
}
.content {
flex: 1;
width: 80%;
}
.bind {
height: 56rpx;
border-width: 1rpx;
border-style: solid;
}
}
.license {
margin-top: 80rpx;
color: #a7a7a7;
}
</style>
<template>
<!-- Main Start -->
<!-- 头部修改头像 -->
<view class="header bg-white pt-[30rpx]">
<view class="flex justify-center">
<image @click="uploaderAvatar" :src="userInfo?.avatar"></image>
</view>
<view class="mt-[20rpx] text-center text-muted text-xs" @click="uploaderAvatar"
>点击修改头像</view
>
</view>
<!-- 用户ID -->
<view
class="item text-nr flex justify-between"
@click=";(showUserName = true), (newUsername = userInfo?.username)"
>
<view class="label">账号</view>
<view class="content">{{ userInfo?.username }}</view>
<u-icon name="arrow-right" size="22"></u-icon>
</view>
<!-- 昵称 -->
<view
class="item text-nr flex justify-between"
@click=";(showNickName = true), (newNickname = userInfo?.nickname)"
>
<view class="label">昵称</view>
<view class="content">{{ userInfo?.nickname }}</view>
<u-icon name="arrow-right" size="22"></u-icon>
</view>
<!-- 性别 -->
<view class="item text-nr flex justify-between" @click="changeSex">
<view class="label">性别</view>
<view class="content">{{ userInfo?.sex }}</view>
<u-icon name="arrow-right" size="22"></u-icon>
</view>
<!-- 手机号 -->
<view class="item text-nr flex justify-between">
<view class="label">手机号</view>
<view class="content">{{
userInfo?.mobile == '' ? '未绑定手机号' : userInfo?.mobile
}}</view>
<!-- #ifdef MP-WEIXIN -->
<u-button
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
size="mini"
type="primary"
shape="circle"
:plain="true"
>
{{ userInfo?.mobile == '' ? '绑定手机号' : '更换手机号' }}
</u-button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<u-button @click="" size="mini" type="primary" shape="circle" :plain="true">
{{ userInfo?.mobile == '' ? '绑定手机号' : '更换手机号' }}
</u-button>
<!-- #endif -->
</view>
<!-- 注册时间 -->
<view class="item text-nr flex justify-between">
<view class="label">注册时间</view>
<view class="content">{{ userInfo?.createTime }}</view>
</view>
<!-- 昵称修改组件 -->
<u-popup v-model="showNickName" :closeable="true" mode="center" border-radius="20">
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
<view class="mb-[70rpx] text-xl text-center">修改昵称</view>
<u-field v-model="newNickname" label="新昵称" placeholder="请输入新的昵称"> </u-field>
<view class="mt-[80rpx]">
<u-button @click="changeNameConfirm" type="primary" shape="circle"> 确定 </u-button>
</view>
</view>
</u-popup>
<!-- 账号修改组件 -->
<u-popup v-model="showUserName" :closeable="true" mode="center" border-radius="20">
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
<view class="mb-[70rpx] text-xl text-center">修改账号</view>
<u-field v-model="newUsername" label="新账号" placeholder="请输入新的账号"> </u-field>
<view class="mt-[80rpx]">
<u-button @click="changeUserNameConfirm" type="primary" shape="circle">
确定
</u-button>
</view>
</view>
</u-popup>
<!-- 性别修改组件 -->
<u-picker
mode="selector"
v-model="showPicker"
confirm-color="#4173FF"
:default-selector="[0]"
:range="sexList"
@confirm="changeSexConfirm"
/>
<!-- 账号修改组件 -->
<u-popup v-model="showMobilePop" :closeable="true" mode="center" border-radius="20">
<view class="px-[50rpx] py-[40rpx] bg-white" style="width: 85vw">
<view class="mb-[70rpx] text-xl text-center">修改手机号码</view>
<u-form-item borderBottom>
<u-input
class="flex-1"
v-model="newMobile"
placeholder="请输入新的手机号码"
:border="false"
/>
</u-form-item>
<u-form-item borderBottom>
<u-input
class="flex-1"
v-model="mobileCode"
placeholder="请输入验证码"
:border="false"
/>
<view
class="border-l border-solid border-0 border-light pl-3 text-muted leading-4 ml-3 w-[180rpx]"
@click="sendSms"
>
<u-verification-code
ref="uCodeRef"
:seconds="60"
@change="codeChange"
change-text="x秒"
/>
{{ codeTips }}
</view>
</u-form-item>
<view class="mt-[80rpx]">
<u-button @click="changeCodeMobile" type="primary" shape="circle"> 确定 </u-button>
</view>
</view>
</u-popup>
</template>
<script lang="ts" setup>
import { ref, shallowRef } from 'vue'
import { onShow, onUnload } from '@dcloudio/uni-app'
import { getUserInfo, userEdit, userBindMobile, userMnpMobile } from '@/api/user.ts'
import { smsSend } from '@/api/app'
import { FieldType, SMSEnum } from '@/enums/appEnums'
import { uploadFile } from '@/utils/util.ts'
// 用户信息
const userInfo = ref<any | null>(null)
// 用户信息的枚举
const fieldType = ref(FieldType.NONE)
//选择性别数据
const sexList = ref<Array<string> | null>(['男', '女'])
//显示昵称弹窗
const showNickName = ref<boolean | null>(false)
//显示账户弹窗
const showUserName = ref<boolean | null>(false)
//显示性别选择弹窗
const showPicker = ref<boolean | null>(false)
// 显示手机号验证码调整弹窗 非小程序才需要
const showMobilePop = ref<boolean | null>(false)
//新昵称
const newNickname = ref<string>('')
//新账号
const newUsername = ref<string>('')
//新的手机号码
const newMobile = ref<string>('')
//修改手机验证码
const mobileCode = ref<string>('')
const codeTips = ref('')
const uCodeRef = shallowRef()
// 获取用户信息
const getUser = async (): Promise<void> => {
userInfo.value = await getUserInfo()
}
// 获取验证码显示字段
const codeChange = (text: string) => {
codeTips.value = text
}
// 发送验证码
const sendSms = async () => {
if (!newMobile.value) return uni.$u.toast('请输入新的手机号码')
if (uCodeRef.value?.canGetCode) {
await smsSend({
scene: userInfo.value.mobile ? SMSEnum.CHANGE_MOBILE : SMSEnum.BIND_MOBILE,
mobile: newMobile.value
})
uni.$u.toast('发送成功')
uCodeRef.value?.start()
}
}
// 验证码修改手机号-非微信小程序
const changeCodeMobile = async () => {
await userBindMobile({
type: userInfo.value.mobile ? 'change' : 'bind',
mobile: newMobile.value,
code: mobileCode.value
})
getUser()
}
// 修改用户信息
const setUserInfoFun = async (value: string): Promise<void> => {
await userEdit({
field: fieldType.value,
value: value
})
getUser()
}
// 上传头像
const uploaderAvatar = () => {
fieldType.value = FieldType.AVATAR
uni.navigateTo({
url: '/uni_modules/vk-uview-ui/components/u-avatar-cropper/u-avatar-cropper?destWidth=300&rectWidth=200&fileType=jpg'
})
}
// 显示修改用户性别弹窗
const changeSex = () => {
showPicker.value = true
fieldType.value = FieldType.SEX
}
// 修改用户性别
const changeSexConfirm = (value) => {
setUserInfoFun(value[0] + 1)
showPicker.value = false
}
// 修改用户账号
const changeUserNameConfirm = () => {
if (newUsername.value == '') return uni.$u.toast('账号不能为空')
if (newUsername.value.length > 10) return uni.$u.toast('账号长度不得超过十位数')
fieldType.value = FieldType.USERNAME
setUserInfoFun(newUsername.value)
showUserName.value = false
}
// 修改用户昵称
const changeNameConfirm = () => {
if (newNickname.value == '') return uni.$u.toast('昵称不能为空')
if (newNickname.value.length > 10) return uni.$u.toast('昵称长度不得超过十位数')
showNickName.value = false
fieldType.value = FieldType.NICKNAME
setUserInfoFun(newNickname.value)
}
// 微信小程序 绑定||修改用户手机号
const getPhoneNumber = async (e): Promise<void> => {
fieldType.value = FieldType.MOBILE
const { encryptedData, iv, code } = e.detail
const data = {
code,
encrypted_data: encryptedData,
iv
}
if (encryptedData) {
await userMnpMobile({
...data
})
getUser()
}
}
const goPage = (url: string) => {
uni.navigateTo({
url: url
})
}
// 监听从裁剪页发布的事件,获得裁剪结果
uni.$on('uAvatarCropper', (path) => {
uni.showLoading({
title: '正在上传中...',
mask: true
})
uploadFile(path)
.then((res) => {
uni.hideLoading()
setUserInfoFun(res.url)
})
.catch(() => {
uni.hideLoading()
uni.$u.toast('上传失败')
})
})
onShow(async () => {
getUser()
})
onUnload(() => {
uni.$off('uAvatarCropper')
})
</script>
<style lang="scss">
.header {
width: 100%;
height: 240rpx;
// border-radius: 14rpx;
image {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
}
}
.item {
margin-top: 2rpx;
padding: 30rpx 20rpx;
// border-radius: 14rpx;
background-color: #ffffff;
.label {
width: 150rpx;
}
.content {
flex: 1;
width: 80%;
}
.bind {
height: 56rpx;
border-width: 1rpx;
border-style: solid;
}
}
.license {
margin-top: 80rpx;
color: #a7a7a7;
}
</style>

View File

@@ -1,108 +1,108 @@
<template>
<view class="user-set">
<navigator :url="`/pages/user_data/user_data`">
<view class="item flex bg-white mt-[20rpx]">
<u-avatar :src="userInfo.avatar" shape="square"></u-avatar>
<view class="ml-[20rpx] flex flex-1 justify-between">
<view>
<view class="mb-[15rpx] text-xl font-medium">{{ userInfo.nickname }}</view>
<view class="text-content text-xs">{{ userInfo.username }}</view>
</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</view>
</navigator>
<view
class="item bg-white mt-[20rpx] btn-border flex flex-1 justify-between"
@click="show = true"
>
<view class="text-xl">登录密码</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
<view class="item bg-white btn-border flex flex-1 justify-between">
<view class="text-xl">绑定微信</view>
<view class="flex justify-between">
<view class="text-muted mr-[20rpx]">
{{ userInfo.isBindMnp ? '已绑定' : '未绑定' }}
</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</view>
<navigator :url="`/pages/agreement/agreement?type=${AgreementEnum.PRIVACY}`">
<view class="item bg-white mt-[20rpx] btn-border flex flex-1 justify-between">
<view class="text-xl">隐私政策</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</navigator>
<navigator :url="`/pages/agreement/agreement?type=${AgreementEnum.SERVICE}`">
<view class="item bg-white btn-border flex flex-1 justify-between">
<view class="text-xl">服务协议</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</navigator>
<view class="item bg-white btn-border flex flex-1 justify-between">
<view class="text-xl">关于我们</view>
<view class="flex justify-between">
<view class="text-muted mr-[20rpx]">
{{ appStore.config.version }}
</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</view>
<u-action-sheet :list="list" v-model="show" @click="handleClick"></u-action-sheet>
</view>
</template>
<script setup lang="ts">
import { getUserInfo } from '@/api/user'
import { onShow } from '@dcloudio/uni-app'
import { reactive, ref } from 'vue'
import { useAppStore } from '@/stores/app'
import { AgreementEnum } from '@/enums/agreementEnums'
const appStore = useAppStore()
const userInfo = ref({})
const list = ref([
{
text: '修改密码'
},
{
text: '忘记密码'
}
])
const show = ref(false)
const getUser = async () => {
const res = await getUserInfo()
console.log(res, 'res')
userInfo.value = res
}
const handleClick = (index: number) => {
switch (index) {
case 0:
uni.navigateTo({ url: '/pages/change_password/change_password' })
break
case 1:
uni.navigateTo({ url: '/pages/forget_pwd/forget_pwd' })
break
}
}
onShow(() => {
getUser()
})
</script>
<style lang="scss" scoped>
.user-set {
.item {
padding: 30rpx;
}
.btn-border {
border-bottom: 1rpx solid $u-form-item-border-color;
}
}
</style>
<template>
<view class="user-set">
<navigator :url="`/pages/user_data/user_data`">
<view class="item flex bg-white mt-[20rpx]">
<u-avatar :src="userInfo.avatar" shape="square"></u-avatar>
<view class="ml-[20rpx] flex flex-1 justify-between">
<view>
<view class="mb-[15rpx] text-xl font-medium">{{ userInfo.nickname }}</view>
<view class="text-content text-xs">{{ userInfo.username }}</view>
</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</view>
</navigator>
<view
class="item bg-white mt-[20rpx] btn-border flex flex-1 justify-between"
@click="show = true"
>
<view class="">登录密码</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
<view class="item bg-white btn-border flex flex-1 justify-between">
<view class="">绑定微信</view>
<view class="flex justify-between">
<view class="text-muted mr-[20rpx]">
{{ userInfo.isBindMnp ? '已绑定' : '未绑定' }}
</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</view>
<navigator :url="`/pages/agreement/agreement?type=${AgreementEnum.PRIVACY}`">
<view class="item bg-white mt-[20rpx] btn-border flex flex-1 justify-between">
<view class="">隐私政策</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</navigator>
<navigator :url="`/pages/agreement/agreement?type=${AgreementEnum.SERVICE}`">
<view class="item bg-white btn-border flex flex-1 justify-between">
<view class="">服务协议</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</navigator>
<view class="item bg-white btn-border flex flex-1 justify-between">
<view class="">关于我们</view>
<view class="flex justify-between">
<view class="text-muted mr-[20rpx]">
{{ appStore.config.version }}
</view>
<u-icon name="arrow-right" color="#666"></u-icon>
</view>
</view>
<u-action-sheet :list="list" v-model="show" @click="handleClick"></u-action-sheet>
</view>
</template>
<script setup lang="ts">
import { getUserInfo } from '@/api/user'
import { onShow } from '@dcloudio/uni-app'
import { reactive, ref } from 'vue'
import { useAppStore } from '@/stores/app'
import { AgreementEnum } from '@/enums/agreementEnums'
const appStore = useAppStore()
const userInfo = ref({})
const list = ref([
{
text: '修改密码'
},
{
text: '忘记密码'
}
])
const show = ref(false)
const getUser = async () => {
const res = await getUserInfo()
console.log(res, 'res')
userInfo.value = res
}
const handleClick = (index: number) => {
switch (index) {
case 0:
uni.navigateTo({ url: '/pages/change_password/change_password' })
break
case 1:
uni.navigateTo({ url: '/pages/forget_pwd/forget_pwd' })
break
}
}
onShow(() => {
getUser()
})
</script>
<style lang="scss" scoped>
.user-set {
.item {
padding: 30rpx;
}
.btn-border {
border-bottom: 1rpx solid $u-form-item-border-color;
}
}
</style>

View File

@@ -123,7 +123,7 @@ export function uploadFile(path: any) {
success: (res) => {
console.log('uploadFile res ==> ', res)
const data = JSON.parse(res.data)
console.log('data.code', data.code)
console.log('data.code', data.code)
if (data.code == 200) {
resolve(data.data)
} else {