This commit is contained in:
王创世 2023-12-25 17:56:30 +08:00
commit 395d007dc2
313 changed files with 67818 additions and 0 deletions

16
.hbuilderx/launch.json Normal file
View File

@ -0,0 +1,16 @@
{ // launch.json configurations app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtypelocalremote, localremote
"version": "0.0",
"configurations": [{
"default" :
{
"launchtype" : "local"
},
"mp-weixin" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
]
}

5
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/云商会.iml" filepath="$PROJECT_DIR$/.idea/云商会.iml" />
</modules>
</component>
</project>

12
.idea/云商会.iml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

93
App.vue Normal file
View File

@ -0,0 +1,93 @@
<script>
import Vue from 'vue'
import store from './store/index.js'
import updateCustomBarInfo from './tuniao-ui/libs/function/updateCustomBarInfo.js'
export default {
onLaunch: function() {
uni.getSystemInfo({
success: function(e) {
// #ifndef H5
//
const system = e.system.toLowerCase()
const platform = e.platform.toLowerCase()
// ios
if (platform.indexOf('ios') != -1 && (system.indexOf('ios') != -1 || system.indexOf(
'macos') != -1)) {
Vue.prototype.SystemPlatform = 'apple'
} else if (platform.indexOf('android') != -1 && (system.indexOf('android') != -1)) {
Vue.prototype.SystemPlatform = 'android'
} else {
Vue.prototype.SystemPlatform = 'devtools'
}
// #endif
}
})
//
// store.dispatch('updateCustomBarInfo')
updateCustomBarInfo().then((res) => {
store.commit('$tStore', {
name: 'vuex_status_bar_height',
value: res.statusBarHeight
})
store.commit('$tStore', {
name: 'vuex_custom_bar_height',
value: res.customBarHeight
})
})
// #ifdef MP-WEIXIN
//
if (wx.canIUse('getUpdateManager')) {
const updateManager = wx.getUpdateManager();
updateManager && updateManager.onCheckForUpdate((res) => {
if (res.hasUpdate) {
updateManager.onUpdateReady(() => {
uni.showModal({
title: '更新提示',
content: '新版本已经准备就绪,是否需要重新启动应用?',
success: (res) => {
if (res.confirm) {
uni.clearStorageSync() // storage
updateManager.applyUpdate()
}
}
})
})
updateManager.onUpdateFailed(() => {
uni.showModal({
title: '已有新版本上线',
content: '小程序自动更新失败,请删除该小程序后重新搜索打开哟~~~',
showCancel: false
})
})
} else {
//
}
})
} else {
uni.showModal({
title: '提示',
content: '当前微信版本过低,无法使用该功能,请更新到最新的微信后再重试。',
showCancel: false
})
}
// #endif
},
onShow: function() {
// console.log('App Show')
},
onHide: function() {
// console.log('App Hide')
}
}
</script>
<style lang="scss">
/* 注意要写在第一行同时给style标签加入lang="scss"属性 */
@import './tuniao-ui/index.scss';
@import './tuniao-ui/iconfont.css';
@import './static/css/my.scss';
</style>

20
index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,94 @@
<template>
<view class="demo-title">
<view>
<view v-if="type === 'first'" class="main_title">
<view v-if="leftIcon" class="main_title__icon main_title__icon--left" :class="[`tn-icon-${leftIcon}`]"></view>
<view class="main_title__content">{{ title }}</view>
<view v-if="rightIcon" class="main_title__icon main_title__icon--right" :class="[`tn-icon-${rightIcon}`]"></view>
</view>
<view v-if="type === 'second'" class="second_title">
<view class="second_title__content">{{ title }}</view>
</view>
</view>
<view class="content" :class="[{
'content--padding': contentPadding
}]">
<slot></slot>
</view>
</view>
</template>
<script>
export default {
name: 'demo-title',
options: {
// Vue(shadow)
virtualHost: true
},
props: {
//
type: {
type: String,
default: 'first'
},
//
title: {
type: String,
default: ''
},
//
leftIcon: {
type: String,
default: 'star'
},
//
rightIcon: {
type: String,
default: 'star'
},
//
contentPadding: {
type: Boolean,
default: true
}
}
}
</script>
<style lang="scss" scoped>
.main_title {
display: flex;
align-items: center;
justify-content: center;
margin-top: 50rpx;
font-size: 36rpx;
font-weight: bold;
&__content {
padding: 0 18rpx;
}
&__icon {
font-size: 34rpx;
}
}
.second_title {
margin: 24rpx 0;
margin-left: 30rpx;
&__content {
font-size: 32rpx;
font-weight: bold;
}
}
.content {
margin-top: 30rpx;
&--padding {
margin-left: 30rpx;
margin-right: 30rpx;
}
}
</style>

View File

@ -0,0 +1,689 @@
<template>
<view class="dynamic-demo">
<!-- 效果预览窗口 -->
<view v-if="!noDemo" class="demo-container" :class="{'demo-container--full': full}">
<view class="demo">
<slot></slot>
</view>
<!-- 提示信息 -->
<view v-if="haveTips">
<view class="demo__tips__icon" @click="demoTipsClick">
<view class="icon tn-icon-help"></view>
</view>
<view class="demo__tips__content"
:class="[showContentTips ? 'demo__tips__content--show' : 'demo__tips__content--hide']">
<view v-for="(item,index) in tipsData" :key="index" class="demo__tips__content--item">{{ item }}</view>
</view>
</view>
</view>
<!-- 模式切换 -->
<view v-if="multiMode" class="mode-switch">
<view class="mode-switch__container">
<view v-for="(item, index) in sectionModeListInfos" :key="index" class="mode-switch__item"
:class="[`mode-switch-item-${index}`,{'mode-switch__item--active': modeIndex === index}]"
@click="switchMode(index)">{{ item.name }}</view>
<!-- 滑块样式 -->
<view class="mode-switch__slider" :style="[modeSwitchSliderStyle]"></view>
</view>
</view>
<!-- 组件对应可选项容器 -->
<view class="section-container">
<scroll-view
class="section__scroll-view"
:class="{'section__scroll-view--auto': sectionScrollViewStyle.height === 'auto'}"
:style="[sectionScrollViewStyle]"
:scroll-y="sectionScrollViewStyle.height !== 'auto'"
>
<block v-for="(item,index) in btnsList" :key="index">
<view class="section__content" :class="{'section__content--visible': item.show}">
<view class="section__content__title">
<view class="section__content__title__left-line" :class="[`tn-main-gradient-${tuniaoColorList[index]}`]"></view>
<view class="section__content__title--text tn-text-ellipsis" :class="[`tn-main-gradient-${tuniaoColorList[index]}`]">{{ item.title }}</view>
<view class="section__content__title__right-line" :class="[`tn-main-gradient-${tuniaoColorList[index]}`]"></view>
</view>
<view class="section__content__btns">
<view v-for="(section_btn,section_index) in item.optional" :key="section_index"
class="section__content__btns__item" :class="[`tn-main-gradient-${tuniaoColorList[index]}--light`]" @click="sectionBtnClick(index, section_index)">
<view class="section__content__btns__item__bg"
:class="[`tn-main-gradient-${tuniaoColorList[index]}`, {'section__content__btns__item__bg--active':sectionIndex[modeIndex][index]['value'] === section_index}]"></view>
<view class="section__content__btns__item--text tn-text-ellipsis"
:class="[sectionIndex[modeIndex][index]['value'] === section_index ? 'section__content__btns__item--text--active' : `tn-color-${tuniaoColorList[index]}`]">{{ section_btn }}</view>
</view>
</view>
</view>
</block>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
name: 'dynamic-demo-template',
props: {
//
sectionList: {
type: Array,
default() {
return []
}
},
//
tips: {
type: [String, Array],
default: ''
},
//
full: {
type: Boolean,
default: false
},
// 使
customBar: {
type: Boolean,
default: true
},
//
fullWindowsScroll: {
type: Boolean,
default: false
},
//
noDemo: {
type: Boolean,
default: false
}
},
computed: {
tipsData() {
if (typeof this.tips === 'string') {
return [this.tips]
}
return this.tips
},
haveTips() {
return this.tips && this.tips.length > 0
},
multiMode() {
return this.sectionList.length > 1
},
sectionModeList() {
return this.sectionList.map((item) => {
return item.name
})
}
},
data() {
return {
//
tuniaoColorList: this.$tn.color.getTuniaoColorList(),
// prop
_sectionList: [],
//
sectionModeListInfos: [],
//
modeIndex: 0,
//
modeSwitchSliderStyle: {
width: 0,
left: 0
},
//
showContentTips: false,
//
sectionScrollViewStyle: {
height: 0
},
//
btnsList: [],
//
sectionIndex: [],
// 使scroll-view
sectionScrollFlag: true
}
},
watch: {
sectionList: {
handler(value) {
// sectionList
this.initSectionBtns()
},
deep: true
},
sectionScrollFlag(value) {
if (!value) {
this.sectionScrollViewStyle.height = 'auto'
}
},
fullWindowsScroll: {
handler(value) {
if (value) {
this.sectionScrollViewStyle.height = 'auto'
}
},
immediate: true
}
},
created() {
//
this.sectionModeListInfos = this.sectionModeList.map((item) => {
return {
name: item
}
})
//
this.initSectionBtns()
},
mounted() {
//
// setTimeout(() => {
// // scroll-view
// this.initSectionScrollView()
// if (this.multiMode) {
// //
// this.getModeTabsInfo()
// }
// }, 10)
this.$nextTick(() => {
// scroll-view
this.initSectionScrollView()
if (this.multiMode) {
//
this.getModeTabsInfo()
}
})
},
methods: {
//
initSectionScrollView() {
//
if (this.fullWindowsScroll) {
return
}
//
uni.getSystemInfo({
success: (systemInfo) => {
//
const navBarHeight = this.customBar ? 0 : this.vuex_custom_bar_height
if (this.multiMode) {
uni.createSelectorQuery().in(this).select('.mode-switch').boundingClientRect(data => {
if (data.bottom >= systemInfo.safeArea.height) {
this.sectionScrollFlag = false
} else {
this.sectionScrollFlag = true
const containerBaseHeight = systemInfo.safeArea.height - data.bottom
this.sectionScrollViewStyle.height = (containerBaseHeight - navBarHeight) + systemInfo.statusBarHeight - uni.upx2px(75) + 'px'
}
}).exec()
} else {
if (!this.noDemo) {
uni.createSelectorQuery().in(this).select('.demo-container').boundingClientRect(data => {
if (data.bottom >= systemInfo.safeArea.height) {
this.sectionScrollFlag = false
} else {
this.sectionScrollFlag = true
const containerBaseHeight = systemInfo.safeArea.height - data.bottom
this.sectionScrollViewStyle.height = (containerBaseHeight - navBarHeight) + systemInfo.statusBarHeight - uni.upx2px(75) + 'px'
}
}).exec()
} else {
this.sectionScrollFlag = false
}
}
}
})
},
//
updateSectionScrollView() {
this.$nextTick(() => {
this.initSectionScrollView()
})
},
// tab
getModeTabsInfo() {
let view = uni.createSelectorQuery().in(this)
for (let i = 0; i < this.sectionModeListInfos.length; i++) {
view.select('.mode-switch-item-' + i).boundingClientRect()
}
view.exec(res => {
//
if (!res.length) {
setTimeout(() => {
this.getModeTabsInfo()
}, 10)
return
}
// list
res.map((item, index) => {
this.sectionModeListInfos[index].width = item.width
})
//
this.modeSwitchSliderStyle.width = this.sectionModeListInfos[0].width + 'px'
//
this.modeSliderPosition()
})
},
//
modeSliderPosition() {
let left = 0
//
this.sectionModeListInfos.map((item, index) => {
if (index < this.modeIndex) left += item.width
})
this.modeSwitchSliderStyle.left = left + 'px'
},
//
switchMode(index) {
//
if (index === this.modeIndex) return
this.modeIndex = index
this.modeSliderPosition()
this.updateSectionBtns()
this.$emit('modeClick', {
index: index
})
},
//
demoTipsClick() {
this.showContentTips = !this.showContentTips
},
//
initSectionBtns() {
this.sectionIndex = []
this.sectionIndex = this.sectionList.map((item) => {
if (item.hasOwnProperty('section') && item.section.length > 0) {
return Array(item.section.length).fill({
value: 0,
change: false
})
} else {
return []
}
})
this._sectionList = this.$tn.deepClone(this.sectionList)
// show
this._sectionList.map((item) => {
const section = item.section.map((section_item) => {
if (!section_item.hasOwnProperty('show')) {
section_item.show = true
}
return section_item
})
item.section = section
return item
})
//
this.updateSectionBtns()
},
//
updateSectionBtns(sectionIndex = -1, showState = true) {
let sectionOptional = this._sectionList[this.modeIndex]['section']
this.btnsList = sectionOptional.map((item, index) => {
//
let changeValue = this.sectionIndex[this.modeIndex][index]['change'] || false
let currentSectionIndexValue = this.sectionIndex[this.modeIndex][index]['value'] || 0
// (使)
let indexValue = changeValue ? currentSectionIndexValue : item.hasOwnProperty('current') ? item.current : 0
//
let show = (sectionIndex !== -1 && sectionIndex === index) ? showState : item.hasOwnProperty('show') ? item.show : true
//
if (indexValue < 0) {
indexValue = 0
}
if (indexValue >= item.optional.length) {
indexValue = item.optional.length
}
// this.sectionIndex[this.modeIndex][index]['value'] = indexValue
this.$set(this.sectionIndex[this.modeIndex], index, {value: indexValue, change: changeValue})
item.show = show
return item
})
},
//
updateSectionBtnsState(sectionIndex = -1, showState = true) {
// sectionIndex
if (this.$tn.array.isArray(sectionIndex)) {
if (sectionIndex.length === 0) {
return
}
sectionIndex = sectionIndex.filter((item) => item >= 0 && item < this.sectionList[this.modeIndex]['section'].length)
sectionIndex.map((item) => {
this.btnsList[item]['show'] = showState
this._sectionList[this.modeIndex]['section'][item]['show'] = showState
})
} else {
if (sectionIndex < 0 || sectionIndex >= this.sectionList[this.modeIndex]['section'].length) {
return
}
//
this.btnsList[sectionIndex]['show'] = showState
this._sectionList[this.modeIndex]['section'][sectionIndex]['show'] = showState
}
},
//
updateSectionBtnsValue(modeIndex = 0, sectionIndex = -1, value = 0) {
if (sectionIndex < 0 || sectionIndex >= this.sectionList[modeIndex]['section'].length) {
return
}
// showStatefalse
this.sectionIndex[modeIndex][sectionIndex] = {
value,
change: true
}
},
//
sectionBtnClick(index, sectionIndex) {
// if (this.sectionIndex[this.modeIndex][index] === sectionIndex) {
// return
// }
this.$set(this.sectionIndex[this.modeIndex], index, {value: sectionIndex, change: true})
this.$emit('click', {
methods: this.btnsList[index]['methods'],
index: sectionIndex,
name: this.btnsList[index]['optional'][sectionIndex]
})
}
}
}
</script>
<style lang="scss" scoped>
.dynamic-demo {
padding-top: 78rpx;
/* 顶部模式切换start */
.mode-switch {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-top: 75rpx;
padding: 0 30rpx;
&__container {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
width: 476rpx;
height: 62rpx;
background-color: #FFFFFF;
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 3, 72, 0.1);
border-radius: 31rpx;
}
&__item {
flex: 1;
height: 62rpx;
width: 100%;
line-height: 62rpx;
text-align: center;
font-size: 28rpx;
color: $tn-font-sub-color;
z-index: 2;
transition: all 0.3s;
&--active {
color: #FFFFFF;
font-weight: bold;
}
}
&__slider {
position: absolute;
height: 62rpx;
border-radius: 31rpx;
// background-image: linear-gradient(-86deg, #FF8359 0%, #FFDF40 100%);
background-image: linear-gradient(-86deg, #00C3FF 0%, #58FFF5 100%);
box-shadow: 1rpx 10rpx 24rpx 0rpx #00C3FF77;
z-index: 1;
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
}
/* 顶部模式切换end */
/* 演示内容展示start */
.demo-container {
min-height: 327rpx;
width: calc(100% - 60rpx);
background-color: #FFFFFF;
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 3, 72, 0.1);
margin: 0 30rpx 5rpx 30rpx;
border-radius: 20rpx;
position: relative;
display: flex;
justify-content: center;
align-items: center;
&--full {
display: inline-block;
padding-bottom: 20rpx;
min-height: 0rpx;
padding: 10rpx 20rpx 30rpx;
}
.demo {
padding-top: 70rpx;
&__tips {
&__icon {
position: absolute;
top: 20rpx;
right: 16rpx;
width: 39rpx;
height: 39rpx;
line-height: 39rpx;
font-size: 39rpx;
.icon {
background: linear-gradient(-45deg, #FF8359 0%, #FFDF40 100%);
-webkit-background-clip: text;
color: transparent;
text-shadow: 0rpx 10rpx 10rpx rgba(255, 156, 82, 0.2);
}
}
&__content {
position: absolute;
top: 65rpx;
right: 16rpx;
font-size: 20rpx;
margin-left: 20rpx;
word-wrap: normal;
display: flex;
flex-direction: column;
background-color: #E6E6E6;
padding: 20rpx;
border-radius: 10rpx;
transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1);
transform-origin: 0 0;
z-index: 999999;
&--hide {
transform: scaleY(0);
}
&--show {
transform: scaleY(100%);
&::after {
content: "";
width: 0px;
height: 0px;
border-width: 4px;
border-style: solid;
border-color: transparent transparent rgba(149, 149, 149, 0.1) transparent;
position: absolute;
top: -8px;
right: 6px;
}
}
}
}
}
}
/* 演示内容展示end */
/* 可选项start */
.section-container {
width: 100%;
height: auto;
margin-top: 70rpx;
.section {
&__content {
margin-top: 70rpx;
display: none;
&--visible {
display: block;
&:last-child {
padding-bottom: calc(70rpx + env(safe-area-inset-bottom));
}
}
&:nth-child(1) {
margin-top: 0rpx;
}
&__title {
display: flex;
justify-content: center;
align-items: center;
margin: 0 30rpx;
text-align: center;
&__left-line,
&__right-line {
width: 100rpx;
height: 2rpx;
position: relative;
}
&__left-line {
&::after {
content: '';
background: inherit;
width: 12rpx;
height: 12rpx;
position: absolute;
top: -12rpx;
right: 0rpx;
border-radius: 50%;
transform: translateY(50%);
}
}
&__right-line {
&::after {
content: '';
background: inherit;
width: 12rpx;
height: 12rpx;
position: absolute;
top: -12rpx;
left: 0rpx;
border-radius: 50%;
transform: translateY(50%);
}
}
&--text {
-webkit-background-clip: text;
color: transparent;
min-width: 124rpx;
height: 30rpx;
font-size: 32rpx;
line-height: 1;
margin: 0 35rpx;
}
}
&__btns {
width: calc(100% - 60rpx);
margin: 0 30rpx;
margin-top: 29rpx;
padding: 50rpx 30rpx 0rpx 0rpx;
background-color: #FFFFFF;
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 3, 72, 0.1);
border-radius: 20rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
&__item {
max-width: 30%;
padding: 17rpx 36rpx;
border-radius: 10rpx;
margin-bottom: 40rpx;
margin-left: 40rpx;
position: relative;
z-index: 1;
// &::before {
// content: " ";
// position: absolute;
// top: 10rpx;
// left: 1rpx;
// width: 100%;
// height: 100%;
// background: inherit;
// filter: blur(24rpx);
// opacity: 1;
// z-index: -1;
// }
&__bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: inherit;
z-index: -1;
opacity: 0;
transform: scale(0);
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
&--active {
opacity: 1;
transform: scale(1);
}
}
&--text {
font-size: 24rpx;
line-height: 1.2em;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
&--active {
color: #FFFFFF;
}
}
}
}
}
}
}
/* 可选项end */
}
</style>

View File

@ -0,0 +1,147 @@
<template>
<view class="multiple-options">
<view class="list">
<block v-for="(item, index) in listData" :key="index">
<view
class="list__item"
:class="[`tn-main-gradient-${tuniaoColorList[item.bgColorIndex]}--light`]"
@tap="navOptionsPage(item.url)"
>
<view class="list__content">
<view class="list__content__title">{{ item.title }}</view>
<view class="list__content__desc">{{ item.desc }}</view>
</view>
<view class="list__icon">
<view class="list__icon__main" :class="[`tn-icon-${item.mainIcon}`, `tn-main-gradient-${tuniaoColorList[item.bgColorIndex]}`]"></view>
<view class="list__icon__sub" :class="[`tn-icon-${item.subIcon}`, `tn-main-gradient-${tuniaoColorList[item.bgColorIndex]}`]"></view>
</view>
</view>
</block>
</view>
</view>
</template>
<script>
export default {
name: 'multiple-options-demo',
props: {
//
list: {
type: Array,
default() {
return []
}
}
},
data() {
return {
//
tuniaoColorList: [
'red',
'purplered',
'purple',
'bluepurple',
'aquablue',
'blue',
'indigo',
'cyan',
'teal',
'green',
'orange',
'orangered'
],
listData: []
}
},
watch: {
list(val) {
this.initList()
}
},
created() {
this.initList()
},
methods: {
//
initList() {
//
this.listData = this.list.map((item, index) => {
item.bgColorIndex = this.getBgNum()
item.mainIcon = item?.mainIcon || 'computer-fill'
item.subIcon = item?.subIcon || 'share'
return item
})
},
//
navOptionsPage(url) {
uni.navigateTo({
url: url
})
},
//
getBgNum() {
return Math.floor((Math.random() * this.tuniaoColorList.length))
}
}
}
</script>
<style lang="scss" scoped>
.list {
&__item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: calc(100% - 60rpx);
margin: 108rpx 30rpx 0rpx 30rpx;
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 3, 72, 0.1);
border-radius: 20rpx;
}
&__content {
flex: 1;
// color: $tn-font-color;
margin: 34rpx 0rpx 27rpx 37rpx;
&__title {
font-size: 36rpx;
font-weight: bold;
margin-bottom: 12rpx;
}
&__desc {
font-size: 28rpx;
}
}
&__icon {
flex: 1;
margin-right: 26rpx;
position: relative;
&__main, &__sub {
-webkit-background-clip: text;
color: transparent;
position: absolute;
transition: transform 0.25s ease;
}
&__main {
font-size: 200rpx;
width: 190rpx;
line-height: 200rpx;
top: 0;
right: 0rpx;
transform: translateY(-60%);
}
&__sub {
font-size: 70rpx;
top: 0;
right: 175rpx;
transform: translateY(-5rpx);
}
}
}
</style>

View File

@ -0,0 +1,169 @@
<template>
<view class="nav-index-button" :style="{bottom: `${bottom}rpx`, right: `${right}rpx`}" @tap.stop="navIndex">
<view class="nav-index-button__content">
<view class="nav-index-button__content--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur tn-cool-bg-color-7">
<view class="tn-icon-home-vertical-fill"></view>
</view>
</view>
<view class="nav-index-button__meteor">
<view class="nav-index-button__meteor__wrapper">
<view v-for="(item,index) in 6" :key="index" class="nav-index-button__meteor__item" :style="{transform: `rotateX(${-60 + (30 * index)}deg) rotateZ(${-60 + (30 * index)}deg)`}">
<view class="nav-index-button__meteor__item--pic"></view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'nav-index-button',
props: {
//
bottom: {
type: [Number, String],
default: 300
},
//
right: {
type: [Number, String],
default: 75
},
//
indexPath: {
type: String,
default: '/pages/index/index'
}
},
methods: {
//
navIndex() {
//
const pages = getCurrentPages()
if (pages && pages.length > 0) {
const indexPath = this.indexPath || '/pages/index/index'
const firstPage = pages[0]
if (pages.length == 1 && (!firstPage.route || firstPage.route != indexPath.substring(1, indexPath.length))) {
uni.reLaunch({
url: indexPath
})
} else {
uni.navigateBack({
delta: 1
})
}
} else {
uni.reLaunch({
url: indexPath
})
}
}
}
}
</script>
<style lang="scss" scoped>
.nav-index-button {
position: fixed;
animation: suspension 3s ease-in-out infinite;
z-index: 999999;
&__content {
position: absolute;
width: 100rpx;
height: 100rpx;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
&--icon {
width: 100rpx;
height: 100rpx;
font-size: 60rpx;
border-radius: 50%;
margin-bottom: 18rpx;
position: relative;
z-index: 1;
transform: scale(0.85);
&::after {
content: " ";
position: absolute;
z-index: -1;
width: 100%;
height: 100%;
left: 0;
bottom: 0;
border-radius: inherit;
opacity: 1;
transform: scale(1, 1);
background-size: 100% 100%;
background-image: url(https://resource.tuniaokj.com/images/cool_bg_image/icon_bg6.png);
}
}
}
&__meteor {
position: absolute;
top: 50%;
left: 50%;
width: 100rpx;
height: 100rpx;
transform-style: preserve-3d;
transform: translate(-50%, -50%) rotateY(75deg) rotateZ(10deg);
&__wrapper {
width: 100rpx;
height: 100rpx;
transform-style: preserve-3d;
animation: spin 20s linear infinite;
}
&__item {
position: absolute;
width: 100rpx;
height: 100rpx;
border-radius: 1000rpx;
left: 0;
top: 0;
&--pic {
display: block;
width: 100%;
height: 100%;
background: url(https://resource.tuniaokj.com/images/cool_bg_image/arc3.png) no-repeat center center;
background-size: 100% 100%;
animation: arc 4s linear infinite;
}
}
}
}
@keyframes suspension {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-0.8rem);
}
}
@keyframes spin {
0% {
transform: rotateX(0deg);
}
100% {
transform: rotateX(-360deg);
}
}
@keyframes arc {
to {
transform: rotate(360deg);
}
}
</style>

View File

@ -0,0 +1,52 @@
/**
* 动态参数演示mixin
*/
module.exports = {
data() {
return {
// 效果显示框top的值
contentContainerTop: '0px',
contentContainerIsTop: false,
// 参数显示框top的值
sectionContainerTop: '0px'
}
},
onReady() {
this.updateSectionContainerTop()
},
methods: {
// 处理演示效果框的位置
async _handleContentConatinerPosition() {
// 获取效果演示框的节点信息
const contentContainer = await this._tGetRect('#content_container')
// 获取参数框的节点信息
this._tGetRect('#section_container').then((res) => {
// 判断参数框是否在移动,如果是则更新效果框的位置
// 如果效果框的顶部已经触控到顶部导航栏就停止跟随
if (res.top - contentContainer.bottom != 15) {
const newTop = res.top - (contentContainer.height + uni.upx2px(20))
const minTop = this.vuex_custom_bar_height + 1
if (newTop < minTop) {
this.contentContainerTop = minTop + 'px'
this.contentContainerIsTop = true
} else {
this.contentContainerTop = newTop + 'px'
this.contentContainerIsTop = false
}
}
})
},
// 更新状态切换栏位置信息
updateSectionContainerTop() {
this._tGetRect('#content_container').then((res) => {
this.contentContainerTop = (this.vuex_custom_bar_height + 148) + 'px'
this.sectionContainerTop = (res.height + 20) + 'px'
})
}
},
// 监听页面滚动
onPageScroll() {
this._handleContentConatinerPosition()
}
}

View File

@ -0,0 +1,60 @@
/**
* 演示页面mixin
*/
module.exports = {
data() {
return {
}
},
onLoad() {
// 更新顶部导航栏信息
this.updateCustomBarInfo()
},
methods: {
// 点击左上角返回按钮时触发事件
goBack() {
// 通过判断当前页面的页面栈信息,是否有上一页进行返回,如果没有则跳转到首页
const pages = getCurrentPages()
if (pages && pages.length > 0) {
const firstPage = pages[0]
if (pages.length == 1 && (!firstPage.route || firstPage.route != 'pages/index/index')) {
uni.reLaunch({
url: '/pages/index/index'
})
} else {
uni.navigateBack({
delta: 1
})
}
} else {
uni.reLaunch({
url: '/pages/index/index'
})
}
},
// 更新顶部导航栏信息
async updateCustomBarInfo() {
// 获取vuex中的自定义顶栏的高度
let customBarHeight = this.vuex_custom_bar_height
let statusBarHeight = this.vuex_status_bar_height
// 如果获取失败则重新获取
if (!customBarHeight) {
try {
const navBarInfo = await this.$tn.updateCustomBar()
customBarHeight = navBarInfo.customBarHeight
statusBarHeight = navBarInfo.statusBarHeight
} catch(e) {
setTimeout(() => {
this.updateCustomBarInfo()
}, 10)
return
}
}
// 更新vuex中的导航栏信息
this.$tn.vuex('vuex_status_bar_height', statusBarHeight)
this.$tn.vuex('vuex_custom_bar_height', customBarHeight)
}
}
}

25
main.js Normal file
View File

@ -0,0 +1,25 @@
import App from './App'
import store from './store'
import Vue from 'vue'
Vue.config.productionTip = false
App.mpType = 'app'
// 引入全局TuniaoUI
import TuniaoUI from 'tuniao-ui'
Vue.use(TuniaoUI)
// 引入TuniaoUI提供的vuex简写方法
let vuexStore = require('@/store/$tn.mixin.js')
Vue.mixin(vuexStore)
// 引入TuniaoUI对小程序分享的mixin封装
let mpShare = require('tuniao-ui/libs/mixin/mpShare.js')
Vue.mixin(mpShare)
const app = new Vue({
store,
...App
})
app.$mount()

72
manifest.json Normal file
View File

@ -0,0 +1,72 @@
{
"name" : "云商会",
"appid" : "__UNI__F702B81",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "2"
}

133
pages.json Normal file
View File

@ -0,0 +1,133 @@
{
"easycom": {
"^tn-(.*)": "@/tuniao-ui/components/tn-$1/tn-$1.vue"
},
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path" : "pages/index/home",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/service",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/pizz",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/user",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/discovery",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/directory",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/user_info",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/new_info",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/events_list",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/knowledge_list",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/goods_info",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/events_info",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/goods_list",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
},
{
"path" : "pages/index/search/search",
"style" :
{
"navigationBarTitleText" : "",
"enablePullDownRefresh" : false
}
}
],
"globalStyle": {
"navigationStyle": "custom",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"uniIdRouter": {}
}

337
pages/index/directory.vue Normal file
View File

