flat: 视频版本0.1

This commit is contained in:
史典卓
2025-06-26 08:56:42 +08:00
parent 857dedad01
commit 36798d3054
28 changed files with 1437 additions and 140 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -7,12 +7,9 @@ const { $api, navTo, appendScriptTagElement } = inject('globalFunction');
onLaunch((options) => {
useDictStore().getDictData();
// uni.onTabBarMidButtonTap(() => {
// uni.navigateTo({
// url: '/pages/chat/chat',
// });
// });
// uni.hideTabBar();
// 登录
let token = uni.getStorageSync('token') || ''; // 同步获取 缓存信息
if (token) {
useUserStore()
@@ -52,6 +49,7 @@ onHide(() => {
<style>
/*每个页面公共css */
@import '@/common/animation.css';
@import '@/common/common.css';
/* 修改pages tabbar样式 H5有效 */
.uni-tabbar .uni-tabbar__item:nth-child(4) .uni-tabbar__bd .uni-tabbar__icon {

193
common/animation.css Normal file
View File

@@ -0,0 +1,193 @@
/*base code*/
.animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.animated.infinite {
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.animated.hinge {
-webkit-animation-duration: 2s;
animation-duration: 2s;
}
/*the animation definition*/
@-webkit-keyframes tada {
0% {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1)
}
10%,
20% {
-webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)
}
30%,
50%,
70%,
90% {
-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)
}
40%,
60%,
80% {
-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)
}
100% {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1)
}
}
@keyframes tada {
0% {
-webkit-transform: scale3d(1, 1, 1);
-ms-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1)
}
10%,
20% {
-webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
-ms-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg)
}
30%,
50%,
70%,
90% {
-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
-ms-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg)
}
40%,
60%,
80% {
-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
-ms-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg)
}
100% {
-webkit-transform: scale3d(1, 1, 1);
-ms-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1)
}
}
.tada {
-webkit-animation-name: tada;
animation-name: tada
}
.btn-tada:active {
-webkit-animation-name: tada;
animation-name: tada
}
/*the animation definition*/
@-webkit-keyframes rubberBand {
0% {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1)
}
30% {
-webkit-transform: scale3d(1.25, .75, 1);
transform: scale3d(1.25, .75, 1)
}
40% {
-webkit-transform: scale3d(0.75, 1.25, 1);
transform: scale3d(0.75, 1.25, 1)
}
50% {
-webkit-transform: scale3d(1.15, .85, 1);
transform: scale3d(1.15, .85, 1)
}
65% {
-webkit-transform: scale3d(.95, 1.05, 1);
transform: scale3d(.95, 1.05, 1)
}
75% {
-webkit-transform: scale3d(1.05, .95, 1);
transform: scale3d(1.05, .95, 1)
}
100% {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1)
}
}
@keyframes rubberBand {
0% {
-webkit-transform: scale3d(1, 1, 1);
-ms-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1)
}
30% {
-webkit-transform: scale3d(1.25, .75, 1);
-ms-transform: scale3d(1.25, .75, 1);
transform: scale3d(1.25, .75, 1)
}
40% {
-webkit-transform: scale3d(0.75, 1.25, 1);
-ms-transform: scale3d(0.75, 1.25, 1);
transform: scale3d(0.75, 1.25, 1)
}
50% {
-webkit-transform: scale3d(1.15, .85, 1);
-ms-transform: scale3d(1.15, .85, 1);
transform: scale3d(1.15, .85, 1)
}
65% {
-webkit-transform: scale3d(.95, 1.05, 1);
-ms-transform: scale3d(.95, 1.05, 1);
transform: scale3d(.95, 1.05, 1)
}
75% {
-webkit-transform: scale3d(1.05, .95, 1);
-ms-transform: scale3d(1.05, .95, 1);
transform: scale3d(1.05, .95, 1)
}
100% {
-webkit-transform: scale3d(1, 1, 1);
-ms-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1)
}
}
.rubberBand {
-webkit-animation-name: rubberBand;
animation-name: rubberBand
}
.btn-rubberBand:active {
-webkit-animation-name: tada;
animation-name: tada
}

View File