@ -0,0 +1,337 @@
<template>
<view style="background-color: #EBF4F7;letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" backTitle="" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view style="padding-left: 15rpx;" @click="goBack()">
<text class="tn-icon-left" style="font-size: 40rpx;"></text>
</view>
<view class="tn-margin-top"
style=";text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'会员名录'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view class="tn-flex tn-flex-col-center tn-flex-row-between" style="padding: 30rpx;">
<view>
<text>河南省青年企业家协会</text>
<text class="tn-icon-down-triangle"></text>
</view>
<view>
<text>筛选</text>
</view>
</view>
</view>
<view class="tn-classify__container">
<view class="tn-classify__container__wrap tn-flex tn-flex-nowrap tn-flex-row-around"
style="height: 100vh;background-color: #EBF4F7;">
<!-- 左边容器 -->
<scroll-view class="tn-classify__left-box left-width" scroll-y scroll-with-animation>
<view v-for="(item, index) in tabbar" :key="index" :id="`tabbar_item_${index}`"
style="padding-left: 20rpx;" class="tn-classify__tabbar__item tn-flex tn-flex-col-center"
:class="[tabbarItemClass(index)]" @tap.stop="clickClassifyNav(index)">
<view class="tn-classify__tabbar__item__title">{{ item }}</view>
</view>
</scroll-view>
<!-- 右边容器 -->
<scroll-view class="tn-classify__right-box" scroll-y>
<block>
<view class="tn-classify__content">
<!-- 分类内容子栏目 -->
<view class="tn-classify__content__sub-classify__content ">
<view v-for="(item,index) in list" :key="index">
<view
style="font-weight: 300;;background-color: #EBF4F7;color:#4AA2EF;width: 100%;height: 60rpx;line-height: 60rpx;text-align: center;">
{{item.name}}
</view>
<view v-for="(v,k) in item.list" @click="tn('/pages/index/user_info')"
class="tn-classify__content__sub-classify__content__item tn-flex tn-flex-center tn-flex-col-center">
<!-- 标题有需要可以显示出来 -->
<view style="width: 100rpx;height: 100rpx">
<image src="/static/u1.jpg"
style="width: 100rpx;height: 100rpx;border-radius:50%;">
</image>
</view>
<view style="margin-left: 20rpx;">
<view style="font-size: 28rpx;">
<text>{{v.name}}</text>
<text style="margin-left: 50rpx;">{{v.family}}</text>
</view>
<view style="font-size: 24rpx;margin-top: 10rpx;">
<text>{{v.desc}}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</block>
</scroll-view>
</view>
</view>
<view style="line-height: 40rpx;text-align: center;position: fixed;bottom:15%;right: 20rpx;width: 120rpx;height: 120rpx;border-radius: 50%;background: linear-gradient(270deg, #EE7E45, #EE9657);
box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(12,0,5,0.2);">
<view style="color: #fff;letter-spacing: 2rpx;padding: 20rpx;font-size: 32rpx;">申请入会
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
tabbar: [
'房产建设行业',
'服务行业',
'高新技术行业',
'教育文卫行业',
'能源生产供应',
'农林牧渔',
'物流贸易行业',
'信息行业',
'制造行业',
],
list: [{
name: '主任',
list: [{
'name': '朱荣梅',
'family': '汉族',
'desc': '康桥集团执行总裁'
}]
}, {
name: '副主任',
list: [{
'name': '李小冰',
'family': '汉族',
'desc': '恒达集团(控股)有限公司董事长'
}, {
'name': '李小冰',
'family': '汉族',
'desc': '恒达集团(控股)有限公司董事长'
}]
}, {
name: '会员',
list: [{
'name': '李小冰',
'family': '汉族',
'desc': '恒达集团(控股)有限公司董事长'
}, {
'name': '李小冰',
'family': '汉族',
'desc': '恒达集团(控股)有限公司董事长'
}, {
'name': '李小冰',
'family': '汉族',
'desc': '恒达集团(控股)有限公司董事长'
}, {
'name': '李小冰',
'family': '汉族',
'desc': '恒达集团(控股)有限公司董事长'
}, {
'name': '李小冰',
'family': '汉族',
'desc': '恒达集团(控股)有限公司董事长'
}, {
'name': '李小冰',
'family': '汉族',
'desc': '恒达集团(控股)有限公司董事长'
}, {
'name': '李小冰',
'family': '汉族',
'desc': '恒达集团(控股)有限公司董事长'
}]
}],
topCurrent: 0,
tabbarIndex: 0,
// item
tabbarItemInfo: [],
// scrollViewtop
scrollViewBasicTop: 0,
// scrollView
scrollViewHeight: 0,
// scrollView
leftScrollViewTop: 0,
// scrollView
rightScrollViewTop: 0,
// tabbar
currentTabbarIndex: 0
}
},
computed: {
tabbarItemClass() {
return index => {
if (index === this.currentTabbarIndex) {
return 'tn-classify__tabbar__item--active tn-bg-white'
} else {
let clazz = ''
if (this.currentTabbarIndex > 0 && index === this.currentTabbarIndex - 1) {
clazz += ' tn-classify__tabbar__item--active--prev'
}
if (this.currentTabbarIndex < this.tabbar.length && index === this.currentTabbarIndex + 1) {
clazz += ' tn-classify__tabbar__item--active--next'
}
return clazz
}
}
}
},
mounted() {
},
methods: {
clickClassifyNav(index) {
this.currentTabbarIndex = index
//this.handleLeftScrollView(index)
//this.switchClassifyContent();
},
// scrollView
handleLeftScrollView(index) {
const tabbarItemTop = this.tabbarItemInfo[index].top - this.scrollViewBasicTop
if (tabbarItemTop > this.scrollViewHeight / 2) {
this.leftScrollViewTop = tabbarItemTop - (this.scrollViewHeight / 2) + this.tabbarItemInfo[index]
.height
} else {
this.leftScrollViewTop = 0
}
},
//
switchClassifyContent() {
this.rightScrollViewTop = 1
this.$nextTick(() => {
this.rightScrollViewTop = 0
})
//this.classifyContent.subClassify[0].title = this.tabbar[this.currentTabbarIndex]
},
tn(url) {
uni.navigateTo({
url: url
})
},
goBack() {
if (getCurrentPages().length > 1) {
uni.navigateBack()
} else {
uni.redirectTo({
url: '/pages/index/index'
})
}
}
}
}
</script>
<style lang="scss" scoped>
/* 自定义导航栏内容 start */
.custom-nav {
height: 100%;
&__back {
margin: auto 30rpx;
margin-right: 10rpx;
flex-basis: 5%;
width: 100rpx;
position: absolute;
}
}
.left-width {
flex-basis: 42%;
}
/* 自定义导航栏内容 end */
.tn-classify {
/* 搜索栏 start */
/* 搜索栏 end */
/* 分类列表和内容 strat */
&__container {}
&__left-box {}
&__right-box {
background-color: #FFFFFF;
}
/* 分类列表和内容 end */
/* 侧边导航 start */
&__tabbar {
&__item {
height: 90rpx;
&:first-child {
border-top-right-radius: 0rpx;
}
&:last-child {
border-bottom-right-radius: 0rpx;
}
&--active {
background-color: #FFFFFF;
position: relative;
// font-weight: bold;
color: #4AA2EF;
&--prev {
border-bottom-right-radius: 26rpx;
}
&--next {
border-top-right-radius: 26rpx;
}
}
}
}
/* 侧边导航 end */
/* 分类内容 start */
&__content {
margin: 18rpx;
/* 推荐商品 start */
&__recomm {
margin-bottom: 40rpx;
&__swiper {}
}
/* 推荐商品 end */
/* 子栏目 start */
&__sub-classify {
margin-bottom: 20rpx;
padding-bottom: 40rpx;
&--title {
font-weight: bold;
margin-bottom: 18rpx;
}
&__content {
&__item {
padding: 20rpx;
}
}
}
/* 子栏目 end */
}
/* 分类内容 end */
}
</style>

114
pages/index/discovery.vue Normal file
View File

@ -0,0 +1,114 @@
<template>
<view style="letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view class="tn-margin-top"
style="text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'智慧云商协'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view style="position: relative;">
<swiper class="card-swiper" :circular="true" :autoplay="true" duration="500" interval="8000"
@change="cardSwiper" style="height: 370rpx;">
<swiper-item v-for="(item,index) in swiperList" :key="index" :class="cardCur==index?'cur':''"
style="padding: 0px;border-radius: 0;">
<view class="swiper-item image-banner"
:style="'background-image:url('+ item.url + ');background-size: 100%; background-repeat: no-repeat;border-radius: 0;'">
</view>
</swiper-item>
</swiper>
<view style="position: absolute;top: 10px;right: 10px;">
<view
style="position: relative;;width: 70rpx;height: 60rpx;background-color: rgba(255, 255, 255, 0.9);border-radius: 8rpx;box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(12,0,5,0.2);">
<image src="/static/c1455.png" mode="widthFix"
style="width: 40rpx;position: absolute;left: 0;right: 0;margin: 0 auto;top: 15rpx;"></image>
</view>
</view>
</view>
</view>
<view style="padding:20rpx 30rpx;">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-between" style="font-size: 30rpx;">
<view>协会地址</view>
<view>郑州市金水路17号</view>
</view>
<view style="margin: 20rpx 0rpx;font-size: 30rpx;"
class="custom-nav tn-flex tn-flex-col-center tn-flex-row-between">
<view>协会邮箱</view>
<view>hnqqx@126.com</view>
</view>
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-between" style="font-size: 30rpx;">
<view>联系方式</view>
<view>0371-65560130</view>
</view>
</view>
<view style="height: 10rpx;background-color: #EBF4F7;"></view>
<view style="padding:10rpx 30rpx 180rpx 30rpx;">
<tn-tabs :list="list" :isScroll="false" :activeItemStyle="{'fontWeight':'600','fontSize':'30rpx'}"
style="font-weight: ;" activeColor="#000" :barWidth="50" :barHeight="6"
:barStyle="{'background': 'linear-gradient(-45deg, #4AA2EF, #3A7FF4)','borderRadius': '4rpx'}"
:current="current" name="name" @change="change"></tn-tabs>
<view style="margin-top: 20rpx;position: relative;">
<image src="/static/b1.png"
style="width: 100rpx;height: 100rpx;position: absolute;left: 0;right: 0;top: 38%;z-index: 10;margin: 0 auto;">
</image>
<image src="/static/t7.jpg" style="width: 100%;border-radius: 20rpx;" mode="widthFix"></image>
</view>
<view style="line-height: 50rpx; text-indent: 2em;margin-top: 20rpx;">
<p>河南省青年企业家协会HeNan Young
EntrepreneursAssociation简称为省青企协是全省性的青年企业家的群众组织是共青团联系青年企业家的桥梁和纽带是具有独立法人资格的非营利性社会团体是中国青年企业家协会河南省青年联合会河南省企业联合会河南省企业家协会的团体会员
</p>
</view>
</view>
<view style="position: fixed;bottom: 12%;width: 100%;">
<view class="tn-flex tn-flex-row-around">
<view @click="openUrl('/pages/index/directory')"
style="color: #fff;;letter-spacing: 10rpx;line-height: 70rpx;;text-align: center;;width: 300rpx;height: 70rpx;background: linear-gradient(-45deg, #4AA2EF, #3A7FF4);border-radius: 50rpx;">
会员名录</view>
<view
style="color: #fff;;letter-spacing: 10rpx;line-height: 70rpx;;text-align: center;;width: 300rpx;height: 70rpx;background: linear-gradient(270deg, #EE7E45, #EE9657);border-radius: 50rpx;">
申请入会</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0,
swiperList: [{
url: '/static/banner1.jpg',
}],
list: [{
name: '协会简介'
}, {
name: '组织架构'
}, {
name: '规章制度',
}, {
name: '入会须知',
}],
current: 0
}
},
methods: {
openUrl(url) {
uni.navigateTo({
url: url
})
},
change(e) {
this.current = e;
},
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,84 @@
<template>
<view style="letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" backTitle="" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view style="padding-left: 15rpx;" @click="goBack()">
<text class="tn-icon-left" style="font-size: 40rpx;"></text>
</view>
<view class="tn-margin-top"
style=";text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'活动详情'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view style="padding: 30rpx;">
<view style="text-align: center;font-size:40rpx;font-weight: 600">青年企业家能力提升计划培训班(第20期)</view>
<view style="margin-top: 30rpx;color: #999;">
<text>时间2023-12-25</text>
<text style="margin-left: 20rpx;">来源河南青企协</text>
</view>
<view style="padding: 20rpx 0rpx;">
<tn-button padding="0px" width="100rpx" height="40rpx" size="sm" backgroundColor="#6BC7F0 "
fontColor="tn-color-white">报名中</tn-button>
</view>
<view style="width: 100%;height: 2rpx;background-color: #eee;margin-top: 0rpx;"></view>
<view style="padding-bottom: 200rpx;">
<view style="margin-top: 20rpx;position: relative;">
<image src="/static/b1.png"
style="width: 100rpx;height: 100rpx;position: absolute;left: 0;right: 0;top: 38%;z-index: 10;margin: 0 auto;">
</image>
<image src="/static/t7.jpg" style="width: 100%;border-radius: 20rpx;" mode="widthFix">
</image>
</view>
<view style="line-height: 50rpx; text-indent: 2em;margin-top: 20rpx;">
<p>河南省青年企业家协会HeNan Young
EntrepreneursAssociation简称为省青企协是全省性的青年企业家的群众组织是共青团联系青年企业家的桥梁和纽带是具有独立法人资格的非营利性社会团体是中国青年企业家协会河南省青年联合会河南省企业联合会河南省企业家协会的团体会员
</p>
<p>河南省青年企业家协会HeNan Young
EntrepreneursAssociation简称为省青企协是全省性的青年企业家的群众组织是共青团联系青年企业家的桥梁和纽带是具有独立法人资格的非营利性社会团体是中国青年企业家协会河南省青年联合会河南省企业联合会河南省企业家协会的团体会员
</p>
<p>河南省青年企业家协会HeNan Young
EntrepreneursAssociation简称为省青企协是全省性的青年企业家的群众组织是共青团联系青年企业家的桥梁和纽带是具有独立法人资格的非营利性社会团体是中国青年企业家协会河南省青年联合会河南省企业联合会河南省企业家协会的团体会员
</p>
</view>
</view>
</view>
</view>
<view style="position: fixed;bottom: 5%;width: 100%;">
<view class="tn-flex tn-flex-row-around">
<view
style="color: #fff;;letter-spacing: 10rpx;line-height: 70rpx;;text-align: center;;width: 400rpx;height: 70rpx;background: linear-gradient(270deg, #EE7E45, #EE9657);border-radius: 50rpx;">
报名参加</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0
}
},
methods: {
goBack() {
if (getCurrentPages().length > 1) {
uni.navigateBack()
} else {
uni.redirectTo({
url: '/pages/index/index'
})
}
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,74 @@
<template>
<view style="background-color: #EBF4F7;letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" backTitle="" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view style="padding-left: 15rpx;" @click="goBack()">
<text class="tn-icon-left" style="font-size: 40rpx;"></text>
</view>
<view class="tn-margin-top"
style="text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'协会活动'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view style="padding:10px 30rpx;">
<view v-for="(item,index) in 10" class="tn-flex tn-flex-row-between"
@click="openUrl('/pages/index/events_info')"
style="background-color: #ffffff;padding: 20rpx;border-radius: 10rpx;margin-bottom: 10rpx;">
<view style="position:relative;">
<view style="font-size: 28rpx;">青年企业家能力提升 计划培训班(第20期)</view>
<view class="tn-flex tn-flex-row-between"
style="position: absolute;bottom: 0rpx;width: 100%;color: #808080;">
<view>2023-12-25</view>
<view>
<tn-button v-if="index%2==0&&index!=2" padding="0px" width="100rpx" height="40rpx"
size="sm" backgroundColor="#6BC7F0 " fontColor="tn-color-white">报名中</tn-button>
<tn-button v-if="index%2==1&&index!=2" padding="0px" width="100rpx" height="40rpx"
size="sm" backgroundColor="#EE9556 " fontColor="tn-color-white">预告</tn-button>
<tn-button v-if="index==2" padding="0px" width="100rpx" height="40rpx" size="sm"
backgroundColor="#E12B33 " fontColor="tn-color-white">进行中</tn-button>
</view>
</view>
</view>
<view style="margin-left: 20rpx;">
<image src="/static/hd1.jpg" style="width: 200rpx;height: 135rpx;;border-radius: 10rpx;">
</image>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0
}
},
methods: {
openUrl(url) {
uni.navigateTo({
url: url
})
},
goBack() {
if (getCurrentPages().length > 1) {
uni.navigateBack()
} else {
uni.redirectTo({
url: '/pages/index/index'
})
}
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,86 @@
<template>
<view style="letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" backTitle="" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view style="padding-left: 15rpx;" @click="goBack()">
<text class="tn-icon-left" style="font-size: 40rpx;"></text>
</view>
<view class="tn-margin-top"
style=";text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'供需服务'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view style="padding: 30rpx;">
<view style="text-align: center;font-size:40rpx;font-weight: 600">劲捷飞燕系列P056C旅拍三脚架碳纤维摄影</view>
<view style="margin-top: 30rpx;color: #999;">
<text>时间2023-12-25</text>
<text style="margin-left: 20rpx;">来源洛阳灵睿网络技术有限公司</text>
</view>
<view>
<tn-tag shape="radius" width="auto" backgroundColor="#F7F7F7" fontColor="#808080">洛阳市</tn-tag>
<tn-tag shape="radius" margin="20rpx" width="auto" backgroundColor="#EBF4F7"
fontColor="#3377FF">合作意向18人</tn-tag>
<tn-tag shape="radius" width="auto" backgroundColor="#F7F3EB" fontColor="#FF8C19">项目金额1万以下</tn-tag>
</view>
<view style="width: 100%;height: 2rpx;background-color: #eee;margin-top: 0rpx;"></view>
<view style="padding-bottom: 200rpx;">
<view style="margin-top: 20rpx;position: relative;">
<image src="/static/b1.png"
style="width: 100rpx;height: 100rpx;position: absolute;left: 0;right: 0;top: 38%;z-index: 10;margin: 0 auto;">
</image>
<image src="/static/t7.jpg" style="width: 100%;border-radius: 20rpx;" mode="widthFix">
</image>
</view>
<view style="line-height: 50rpx; text-indent: 2em;margin-top: 20rpx;">
<p>河南省青年企业家协会HeNan Young
EntrepreneursAssociation简称为省青企协是全省性的青年企业家的群众组织是共青团联系青年企业家的桥梁和纽带是具有独立法人资格的非营利性社会团体是中国青年企业家协会河南省青年联合会河南省企业联合会河南省企业家协会的团体会员
</p>
<p>河南省青年企业家协会HeNan Young
EntrepreneursAssociation简称为省青企协是全省性的青年企业家的群众组织是共青团联系青年企业家的桥梁和纽带是具有独立法人资格的非营利性社会团体是中国青年企业家协会河南省青年联合会河南省企业联合会河南省企业家协会的团体会员
</p>
<p>河南省青年企业家协会HeNan Young
EntrepreneursAssociation简称为省青企协是全省性的青年企业家的群众组织是共青团联系青年企业家的桥梁和纽带是具有独立法人资格的非营利性社会团体是中国青年企业家协会河南省青年联合会河南省企业联合会河南省企业家协会的团体会员
</p>
</view>
</view>
</view>
</view>
<view style="position: fixed;bottom: 5%;width: 100%;">
<view class="tn-flex tn-flex-row-around">
<view
style="color: #fff;;letter-spacing: 10rpx;line-height: 70rpx;;text-align: center;;width: 400rpx;height: 70rpx;background: linear-gradient(270deg, #EE7E45, #EE9657);border-radius: 50rpx;">
联系TA</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0
}
},
methods: {
goBack() {
if (getCurrentPages().length > 1) {
uni.navigateBack()
} else {
uni.redirectTo({
url: '/pages/index/index'
})
}
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,93 @@
<template>
<view style="background-color: #EBF4F7;letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" backTitle="" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view style="padding-left: 15rpx;" @click="goBack()">
<text class="tn-icon-left" style="font-size: 40rpx;"></text>
</view>
<view class="tn-margin-top"
style=";text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'供需服务'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view style="background-color: #FFFFFF;">
<tn-tabs :list="list" :isScroll="false" :activeItemStyle="{'fontWeight':'600','fontSize':'30rpx'}"
activeColor="#000" :barWidth="50" :barHeight="6" backgroundColor="#FFFFFF"
:barStyle="{'background': 'linear-gradient(-45deg, #4AA2EF, #3A7FF4)','borderRadius': '4rpx'}"
:current="current" name="name" @change="change"></tn-tabs>
</view>
<view style="padding: 30rpx;">
<view v-for="(item,index) in 10" @click="openUrl('/pages/index/goods_info')" style="background-color: #FFF;padding: 20rpx;border-radius: 20rpx;
box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(12,0,5,0.1);margin-bottom: 20rpx;">
<view class="tn-text-ellipsis">
<tn-tag v-if="current==0" shape="radius" size="sm"
backgroundColor="tn-main-gradient-red--reverse" width="60rpx">需求</tn-tag>
<tn-tag v-if="current==1" shape="radius" size="sm" backgroundColor="tn-main-gradient-blue"
width="60rpx">供应</tn-tag>
<text style="vertical-align: middle;padding-left: 20rpx;font-size:30rpx;">
劲捷飞燕系列P056C旅拍三脚架碳纤维摄影旅拍三脚架碳纤维摄影</text>
</view>
<view style="font-size: 24rpx;color: #808080;margin-top: 20rpx;">洛阳灵睿网络技术有限公司</view>
<view>
<tn-tag shape="radius" width="auto" backgroundColor="#F7F7F7" fontColor="#808080">洛阳市</tn-tag>
<tn-tag shape="radius" margin="20rpx" width="auto" backgroundColor="#EBF4F7"
fontColor="#3377FF">合作意向18人</tn-tag>
<tn-tag shape="radius" width="auto" backgroundColor="#F7F3EB"
fontColor="#FF8C19">项目金额1万以下</tn-tag>
</view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-flex-row-center"
style="margin-top: 20rpx;">
<view style="color: #808080;font-size: 24rpx;">2023-12-19</view>
<view>
<tn-button shape="round" :fontSize="22" width="148rpx" height="50rpx"
backgroundColor="tn-main-gradient-orange" fontColor="#ffffff">意向合作</tn-button>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0,
list: [{
name: '供应'
}, {
name: '需求'
}],
current: 0
}
},
methods: {
openUrl(e) {
uni.navigateTo({
url: e
})
},
change(e) {
this.current = e;
},
goBack() {
if (getCurrentPages().length > 1) {
uni.navigateBack()
} else {
uni.redirectTo({
url: '/pages/index/index'
})
}
}
}
}
</script>
<style>
</style>

183
pages/index/home.vue Normal file
View File

@ -0,0 +1,183 @@
<template>
<view style="background-color: #EBF4F7;letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view class="tn-margin-top"
style="text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'智慧云商协'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view class="tn-flex tn-flex-center tn-flex-col-center tn-flex-row-between" style="padding-top: 30rpx;">
<view class="tn-color-gray--dark"
style="width: 100%;margin: 0rpx 30rpx 0 30rpx;border-radius: 100rpx;padding-left: 6rpx;background-color: #ffffff;"
@click="openUrl('/pages/index/search/search')">
<tn-notice-bar :list="searlist" mode="vertical" leftIconName="search"
:duration="6000"></tn-notice-bar>
</view>
<view class="tn-flex" style="margin:0px 30rpx;">
<image src="/static/t1.png" style="width: 35rpx;height: 35rpx;"></image>
<image src="/static/t2.png" style="width: 35rpx;height: 35rpx;margin-left: 40rpx;"></image>
</view>
</view>
<swiper class="card-swiper" :circular="true" :autoplay="true" duration="500" interval="8000"
@change="cardSwiper">
<swiper-item v-for="(item,index) in swiperList" :key="index" :class="cardCur==index?'cur':''">
<view class="swiper-item image-banner"
:style="'background-image:url('+ item.url + ');background-size: cover;border-radius: 15rpx;'">
</view>
</swiper-item>
</swiper>
<view class="indication">
<block v-for="(item,index) in swiperList" :key="index">
<view class="spot" :class="cardCur==index?'active':''"></view>
</block>
</view>
</view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-flex-row-center"
style="padding:0px 30rpx 0rpx 30rpx;text-align: center;font-size: 28rpx;">
<view @click="openUrl()"
style="width: 25%;background: linear-gradient(270deg, #3CBAEA, #6BC7F0);;border-radius: 16rpx;padding:35rpx 20rpx;">
<view>
<image src="/static/c3.png" style="width: 35rpx;height: 35rpx;vertical-align: middle;">
</image>
</view>
<view style="margin-top: 10rpx;">
<text style="color: #FFF;">组织架构</text>
</view>
</view>
<view @click="openUrl('/pages/index/events_list')"
style="width: 25%;background: linear-gradient(270deg, #EE7E45, #EE9657);border-radius: 16rpx;padding:35rpx 20rpx;margin-left:10rpx;">
<view>
<image src="/static/c2.png" style="width: 35rpx;height: 35rpx;vertical-align: middle;">
</image>
</view>
<view style="margin-top: 10rpx;">
<text style="color: #FFF;">协会活动</text>
</view>
</view>
<view @click="openUrl('/pages/index/directory')"
style="width: 25%;background: linear-gradient(270deg, #45B335, #89C33D);border-radius: 16rpx;padding:35rpx 20rpx;margin-left:10rpx;">
<view>
<image src="/static/c1.png" style="width: 35rpx;height: 35rpx;vertical-align: middle;">
</image>
</view>
<view style="margin-top: 10rpx;">
<text style="color: #FFF;">通讯录</text>
</view>
</view>
<view @click="openUrl('/pages/index/knowledge_list')"
style="width: 25%;background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%);;border-radius: 16rpx;padding:35rpx 20rpx;margin-left:10rpx;">
<view>
<image src="/static/c1.png" style="width: 35rpx;height: 35rpx;vertical-align: middle;">
</image>
</view>
<view style="margin-top: 10rpx;">
<text style="color: #FFF;">商学院</text>
</view>
</view>
</view>
<view @click.stop="openUrl('/pages/index/events_list')"
class="tn-flex tn-flex-row-between tn-flex-col-center tn-flex-row-center" style="padding: 30rpx;">
<view style="font-size: 36rpx;">协会活动</view>
<view style="color: #808080;">
<text>更多</text>
<text class="tn-icon-right"></text>
</view>
</view>
<view style="padding-bottom: 30rpx;">
<scroll-view :scroll-x="true" style="padding:0rpx 30rpx;white-space: nowrap;">
<view v-for="(item,index) in 3" @click="openUrl('/pages/index/new_info')"
style="position: relative;;display: inline-block;width: 300rpx;text-align: center;background-color: #FFF;border-radius: 20rpx;overflow: hidden;margin-right: 20rpx;">
<view>
<image src="/static/hd1.jpg" mode="widthFix" style="width: 300rpx;"></image>
</view>
<view style="padding:10rpx 20rpx;font-weight: 400;">
<view class="tn-text-ellipsis-2" style="letter-spacing: 1px;">青年企业家能力提升 计划培训班(第20期)</view>
</view>
<view style="position: absolute;top: 10rpx;left: 10rpx;">
<tn-button v-if="index==0" width="80rpx" height="40rpx" size="sm" backgroundColor="#6BC7F0 "
fontColor="tn-color-white">报名中</tn-button>
<tn-button v-if="index==1" width="80rpx" height="40rpx" size="sm" backgroundColor="#EE9556 "
fontColor="tn-color-white">预告</tn-button>
<tn-button v-if="index==2" width="80rpx" height="40rpx" size="sm" backgroundColor="#E12B33 "
fontColor="tn-color-white">进行中</tn-button>
</view>
</view>
</scroll-view>
</view>
<view style="background-color: #ffffff;">
<tn-tabs :list="list" :isScroll="true" :activeItemStyle="{fontSize:'35rpx',fontWeight:'600'}"
style="font-weight: ;" activeColor="#000000" :current="current" name="name" @change="change"
:fontSize="28"></tn-tabs>
</view>
<view style="padding:10px 30rpx;">
<view v-for="(item,index) in 5" class="tn-flex tn-flex-row-between"
@click="openUrl('/pages/index/new_info')"
style="background-color: #ffffff;padding: 20rpx;border-radius: 10rpx;margin-bottom: 10rpx;">
<view style="position:relative;">
<view style="font-size: 28rpx;">团省委书记王笃波一行到访中青 企协开展对接洽谈</view>
<view class="tn-flex tn-flex-row-between"
style="position: absolute;bottom: 0rpx;width: 100%;color: #808080;">
<view>河南青企协</view>
<view>
<text class="tn-icon-eye"></text>
<text>568</text>
</view>
</view>
</view>
<view style="margin-left: 20rpx;">
<image src="/static/c1.jpg" style="width: 240rpx;border-radius: 10rpx;" mode="widthFix"></image>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0,
searlist: [
'企业家名称/公司名称',
],
cardCur: 0,
isAndroid: true,
swiperList: [{
url: '/static/banner.jpg',
}],
list: [{
name: '最新'
}, {
name: '协会动态'
}, {
name: '协会新闻',
}, {
name: '学习考察',
}],
current: 0
}
},
methods: {
change(e) {
this.current = e;
},
openUrl(url) {
uni.navigateTo({
url: url
})
}
}
}
</script>
<style lang="scss" scoped>
</style>

126
pages/index/index.vue Normal file
View File

@ -0,0 +1,126 @@
<template>
<view class="start-index">
<view v-if="tabberPageLoadFlag[0]" :style="{display: currentIndex === 0 ? '' : 'none'}">
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
<Home ref="home"></Home>
</scroll-view>
</view>
<view v-if="tabberPageLoadFlag[1]" :style="{display: currentIndex === 1 ? '' : 'none'}">
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
<Service ref="service"></Service>
</scroll-view>
</view>
<view v-if="tabberPageLoadFlag[2]" :style="{display: currentIndex === 2 ? '' : 'none'}">
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
<Discovery ref="discovery"></Discovery>
</scroll-view>
</view>
<view v-if="tabberPageLoadFlag[3]" :style="{display: currentIndex === 3 ? '' : 'none'}">
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
<Pizz ref="pizz"></Pizz>
</scroll-view>
</view>
<view v-if="tabberPageLoadFlag[4]" :style="{display: currentIndex === 4 ? '' : 'none'}">
<scroll-view class="custom-tabbar-page" scroll-y enable-back-to-top @scrolltolower="tabbarPageScrollLower">
<User ref="user"></User>
</scroll-view>
</view>
<tn-tabbar :outHeight="140" :height="120" v-model="currentIndex" :list="tabbarList" activeColor="#3377FF"
inactiveColor="#AAAAAA" activeIconColor="#3377FF" inactiveIconColor="#8A8E99" :animation="true"
:safeAreaInsetBottom="true" @change="switchTabbar"></tn-tabbar>
</view>
</template>
<script>
import Home from './home.vue'
import Service from './service.vue'
import Discovery from './discovery.vue'
import Pizz from './pizz.vue'
import User from './user.vue'
export default {
components: {
Home,
Service,
Discovery,
Pizz,
User
},
data() {
return {
// tabbar
tabbarList: [{
title: '首页',
activeIcon: '/static/01_1.png',
inactiveIcon: '/static/01.png'
},
{
title: '协会活动',
activeIcon: '/static/02_2.png',
inactiveIcon: '/static/02.png'
},
{
//
activeIcon: '/static/05.png',
inactiveIcon: '/static/05.png',
iconSize: 100,
out: true
},
{
title: '发现',
activeIcon: '/static/03_3.png',
inactiveIcon: '/static/03.png'
},
{
title: '个人中心',
activeIcon: '/static/04_4.png',
inactiveIcon: '/static/04.png'
}
],
// tabbar
currentIndex: 0,
//
tabberPageLoadFlag: []
}
},
onLoad(options) {
const index = Number(options.index || 0)
// tabbar
this.tabberPageLoadFlag = this.tabbarList.map((item, tabbar_index) => {
return index === tabbar_index
})
this.switchTabbar(index)
},
methods: {
//
switchTabbar(index) {
this._switchTabbarPage(index)
},
//
tabbarPageScrollLower(e) {
if (this.currentIndex === 1) {
//this.$refs.comm.getRandomData && this.$refs.comm.getRandomData()
}
},
//
_switchTabbarPage(index) {
wx.vibrateShort();
const selectPageFlag = this.tabberPageLoadFlag[index]
if (selectPageFlag === undefined) {
return
}
if (selectPageFlag === false) {
this.tabberPageLoadFlag[index] = true
}
this.currentIndex = index
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,70 @@
<template>
<view style="background-color: #EBF4F7;letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" backTitle="" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view style="padding-left: 15rpx;" @click="goBack()">
<text class="tn-icon-left" style="font-size: 40rpx;"></text>
</view>
<view class="tn-margin-top"
style="text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'商学院'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view style="padding:10px 30rpx;">
<view v-for="(item,index) in 10" class="tn-flex tn-flex-row-between"
@click="openUrl('/pages/index/new_info')"
style="background-color: #ffffff;padding: 20rpx;border-radius: 10rpx;margin-bottom: 10rpx;">
<view style="position:relative;">
<view style="font-size: 28rpx;">青年企业家能力提升 计划培训班(第20期)</view>
<view class="tn-flex tn-flex-row-between"
style="position: absolute;bottom: 0rpx;width: 100%;color: #808080;">
<view>河南青企协</view>
<view>
<text class="tn-icon-eye"></text>
<text>568</text>
</view>
</view>
</view>
<view style="margin-left: 20rpx;">
<image src="/static/s1.jpg" style="width: 200rpx;height: 135rpx;;border-radius: 10rpx;">
</image>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0
}
},
methods: {
openUrl(url) {
uni.navigateTo({
url: url
})
},
goBack() {
if (getCurrentPages().length > 1) {
uni.navigateBack()
} else {
uni.redirectTo({
url: '/pages/index/index'
})
}
}
}
}
</script>
<style>
</style>

66
pages/index/new_info.vue Normal file
View File

@ -0,0 +1,66 @@
<template>
<view style="letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" backTitle="" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view style="padding-left: 15rpx;" @click="goBack()">
<text class="tn-icon-left" style="font-size: 40rpx;"></text>
</view>
<view class="tn-margin-top"
style=";text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'详情'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view style="padding: 30rpx;">
<view style="text-align: center;font-size:40rpx;font-weight: 600">团省委书记王笃波一行到访中青 企协开展对接洽谈</view>
<view style="margin-top: 30rpx;color: #999;">
<text>时间2023-12-25</text>
<text style="margin-left: 20rpx;">来源河南青企协</text>
</view>
<view style="width: 100%;height: 2rpx;background-color: #eee;margin-top: 30rpx;"></view>
<view>
<view style="margin-top: 20rpx;position: relative;">
<image src="/static/b1.png"
style="width: 100rpx;height: 100rpx;position: absolute;left: 0;right: 0;top: 38%;z-index: 10;margin: 0 auto;">
</image>
<image src="/static/t7.jpg" style="width: 100%;border-radius: 20rpx;" mode="widthFix">
</image>
</view>
<view style="line-height: 50rpx; text-indent: 2em;margin-top: 20rpx;">
<p>河南省青年企业家协会HeNan Young
EntrepreneursAssociation简称为省青企协是全省性的青年企业家的群众组织是共青团联系青年企业家的桥梁和纽带是具有独立法人资格的非营利性社会团体是中国青年企业家协会河南省青年联合会河南省企业联合会河南省企业家协会的团体会员
</p>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0
}
},
methods: {
goBack() {
if (getCurrentPages().length > 1) {
uni.navigateBack()
} else {
uni.redirectTo({
url: '/pages/index/index'
})
}
}
}
}
</script>
<style>
</style>

83
pages/index/pizz.vue Normal file
View File

@ -0,0 +1,83 @@
<template>
<view style="background-color: #EBF4F7;letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view class="tn-margin-top"
style="text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'发现'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view class="tn-flex tn-flex-row-around tn-flex-center tn-flex-col-center" style="padding: 30rpx;">
<view style="color: #E15033;">
<!-- <image src="/static/02.png" style="width: 30rpx;height: 30rpx;vertical-align: middle;"></image> -->
<text class="tn-icon-sequence-vertical" style="vertical-align: middle;"></text>
<text style="margin-left: 15rpx;vertical-align: middle;">全部排序</text>
</view>
<view style="height: 25rpx;width: 2rpx;background-color: #808080;"></view>
<view>
<text class="tn-icon-first" style="vertical-align: middle;"></text>
<text style="margin-left: 15rpx;vertical-align: middle;">人气榜</text>
</view>
<view style="height: 25rpx;width: 2rpx;background-color: #808080;"></view>
<view>
<text class="tn-icon-light" style="vertical-align: middle;"></text>
<text style="margin-left: 15rpx;vertical-align: middle;">最新入驻</text>
</view>
</view>
</view>
<view class="tn-flex tn-flex-row-between tn-flex-center tn-flex-col-center" style="padding: 30rpx;">
<view style="text-align: center;background-color: #FFF;padding:10rpx 30rpx;width: 48%;border-radius: 5rpx;">
<text>全部城市</text>
<text class="tn-icon-down-triangle"></text>
</view>
<view style="text-align: center;background-color: #FFF;padding:10rpx 30rpx;width: 48%;border-radius: 5rpx;">
<text>全部</text>
<text class="tn-icon-down-triangle"></text>
</view>
</view>
<view style="padding:0rpx 30rpx 180rpx 30rpx;">
<view v-for="(item,index) in 10" style="position: relative;">
<view class="tn-flex tn-flex-center tn-flex-col-center"
style="background-color: #ffffff;padding:60rpx 30rpx;border-radius: 15rpx;margin-bottom: 20rpx;">
<view>
<image src="/static/123.png" style="width: 100rpx;height: 100rpx;border-radius: 50%;"></image>
</view>
<view style="margin-left: 20rpx;">
<view style="font-size: 31rpx;font-weight: 600;letter-spacing: 5rpx;">河南机器人行业协会</view>
<view style="font-size: 24rpx;color: #808080;margin-top: 15rpx;">成立时间2021-09-08</view>
</view>
</view>
<view
style="font-size: 20rpx;color: #43A9F8;background-color: #CAE5FF;position: absolute;top: 0;right: 0;border-radius: 0px 15rpx 0px 15rpx;padding:10rpx 10rpx;">
<text class="tn-icon-success-circle-fill" style="vertical-align: middle;"></text>
<text style="vertical-align: middle;margin-left: 5rpx;">已认证</text>
</view>
</view>
</view>
<view style="position: fixed;bottom: 13%;width: 100%;">
<view
style="margin: 0 auto;color: #fff;letter-spacing: 10rpx;line-height: 70rpx;;text-align: center;width: 70%;height: 70rpx;background: linear-gradient(270deg, #EE7E45, #EE9657);border-radius: 50rpx;">
快速入驻</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0
}
},
methods: {
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,270 @@
<template>
<view style="letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" backTitle="" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view style="padding-left: 15rpx;" @click="goBack()">
<text class="tn-icon-left" style="font-size: 40rpx;"></text>
</view>
<view class="tn-margin-top"
style=";text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'搜索'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-margin"
:style="{marginTop: vuex_custom_bar_height + 20+'px'}">
<view class="justify-content-item align-content-item" style="width: 100%;">
<view class="tn-flex tn-flex-col-center"
style="border-radius: 100rpx;padding: 10rpx 20rpx 10rpx 20rpx;width: 95%;background-color: rgba(248, 247, 248, 0.9);">
<text
class="tn-icon-search justify-content-item tn-padding-right-xs tn-color-gray tn-text-lg"></text>
<input @input="chenkContent" v-model="content" class="justify-content-item"
placeholder="请填写搜索内容" name="input" placeholder-style="color:#AAAAAA"
style="width: 90%;"></input>
</view>
</view>
<view class="align-content-item">
<view class="justify-content-item tn-text-center">
<tn-button backgroundColor="#3668fc" shape="round" padding="20rpx 20rpx" width="150rpx" @tap="">
<text class="tn-color-white"> </text>
</tn-button>
</view>
</view>
</view>
</view>
<view>
<block>
<view>
<view class="tn-flex tn-flex-row-between tn-margin">
<view class="justify-content-item tn-text-bold">
<text class="tn-text-df tn-color-black">最近搜索</text>
</view>
<view class="justify-content-item tn-text-df tn-color-gray">
<text class="tn-padding-xs">删除</text>
<text class="tn-icon-delete"></text>
</view>
</view>
</view>
<view class="">
<view class="tn-tag-search tn-text-justify" style="padding: 0rpx 30rpx;">
<view v-for="(item, index) in tagList" :key="index"
class="tn-tag-search__item tn-margin-right tn-round tn-text-sm tn-bg-gray--light tn-color-gray">
{{ item.name }}
</view>
</view>
</view>
</block>
<view class="tn-flex tn-flex-row-between tn-padding-bottom"
style="padding-top: 20rpx;margin: 30rpx 30rpx 0rpx 30rpx;">
<view class="justify-content-item tn-text-bold">
<text class="tn-text-df tn-color-black">会员搜索结果</text>
</view>
</view>
<view style="padding:0rpx 30rpx;">
<view class="tn-flex tn-flex-center tn-flex-col-center" @click="tn('/pages/index/user_info')"
style="background-color: #ffffff;padding:30rpx;border-radius: 15rpx;margin-bottom: 20rpx;box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(12,0,5,0.1);">
<view>
<image src="/static/u1.jpg" style="width: 100rpx;height: 100rpx;border-radius: 50%;"></image>
</view>
<view style="margin-left: 20rpx;">
<view style="font-size: 31rpx;font-weight: 600;letter-spacing: 5rpx;">朱荣梅</view>
<view style="font-size: 24rpx;color: #808080;margin-top: 15rpx;">康桥集团执行总裁</view>
</view>
</view>
</view>
<view class="tn-flex tn-flex-row-between tn-padding-bottom"
style="padding-top: 20rpx;margin: 30rpx 30rpx 0rpx 30rpx;">
<view class="justify-content-item tn-text-bold">
<text class="tn-text-df tn-color-black">协会搜索结果</text>
</view>
</view>
<view style="padding:0rpx 30rpx;">
<view class="tn-flex tn-flex-center tn-flex-col-center"
style="background-color: #ffffff;padding:30rpx;border-radius: 15rpx;margin-bottom: 20rpx;box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(12,0,5,0.1);">
<view>
<image src="/static/123.png" style="width: 100rpx;height: 100rpx;border-radius: 50%;"></image>
</view>
<view style="margin-left: 20rpx;">
<view style="font-size: 31rpx;font-weight: 600;letter-spacing: 5rpx;">河南机器人行业协会</view>
<view style="font-size: 24rpx;color: #808080;margin-top: 15rpx;">成立时间2021-09-08</view>
</view>
</view>
</view>
<view class="tn-flex tn-flex-row-between tn-padding-bottom"
style="padding-top: 20rpx;margin: 30rpx 30rpx 0rpx 30rpx;">
<view class="justify-content-item tn-text-bold">
<text class="tn-text-df tn-color-black">文章搜索结果</text>
</view>
</view>
<view style="padding:0px 30rpx;">
<view v-for="(item,index) in 5" class="tn-flex tn-flex-row-between" @click="tn('/pages/index/new_info')"
style="background-color: #ffffff;padding: 20rpx;border-radius: 10rpx;margin-bottom: 20rpx;box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(12,0,5,0.1);">
<view style="position:relative;">
<view style="font-size: 28rpx;">团省委书记王笃波一行到访中青 企协开展对接洽谈</view>
<view class="tn-flex tn-flex-row-between"
style="position: absolute;bottom: 0rpx;width: 100%;color: #808080;">
<view>河南青企协</view>
<view>
<text class="tn-icon-eye"></text>
<text>568</text>
</view>
</view>
</view>
<view style="margin-left: 20rpx;">
<image src="/static/c1.jpg" style="width: 240rpx;border-radius: 10rpx;" mode="widthFix"></image>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0,
tagList: [{
name: '李小兵'
}, {
name: '李小兵'
}]
}
},
methods: {
tn(e) {
uni.navigateTo({
url: e
})
},
goBack() {
if (getCurrentPages().length > 1) {
uni.navigateBack()
} else {
uni.redirectTo({
url: '/pages/index/index'
})
}
},
}
}
</script>
<style lang="scss" scoped>
.tn-search-fixed {
position: fixed;
top: 50rpx;
width: 100%;
transition: all 0.25s ease-out;
z-index: 1;
}
/* 搜索标签 start*/
.tn-tag-search {
&__item {
display: inline-block;
line-height: 45rpx;
padding: 10rpx 30rpx;
margin: 20rpx 20rpx 5rpx 0rpx;
&--prefix {
padding-right: 10rpx;
}
}
}
/* 标签内容 end*/
/* 标题 start */
.nav_title {
-webkit-background-clip: text;
color: transparent;
&--wrap {
position: relative;
display: flex;
height: 120rpx;
font-size: 42rpx;
align-items: center;
justify-content: center;
font-weight: bold;
background-image: url(https://resource.tuniaokj.com/images/title_bg/title00.png);
background-size: cover;
}
}
/* 标题 end */
/* 富文本图示意 start */
.news-img {
z-index: -1;
padding-bottom: 40rpx;
image {
width: 100%;
margin: 20rpx 0;
// height: 3373rpx;
// z-index: -1;
}
}
/* 资讯主图 start*/
.image-article {
border-radius: 8rpx;
border: 1rpx solid #F8F7F8;
width: 200rpx;
height: 200rpx;
position: relative;
}
.image-pic {
background-size: cover;
background-repeat: no-repeat;
// background-attachment:fixed;
background-position: top;
border-radius: 10rpx;
}
.article-shadow {
border-radius: 15rpx;
box-shadow: 0rpx 0rpx 50rpx 0rpx rgba(0, 0, 0, 0.07);
}
/* 文字截取*/
.clamp-text-1 {
-webkit-line-clamp: 1;
display: -webkit-box;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
overflow: hidden;
}
.clamp-text-2 {
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
overflow: hidden;
}
/* 标签内容 start*/
.tn-tag-content {
&__item {
display: inline-block;
line-height: 35rpx;
padding: 7rpx 25rpx 5rpx 25rpx;
&--prefix {
padding-right: 10rpx;
}
}
}
/* 标签内容 end*/
</style>

184
pages/index/service.vue Normal file
View File

@ -0,0 +1,184 @@
<template>
<view style="background-color: #EBF4F7;letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view class="tn-margin-top"
style="text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'协会活动'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<!-- <view style="padding: 30rpx;">
<view class="tn-flex tn-flex-center tn-flex-col-center tn-flex-row-between"
style="text-align: center;padding: 30rpx;background-color: #FFF;border-radius: 20rpx;">
<view>
<view>
<image src="/static/ico1.png" style="width: 70rpx;height: 70rpx;"></image>
</view>
<view style="margin-top: 10rpx;">
<text>协会活动</text>
</view>
</view>
<view>
<view>
<image src="/static/ico2.png" style="width: 70rpx;height: 70rpx;"></image>
</view>
<view style="margin-top: 10rpx;">
<text>知识学堂</text>
</view>
</view>
<view>
<view>
<image src="/static/ico3.png" style="width: 70rpx;height: 70rpx;"></image>
</view>
<view style="margin-top: 10rpx;">
<text>供需服务</text>
</view>
</view>
<view>
<view>
<image src="/static/ico4.png" style="width: 70rpx;height: 70rpx;"></image>
</view>
<view style="margin-top: 10rpx;">
<text>金融服务</text>
</view>
</view>
</view>
</view> -->
</view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-flex-row-center"
style="padding:10rpx 30rpx;margin-top: 20rpx;">
<view style="font-size: 36rpx;">协会活动</view>
<view style="color: #808080;" @click="openUrl('/pages/index/events_list')">
<text>更多</text>
<text class="tn-icon-right"></text>
</view>
</view>
<view style="padding-bottom: 30rpx;">
<scroll-view :scroll-x="true" style="padding:0rpx 30rpx;white-space: nowrap;">
<view v-for="(item,index) in 3" @click="openUrl('/pages/index/events_info')"
style="box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(12,0,5,0.1);position: relative;;display: inline-block;width: 300rpx;text-align: center;background-color: #FFF;border-radius: 20rpx;overflow: hidden;margin-right: 20rpx;">
<view>
<image src="/static/hd1.jpg" mode="widthFix" style="width: 300rpx;"></image>
</view>
<view style="padding:10rpx 20rpx;font-weight: 400;">
<view class="tn-text-ellipsis-2" style="letter-spacing: 1px;">青年企业家能力提升 计划培训班(第20期)</view>
</view>
<view style="position: absolute;top: 10rpx;left: 10rpx;">
<tn-button v-if="index==0" width="80rpx" height="40rpx" size="sm" backgroundColor="#6BC7F0 "
fontColor="tn-color-white">报名中</tn-button>
<tn-button v-if="index==1" width="80rpx" height="40rpx" size="sm" backgroundColor="#EE9556 "
fontColor="tn-color-white">预告</tn-button>
<tn-button v-if="index==2" width="80rpx" height="40rpx" size="sm" backgroundColor="#E12B33 "
fontColor="tn-color-white">进行中</tn-button>
</view>
</view>
</scroll-view>
</view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-flex-row-center" style="padding:10rpx 30rpx;">
<view style="font-size: 36rpx;">知识学堂</view>
<view style="color: #808080;" @click="openUrl('/pages/index/knowledge_list')">
<text>更多</text>
<text class="tn-icon-right"></text>
</view>
</view>
<view style="padding:0rpx 30rpx;">
<view @click="openUrl('/pages/index/new_info')"
style="box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(12,0,5,0.1);position: relative;width: 100%;background-color: #FFF;border-radius: 20rpx;overflow: hidden;">
<view>
<image src="/static/s1.jpg" mode="widthFix" style="width: 100%;"></image>
</view>
<view style="padding:10rpx 20rpx;font-weight: 400;">
<view class="tn-text-ellipsis-2">青年企业家能力提升 计划培训班(第20期)</view>
<view class="tn-flex tn-flex-row-between"
style="font-size: 24rpx;color:#808080;padding: 20rpx 0rpx;">
<view>河南青企协</view>
<view>
<text class="tn-icon-eye"></text>
<text style="margin-left: 5rpx;">568</text>
</view>
</view>
</view>
</view>
</view>
<view style="padding: 30rpx 0rpx;">
<scroll-view :scroll-x="true" style="padding:0rpx 30rpx;white-space: nowrap;">
<view v-for="(item,index) in 3" @click="openUrl('/pages/index/new_info')"
style="box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(12,0,5,0.1);position: relative;;display: inline-block;width: 335rpx;text-align: center;background-color: #FFF;border-radius: 20rpx;overflow: hidden;margin-right: 20rpx;">
<view>
<image src="/static/hd1.jpg" style="width: 335rpx;height: 200rpx;"></image>
</view>
<view style="padding:10rpx 20rpx;font-weight: 400;">
<view class="tn-text-ellipsis" style="letter-spacing: 1px;">青年企业家能力提升 计划培训班(第20期)</view>
<view class="tn-flex tn-flex-row-between"
style="font-size: 24rpx;color:#808080;padding: 15rpx 0rpx;">
<view>河南青企协</view>
<view>
<text class="tn-icon-eye"></text>
<text style="margin-left: 5rpx;">568</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-flex-row-center" style="padding:10rpx 30rpx;">
<view style="font-size: 36rpx;">供需服务</view>
<view style="color: #808080;" @click="openUrl('/pages/index/goods_list')">
<text>更多</text>
<text class="tn-icon-right"></text>
</view>
</view>
<view style="padding:10rpx 30rpx 100rpx 30rpx;">
<view v-for="(item,index) in 3" @click="openUrl('/pages/index/goods_info')" style="background-color: #FFF;padding: 20rpx;border-radius: 20rpx;
box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(12,0,5,0.1);margin-bottom: 20rpx;">
<view class="tn-text-ellipsis">
<tn-tag v-if="index%2==0" shape="radius" size="sm" backgroundColor="tn-main-gradient-red--reverse"
width="60rpx">需求</tn-tag>
<tn-tag v-if="index%2!=0" shape="radius" size="sm" backgroundColor="tn-main-gradient-blue"
width="60rpx">供应</tn-tag>
<text style="vertical-align: middle;padding-left: 20rpx;font-size:30rpx;">
劲捷飞燕系列P056C旅拍三脚架碳纤维摄影旅拍三脚架碳纤维摄影</text>
</view>
<view style="font-size: 24rpx;color: #808080;margin-top: 20rpx;">洛阳灵睿网络技术有限公司</view>
<view>
<tn-tag shape="radius" width="auto" backgroundColor="#F7F7F7" fontColor="#808080">洛阳市</tn-tag>
<tn-tag shape="radius" margin="20rpx" width="auto" backgroundColor="#EBF4F7"
fontColor="#3377FF">合作意向18人</tn-tag>
<tn-tag shape="radius" width="auto" backgroundColor="#F7F3EB" fontColor="#FF8C19">项目金额1万以下</tn-tag>
</view>
<view class="tn-flex tn-flex-row-between tn-flex-col-center tn-flex-row-center"
style="margin-top: 20rpx;">
<view style="color: #808080;font-size: 24rpx;">2023-12-19</view>
<view>
<tn-button shape="round" :fontSize="22" width="148rpx" height="50rpx"
backgroundColor="tn-main-gradient-orange" fontColor="#ffffff">意向合作</tn-button>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0
}
},
methods: {
openUrl(e) {
uni.navigateTo({
url: e
})
}
}
}
</script>
<style>
</style>

119
pages/index/user.vue Normal file
View File

@ -0,0 +1,119 @@
<template>
<view style="background-color: #EBF4F7;letter-spacing: 1rpx;">
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}"
style="background: linear-gradient(50deg, #034EF9 0%, #05ACFF 99%);height: 350rpx;">
<view class="tn-flex tn-flex-center tn-flex-col-center" style="padding: 30rpx;width: 100%;">
<view>
<image src="/static/u1.jpg" style="width: 100rpx;height: 100rpx;border-radius: 50%;"></image>
</view>
<view style="width: 100%;">
<view class="tn-flex tn-flex-col-center tn-flex-row-between">
<view style="margin-left: 20rpx;color: #fff">
<view style="font-size: 35rpx;">朱荣梅</view>
<view style="font-size: 24rpx;margin-top: 10rpx;">康桥集团执行总裁</view>
</view>
<view>
<image src="/static/ico13.png" style="width: 40rpx ;" mode="widthFix"></image>
</view>
</view>
</view>
</view>
</view>
<view>
<view style="padding: 30rpx;margin-top:-115rpx;background-color: transparent;">
<view class="tn-flex tn-flex-center tn-flex-col-center tn-flex-row-between"
style="text-align: center;padding: 30rpx;background-color: #FFF;border-radius: 20rpx;box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(12,0,5,0.1);">
<view>
<view>
<image src="/static/ico5.png" style="width: 50rpx;height: 50rpx;"></image>
</view>
<view style="margin-top: 10rpx;">
<text>供需记录</text>
</view>
</view>
<view>
<view>
<image src="/static/ico1.png" style="width: 50rpx;height: 50rpx;"></image>
</view>
<view style="margin-top: 10rpx;">
<text>参与活动</text>
</view>
</view>
<view>
<view>
<image src="/static/02_2.png" style="width: 50rpx;height: 50rpx;"></image>
</view>
<view style="margin-top: 10rpx;">
<text>协会服务</text>
</view>
</view>
<view>
<view>
<image src="/static/ico6.png" style="width: 50rpx;height: 50rpx;"></image>
</view>
<view style="margin-top: 10rpx;">
<text>我的名片</text>
</view>
</view>
</view>
</view>
<view style="background-color: #fff;">
<view style="padding:0px 20rpx;">
<tn-list-cell :arrow="true">
<view class="tn-flex tn-flex-center tn-flex-col-center">
<image src="/static/ico7.png" style="width: 50rpx;height: 50rpx"></image>
<view style="margin-left: 20rpx;">所在商/协会</view>
</view>
</tn-list-cell>
<tn-list-cell :arrow="true">
<view class="tn-flex tn-flex-center tn-flex-col-center">
<image src="/static/ico8.png" style="width: 50rpx;height: 50rpx"></image>
<view style="margin-left: 20rpx;">入会申请记录</view>
</view>
</tn-list-cell>
<tn-list-cell :arrow="true">
<view class="tn-flex tn-flex-center tn-flex-col-center">
<image src="/static/ico9.png" style="width: 50rpx;height: 50rpx"></image>
<view style="margin-left: 20rpx;">我的消息</view>
</view>
</tn-list-cell>
<tn-list-cell :arrow="true">
<view class="tn-flex tn-flex-center tn-flex-col-center">
<image src="/static/ico10.png" style="width: 50rpx;height: 50rpx"></image>
<view style="margin-left: 20rpx;">常见问题</view>
</view>
</tn-list-cell>
<tn-list-cell :arrow="true">
<view class="tn-flex tn-flex-center tn-flex-col-center">
<image src="/static/ico11.png" style="width: 50rpx;height: 50rpx"></image>
<view style="margin-left: 20rpx;">设置</view>
</view>
</tn-list-cell>
<tn-list-cell :arrow="true">
<view class="tn-flex tn-flex-center tn-flex-col-center">
<image src="/static/ico12.png" style="width: 50rpx;height: 50rpx"></image>
<view style="margin-left: 20rpx;">意见反馈</view>
</view>
</tn-list-cell>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0
}
},
methods: {
}
}
</script>
<style>
</style>

102
pages/index/user_info.vue Normal file
View File

@ -0,0 +1,102 @@
<template>
<view style="background-color: #EBF4F7;letter-spacing: 1rpx;">
<tn-nav-bar :isBack="false" backTitle="" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view style="padding-left: 15rpx;" @click="goBack()">
<text class="tn-icon-left" style="font-size: 40rpx;"></text>
</view>
<view class="tn-margin-top"
style=";text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'会员名录'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 'px'}">
<view style="padding:30rpx 30rpx 180rpx 30rpx;">
<image src="/static/u2.jpg" style="width: 100%;border-radius: 20rpx;" mode="widthFix"></image>
<view style="margin-top: 10rpx;background-color: #fff;padding: 30rpx;border-radius: 20rpx;">
<view style="font-size: 32rpx;font-weight: 600;">
<text>朱荣梅</text>
<text style="margin-left: 40rpx;">汉族</text>
</view>
<view style="margin: 10rpx 0rpx;">
<text>河南省青年企业家协会</text>
<text style="margin-left: 20rpx;">副会长</text>
</view>
<view>
<text>郑州康桥房地产开发有限责任公司</text>
<text style="margin-left: 20rpx;">执行总裁</text>
</view>
</view>
<view
style="padding:10rpx 30rpx 50rpx 30rpx;background-color: #fff;border-radius: 20rpx;margin-top: 20rpx;">
<tn-tabs :list="list" :isScroll="true" :activeItemStyle="{'fontWeight':'600','fontSize':'30rpx'}"
style="font-weight: ;" activeColor="#000" :barWidth="50" :barHeight="6"
:barStyle="{'background': 'linear-gradient(-45deg, #4AA2EF, #3A7FF4)','borderRadius': '4rpx'}"
:current="current" name="name" @change="change"></tn-tabs>
<view style="margin-top: 20rpx;position: relative;">
<image src="/static/b1.png"
style="width: 100rpx;height: 100rpx;position: absolute;left: 0;right: 0;top: 38%;z-index: 10;margin: 0 auto;">
</image>
<image src="/static/t7.jpg" style="width: 100%;border-radius: 20rpx;" mode="widthFix"></image>
</view>
<view style="line-height: 50rpx; text-indent: 2em;margin-top: 20rpx;">
<p>河南省青年企业家协会HeNan Young
EntrepreneursAssociation简称为省青企协是全省性的青年企业家的群众组织是共青团联系青年企业家的桥梁和纽带是具有独立法人资格的非营利性社会团体是中国青年企业家协会河南省青年联合会河南省企业联合会河南省企业家协会的团体会员
</p>
</view>
</view>
</view>
</view>
<view style="position: fixed;bottom: 5%;width: 100%;">
<view class="tn-flex tn-flex-row-around">
<view
style="color: #fff;;letter-spacing: 10rpx;line-height: 70rpx;;text-align: center;;width: 300rpx;height: 70rpx;background: linear-gradient(-45deg, #4AA2EF, #3A7FF4);border-radius: 50rpx;">
公司/产品介绍</view>
<view
style="color: #fff;;letter-spacing: 10rpx;line-height: 70rpx;;text-align: center;;width: 300rpx;height: 70rpx;background: linear-gradient(270deg, #EE7E45, #EE9657);border-radius: 50rpx;">
联系TA</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
topCurrent: 0.,
list: [{
name: '公司介绍'
}, {
name: '个人介绍'
}],
current: 0
}
},
methods: {
change(e) {
this.current = e;
},
goBack() {
if (getCurrentPages().length > 1) {
uni.navigateBack()
} else {
uni.redirectTo({
url: '/pages/index/index'
})
}
}
}
}
</script>
<style>
</style>

BIN
static/01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
static/01_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
static/02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
static/02_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
static/03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
static/03_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
static/04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
static/04_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
static/05.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
static/123.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
static/b1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
static/banner.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

BIN
static/banner1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

BIN
static/c1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
static/c1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

BIN
static/c1455.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

BIN
static/c2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