@@ -56,6 +56,7 @@ html {
background-color: rgba(189, 197, 254, 0.15);
}
.btn-incline {
transition: transform 0.2s ease;
transform-style: preserve-3d;
@@ -74,6 +75,23 @@ html {
transform: perspective(600px) rotateX(6deg) scale(0.98);
}
.press-button {
padding: 10px 20px;
background: #3A4750;
/* 深灰蓝 */
color: #ffffff;
font-size: 16px;
border: none;
border-radius: 6px;
cursor: pointer;
transition: transform 0.1s ease, box-shadow 0.1s ease;
/* box-shadow: 0 4px 0 #2C3E50; */
}
.press-button:active {
transform: scale(0.95) translateY(2px);
/* box-shadow: 0 2px 0 #1C2833; */
}
/* 动画效果 */
.btn-shaky:active {

View File

@@ -0,0 +1,312 @@
<template>
<swiper
class="m-tiktok-video-swiper"
circular
@change="swiperChange"
:current="state.current"
:vertical="true"
duration="300"
>
<swiper-item v-for="(item, index) in state.displaySwiperList" :key="index">
<view class="swiper-item" @click="(e) => handleClick(index, e)">
<video
:id="`video__${index}`"
:controls="controls"
:autoplay="false"
:loop="loop"
@ended="ended"
@controlstoggle="controlstoggle"
@play="onPlay"
@error="emits('error')"
class="m-tiktok-video-player"
:src="item.src || item.explainUrl"
v-if="index === 0 || !state.isFirstLoad"
></video>
<view class="cover-triangle" v-if="pause"></view>
<image
v-if="item.poster && state.displayIndex != index"
:src="item.poster"
class="m-tiktok-video-poster"
mode="aspectFit"
></image>
<slot :item="item"></slot>
</view>
</swiper-item>
</swiper>
</template>
<script setup>
import { ref, reactive, getCurrentInstance, watch, nextTick } from 'vue';
import { onLoad, onUnload } from '@dcloudio/uni-app';
const _this = getCurrentInstance();
const emits = defineEmits(['play', 'error', 'loadMore', 'change', 'controlstoggle', 'click', 'ended']);
const lastTapTime = ref(0);
const pause = ref(false);
const props = defineProps({
/**
* 视频列表
*/
videoList: {
type: Array,
default: () => [],
},
/**
* 是否循环播放一个视频
*/
loop: {
type: Boolean,
default: true,
},
/**
* 显示原生控制栏
*/
controls: {
type: Boolean,
default: true,
},
/**
* 是否自动播放
*/
autoplay: {
type: Boolean,
default: true,
},
/**
* 是否自动滚动播放
*/
autoChange: {
type: Boolean,
default: false,
},
/**
* 滚动加载阈值(即播放到剩余多少个之后触发加载更多
*/
loadMoreOffsetCount: {
type: Number,
default: 2,
},
/**
* 暂停 1 单机暂停; 2 双击暂停; 0关闭
*/
pauseType: {
type: Number,
default: 2,
},
});
const state = reactive({
originList: [], // 源数据
displaySwiperList: [], // swiper需要的数据
displayIndex: 0, // 用于显示swiper的真正的下标数值只有012。
originIndex: 0, // 记录源数据的下标
current: 0,
oid: 0,
showControls: '',
toggleShow: true, // 显示面板
videoContexts: [],
isFirstLoad: true,
});
const initVideoContexts = () => {
state.videoContexts = [
uni.createVideoContext('video__0', _this),
uni.createVideoContext('video__1', _this),
uni.createVideoContext('video__2', _this),
];
};
const onPlay = (e) => {
emits('play', e);
};
const setVideoRef = (el, index) => {
if (el) {
videoRefs.value[index] = el;
}
};
function handleClick(index, e) {
const now = Date.now();
switch (props.pauseType) {
case 1:
if (pause.value) {
state.videoContexts[index].play();
pause.value = false;
} else {
state.videoContexts[index].pause();
pause.value = true;
}
break;
case 2:
if (now - lastTapTime.value < 300) {
if (pause.value) {
state.videoContexts[index].play();
pause.value = false;
} else {
state.videoContexts[index].pause();
pause.value = true;
}
}
break;
}
lastTapTime.value = now;
state.toggleShow = !state.toggleShow;
emits('click', e);
}
function ended() {
// 自动切换下一个视频
if (props.autoChange) {
if (state.displayIndex < 2) {
state.current = state.displayIndex + 1;
} else {
state.current = 0;
}
}
emits('ended');
}
/**
* 初始一个显示的swiper数据
* @originIndex 从源数据的哪个开始显示默认0如从其他页面跳转进来要显示第n个这个参数就是他的下标
*/
function initSwiperData(originIndex = state.originIndex) {
const originListLength = state.originList.length; // 源数据长度
const displayList = [];
displayList[state.displayIndex] = state.originList[originIndex];
displayList[state.displayIndex - 1 == -1 ? 2 : state.displayIndex - 1] =
state.originList[originIndex - 1 == -1 ? originListLength - 1 : originIndex - 1];
displayList[state.displayIndex + 1 == 3 ? 0 : state.displayIndex + 1] =
state.originList[originIndex + 1 == originListLength ? 0 : originIndex + 1];
state.displaySwiperList = displayList;
if (state.oid >= state.originList.length) {
state.oid = 0;
}
if (state.oid < 0) {
state.oid = state.originList.length - 1;
}
// 暂停所有视频
state.videoContexts.map((item) => item?.stop());
setTimeout(() => {
// 当前视频
if (props.autoplay) {
uni.createVideoContext(`video__${state.displayIndex}`, _this).play();
}
}, 500);
// 数据改变
emits('change', {
index: originIndex,
detail: state.originList[originIndex],
});
// 加载更多
var pCount = state.originList.length - props.loadMoreOffsetCount;
if (originIndex == pCount) {
emits('loadMore');
}
}
/**
* swiper滑动时候
*/
function swiperChange(event) {
const { current } = event.detail;
state.isFirstLoad = false;
const originListLength = state.originList.length; // 源数据长度
// 向后滚动
if (state.displayIndex - current == 2 || state.displayIndex - current == -1) {
state.originIndex = state.originIndex + 1 == originListLength ? 0 : state.originIndex + 1;
state.displayIndex = state.displayIndex + 1 == 3 ? 0 : state.displayIndex + 1;
state.oid = state.originIndex - 1;
initSwiperData(state.originIndex);
}
// 如果两者的差为-2或者1则是向前滑动
else if (state.displayIndex - current == -2 || state.displayIndex - current == 1) {
state.originIndex = state.originIndex - 1 == -1 ? originListLength - 1 : state.originIndex - 1;
state.displayIndex = state.displayIndex - 1 == -1 ? 2 : state.displayIndex - 1;
state.oid = state.originIndex + 1;
initSwiperData(state.originIndex);
}
state.toggleShow = true;
}
function controlstoggle(e) {
state.showControls = e.detail.show;
emits('controlstoggle', e);
}
watch(
() => props.videoList,
() => {
if (props.videoList?.length) {
state.originList = props.videoList;
if (state.isFirstLoad || !state.videoContexts?.length) {
initSwiperData();
initVideoContexts();
}
}
},
{
immediate: true,
}
);
let loadTimer = null;
onLoad(() => {
// 为了首次只加载一条视频(提高首次加载性能),延迟加载后续视频
loadTimer = setTimeout(() => {
state.isFirstLoad = false;
clearTimeout(loadTimer);
}, 5000);
});
onUnload(() => {
clearTimeout(loadTimer);
});
defineExpose({
initSwiperData,
});
</script>
<style lang="stylus" scoped>
.m-tiktok-video-swiper,
.m-tiktok-video-player {
width: 100%;
height: 100%;
background-color: #000;
}
.m-tiktok-video-swiper {
.swiper-item {
position: relative;
height: 100%;
}
.m-tiktok-video-poster {
background-color: #000;
position: absolute;
width: 100%;
height: 100%;
}
}
.cover-triangle{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%)
width: 132rpx
height: 132rpx
border-radius: 50%;
background: rgba(0,0,0,0.3)
}
.cover-triangle::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-40%, -50%) rotate(90deg);
width: 0;
height: 0;
border-left: 40rpx solid transparent;
border-right: 40rpx solid transparent;
border-bottom: 60rpx solid #fff;
}
</style>

View File