BIN
static/c3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,193 @@
/* 顶部 start */
.header {
padding: 80rpx 60rpx 40rpx 60rpx;
.title {
font-size: 36rpx;
color: $tn-font-color;
font-weight: bold;
}
.sub-title {
font-size: 28rpx;
color: $tn-content-color;
padding-top: 18rpx;
}
.tips-title {
font-size: 24rpx;
color: $tn-font-sub-color;
padding-top: 5rpx;
}
}
/* 顶部 end */
/* 展示内容容器 start */
.show-content-container {
/* 标题容器 start */
.title-container {
display: flex;
position: relative;
align-items: center;
justify-content: space-between;
min-height: 100rpx;
// 标题样式
.title {
height: 100%;
max-width: 100%;
margin: 0 20rpx;
font-size: 30rpx;
display: flex;
align-items: center;
justify-content: center;
// 标题前面小点
&:before {
content: " ";
background-color: $tn-main-color;
width: 15rpx;
height: 15rpx;
border-radius: 10rpx;
margin-right: 18rpx;
}
}
}
/* 标题容器 end */
/* 内容 start */
.content {
padding: 20rpx;
}
/* 内容 end */
}
/* 展示内容容器 end */
/* 内容容器 start */
.demo-content-container {
border: 1rpx dashed $tn-main-color;
margin: 20rpx;
margin-top: 0rpx;
position: fixed;
width: 95%;
z-index: 10;
transition: all 0.15s ease-out;
&.top {
width: 100%;
margin: 0;
}
&.no-fixed {
position: relative !important;
}
/* 标题容器 start */
.title-container {
display: flex;
position: relative;
align-items: center;
justify-content: center;
min-height: 100rpx;
// 标题样式
.title {
height: 100%;
max-width: 100%;
margin: 0 30rpx;
font-size: 30rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
/* 标题容器 end */
/* 内容 start */
.content {
padding: 30rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* 内容 end */
}
/* 内容容器 end */
/* 可选项内容容器 start */
.demo-section-container {
margin: 20rpx;
height: 100%;
/* 标题容器 start */
.title-container {
display: flex;
position: relative;
align-items: center;
justify-content: center;
min-height: 100rpx;
margin-bottom: 10rpx;
// 标题样式
.title {
height: 100%;
max-width: 100%;
margin: 0 30rpx;
font-size: 30rpx;
display: flex;
align-items: center;
justify-content: center;
}
&::after {
content: " ";
box-sizing: border-box;
width: 90%;
height: 100%;
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
border-bottom: 1rpx solid $tn-border-solid-color;
}
}
/* 标题容器 end */
/* 参数内容 start*/
.content {
padding: 0 20rpx 10rpx 20rpx;
// 标题样式
.title {
padding-left: 20rpx;
height: 100%;
position: relative;
&::before {
content: " ";
position: absolute;
top: 50%;
left: 0;
width: 4rpx;
height: 90%;
background-color: $tn-main-color;
border-radius: 6rpx;
transform: translateY(-50%);
}
}
// 参数样式
.section {
margin-top: 15rpx;
margin-bottom: 20rpx;
}
}
/* 参数内容 end*/
}
/* 可选项内容容器 end */

240
static/css/my.scss Normal file
View File

@ -0,0 +1,240 @@
.pages-a {
max-height: 100vh;
}
/* 自定义导航栏内容 start */
.custom-nav {
height: 100%;
&__back {
margin: auto 5rpx;
font-size: 50rpx;
margin-right: 10rpx;
margin-left: 30rpx;
flex-basis: 5%;
}
}
/* 底部安全边距 start*/
.tn-tabbar-height {
min-height: 120rpx;
height: calc(140rpx + env(safe-area-inset-bottom) / 2);
height: calc(140rpx + constant(safe-area-inset-bottom));
}
/* 图标容器5 start */
.icon5 {
&__item {
// width: 30%;
background-color: #FFFFFF;
border-radius: 10rpx;
padding: 0rpx;
margin: 0rpx;
transform: scale(1);
transition: transform 0.3s linear;
transform-origin: center center;
&--icon {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
margin-bottom: 18rpx;
position: relative;
z-index: 1;
}
}
}
/* 轮播视觉差 start */
.card-swiper {
height: 450rpx;
}
.card-swiper swiper-item {
width: 750rpx;
left: 0rpx;
box-sizing: border-box;
padding: 40rpx 30rpx 30rpx 30rpx;
overflow: initial;
}
.card-swiper swiper-item .swiper-item {
width: 100%;
display: block;
height: 100%;
border-radius: 15rpx;
transform: scale(1);
transition: all 0.2s ease-in 0s;
will-change: transform;
// overflow: hidden;
}
.card-swiper swiper-item.cur .swiper-item {
transform: none;
transition: all 0.2s ease-in 0s;
will-change: transform;
}
.card-swiper swiper-item .swiper-item-text {
margin-top: -220rpx;
text-align: center;
width: 100%;
display: block;
border-radius: 10rpx;
transform: translate(100rpx, 0rpx) scale(0.9, 0.9);
transition: all 0.6s ease 0s;
will-change: transform;
overflow: hidden;
}
.card-swiper swiper-item.cur .swiper-item-text {
margin-top: -220rpx;
width: 100%;
transform: translate(0rpx, 0rpx) scale(0.9, 0.9);
transition: all 0.6s ease 0s;
will-change: transform;
}
.image-banner {
display: flex;
align-items: center;
justify-content: center;
}
.image-banner image {
width: 100%;
height: 100%;
}
/* 轮播指示点 start*/
.indication {
z-index: 9999;
width: 100%;
height: 36rpx;
position: absolute;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.spot {
background-color: #FFFFFF;
opacity: 0.6;
width: 10rpx;
height: 10rpx;
border-radius: 20rpx;
top: -70rpx;
margin: 0 8rpx !important;
position: relative;
}
.spot.active {
opacity: 1;
width: 30rpx;
background-color: #FFFFFF;
}
/* 胶囊banner*/
.image-capsule {
padding: 100rpx 0rpx;
font-size: 40rpx;
font-weight: 300;
position: relative;
}
.image-piccapsule {
background-size: cover;
background-repeat: no-repeat;
// background-attachment:fixed;
background-position: top;
border-radius: 20rpx 20rpx 0 0;
}
/* 用户头像 start */
.logo-image {
width: 40rpx;
height: 40rpx;
position: relative;
}
.logo-pic {
background-size: cover;
background-repeat: no-repeat;
// background-attachment:fixed;
background-position: top;
border: 1rpx solid rgba(255, 255, 255, 0.05);
// box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.15);
border-radius: 50%;
overflow: hidden;
// background-color: #FFFFFF;
}
/* 瀑布流*/
.wallpaper__item {
background-color: #FFFFFF;
overflow: hidden;
margin: 0 10rpx;
margin-bottom: 40rpx;
// box-shadow: 0rpx 0rpx 30rpx 0rpx rgba(0, 0, 0, 0.07);
.item {
/* 图片 start */
&__image {
width: 100%;
height: auto;
background-color: #FFFFFF;
border: 1rpx solid #F8F7F8;
border-radius: 10rpx;
overflow: hidden;
}
/* 图片 end */
/* 内容 start */
&__data {
padding: 14rpx 0rpx;
}
/* 标题 start */
&__title-container {
text-align: justify;
line-height: 38rpx;
vertical-align: middle;
}
&__title {
font-size: 30rpx;
}
/* 标题 end */
/* 标签 start */
&__tags-container {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-start;
}
&__tag {
margin: 10rpx;
color: #7C8191;
background-color: #F3F2F7;
padding: 4rpx 14rpx 6rpx;
border-radius: 10rpx;
font-size: 20rpx;
&:first-child {
margin-left: 0rpx !important;
}
}
/* 标签 end */
/* 内容 end */
}
}

View File

@ -0,0 +1,38 @@
.tn-custom-nav-bar__back {
width: 100%;
height: 100%;
position: relative;
display: flex;
justify-content: space-evenly;
align-items: center;
box-sizing: border-box;
background-color: rgba(0, 0, 0, 0.15);
border-radius: 1000rpx;
border: 1rpx solid rgba(255, 255, 255, 0.5);
color: #FFFFFF;
font-size: 18px;
.icon {
display: block;
flex: 1;
margin: auto;
text-align: center;
}
&:before {
content: " ";
width: 1rpx;
height: 110%;
position: absolute;
top: 22.5%;
left: 0;
right: 0;
margin: auto;
transform: scale(0.5);
transform-origin: 0 0;
pointer-events: none;
box-sizing: border-box;
opacity: 0.7;
background-color: #FFFFFF;
}
}

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
static/hd1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
static/ico1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
static/ico10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
static/ico11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
static/ico12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
static/ico13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
static/ico2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
static/ico3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
static/ico4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
static/ico5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
static/ico6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
static/ico7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
static/ico8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
static/ico9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
static/s1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

BIN
static/t1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
static/t2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
static/t7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
static/u1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
static/u2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

28
store/$tn.mixin.js Normal file
View File

@ -0,0 +1,28 @@
import { mapState } from 'vuex'
import store from '@/store'
// 尝试将用户在根目录中的store/index.js的vuex的state变量加载到全局变量中
let $tStoreKey = []
try {
$tStoreKey = store.state ? Object.keys(store.state) : []
} catch(e) {
}
module.exports = {
beforeCreate() {
// 将vuex方法挂在在$t中
// 使用方法:
// 修改vuex的state中的user.name变量为图鸟小菜 => this.$tn.vuex('user.name', '图鸟小菜')
// 修改vuexde state中的version变量为1.0.1 => this.$tn.vuex('version', 1.0.1)
this.$tn.vuex = (name, value) => {
this.$store.commit('$tStore', {
name, value
})
}
},
computed: {
// 将vuex的state中的变量结构到全局混入mixin中
...mapState($tStoreKey)
}
}

75
store/index.js Normal file
View File

@ -0,0 +1,75 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
let lifeData = {}
// 尝试获取本地是否存在lifeData变量第一次启动时不存在
try {
lifeData = uni.getStorageSync('lifeData')
} catch(e) {
}
// 标记需要永久存储的变量在每次启动时取出在state中的变量名
let saveStateKeys = ['vuex_user']
// 保存变量到本地存储
const saveLifeData = function(key, value) {
// 判断变量是否在存储数组中
if (saveStateKeys.indexOf(key) != -1) {
// 获取本地存储的lifeData对象将变量添加到对象中
let tmpLifeData = uni.getStorageSync('lifeData')
// 第一次启动时不存在,则放一个空对象
tmpLifeData = tmpLifeData ? tmpLifeData : {},
tmpLifeData[key] = value
// 将变量再次放回本地存储中
uni.setStorageSync('lifeData', tmpLifeData)
}
}
const store = new Vuex.Store({
state: {
// 如果上面从本地获取的lifeData对象下有对应的属性就赋值给state中对应的变量
// 加上vuex_前缀是防止变量名冲突也让人一目了然
vuex_user: lifeData.vuex_user ? lifeData.vuex_user : {name: '图鸟'},
// 如果vuex_version无需保存到本地永久存储无需lifeData.vuex_version方式
// app版本
vuex_version: "1.0.0",
// 是否使用自定义导航栏
vuex_custom_nav_bar: true,
// 状态栏高度
vuex_status_bar_height: 0,
// 自定义导航栏的高度
vuex_custom_bar_height: 0
},
mutations: {
$tStore(state, payload) {
// 判断是否多层调用state中为对象存在的情况例如user.info.score = 1
let nameArr = payload.name.split('.')
let saveKey = ''
let len = nameArr.length
if (len >= 2) {
let obj = state[nameArr[0]]
for (let i= 1; i < len - 1; i++) {
obj = obj[nameArr[i]]
}
obj[nameArr[len - 1]] = payload.value
saveKey = nameArr[0]
} else {
// 单层级变量
state[payload.name] = payload.value
saveKey = payload.name
}
// 保存变量到本地中
saveLifeData(saveKey, state[saveKey])
}
},
actions: {
}
})
export default store

4
tuniao-ui/README.md Normal file
View File

@ -0,0 +1,4 @@
TuniaoUi for uniApp v1.0.0 | by 图鸟 2021-09-01
仅供开发,如作它用所承受的法律责任一概与作者无关
*使用TuniaoUi开发扩展与插件时请注明基于tuniao字眼

View File

@ -0,0 +1,206 @@
<template>
<view v-if="value" class="tn-action-sheet-class tn-action-sheet">
<tn-popup
v-model="value"
mode="bottom"
length="auto"
:popup="false"
:borderRadius="borderRadius"
:maskCloseable="maskCloseable"
:safeAreaInsetBottom="safeAreaInsetBottom"
:zIndex="elZIndex"
@close="close"
>
<!-- 提示信息 -->
<view
v-if="tips.text"
class="tn-action-sheet__tips border-solid-bottom"
:style="[tipsStyle]"
>
{{tips.text}}
</view>
<!-- 按钮列表 -->
<block v-for="(item, index) in list" :key="index">
<view
class="tn-action-sheet__item tn-text-ellipsis"
:class="[ index < list.length - 1 ? 'border-solid-bottom' : '']"
:style="[itemStyle(index)]"
hover-class="tn-hover-class"
:hover-stay-time="150"
@tap="itemClick(index)"
@touchmove.stop.prevent
>
<text>{{item.text}}</text>
<text v-if="item.subText" class="tn-action-sheet__item__subtext tn-text-ellipsis">{{item.subText}}</text>
</view>
</block>
<!-- 取消按钮 -->
<block v-if="cancelBtn">
<view class="tn-action-sheet__cancel--gab"></view>
<view
class="tn-action-sheet__cancel tn-action-sheet__item"
hover-class="tn-hover-class"
:hover-stay-time="150"
@tap="close"
>{{cancelText}}</view>
</block>
</tn-popup>
</view>
</template>
<script>
export default {
name: 'tn-action-sheet',
props: {
// v-model
value: {
type: Boolean,
default: false
},
//
// return [{
// text: '',
// subText: '',
// color: '',
// fontSize: '',
// disabled: true
// }]
list: {
type: Array,
default() {
return []
}
},
//
tips: {
type: Object,
default() {
return {
text: '',
color: '',
fontSize: 26
}
}
},
//
borderRadius: {
type: Number,
default: 0
},
//
maskCloseable: {
type: Boolean,
default: true
},
//
cancelBtn: {
type: Boolean,
default: true
},
//
cancelText: {
type: String,
default: '取消'
},
//
// iPhoneX
safeAreaInsetBottom: {
type: Boolean,
default: false
},
// z-index
zIndex: {
type: Number,
default: 0
}
},
computed: {
//
tipsStyle() {
let style = {}
if (this.tips.color) style.color = this.tips.color
if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx'
return style
},
//
itemStyle() {
return (index) => {
let style = {}
if (this.list[index].color) style.color = this.list[index].color
if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx'
//
if (this.list[index].disabled) style.color = '#AAAAAA'
return style
}
},
elZIndex() {
return this.zIndex ? this.zIndex : this.$tn.zIndex.popup
}
},
methods: {
//
close() {
// inputpropsvalue
this.popupClose();
this.$emit('close');
},
//
popupClose() {
this.$emit('input', false)
},
// item
itemClick(index) {
//
if (this.list[index].disabled) return
this.$emit('click', index)
this.popupClose()
}
}
}
</script>
<style lang="scss" scoped>
.border-solid-bottom{
border-bottom: 1rpx solid #F8F7F8;
}
.tn-action-sheet {
&__tips {
font-size: 26rpx;
text-align: center;
padding: 34rpx 0;
line-height: 1;
color: $tn-content-color;
}
&__item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 32rpx;
padding: 34rpx 0;
&__subtext {
font-size: 24rpx;
color: $tn-content-color;
margin-top: 20rpx;
}
}
&__cancel {
color: $tn-font-color;
&--gab {
height: 12rpx;
background-color: #F8F7F8;
}
}
}
</style>

View File

@ -0,0 +1,103 @@
<template>
<view class="tn-avatar-group-class tn-avatar-group">
<view v-for="(item, index) in lists" :key="index" class="tn-avatar-group__item" :style="[itemStyle(index)]">
<tn-avatar
:src="item.src || ''"
:text="item.text || ''"
:icon="item.icon || ''"
:size="size"
:shape="shape"
:imgMode="imgMode"
:border="true"
:borderSize="4"
></tn-avatar>
</view>
</view>
</template>
<script>
export default {
name: 'tn-avatar-group',
props: {
//
lists: {
type: Array,
default() {
return []
}
},
//
// square circle
shape: {
type: String,
default: 'circle'
},
//
// sm lg xl
//
size: {
type: [Number, String],
default: ''
},
//
//
imgMode: {
type: String,
default: 'aspectFill'
},
//
// 0.4 40%
gap: {
type: Number,
default: 0.4
}
},
computed: {
itemStyle() {
return (index) => {
let style = {}
if (this._checkSizeIsInline()) {
switch(this.size) {
case 'sm':
style.marginLeft = index != 0 ? `${-48 * this.gap}rpx` : ''
break
case 'lg':
style.marginLeft = index != 0 ? `${-96 * this.gap}rpx` : ''
break
case 'xl':
style.marginLeft = index != 0 ? `${-128 * this.gap}rpx` : ''
break
}
} else {
const size = Number(this.size.replace(/(px|rpx)/g, '')) || 64
style.marginLeft = index != 0 ? `-${size * this.gap}rpx` : ''
}
return style
}
}
},
data() {
return {
}
},
methods: {
// 使
_checkSizeIsInline() {
if (/(xs|sm|md|lg|xl|xxl)/.test(this.size)) return true
else return false
}
}
}
</script>
<style lang="scss" scoped>
.tn-avatar-group {
display: flex;
flex-direction: row;
&__item {
position: relative;
}
}
</style>

View File

@ -0,0 +1,298 @@
<template>
<view
class="tn-avatar-class tn-avatar"
:class="[backgroundColorClass,avatarClass]"
:style="[avatarStyle]"
@tap="click"
>
<image
v-if="showImg"
class="tn-avatar__img"
:class="[imgClass]"
:src="src"
:mode="imgMode || 'aspectFill'"
@error="loadImageError"
></image>
<view v-else class="tn-avatar__text" >
<view v-if="text">{{ text }}</view>
<view v-else :class="[`tn-icon-${icon}`]"></view>
</view>
<!-- 角标 -->
<tn-badge
v-if="badge && (badgeIcon || badgeText)"
:radius="badgeSize"
:backgroundColor="badgeBgColor"
:fontColor="badgeColor"
:fontSize="badgeSize - 8"
:absolute="true"
:top="badgePosition[0]"
:right="badgePosition[1]"
>
<view v-if="badgeIcon && badgeText === ''">
<view :class="[`tn-icon-${badgeIcon}`]"></view>
</view>
<view v-else>
{{ badgeText }}
</view>
</tn-badge>
</view>
</template>
<script>
import componentsColorMixin from '../../libs/mixin/components_color.js'
export default {
mixins: [componentsColorMixin],
name: 'tn-avatar',
props: {
//
index: {
type: [Number, String],
default: 0
},
//
// square circle
shape: {
type: String,
default: 'circle'
},
//
// sm lg xl
//
size: {
type: [Number, String],
default: ''
},
//
shadow: {
type: Boolean,
default: false
},
//
border: {
type: Boolean,
default: false
},
//
borderColor: {
type: String,
default: 'rgba(0, 0, 0, 0.1)'
},
// , rpx
borderSize: {
type: Number,
default: 2
},
//
src: {
type: String,
default: ''
},
//
text: {
type: String,
default: ''
},
//
icon: {
type: String,
default: ''
},
//
//
imgMode: {
type: String,
default: 'aspectFill'
},
//
badge: {
type: Boolean,
default: false
},
//
badgeSize: {
type: Number,
default: 0
},
//
badgeBgColor: {
type: String,
default: '#AAAAAA'
},
//
badgeColor: {
type: String,
default: '#FFFFFF'
},
//
badgeIcon: {
type: String,
default: ''
},
// icon
badgeText: {
type: String,
default: ''
},
//
// [top, right]
badgePosition: {
type: Array,
default() {
return [0, 0]
}
}
},
data() {
return {
//
imgLoadError: false
}
},
computed: {
showImg() {
//
return this.text === '' && this.icon === ''
},
avatarClass() {
let clazz = ''
clazz += ` tn-avatar--${this.shape}`
if (this._checkSizeIsInline()) {
clazz += ` tn-avatar--${this.size}`
}
if (this.shadow) {
clazz += ' tn-avatar--shadow'
}
return clazz
},
avatarStyle() {
let style = {}
if (this.backgroundColorStyle) {
style.background = this.backgroundColorStyle
} else if (this.shadow && this.showImg) {
style.backgroundImage = `url(${this.src})`
}
if (this.border) {
style.border = `${this.borderSize}rpx solid ${this.borderColor}`
}
if (!this._checkSizeIsInline()) {
style.width = this.size
style.height = this.size
}
return style
},
imgClass() {
let clazz = ''
clazz += ` tn-avatar__img--${this.shape}`
return clazz
}
},
methods: {
//
loadImageError() {
this.imgLoadError = true
},
//
click() {
this.$emit("click", this.index)
},
// 使
_checkSizeIsInline() {
if (/^(xs|sm|md|lg|xl|xxl)$/.test(this.size)) return true
else return false
}
}
}
</script>
<style lang="scss" scoped>
.tn-avatar {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
margin: 0;
padding: 0;
text-align: center;
align-items: center;
justify-content: center;
background-color: $tn-font-holder-color;
// color: #FFFFFF;
white-space: nowrap;
position: relative;
width: 64rpx;
height: 64rpx;
z-index: 1;
&--sm {
width: 48rpx;
height: 48rpx;
}
&--lg {
width: 96rpx;
height: 96rpx;
}
&--xl {
width: 128rpx;
height: 128rpx;
}
&--square {
border-radius: 10rpx;
}
&--circle {
border-radius: 5000rpx;
}
&--shadow {
position: relative;
&::after {
content: " ";
display: block;
background: inherit;
filter: blur(10rpx);
position: absolute;
width: 100%;
height: 100%;
top: 10rpx;
left: 10rpx;
z-index: -1;
opacity: 0.4;
transform-origin: 0 0;
border-radius: inherit;
transform: scale(1, 1);
}
}
&__img {
width: 100%;
height: 100%;
&--square {
border-radius: 10rpx;
}
&--circle {
border-radius: 5000rpx;
}
}
&__text {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
}
</style>

View File

@ -0,0 +1,173 @@
<template>
<view
class="tn-badge-class tn-badge"
:class="[
backgroundColorClass,
fontColorClass,
badgeClass
]"
:style="[badgeStyle]"
@click="handleClick"
>
<slot v-if="!dot"></slot>
</view>
</template>
<script>
import componentsColorMixin from '../../libs/mixin/components_color.js'
export default {
mixins: [componentsColorMixin],
name: 'tn-badge',
props: {
//
index: {
type: [Number, String],
default: '0'
},
// rpx
radius: {
type: Number,
default: 0
},
//
padding: {
type: String,
default: ''
},
//
margin: {
type: String,
default: ''
},
//
dot: {
type: Boolean,
default: false
},
// 使
absolute: {
type: Boolean,
default: false
},
// top
top: {
type: [String, Number],
default: ''
},
// right
right: {
type: [String, Number],
default: ''
},
//
translateCenter: {
type: Boolean,
default: true
}
},
computed: {
badgeClass() {
let clazz = ''
if (this.dot) {
clazz += ' tn-badge--dot'
}
if (this.absolute) {
clazz += ' tn-badge--absolute'
if (this.translateCenter) {
clazz += ' tn-badge--center-position'
}
}
return clazz
},
badgeStyle() {
let style = {}
if (this.radius !== 0) {
style.width = this.radius + 'rpx'
style.height = this.radius + 'rpx'
style.lineHeight = this.radius + 'rpx'
// style.borderRadius = (this.radius * 8) + 'rpx'
}
if (this.padding) {
style.padding = this.padding
}
if (this.margin) {
style.margin = this.margin
}
if (this.fontColorStyle) {
style.color = this.fontColorStyle
}
if (this.fontSize) {
style.fontSize = this.fontSize + this.fontUnit
}
if (this.backgroundColorStyle) {
style.backgroundColor = this.backgroundColorStyle
}
if (this.top) {
style.top = this.$tn.string.getLengthUnitValue(this.top)
}
if (this.right) {
style.right = this.$tn.string.getLengthUnitValue(this.right)
}
return style
},
},
data() {
return {
}
},
methods: {
//
handleClick() {
this.$emit('click', {
index: Number(this.index)
})
this.$emit('tap', {
index: Number(this.index)
})
},
}
}
</script>
<style lang="scss" scoped>
.tn-badge {
width: auto;
height: auto;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
font-size: 20rpx;
background-color: #FFFFFF;
// color: #FFFFFF;
border-radius: 100rpx;
padding: 4rpx 8rpx;
line-height: initial;
&--dot {
width: 8rpx;
height: 8rpx;
border-radius: 50%;
padding: 0;
}
&--absolute {
position: absolute;
top: 0;
right: 0;
}
&--center-position {
transform: translate(50%, -50%);
}
}
</style>

View File

@ -0,0 +1,348 @@
<template>
<button class="tn-btn-class tn-btn" :class="[
buttonClass,
backgroundColorClass,
fontColorClass
]" :style="[buttonStyle]" hover-class="tn-hover" :loading="loading" :disabled="disabled" :form-type="formType"
:open-type="openType" @getuserinfo="handleGetUserInfo" @getphonenumber="handleGetPhoneNumber"
@contact="handleContact" @error="handleError" @tap="handleClick">
<slot></slot>
</button>
</template>
<script>
import componentsColorMixin from '../../libs/mixin/components_color.js'
import {
debounceFun,
throttleFun
} from '../../libs/function/applyEven.js'
let spanTime = 200;
export default {
mixins: [componentsColorMixin],
name: "tn-button",
// bindsubmit
behaviors: ['wx://form-field-button'],
props: {
//
index: {
type: [Number, String],
default: 0
},
// default round icon
shape: {
type: String,
default: 'default'
},
//
shadow: {
type: Boolean,
default: false
},
// rpx%
width: {
type: String,
default: 'auto'
},
// rpx%
height: {
type: String,
default: ''
},
// sm lg
size: {
type: String,
default: ''
},
//
fontBold: {
type: Boolean,
default: false
},
padding: {
type: String,
default: '0 30rpx'
},
// cssmargin
margin: {
type: String,
default: ''
},
//
plain: {
type: Boolean,
default: false
},
// plain=true
border: {
type: Boolean,
default: true
},
// plain=true
borderBold: {
type: Boolean,
default: false
},
//
disabled: {
type: Boolean,
default: false
},
//
loading: {
type: Boolean,
default: false
},
// form
formType: {
type: String,
default: ''
},
//
openType: {
type: String,
default: ''
},
// (200ms)
blockRepeatClick: {
type: Boolean,
default: false
},
//blockRepeatClicknone debounce throttle
scene:{
type: String,
default: 'none'
},
//
blockTime:{
type: Number,
default: 200
}
},
computed: {
// class
buttonClass() {
let clazz = ''
//
switch (this.shape) {
case 'icon':
case 'round':
clazz += ' tn-round'
break
}
//
if (this.shadow) {
if (this.backgroundColorClass !== '' && this.backgroundColorClass.indexOf('tn-bg') != -1) {
const color = this.backgroundColor.slice(this.backgroundColor.lastIndexOf('-') + 1)
clazz += ` tn-shadow-${color}`
} else {
clazz += ' tn-shadow-blur'
}
}
//
if (this.fontBold) {
clazz += ' tn-text-bold'
}
// 便
if (this.plain) {
clazz += ' tn-btn--plain'
if (this.border) {
clazz += ' tn-border-solid'
if (this.borderBold) {
clazz += ' tn-bold-border'
}
if (this.backgroundColor !== '' && this.backgroundColor.includes('tn-bg')) {
const color = this.backgroundColor.slice(this.backgroundColor.lastIndexOf('-') + 1)
clazz += ` tn-border-${color}`
}
}
}
return clazz
},
//
buttonStyle() {
let style = {}
switch (this.size) {
case 'sm':
style.padding = '0 20rpx'
style.fontSize = '22rpx'
style.height = this.height || '48rpx'
break
case 'lg':
style.padding = '0 40rpx'
style.fontSize = '32rpx'
style.height = this.height || '80rpx'
break
default:
style.padding = '0 30rpx'
style.fontSize = '28rpx'
style.height = this.height || '64rpx'
}
//
if (this.padding) {
style.padding = this.padding
}
//
if (this.margin) {
style.margin = this.margin
}
//
if (this.fontSize) {
style.fontSize = this.fontSize + this.fontUnit
}
style.width = this.shape === 'icon' ? style.height : this.width
style.padding = this.shape === 'icon' ? '0' : style.padding
if (this.fontColorStyle) {
style.color = this.fontColorStyle
}
if (!this.backgroundColorClass) {
if (this.plain) {
style.borderColor = this.backgroundColorStyle || '#080808'
} else {
style.backgroundColor = this.backgroundColorStyle || '#FFFFFF'
}
}
//
if (this.shadow && !this.backgroundColorClass) {
if (this.backgroundColorStyle.indexOf('#') != -1) {
style.boxShadow = `6rpx 6rpx 8rpx ${(this.backgroundColorStyle || '#000000')}10`
} else if (this.backgroundColorStyle.indexOf('rgb') != -1 || this.backgroundColorStyle.indexOf(
'rgba') != -1 || !this.backgroundColorStyle) {
style.boxShadow = `6rpx 6rpx 8rpx ${(this.backgroundColorStyle || 'rgba(0, 0, 0, 0.1)')}`
}
}
return style
},
},
data() {
return {
}
},
watch:{
//
//使
blockTime:{
handler(newVal,oldVal){
this.initScene();
}
},
},
mounted() {
this.initScene()
},
methods: {
initScene(){
// blockTime,
//
this.debounceClick=debounceFun(function() {
this.emitClick();
}, this.blockTime);
//
this.throttleClick=throttleFun(function() {
this.emitClick();
}, this.blockTime);
},
//
debounceClick:debounceFun(function() {
this.emitClick();
}, spanTime),
//
throttleClick:throttleFun(function() {
this.emitClick();
}, spanTime),
emitClick() {
//
this.$emit('click', {
index: Number(this.index)
})
// tap
this.$emit('tap', {
index: Number(this.index)
})
},
//
handleClick() {
if (this.disabled) {
return
}
//
if (this.blockRepeatClick) {
this.throttleClick();
return;
}
//
if(this.scene === 'none'){
this.emitClick();
}else if(this.scene == 'debounce'){
//
this.debounceClick();
}else{
//
this.throttleClick();
}
},
handleGetUserInfo({
detail = {}
} = {}) {
this.$emit('getuserinfo', detail);
},
handleContact({
detail = {}
} = {}) {
this.$emit('contact', detail);
},
handleGetPhoneNumber({
detail = {}
} = {}) {
this.$emit('getphonenumber', detail);
},
handleError({
detail = {}
} = {}) {
this.$emit('error', detail);
},
}
}
</script>
<style lang="scss" scoped>
.tn-btn {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
line-height: 1;
text-align: center;
text-decoration: none;
overflow: visible;
transform: translate(0rpx, 0rpx);
// background-color: $tn-mai
border-radius: 12rpx;
// color: $tn-font-color;
margin: 0;
&--plain {
background-color: transparent !important;
background-image: none;
&.tn-round {
border-radius: 1000rpx !important;
}
}
}
</style>

View File

@ -0,0 +1,707 @@
<template>
<tn-popup
v-model="value"
mode="bottom"
:popup="false"
length="auto"
:borderRadius="borderRadius"
:safeAreaInsetBottom="safeAreaInsetBottom"
:maskCloseable="maskCloseable"
:closeBtn="closeBtn"
:zIndex="elIndex"
@close="close"
>
<view class="tn-calendar-class tn-calendar">
<!-- 头部 -->
<view class="tn-calendar__header">
<view v-if="!$slots.tooltip || !$slots.$tooltip" class="tn-calendar__header__text">
{{ toolTips }}
</view>
<view v-else>
<slot name="tooltip"></slot>
</view>
</view>
<!-- 操作提示信息 -->
<view class="tn-calendar__action">
<view v-if="changeYear" class="tn-calendar__action__icon" :style="{backgroundColor: yearArrowColor}" @tap.stop="changeYearHandler(false)">
<view><text class="tn-icon-left"></text></view>
</view>
<view v-if="changeMonth" class="tn-calendar__action__icon" :style="{backgroundColor: monthArrowColor}" @tap.stop="changeMonthHandler(false)">
<view><text class="tn-icon-left"></text></view>
</view>
<view class="tn-calendar__action__text">{{ dateTitle }}</view>
<view v-if="changeMonth" class="tn-calendar__action__icon" :style="{backgroundColor: monthArrowColor}" @tap.stop="changeMonthHandler(true)">
<view><text class="tn-icon-right"></text></view>
</view>
<view v-if="changeYear" class="tn-calendar__action__icon" :style="{backgroundColor: yearArrowColor}" @tap.stop="changeYearHandler(true)">
<view><text class="tn-icon-right"></text></view>
</view>
</view>
<!-- 星期中文标识 -->
<view class="tn-calendar__week-day-zh">
<view v-for="(item,index) in weekDayZh" :key="index" class="tn-calendar__week-day-zh__text">{{ item }}</view>
</view>
<!-- 日历主体 -->
<view class="tn-calendar__content">
<!-- 前置空白部分 -->
<block v-for="(item, index) in weekdayArr" :key="index">
<view class="tn-calendar__content__item"></view>
</block>
<view
v-for="(item, index) in daysArr"
:key="index"
class="tn-calendar__content__item"
:class="{
'tn-hover': disabledChoose(year, month, index + 1),
'tn-calendar__content--start-date': (mode === 'range' && startDate == `${year}-${month}-${index+1}`) || mode === 'date',
'tn-calendar__content--end-date': (mode === 'range' && endDate == `${year}-${month}-${index+1}`) || mode === 'date'
}"
:style="{
backgroundColor: colorValue(index, 'bg')
}"
@tap.stop="dateClick(index)"
>
<view class="tn-calendar__content__item__text" :style="{color: colorValue(index, 'text')}">
<view>{{ item.day }}</view>
</view>
<view class="tn-calendar__content__item__tips" :style="{color: item.color}">
{{ item.bottomInfo }}
</view>
</view>
<view class="tn-calendar__content__month--bg">{{ month }}</view>
</view>
<!-- 底部 -->
<view class="tn-calendar__bottom">
<view class="tn-calendar__bottom__choose">
<text>{{ mode === 'date' ? activeDate : startDate }}</text>
<text v-if="endDate">{{ endDate }}</text>
</view>
<view class="tn-calendar__bottom__btn" :style="{backgroundColor: btnColor}" @click="handleBtnClick(false)">
<view class="tn-calendar__bottom__btn--text">确定</view>
</view>
</view>
</view>
</tn-popup>
</template>
<script>
import Calendar from '../../libs/utils/calendar.js'
export default {
name: 'tn-calendar',
props: {
//
value: {
type: Boolean,
default: false
},
//
// date -> range ->
mode: {
type: String,
default: 'date'
},
//
changeYear: {
type: Boolean,
default: true
},
//
changeMonth: {
type: Boolean,
default: true
},
//
maxYear: {
type: [Number, String],
default: 2100
},
//
minYear: {
type: [Number, String],
default: 1970
},
// ()
minDate: {
type: String,
default: '1970-01-01'
},
//
maxDate: {
type: String,
default: ''
},
//
monthArrowColor: {
type: String,
default: '#AAAAAA'
},
//
yearArrowColor: {
type: String,
default: '#C8C8C8'
},
//
color: {
type: String,
default: '#080808'
},
// |
activeBgColor: {
type: String,
default: '#01BEFF'
},
// |
activeColor: {
type: String,
default: '#FFFFFF'
},
//
rangeBgColor: {
type: String,
default: '#E6E6E655'
},
//
rangeColor: {
type: String,
default: '#01BEFF'
},
// mode=range
startText: {
type: String,
default: '开始'
},
// mode=range
endText: {
type: String,
default: '结束'
},
//
btnColor: {
type: String,
default: '#01BEFF'
},
//
lunarColor: {
type: String,
default: '#AAAAAA'
},
//
isActiveCurrent: {
type: Boolean,
default: true
},
// mode=date
isChange: {
type: Boolean,
default: false
},
//
showLunar: {
type: Boolean,
default: true
},
//
toolTips: {
type: String,
default: '请选择日期'
},
//
borderRadius: {
type: Number,
default: 8
},
// iPhoneX
safeAreaInsetBottom: {
type: Boolean,
default: false
},
//
maskCloseable: {
type: Boolean,
default: true
},
// zIndex
zIndex: {
type: Number,
default: 0
},
//
closeBtn: {
type: Boolean,
default: false
},
},
computed: {
dateChange() {
return `${this.mode}-${this.minDate}-${this.maxDate}`
},
elIndex() {
return this.zIndex ? this.zIndex : this.$tn.zIndex.popup
},
colorValue() {
return (index, type) => {
let color = type === 'bg' ? '' : this.color
let day = index + 1
let date = `${this.year}-${this.month}-${day}`
let timestamp = new Date(date.replace(/\-/g,'/')).getTime()
let start = this.startDate.replace(/\-/g,'/')
let end = this.endDate.replace(/\-/g,'/')
if ((this.mode === 'date' && this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
color = type === 'bg' ? this.activeBgColor : this.activeColor
} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
color = type === 'bg' ? this.rangeBgColor : this.rangeColor
}
return color
}
}
},
data() {
return {
// 1-7
weekday: 1,
weekdayArr: [],
//
weekDayZh: ['日','一','二','三','四','五','六'],
//
days: 0,
daysArr: [],
year: 2021,
month: 0,
day: 0,
startYear: 0,
startMonth: 0,
startDay: 0,
endYear: 0,
endMonth: 0,
endDay: 0,
today: '',
activeDate: '',
startDate: '',
endDate: '',
min: null,
max: null,
//
dateTitle: '',
//
chooseStart: false
}
},
watch: {
dateChange() {
this.init()
}
},
created() {
this.init()
},
methods: {
//
init() {
let now = new Date()
this.year = now.getFullYear()
this.month = now.getMonth() + 1
this.day = now.getDate()
this.today = `${this.year}-${this.month}-${this.day}`
this.activeDate = this.today
this.min = this.initDate(this.minDate)
this.max = this.initDate(this.maxDate || this.today)
this.startDate = ''
this.startYear = 0
this.startMonth = 0
this.startDay = 0
this.endDate = ''
this.endYear = 0
this.endMonth = 0
this.endDay = 0
this.chooseStart = false
this.changeData()
},
//
changeMonthHandler(add) {
if (add) {
let month = this.month + 1
let year = month > 12 ? this.year + 1 : this.year
if (!this.checkRange(year)) {
this.month = month > 12 ? 1 : month
this.year = year
this.changeData()
}
} else {
let month = this.month - 1
let year = month < 1 ? this.year - 1 : this.year
if (!this.checkRange(year)) {
this.month = month < 1 ? 12 : month
this.year = year
this.changeData()
}
}
},
//
changeYearHandler(add) {
let year = add ? this.year + 1 : this.year - 1
if (!this.checkRange(year)) {
this.year = year
this.changeData()
}
},
//
dateClick(day) {
day += 1
if (!this.disabledChoose(this.year, this.month, day)) {
this.day = day
let date = `${this.year}-${this.month}-${day}`
if (this.mode === 'date') {
this.activeDate = date
} else {
let startTimeCompare = new Date(date.replace(/\-/g,'/')).getTime() < new Date(this.startDate.replace(/\-/g,'/')).getTime()
if (!this.chooseStart || startTimeCompare) {
this.startDate = date
this.startYear = this.year
this.startMonth = this.month
this.startDay = this.day
this.endYear = 0
this.endMonth = 0
this.endDay = 0
this.endDate = ''
this.activeDate = ''
this.chooseStart = true
} else {
this.endDate = date
this.endYear = this.year
this.endMonth = this.month
this.endDay = this.day
this.chooseStart = false
}
}
this.daysArr = this.handleDaysArr()
}
},
//
changeData() {
this.days = this.getMonthDay(this.year, this.month)
this.daysArr = this.handleDaysArr()
this.weekday = this.getMonthFirstWeekDay(this.year, this.month)
this.weekdayArr = this.generateArray(1, this.weekday)
this.dateTitle = `${this.year}${this.month}`
if (this.isChange && this.mode === 'date') {
this.handleBtnClick(true)
}
},
//
handleBtnClick(show) {
if (!show) {
this.close()
}
if (this.mode === 'date') {
let arr = this.activeDate.split('-')
let year = this.isChange ? this.year : Number(arr[0])
let month = this.isChange ? this.month : Number(arr[1])
let day = this.isChange ? this.day : Number(arr[2])
let days = this.getMonthDay(year, month)
let result = `${year}-${this.formatNumber(month)}-${this.formatNumber(day)}`
let weekText = this.getWeekText(result)
let isToday = false
if (`${year}-${month}-${day}` === this.today) {
isToday = true
}
this.$emit('change', {
year,
month,
day,
days,
week: weekText,
isToday,
date: result,
//
switch: show
})
} else {
if (!this.startDate || !this.endDate) return
let startMonth = this.formatNumber(this.startMonth)
let startDay = this.formatNumber(this.startDay)
let startDate = `${this.startYear}-${startMonth}-${startDay}`
let startWeek = this.getWeekText(startDate)
let endMonth = this.formatNumber(this.endMonth)
let endDay = this.formatNumber(this.endDay)
let endDate = `${this.endYear}-${endMonth}-${endDay}`
let endWeek = this.getWeekText(endDate)
this.$emit('change', {
startYear: this.startYear,
startMonth: this.startMonth,
startDay: this.startDay,
startDate,
startWeek,
endYear: this.endYear,
endMonth: this.endMonth,
endDay: this.endDay,
endDate,
endWeek
})
}
},
//
disabledChoose(year, month, day) {
let flag = true
let date = `${year}/${month}/${day}`
let min = `${this.min.year}/${this.min.month}/${this.min.day}`
let max = `${this.max.year}/${this.max.month}/${this.max.day}`
let timestamp = new Date(date).getTime()
if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
flag = false
}
return flag
},
//
checkRange(year) {
let overstep = false
if (year < this.minYear || year > this.maxYear) {
uni.showToast({
title: '所选日期超出范围',
icon: 'none'
})
overstep = true
}
return overstep
},
//
initDate(date) {
let fdate = date.split('-')
return {
year: Number(fdate[0] || 1970),
month: Number(fdate[1] || 1),
day: Number(fdate[2] || 1)
}
},
//
handleDaysArr() {
let days = this.generateArray(1, this.days)
let daysArr = days.map((item) => {
let bottomInfo = this.showLunar ? Calendar.solar2lunar(this.year, this.month, item).IDayCn : ''
let color = this.showLunar ? this.lunarColor : this.activeColor
if (
(this.mode === 'date' && this.day == item) ||
(this.mode === 'range' && (this.startDay == item || this.endDay == item))
) {
color = this.activeColor
}
if (this.mode === 'range') {
if (this.startDay == item && this.startDay != this.endDay) {
bottomInfo = this.startText
}
if (this.endDay == item) {
bottomInfo = this.endText
}
}
return {
day: item,
color: color,
bottomInfo: bottomInfo
}
})
return daysArr
},
//
getMonthDay(year, month) {
return new Date(year, month, 0).getDate()
},
//
getMonthFirstWeekDay(year, month) {
return new Date(`${year}/${month}/01 00:00:00`).getDay()
},
//
getWeekText(date) {
date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`)
let week = date.getDay()
return '星期' + this.weekDayZh[week]
},
//
generateArray(start, end) {
return Array.from(new Array(end + 1).keys()).slice(start)
},
//
formatNumber(num) {
return num < 10 ? '0' + num : num + ''
},
//
close() {
this.$emit('input', false)
}
}
}
</script>
<style lang="scss" scoped>
.tn-calendar {
color: $tn-font-color;
&__header {
width: 100%;
box-sizing: border-box;
font-size: 30rpx;
background-color: #FFFFFF;
color: $tn-main-color;
&__text {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-top: 30rpx;
padding: 0 60rpx;
}
}
&__action {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 40rpx 0 40rpx 0;
&__icon {
display: flex;
align-items: center;
justify-content: center;
margin: 0 16rpx;
width: 32rpx;
height: 32rpx;
font-size: 20rpx;
// line-height: 32rpx;
border-radius: 50%;
color: #FFFFFF;
}
&__text {
padding: 0 16rpx;
color: $tn-font-color;
font-size: 32rpx;
font-weight: bold;
}
}
&__week-day-zh {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 12rpx 0;
overflow: hidden;
box-shadow: 16rpx 6rpx 8rpx 0 #E6E6E6;
margin-bottom: 2rpx;
&__text {
flex: 1;
text-align: center;
}
}
&__content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
padding: 12rpx 0;
box-sizing: border-box;
background-color: #F7F7F7;
position: relative;
&__item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 14.2857%;
padding: 12rpx 0;
margin: 6rpx 0;
overflow: hidden;
position: relative;
z-index: 2;
// box-shadow: inset 0rpx 0rpx 22rpx 4rpx rgba(255,255,255, 0.52);
&__text {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 80rpx;
font-size: 32rpx;
position: relative;
}
&__tips {
position: absolute;
width: 100%;
line-height: 24rpx;
left: 0;
bottom: 8rpx;
text-align: center;
z-index: 2;
transform-origin: center center;
transform: scale(0.8);
}
}
&--start-date {
border-top-left-radius: 8rpx;
border-bottom-left-radius: 8rpx;
}
&--end-date {
border-top-right-radius: 8rpx;
border-bottom-right-radius: 8rpx;
}
&__month {
&--bg {
position: absolute;
font-size: 200rpx;
line-height: 200rpx;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: $tn-font-holder-color;
z-index: 1;
}
}
}
&__bottom {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
background-color: #F7F7F7;
padding: 0 40rpx 30rpx;
box-sizing: border-box;
font-size: 24rpx;
color: $tn-font-sub-color;
&__choose {
height: 50rpx;
}
&__btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 60rpx;
border-radius: 40rpx;
color: #FFFFFF;
font-size: 28rpx;
}
}
}
</style>

View File

@ -0,0 +1,320 @@
<template>
<view class="tn-car-keyboard-class tn-car-keyboard" @touchmove.stop.prevent="() => {}">
<view class="tn-car-keyboard__grids">
<view
v-for="(data, index) in inputCarNumber ? endKeyBoardList : areaList"
:key="index"
class="tn-car-keyboard__grids__item"
>
<view
v-for="(sub_data, sub_index) in data"
:key="sub_index"
class="tn-car-keyboard__grids__btn"
:class="{'tn-car-keyboard__grids__btn--disabled': sub_data === 'I'}"
:hover-class="sub_data !== 'I' ? 'tn-car-keyboard--hover' : ''"
:hover-stay-time="100"
@tap="click(index, sub_index)"
>
{{ sub_data }}
</view>
</view>
<view
class="tn-car-keyboard__back"
hover-class="tn-hover-class"
:hover-stay-time="150"
@touchstart.stop="backspaceClick"
@touchend="clearTimer"
>
<view class="tn-icon-left-arrow tn-car-keyboard__back__icon"></view>
</view>
<view
class="tn-car-keyboard__change"
hover-class="tn-car-keyboard--hover"
:hover-stay-time="150"
@tap="changeMode"
>
<text class="tn-car-keyboard__mode--zh" :class="[`tn-car-keyboard__mode--${!inputCarNumber ? 'active' : 'inactive'}`]"></text>
/
<text class="tn-car-keyboard__mode--en" :class="[`tn-car-keyboard__mode--${inputCarNumber ? 'active' : 'inactive'}`]"></text>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'tn-car-keyboard',
props: {
//
randomEnabled: {
type: Boolean,
default: false
},
//
switchEnMode: {
type: Boolean,
default: false
}
},
computed: {
areaList() {
let data = [
'京',
'沪',
'粤',
'津',
'冀',
'豫',
'云',
'辽',
'黑',
'湘',
'皖',
'鲁',
'苏',
'浙',
'赣',
'鄂',
'桂',
'甘',
'晋',
'陕',
'蒙',
'吉',
'闽',
'贵',
'渝',
'川',
'青',
'琼',
'宁',
'藏',
'港',
'澳',
'新',
'使',
'学',
'临',
'警'
]
//
if (this.randomEnabled) data = this.$tn.array.random(data)
//
let showData = []
showData[0] = data.slice(0, 10)
showData[1] = data.slice(10, 20)
showData[2] = data.slice(20, 30)
showData[3] = data.slice(30, 37)
return showData
},
endKeyBoardList() {
let data = [
1,
2,
3,
4,
5,
6,
7,
8,
9,
0,
'Q',
'W',
'E',
'R',
'T',
'Y',
'U',
'I',
'O',
'P',
'A',
'S',
'D',
'F',
'G',
'H',
'J',
'K',
'L',
'Z',
'X',
'C',
'V',
'B',
'N',
'M'
]
//
if (this.randomEnabled) data = this.$tn.array.random(data)
//
let showData = []
showData[0] = data.slice(0, 10)
showData[1] = data.slice(10, 20)
showData[2] = data.slice(20, 29)
showData[3] = data.slice(29, 36)
return showData
}
},
data() {
return {
//
inputCarNumber: false,
//
longPressDeleteTimer: null
}
},
watch:{
switchEnMode: {
handler(value) {
if (value) {
this.inputCarNumber = true
} else {
this.inputCarNumber = false
}
},
immediate: true
}
},
methods: {
//
click(i, j) {
let value = ''
//
if (this.inputCarNumber) value = this.endKeyBoardList[i][j]
else value = this.areaList[i][j]
// I
if (value === 'I') return
this.$emit('change', value)
},
//
// /
changeMode() {
this.inputCarNumber = !this.inputCarNumber
},
// 退
backspaceClick() {
this.$emit('backspace')
this.clearTimer()
this.longPressDeleteTimer = setInterval(() => {
this.$emit('backspace')
}, 250)
},
//
clearTimer() {
if (this.longPressDeleteTimer) {
clearInterval(this.longPressDeleteTimer)
this.longPressDeleteTimer = null
}
}
}
}
</script>
<style lang="scss" scoped>
.tn-car-keyboard {
position: relative;
padding: 24rpx 0;
background-color: #E6E6E6;
&__grids {
&__item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
&__btn {
display: inline-flex;
justify-content: center;
flex: 0 0 64rpx;
width: 62rpx;
height: 80rpx;
font-size: 38rpx;
line-height: 80rpx;
font-weight: 500;
text-decoration: none;
text-align: center;
background-color: #FFFFFF;
margin: 8rpx 5rpx;
border-radius: 8rpx;
box-shadow: 0 2rpx 0rpx $tn-box-shadow-color;
&--disabled {
opacity: 0.6;
}
}
}
&__back {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
position: absolute;
width: 96rpx;
height: 80rpx;
right: 22rpx;
bottom: 32rpx;
background-color: #E6E6E6;
border-radius: 8rpx;
box-shadow: 0 2rpx 0rpx $tn-box-shadow-color;
}
&__change {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
position: absolute;
width: 96rpx;
height: 80rpx;
left: 22rpx;
bottom: 32rpx;
line-height: 1;
background-color: #FFFFFF;
border-radius: 8rpx;
box-shadow: 0 2rpx 0rpx $tn-box-shadow-color;
}
&__mode {
&--zh {
transform: translateY(-10rpx);
}
&--en {
transform: translateY(10rpx);
}
&--active {
color: $tn-main-color;
font-size: 30rpx;
}
&--inactive {
&.tn-car-keyboard__mode--zh {
transform: scale(0.85) translateY(-10rpx);
}
}
&--inactive {
&.tn-car-keyboard__mode--en {
transform: scale(0.85) translateY(10rpx);
}
}
}
&--hover {
background-color: #E6E6E6 !important;
}
}
</style>

View File

@ -0,0 +1,654 @@
<template>
<view class="tn-cascade-selection tn-cascade-selection-class">
<scroll-view
class="selection__scroll-view"
:class="[{'tn-border-solid-bottom': headerLine}]"
:style="[scrollViewStyle]"
scroll-x
scroll-with-animation
:scroll-into-view="scrollViewId"
>
<view class="selection__header" :class="[backgroundColorClass]" :style="[headerStyle]">
<view
v-for="(item, index) in selectedArr"
:key="index"
:id="`select__${index}`"
class="selection__header__item"
:class="[headerItemClass(index)]"
:style="[headerItemStyle(index)]"
@tap.stop="clickNav(index)"
>
{{ item.text }}
<view
v-if="index===currentTab && showActiveLine"
class="selection__header__line"
:style="{backgroundColor: activeLineColor}"
></view>
</view>
</view>
</scroll-view>
<swiper
class="selection__list"
:class="[backgroundColorClass]"
:style="[listStyle]"
:current="currentTab"
:duration="300"
@change="switchTab"
>
<swiper-item
v-for="(item, index) in selectedArr"
:key="index"
>
<scroll-view
class="selection__list__item"
:style="{height: selectionContainerHeight + 'rpx'}"
scroll-y
:scroll-into-view="item.scrollViewId"
>
<view class="selection__list__item--first"></view>
<view
v-for="(subItem, subIndex) in item.list"
:key="subIndex"
:id="`select__${subIndex}`"
class="selection__list__item__cell"
:style="[itemStyle]"
@tap="change(index, subIndex, subItem)"
>
<view
v-if="item.index === subIndex"
class="selection__list__item__icon tn-icon-success"
:style="[itemIconStyle]"
></view>
<image
v-if="subItem.src"
class="selection__list__item__image"
:style="[itemImageStyle]"
:src="subItem.src"
></image>
<view
class="selection__list__item__title"
:class="[{'tn-text-bold': item.index === subIndex && itemActiveBold}]"
:style="[itemTitleStyle(index, subIndex)]"
>
{{ subItem.text }}
</view>
<view
v-if="subItem.subText"
class="selection__list__item__title--sub"
:style="[itemSubTitleStyle]"
>
{{ subItem.subText }}
</view>
</view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</template>
<script>
import componentsColorMixin from '../../libs/mixin/components_color.js'
export default {
name: 'tn-cascade-selection',
mixins: [ componentsColorMixin ],
props: {
//
/* {
text: '', //
subText: '', //
src: '', //
value: 0, //
children: [
{
text: '',
subText: '',
value: 0,
children: []
}
]
} */
list: {
type: Array,
default() {
return []
}
},
//
// ['value1','value2','value3']
defaultValue: {
type: Array,
default() {
return []
}
},
//
request: {
type: Boolean,
default: false
},
// requesttrue,
receiveData: {
type: Array,
default() {
return []
}
},
// header线
headerLine: {
type: Boolean,
default: true
},
// header
headerBgColor: {
type: String,
default: ''
},
// ,rpx
tabsHeight: {
type: Number,
default: 88
},
//
text: {
type: String,
default: '请选择'
},
//
activeColor: {
type: String,
default: '#01BEFF'
},
//
activeBold: {
type: Boolean,
default: true
},
// 线
showActiveLine: {
type: Boolean,
default: true
},
// 线
activeLineColor: {
type: String,
default: '#01BEFF'
},
// icon,rpx
activeIconSize: {
type: Number,
default: 0
},
// icon
activeIconColor: {
type: String,
default: '#01BEFF'
},
// item, rpx
itemImgWidth: {
type: Number,
default: 0
},
// item, rpx
itemImgHeight: {
type: Number,
default: 0
},
// item
itemImgRadius: {
type: String,
default: '50%'
},
// item text
itemTextColor: {
type: String,
default: ''
},
// item text
itemActiveTextColor: {
type: String,
default: ''
},
// item text
itemActiveBold: {
type: Boolean,
default: true
},
// item text, rpx
itemTextSize: {
type: Number,
default: 0
},
// item subText
itemSubTextColor: {
type: String,
default: ''
},
// item subText, rpx
itemSubTextSize: {
type: Number,
default: 0
},
// item
itemStyle: {
type: Object,
default() {
return {}
}
},
// selection, rpx
selectionContainerHeight: {
type: Number,
default: 300
}
},
computed: {
scrollViewStyle() {
let style = {}
if (this.headerBgColor) {
style.backgroundColor = this.headerBgColor
}
return style
},
headerStyle() {
let style = {}
style.height = `${this.tabsHeight}rpx`
if (this.backgroundColorStyle) {
style.backgroundColor = this.backgroundColorStyle
}
return style
},
headerItemClass() {
return (index) => {
let clazz = ''
if (index !== this.currentTab) {
clazz += ` ${this.fontColorClass}`
} else {
if (this.activeBold) {
clazz += ' tn-text-bold'
}
}
return clazz
}
},
headerItemStyle() {
return (index) => {
let style = {}
style.color = index === this.currentTab ? this.activeColor : (this.fontColorStyle ? this.fontColorStyle : '')
if (this.fontSizeStyle) {
style.fontSize = this.fontSizeStyle
}
return style
}
},
listStyle() {
let style = {}
style.height = `${this.selectionContainerHeight}rpx`
if (this.backgroundColorStyle) {
style.color = this.backgroundColorStyle
}
return style
},
itemIconStyle() {
let style = {}
if (this.activeIconColor) {
style.color = this.activeIconColor
}
if (this.activeIconSize) {
style.fontSize = this.activeIconSize + 'rpx'
}
return style
},
itemImageStyle() {
let style = {}
if (this.itemImgWidth) {
style.width = this.itemImgWidth + 'rpx'
}
if (this.itemImgHeight) {
style.height = this.itemImgHeight + 'rpx'
}
if (this.itemImgRadius) {
style.borderRadius = this.itemImgRadius
}
return style
},
itemTitleStyle() {
return (index, subIndex) => {
let style = {}
if (index === subIndex) {
if (this.itemActiveTextColor) {
style.color = this.itemActiveTextColor
}
} else {
if (this.itemTextColor) {
style.color = this.itemTextColor
}
}
if (this.itemTextSize) {
style.fontSize = this.itemTextSize + 'rpx'
}
return style
}
},
itemSubTitleStyle() {
let style = {}
if (this.itemSubTextColor) {
style.color = this.itemSubTextColor
}
if (this.itemSubTextSize) {
style.fontSize = this.itemSubTextSize + 'rpx'
}
return {}
}
},
watch: {
list(val) {
this.initData(val, -1)
},
defaultValue(val) {
this.setDefaultValue(val)
},
receiveData(val) {
this.addSubData(val, this.currentTab)
},
},
data() {
return {
//
currentTab: 0,
// tabsscrollView
scrollViewId: 'select__0',
//
selectedArr: []
}
},
created() {
this.setDefaultValue(this.defaultValue)
},
methods: {
//
initData(data, index) {
if (!data || data.length === 0) return
if (this.request) {
//
this.addSubData(data, index)
} else {
this.addSubData(this.getItemList(index, -1), index)
}
},
//
reset() {
this.initData(this.list, -1)
},
//
switchTab(e) {
this.currentTab = e.detail.current
this.checkSelectPosition()
},
//
clickNav(index) {
if (this.currentTab !== index) {
this.currentTab = index
}
},
//
change(index, subIndex, subItem) {
let item = this.selectedArr[index]
if (item.index === subIndex) return
item.index = subIndex
item.text = subItem.text
item.subText = subItem.subText || ''
item.value = subItem.value
item.src = subItem.src || ''
this.$emit('change', {
index: index,
subIndex: subIndex,
...subItem
})
//
if (!this.request) {
let data = this.getItemList(index, subIndex)
this.addSubData(data, index)
}
},
//
setDefaultValue(val) {
let defaultValues = val || []
if (defaultValues.length > 0) {
this.selectedArr = this.getItemListWithValues(JSON.parse(JSON.stringify(this.list)), defaultValues)
if (!this.selectedArr) return
this.currentTab = this.selectedArr.length - 1
this.$nextTick(() => {
this.checkSelectPosition()
})
// defaultItemList.map((item) => {
// item.scrollViewId = `select__${item.index}`
// })
// this.selectedArr = defaultItemList
// this.currentTab = defaultItemList.length - 1
// this.$nextTick(() => {
// this.checkSelectPosition()
// })
} else {
this.initData(this.list, -1)
}
},
// item
getItemList(index, subIndex) {
let list = []
let arr = JSON.parse(JSON.stringify(this.list))
//
if (index === -1) {
list = this.removeChildren(arr)
} else {
//
let value = this.selectedArr[0].index
value = value === -1 ? subIndex : value
list = arr[value].children || []
if (index > 0) {
for (let i = 1; i < index + 1; i++) {
//
let val = index === i ? subIndex : this.selectedArr[i].index
list = list[val].children || []
if (list.length === 0) break
}
}
list = this.removeChildren(list)
}
return list
},
// item
getItemListWithValues(data, values) {
const defaultValues = JSON.parse(JSON.stringify(values))
if (!defaultValues || defaultValues.length === 0) return
// item
const itemIndex = data.findIndex((item) => {
return item.value === defaultValues[0]
})
if (itemIndex === -1) return
const item = data[itemIndex]
item.index = itemIndex
item.scrollViewId = `select__${itemIndex}`
item.list = this.removeChildren(JSON.parse(JSON.stringify(data)))
// 1
if (defaultValues.length === 1 || (!item.hasOwnProperty('children') || item.children.length === 0)) {
return this.removeChildren([item])
} else {
let selectItemList = []
const children = item.children
selectItemList.push(item)
//
defaultValues.splice(0, 1)
const childrenValue = this.getItemListWithValues(children, defaultValues)
selectItemList = selectItemList.concat(childrenValue)
return this.removeChildren(selectItemList)
}
},
//
removeChildren(data) {
let list = data.map((item) => {
if (item.hasOwnProperty('children')) {
delete item['children']
}
return item
})
return list
},
//
addSubData(data, index) {
//
if (!data || data.length === 0) {
if (index == -1) return
//
let arr = this.selectedArr
//
if (index < arr.length - 1) {
let newArr = arr.slice(0, index + 1)
this.selectedArr = newArr
}
let result = JSON.parse(JSON.stringify(this.selectedArr))
let lastItem = result[result.length - 1] || {}
let text = ''
result.map(item => {
text += item.text
delete item['list']
delete item['scrollViewId']
return item
})
this.$emit('complete', {
result: result,
value: lastItem.value,
text: text,
subText: lastItem.subText,
src: lastItem.src
})
} else {
//
let item = [{
text: this.text,
subText: '',
value: '',
src: '',
index: -1,
scrollViewId: 'select__0',
list: data
}]
//
if (index === -1) {
this.selectedArr = item
} else {
//
let retainArr = this.selectedArr.slice(0, index + 1)
this.selectedArr = retainArr.concat(item)
}
this.$nextTick(() => {
this.currentTab = this.selectedArr.length - 1
})
}
},
//
checkSelectPosition() {
let item = this.selectedArr[this.currentTab]
item.scrollViewId = 'select__0'
this.$nextTick(() => {
setTimeout(() => {
//
let val = item.index < 2 ? 0 : Number(item.index - 2)
item.scrollViewId = `select__${val}`
}, 10)
})
//
if (this.currentTab > 1) {
this.scrollViewId = `select__${this.currentTab - 1}`
} else {
this.scrollViewId = `select__0`
}
}
}
}
</script>
<style lang="scss" scoped>
.tn-cascade-selection {
width: 100%;
}
.selection {
&__scroll-view {
background-color: #FFFFFF;
}
&__header {
width: 100%;
display: flex;
align-items: center;
position: relative;
&__item {
max-width: 240rpx;
padding: 15rpx 30rpx;
flex-shrink: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
position: relative;
}
&__line {
width: 60rpx;
height: 6rpx;
border-radius: 4rpx;
position: absolute;
bottom: 0;
right: 0;
left: 50%;
transform: translateX(-50%);
}
}
&__list {
background-color: #FFFFFF;
&__item {
&--first {
width: 100%;
height: 20rpx;
}
&__cell {
width: 100%;
display: flex;
align-items: center;
padding: 20rpx 30rpx;
}
&__icon {
margin-right: 12rpx;
font-size: 24rpx;
}
&__image {
width: 40rpx;
height: 40rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
&__title {
word-break: break-all;
color: #333333;
font-size: 28rpx;
&--sub {
margin-left: 20rpx;
word-break: break-all;
color: $tn-font-sub-color;
font-size: 24rpx;
}
}
}
}
}
</style>

View File

@ -0,0 +1,134 @@
<template>
<view class="tn-checkbox-group-class tn-checkbox-group">
<slot></slot>
</view>
</template>
<script>
import Emitter from '../../libs/utils/emitter.js'
export default {
mixins: [ Emitter ],
name: 'tn-checkbox-group',
props: {
value: {
type: Array,
default() {
return []
}
},
// checkbox
max: {
type: Number,
default: 999
},
//
name: {
type: String,
default: ''
},
//
disabled: {
type: Boolean,
default: false
},
//
disabledLabel: {
type: Boolean,
default: false
},
// square circle
shape: {
type: String,
default: 'square'
},
//
activeColor: {
type: String,
default: '#01BEFF'
},
//
size: {
type: Number,
default: 34
},
// checkbox
width: {
type: String,
default: 'auto'
},
//
wrap: {
type: Boolean,
default: false
},
//
iconSize: {
type: Number,
default: 20
}
},
computed: {
// computedtn-checkbox
// parentDatawatch(tn-checkbox-group)
//
parentData() {
return [this.value, this.disabled, this.disabledLabel, this.shape, this.activeColor, this.size, this.width, this.wrap, this.iconSize]
}
},
data() {
return {
}
},
watch: {
//
parentData() {
if (this.children.length) {
this.children.map(child => {
// (tn-checkbox)updateParentData
typeof(child.updateParentData) === 'function' && child.updateParentData()
})
}
}
},
created() {
this.children = []
},
methods: {
initValue(values) {
this.$emit('input', values)
},
//
emitEvent() {
let values = []
this.children.map(child => {
if (child.checkValue) values.push(child.name)
})
this.$emit('change', values)
this.$emit('input', values)
// checkbox
//
setTimeout(() => {
// tn-form-item
this.dispatch('tn-form-item', 'on-form-change', values)
}, 60)
}
}
}
</script>
<style lang="scss" scoped>
.tn-checkbox-group {
/* #ifndef MP || APP-NVUE */
display: inline-flex;
flex-wrap: wrap;
/* #endif */
&::after {
content: " ";
display: table;
clear: both;
}
}
</style>

View File

@ -0,0 +1,328 @@
<template>
<view class="tn-checkbox-class tn-checkbox" :style="[checkboxStyle]">
<view
class="tn-checkbox__icon-wrap"
:class="[iconClass]"
:style="[iconStyle]"
@tap="toggle"
>
<view class="tn-checkbox__icon-wrap__icon" :class="[`tn-icon-${iconName}`]"></view>
</view>
<view
class="tn-checkbox__label"
:class="[labelClass]"
:style="{
fontSize: labelSize ? labelSize + 'rpx' : ''
}"
@tap="onClickLabel"
>
<slot></slot>
</view>
</view>
</template>
<script>
export default {
name: 'tn-checkbox',
props: {
// checkbox
name: {
type: [String, Number],
default: ''
},
//
value: {
type: Boolean,
default: false
},
//
disabled: {
type: Boolean,
default: false
},
//
disabledLabel: {
type: Boolean,
default: false
},
// square circle
shape: {
type: String,
default: ''
},
//
activeColor: {
type: String,
default: ''
},
//
size: {
type: Number,
default: 0
},
//
iconName: {
type: String,
default: 'success'
},
//
iconSize: {
type: Number,
default: 0
},
// label
labelSize: {
type: Number,
default: 0
}
},
computed: {
//
isDisabled() {
return this.disabled ? this.disabled : (this.parent ? this.parentData.disabled : false)
},
// label
isDisabledLabel() {
return this.disabledLabel ? this.disabledLabel : (this.parent ? this.parentData.disabledLabel : false)
},
//
checkboxSize() {
return this.size ? this.size : (this.parent ? this.parentData.size : 34)
},
//
elAvtiveColor() {
return this.activeColor ? this.activeColor : (this.parent ? this.parentData.activeColor : '#01BEFF')
},
//
elShape() {
return this.shape ? this.shape : (this.parent ? this.parentData.shape : 'square')
},
iconClass() {
let clazz = ''
clazz += (' tn-checkbox__icon-wrap--' + this.elShape)
if (this.checkValue) clazz += ' tn-checkbox__icon-wrap--checked'
if (this.isDisabled) clazz += ' tn-checkbox__icon-wrap--disabled'
if (this.value && this.isDisabled) clazz += ' tn-checkbox__icon-wrap--disabled--checked'
return clazz
},
iconStyle() {
let style = {}
//
if (this.elAvtiveColor && this.checkValue && !this.isDisabled) {
style.borderColor = this.elAvtiveColor
style.backgroundColor = this.elAvtiveColor
}
// checkbox
style.color = this.checkValue ? '#FFFFFF' : 'transparent'
style.width = this.checkboxSize + 'rpx'
style.height = style.width
style.fontSize = (this.iconSize ? this.iconSize : (this.parent ? this.parentData.iconSize : 20)) + 'rpx'
return style
},
checkboxStyle() {
let style = {}
if (this.parent && this.parentData.width) {
// #ifdef MP
// 使float
style.float = 'left';
// #endif
// #ifndef MP
// H5APP使flex
style.flex = `0 0 ${this.parentData.width}`;
// #endif
}
if(this.parent && this.parentData.wrap) {
style.width = '100%';
// #ifndef MP
// H5APP使flex100%
style.flex = '0 0 100%';
// #endif
}
return style
},
labelClass() {
let clazz = ''
if (this.isDisabled) {
clazz += ' tn-checkbox__label--disabled'
}
return clazz
}
},
data() {
return {
// checkboxvalue
checkValue: false,
parentData: {
value: null,
max: null,
disabled: null,
disabledLabel: null,
shape: null,
activeColor: null,
size: null,
width: null,
wrap: null,
iconSize: null
}
}
},
watch: {
value(val) {
this.checkValue = val
}
},
created() {
// provide/inject使created
// this.parent = this.$tn.$parent.call(this, 'tn-checkbox-group')
// // u-checkbox-groupthischildren
// this.parent && this.parent.children.push(this)
// // value
// this.parent && this.parent.emitEvent()
this.updateParentData()
this.parent && this.parent.children.push(this)
},
methods: {
updateCheckValue() {
// checkbox
this.checkValue = (this.parent && this.parentData.value.includes(this.name)) || this.value === true
if (this.parent) {
if (this.value && !this.parentData.value.includes(this.name)) {
this.parentData.value.push(this.name)
this.parent.initValue(this.parentData.value)
}
}
},
updateParentData() {
this.getParentData('tn-checkbox-group')
this.updateCheckValue()
},
onClickLabel() {
if (!this.isDisabled && !this.isDisabledLabel) {
this.setValue()
}
},
toggle() {
if (!this.isDisabled) {
this.setValue()
}
},
emitEvent() {
this.$emit('change', {
name: this.name,
value: !this.checkValue
})
if (this.parent) {
this.checkValue = !this.checkValue
// tn-checkbox-group
// this.$emit('input')
setTimeout(() => {
if(this.parent.emitEvent) this.parent.emitEvent();
}, 80)
}
},
// inputv-modal
setValue() {
//
if (this.parent) {
//
if (this.checkValue === true) {
this.emitEvent()
// this.$emit('input', !this.checkValue)
} else {
//
if (this.parentData.value.length >= this.parentData.max) {
return this.$tn.message.toast(`最多可选${this.parent.max}`)
}
// max
this.emitEvent();
// this.$emit('input', !this.checkValue);
}
} else {
//
this.emitEvent()
this.$emit('input', !this.checkValue)
}
}
}
}
</script>
<style lang="scss" scoped>
.tn-checkbox {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
overflow: hidden;
user-select: none;
line-height: 1.8;
&__icon-wrap {
color: $tn-font-color;
flex: none;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 42rpx;
height: 42rpx;
color: transparent;
text-align: center;
transition-property: color, border-color, background-color;
border: 1px solid $tn-font-sub-color;
transition-duration: 0.2s;
/* #ifdef MP-TOUTIAO */
// 0
&__icon {
line-height: 0;
}
/* #endif */
&--circle {
border-radius: 100%;
}
&--square {
border-radius: 6rpx;
}
&--checked {
color: #FFFFFF;
background-color: $tn-main-color;
border-color: $tn-main-color;
}
&--disabled {
background-color: $tn-font-holder-color;
border-color: $tn-font-sub-color;
}
&--disabled--checked {
color: $tn-font-sub-color !important;
}
}
&__label {
word-wrap: break-word;
margin-left: 10rpx;
margin-right: 24rpx;
color: $tn-font-color;
font-size: 30rpx;
&--disabled {
color: $tn-font-sub-color;
}
}
}
</style>

View File

@ -0,0 +1,223 @@
<template>
<view
class="tn-circle-progress-class tn-circle-progress"
:style="{
width: widthPx + 'px',
height: widthPx + 'px'
}"
>
<!-- 支付宝小程序不支持canvas-id属性必须用id属性 -->
<!-- 默认圆环 -->
<canvas
class="tn-circle-progress__canvas-bg"
:canvas-id="elBgId"
:id="elBgId"
:style="{
width: widthPx + 'px',
height: widthPx + 'px'
}"
></canvas>
<!-- 进度圆环 -->
<canvas
class="tn-circle-progress__canvas"
:canvas-id="elId"
:id="elId"
:style="{
width: widthPx + 'px',
height: widthPx + 'px'
}"
></canvas>
<view class="tn-circle-progress__content">
<slot v-if="$slots.default || $slots.$default"></slot>
<view v-else-if="showPercent" class="tn-circle-progress__content__percent">{{ percent + '%' }}</view>
</view>
</view>
</template>
<script>
export default {
name: 'tn-circle-progress',
props: {
//
percent: {
type: Number,
default: 0,
validator: val => {
return val >= 0 && val <= 100
}
},
// 线
borderWidth: {
type: Number,
default: 14
},
//
width: {
type: Number,
default: 200
},
//
striped: {
type: Boolean,
default: false
},
//
stripedActive: {
type: Boolean,
default: true
},
//
activeColor: {
type: String,
default: '#01BEFF'
},
//
inactiveColor: {
type: String,
default: '#f0f0f0'
},
//
showPercent: {
type: Boolean,
default: false
},
// ms
duration: {
type: Number,
default: 1500
}
},
data() {
return {
// 使this.$tn.uuid()id
// #ifdef MP-WEIXIN
elBgId: 'tCircleProgressBgId',
elId: 'tCircleProgressElId',
// #endif
// #ifndef MP-WEIXIN
elBgId: this.$tn.uuid(),
elId: this.$tn.uuid(),
// #endif
//
progressContext: null,
// px
widthPx: uni.upx2px(this.width || 200),
// px
borderWidthPx: uni.upx2px(this.borderWidth || 14),
// canvas-90
startAngle: -90 * Math.PI / 180,
//
newPercent: 0,
oldPercent: 0
}
},
watch: {
percent(newVal, oldVal = 0) {
if (newVal > 100) newVal = 100
if (oldVal < 0) oldVal = 0
this.newPercent = newVal
this.oldPercent = oldVal
setTimeout(() => {
//
//
this.drawCircleByProgress(oldVal)
}, 50)
}
},
created() {
// 使
this.newPercent = this.percent;
this.oldPercent = 0;
},
mounted() {
setTimeout(() => {
this.drawProgressBg()
this.drawCircleByProgress(this.oldPercent)
}, 50)
},
methods: {
//
drawProgressBg() {
let ctx = uni.createCanvasContext(this.elBgId, this)
// 线
ctx.setLineWidth(this.borderWidthPx)
//
ctx.setStrokeStyle(this.inactiveColor)
ctx.beginPath()
let radius = this.widthPx / 2
ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 360 * Math.PI / 180, false)
ctx.stroke()
ctx.draw()
},
//
drawCircleByProgress(progress) {
// 使
let ctx = this.progressContext
if (!ctx) {
ctx =uni.createCanvasContext(this.elId, this)
this.progressContext = ctx
}
ctx.setLineCap('round')
// 线
ctx.setLineWidth(this.borderWidthPx)
ctx.setStrokeStyle(this.activeColor)
// 100
let preSecondTime = Math.floor(this.duration / 100)
// 2π100
let endAngle = ((360 * Math.PI / 180) / 100) * progress + this.startAngle
let radius = this.widthPx / 2
ctx.beginPath()
ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false)
ctx.stroke()
ctx.draw()
//
if (this.newPercent > this.oldPercent) {
//
progress++
//
if (progress > this.newPercent) return
} else {
progress--
if (progress < this.newPercent) return
}
setTimeout(() => {
// time
this.drawCircleByProgress(progress)
}, preSecondTime)
}
}
}
</script>
<style lang="scss" scoped>
.tn-circle-progress {
position: relative;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
justify-content: center;
background-color: transparent;
&__canvas {
position: absolute;
&-bg {
position: absolute;
}
}
&__content {
display: flex;
align-items: center;
justify-content: center;
&__percent {
font-size: 28rpx;
}
}
}
</style>

View File

@ -0,0 +1,236 @@
<template>
<view class="tn-collapse-item-class tn-collapse-item" :style="[itemStyle]">
<!-- 头部 -->
<view
class="tn-collapse-item__head"
:style="[headStyle]"
:hover-stay-time="200"
:hover-class="hoverClass"
@tap.stop="headClick"
>
<block v-if="!$slots['title-all'] || !$slots['$title-all']">
<view
v-if="!$slots.title || !$slots.$title"
class="tn-collapse-item__head__title tn-text-ellipsis"
:style="[
{ textAlign: align ? align : 'left'},
isShow && activeStyle && !arrow ? activeStyle : ''
]"
>{{ title }}</view>
<view v-else>
<slot name="title"></slot>
</view>
<view class="tn-collapse-item__head__icon__wrap">
<view
v-if="arrow"
class="tn-icon-down tn-collapse-item__head__icon__arrow"
:class="{'tn-collapse-item__head__icon__arrow--active': isShow}"
:style="[arrowIconStyle]"
></view>
</view>
</block>
<view v-else>
<slot name="title-all"></slot>
</view>
</view>
<!-- 内容 -->
<view
class="tn-collapse-item__body"
:style="[{
height: isShow ? height + 'px' : '0'
}]"
>
<view class="tn-collapse-item__body__content" :id="elId" :style="[bodyStyle]">
<slot></slot>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'tn-collapse-item',
props: {
//
open: {
type: Boolean,
default: false
},
//
name: {
type: String,
default: ''
},
//
title: {
type: String,
default: ''
},
//
align: {
type: String,
default: 'left'
},
//
disabled: {
type: Boolean,
default: false
},
//
activeStyle: {
type: Object,
default() {
return {}
}
},
//
index: {
type: [Number, String],
default: ''
}
},
computed: {
arrowIconStyle() {
let style = {}
if (this.arrowColor) {
style.color = this.arrowColor
}
return style
}
},
data() {
return {
isShow: false,
elId: this.$tn.uuid(),
// body
height: 0,
//
headStyle: {},
//
bodyStyle: {},
// item
itemStyle: {},
//
arrow: true,
//
arrowColor: '',
//
hoverClass: ''
}
},
watch: {
open(value) {
this.isShow = value
}
},
created() {
this.parent = false
this.isShow = this.open
},
mounted() {
this.init()
},
methods: {
//
init() {
this.parent = this.$tn.$parent.call(this, 'tn-collapse')
if (this.parent) {
this.nameSync = this.name ? this.name : this.parent.childrens.length
//
!this.parent.childrens.includes(this) && this.parent.childrens.push(this)
this.headStyle = this.parent.headStyle
this.bodyStyle = this.parent.bodyStyle
this.itemStyle = this.parent.itemStyle
this.arrow = this.parent.arrow
this.arrowColor = this.parent.arrowColor
this.hoverClass = this.parent.hoverClass
}
this.$nextTick(() => {
this.queryRect()
})
},
//
headClick() {
if (this.disabled) return
if (this.parent && this.parent.accordion) {
this.parent.childrens.map(child => {
// item
if (this !== child) {
child.isShow = false
}
})
}
this.isShow = !this.isShow
//
this.$emit('change', {
index: this.index,
show: this.isShow
})
// change
if (this.isShow) this.parent && this.parent.onChange()
this.$forceUpdate()
},
//
queryRect() {
this._tGetRect('#'+this.elId).then(res => {
this.height = res.height
})
}
}
}
</script>
<style lang="scss" scoped>
.tn-collapse-item {
&__head {
position: relative;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
color: $tn-font-color;
font-size: 30rpx;
line-height: 1;
padding: 24rpx 0;
padding-left: 24rpx;
text-align: left;
background-color: #FFFFFF;
&__title {
flex: 1;
overflow: hidden;
}
&__icon {
&__arrow {
transition: all 0.3s;
margin-right: 20rpx;
margin-left: 14rpx;
font-size: inherit;
&--active {
transform: rotate(180deg);
transform-origin: center center;
}
}
}
}
&__body {
transition: all 0.3s;
overflow: hidden;
&__content {
overflow: hidden;
font-size: 28rpx;
color: $tn-font-color;
text-align: left;
background-color: #FFFFFF;
padding-left: 24rpx;
}
}
}
</style>

View File

@ -0,0 +1,98 @@
<template>
<view class="tn-collapse-class tn-collapse">
<slot></slot>
</view>
</template>
<script>
export default {
name: 'tn-collapse',
props: {
//
accordion: {
type: Boolean,
default: true
},
//
headStyle: {
type: Object,
default() {
return {}
}
},
//
bodyStyle: {
type: Object,
default() {
return {}
}
},
// item
itemStyle: {
type: Object,
default() {
return {}
}
},
//
arrow: {
type: Boolean,
default: true
},
//
arrowColor: {
type: String,
default: '#AAAAAA'
},
//
hoverClass: {
type: String,
default: 'tn-hover'
}
},
computed: {
parentData() {
return [this.headStyle, this.bodyStyle, this.itemStyle, this.arrow, this.arrowColor, this.hoverClass]
}
},
data() {
return {
}
},
watch: {
parentData() {
//
if (this.childrens.length > 0) {
this.init()
}
}
},
created() {
this.childrens = []
},
methods: {
//
init() {
this.childrens.forEach((child, index) => {
child.init()
})
},
// collapseItemcollapseItem
onChange() {
let activeItem = []
this.childrens.forEach((child, index) => {
if (child.isShow) {
activeItem.push(child.nameSync)
}
})
// activeItem1
if (this.accordion) activeItem = activeItem.join(',')
this.$emit('change', activeItem)
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,318 @@
<template>
<text
class="tn-color-icon-class tn-color-icon"
:class="[
'tn-color-icon-' + name
]"
:style="{
fontSize: size + unit,
margin: margin
}"
@tap="handleClick"
></text>
</template>
<script>
export default {
name: 'tn-color-icon',
props: {
//
index: {
type: [Number, String],
default: '0'
},
//
name: {
type: String,
default: ''
},
//
size: {
type: Number,
default:32
},
//
unit: {
type: String,
default: 'px'
},
//
margin: {
type: String,
default: '0'
}
},
computed: {
},
data() {
return {
}
},
methods: {
//
handleClick() {
this.$emit("click", {
index: Number(this.index)
})
this.$emit("tap", {
index: Number(this.index)
})
}
}
}
</script>
<style scoped>
@charset "UTF-8";
@font-face {
font-family: "tuniaoColorFont"; /* Project id 2445412 */
/* Color fonts */
src: url('iconfont.woff2?t=1632654518618') format('woff2');
}
.tn-color-icon {
font-family: "tuniaoColorFont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
text-decoration: none;
}
.tn-color-icon-logo-github:before {
content: "\e601";
}
.tn-color-icon-logo-qq:before {
content: "\e602";
}
.tn-color-icon-logo-weixin:before {
content: "\e603";
}
.tn-color-icon-logo-alipay:before {
content: "\e604";
}
.tn-color-icon-logo-weibo:before {
content: "\e605";
}
.tn-color-icon-logo-dingtalk:before {
content: "\e606";
}
.tn-color-icon-safe:before {
content: "\e607";
}
.tn-color-icon-wifi:before {
content: "\e608";
}
.tn-color-icon-help:before {
content: "\e609";
}
.tn-color-icon-tag:before {
content: "\e60a";
}
.tn-color-icon-play:before {
content: "\e60b";
}
.tn-color-icon-stopwatch:before {
content: "\e60c";
}
.tn-color-icon-home:before {
content: "\e60d";
}
.tn-color-icon-map:before {
content: "\e60e";
}
.tn-color-icon-book:before {
content: "\e60f";
}
.tn-color-icon-qrcode:before {
content: "\e610";
}
.tn-color-icon-discover:before {
content: "\e611";
}
.tn-color-icon-visitor:before {
content: "\e612";
}
.tn-color-icon-menu:before {
content: "\e613";
}
.tn-color-icon-renew:before {
content: "\e614";
}
.tn-color-icon-business:before {
content: "\e615";
}
.tn-color-icon-telephone:before {
content: "\e616";
}
.tn-color-icon-medicine:before {
content: "\e617";
}
.tn-color-icon-chicken:before {
content: "\e618";
}
.tn-color-icon-clock:before {
content: "\e619";
}
.tn-color-icon-download:before {
content: "\e61a";
}
.tn-color-icon-lamp:before {
content: "\e61b";
}
.tn-color-icon-hourglass:before {
content: "\e61c";
}
.tn-color-icon-calendar:before {
content: "\e61d";
}
.tn-color-icon-bluetooth:before {
content: "\e61e";
}
.tn-color-icon-fish:before {
content: "\e61f";
}
.tn-color-icon-seal:before {
content: "\e620";
}
.tn-color-icon-remind:before {
content: "\e621";
}
.tn-color-icon-music:before {
content: "\e622";
}
.tn-color-icon-email:before {
content: "\e623";
}
.tn-color-icon-medal:before {
content: "\e624";
}
.tn-color-icon-image:before {
content: "\e625";
}
.tn-color-icon-network:before {
content: "\e626";
}
.tn-color-icon-wallet:before {
content: "\e627";
}
.tn-color-icon-program:before {
content: "\e628";
}
.tn-color-icon-shrimp:before {
content: "\e629";
}
.tn-color-icon-collect:before {
content: "\e62a";
}
.tn-color-icon-screw:before {
content: "\e62b";
}
.tn-color-icon-set:before {
content: "\e62c";
}
.tn-color-icon-userfavorite:before {
content: "\e62d";
}
.tn-color-icon-useradd:before {
content: "\e62e";
}
.tn-color-icon-honor:before {
content: "\e62f";
}
.tn-color-icon-shop:before {
content: "\e630";
}
.tn-color-icon-usercard:before {
content: "\e631";
}
.tn-color-icon-school:before {
content: "\e632";
}
.tn-color-icon-user:before {
content: "\e633";
}
.tn-color-icon-internet:before {
content: "\e634";
}
.tn-color-icon-time:before {
content: "\e635";
}
.tn-color-icon-topic:before {
content: "\e636";
}
.tn-color-icon-phone:before {
content: "\e637";
}
.tn-color-icon-usertable:before {
content: "\e638";
}
.tn-color-icon-userset:before {
content: "\e639";
}
.tn-color-icon-game:before {
content: "\e63a";
}
</style>

View File

@ -0,0 +1,251 @@
<template>
<view
class="tn-column-notice-class tn-column-notice"
:class="[backgroundColorClass]"
:style="[noticeStyle]"
>
<!-- 左图标 -->
<view class="tn-column-notice__icon">
<view
v-if="leftIcon"
class="tn-column-notice__icon--left"
:class="[`tn-icon-${leftIconName}`,fontColorClass]"
:style="[fontStyle('leftIcon')]"
@tap="clickLeftIcon"></view>
</view>
<!-- 滚动显示内容 -->
<swiper class="tn-column-notice__swiper" :style="[swiperStyle]" :vertical="vertical" circular :autoplay="autoplay && playStatus === 'play'" :interval="duration" @change="change">
<swiper-item v-for="(item, index) in list" :key="index" class="tn-column-notice__swiper--item">
<view
class="tn-column-notice__swiper--content tn-text-ellipsis"
:class="[fontColorClass]"
:style="[fontStyle()]"
@tap="click(index)"
>{{ item }}</view>
</swiper-item>
</swiper>
<!-- 右图标 -->
<view class="tn-column-notice__icon">
<view
v-if="rightIcon"
class="tn-column-notice__icon--right"
:class="[`tn-icon-${rightIconName}`,fontColorClass]"
:style="[fontStyle('rightIcon')]"
@tap="clickRightIcon"></view>
<view
v-if="closeBtn"
class="tn-column-notice__icon--right"
:class="[`tn-icon-close`,fontColorClass]"
:style="[fontStyle('close')]"
@tap="close"></view>
</view>
</view>
</template>
<script>
import componentsColorMixin from '../../libs/mixin/components_color.js'
export default {
name: 'tn-column-notice',
mixins: [componentsColorMixin],
props: {
//
list: {
type: Array,
default() {
return []
}
},
//
show: {
type: Boolean,
default: true
},
//
// play -> paused ->
playStatus: {
type: String,
default: 'play'
},
//
// horizontal -> vertical ->
mode: {
type: String,
default: 'horizontal'
},
//
leftIcon: {
type: Boolean,
default: true
},
//
leftIconName: {
type: String,
default: 'sound'
},
//
leftIconSize: {
type: Number,
default: 34
},
//
rightIcon: {
type: Boolean,
default: false
},
//
rightIconName: {
type: String,
default: 'right'
},
//
rightIconSize: {
type: Number,
default: 26
},
//
closeBtn: {
type: Boolean,
default: false
},
//
radius: {
type: Number,
default: 0
},
//
padding: {
type: String,
default: '18rpx 24rpx'
},
//
autoplay: {
type: Boolean,
default: true
},
//
duration: {
type: Number,
default: 2000
}
},
computed: {
fontStyle() {
return (type) => {
let style = {}
style.color = this.fontColorStyle ? this.fontColorStyle : ''
style.fontSize = this.fontSizeStyle ? this.fontSizeStyle : ''
if (type === 'leftIcon' && this.leftIconSize) {
style.fontSize = this.leftIconSize + 'rpx'
}
if (type === 'rightIcon' && this.rightIconSize) {
style.fontSize = this.rightIconSize + 'rpx'
}
if (type === 'close') {
style.fontSize = '24rpx'
}
return style
}
},
noticeStyle() {
let style = {}
style.backgroundColor = this.backgroundColorStyle ? this.backgroundColorStyle : 'transparent'
if (this.padding) style.padding = this.padding
return style
},
swiperStyle() {
let style = {}
style.height = this.fontSize ? this.fontSize + 6 + this.fontUnit : '32rpx'
style.lineHeight = style.height
return style
},
//
vertical() {
if (this.mode === 'horizontal') return false
else return true
}
},
data() {
return {
}
},
watch: {
},
methods: {
//
click(index) {
this.$emit('click', index)
},
//
close() {
this.$emit('close')
},
//
clickLeftIcon() {
this.$emit('clickLeft')
},
//
clickRightIcon() {
this.$emit('clickRight')
},
//
change(event) {
let index = event.detail.current
if (index === this.list.length - 1) {
this.$emit('end')
}
}
}
}
</script>
<style lang="scss" scoped>
.tn-column-notice {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-wrap: nowrap;
overflow: hidden;
&__swiper {
height: auto;
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
margin-left: 12rpx;
&--item {
display: flex;
flex-direction: row;
align-items: center;
overflow: hidden;
}
&--content {
overflow: hidden;
}
}
&__icon {
&--left {
display: inline-flex;
align-items: center;
}
&--right {
margin-left: 12rpx;
display: inline-flex;
align-items: center;
}
}
}
</style>

View File

@ -0,0 +1,314 @@
<template>
<view class="tn-countdown-class tn-countdown">
<view
v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))"
class="tn-countdown__item"
:class="[backgroundColorClass]"
:style="[itemStyle]"
>
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
{{ d }}
</view>
</view>
<view
v-if="showHours && (hideZeroDay || (!hideZeroDay && d != '00'))"
class="tn-countdown__separator"
:style="{
fontSize: separatorSize + 'rpx',
color: separatorColor,
paddingBottom: separator === 'en' ? '4rpx' : 0
}"
>
{{ separator === 'en' ? ':' : '天'}}
</view>
<view
v-if="showHours"
class="tn-countdown__item"
:class="[backgroundColorClass]"
:style="[itemStyle]"
>
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
{{ h }}
</view>
</view>
<view
v-if="showMinutes"
class="tn-countdown__separator"
:style="{
fontSize: separatorSize + 'rpx',
color: separatorColor,
paddingBottom: separator === 'en' ? '4rpx' : 0
}"
>
{{ separator === 'en' ? ':' : '时'}}
</view>
<view
v-if="showMinutes"
class="tn-countdown__item"
:class="[backgroundColorClass]"
:style="[itemStyle]"
>
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
{{ m }}
</view>
</view>
<view
v-if="showSeconds"
class="tn-countdown__separator"
:style="{
fontSize: separatorSize + 'rpx',
color: separatorColor,
paddingBottom: separator === 'en' ? '4rpx' : 0
}"
>
{{ separator === 'en' ? ':' : '分'}}
</view>
<view
v-if="showSeconds"
class="tn-countdown__item"
:class="[backgroundColorClass]"
:style="[itemStyle]"
>
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
{{ s }}
</view>
</view>
<view
v-if="showSeconds && separator === 'cn'"
class="tn-countdown__separator"
:style="{
fontSize: separatorSize + 'rpx',
color: separatorColor,
paddingBottom: separator === 'en' ? '4rpx' : 0
}"
>
</view>
</view>
</template>
<script>
import componentsColorMixin from '../../libs/mixin/components_color.js'
export default {
name: 'tn-count-down',
mixins: [componentsColorMixin],
props: {
//
timestamp: {
type: Number,
default: 0
},
//
autoplay: {
type: Boolean,
default: true
},
//
height: {
type: [String, Number],
default: 'auto'
},
//
// en -> 使 cn -> 使
separator: {
type: String,
default: 'en'
},
//
separatorSize: {
type: Number,
default: 30
},
//
separatorColor: {
type: String,
default: '#080808'
},
//
showBorder: {
type: Boolean,
default: false
},
//
borderColor: {
type: String,
default: '#080808'
},
//
showSeconds: {
type: Boolean,
default: true
},
//
showMinutes: {
type: Boolean,
default: true
},
//
showHours: {
type: Boolean,
default: true
},
//
showDays: {
type: Boolean,
default: true
},
// 0
hideZeroDay: {
type: Boolean,
default: false
}
},
computed: {
// item
itemStyle() {
let style = {}
if (this.height) {
style.height = this.$tn.string.getLengthUnitValue(this.height)
style.width = style.height
}
if (this.showBorder) {
style.borderStyle = 'solid'
style.borderColor = this.borderColor
style.borderWidth = '1rpx'
}
style.backgroundColor = this.backgroundColorStyle || '#FFFFFF'
return style
},
//
letterStyle() {
let style = {}
style.fontSize = this.fontSizeStyle || '30rpx'
style.color = this.fontColorStyle || '#080808'
return style
}
},
data() {
return {
d: '00',
h: '00',
m: '00',
s: '00',
//
timer: null,
//
seconds: 0
}
},
watch: {
//
timestamp(value) {
this.clearTimer()
this.start()
}
},
mounted() {
//
this.autoplay && this.timestamp && this.start()
},
beforeDestroy() {
this.clearTimer()
},
methods: {
//
start() {
//
this.clearTimer()
if (this.timestamp <= 0) return
this.seconds = Number(this.timestamp)
this.formatTime(this.seconds)
this.timer = setInterval(() => {
this.seconds--
// change
this.$emit('change', this.seconds)
if (this.seconds < 0) {
return this.end()
}
this.formatTime(this.seconds)
}, 1000)
},
//
formatTime(seconds) {
// 0
seconds <= 0 && this.end()
let [day, hour, minute, second] = [0, 0, 0, 0]
day = Math.floor(seconds / (60 * 60 * 24))
//
// hour使
hour = Math.floor(seconds / (60 * 60)) - (day * 24)
let showHour = null
if (this.showDays) {
showHour = hour
} else {
//
showHour = Math.floor(seconds / (60 * 60))
}
minute = Math.floor(seconds / 60) - (hour * 60) - (day * 24 * 60)
second = Math.floor(seconds) - (minute * 60) - (hour * 60 * 60) - (day * 24 * 60 * 60)
// 00
showHour = this.$tn.number.formatNumberAddZero(showHour)
minute = this.$tn.number.formatNumberAddZero(minute)
second = this.$tn.number.formatNumberAddZero(second)
day = this.$tn.number.formatNumberAddZero(day)
this.d = day
this.h = showHour
this.m = minute
this.s = second
},
//
end() {
this.clearTimer()
this.$emit('end')
},
//
clearTimer() {
if (this.timer !== null) {
clearInterval(this.timer)
this.timer = null
}
}
}
}
</script>
<style lang="scss" scoped>
.tn-countdown {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
&__item {
box-sizing: content-box;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 2rpx;
border-radius: 6rpx;
white-space: nowrap;
transform: translateZ(0);
&__time {
margin: 0;
padding: 0;
line-height: 1;
}
}
&__separator {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 0 5rpx;
line-height: 1;
}
}
</style>

View File

@ -0,0 +1,171 @@
<template>
<view class="tn-count-scroll-class tn-count-scroll">
<view
v-for="(item, index) in columns"
:key="index"
class="tn-count-scroll__box"
:style="{
width: $tn.string.getLengthUnitValue(width),
height: heightPxValue + 'px'
}"
>
<view
class="tn-count-scroll__column"
:style="{
transform: `translate3d(0, -${keys[index] * heightPxValue}px, 0)`,
transitionDuration: `${duration}s`
}"
>
<view
v-for="(value, value_index) in item"
:key="value_index"
class="tn-count-scroll__column__item"
:class="[fontColorClass]"
:style="{
height: heightPxValue + 'px',
lineHeight: heightPxValue + 'px',
fontSize: fontSizeStyle || '32rpx',
fontWeight: bold ? 'bold': 'normal',
color: fontColorStyle || '#080808'
}"
>
{{ value }}
</view>
</view>
</view>
</view>
</template>
<script>
import componentsColorMixin from '../../libs/mixin/components_color.js'
export default {
name: 'tn-count-scroll',
mixins: [componentsColorMixin],
props: {
value: {
type: Number,
default: 0
},
//
height: {
type: Number,
default: 32
},
//
width: {
type: [String, Number],
default: 'auto'
},
//
bold: {
type: Boolean,
default: false
},
//
duration: {
type: Number,
default: 1.2
},
//
decimalSeparator: {
type: String,
default: '.'
},
//
thousandthsSeparator: {
type: String,
default: ''
}
},
computed: {
heightPxValue() {
return uni.upx2px(this.height || 0)
}
},
data() {
return {
//
columns: [],
//
keys: []
}
},
watch: {
value(val) {
this.initColumn(val)
}
},
created() {
//
this.initColumn()
setTimeout(() => {
this.initColumn(this.value)
}, 20)
},
methods: {
//
initColumn(val) {
val = val + ''
let digit = val.length,
columnArray = [],
rows = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
for (let i = 0; i < digit; i++) {
if (val[i] === this.decimalSeparator || val[i] === this.thousandthsSeparator) {
columnArray.push(val[i])
} else {
columnArray.push(rows)
}
}
this.columns = columnArray
this.roll(val)
},
//
roll(value) {
let valueArray = value.toString().split(''),
lengths = this.columns.length,
indexs = [];
while (valueArray.length) {
let figure = valueArray.pop()
if (figure === this.decimalSeparator || figure === this.thousandthsSeparator) {
indexs.unshift(0)
} else {
indexs.unshift(Number(figure))
}
}
while(indexs.length < lengths) {
indexs.unshift(0)
}
this.keys = indexs
}
}
}
</script>
<style lang="scss" scoped>
.tn-count-scroll {
display: inline-flex;
align-items: center;
justify-content: space-between;
&__box {
overflow: hidden;
}
&__column {
transform: translate3d(0, 0, 0);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
transition-timing-function: cubic-bezier(0, 1, 0, 1);
&__item {
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>

View File

@ -0,0 +1,231 @@
<template>
<view
class="tn-count-num-class tn-count-num"
:class="[fontColorClass]"
:style="{
fontSize: fontSizeStyle || '50rpx',
fontWeight: bold ? 'bold' : 'normal',
color: fontColorStyle || '#080808'
}"
>
{{ displayValue }}
</view>
</template>
<script>
import componentsColorMixin from '../../libs/mixin/components_color.js'
export default {
name: 'tn-count-to',
mixins: [componentsColorMixin],
props: {
// 0
startVal: {
type: Number,
default: 0
},
//
endVal: {
type: Number,
default: 0,
required: true
},
//
autoplay: {
type: Boolean,
default: true
},
//
duration: {
type: Number,
default: 2000
},
// 使
useEasing: {
type: Boolean,
default: true
},
//
decimals: {
type: Number,
default: 0
},
//
decimalSeparator: {
type: String,
default: '.'
},
//
// (23,321.05",")
thousandthsSeparator: {
type: String,
default: ''
},
//
bold: {
type: Boolean,
default: false
}
},
computed: {
countDown() {
return this.startVal > this.endVal
}
},
data() {
return {
localStartVal: this.startVal,
localDuration: this.duration,
//
displayValue: this.formatNumber(this.startVal),
//
printValue: null,
//
paused: false,
//
startTime: null,
//
remainingTime: null,
//
timestamp: null,
//
lastTime: 0,
rAF: null
}
},
watch: {
startVal() {
this.autoplay && this.start()
},
endVal() {
this.autoplay && this.start()
}
},
mounted() {
this.autoplay && this.start()
},
methods: {
//
start() {
this.localStartVal = this.startVal
this.startTime = null
this.localDuration = this.duration
this.paused = false
this.rAF = this.requestAnimationFrame(this.count)
},
//
reStart() {
if (this.paused) {
this.resume()
this.paused = false
} else {
this.stop()
this.paused = true
}
},
//
stop() {
this.cancelAnimationFrame(this.rAF)
},
//
resume() {
this.startTime = null
this.localDuration = this.remainingTime
this.localStartVal = this.printValue
this.requestAnimationFrame(this.count)
},
//
reset() {
this.startTime = null
this.cnacelAnimationFrame(this.rAF)
this.displayValue = this.formatNumber(this.startVal)
},
//
destroyed() {
this.cancelAnimationFrame(this.rAF)
},
//
count(timestamp) {
if (!this.startTime) this.startTime = timestamp
this.timestamp = timestamp
const progress = timestamp - this.startTime
this.remainingTime = this.localDuration - progress
if (this.useEasing) {
if (this.countDown) {
this.printValue = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration)
} {
this.printValue = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration)
}
} else {
if (this.countDown) {
this.printValue = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration)
} else {
this.printValue = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration)
}
}
if (this.countDown) {
this.printValue = this.printValue < this.endVal ? this.endVal : this.printValue
} else {
this.printValue = this.printValue > this.endVal ? this.endVal : this.printValue
}
this.displayValue = this.formatNumber(this.printValue)
if (progress < this.localDuration) {
this.rAF = this.requestAnimationFrame(this.count)
} else {
this.$emit('end')
}
},
//
easingFn(t, b, c, d) {
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
},
//
requestAnimationFrame(cb) {
const currentTime = new Date().getTime()
// 使setTimteout60
const timeToCall = Math.max(0, 16 - (currentTime - this.lastTime))
const timerId = setTimeout(() => {
cb && cb(currentTime + timeToCall)
}, timeToCall)
this.lastTime = currentTime + timeToCall
return timerId
},
//
clearAnimationFrame(timerId) {
clearTimeout(timerId)
},
//
formatNumber(number) {
const reg = /(\d+)(\d{3})/
number = Number(number)
number = number.toFixed(Number(this.decimals))
number += ''
const numberArray = number.split('.')
let num1 = numberArray[0]
const num2 = numberArray.length > 1 ? this.decimalSeparator + numberArray[1] : ''
if (this.thousandthsSeparator && !this.isNumber(this.thousandthsSeparator)) {
while(reg.test(num1)) {
num1 = num1.replace(reg, '$1' + this.thousandthsSeparator + '$2')
}
}
return num1 + num2
},
//
isNumber(val) {
return !isNaN(parseFloat(val))
}
}
}
</script>
<style lang="scss" scoped>
.tn-count-num {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
text-align: center;
line-height: 1;
}
</style>

View File

@ -0,0 +1,332 @@
var cropper = {
// 画布x轴起点
cutX: 0,
// 画布y轴起点
cutY: 0,
// 触摸点信息(手指与图片中心点的相对位置)
touchRelactive: [{
x: 0,
y: 0
}],
// 双指触摸时斜边的长度
hypotenuseLength:0,
// 是否结束触摸
touchEndFlag: false,
// 画布宽高
canvasWidth: 0,
canvasHeight: 0,
// 图片宽高
imgWidth: 0,
imgHeight: 0,
// 图片缩放比例
scale: 1,
// 图片旋转角度
angle: 0,
// 图片上边距
imgTop: 0,
// 图片左边距
imgLeft: 0,
// 窗口宽高
windowWidth: 0,
windowHeight: 0,
init: true
}
function bool(str) {
return str === 'true' || str === true
}
function propChange(prop, oldProp, ownerInstance, instance) {
if (prop && prop !== 'null') {
var params = prop.split(',')
var type = +params[0]
var dataset = instance.getDataset()
if (cropper.init || type == 4) {
cropper.canvasWidth = +dataset.width
cropper.canvasHeight = +dataset.height
cropper.imgTop = +dataset.windowheight / 2
cropper.imgLeft = +dataset.windowwidth / 2
cropper.imgWidth = +dataset.imgwidth
cropper.imgHeight = +dataset.imgheight
cropper.windowHeight = +dataset.windowheight
cropper.windowWidth = +dataset.windowwidth
cropper.init = false
} else if (type == 2 || type == 3) {
cropper.imgWidth = +dataset.imgwidth
cropper.imgHeight = +dataset.imgheight
}
cropper.angle = +dataset.angle
if (type == 3) {
imgTransform(ownerInstance)
}
switch(type) {
case 1:
setCutCenter(ownerInstance)
// // 设置裁剪框大小
computeCutSize(ownerInstance)
// // 检查裁剪框是否在范围内
cutDetectionPosition(ownerInstance)
break
case 2:
setCutCenter(ownerInstance)
break
case 3:
imgMarginDetectionScale(ownerInstance)
break
case 4:
imageReset(ownerInstance)
break
case 5:
setCutCenter(ownerInstance)
break
default:
break
}
}
}
function touchStart(event, ownerInstance) {
var touch = event.touches || event.changedTouches
cropper.touchEndFlag = false
if (touch.length === 1) {
cropper.touchRelactive[0] = {
x: touch[0].pageX - cropper.imgLeft,
y: touch[0].pageY - cropper.imgTop
}
} else {
var width = Math.abs(touch[0].pageX - touch[1].pageX)
var height = Math.abs(touch[0].pageY - touch[1].pageY)
cropper.touchRelactive = [{
x: touch[0].pageX - cropper.imgLeft,
y: touch[0].pageY - cropper.imgTop
},{
x: touch[1].pageX - cropper.imgLeft,
y: touch[1].pageY - cropper.imgTop
}]
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2))
}
}
function touchMove(event, ownerInstance) {
var touch = event.touches || event.changedTouches
if (cropper.touchEndFlag) return
moveDuring(ownerInstance)
if (event.touches.length === 1) {
var left = touch[0].pageX - cropper.touchRelactive[0].x,
top = touch[0].pageY - cropper.touchRelactive[0].y;
cropper.imgLeft = left
cropper.imgTop = top
imgTransform(ownerInstance)
imgMarginDetectionPosition(ownerInstance)
} else {
var dataset = event.instance.getDataset()
var minScale = +dataset.minscale
var maxScale = +dataset.maxscale
var width = Math.abs(touch[0].pageX - touch[1].pageX),
height = Math.abs(touch[0].pageY - touch[1].pageY),
hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
scale = cropper.scale * (hypotenuse / cropper.hypotenuseLength),
current_deg = 0;
scale = scale <= minScale ? minScale : scale
scale = scale >= maxScale ? maxScale : scale
cropper.scale = scale
imgMarginDetectionScale(ownerInstance, true)
var touchRelative = [{
x: touch[0].pageX - cropper.imgLeft,
y: touch[0].pageY - cropper.imgTop
}, {
x: touch[1].pageX - cropper.imgLeft,
y: touch[1].pageY - cropper.imgTop
}]
cropper.touchRelactive = touchRelative
cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2))
// 更新视图
cropper.angle = cropper.angle + current_deg
imgTransform(ownerInstance)
}
}
function touchEnd(event, ownerInstance) {
cropper.touchEndFlag = true
moveStop(ownerInstance)
updateData(ownerInstance)
}
function moveDuring(ownerInstance) {
if (!ownerInstance) return
ownerInstance.callMethod('moveDuring')
}
function moveStop(ownerInstance) {
if (!ownerInstance) return
ownerInstance.callMethod('moveStop')
}
function setCutCenter(ownerInstance) {
var cutX = (cropper.windowWidth - cropper.canvasWidth) * 0.5
var cutY = (cropper.windowHeight - cropper.canvasHeight) * 0.5
cropper.imgTop = cropper.imgTop - cropper.cutY + cutY
cropper.cutY = cutY
cropper.imgLeft = cropper.imgLeft - cropper.cutX + cutX
cropper.cutX = cutX
cutDetectionPosition(ownerInstance)
imgTransform(ownerInstance)
updateData(ownerInstance)
}
// 检测剪裁框位置是否在允许的范围内(屏幕内)
function cutDetectionPosition(ownerInstance) {
var windowHeight = cropper.windowHeight,
windowWidth = cropper.windowWidth;
// 检测上边距是否在范围内
var cutDetectionPositionTop = function() {
if (cropper.cutY < 0) {
cropper.cutY = 0
}
if (cropper.cutY > windowHeight - cropper.canvasHeight) {
cropper.cutY = windowHeight - cropper.canvasHeight
}
}
// 检测左边距是否在范围内
var cutDetectionPositionLeft = function() {
if (cropper.cutX < 0) {
cropper.cutX = 0
}
if (cropper.cutX > windowWidth - cropper.canvasWidth) {
cropper.cutX = windowWidth - cropper.canvasWidth
}
}
// 裁剪框坐标处理如果只写一个参数则另一个默认为0都不写默认为居中
if (cropper.cutX === null && cropper.cutY === null) {
var cutX = (windowWidth - cropper.canvasWidth) * 0.5,
cutY = (windowHeight - cropper.canvasHeight) * 0.5;
cropper.cutX = cutX
cropper.cutY = cutY
} else if (cropper.cutX !== null && cropper.cutX !== null) {
cutDetectionPositionTop()
cutDetectionPositionLeft()
} else if (cropper.cutX !== null && cropper.cutY === null) {
cutDetectionPositionLeft()
cropper.cutY = (windowHeight - cropper.canvasHeight) / 2
} else if (cropper.cutX === null && cropper.cutY !== null) {
cutDetectionPositionTop()
cropper.cutX = (windowWidth - cropper.canvasWidth) / 2
}
}
// 图片边缘检测-缩放
function imgMarginDetectionScale(ownerInstance, delay) {
var scale = cropper.scale,
imgWidth = cropper.imgWidth,
imgHeight = cropper.imgHeight;
if ((cropper.angle / 90) % 2) {
imgWidth = cropper.imgHeight
imgHeight = cropper.imgWidth
}
if (imgWidth * scale < cropper.canvasWidth) {
scale = cropper.canvasWidth / imgWidth
}
if (imgHeight * scale < cropper.canvasHeight) {
scale = Math.max(scale, cropper.canvasHeight / imgHeight)
}
imgMarginDetectionPosition(ownerInstance, scale, delay)
}
// 图片边缘检测-位置
function imgMarginDetectionPosition(ownerInstance, scale, delay) {
var left = cropper.imgLeft,
top = cropper.imgTop,
imgWidth = cropper.imgWidth,
imgHeight = cropper.imgHeight;
scale = scale || cropper.scale
if ((cropper.angle / 90) % 2) {
imgWidth = cropper.imgHeight
imgHeight = cropper.imgWidth
}
left = cropper.cutX + (imgWidth * scale) / 2 >= left ? left : cropper.cutX + (imgWidth * scale) / 2
left = cropper.cutX + cropper.canvasWidth - (imgWidth * scale) / 2 <= left ? left : cropper.cutX + cropper.canvasWidth - (imgWidth * scale) / 2
top = cropper.cutY + (imgHeight * scale) / 2 >= top ? top : cropper.cutY + (imgHeight * scale) / 2
top = cropper.cutY + cropper.canvasHeight - (imgHeight * scale) / 2 <= top ? top : cropper.cutY + cropper.canvasHeight - (imgHeight * scale) / 2
cropper.imgLeft = left
cropper.imgTop = top
cropper.scale = scale
if (!delay || delay === 'null') {
imgTransform(ownerInstance)
}
}
// 改变截取值大小
function computeCutSize(ownerInstance) {
if (cropper.canvasWidth > cropper.windowWidth) {
cropper.canvasWidth = cropper.windowWidth
} else if (cropper.canvasWidth + cropper.cutX > cropper.windowWidth) {
cropper.cutX = cropper.windowWidth - cropper.cutX
}
if (cropper.canvasHeight > cropper.windowHeight) {
cropper.canvasHeight = cropper.windowHeight
} else if (cropper.canvasHeight + cropper.cutY > cropper.windowHeight) {
cropper.cutY = cropper.windowHeight - cropper.cutY
}
}
// 图片动画
function imgTransform(ownerInstance) {
try {
var image = ownerInstance.selectComponent('.tn-cropper__image')
if (!image) return
var x = cropper.imgLeft - cropper.imgWidth / 2,
y = cropper.imgTop - cropper.imgHeight / 2;
image.setStyle({
'transform': 'translate3d('+ x + 'px,' + y + 'px,0) scale(' + cropper.scale +') rotate(' + cropper.angle + 'deg)'
})
} catch(e) {
}
}
// 图片重置
function imageReset(ownerInstance) {
cropper.scale = 1
cropper.angle = 0
imgTransform(ownerInstance)
}
// 高度变化
function canvasHeight(ownerInstance) {
if (!ownerInstance) return
computeCutSize(ownerInstance)
}
// 宽度变化
function canvasWidth(ownerInstance) {
if (!ownerInstance) return
computeCutSize(ownerInstance)
}
// 更新数据
function updateData(ownerInstance) {
if (!ownerInstance) return
ownerInstance.callMethod('change', {
cutX: cropper.cutX,
cutY: cropper.cutY,
imgWidth: cropper.imgWidth,
imgHeight: cropper.imgHeight,
scale: cropper.scale,
angle: cropper.angle,
imgTop: cropper.imgTop,
imgLeft: cropper.imgLeft
})
}
module.exports = {
touchStart: touchStart,
touchMove: touchMove,
touchEnd: touchEnd,
propChange: propChange
}

View File

@ -0,0 +1,574 @@
<template>
<view class="tn-cropper-class tn-cropper" @touchmove.stop.prevent="stop">
<image
v-if="imageUrl"
:src="imageUrl"
class="tn-cropper__image"
:style="{
width: (imgWidth ? imgWidth : width) + 'px',
height: (imgHeight ? imgHeight : height) + 'px',
transitionDuration: (animation ? 0.3 : 0) + 's'
}"
mode="widthFix"
:data-minScale="minScale"
:data-maxScale="maxScale"
@load="imageLoad"
@error="imageLoad"
@touchstart="wxs.touchStart"
@touchmove="wxs.touchMove"
@touchend="wxs.touchEnd"
></image>
<view
class="tn-cropper__wrapper"
:style="{
width: width + 'px',
height: height + 'px',
borderRadius: isRound ? '50%' : '0'
}"
>
<view
class="tn-cropper__border"
:style="{
border: borderStyle,
borderRadius: isRound ? '50%' : '0',
}"
:prop="prop"
:change:prop="wxs.propChange"
:data-width="width"
:data-height="height"
:data-windowHeight="systemInfo.windowHeight || 600"
:data-windowWidth="systemInfo.windowWidth || 400"
:data-imgTop="imgTop"
:data-imgWidth="imgWidth"
:data-imgHeight="imgHeight"
:data-angle="angle"
></view>
</view>
<canvas
class="tn-cropper__canvas"
:style="{
width: width * scaleRatio + 'px',
height: height * scaleRatio + 'px'
}"
:canvas-id="CANVAS_ID"
:id="CANVAS_ID"
:disable-scroll="true"
></canvas>
<view
v-if="!custom"
class="tn-cropper__tabbar"
>
<view class="tn-cropper__tabbar__btn tn-cropper__tabber__cancel" @tap.stop="back">取消</view>
<view class="tn-cropper__tabbar__rotate" :class="[`tn-icon-${rotateIcon}`]" @tap.stop="setAngle"></view>
<view class="tn-cropper__tabbar__btn tn-cropper__tabber__confirm" @tap.stop="getCutImage">完成</view>
</view>
</view>
</template>
<script src="./index.wxs" lang="wxs" module="wxs"></script>
<script>
export default {
name: 'tn-cropper',
props: {
//
imageUrl: {
type: String,
default: ''
},
// px
height: {
type: Number,
default: 280
},
// px
width: {
type: Number,
default: 280
},
//
isRound: {
type: Boolean,
default: false
},
//
borderStyle: {
type: String,
default: '1rpx solid #FFF'
},
//
scaleRatio: {
type: Number,
default: 1
},
//
// (0, 1]
quality: {
type: Number,
default: 0.8
},
// base64(H5base64)
returnBase64: {
type: Boolean,
default: false
},
//
rotateAngle: {
type: Number,
default: 0
},
//
minScale: {
type: Number,
default: 0.5
},
//
maxScale: {
type: Number,
default: 2
},
// ()
custom: {
type: Boolean,
default: false
},
//
// customtrue
startCutting: {
type: Boolean,
default: false
},
// loading
loading: {
type: Boolean,
default: true
},
//
rotateIcon: {
type: String,
default: 'circle-arrow'
}
},
data() {
return {
// canvasid
CANVAS_ID: 'tn-cropper-canvas',
//
TIME_CUT_CENTER: null,
// canvas
ctx: null,
// x
cutX: 0,
// y
cutY: 0,
//
imgWidth: 0,
//
imgHeight: 0,
//
imgTop: 0,
//
imgLeft: 0,
//
scale: 1,
//
angle: 0,
//
animation: false,
//
animationTime: null,
//
systemInfo: {},
//
prop: '',
//
sizeChange: 0,
angleChange: 0,
resetChange: 0,
centerChange: 0
}
},
watch: {
imageUrl(val) {
this.imageReset()
this.showLoading()
uni.getImageInfo({
src: val,
success: (res) => {
//
this.imgComputeSize(res.width, res.height)
this.angleChange++
this.prop = `3,${this.angleChange}`
},
fail: (err) => {
console.log(err);
this.imgComputeSize()
this.angleChange++
this.prop = `3,${this.angleChange}`
}
})
},
isRound(val) {
if (val) {
this.$nextTick(() => {
this.imageReset()
})
}
},
rotateAngle(val) {
this.animation = true
this.angle = val
this.angleChanged(val)
},
animation(val) {
clearTimeout(this.animationTime)
if (val) {
this.animationTime = setTimeout(() => {
this.animation = false
}, 200)
}
},
startCutting(val) {
if (this.custom && val) {
this.getCutImage()
}
}
},
mounted() {
this.systemInfo = uni.getSystemInfoSync()
this.imgTop = this.systemInfo.windowHeight / 2
this.imgLeft = this.systemInfo.windowWidth / 2
this.ctx = uni.createCanvasContext(this.CANVAS_ID, this)
//
this.$nextTick(() => {
this.prop = '1,1'
})
setTimeout(() => {
this.$emit('ready', {})
}, 200)
},
methods: {
//
async getLocalImage(url) {
return await new Promise((resolve, reject) => {
uni.downloadFile({
url: url,
success: (res) => {
resolve(res.tempFilePath)
},
fail: (err) => {
reject(false)
}
})
})
},
//
getCutImage() {
if (!this.imageUrl) {
uni.showToast({
title: '请选择图片',
icon: 'none'
})
return
}
this.loading && this.showLoading()
const draw = async () => {
//
let imgWidth = this.imgWidth * this.scale * this.scaleRatio
let imgHeight = this.imgHeight * this.scale * this.scaleRatio
// canvas
let xpos = this.imgLeft - this.cutX
let ypos = this.imgTop - this.cutY
let imgUrl = this.imageUrl
// #ifdef APP-PLUS || MP-WEIXIN
if (~this.imageUrl.indexOf('https:')) {
imgUrl = await this.getLocalImage(this.imageUrl)
}
// #endif
//
this.ctx.translate(xpos * this.scaleRatio, ypos * this.scaleRatio)
//
if (this.isRound) {
const r = this.width > this.height ? Math.floor(this.height / 2) : Math.floor(this.width / 2)
let translateX = Math.floor(this.width / 2)
let translateY = Math.floor(this.height / 2)
this.ctx.beginPath()
this.ctx.arc(translateX - (xpos * this.scaleRatio), translateY - (ypos * this.scaleRatio), r, 0, (360 * Math.PI) / 180)
this.ctx.closePath()
this.ctx.stroke()
this.ctx.clip()
}
this.ctx.rotate((this.angle * Math.PI) / 180)
this.ctx.drawImage(imgUrl, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight)
//
this.ctx.draw(false, () => {
let params = {
width: this.width * this.scaleRatio,
height: Math.round(this.height * this.scaleRatio),
destWidth: this.width * this.scaleRatio,
destHeight: Math.round(this.height) * this.scaleRatio,
fileType: 'png',
quality: this.quality
}
let data = {
url: '',
base64: '',
width: this.width * this.scaleRatio,
height: this.height * this.scaleRatio
}
// #ifdef MP-ALIPAY
if (this.returnBase64) {
this.ctx.toDataURL(params).then((urlData) => {
data.base64 = urlData
this.loading && uni.hideLoading()
this.$emit('cropper', data)
})
} else {
this.ctx.toTempFilePath({
...params,
success: (res) => {
data.url = res.apFilePath
this.loading && uni.hideLoading()
this.$emit('cropper', data)
}
})
}
// #endif
let base64Flag = this.returnBase64
// #ifndef MP-ALIPAY
// #ifdef MP-BAIDU || MP-TOUTIAO || H5
base64Flag = false
// #endif
if (base64Flag) {
uni.canvasGetImageData({
canvasId: this.CANVAS_ID,
x: 0,
y: 0,
width: this.width * this.scaleRatio,
height: Math.round(this.height * this.scaleRatio),
success: (res) => {
const arrayBuffer = new Uint8Array(res.data)
const base64 = uni.arrayBufferToBase64(arrayBuffer)
data.base64 = base64
this.loading && uni.hideLoading()
this.$emit('cropper', data)
}
}, this)
} else {
uni.canvasToTempFilePath({
...params,
canvasId: this.CANVAS_ID,
success: (res) => {
data.url = res.tempFilePath
// #ifdef H5
data.base64 = res.tempFilePath
// #endif
this.loading && uni.hideLoading()
this.$emit('cropper', data)
}
}, this)
}
// #endif
})
}
draw()
},
//
change(e) {
this.cutX = e.cutX || 0
this.cutY = e.cutY || 0
this.imgWidth = e.imgWidth || this.imgWidth
this.imgHeight = e.imgHeight || this.imgHeight
this.scale = e.scale || 1
this.angle = e.angle || 0
this.imgTop = e.imgTop || 0
this.imgLeft = e.imgLeft || 0
},
//
imageReset() {
this.scale = 1
this.angle = 0
let systemInfo = this.systemInfo.windowHeight ? this.systemInfo : uni.getSystemInfoSync()
this.imgTop = systemInfo.windowHeight / 2
this.imgLeft = systemInfo.windowWidth / 2
this.resetChange++
this.prop = `4,${this.resetChange}`
//
this.$emit('initAngle', {})
},
//
imgComputeSize(width, height) {
// =
let imgWidth = width,
imgHeight = height;
if (imgWidth && imgHeight) {
if (imgWidth / imgHeight > this.width / this.height) {
imgHeight = this.height
imgWidth = (width / height) * imgHeight
} else {
imgWidth = this.width
imgHeight = (height / width) * imgWidth
}
} else {
let systemInfo = this.systemInfo.windowHeight ? this.systemInfo : uni.getSystemInfoSync()
imgWidth = systemInfo.windowWidth
imgHeight = 0
}
this.imgWidth = imgWidth
this.imgHeight = imgHeight
this.sizeChange++
this.prop = `2,${this.sizeChange}`
},
//
imageLoad(e) {
this.imageReset()
uni.hideLoading()
this.$emit('imageLoad', {})
},
//
moveStop() {
clearTimeout(this.TIME_CUT_CENTER)
this.TIME_CUT_CENTER = setTimeout(() => {
this.centerChange++
this.prop = `5,${this.centerChange}`
}, 688)
},
//
moveDuring() {
clearTimeout(this.TIME_CUT_CENTER)
},
//
showLoading() {
uni.showLoading({
title: '请稍等......',
mask: true
})
},
//
stop() {},
// /
back() {
uni.navigateBack()
},
//
angleChanged(val) {
this.moveStop()
if (val % 90) {
this.angle = Math.round(val / 90) * 90
}
this.angleChange++
this.prop = `3,${this.angleChange}`
},
//
setAngle() {
this.animation = true
this.angle = this.angle + 90
this.angleChanged(this.angle)
}
}
}
</script>
<style lang="scss" scoped>
.tn-cropper {
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.7);
position: fixed;
top: 0;
left: 0;
z-index: 1;
&__image {
width: 100%;
border-style: none;
position: absolute;
top: 0;
left: 0;
z-index: 2;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
transform-origin: center;
}
&__canvas {
position: fixed;
z-index: 10;
left: -2000px;
top: -2000px;
pointer-events: none;
}
&__wrapper {
position: fixed;
z-index: 4;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border: 3000px solid rgba(0, 0, 0, 0.55);
pointer-events: none;
box-sizing: content-box;
}
&__border {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
pointer-events: none;
}
&__tabbar {
width: 100%;
height: 120rpx;
padding: 0 40rpx;
box-sizing: border-box;
position: fixed;
left: 0;
bottom: 0;
z-index: 99;
display: flex;
align-items: center;
justify-content: space-between;
color: #FFFFFF;
font-size: 32rpx;
&::after {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
border-top: 1rpx solid rgba(255, 255, 255, 0.2);
-webkit-transform: scaleY(0.5) translateZ(0);
transform: scaleY(0.5) translateZ(0);
transform-origin: 0 100%;
}
&__btn {
height: 80rpx;
display: flex;
align-items: center;
}
&__rotate {
width: 44rpx;
height: 44rpx;
font-size: 40rpx;
text-align: center;
}
}
}
</style>

View File

@ -0,0 +1,288 @@
function setTimeout(instance, cb, time) {
if (time > 0) {
var s = getDate().getTime()
var fn = function () {
if (getDate().getTime() - s > time) {
cb && cb()
} else
instance.requestAnimationFrame(fn)
}
fn()
}
else
cb && cb()
}
// 判断触摸的移动方向
function decideSwiperDirection(startTouches, currentTouches, vertical) {
// 震动偏移容差
var toleranceShake = 150
// 移动容差
var toleranceTranslate = 10
if (!vertical) {
// 水平方向移动
if (Math.abs(currentTouches.y - startTouches.y) <= toleranceShake) {
// console.log(currentTouches.x, startTouches.x);
if (Math.abs(currentTouches.x - startTouches.x) > toleranceTranslate) {
if (currentTouches.x - startTouches.x > 0) {
return 'right'
} else if (currentTouches.x - startTouches.x < 0) {
return 'left'
}
}
}
} else {
// 垂直方向移动
if (Math.abs(currentTouches.x - startTouches.x) <= toleranceShake) {
// console.log(currentTouches.x, startTouches.x);
if (Math.abs(currentTouches.y - startTouches.y) > toleranceTranslate) {
if (currentTouches.y - startTouches.y > 0) {
return 'down'
} else if (currentTouches.y - startTouches.y < 0) {
return 'up'
}
}
}
}
return ''
}
// swiperItem参数数据更新
var itemDataObserver = function(newVal, oldVal, ownerInstance, instance) {
if (!newVal || newVal === 'undefined') return
var state = ownerInstance.getState()
state.itemData = newVal
}
// swiperIndex数据更新
var currentIndexObserver = function(newVal, oldVal, ownerInstance, instance) {
if ((!newVal && newVal != 0) || newVal === 'undefined') return
var state = ownerInstance.getState()
state.currentIndex = newVal
}
// containerData数据更新
var containerDataObserver = function(newVal, oldVal, ownerInstance, instance) {
if (!newVal || newVal === 'undefined') return
var state = ownerInstance.getState()
state.containerData = newVal
}
// 开始触摸
var touchStart = function(event, ownerInstance) {
console.log('touchStart');
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var itemData = state.itemData
var containerData = state.containerData
// 由于当前SwiperIndex初始为0可能会导致swiperIndex数据没有更新
if (!state.currentIndex || state.currentIndex === 'undefined') {
state.currentIndex = 0
}
if (!containerData || containerData.circular === 'undefined') {
containerData.circular = false
}
state.containerData = containerData
// 如果当前切换动画还没执行结束再次触摸会重新加载对应的swiperContainer的信息
// console.log(containerData.animationFinish);
if (!containerData.animationFinish) {
ownerInstance.callMethod('changeParentSwiperContainerStyleStatus',{
status: 'reload'
})
}
// 判断是否为为当前显示的SwiperItem
if (itemData.index != state.currentIndex) return
var touches = event.changedTouches[0]
if (!touches) return
// 标记滑动开始时间
state.touchStartTime = getDate().getTime()
// 记录当前滑动开始的xy坐标
state.touchRelactive = {
x: touches.pageX,
y: touches.pageY
}
// 记录触摸id用于处理多指的情况
state.touchId = touches.identifier
// 标记开始触摸
state.touching = true
ownerInstance.callMethod('updateTouchingStatus', {
status: true
})
}
// 正在移动
var touchMove = function(event, ownerInstance) {
console.log('touchMove');
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var itemData = state.itemData
var containerData = state.containerData
// 判断是否为为当前显示的SwiperItem
if (itemData.index != state.currentIndex) return
// 判断是否开始触摸
if (!state.touching) return
var touches = event.changedTouches[0]
if (!touches) return
// 判断是否为同一个触摸点
if (state.touchId != touches.identifier) return
var currentTouchRelactive = {
x: touches.pageX,
y: touches.pageY
}
// 计算相对位移比例
if (containerData.vertical) {
var touchDistance = currentTouchRelactive.y - state.touchRelactive.y
var itemHeight = itemData.itemHeight
var distanceRate = touchDistance / itemHeight
// console.log(currentTouchRelactive.y, touchDistance, itemHeight, distanceRate);
// 判断是否为衔接轮播如果不是衔接轮播如果当前为第一个swiperItem并且向下滑、当前为最后一个swiperItem并且向上滑时不进行操作
if (!containerData.circular &&
((state.currentIndex === 0 && touchDistance > 0) || (state.currentIndex === containerData.swiperItemLength - 1 && touchDistance < 0))
) {
return
}
// 如果超出了距离则不进行操作
if((Math.abs(touchDistance) > (itemData.itemTop + itemData.itemHeight))) {
ownerInstance.callMethod('updateParentSwiperContainerStyle', {
value: distanceRate < 0 ? -1 : 1
})
return
}
} else {
var touchDistance = currentTouchRelactive.x - state.touchRelactive.x
var itemWidth = itemData.itemWidth
var distanceRate = touchDistance / itemWidth
// console.log(currentTouchRelactive.x, touchDistance, itemWidth, distanceRate);
// 判断是否为衔接轮播如果不是衔接轮播如果当前为第一个swiperItem并且向右滑、当前为最后一个swiperItem并且向左滑时不进行操作
if (!containerData.circular &&
((state.currentIndex === 0 && touchDistance > 0) || (state.currentIndex === containerData.swiperItemLength - 1 && touchDistance < 0))
) {
return
}
// 如果超出了距离则不进行操作
if((Math.abs(touchDistance) > (itemData.itemLeft + itemData.itemWidth))) {
ownerInstance.callMethod('updateParentSwiperContainerStyle', {
value: distanceRate < 0 ? -1 : 1
})
return
}
}
ownerInstance.callMethod('updateParentSwiperContainerStyle', {
value: distanceRate
})
}
// 移动结束
var touchEnd = function(event, ownerInstance) {
console.log('touchEnd');
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var itemData = state.itemData
var containerData = state.containerData
// 判断是否为为当前显示的SwiperItem
if (itemData.index != state.currentIndex) return
// 判断是否开始触摸
if (!state.touching) return
var touches = event.changedTouches[0]
if (!touches) return
// 判断是否为同一个触摸点
if (state.touchId != touches.identifier) return
var currentTime = getDate().getTime()
var currentTouchRelactive = {
x: touches.pageX,
y: touches.pageY
}
if (containerData.vertical) {
// 判断触摸移动方向
var direction = decideSwiperDirection(state.touchRelactive, currentTouchRelactive, true)
// 判断是否为衔接轮播如果不是衔接轮播如果当前为第一个swiperItem并且向下滑、当前为最后一个swiperItem并且向上滑时不进行操作
if (containerData.circular ||
!((state.currentIndex === 0 && direction === 'down') || (state.currentIndex === containerData.swiperItemLength - 1 && direction === 'up'))
) {
// 判断触摸的时间和移动的距离是否超过了当前itemHeight的一半如果是则执行切换操作
// console.log(currentTime - state.touchStartTime, Math.abs(currentTouchRelactive.y - state.touchRelactive.y));
if ((currentTime - state.touchStartTime) > 200 && Math.abs(currentTouchRelactive.y - state.touchRelactive.y) < itemData.itemHeight / 2) {
ownerInstance.callMethod('changeParentSwiperContainerStyleStatus',{
status: 'reset'
})
} else {
// console.log(direction, state.touchRelactive.y, currentTouchRelactive.y);
ownerInstance.callMethod('updateParentSwiperContainerStyleWithDirection', {
direction: direction
})
}
}
} else {
// 判断触摸移动方向
var direction = decideSwiperDirection(state.touchRelactive, currentTouchRelactive, false)
// 判断是否为衔接轮播如果不是衔接轮播如果当前为第一个swiperItem并且向右滑、当前为最后一个swiperItem并且向左滑时不进行操作
if (containerData.circular ||
!((state.currentIndex === 0 && direction === 'right') || (state.currentIndex === containerData.swiperItemLength - 1 && direction === 'left'))
) {
// 判断触摸的时间和移动的距离是否超过了当前itemWidth的一半如果是则执行切换操作
// console.log(currentTime - state.touchStartTime, Math.abs(currentTouchRelactive.x - state.touchRelactive.x));
if ((currentTime - state.touchStartTime) > 200 && Math.abs(currentTouchRelactive.x - state.touchRelactive.x) < itemData.itemWidth / 2) {
ownerInstance.callMethod('changeParentSwiperContainerStyleStatus',{
status: 'reset'
})
} else {
// console.log(direction, state.touchRelactive.x, currentTouchRelactive.x);
ownerInstance.callMethod('updateParentSwiperContainerStyleWithDirection', {
direction: direction
})
}
}
}
// 清除标记
state.touchId = null
state.touchRelactive = null
state.touchStartTime = 0
// 标记停止触摸
state.touching = true
ownerInstance.callMethod('updateTouchingStatus', {
status: false
})
}
module.exports = {
itemDataObserver: itemDataObserver,
currentIndexObserver: currentIndexObserver,
containerDataObserver: containerDataObserver,
touchStart: touchStart,
touchMove: touchMove,
touchEnd: touchEnd
}

View File

@ -0,0 +1,277 @@
<template>
<!-- #ifdef MP-WEIXIN -->
<view
class="tn-c-swiper-item"
:style="[swiperStyle]"
:itemData="itemData"
:currentIndex="currentIndex"
:containerData="containerData"
:change:itemData="wxs.itemDataObserver"
:change:currentIndex="wxs.currentIndexObserver"
:change:containerData="wxs.containerDataObserver"
@touchstart="wxs.touchStart"
:catch:touchmove="touching?wxs.touchMove:''"
:catch:touchend="touching?wxs.touchEnd:''"
>
<view class="item__container tn-c-swiper-item__container" :style="[containerStyle]">
<slot></slot>
</view>
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view
class="tn-c-swiper-item"
:style="[swiperStyle]"
:itemData="itemData"
:currentIndex="currentIndex"
:containerData="containerData"
:change:itemData="wxs.itemDataObserver"
:change:currentIndex="wxs.currentIndexObserver"
:change:containerData="wxs.containerDataObserver"
@touchstart="wxs.touchStart"
@touchmove="wxs.touchMove"
@touchend="wxs.touchEnd"
>
<view class="item__container tn-c-swiper-item__container" :style="[containerStyle]">
<slot></slot>
</view>
</view>
<!-- #endif -->
</template>
<script src="./index.wxs" lang="wxs" module="wxs"></script>
<script>
export default {
name: 'tn-custom-swiper-item',
props: {
},
computed: {
// swiperItem
itemData() {
return {
index: this.index,
itemWidth: this.itemWidth,
itemHeight: this.itemHeight,
itemTop: this.itemTop,
itemLeft: this.itemLeft
}
},
currentIndex() {
return this.parentData.currentIndex
},
containerData() {
return {
duration: this.parentData.duration,
animationFinish: this.parentData.swiperContainerAnimationFinish,
circular: this.parentData.circular,
swiperItemLength: this.swiperItemLength,
vertical: this.parentData.vertical
}
},
swiperStyle() {
let style = {}
style.transform = `translate3d(${this.translateX}%, ${this.translateY}%, 0px)`
return style
},
containerStyle() {
let style = {}
if (this.parentData.customSwiperStyle && Object.keys(this.parentData.customSwiperStyle).length > 0) {
style = this.parentData.customSwiperStyle
}
if ((this.currentIndex === 0 && this.index === this.swiperItemLength - 1) || (this.index === this.currentIndex - 1) &&
(this.parentData.prevSwiperStyle && Object.keys(this.parentData.prevSwiperStyle).length > 0)
) {
// swiperItem
const copyStyle = JSON.parse(JSON.stringify(style))
style = Object.assign(copyStyle, this.parentData.prevSwiperStyle)
}
if ((this.currentIndex === this.swiperItemLength - 1 && this.index === 0) || (this.index === this.currentIndex + 1) &&
(this.parentData.nextSwiperStyle && Object.keys(this.parentData.nextSwiperStyle).length > 0)
) {
// swiperItem
const copyStyle = JSON.parse(JSON.stringify(style))
style = Object.assign(copyStyle, this.parentData.nextSwiperStyle)
}
return style
}
},
data() {
return {
//
parentData: {
duration: 500,
currentIndex: 0,
swiperContainerAnimationFinish: false,
circular: false,
vertical: false,
prevSwiperStyle: {},
customSwiperStyle: {},
nextSwiperStyle: {}
},
//
touching: true,
// swiperItem
translateX: 0,
translateY: 0,
// swiperItem
itemWidth: 0,
itemHeight: 0,
// swiperItem
itemTop: 0,
itemLeft: 0,
// swiperItem prev current next
status: 'current',
// swiperItemindex
index: 0,
// swiperItem
swiperItemLength: 0
}
},
created() {
this.parent = false
this.updateParentData()
// childrenswiperItem
this.index = this.parent.children.length
this.parent && this.parent.children.push(this)
},
mounted() {
this.$nextTick(() => {
this.initSwiperItem()
})
},
methods: {
// swiperItem
initSwiperItem() {
this.getSwiperItemRect(() => {
this.parent.updateAllSwiperItemStyle()
this.parentData.swiperContainerAnimationFinish = true
})
},
// swiperItem
async getSwiperItemRect(callback) {
const swiperItemRes = await this._tGetRect('.tn-c-swiper-item')
if (!swiperItemRes.height || !swiperItemRes.width) {
setTimeout(() => {
this.getSwiperItemRect()
}, 30)
return
}
this.itemWidth = swiperItemRes.width
this.itemHeight = swiperItemRes.height
this.itemTop = swiperItemRes.top
this.itemLeft = swiperItemRes.left
callback && callback()
},
// swiperItem
updateSwiperItemStyle(swiperItemLength, currentIndex = undefined) {
currentIndex = currentIndex != undefined ? currentIndex : this.parentData.currentIndex
this.swiperItemLength = swiperItemLength
// swiperItem
// swiperItemswiperItem
if (currentIndex === 0 && this.index === swiperItemLength - 1) {
if (this.parentData.vertical) {
this.translateX = 0
this.translateY = -100
} else {
this.translateX = -100
this.translateY = 0
}
}
// swiperItemswiperItem
else if (currentIndex === swiperItemLength - 1 && this.index === 0) {
if (this.parentData.vertical) {
this.translateX = 0
this.translateY = swiperItemLength * 100
} else {
this.translateX = swiperItemLength * 100
this.translateY = 0
}
}
//
else {
if (this.parentData.vertical) {
this.translateX = 0
this.translateY = this.index * 100
} else {
this.translateX = this.index * 100
this.translateY = 0
}
}
},
//
updateParentSwiperContainerStyle(e) {
this.parent.updateSwiperContainerStyleWithValue(e.value)
},
//
updateParentSwiperContainerStyleWithDirection(e) {
this.parent.updateSwiperContainerStyleWithDirection(e.direction)
},
//
changeParentSwiperContainerStyleStatus(e) {
// reset -> reload ->
this.parent.updateSwiperContainerStyleWithDirection(e.status)
},
//
updateParentData() {
this.getParentData('tn-custom-swiper')
},
//
updateTouchingStatus(e) {
this.touching = e.status
if (e.status) {
this.parent.stopAutoPlay()
} else {
this.parent.startAutoPlay()
}
},
//
extractCustomStyle(customStyle) {
let data = {
transform: {},
style: {}
}
if (!customStyle) return data
// transform
const allowTransformProps = ['scale','scaleX','scaleY','scaleZ','rotate','rotateX','rotateY','rotateZ']
for (let prop in customStyle) {
if (prop.startsWith('transformProp')) {
// transform
let transformProp = prop.substring('transformProp'.length)
const index = allowTransformProps.findIndex((item) => {
return item.toLowerCase() === transformProp.toLowerCase()
})
if (index !== -1) {
transformProp = allowTransformProps[index]
data.transform[transformProp] = customStyle[prop]
}
} else {
//
data.style[prop] = customStyle[prop]
}
}
return data
}
}
}
</script>
<style lang="scss" scoped>
.tn-c-swiper-item {
width: 100%;
height: 100%;
position: absolute;
display: block;
will-change: transform;
cursor: none;
transform: translate3d(0px, 0px, 0px);
.item__container {
width: 100%;
height: 100%;
display: block;
position: absolute;
}
}
</style>

View File

@ -0,0 +1,535 @@
<template>
<view
class="tn-c-swiper-class tn-c-swiper"
>
<!-- 轮播item容器-->
<view class="tn-swiper__container" :style="[swiperContainerStyle]" :animation="containerAnimation">
<slot></slot>
</view>
<!-- 轮播指示器-->
<view v-if="indicator" class="tn-swiper__indicator" :class="[`tn-swiper__indicator--${vertical ? 'vertical' : 'horizontal'}`]" :style="[indicatorStyle]">
<!-- 方形 -->
<block v-if="indicatorType === 'rect'">
<view
v-for="(item, index) in children.length"
:key="index"
class="tn-swiper__indicator__rect"
:class="[
`tn-swiper__indicator__rect--${vertical ? 'vertical' : 'horizontal'}`,
currentIndex === index ? `tn-swiper__indicator__rect--active tn-swiper__indicator__rect--active--${vertical ? 'vertical' : 'horizontal'}` : ''
]"
:style="[indicatorPointStyle(index)]"
></view>
</block>
<!-- -->
<block v-if="indicatorType === 'dot'">
<view
v-for="(item, index) in children.length"
:key="index"
class="tn-swiper__indicator__dot"
:class="[
`tn-swiper__indicator__dot--${vertical ? 'vertical' : 'horizontal'}`,
currentIndex === index ? `tn-swiper__indicator__dot--active tn-swiper__indicator__dot--active--${vertical ? 'vertical' : 'horizontal'}` : ''
]"
:style="[indicatorPointStyle(index)]"
></view>
</block>
<!-- 圆角方形 -->
<block v-if="indicatorType === 'round'">
<view
v-for="(item, index) in children.length"
:key="index"
class="tn-swiper__indicator__round"
:class="[
`tn-swiper__indicator__round--${vertical ? 'vertical' : 'horizontal'}`,
currentIndex === index ? `tn-swiper__indicator__round--active tn-swiper__indicator__round--active--${vertical ? 'vertical' : 'horizontal'}` : ''
]"
:style="[indicatorPointStyle(index)]"
></view>
</block>
<!-- 序号 -->
<block v-if="indicatorType === 'number' && !vertical">
<view class="tn-swiper__indicator__number">{{ currentIndex + 1 }}/{{ children.length }}</view>
</block>
</view>
</view>
</template>
<script>
export default {
name: 'tn-custom-swiper',
props: {
//
current: {
type: Number,
default: 0
},
//
autoplay: {
type: Boolean,
default: false
},
//
interval: {
type: Number,
default: 5000
},
//
duration: {
type: Number,
default: 500
},
//
circular: {
type: Boolean,
default: false
},
//
vertical: {
type: Boolean,
default: false
},
//
indicator: {
type: Boolean,
default: false
},
//
// rect -> round -> dot -> number ->
indicatorType: {
type: String,
default: 'dot'
},
//
// topLeft \ topCenter \ topRight \ bottomLeft \ bottomCenter \ bottomRight
indicatorPosition: {
type: String,
default: 'bottomCenter'
},
//
indicatorActiveColor: {
type: String,
default: ''
},
//
indicatorInactiveColor: {
type: String,
default: ''
},
//
prevSwiperStyle: {
type: Object,
default() {
return {}
}
},
//
customSwiperStyle: {
type: Object,
default() {
return {}
}
},
//
nextSwiperStyle: {
type: Object,
default() {
return {}
}
}
},
computed: {
parentData() {
return [
this.duration,
this.currentIndex,
this.swiperContainerAnimationFinish,
this.circular,
this.vertical,
this.prevSwiperStyle,
this.customSwiperStyle,
this.nextSwiperStyle
]
},
indicatorStyle() {
let style = {}
if (this.vertical) {
if (this.indicatorPosition === 'topLeft' || this.indicatorPosition === 'bottomLeft') style.justifyContent = 'flex-start'
if (this.indicatorPosition === 'topCenter' || this.indicatorPosition === 'bottomCenter') style.justifyContent = 'center'
if (this.indicatorPosition === 'topRight' || this.indicatorPosition === 'bottomRight') style.justifyContent = 'flex-end'
if (['topLeft','topCenter','topRight'].indexOf(this.indicatorPosition) >= 0) {
if (this.vertical) {
style.right = '12rpx'
style.left = 'auto'
} else {
style.top = '12rpx'
style.bottom = 'auto'
}
} else {
if (this.vertical) {
style.right = 'auto'
style.left = '12rpx'
} else {
style.top = 'auto'
style.bottom = '12rpx'
}
}
} else {
if (this.indicatorPosition === 'topLeft' || this.indicatorPosition === 'bottomLeft') style.justifyContent = 'flex-start'
if (this.indicatorPosition === 'topCenter' || this.indicatorPosition === 'bottomCenter') style.justifyContent = 'center'
if (this.indicatorPosition === 'topRight' || this.indicatorPosition === 'bottomRight') style.justifyContent = 'flex-end'
if (['topLeft','topCenter','topRight'].indexOf(this.indicatorPosition) >= 0) {
style.top = '12rpx'
style.bottom = 'auto'
} else {
style.top = 'auto'
style.bottom = '12rpx'
}
}
return style
},
indicatorPointStyle() {
return (index) => {
let style = {}
if (index === this.currentIndex && this.indicatorActiveColor !== '') {
style.backgroundColor = this.indicatorActiveColor
} else if (this.indicatorInactiveColor !== '') {
style.backgroundColor = this.indicatorInactiveColor
}
return style
}
}
},
watch: {
parentData() {
if (this.children.length) {
this.children.forEach((item) => {
// updateParentData()
typeof(item.updateParentData) === 'function' && item.updateParentData()
})
}
},
current(nVal, oVal) {
if (this.currentIndex === nVal) return
this.currentIndex = nVal > this.children.length ? this.children.length - 1 : nVal
this.swiperContainerAnimationFinish = false
//
this.swiperContainerStyle.transitionDuration = `${this.duration + 90}ms`
this.updateSwiperContainerItem(oVal)
}
},
data() {
return {
//
clearAnimationTimer: null,
//
convergeTimer: null,
// Timer
autoPlayTimer: null,
//
currentIndex: this.current,
// swiperContainer
swiperContainerStyle: {
transform: 'translate3d(0px, 0px, 0px)',
transitionDuration: '0ms'
},
// swiperContainer
containerAnimation: {},
//
swiperContainerAnimationFinish: false
}
},
created() {
this.children = []
},
mounted() {
this.$nextTick(() => {
const index = this.currentIndex > this.children.length ? this.children.length - 1 : this.currentIndex
this.updateSwiperContainerStyle(index)
this.startAutoPlay()
})
},
methods: {
// swiperItem
updateAllSwiperItemStyle() {
this.children.forEach((item, index) => {
typeof(item.updateSwiperItemStyle) === 'function' && item.updateSwiperItemStyle(this.children.length)
})
},
// swiperIndexswiperItemContainer
updateSwiperContainerStyle(index) {
if (this.vertical) {
this.swiperContainerStyle.transform = `translate3d(0px, ${-index * 100}%, 0px)`
} else {
this.swiperContainerStyle.transform = `translate3d(${-index * 100}%, 0px, 0px)`
}
},
// swiperItemContainer
updateSwiperContainerStyleWithValue(value) {
if (this.vertical) {
this.swiperContainerStyle.transform = `translate3d(0px, ${(-this.currentIndex * 100) + value * 100}%, 0px)`
} else {
this.swiperContainerStyle.transform = `translate3d(${(-this.currentIndex * 100) + value * 100}%, 0px, 0px)`
}
},
// swiperItemContainer
updateSwiperContainerStyleWithDirection(direction) {
const oldCurrent = this.currentIndex
const childrenLength = this.children.length
const lastSwiperItemIndex = childrenLength - 1
this.swiperContainerAnimationFinish = false
// SwiperItem
if (direction === 'reset') {
//
this.swiperContainerStyle.transitionDuration = `${this.duration}ms`
this.updateSwiperContainerStyle(this.currentIndex)
this.clearAnimationTimer = setTimeout(() => {
this.clearSwiperContainerAnimation()
}, this.duration)
} else if (direction === 'reload') {
this.clearConvergeSwiperItemTimer()
this.clearSwiperContainerAnimation()
this.updateSwiperItemStyle(0)
this.updateSwiperItemStyle(lastSwiperItemIndex)
} else {
if (direction === 'left' || direction === 'up') {
if (oldCurrent === childrenLength - 1 && !this.circular) {
this.clearSwiperContainerAnimation()
this.clearConvergeSwiperItemTimer()
return
}
this.currentIndex = oldCurrent + 1 >= childrenLength ? 0 : oldCurrent + 1
} else if (direction === 'right' || direction === 'down') {
if (oldCurrent === 0 && !this.circular) {
this.clearSwiperContainerAnimation()
this.clearConvergeSwiperItemTimer()
return
}
this.currentIndex = oldCurrent - 1 < 0 ? childrenLength - 1 : oldCurrent - 1
}
//
this.swiperContainerStyle.transitionDuration = `${this.duration + 90}ms`
// this.updateSwiperItemContainerRect(this.currentIndex)
}
// console.log(direction, oldCurrent, this.currentIndex);
this.updateSwiperContainerItem(oldCurrent)
//
this.$emit('change', {
current: this.currentIndex
})
},
//
startAutoPlay() {
if (this.autoplay && !this.autoPlayTimer && this.circular) {
this.autoPlayTimer = setInterval(() => {
this.updateSwiperContainerStyleWithDirection('left')
}, this.interval)
}
},
//
stopAutoPlay() {
if (this.autoPlayTimer) {
clearInterval(this.autoPlayTimer)
this.autoPlayTimer = null
}
},
// swiperContainerswiperItem
updateSwiperContainerItem(oldCurrent) {
const childrenLength = this.children.length
const lastSwiperItemIndex = childrenLength - 1
// SwiperItem
// swiperItemContainer
if (oldCurrent === 0 && this.currentIndex === lastSwiperItemIndex) {
//
// this.swiperContainerStyle.transform = `translate3d(100%, 0px, 0px)`
this.updateSwiperContainerStyle(-1)
this.clearSwiperContainerAnimationTimer()
this.clearAnimationTimer = setTimeout(() => {
this.convergeSwiperItem()
}, this.duration)
} else if (oldCurrent === lastSwiperItemIndex && this.currentIndex === 0) {
//
// this.swiperContainerStyle.transform = `translate3d(${-childrenLength * 100}%, 0px, 0px)`
this.updateSwiperContainerStyle(childrenLength)
this.clearSwiperContainerAnimationTimer()
this.clearAnimationTimer = setTimeout(() => {
this.convergeSwiperItem()
}, this.duration)
} else {
this.updateSwiperContainerStyle(this.currentIndex)
this.updateSwiperItemStyle(0)
this.updateSwiperItemStyle(lastSwiperItemIndex)
this.clearAnimationTimer = setTimeout(() => {
this.clearSwiperContainerAnimation()
}, this.duration)
}
},
// swiperItem
updateSwiperItemStyle(index) {
const childrenLength = this.children.length
if (index < 0) index = 0
if (index > childrenLength - 1) index = childrenLength - 1
typeof(this.children[index].updateSwiperItemStyle) === 'function' && this.children[index].updateSwiperItemStyle(childrenLength, this.currentIndex)
},
// swiperItem
updateSwiperItemContainerRect(index) {
const childrenLength = this.children.length
if (index < 0) index = 0
if (index > childrenLength - 1) index = childrenLength - 1
typeof(this.children[index].getSwiperItemRect) === 'function' && this.children[index].getSwiperItemRect()
},
//
convergeSwiperItem() {
const lastSwiperItemIndex = this.children.length - 1
this.clearSwiperContainerAnimation()
this.clearConvergeSwiperItemTimer()
this.convergeTimer = setTimeout(() => {
this.updateSwiperItemStyle(0)
this.updateSwiperItemStyle(lastSwiperItemIndex)
this.updateSwiperContainerStyle(this.currentIndex)
this.clearConvergeSwiperItemTimer()
}, 30)
},
// /
clearSwiperContainerAnimation() {
this.swiperContainerStyle.transitionDuration = `0ms`
this.swiperContainerAnimationFinish = true
this.clearSwiperContainerAnimationTimer()
},
// /
clearConvergeSwiperItemTimer() {
if (this.convergeTimer) {
clearTimeout(this.convergeTimer)
this.convergeTimer = null
}
},
// /
clearSwiperContainerAnimationTimer() {
if (this.clearAnimationTimer) {
clearTimeout(this.clearAnimationTimer)
this.clearAnimationTimer = null
}
}
}
}
</script>
<style lang="scss" scoped>
.tn-c-swiper {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
.tn-swiper {
&__container {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
will-change: transform;
transition-property: all;
transition-timing-function: ease-out;
}
&__indicator {
position: absolute;
display: flex;
z-index: 1;
&--horizontal {
padding: 0 24rpx;
flex-direction: row;
width: 100%;
}
&--vertical {
padding: 24rpx 0;
flex-direction: column;
height: 100%;
}
&__rect {
background-color: rgba(0, 0, 0, 0.3);
transition: all 0.5s;
&--horizontal {
width: 26rpx;
height: 8rpx;
}
&--vertical {
width: 8rpx;
height: 26rpx;
}
&--active {
background-color: rgba(255, 255, 255, 0.8);
}
}
&__dot {
width: 14rpx;
height: 14rpx;
border-radius: 20rpx;
background-color: rgba(0, 0, 0, 0.3);
transition: all 0.5s;
&--horizontal {
margin: 0 6rpx;
}
&--vertical {
margin: 6rpx 0;
}
&--active {
background-color: rgba(255, 255, 255, 0.8);
}
}
&__round {
width: 14rpx;
height: 14rpx;
border-radius: 20rpx;
background-color: rgba(0, 0, 0, 0.3);
transition: all 0.5s;
&--horizontal {
margin: 0 6rpx;
}
&--vertical {
margin: 6rpx 0;
}
&--active {
background-color: rgba(255, 255, 255, 0.8);
&--horizontal {
width: 34rpx;
}
&--vertical {
height: 34rpx;
}
}
}
&__number {
padding: 6rpx 16rpx;
line-height: 1;
background-color: rgba(0, 0, 0, 0.3);
color: rgba(255, 255, 255, 0.8);
border-radius: 100rpx;
font-size: 26rpx;
}
}
}
}
</style>

View File

@ -0,0 +1,265 @@
// 判断是否出界
var isOutRange = function(x1, y1, x2, y2, x3, y3) {
return x1 < 0 || x1 >= y1 || x2 < 0 || x2 >= y2 || x3 < 0 || x3 >= y3
}
var edit = false
function bool(str) {
return str === 'true' || str === true
}
/**
* 排序核心
* @param {Object} startKey 开始时位置
* @param {Object} endKey 结束时位置
* @param {Object} instance wxs内的局部变量快照
*/
var sortCore = function(startKey, endKey, state) {
var basedata = state.basedata
var excludeFix = function(sortKey, type) {
// fixed 元素位置不会变化, 这里直接用 sortKey 获取,更加便捷
if (state.list[sortKey].fixed) {
var _sortKey = type ? --sortKey : ++sortKey
return excludeFix(sortKey, type)
}
return sortKey
}
// 先获取到 endKey 对应的 realKey, 防止下面排序过程中该 realKey 被修改
var endRealKey = -1
state.list.forEach(function(item) {
if (item.sortKey === endKey) endRealKey = item.realKey
})
return state.list.map(function(item) {
if (item.fixed) return item
var sortKey = item.sortKey
var realKey = item.realKey
if (startKey < endKey) {
// 正序拖动
if (sortKey > startKey && sortKey <= endKey) {
--realKey
sortKey = excludeFix(--sortKey, true)
} else if (sortKey === startKey) {
realKey = endRealKey
sortKey = endKey
}
} else if (startKey > endKey) {
// 倒序拖动
if (sortKey >= endKey && sortKey < startKey) {
++realKey
sortKey = excludeFix(++sortKey, false)
} else if (sortKey === startKey) {
realKey = endRealKey
sortKey = endKey
}
}
if (item.sortKey != sortKey) {
item.translateX = (sortKey % basedata.columns) * 100 + '%'
item.translateY = Math.floor(sortKey / basedata.columns) * 100 + '%'
item.sortKey = sortKey
item.realKey = realKey
}
return item
})
}
var triggerCustomEvent = function(list, type, instance) {
if (!instance) return
var _list = [],
listData = [];
list.forEach(function(item) {
_list[item.sortKey] = item
})
_list.forEach(function(item) {
listData.push(item.data)
})
// 编译到小程序 funcName作为参数传递导致事件不执行
switch(type) {
case 'change':
instance.callMethod('change', {data: listData})
break
case 'sortEnd':
instance.callMethod('sortEnd', {data: listData})
break
}
}
var listObserver = function(newVal, oldVal, ownerInstance, instance) {
var state = ownerInstance.getState()
state.itemsInstance = ownerInstance.selectAllComponents('.tn-drag__item')
state.list = newVal || []
state.list.forEach(function(item, index) {
var itemInstance = state.itemsInstance[index]
if (item && itemInstance) {
itemInstance.setStyle({
'transform': 'translate3d('+ item.translateX + ',' + item.translateY +', 0)'
})
if (item.fixed) itemInstance.addClass('tn-drag__fixed')
}
})
}
var baseDataObserver = function(newVal, oldVal, ownerInstance, instance) {
var state = ownerInstance.getState()
state.basedata = newVal
}
var longPress = function(event, ownerInstance) {
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
edit = bool(dataset.edit)
if (!edit) return
if (!state.basedata || state.basedata === 'undefined') {
state.basedata = JSON.parse(dataset.basedata)
}
var basedata = state.basedata
var touches = event.changedTouches[0]
if (!touches) return
state.current = +dataset.index
// 初始项是固定项则返回
var item = state.list[state.current]
if (item && item.fixed) return
// 如果已经在 drag 中则返回, 防止多指触发 drag 动作, touchstart 事件中有效果
if (state.dragging) return
ownerInstance.callMethod("drag", {
dragging: true
})
// 计算X, Y轴初始位移使item中心移动到点击处单列的时候X轴初始不做位移
state.translateX = basedata.columns === 1 ? 0 : touches.pageX - (basedata.itemWidth / 2 + basedata.left)
state.translateY = touches.pageY - (basedata.itemHeight / 2 + basedata.top)
state.touchId = touches.identifier
instance.setStyle({
'transform': 'translate3d(' + state.translateX + 'px,' + state.translateY +'px, 0)'
})
state.itemsInstance.forEach(function(item, index) {
item.removeClass("tn-drag__transition").removeClass("tn-drag__current")
item.addClass(index === state.current ? "tn-drag__current" : "tn-drag__transition")
})
ownerInstance.callMethod("vibrate")
state.dragging = true
}
var touchStart = function(event, ownerInstance) {
var instance = event.instance
var dataset = instance.getDataset()
edit = bool(dataset.edit)
}
var touchMove = function(event, ownerInstance) {
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var basedata = state.basedata
if (!state.dragging || !edit) return
var touches = event.changedTouches[0]
if (!touches) return
// 如果不是同一个触发点则返回
if (state.touchId !== touches.identifier) return
// 计算X,Y轴位移, 单列时候X轴初始不做位移
var translateX = basedata.columns === 1 ? 0 : touches.pageX - (basedata.itemWidth / 2 + basedata.left)
var translateY = touches.pageY - (basedata.itemHeight / 2 + basedata.top)
// 到顶到低自动滑动
if (touches.clientY > basedata.windowHeight - basedata.itemHeight - basedata.realBottomSize) {
// 当前触摸点pageY + item高度 - (屏幕高度 - 底部固定区域高度)
ownerInstance.callMethod('pageScroll', {
scrollTop: touches.pageY + basedata.itemHeight - (basedata.windowHeight - basedata.realBottomSize)
})
} else if (touches.clientY < basedata.itemHeight + basedata.realTopSize) {
// 当前触摸点pageY - item高度 - 顶部固定区域高
ownerInstance.callMethod('pageScroll', {
scrollTop: touches.pageY - basedata.itemHeight - basedata.realTopSize
})
}
// 设置当前激活元素的偏移量
instance.setStyle({
'transform': 'translate3d('+ translateX + 'px,' + translateY + 'px, 0)'
})
var startKey = state.list[state.current].sortKey
var currentX = Math.round(translateX / basedata.itemWidth)
var currentY = Math.round(translateY / basedata.itemHeight)
var endKey = currentX + basedata.columns * currentY
// 目标项时固定项则返回
var item = state.list[endKey]
if (item && item.fixed) return
// X轴或者Y轴超出范围则返回
if (isOutRange(currentX, basedata.columns, currentY, basedata.rows, endKey, state.list.length)) return
// 防止拖拽过程中发生乱序问题
if (startKey === endKey || startKey === state.preStartKey) return
state.preStartKey = startKey
var list = sortCore(startKey, endKey, state)
state.itemsInstance.forEach(function(itemInstance, index) {
var item = list[index]
if (index !== state.current) {
itemInstance.setStyle({
'transform': 'translate3d('+ item.translateX + ',' + item.translateY +', 0)'
})
}
})
// ownerInstance.callMethod('vibrate')
ownerInstance.callMethod('listDataChange', {
data: list
})
triggerCustomEvent(list, "change", ownerInstance)
}
var touchEnd = function(event, ownerInstance) {
var instance = event.instance
var dataset = instance.getDataset()
var state = ownerInstance.getState()
var basedata = state.basedata
if (!state.dragging || !edit) return
triggerCustomEvent(state.list, "sortEnd", ownerInstance)
instance.addClass('tn-drag__transition')
instance.setStyle({
'transform': 'translate3d('+ state.list[state.current].translateX + ',' + state.list[state.current].translateY + ', 0)'
})
state.itemsInstance.forEach(function(item, index) {
item.removeClass('tn-drag__transition')
})
state.preStartKey = -1
state.dragging = false
ownerInstance.callMethod('drag', {
dragging: false
})
state.current = -1
state.translateX = 0
state.translateY = 0
}
module.exports = {
longPress: longPress,
touchStart: touchStart,
touchMove: touchMove,
touchEnd: touchEnd,
baseDataObserver: baseDataObserver,
listObserver: listObserver
}

View File

@ -0,0 +1,278 @@
<template>
<view
class="tn-drag-class tn-drag"
:style="{
height: wrapHeight + 'rpx'
}"
:list="listData"
:basedata="baseData"
:change:list="wxs.listObserver"
:change:basedata="wxs.baseDataObserver"
>
<!-- #ifdef MP-WEIXIN -->
<view
v-for="(item, index) in listData"
:key="item.id"
class="tn-drag__item"
:style="{
width: 100 / columns + '%',
height: itemHeight + 'rpx'
}"
:data-index="index"
:data-basedata="baseData"
:data-edit="edit"
@longpress="wxs.longPress"
@touchstart="wxs.touchStart"
:catch:touchmove="dragging?wxs.touchMove:''"
:catch:touchend="dragging?wxs.touchEnd:''"
>
<slot :entity="item.data" :fixed="item.fixed" :index="index" :height="itemHeight" :isEdit="edit"></slot>
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view
v-for="(item, index) in listData"
:key="item.id"
class="tn-drag__item"
:style="{
width: 100 / columns + '%',
height: itemHeight + 'rpx'
}"
@longpress="wxs.longPress"
:data-index="index"
:data-basedata="baseData"
:data-edit="edit"
@touchstart="wxs.touchStart"
@touchmove="wxs.touchMove"
@touchend="wxs.touchEnd"
>
<slot :entity="item.data" :fixed="item.fixed" :index="index" :height="itemHeight" :isEdit="edit"></slot>
</view>
<!-- #endif -->
</view>
</template>
<script src="./index.wxs" lang="wxs" module="wxs"></script>
<script>
export default {
name: 'tn-drag',
props: {
//
// fixed
list: {
type: Array,
default () {
return []
}
},
//
edit: {
type: Boolean,
default: true
},
//
columns: {
type: Number,
default: 3
},
// item, rpx
itemHeight: {
type: Number,
default: 0
},
//
scrollTop: {
type: Number,
default: 0
}
},
computed: {
wrapHeight() {
return this.rows * this.itemHeight
}
},
data() {
return {
//
baseData: {},
//
dragData: [],
//
rows: 0,
//
listData: [],
//
dragging: false
}
},
watch: {
list(val) {
this.listData = []
this.$nextTick(() => {
this.init()
})
},
columns(val) {
this.listData = []
this.$nextTick(() => {
this.init()
})
}
},
mounted() {
this.$nextTick(() => {
this.init()
})
},
methods: {
//
init() {
this.dragging = true
const initDragItem = item => {
const obj = {
...item
}
const fixed = obj?.fixed || false
delete obj["fixed"]
return {
id: this.unique(),
fixed,
data: {
...obj
}
}
}
let i = 0
const listData = (this.list || []).map((item, index) => {
let listItem = initDragItem(item)
//
listItem.realKey = i++
//
listItem.sortKey = index
listItem.translateX = `${(listItem.sortKey % this.columns) * 100}%`
listItem.translateY = `${Math.floor(listItem.sortKey / this.columns) * 100}%`
return listItem
})
this.rows = Math.ceil(listData.length / this.columns)
this.listData = listData
this.dragData = listData
if (listData.length === 0) return
// console.log(listData);
// dom
this.$nextTick(() => {
this.initRect()
})
},
// dom
initRect() {
const {
windowWidth,
windowHeight
} = uni.getSystemInfoSync()
let baseData = {}
baseData.windowHeight = windowHeight
baseData.realTopSize = 0
baseData.realBottomSize = 0
baseData.columns = this.columns
baseData.rows = this.rows
const query = uni.createSelectorQuery().in(this)
query.select('.tn-drag').boundingClientRect()
query.select('.tn-drag__item').boundingClientRect()
query.exec(res => {
if (!res) {
setTimeout(() => {
this.initRect()
}, 10)
return
}
baseData.itemWidth = res[1].width
baseData.itemHeight = res[1].height
baseData.left = res[0].left
baseData.top = res[0].top + this.scrollTop
this.dragging = false
this.baseData = baseData
})
},
//
vibrate() {
uni.vibrateShort()
},
//
pageScroll(e) {
uni.pageScrollTo({
scrollTop: e.scrollTop,
duration: 0
})
},
//
drag(e) {
this.dragging = e.dragging
},
//
listDataChange(e) {
this.dragData = e.data
},
// item
itemClick(index) {
const item = this.dragData[index]
this.$emit('click', {
key: item.realKey,
data: item.data
})
},
//
sortEnd(e) {
this.$emit('end', {
data: e.data
})
},
//
change(e) {
this.$emit('change', {
data: e.data
})
},
// id
unique(n = 6) {
let id = ''
for (let i = 0; i < n; i++) id += Math.floor(Math.random() * 10)
return 'tn_' + new Date().getTime() + id
}
}
}
</script>
<style lang="scss" scoped>
.tn-drag {
position: relative;
&__item {
position: absolute;
z-index: 2;
top: 0;
left: 0;
}
&__transition {
transition: transform 0.25s !important;
}
&__current {
z-index: 10 !important;
}
&__fixed {
z-index: 1 !important;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More