@@ -6,6 +6,7 @@
background-color="#FFFFFF"
@maskClick="maskClickFn"
:mask-click="maskClick"
class="popup-fix"
>
<view class="popup-content">
<view class="popup-header">
@@ -183,6 +184,15 @@ defineExpose({
</script>
<style lang="scss" scoped>
.popup-fix {
position: fixed !important;
left: 0;
right: 0;
bottom: 0;
top: 0;
height: 100vh;
z-index: 9999;
}
.popup-content {
color: #000000;
height: 80vh;
@@ -320,7 +330,7 @@ defineExpose({
font-weight: 400;
font-size: 28rpx;
color: #333333;
margin-bottom: 15rpx;
margin-bottom: 15rpx;
}
}
.content-item:first-child {
@@ -329,8 +339,8 @@ defineExpose({
.check-content {
display: grid;
gap:16rpx;
grid-template-columns: repeat(auto-fill, minmax(180rpx, 1fr));
gap: 16rpx;
grid-template-columns: repeat(auto-fill, minmax(180rpx, 1fr));
place-items: stretch;
.checkbox-item {
@@ -338,9 +348,9 @@ defineExpose({
align-items: center;
text-align: center;
background-color: #d9d9d9;
min-width: 0;
padding: 0 10rpx;
min-width: 0;
padding: 0 10rpx;
height: 80rpx;
background: #e8eaee;
border-radius: 12rpx 12rpx 12rpx 12rpx;
@@ -348,12 +358,11 @@ defineExpose({
.option-label {
font-size: 28rpx;
width: 100%;
white-space: nowrap;
overflow: hidden;
white-space: nowrap;
overflow: hidden;
}
}
.checkedstyle {
height: 76rpx;
background: rgba(37, 107, 250, 0.06);
border-radius: 12rpx 12rpx 12rpx 12rpx;
@@ -362,4 +371,4 @@ defineExpose({
}
}
}
</style>
</style>

View File

@@ -1,7 +1,13 @@
<template>
<view class="tabbar_container">
<view class="tabbar_item" v-for="(item, index) in tabbarList" :key="index" @click="changeItem(item)">
<view class="item-top" :class="[item.centerItem ? 'center-item-img' : '']">
<view
class="item-top"
:class="[
item.centerItem ? 'center-item-img ' : '',
item.centerItem && currentItem === item.id ? 'rubberBand animated' : '',
]"
>
<image :src="currentItem == item.id ? item.selectedIconPath : item.iconPath"></image>
</view>
<view class="item-bottom" :class="[currentItem == item.id ? 'item-active' : '']">
@@ -37,8 +43,8 @@ export default {
id: 2,
text: '',
path: '/pages/chat/chat',
iconPath: '../../static/tabbar/logo2copy.png',
selectedIconPath: '../../static/tabbar/logo2copy.png',
iconPath: '../../static/tabbar/logo3.png',
selectedIconPath: '../../static/tabbar/logo3.png',
centerItem: true,
},
{
@@ -84,17 +90,18 @@ export default {
<style lang="scss" scoped>
.tabbar_container {
background-color: #ffffff;
position: fixed;
bottom: 0rpx;
left: 0rpx;
width: 100%;
height: 126rpx;
// box-shadow: 0 0 5px #999;
height: 88rpx;
display: flex;
align-items: center;
padding: 5rpx 0;
padding-bottom: env(safe-area-inset-bottom);
z-index: 998;
overflow: hidden;
// position: fixed;
// bottom: 0rpx;
// left: 0rpx;
// box-shadow: 0 0 5px #999;
// padding-bottom: env(safe-area-inset-bottom);
// z-index: 998;
.tabbar_item {
width: 33.33%;
height: 100rpx;
@@ -120,12 +127,12 @@ export default {
}
}
.center-item-img {
position: absolute;
top: 0rpx;
left: 50%;
transform: translate(-50%, 0);
width: 96rpx !important;
height: 96rpx !important;
// position: absolute;
// top: 0rpx;
// left: 50%;
// transform: translate(-50%, 0);
width: 108rpx !important;
height: 98rpx !important;
}
.item-active {
color: #256bfa;

173
hook/usePagination.js Normal file
View File

@@ -0,0 +1,173 @@
import {
ref,
reactive,
watch,
isRef,
nextTick
} from 'vue'
export function usePagination(
requestFn,
transformFn,
options = {}
) {
const list = ref([])
const loading = ref(false)
const error = ref(false)
const finished = ref(false)
const firstLoading = ref(true)
const empty = ref(false)
const {
pageSize = 10,
search = {},
autoWatchSearch = false,
debounceTime = 300,
autoFetch = false,
// 字段映射
dataKey = 'rows',
totalKey = 'total',
// 分页字段名映射
pageField = 'current',
sizeField = 'pageSize',
onBeforeRequest,
onAfterRequest
} = options
const pageState = reactive({
page: 1,
pageSize: isRef(pageSize) ? pageSize.value : pageSize,
total: 0,
maxPage: 1,
search: isRef(search) ? search.value : search
})
let debounceTimer = null
const fetchData = async (type = 'refresh') => {
if (loading.value) return Promise.resolve()
loading.value = true
error.value = false
if (typeof onBeforeRequest === 'function') {
try {
onBeforeRequest(type, pageState)
} catch (err) {
console.warn('onBeforeRequest 执行异常:', err)
}
}
if (type === 'refresh') {
pageState.page = 1
finished.value = false
if (list.value.length === 0) {
firstLoading.value = true
}
} else if (type === 'loadMore') {
if (pageState.page >= pageState.maxPage) {
loading.value = false
finished.value = true
return Promise.resolve('no more')
}
pageState.page += 1
}
const params = {
[pageField]: pageState.page,
[sizeField]: pageState.pageSize,
...pageState.search
}
try {
const res = await requestFn(params)
const rawData = res[dataKey]
const total = res[totalKey]
const data = typeof transformFn === 'function' ? transformFn(rawData) : rawData
if (type === 'refresh') {
list.value = data
} else {
list.value.push(...data)
}
pageState.total = total
pageState.maxPage = Math.ceil(total / pageState.pageSize)
finished.value = list.value.length >= total
empty.value = list.value.length === 0
} catch (err) {
console.error('分页请求失败:', err)
error.value = true
} finally {
loading.value = false
firstLoading.value = false
if (typeof onAfterRequest === 'function') {
try {
onAfterRequest(type, pageState, {
error: error.value
})
} catch (err) {
console.warn('onAfterRequest 执行异常:', err)
}
}
}
}
const refresh = () => fetchData('refresh')
const loadMore = () => fetchData('loadMore')
const resetPagination = () => {
list.value = []
pageState.page = 1
pageState.total = 0
pageState.maxPage = 1
finished.value = false
error.value = false
firstLoading.value = true
empty.value = false
}
if (autoWatchSearch && isRef(search)) {
watch(search, (newVal) => {
pageState.search = newVal
// clearTimeout(debounceTimer)
// debounceTimer = setTimeout(() => {
// refresh()
// }, debounceTime)
}, {
deep: true
})
}
watch(pageSize, (newVal) => {
pageState.pageSize = newVal
}, {
deep: true
})
if (autoFetch) {
nextTick(() => {
refresh()
})
}
return {
list,
loading,
error,
finished,
firstLoading,
empty,
pageState,
refresh,
loadMore,
resetPagination
}
}

View File

@@ -13,7 +13,13 @@
</template>
<view class="content" v-show="!isEmptyObject(jobInfo)">
<view class="content-top btn-feel">
<view class="top-salary">{{ jobInfo.minSalary }}-{{ jobInfo.maxSalary }}/</view>
<view class="top-salary">
<Salary-Expectation
:max-salary="jobInfo.maxSalary"
:min-salary="jobInfo.minSalary"
:is-month="true"
></Salary-Expectation>
</view>
<view class="top-name">{{ jobInfo.jobTitle }}</view>
<view class="top-info">
<view class="info-img"><image src="/static/icon/post12.png"></image></view>

View File

@@ -0,0 +1,136 @@
<template>
<div class="video-container">
<view class="back-box">
<view class="btn">
<uni-icons type="left" size="26" color="#FFFFFF" @click="navBack"></uni-icons>
</view>
</view>
<mTikTok :video-list="state.videoList" :pause-type="1" :controls="false" @loadMore="loadMore" @change="change">
<template v-slot="data">
<view class="video-layer">
<view class="title line_1">{{ currentItem.companyName }}</view>
<view class="discription">
<text class="line_1">
{{ currentItem.jobTitle }}
</text>
<view class="seedetail" @click="nextDetail">
查看详情
<uni-icons type="right" color="#FFFFFF" size="14"></uni-icons>
</view>
</view>
</view>
</template>
</mTikTok>
</div>
</template>
<script setup>
import { inject, ref, reactive } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
const { $api, navBack, navTo } = inject('globalFunction');
import mTikTok from '@/components/TikTok/TikTok.vue';
import useUserStore from '@/stores/useUserStore';
import { useRecommedIndexedDBStore } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore();
const state = reactive({
videoList: [],
});
const currentItem = ref(null);
onLoad(() => {
const jobInfo = uni.getStorageSync(`job-Info`);
if (jobInfo) {
currentItem.value = jobInfo;
state.videoList.push(jobInfo);
}
getNextVideoSrc(2);
});
function nextDetail() {
const job = currentItem.value;
// 记录岗位类型,用作数据分析
if (job.jobCategory) {
const recordData = recommedIndexDb.JobParameter(job);
recommedIndexDb.addRecord(recordData);
}
console.log(job.jobId);
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
}
function getNextVideoSrc(num) {
let params = {
uuid: useUserStore().seesionId,
count: num || 1,
};
$api.createRequest('/app/job/littleVideo/random', params).then((resData) => {
const { data, code } = resData;
state.videoList.push(...data);
});
}
const loadMore = () => {
// 触发加载更多
console.log('加载更多');
getNextVideoSrc();
};
const change = (e) => {
currentItem.value = e.detail;
console.log('🚀 ~ file: index.vue:53 ~ change ~ data:', e);
};
</script>
<style lang="stylus" scoped>
.video-container{
width: 100%;
height: 100vh;
position: relative
.back-box{
position: absolute;
left: 20rpx;
top: 20rpx
color: #FFFFFF
z-index: 2
}
}
.video-layer {
position: absolute;
left: 24rpx;
right: 24rpx;
bottom: 30rpx;
color: #fff;
.title{
font-weight: 500;
font-size: 30rpx;
line-height: 100%;
letter-spacing: 0%;
}
.discription{
font-weight: 400;
font-size: 28rpx;
letter-spacing: 0%;
margin-top: 20rpx
display: flex
align-items: center
.seedetail{
font-weight: 500;
font-size: 32rpx;
line-height: 100%;
letter-spacing: 0%;
white-space: nowrap
padding-left: 20rpx
}
}
}
.btn {
display: flex;
justify-content: space-between;
align-items: center;
width: 60rpx;
height: 60rpx;
image {
height: 100%;
width: 100%;
}
}
</style>

View File

@@ -186,10 +186,20 @@
"navigationBarTitleText": "系统通知",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
{
"path": "pages/tiktok/tiktok",
"style": {
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
}
]
}],
"tabBar": {
"custom": true,
"display": "none",
"color": "#5E5F60",
"selectedColor": "#256BFA",
"borderStyle": "black",

View File

@@ -39,7 +39,7 @@
<scroll-view scroll-y class="main-scroll" @scrolltolower="handleScrollToLower">
<view class="cards" v-if="fairList.length">
<view
class="card btn-incline"
class="card press-button"
v-for="(item, index) in fairList"
:key="index"
@click="navTo('/packageA/pages/exhibitors/exhibitors?jobFairId=' + item.jobFairId)"
@@ -83,6 +83,7 @@
<empty v-else pdTop="200"></empty>
</scroll-view>
</view>
<Tabbar :currentpage="1"></Tabbar>
</view>
</view>
</template>
@@ -90,6 +91,7 @@
<script setup>
import { reactive, inject, watch, ref, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import Tabbar from '@/components/tabbar/midell-box.vue';
import useLocationStore from '@/stores/useLocationStore';
import { storeToRefs } from 'pinia';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());

View File

@@ -62,10 +62,11 @@
<view class="chatmain-warpper">
<ai-paging ref="paging"></ai-paging>
</view>
<!-- 自定义tabbar -->
<view class="chatmain-footer" v-show="!isDrawerOpen">
<Tabbar :currentpage="2"></Tabbar>
</view>
</view>
<!-- 自定义tabbar -->
<!-- <tabbar-custom :currentpage="2"></tabbar-custom> -->
</view>
</template>
@@ -73,6 +74,7 @@
import { ref, inject, nextTick, computed } from 'vue';
const { $api, navTo, insertSortData } = inject('globalFunction');
import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
import Tabbar from '@/components/tabbar/midell-box.vue';
import useChatGroupDBStore from '@/stores/userChatGroupStore';
import useUserStore from '@/stores/useUserStore';
import aiPaging from './components/ai-paging.vue';
@@ -108,16 +110,16 @@ onHide(() => {
paging.value?.handleTouchCancel();
if (isDrawerOpen.value) {
isDrawerOpen.value = false;
uni.showTabBar();
// uni.showTabBar();
}
});
const toggleDrawer = () => {
isDrawerOpen.value = !isDrawerOpen.value;
if (isDrawerOpen.value) {
uni.hideTabBar();
// uni.hideTabBar();
} else {
uni.showTabBar();
// uni.showTabBar();
}
};
@@ -144,6 +146,7 @@ function updateSetting() {
<style lang="stylus" scoped>
header-height = 88rpx
footer-height = 98rpx
/* 页面容器 */
.container {
@@ -277,6 +280,8 @@ header-height = 88rpx
transition: margin-left 0.3s ease-in-out;
position: relative
background: #FFFFFF
display: flex
flex-direction: column
.head
display: block;
box-sizing: border-box;
@@ -304,15 +309,20 @@ header-height = 88rpx
height: 37rpx;
.chatmain-warpper
height: 'calc(100% - %s)' % header-height
height: 'calc(100% - %s)' %( header-height + footer-height)
position: relative;
display: block;
box-sizing: border-box;
width: 100%;
border-top: 2rpx solid #F4F4F4;
flex: 1
/* 页面被挤压时向右移动 */
.main-content.shift {
margin-left: 500rpx;
}
</style>
.chatmain-footer{
height: footer-height;
}
</style>

View File

@@ -9,11 +9,11 @@
<view class="chart button-click">职业图谱</view>
</view>
<view class="cards">
<view class="card btn-feel" @click="navTo('/pages/nearby/nearby')">
<view class="card press-button" @click="navTo('/pages/nearby/nearby')">
<view class="card-title">附近工作</view>
<view class="card-text">好岗职等你来</view>
</view>
<view class="card btn-feel" @click="navTo('/packageA/pages/choiceness/choiceness')">
<view class="card press-button" @click="navTo('/packageA/pages/choiceness/choiceness')">
<view class="card-title">精选企业</view>
<view class="card-text">优选职得信赖</view>
</view>
@@ -132,6 +132,14 @@
</view>
<!-- 筛选 -->
<select-filter ref="selectFilterModel"></select-filter>
<view class="maskFristEntry" v-if="maskFristEntry">
<view class="entry-content">
<text>左滑查看视频</text>
<image class="indicateArrow" src="/static/gif/indicateArrow.gif"></image>
</view>
<view class="maskFristEntry-Close" @click="closeFristEntry"></view>
</view>
</view>
</template>
@@ -153,11 +161,13 @@ import { useScrollDirection } from '@/hook/useScrollDirection';
import { useColumnCount } from '@/hook/useColumnCount';
const { isScrollingDown, handleScroll } = useScrollDirection();
const recommedIndexDb = useRecommedIndexedDBStore();
const emits = defineEmits(['onShowTabbar']);
const waterfallsFlowRef = ref(null);
const loadmoreRef = ref(null);
const conditionSearch = ref({});
const waterfallcolumn = ref(2);
const maskFristEntry = ref(false);
const state = reactive({
tabIndex: 'all',
});
@@ -193,6 +203,12 @@ const isLoaded = ref(false);
// { name: '创意总监', highlight: false },
// ]);
onLoad(() => {
// 判断浏览器是否有 fristEntry 第一次进入
let fristEntry = uni.getStorageSync('fristEntry') || true; // 默认未读
maskFristEntry.value = fristEntry;
});
const { columnCount, columnSpace } = useColumnCount(() => {
pageState.pageSize = 10 * (columnCount.value - 1);
getJobRecommend('refresh');
@@ -202,6 +218,11 @@ const { columnCount, columnSpace } = useColumnCount(() => {
});
});
function closeFristEntry() {
uni.setStorageSync('fristEntry', false);
maskFristEntry.value = false;
}
// onLoad(() => {
// getJobRecommend('refresh');
// });
@@ -286,6 +307,7 @@ function nextDetail(job) {
function openFilter() {
showFilter.value = true;
emits('onShowTabbar', false);
selectFilterModel.value?.open({
title: '筛选',
maskClick: true,
@@ -301,6 +323,7 @@ function openFilter() {
},
cancel: () => {
showFilter.value = false;
emits('onShowTabbar', true);
},
});
}
@@ -438,6 +461,51 @@ defineExpose({ loadData });
</script>
<style lang="stylus" scoped>
.maskFristEntry
position: fixed;
right: 20rpx;
bottom: calc(50% - 200rpx);
// background: rgba(0,0,0,0.3)
.entry-content
display: flex;
align-items: center
text
font-size: 36rpx
background: linear-gradient(273.34deg, #356CFA 3.58%, #A47FFD 85.84%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text; /* 有些浏览器兼容用 */
text-fill-color: transparent;
.indicateArrow
height: 76rpx
width: 68rpx
.maskFristEntry-Close
position: absolute;
left: calc(50% - 10rpx);
top: 86rpx
width: 42rpx
height: 42rpx
background: linear-gradient(273.34deg, #356CFA 3.58%, #A47FFD 85.84%);
border-radius: 50%;
.maskFristEntry-Close::before
position: absolute;
left: calc( 50% - 2rpx)
top: calc( 50% - 10rpx)
transform: rotate(45deg);
content: ''
background: #FFFFFF
width: 4rpx
height: 20rpx
.maskFristEntry-Close::after
position: absolute;
left: calc( 50% - 2rpx)
top: calc( 50% - 10rpx)
transform: rotate(-45deg);
content: ''
background: #FFFFFF
width: 4rpx
height: 20rpx
.app-container
width: 100%;

View File

@@ -29,16 +29,40 @@
</view>
</view>
<view class="cards">
<scroll-view :scroll-y="true" class="tab-scroll">
<scroll-view :scroll-y="true" class="tab-scroll" @scrolltolower="scrollBottom">
<view class="scroll-content">
<custom-waterfalls-flow ref="waterfallsFlowRef" :value="data.list">
<template v-slot:default="item">
<view class="item">
<view class="title">{{ item.title }}</view>
<view class="desc">{{ item.desc }}</view>
<custom-waterfalls-flow
ref="waterfallsFlowRef"
:column="columnCount"
:columnSpace="columnSpace"
@loaded="imageloaded"
:value="list"
>
<template v-slot:default="job">
<view class="slot-item">
<view class="job-image btn-feel" @click="nextVideo(job)">
<image class="cover-image" :src="job.cover" mode="aspectFill"></image>
<view class="cover-triangle"></view>
</view>
<view class="job-info" @click="nextDetail(job)">
<view class="salary">
<Salary-Expectation
:max-salary="job.maxSalary"
:min-salary="job.minSalary"
:is-month="true"
></Salary-Expectation>
<image v-if="job.isHot" class="flame" src="/static/icon/flame.png"></image>
</view>
<view class="title">{{ job.jobTitle }}</view>
<view class="desc">
<!-- <uni-icons type="location" size="14"></uni-icons> -->
<view class="descText">{{ job.companyName }}</view>
</view>
</view>
</view>
</template>
</custom-waterfalls-flow>
<loadmore ref="loadmoreRef"></loadmore>
</view>
</scroll-view>
</view>
@@ -47,77 +71,100 @@
<script setup>
import { reactive, inject, watch, ref, onMounted, watchEffect, nextTick } from 'vue';
import { usePagination } from '@/hook/usePagination';
const { $api, navTo } = inject('globalFunction');
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
import img from '@/static/icon/filter.png';
import { useColumnCount } from '@/hook/useColumnCount';
import { useRecommedIndexedDBStore, jobRecommender } from '@/stores/useRecommedIndexedDBStore.js';
const recommedIndexDb = useRecommedIndexedDBStore();
// status
const { userInfo } = storeToRefs(useUserStore());
const isLoaded = ref(false);
const waterfallsFlowRef = ref(null);
const loadmoreRef = ref(null);
const state = reactive({
tabIndex: 'all',
});
const data = reactive({
list: [
{
image: 'https://via.placeholder.com/200x500.png/ff0000',
title: '我是标题1',
desc: '描述描述描述描述描述描述描述描述1',
// 响应式搜索条件(可以被修改)
const searchParams = ref({});
const pageSize = ref(10);
const { list, loading, refresh, loadMore } = usePagination(
(params) => $api.createRequest('/app/job/littleVideo', params),
dataToImg, // 转换函数
{
pageSize: pageSize,
search: searchParams,
dataKey: 'data',
onBeforeRequest: () => {
loadmoreRef.value?.change('loading');
},
{
image: 'https://via.placeholder.com/200x200.png/2878ff',
title: '我是标题2',
desc: '描述描述描述描述描述描述描述描述2',
},
{
image: 'https://via.placeholder.com/200x100.png/FFB6C1',
title: '我是标题3',
desc: '描述描述描述描述描述描述描述描述3',
},
{
image: 'https://via.placeholder.com/200x300.png/9400D3',
title: '我是标题4',
desc: '描述描述描述描述描述描述描述描述4',
},
{
image: 'https://via.placeholder.com/100x240.png/B0E0E6',
title: '我是标题5',
desc: '描述描述描述描述描述描述描述描述5',
},
{
image: 'https://via.placeholder.com/140x280.png/7FFFAA',
title: '我是标题6',
desc: '描述描述描述描述描述描述描述描述6',
},
{
image: 'https://via.placeholder.com/40x60.png/EEE8AA',
title: '我是标题7',
desc: '描述描述描述描述描述描述描述描述7',
},
{
image: 'https://via.placeholder.com/140x280.png/7FFFAA',
title: '我是标题6',
desc: '描述描述描述描述描述描述描述描述6',
},
{
image: 'https://via.placeholder.com/40x60.png/EEE8AA',
title: '我是标题7',
desc: '描述描述描述描述描述描述描述描述7',
},
],
}
);
function imageloaded() {
loadmoreRef.value?.change('more');
}
const { columnCount, columnSpace } = useColumnCount(() => {
pageSize.value = 10 * (columnCount.value - 1);
nextTick(() => {
waterfallsFlowRef.value?.refresh?.();
useLocationStore().getLocation();
});
});
async function loadData() {
try {
if (isLoaded.value) return;
isLoaded.value = true;
refresh();
} catch (err) {
isLoaded.value = false; // 重置状态允许重试
throw err;
}
}
async function choosePosition() {}
async function choosePosition(index) {
state.tabIndex = index;
if (index === 'all') {
searchParams.value.jobTitle = '';
} else {
searchParams.value.jobTitle = userInfo.value.jobTitle[index];
}
console.log(searchParams.value);
refresh('refresh');
waterfallsFlowRef.value.refresh();
}
function scrollBottom() {
loadMore();
}
function nextDetail(job) {
// 记录岗位类型,用作数据分析
if (job.jobCategory) {
const recordData = recommedIndexDb.JobParameter(job);
recommedIndexDb.addRecord(recordData);
}
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
}
function nextVideo(job) {
uni.setStorageSync(`job-Info`, job);
navTo(`/packageA/pages/tiktok/tiktok`);
}
function dataToImg(data) {
return data.map((item) => ({
...item,
// image: item.cover,
image: img,
hide: true,
}));
}
defineExpose({ loadData });
</script>
@@ -159,9 +206,10 @@ defineExpose({ loadData });
.jobs-left
display: flex
flex-wrap: nowrap
align-items: center
.job
font-weight: 400;
font-size: 36rpx;
font-size: 28rpx;
color: #666D7F;
margin-right: 32rpx;
white-space: nowrap
@@ -174,9 +222,11 @@ defineExpose({ loadData });
align-items: center;
justify-content: center;
font-weight: 400;
font-size: 32rpx;
font-size: 28rpx;
color: #666D7F;
line-height: 38rpx;
.iconsearch
margin-right: 6rpx
.filter-bottom
display: flex
justify-content: space-between
@@ -202,4 +252,71 @@ defineExpose({ loadData });
height: 26rpx;
.active
transform: rotate(180deg)
.slot-item
background: #f4f4f4;
// background: #f6f8fa;
.job-image{
width: 100%;
height: 280rpx;
position: relative;
.cover-image{
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.cover-triangle{
position: absolute;
right: 20rpx;
top: 20rpx
width: 36rpx
height: 36rpx
border-radius: 50%
background: rgba(0,0,0,0.3)
}
.cover-triangle::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-40%, -50%) rotate(90deg);
width: 0;
height: 0;
border-left: 8rpx solid transparent;
border-right: 8rpx solid transparent;
border-bottom: 12rpx solid #fff;
}
}
.job-info{
padding: 10rpx 24rpx 24rpx 24rpx
}
.salary
color: #4C6EFB;
font-size: 28rpx
display: flex
align-items: flex-start
justify-content: space-between
.flame
margin-top: 4rpx
margin-right: 4rpx
width: 24rpx
height: 31rpx
.title
font-weight: 500;
font-size: 32rpx;
color: #333333;
margin-top: 6rpx;
white-space: pre-wrap
.desc
font-weight: 400;
font-size: 24rpx;
color: #6C7282;
margin-top: 6rpx;
display: flex
.descText{
white-space: pre-wrap
}
</style>

View File

@@ -6,21 +6,35 @@
<swiper class="swiper" :current="state.current" @change="changeSwiperType">
<swiper-item class="swiper-item" v-for="(_, index) in 2" :key="index">
<!-- #ifndef MP-WEIXIN -->
<component :is="components[index]" :ref="(el) => handelComponentsRef(el, index)" />
<component
:is="components[index]"
@onShowTabbar="changeShowTabbar"
:ref="(el) => handelComponentsRef(el, index)"
/>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<IndexOne v-show="currentIndex === 0" :ref="(el) => handelComponentsRef(el, index)" />
<IndexTwo v-show="currentIndex === 1" :ref="(el) => handelComponentsRef(el, index)" />
<IndexOne
v-show="currentIndex === 0"
@onShowTabbar="changeShowTabbar"
:ref="(el) => handelComponentsRef(el, index)"
/>
<IndexTwo
v-show="currentIndex === 1"
@onShowTabbar="changeShowTabbar"
:ref="(el) => handelComponentsRef(el, index)"
/>
<!-- #endif -->
</swiper-item>
</swiper>
</view>
<Tabbar v-show="showTabbar" :currentpage="0"></Tabbar>
</view>
</view>
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted } from 'vue';
import Tabbar from '@/components/tabbar/midell-box.vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import IndexOne from './components/index-one.vue';
import IndexTwo from './components/index-two.vue';
@@ -30,6 +44,7 @@ const components = [IndexOne, IndexTwo];
import { storeToRefs } from 'pinia';
import { useReadMsg } from '@/stores/useReadMsg';
const { unreadCount } = storeToRefs(useReadMsg());
const showTabbar = ref(true);
onShow(() => {
// 获取消息列表
@@ -49,6 +64,11 @@ const handelComponentsRef = (el, index) => {
swiperRefs[index].value = el;
}
};
function changeShowTabbar(val) {
showTabbar.value = val;
}
// 查看消息类型
function changeSwiperType(e) {
const index = e.detail.current;

View File

@@ -96,12 +96,16 @@
></uni-popup-dialog>
</uni-popup>
</view>
<template #footer>
<Tabbar :currentpage="4"></Tabbar>
</template>
</AppLayout>
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted } from 'vue';
import { storeToRefs } from 'pinia';
import Tabbar from '@/components/tabbar/midell-box.vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
const { $api, navTo } = inject('globalFunction');
import useUserStore from '@/stores/useUserStore';
@@ -170,7 +174,7 @@ function getUserstatistics() {
width: auto;
max-width: 60%;
white-space: nowrap
overflow:hidden;
overflow:hidden;
text-overflow: ellipsis;
}
.top-btn{
@@ -307,4 +311,4 @@ function getUserstatistics() {
border-radius: 2rpx
background: #A2A2A2;
transform: rotate(45deg)
</style>
</style>

View File

@@ -27,6 +27,8 @@
</swiper-item>
</swiper>
</view>
<Tabbar :currentpage="3"></Tabbar>
</view>
</view>
</template>
@@ -34,6 +36,7 @@
<script setup>
import { reactive, inject, watch, ref, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import Tabbar from '@/components/tabbar/midell-box.vue';
import ReadComponent from './read.vue';
import UnreadComponent from './unread.vue';
const loadedMap = reactive([false, false]);

View File

@@ -2,7 +2,7 @@
<scroll-view scroll-y class="main-scroll">
<view class="scrollmain">
<view
class="list-card btn-feel"
class="list-card press-button"
v-for="(item, index) in msgList"
:key="index"
@click="seeDetail(item, index)"

View File

@@ -2,7 +2,7 @@
<scroll-view scroll-y class="main-scroll">
<view class="scrollmain">
<view
class="list-card btn-feel"
class="list-card press-button"
v-for="(item, index) in unreadMsgList"
:key="index"
@click="seeDetail(item)"

View File

@@ -1,33 +1,70 @@
<template>
<view class="container">
<view class="top">
<image class="btnback button-click" src="@/static/icon/back.png" @click="navBack"></image>
<view class="search-box">
<uni-icons
class="iconsearch"
color="#666666"
type="search"
size="18"
@confirm="searchCollection"
></uni-icons>
<input
class="inputed"
type="text"
focus
v-model="searchValue"
placeholder="搜索职位名称"
placeholder-class="placeholder"
@confirm="searchBtn"
/>
<view>
<view class="top">
<image class="btnback button-click" src="@/static/icon/back.png" @click="navBack"></image>
<view class="search-box">
<uni-icons
class="iconsearch"
color="#666666"
type="search"
size="18"
@confirm="searchCollection"
></uni-icons>
<input
class="inputed"
type="text"
focus
v-model="searchValue"
placeholder="搜索职位名称"
placeholder-class="placeholder"
@confirm="searchBtn"
/>
</view>
<view class="search-btn button-click" @click="searchBtn">搜索</view>
</view>
<view class="view-top" v-show="listCom.length || list.length">
<view class="top-item" @click="changeType(0)" :class="{ active: currentTab === 0 }">综合</view>
<view class="top-item" @click="changeType(1)" :class="{ active: currentTab === 1 }">视频</view>
</view>
<view class="search-btn button-click" @click="searchBtn">搜索</view>
</view>
<scroll-view scroll-y class="Detailscroll-view" v-show="list.length" @scrolltolower="getJobList('add')">
<view class="cards-box">
<renderJobs :list="list" :longitude="longitudeVal" :latitude="latitudeVal"></renderJobs>
<scroll-view scroll-y class="Detailscroll-view" v-show="listCom.length" @scrolltolower="choosePosition">
<view class="cards-box" v-show="currentTab === 0">
<renderJobs :list="listCom" :longitude="longitudeVal" :latitude="latitudeVal"></renderJobs>
</view>
<view class="cards-box" style="padding-top: 24rpx" v-show="currentTab === 1">
<custom-waterfalls-flow
ref="waterfallsFlowRef"
:column="columnCount"
:columnSpace="columnSpace"
@loaded="imageloaded"
:value="list"
>
<template v-slot:default="job">
<view class="slot-item">
<view class="job-image btn-feel" @click="nextVideo(job)">
<image class="cover-image" :src="job.cover" mode="aspectFill"></image>
<view class="cover-triangle"></view>
</view>
<view class="job-info" @click="nextDetail(job)">
<view class="salary">
<Salary-Expectation
:max-salary="job.maxSalary"
:min-salary="job.minSalary"
:is-month="true"
></Salary-Expectation>
<image v-if="job.isHot" class="flame" src="/static/icon/flame.png"></image>
</view>
<view class="title">{{ job.jobTitle }}</view>
<view class="desc">{{ job.companyName }}</view>
</view>
</view>
</template>
</custom-waterfalls-flow>
<loadmore ref="loadmoreRef"></loadmore>
</view>
</scroll-view>
<view class="main-content" v-show="!list.length">
<view class="main-content" v-show="!listCom.length">
<view class="content-top">
<view class="top-left">历史搜索</view>
<view class="top-right button-click" @click="remove">
@@ -44,16 +81,19 @@
</template>
<script setup>
import { inject, ref, reactive } from 'vue';
import { inject, ref, reactive, nextTick } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import SelectJobs from '@/components/selectJobs/selectJobs.vue';
const { $api, navBack } = inject('globalFunction');
const { $api, navBack, navTo } = inject('globalFunction');
import useLocationStore from '@/stores/useLocationStore';
import { storeToRefs } from 'pinia';
import { useColumnCount } from '@/hook/useColumnCount';
import { usePagination } from '@/hook/usePagination';
import img from '@/static/icon/filter.png';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const searchValue = ref('');
const historyList = ref([]);
const list = ref([]);
const listCom = ref([]);
const pageState = reactive({
page: 0,
total: 0,
@@ -63,6 +103,47 @@ const pageState = reactive({
order: 0,
},
});
const isLoaded = ref(false);
const waterfallsFlowRef = ref(null);
const loadmoreRef = ref(null);
const currentTab = ref(0);
// 响应式搜索条件(可以被修改)
const searchParams = ref({});
const pageSize = ref(10);
const { list, loading, refresh, loadMore } = usePagination(
(params) => $api.createRequest('/app/job/littleVideo', params),
dataToImg, // 转换函数
{
pageSize: pageSize,
search: searchParams,
dataKey: 'data',
autoWatchSearch: true,
onBeforeRequest: () => {
loadmoreRef.value?.change('loading');
},
}
);
async function choosePosition(index) {
if (currentTab.value === 0) {
getJobList('add');
} else {
loadMore();
}
}
function imageloaded() {
loadmoreRef.value?.change('more');
}
const { columnCount, columnSpace } = useColumnCount(() => {
pageSize.value = 10 * (columnCount.value - 1);
nextTick(() => {
waterfallsFlowRef.value?.refresh?.();
useLocationStore().getLocation();
});
});
onLoad(() => {
let arr = uni.getStorageSync('searchList');
@@ -71,6 +152,20 @@ onLoad(() => {
}
});
function changeType(type) {
if (currentTab.value === type) return;
switch (type) {
case 0:
currentTab.value = 0;
getJobList('refresh');
break;
case 1:
currentTab.value = 1;
refresh();
waterfallsFlowRef.value?.refresh?.();
break;
}
}
function searchFn(item) {
searchValue.value = item;
searchBtn();
@@ -83,7 +178,15 @@ function searchBtn() {
historyList.value.unshift(searchValue.value);
historyList.value = unique(historyList.value);
uni.setStorageSync('searchList', historyList.value);
getJobList('refresh');
searchParams.value = {
jobTitle: searchValue,
};
if (currentTab.value === 0) {
getJobList('refresh');
} else {
refresh();
waterfallsFlowRef.value?.refresh?.();
}
}
function searchCollection(e) {
@@ -112,12 +215,25 @@ function remove() {
historyList.value = [];
}
function nextDetail(job) {
// 记录岗位类型,用作数据分析
if (job.jobCategory) {
const recordData = recommedIndexDb.JobParameter(job);
recommedIndexDb.addRecord(recordData);
}
navTo(`/packageA/pages/post/post?jobId=${btoa(job.jobId)}`);
}
function nextVideo(job) {
uni.setStorageSync(`job-Info`, job);
navTo(`/packageA/pages/tiktok/tiktok`);
}
function getJobList(type = 'add') {
if (type === 'add' && pageState.page < pageState.maxPage) {
pageState.page += 1;
}
if (type === 'refresh') {
list.value = [];
pageState.page = 1;
pageState.maxPage = 2;
}
@@ -132,11 +248,11 @@ function getJobList(type = 'add') {
const { rows, total } = resData;
if (type === 'add') {
const str = pageState.pageSize * (pageState.page - 1);
const end = list.value.length;
const end = listCom.value.length;
const reslist = rows;
list.value.splice(str, end, ...reslist);
listCom.value.splice(str, end, ...reslist);
} else {
list.value = rows;
listCom.value = rows;
}
pageState.total = resData.total;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
@@ -147,6 +263,15 @@ function getJobList(type = 'add') {
}
});
}
function dataToImg(data) {
return data.map((item) => ({
...item,
// image: item.cover,
image: img,
hide: true,
}));
}
</script>
<style lang="stylus" scoped>
@@ -156,12 +281,35 @@ function getJobList(type = 'add') {
.Detailscroll-view{
flex: 1
overflow: hidden
}
.container{
display: flex
flex-direction: column
background: #F4f4f4
height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
.view-top{
display: flex;
justify-content: space-around
background: #FFFFFF;
.top-item{
padding: 6rpx 0 18rpx 0
}
.active{
color: #256BFA;
font-weight: 500;
position: relative;
}
.active::after{
position: absolute;
content: ''
left: calc(50% - 12rpx)
bottom: 10rpx
width: 24rpx
height: 6rpx
background: #256BFA
}
}
.main-content{
background: #FFFFFF
height: 100%
@@ -244,4 +392,67 @@ function getJobList(type = 'add') {
}
}
}
.slot-item
// background: #f4f4f4;
background: #FFFFFF;
.job-info{
padding: 10rpx 24rpx 24rpx 24rpx
}
.job-image{
width: 100%;
height: 280rpx;
position: relative;
.cover-image{
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.cover-triangle{
position: absolute;
right: 20rpx;
top: 20rpx
width: 36rpx
height: 36rpx
border-radius: 50%
background: rgba(0,0,0,0.3)
}
.cover-triangle::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-40%, -50%) rotate(90deg);
width: 0;
height: 0;
border-left: 8rpx solid transparent;
border-right: 8rpx solid transparent;
border-bottom: 12rpx solid #fff;
}
}
.salary
color: #4C6EFB;
font-size: 28rpx
display: flex
align-items: flex-start
justify-content: space-between
.flame
margin-top: 4rpx
margin-right: 4rpx
width: 24rpx
height: 31rpx
.title
font-weight: 500;
font-size: 32rpx;
color: #333333;
margin-top: 6rpx;
white-space: pre-wrap
.desc
font-weight: 400;
font-size: 24rpx;
color: #6C7282;
margin-top: 6rpx;
</style>

BIN
static/.DS_Store vendored

Binary file not shown.

BIN
static/gif/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
static/gif/logo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

View File

@@ -57,7 +57,7 @@ const useUserStore = defineStore("user", () => {
hasLogin.value = true;
userInfo.value = value;
openId.value = value.wxOpenId;
token.value = value.token
token.value = value.token;
uni.setStorage({
key: 'token',
data: value.token

Binary file not shown.