Browse Source

跳绳比赛成绩查询

master
aBin 5 years ago
commit
e1d2316ec8
  1. 23
      .eslintrc.js
  2. 17
      .gitignore
  3. 12
      .prettierrc
  4. 30
      App.vue
  5. 111
      README.md
  6. 6
      api/base.js
  7. 5
      api/user.js
  8. 184
      colorui/animation.scss
  9. 1226
      colorui/icon.scss
  10. 3990
      colorui/main.scss
  11. 34
      common/style/global.scss
  12. 0
      common/style/iconfont.scss
  13. 248
      components/demo/demo.vue
  14. 1226
      components/demo/styles/icon.scss
  15. 11
      components/demo/utils/index.js
  16. 19
      components/test/test.vue
  17. 182
      components/v-tabs/readme.md
  18. 339
      components/v-tabs/v-tabs.vue
  19. 133
      components/wuc-tab/wuc-tab.vue
  20. 1
      config/config.default.js
  21. 20
      config/config.user.js
  22. 64
      main.js
  23. 116
      manifest.json
  24. 36
      package-lock.json
  25. 34
      package.json
  26. 26
      pages.json
  27. 329
      pages/Details/Details.vue
  28. 165
      pages/Grouping/Grouping.vue
  29. 112
      plugins/request/index.js
  30. 273
      plugins/request/readme.md
  31. 316
      plugins/request/request.js
  32. BIN
      static/blue.png
  33. BIN
      static/red.png
  34. 12
      store/index.js
  35. 9
      store/modules/user/actions.js
  36. 5
      store/modules/user/index.js
  37. 26
      store/modules/user/mutations.js
  38. 6
      store/modules/user/state.js
  39. 133
      uni.scss
  40. 31
      utils/ui.js
  41. 132
      utils/user.js
  42. 20
      utils/util.js
  43. 21
      vue.config.js
  44. 8
      yarn.lock

23
.eslintrc.js

@ -0,0 +1,23 @@
/*
* Copyright (c) 2019.
* author: wally
* email: 18603454788@163.com
*/
module.exports = {
root: true,
env: { browser: true, node: true },
extends: ['plugin:vue/recommended', 'plugin:vue/essential', '@vue/prettier'],
rules: {
'vue/html-self-closing': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-param-reassign': ['error', { props: true, ignorePropertyModificationsFor: ['state'] }],
'max-len': ['error', { code: 120, tabWidth: 2 }],
'object-curly-newline': ['error', { multiline: true }],
'arrow-parens': ['error', 'as-needed'],
},
parserOptions: { parser: 'babel-eslint' },
};

17
.gitignore

@ -0,0 +1,17 @@
.DS_Store
node_modules/
dist/
unpackage/
unpackage/*
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln

12
.prettierrc

@ -0,0 +1,12 @@
{
"printWidth": 100,
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"arrowParens": "avoid",
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"jsxBracketSameLine": false,
"proseWrap": "always"
}

30
App.vue

@ -0,0 +1,30 @@
<script>
import { mapState, mapMutations, mapActions } from 'vuex';
export default {
async onLaunch(options) {
},
created () {
//sessionStorage
if (sessionStorage.getItem("store") ) {
this.$store.replaceState(Object.assign({}, this.$store.state,JSON.parse(sessionStorage.getItem("store"))))
}
//vuexsessionStorage
window.addEventListener("beforeunload",()=>{
sessionStorage.setItem("store",JSON.stringify(this.$store.state))
})
}
};
</script>
<style lang="scss">
@import 'colorui/main.scss';
@import 'colorui/icon.scss';
@import 'colorui/animation.scss';
@import 'common/style/global';
@import 'common/style/iconfont';
body{
background: white;
}
</style>

111
README.md

@ -0,0 +1,111 @@
# uni-templete使用说明
## 环境准备
+ 使用HbuilderX作为构建开发环境, 主要使用HX的编译环境, 也可以使用vscode编码 + HX编译的搭配
+ HX需要安装插件
+ es6编译
+ Eslint-js
+ Eslint-plugin-vue
+ git
+ prettier
+ **Scss/sass编译**
+ 微信小程序开发工具
+ appId申请
## 目录说明
+ **api**
+ api接口统一管理文件夹,不允许在组件中直接定义api地址,必须在api文件夹下进行统一管理
+ 不同的模块,分成不同的js文件进行管理
+ 采用`export const signin = params => http.post('/api/xxx', params);`格式,封装请求方法及请求地址,方便统一管理
+ **colorui** colorui样式库引入,如果不需要直接删除,注意删除app.vue里的引用
+ **common** 存放公用的css js font等文件,工具类js建议封装到utils文件里
+ **components**
+ 组件存放文件夹
+ 统一格式 `组件/组件.vue ` 文件夹与组件同名,在页面或组件中引入就不用再`import`和在`components`注册了
+ 如果是某个组件特用的子组件,确定不公用的情况下,建议封装到 `组件/components`文件夹里
+ **config** 配置信息文件,一些复杂对象或复杂数组的配置信息,不要在组件内直接定义,公用的建议提到config文件夹下(分模块管理),确定非公用的直接写到组件的文件夹内部即可
+ **pages** 页面存放目录
+ **plugins** 插件,request插件是封装的类似axios请求处理插件,跟axios用法一致,**注意返回值**:成功的返回对象是res.data.data,失败的返回值是res.data.msg,可根据后台接口对应修改。请求做了header里的token处理
+ **static** 存放静态文件
+ **store** vuex文件,注意分模块处理,参考模板中的user,组件中使用store文件时,优先使用mapState等解构方法
+ **utils** 公用工具类,注意分模块,如:`ui.js` `time.js` ` query.js`
+ **.eslintrc.js** eslint代码格式检测配置文件
+ **.gitignore** 上传git仓库忽略的文件
+ **.prettierrc** prettier自动格式化代码风格的插件配置文件
+ App.vue 入口文件
+ Main.js 入口文件
+ **Manifest.json** 项目配置文件
+ **package.json** 项目中有使用到npm包,初始时先运行`npm i`
+ **uni.scss** scss样式遍历定义文件,在组件中可直接使用其变量而不需要导入
+ **Vue.config.js** vue配置文件,定义了常用的alias,使用时尽量使用alias的绝对路径代替相对路径,如:`api/user.js`代替`./api/user.js`
```js
'~': __dirname,
config: resolve('config'),
api: resolve('api'),
store: resolve('store'),
components: resolve('components'),
pages: resolve('pages'),
common: resolve('common'),
plugins: resolve('plugins'),
utils: resolve('utils'),
```
## scss
+ 使用scss代替css样式
+ HX必须要安装`scss插件`
+ 项目开发前应该先定义好uni.scss里的变量,统一引用这里的变量,有利用界面风格统一及后期维护
[官方文档uni.scss](https://uniapp.dcloud.io/collocation/uni-scss)
## 约定
+ Package.json里内置了时间处理插件`moment.js`,统一使用`moment.js`进行时间处理
+ 使用scss进行样式开发
+ 样式变量(颜色,字体大小,间距等)统一定义到`uni.scss`文件里
+ 尽量使用alias定义的绝对路径代替相对路径,如:`api/user.js`代替`./api/user.js`
+ 保持代码风格统一,建议使用vscode + prettier插件,自动格式化代码
+ 代码提交前,进行lint检测,不允许有eslint未通过提交的情况
+ `components`里的组件统一格式 `组件/组件.vue ` 文件夹与组件同名,在页面或组件中引入就不用再`import`和在`components`注册了
+ 页面及组件中分割出来的子组件,确定不公用的情况下,建议封装到 `组件/components`文件夹里
+ 一些复杂对象或复杂数组的配置信息,不要在组件内直接定义,公用的建议提到config文件夹下(分模块管理),确定非公用的直接写到组件的文件夹内部即可
+ api 参考上文目录说明中的api项
+ git提交规范参考前端规范里的代码提交规范
https://kdocs.cn/l/saAjmwvzT?f=130
[文档] 1-前端技术规范-v1.0-20200618.docx

6
api/base.js

@ -0,0 +1,6 @@
// api基础地质
// export const BASE_URL = 'https://www.sxwikionline.com/gateway';
export const BASE_URL = '/gateway';
// 错误码
export const ERR_CODE = 200;

5
api/user.js

@ -0,0 +1,5 @@
const proxyUrl = '/tall/v1.0';
import { http } from 'plugins/request/index';
// 请求统一使用如下格式
// export const signin = params => http.post('/api/xxx', params);

184
colorui/animation.scss

@ -0,0 +1,184 @@
/*
Animation 微动画
基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28
*/
/* css 滤镜 控制黑白底色gif的 */
.gif-black{
mix-blend-mode: screen;
}
.gif-white{
mix-blend-mode: multiply;
}
/* Animation css */
[class*=animation-] {
animation-duration: .5s;
animation-timing-function: ease-out;
animation-fill-mode: both
}
.animation-fade {
animation-name: fade;
animation-duration: .8s;
animation-timing-function: linear
}
.animation-scale-up {
animation-name: scale-up
}
.animation-scale-down {
animation-name: scale-down
}
.animation-slide-top {
animation-name: slide-top
}
.animation-slide-bottom {
animation-name: slide-bottom
}
.animation-slide-left {
animation-name: slide-left
}
.animation-slide-right {
animation-name: slide-right
}
.animation-shake {
animation-name: shake
}
.animation-reverse {
animation-direction: reverse
}
@keyframes fade {
0% {
opacity: 0
}
100% {
opacity: 1
}
}
@keyframes scale-up {
0% {
opacity: 0;
transform: scale(.2)
}
100% {
opacity: 1;
transform: scale(1)
}
}
@keyframes scale-down {
0% {
opacity: 0;
transform: scale(1.8)
}
100% {
opacity: 1;
transform: scale(1)
}
}
@keyframes slide-top {
0% {
opacity: 0;
transform: translateY(-100%)
}
100% {
opacity: 1;
transform: translateY(0)
}
}
@keyframes slide-bottom {
0% {
opacity: 0;
transform: translateY(100%)
}
100% {
opacity: 1;
transform: translateY(0)
}
}
@keyframes shake {
0%,
100% {
transform: translateX(0)
}
10% {
transform: translateX(-9px)
}
20% {
transform: translateX(8px)
}
30% {
transform: translateX(-7px)
}
40% {
transform: translateX(6px)
}
50% {
transform: translateX(-5px)
}
60% {
transform: translateX(4px)
}
70% {
transform: translateX(-3px)
}
80% {
transform: translateX(2px)
}
90% {
transform: translateX(-1px)
}
}
@keyframes slide-left {
0% {
opacity: 0;
transform: translateX(-100%)
}
100% {
opacity: 1;
transform: translateX(0)
}
}
@keyframes slide-right {
0% {
opacity: 0;
transform: translateX(100%)
}
100% {
opacity: 1;
transform: translateX(0)
}
}

1226
colorui/icon.scss

File diff suppressed because one or more lines are too long

3990
colorui/main.scss

File diff suppressed because it is too large

34
common/style/global.scss

@ -0,0 +1,34 @@
.cc-active {
transform: translate3d(1rpx, 1rpx, 0);
}
.card {
margin-bottom: 60rpx;
.card-head {
display: flex;
align-items: center;
height: 120rpx;
padding: 0 32rpx;
.card-head-avatar {
display: flex;
justify-content: center;
align-items: center;
width: 56rpx;
height: 56rpx;
border-radius: 50%;
}
.card-head-title {
flex: 1;
margin: 0 20rpx;
font-size: 16px;
color: $black;
overflow: hidden;
}
.card-head-action {
font-size: 14px;
}
}
}

0
common/style/iconfont.scss

248
components/demo/demo.vue

@ -0,0 +1,248 @@
<template>
<div>
<div>
<wuc-tab :tab-list="tabList" :tabCur.sync="TabCur" tab-class="text-center bg-white wuc-tab fixed" :tab-style="CustomBar" select-class="text-blue" @change="tabChange"></wuc-tab>
<div class="cu-bar bg-white solid-bottom" style="margin-top:100upx">
<div class="action">
<text class="cuIcon-titles text-orange"></text>基本使用(tab固定只支持点击标签切换)
</div>
</div>
<div class="bg-white padding margin text-center text-black">{{tabList[TabCur].name}}</div>
</div>
<div>
<div class="cu-bar bg-white margin-top solid-bottom">
<div class="action">
<text class="cuIcon-titles text-orange"></text>居中选中放大(外部触发切换)
</div>
</div>
<wuc-tab :tab-list="tabList2" :tabCur="TabCur2" @change="tabChange2" tab-class="text-center text-black bg-white" select-class="text-blue text-xl"></wuc-tab>
<swiper :current="TabCur2" class="swiper" duration="300" :circular="true" indicator-color="rgba(255,255,255,0)" indicator-active-color="rgba(255,255,255,0)" @change="swiperChange2">
<swiper-item v-for="(item,index) in tabList2" :key="index">
<div class="bg-white padding margin text-center text-black">{{item.name}}</div>
</swiper-item>
</swiper>
</div>
<div>
<div class="cu-bar bg-white margin-top solid-bottom">
<div class="action">
<text class="cuIcon-titles text-orange"></text>平分
</div>
</div>
<wuc-tab :tab-list="tabList3" textFlex :tabCur.sync="TabCur3" tab-class="text-center text-black bg-white" select-class="text-orange"></wuc-tab>
<swiper :current="TabCur3" class="swiper" duration="300" :circular="true" indicator-color="rgba(255,255,255,0)" indicator-active-color="rgba(255,255,255,0)" @change="swiperChange3">
<swiper-item v-for="(item,index) in tabList3" :key="index">
<div class="bg-white padding margin text-center text-black">{{item.name}}</div>
</swiper-item>
</swiper>
</div>
<div>
<div class="cu-bar bg-white margin-top solid-bottom">
<div class="action">
<text class="cuIcon-titles text-orange"></text>背景
</div>
</div>
<wuc-tab :tab-list="tabList4" :tabCur.sync="TabCur4" tab-class="text-center text-white bg-blue" select-class="text-white"></wuc-tab>
<swiper :current="TabCur4" class="swiper row" duration="300" :circular="true" indicator-color="rgba(255,255,255,0)" indicator-active-color="rgba(255,255,255,0)" @change="swiperChange4">
<swiper-item v-for="(item,index) in tabList4" :key="index">
<div class="bg-white padding margin text-center text-black">{{item.name}}</div>
</swiper-item>
</swiper>
</div>
<div>
<div class="cu-bar bg-white margin-top solid-bottom">
<div class="action">
<text class="cuIcon-titles text-orange"></text>图标
</div>
</div>
<wuc-tab :tab-list="tabList5" :tabCur.sync="TabCur5" tab-class="text-center text-black bg-white" select-class="text-blue" />
<swiper :current="TabCur5" class="swiper" duration="300" :circular="true" indicator-color="rgba(255,255,255,0)" indicator-active-color="rgba(255,255,255,0)" @change="swiperChange5">
<swiper-item v-for="(item,index) in tabList5" :key="index">
<div class="bg-white padding margin text-center text-black">{{item.name}}</div>
</swiper-item>
</swiper>
</div>
</div>
</template>
<script>
import WucTab from '@/components/wuc-tab/wuc-tab.vue';
import { obj2style } from '@/utils/index';
export default {
data() {
return {
tabList: [
{ name: '选项卡一' },
{ name: '选项卡二' },
{ name: '选项卡三' },
{ name: '选项卡四' },
{ name: '选项卡五' },
{ name: '选项卡六' },
{ name: '选项卡七' },
{ name: '选项卡八' }
],
tabList2: [{ name: '精选' }, { name: '订阅' }],
tabList3: [{ name: '精选' }, { name: '订阅' }],
tabList4: [
{ name: '推荐' },
{ name: '热点' },
{ name: '视频' },
{ name: '问答' },
{ name: '社会' },
{ name: '娱乐' },
{ name: '科技' },
{ name: '汽车' }
],
tabList5: [
{ name: '短信', icon: 'cuIcon-comment' },
{ name: '电话', icon: 'cuIcon-dianhua' },
{ name: 'wifi', icon: 'cuIcon-wifi' }
],
TabCur: 0,
TabCur2: 0,
TabCur3: 0,
TabCur4: 0,
TabCur5: 0
};
},
components: { WucTab },
computed: {
CustomBar() {
let style = {};
// #ifdef MP-WEIXIN
const systemInfo = uni.getSystemInfoSync();
let CustomBar =
systemInfo.platform === "android"
? systemInfo.statusBarHeight + 50
: systemInfo.statusBarHeight + 45;
style['top'] = CustomBar + 'px';
// #endif
// #ifdef H5
style['top'] = 0 + 'px';
// #endif
return obj2style(style);
}
},
methods: {
tabChange(index) {
this.TabCur = index;
},
tabChange2(index) {
this.TabCur2 = index;
},
swiperChange2(e) {
let { current } = e.target;
this.TabCur2 = current;
},
swiperChange3(e) {
let { current } = e.target;
this.TabCur3 = current;
},
swiperChange4(e) {
let { current } = e.target;
this.TabCur4 = current;
},
swiperChange5(e) {
this.TabCur5 = e.target.current;
}
},
onReady() {}
};
</script>
<style>
@import "~@/styles/icon.scss";
div,
scroll-view,
swiper {
box-sizing: border-box;
}
div {
font-size: 28upx;
background-color: #f1f1f1;
}
.swiper {
height: 140upx;
}
.cu-bar {
display: flex;
position: relative;
align-items: center;
min-height: 100upx;
justify-content: space-between;
}
.cu-bar .action {
display: flex;
align-items: center;
height: 100%;
justify-content: center;
max-width: 100%;
background-color: #ffffff;
}
.cu-bar .action:first-child {
margin-left: 30upx;
font-size: 30upx;
}
.solid,
.solid-bottom {
position: relative;
}
.solid::after,
.solid-bottom::after{
content: " ";
width: 200%;
height: 200%;
position: absolute;
top: 0;
left: 0;
border-radius: inherit;
transform: scale(0.5);
transform-origin: 0 0;
pointer-events: none;
box-sizing: border-box;
}
.solid::after {
border: 1upx solid rgba(0, 0, 0, 0.1);
}
.solid-bottom::after {
border-bottom: 1upx solid rgba(0, 0, 0, 0.1);
}
.text-orange{
color:#f37b1d
}
.text-black{
color:#333333;
}
.bg-white{
background-color: #ffffff;
}
.padding {
padding: 30upx;
}
.margin {
margin: 30upx;
}
.margin-top {
margin-top: 30upx;
}
.text-center {
text-align: center;
}
</style>

1226
components/demo/styles/icon.scss

File diff suppressed because one or more lines are too long

11
components/demo/utils/index.js

@ -0,0 +1,11 @@
/**
* 为样式动态赋值
* @param {*} style
*/
export function obj2style(style) {
let str = [];
Object.keys(style).forEach(key => {
str.push(`${key}:${style[key]};`);
});
return str.join(';');
}

19
components/test/test.vue

@ -0,0 +1,19 @@
<template>
<view>
test组件
</view>
</template>
<script>
export default {
data() {
return {
};
}
}
</script>
<style lang="scss">
</style>

182
components/v-tabs/readme.md

@ -0,0 +1,182 @@
## 插件说明
> 这是 `v-tabs` 插件的升级版本,参数上有很大变动,支持 `H5` `小程序` `手机端`,如果是在之前的插件上升级的话,请注意参数的变更,触发的事件没有变更。
## 使用说明
### 1、最基本用法
- 视图文件
```html
<v-tabs v-model="current" :tabs="tabs" @change="changeTab"></v-tabs>
```
- 脚本文件
```js
export default {
data() {
return {
current: 0,
tabs: ['军事', '国内', '新闻新闻', '军事', '国内', '新闻', '军事', '国内', '新闻']
}
},
methods: {
changeTab(index) {
console.log('当前选中的项:' + index)
}
}
}
```
### 2、平铺整个屏幕
- 视图文件
```html
<v-tabs v-model="activeTab" :scroll="false" :tabs="['全部', '进行中', '已完成']"></v-tabs>
```
- 脚本文件
```js
export default {
data() {
return {
activeTab: 0
}
}
}
```
### 3、胶囊用法
- 视图文件
```html
<v-tabs v-model="current" :tabs="tabs" :pills="true" line-height="0" activeColor="#fff" @change="changeTab"></v-tabs>
```
- 脚本文件
```js
data() {
return {
current: 2,
tabs: [
'军事',
'国内',
'新闻新闻',
'军事',
'国内',
'新闻',
'军事',
'国内',
'新闻',
],
},
methods: {
changeTab(index) {
console.log('当前选中索引:' + index)
}
}
}
```
## 文档说明
### 1、属性说明
| 参数 | 类型 | 默认值 | 说明 |
| :---------------: | :-----: | :-------: | :----------------------------------------: |
| value | Number | 0 | 必传(双向绑定的值) |
| color | String | '#333' | 默认文字颜色 |
| activeColor | String | '#2979ff' | 选中文字的颜色 |
| fontSize | String | '28rpx' | 默认文字大小(rpx 或 px) |
| bold | Boolean | true | 是否加粗选中项 |
| scroll | Boolean | true | 是否显示滚动条,平铺设置 false |
| height | String | '70rpx' | tab 高度(rpx 或 px) |
| lineHeight | String | '10rpx' | 滑块高度(rpx 或 px) |
| lineColor | String | '#2979ff' | 滑块的颜色 |
| lineScale | Number | 0.5 | 滑块宽度缩放值 |
| lineRadius | String | '10rpx' | 滑块圆角宽度(rpx 或 px) |
| pills | Boolean | false | 是否开启胶囊 |
| pillsColor | String | '#2979ff' | 胶囊背景颜色(rpx 或 px) |
| pillsBorderRadius | String | '10rpx' | 胶囊圆角宽度(rpx 或 px) |
| field | String | '' | 如果 tabs 子项是对象,输入需要展示的键名 |
| bgColor | String | '#fff' | 背景色,支持 linear-gradient 渐变 |
| padding | String | '0' | 整个 tab padding 属性 |
| fixed | Boolean | false | 是否固定在顶部 |
| paddingItem | String | '0 22rpx' | 选项的边距(设置上下不生效,需要设置高度) |
### 2、事件说明
| 名称 | 参数 | 说明 |
| :----: | :---: | :--------------------------------: |
| change | index | 改变选中项触发, index 选中项的下标 |
## 更新日志
### 2020-09-24
1. 修复 `v-tabs` 第一次可能出现第一个标签显示不完整的情况
2. 修改了 `pages/tabs/order` 示例文件
### 2020-09-21
1. 修复添加 `fixed` 属性后,滚动条无效
2. 修复选项很少的情况下,下划线计算计算错误
3. 新增 `paddingItem` 属性,设置选项左右边距(上下边距需要设置 `height` 属性,或者设置 `padding` 属性)
**写在最后:**
欢迎各位老铁反馈 bug ,本人后端 PHP 一枚,只是应为感兴趣前端,自己琢磨,自己搞。如果你在使用的过程中有什么不合理,需要优化的,都可以在下面评论(或加我 QQ: 1207791534),本人看见后回复、修正,感谢。
### 2020-09-17
1. 紧急修复 bug,横向滑动不了的情况
### 2020-09-16
1. 新增 `fixed` 属性,是否固定在顶部,示例地址:`pages/tabs/tabs-static`
2. 优化之前的页面结构
**注意:**
1. 使用 `padding` 属性的时候,尽量不要左右边距,会导致下划线位置不对
2. 如果不绑定 `v-model` 会导致 `change` 事件改变的时候,下划线不跟随问题
### 2020-09-09
1. 修复 `width` 错误,dom 加载的时候没有及时获取到 `data` 属性导致的。
### 2020-08-29
1. 优化异步改变 `tabs` 后,下划线不初始化问题
2. `github` 地址上有图 2 的源码,需要的自行下载,页面路径:`pages/tabs/order`
### 2020-08-20
1. 优化 `节点查询``选中渲染`
2. 优化支付宝中 `createSelectorQuery()` 的影响
### 2020-08-19
1. 优化 `change` 事件触发机制
### 2020-08-16
1. 修改默认高度为 `70rpx`
2. 新增属性 `bgColor`,可设置背景颜色,默认 `#fff`
3. 新增整个 `tab``padding` 属性,默认 `0`
### 2020-08-13
1. 全新的 `v-tabs 2.0`
2. 支持 `H5` `小程序` `APP`
3. 属性高度可配置
## 预览
![v-tabs 2.0.1.gif](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghsv40mj76g30ai0i2tsd.gif)
![v-tabs 2.0.2.gif](https://img-cdn-aliyun.dcloud.net.cn/stream/plugin_screens/42f3a920-a674-11ea-8a24-ffee00625e2e_1.png?v=1597912963)

339
components/v-tabs/v-tabs.vue

@ -0,0 +1,339 @@
<template>
<view :id="elId" class="v-tabs">
<scroll-view
id="scrollContainer"
:scroll-x="scroll"
:scroll-left="scroll ? scrollLeft : 0"
:scroll-with-animation="scroll"
:style="{ position: fixed ? 'fixed' : 'relative', zIndex: 1993 }"
>
<view
class="v-tabs__container"
:style="{
display: scroll ? 'inline-flex' : 'flex',
whiteSpace: scroll ? 'nowrap' : 'normal',
background: bgColor,
height,
padding
}"
>
<view
class="v-tabs__container-item"
v-for="(v, i) in tabs"
:key="i"
:style="{
color: current == i ? activeColor : color,
fontSize: current == i ? fontSize : fontSize,
fontWeight: bold && current == i ? 'bold' : '',
justifyContent: !scroll ? 'center' : '',
flex: scroll ? '' : 1,
padding: paddingItem
}"
@click="change(i)"
>
{{ field ? v[field] : v }}
</view>
<view
v-if="!pills"
class="v-tabs__container-line"
:style="{
background: lineColor,
width: lineWidth + 'px',
height: lineHeight,
borderRadius: lineRadius,
left: lineLeft + 'px',
transform: `translateX(-${lineWidth / 2}px)`
}"
></view>
<view
v-else
class="v-tabs__container-pills"
:style="{
background: pillsColor,
borderRadius: pillsBorderRadius,
left: pillsLeft + 'px',
width: currentWidth + 'px',
height
}"
></view>
</view>
</scroll-view>
<view
class="v-tabs__placeholder"
:style="{
height: fixed ? height : '0',
padding
}"
></view>
</view>
</template>
<script>
/**
* v-tabs
* @property {Number} value 选中的下标
* @property {Array} tabs tabs 列表
* @property {String} bgColor = '#fff' 背景颜色
* @property {String} color = '#333' 默认颜色
* @property {String} activeColor = '#2979ff' 选中文字颜色
* @property {String} fontSize = '28rpx' 默认文字大小
* @property {String} activeFontSize = '28rpx' 选中文字大小
* @property {Boolean} bold = [true | false] 选中文字是否加粗
* @property {Boolean} scroll = [true | false] 是否滚动
* @property {String} height = '60rpx' tab 的高度
* @property {String} lineHeight = '10rpx' 下划线的高度
* @property {String} lineColor = '#2979ff' 下划线的颜色
* @property {Number} lineScale = 0.5 下划线的宽度缩放比例
* @property {String} lineRadius = '10rpx' 下划线圆角
* @property {Boolean} pills = [true | false] 是否胶囊样式
* @property {String} pillsColor = '#2979ff' 胶囊背景色
* @property {String} pillsBorderRadius = '10rpx' 胶囊圆角大小
* @property {String} field 如果是对象显示的键名
* @property {Boolean} fixed = [true | false] 是否固定
* @property {String} paddingItem = '0 22rpx' 选项的边距
*
* @event {Function(current)} change 改变标签触发
*/
export default {
props: {
value: {
type: Number,
default: 0
},
tabs: {
type: Array,
default() {
return []
}
},
bgColor: {
type: String,
default: '#fff'
},
padding: {
type: String,
default: '0'
},
color: {
type: String,
default: '#ccc'
},
activeColor: {
type: String,
default: 'white'
},
fontSize: {
type: String,
default: '28rpx'
},
activeFontSize: {
type: String,
default: '32rpx'
},
bold: {
type: Boolean,
default: true
},
scroll: {
type: Boolean,
default: true
},
height: {
type: String,
default: '70rpx'
},
lineColor: {
type: String,
default: '#FFD700'
},
lineHeight: {
type: String,
default: '2px'
},
lineScale: {
type: Number,
default: 1
},
lineRadius: {
type: String,
default: '10rpx'
},
pills: {
type: Boolean,
deafult: false
},
pillsColor: {
type: String,
default: '#FE5835'
},
pillsBorderRadius: {
type: String,
default: '25px'
},
field: {
type: String,
default: ''
},
fixed: {
type: Boolean,
default: false
},
paddingItem: {
type: String,
default: '0 22rpx'
}
},
data() {
return {
elId: '',
lineWidth: 30,
currentWidth: 0, //
lineLeft: 0, //
pillsLeft: 0, //
scrollLeft: 0, //
containerWidth: 0, //
current: 0 //
}
},
watch: {
value(newVal) {
this.current = newVal
this.$nextTick(() => {
this.getTabItemWidth()
})
},
current(newVal) {
this.$emit('input', newVal)
},
tabs(newVal) {
this.$nextTick(() => {
this.getTabItemWidth()
})
}
},
methods: {
//
randomString(len) {
len = len || 32
let $chars =
'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
let maxPos = $chars.length
let pwd = ''
for (let i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
}
return pwd
},
//
change(index) {
if (this.current !== index) {
this.current = index
this.$emit('change', index)
}
},
//
getTabItemWidth() {
let query = uni
.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this)
// #endif
//
query
.select(`#scrollContainer`)
.boundingClientRect((data) => {
if (!this.containerWidth && data) {
this.containerWidth = data.width
}
})
.exec()
// tab-item
query
.selectAll('.v-tabs__container-item')
.boundingClientRect((data) => {
if (!data) {
return
}
let lineLeft = 0
let currentWidth = 0
if (data) {
for (let i = 0; i < data.length; i++) {
if (i < this.current) {
lineLeft += data[i].width
} else if (i == this.current) {
currentWidth = data[i].width
} else {
break
}
}
}
//
this.currentWidth = currentWidth
//
this.lineWidth = currentWidth * this.lineScale * 1
//
this.lineLeft = lineLeft + currentWidth / 2
//
this.pillsLeft = lineLeft
//
if (this.scroll) {
this.scrollLeft = this.lineLeft - this.containerWidth / 2
}
})
.exec()
}
},
mounted() {
this.elId = 'xfjpeter_' + this.randomString()
this.current = this.value
this.$nextTick(() => {
this.getTabItemWidth()
})
}
}
</script>
<style lang="scss" scoped>
.v-tabs {
width: 100%;
box-sizing: border-box;
overflow: hidden;
::-webkit-scrollbar {
display: none;
}
&__container {
min-width: 100%;
position: relative;
display: inline-flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
&-item {
display: flex;
align-items: center;
height: 100%;
position: relative;
z-index: 10;
// padding: 0 11px;
transition: all 0.3s;
white-space: nowrap;
}
&-line {
position: absolute;
bottom: 0;
transition: all 0.3s linear;
}
&-pills {
position: absolute;
transition: all 0.3s linear;
z-index: 9;
}
}
}
</style>

133
components/wuc-tab/wuc-tab.vue

@ -0,0 +1,133 @@
<template>
<scroll-view class="wuc-tab" :class="tabClass" :style="tabStyle" scroll-with-animation scroll-x :scroll-left="scrollLeft">
<div v-if="!textFlex">
<div class="wuc-tab-item" :class="[index === tabCur ? selectClass + ' cur':'']" v-for="(item,index) in tabList" :key="index" :id="index" @tap="tabSelect(index,$event)">
<text :class="item.icon"></text>
<span>{{item.name}}</span>
</div>
</div>
<div class="flex text-center" v-if="textFlex">
<div class="wuc-tab-item flex-sub" :class="index === tabCur ? selectClass + ' cur':''" v-for="(item,index) in tabList" :key="index" :id="index" @tap="tabSelect(index,$event)">
<text :class="item.icon"></text>
<span>{{item.name}}</span>
</div>
</div>
</scroll-view>
</template>
<script>
export default {
name: 'wuc-tab',
data() {
return {};
},
props: {
tabList: {
type: Array,
default() {
return [];
}
},
tabCur: {
type: Number,
default() {
return 0;
}
},
tabClass: {
type: String,
default() {
return '';
}
},
tabStyle: {
type: String,
default() {
return '';
}
},
textFlex: {
type: Boolean,
default() {
return false;
}
},
selectClass: {
type: String,
default() {
return 'text-blue';
}
}
},
methods: {
tabSelect(index, e) {
if (this.currentTab === index) return false;
this.$emit('update:tabCur', index);
this.$emit('change', index);
}
},
computed: {
scrollLeft() {
return (this.tabCur - 1) * 60;
}
}
};
</script>
<style>
div,
scroll-view,
swiper {
box-sizing: border-box;
}
.wuc-tab {
white-space: nowrap;
}
.wuc-tab-item {
height: 90upx;
display: inline-block;
line-height: 90upx;
margin: 0 10upx;
padding: 0 20upx;
}
.wuc-tab-item.cur {
border-bottom: 4upx solid;
}
.wuc-tab.fixed {
position: fixed;
width: 100%;
top: 0;
z-index: 1024;
box-shadow: 0 1upx 6upx rgba(0, 0, 0, 0.1);
}
.flex {
display: flex;
}
.text-center {
text-align: center;
}
.flex-sub {
flex: 1;
}
.text-blue{
color:#0081ff;
}
.text-white{
color:#ffffff;
}
.bg-white{
background-color: #ffffff;
}
.bg-blue{
background-color: #0081ff;
}
.text-orange{
color: #f37b1d
}
.text-xl {
font-size: 36upx;
}
</style>

1
config/config.default.js

@ -0,0 +1 @@
export const ERR_CODE = 200;

20
config/config.user.js

@ -0,0 +1,20 @@
/*
* Copyright (c) 2020.
* author: wally
* email: 18603454788@163.com
*/
// 用户登录client
export const SIGN_IN_CLIENTS = { mp: 0, h5: 1, android: 2, ios: 3, wx_work: 4 };
// 用户登录类型
export const SIGN_IN_TYPES = {
mp: 0,
phone: 1,
email: 2,
username: 3,
wx: 4,
wx_web: 5,
wb: 6,
wx_work: 7,
};

64
main.js

@ -0,0 +1,64 @@
import Vue from 'vue';
import moment from 'moment';
import { http } from 'plugins/request/index';
import App from './App';
import store from './store';
import axios from 'axios'
Vue.prototype.$axios = axios
import Fingerprint from 'fingerprintjs'
// 白名单页面
const whitePathList = [
'basic-info',
'statistics',
'user-code',
'sign',
'my-signs',
'my-code',
'my-trips',
];
/**
* 检查url是否在是否在白名单内
* @param {string} url path+query
*/
const checkWhitePath = url => {
let str = url.slice(7).split('/')[0];
return whitePathList.includes(str);
};
// var fingerprint = new Fingerprint().get();
// store.state.user.fingerprint = fingerprint
// console.log(store.state.user.fingerprint)
Vue.config.productionTip = false;
Vue.prototype.$http = http;
Vue.prototype.$moment = moment;
moment.locale('zh-cn');
Vue.prototype.goHome = () => {
uni.reLaunch({
url: '/pages/index/index',
});
};
Vue.prototype.openPage = function(path, query = '') {
let url = query ? `${path}?${query}` : path;
store.commit('user/setPagePath', url);
const isWhite = checkWhitePath(url);
if ((!store.state.user.userInfo || !store.state.user.userInfo.id) && !isWhite) {
url = '/pages/basic-info/basic-info';
}
uni.navigateTo({ url });
};
App.mpType = 'app';
const app = new Vue({
store,
...App,
});
app.$mount();

116
manifest.json

@ -0,0 +1,116 @@
{
"name" : "Loading...",
"appid" : "__UNI__4CABB72",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
"networkTimeout" : {
"uploadFile" : 20000,
"request" : 20000
},
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueCompiler" : "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.READ_CONTACTS\"/>",
"<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.WRITE_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<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.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wx2f9ef33e08053bbf",
"setting" : {
"urlCheck" : false,
"es6" : true,
"postcss" : true,
"minified" : true
},
"permission" : {
"scope.userLocation" : {
"desc" : "你的位置信息将用于获取地理位置及地图展示"
}
},
"usingComponents" : true,
"navigateToMiniProgramAppIdList" : [ "wx5b97b0686831c076" ]
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"h5" : {
"devServer" : {
"proxy" : {
"/gateway" : {
"target" : "https://test.tall.wiki/gateway",
"changeOrigin" : true,
"secure" : true,
"pathRewrite" : {
"^/gateway" : ""
}
}
},
"https" : false,
"port" : ""
},
"router" : {
"base" : ""
},
"domain" : "",
"async" : {
//js
"loading" : "AsyncLoading", //js使
"error" : "AsyncError", //js使
"delay" : 500, // loading js delay loading
"timeout" : 1000 //js error
},
"title" : "tttsss"
}
}

36
package-lock.json

@ -0,0 +1,36 @@
{
"name": "uniTemplete",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"axios": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz",
"integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"fingerprintjs": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/fingerprintjs/-/fingerprintjs-0.5.3.tgz",
"integrity": "sha1-ACZCL64asQA2keE2wUPhm3UnslU="
},
"fingerprintjs2": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/fingerprintjs2/-/fingerprintjs2-2.1.2.tgz",
"integrity": "sha512-ZPsLgjziFRbUb5tXWpEMtWp4XFnzSah8SiNfl3aoURDZ+2zi2tuIOYUULqDBV+Cb6paN+raWT+Q2qpOaCbX/Yw=="
},
"follow-redirects": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
}
}
}

34
package.json

@ -0,0 +1,34 @@
{
"name": "uniTemplete",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"uni-app": {
"scripts": {
"mp-dingtalk": {
"title": "钉钉小程序",
"env": {
"UNI_PLATFORM": "mp-alipay"
},
"define": {
"MP-DINGTALK": true
}
}
}
},
"repository": {
"type": "git",
"url": ""
},
"author": "wally",
"license": "ISC",
"dependencies": {
"axios": "^0.21.0",
"fingerprintjs": "^0.5.3",
"fingerprintjs2": "^2.1.2",
"moment": "^2.29.1"
}
}

26
pages.json

@ -0,0 +1,26 @@
{
"pages": [
{
"path" : "pages/Grouping/Grouping",
"style" : {}
}
,{
"path" : "pages/Details/Details",
"style" : {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "2020年山西省学生跳绳比赛成绩查询",
"navigationBarBackgroundColor": "#FE5A38",
"backgroundColor": "#0a97c6"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "2020年山西省学生跳绳比赛成绩查询",
"navigationBarBackgroundColor": "#4F89F4",
"backgroundColor": "#0a97c6"
},
"easycom": {
"autoscan": true
}
}

329
pages/Details/Details.vue

@ -0,0 +1,329 @@
<template>
<view>
<view class="img-box">
<img src="static/red.png"></img>
<view class="project-title">
30秒单摇跳
<!-- <icon class="cuIcon-right" type="icon"></icon>
<icon class="cuIcon-right" type="icon"></icon> -->
</view>
</view>
<view class="project-box">
<view class="project-team-name">
<v-tabs v-model="current" :tabs="tabs" :pills="true" activeColor="white" pillsBorderRadius="25px" pillsColor="#FCBC3B" @change="changeTab"></v-tabs>
</view>
<view class="project-nav">
<view class="ranking">
<view class="ranking-title">
排名
</view>
</view>
<view class="team">
<view class="team-title">
参赛队伍
</view>
</view>
<view class="player-name">
<view class="name-title">
姓名
</view>
</view>
<view class="result">
<view class="result-title">
成绩
</view>
</view>
</view>
<view class="project-con">
<view class="ranking">
<view class="ranking-con" v-for="(item,index) in list" :key="index">
<text v-if="item.ranking - 0 === 1" class="top1">TOP{{ item.ranking }}</text>
<text v-else-if="item.ranking - 0 === 2" class="top2">TOP{{ item.ranking }}</text>
<text v-else-if="item.ranking - 0 === 3">TOP{{ item.ranking }}</text>
<text v-else>{{ item.ranking }}</text>
</view>
</view>
<view class="team">
<view class="team-con-box">
<view class="team-con" :class="item.team.length - 0 > 8 ? 'team-con-act' : ''" v-for="(item,index) in list" :key="index">
{{ item.team }}
</view>
</view>
</view>
<view class="player-name">
<view class="name-con" v-for="(item,index) in list" :key="index">
{{ item.name }}
</view>
</view>
<view class="result">
<view class="result-con" v-for="(item,index) in list" :key="index">
{{ item.result }}
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
list: [{
ranking: 1,
team: '山西太原实验小学十中',
name: '某某某',
result: 100
},{
ranking: 2,
team: 'XXXXXX学校XXXXXX学校',
name: '某某某',
result: 99
},{
ranking: 3,
team: 'XXXXXX学校',
name: '某某某',
result: 95
},{
ranking: 4,
team: 'XXXXXX学校X',
name: '某某某',
result: 93
},{
ranking: 5,
team: 'XXXXXX学校',
name: '某某某',
result: 92
},{
ranking: 6,
team: 'XXXXXX学校XXXXX',
name: '某某某',
result: 90
},{
ranking: 5,
team: 'XXXXXX学校',
name: '某某某',
result: 92
},{
ranking: 7,
team: 'XXXXXX学校',
name: '某某某',
result: 89
},{
ranking: 8,
team: 'XXXXXX学校',
name: '某某某',
result: 88
},{
ranking: 9,
team: 'XXXXXX学校',
name: '某某某',
result: 87
},{
ranking: 8,
team: 'XXXXXX学校',
name: '某某某',
result: 88
},{
ranking: 9,
team: 'XXXXXX学校',
name: '某某某',
result: 87
},{
ranking: 10,
team: 'XXXXXX学校',
name: '某某某',
result: 86
},{
ranking: 11,
team: 'XXXXXX学校',
name: '某某某',
result: 86
}],
ActIndex: 0,
current: 0,
tabs: ['XXXXXX组', 'XXXXXX组XXXXXX组XXXXXX组', 'XXXXXX组', 'XXXXXX组', 'XXXXXX组', 'XXXXXX组', 'XXXXXX组', 'XXXXXX学校']
}
},
methods: {
changeTab(index) {
console.log('当前选中的项:' + index)
}
}
}
</script>
<style lang="scss" scoped>
.img-box {
position: relative;
width: 750rpx;
height: 500rpx;
img {
width: 100%;
}
}
.project-box {
position: absolute;
width: 630rpx;
left: 60rpx;
background: $white;
box-shadow: 0 0 30px $redShadow;
height: 62%;
top: 31%;
padding: 35rpx 35rpx;
border-radius: 20rpx;
}
.project-title {
height: 26px;
line-height: 26px;
min-width: 130px;
position: absolute;
top: 58%;
left: 8%;
background: $white;
text-align: center;
padding-left: 20px;
padding-right: 20px;
border-radius: 50px;
color: #FE5835;
font-size: 16px;
font-weight: 1000;
}
.project-nav {
margin-top: 14px;
// box-shadow: 0 2px 2px $redShadow;
display: flex;
}
.project-con {
display: flex;
height: 80%;
overflow-y: auto;
font-size: 14px;
}
.ranking {
flex: 1;
text-align: center;
}
.ranking-title {
height: 30px;
line-height: 30px;
margin-bottom: 5px;
}
.ranking-con {
height: 40px;
line-height: 40px;
color: #B87333;
font-size: 14px;
font-weight: 1000;
}
.team {
flex: 2;
text-align: center;
// overflow-y: visible !important;
width: 40%;
}
.team-con-box {
overflow-x: hidden;
}
.team-title {
height: 30px;
line-height: 30px;
margin-bottom: 5px;
}
.team-con {
height: 40px;
line-height: 40px;
// width: 200px;
white-space:nowrap;
}
.team-con-act {
animation: 15s wordsLoop linear infinite normal;
}
@keyframes wordsLoop {
0% {
transform: translateX(0px);
-webkit-transform: translateX(0px);
}
99% {
transform: translateX(-100%);
-webkit-transform: translateX(-100%);
}
100% {
transform: translateX(0%);
-webkit-transform: translateX(0%);
}
}
@-webkit-keyframes wordsLoop {
0% {
transform: translateX(0px);
-webkit-transform: translateX(0px);
}
100% {
transform: translateX(-100%);
-webkit-transform: translateX(-100%);
}
}
.player-name {
flex: 1;
text-align: center;
}
.name-title {
height: 30px;
line-height: 30px;
margin-bottom: 5px;
}
.name-con {
height: 40px;
line-height: 40px;
overflow-x: auto;
}
.result {
flex: 1;
text-align: center;
}
.result-title {
height: 30px;
line-height: 30px;
margin-bottom: 5px;
}
.result-con {
height: 40px;
line-height: 40px;
}
.top1 {
font-size: 18px;
color: #FFD700;
}
.top2 {
font-size: 16px;
color: #c0c0c0;
}
.project-team-name {
height: 40px;
line-height: 40px;
width: 570rpx;
// background: $yellowLight;
display: flex;
overflow-x: auto;
}
.team-det {
flex: 0 0 120px;
color: #ccc;
text-align: center;
font-size: 14px;
// flex-shrink:0;
// width: 100px;
}
.active {
color: $black;
font-size: 16px;
}
</style>

165
pages/Grouping/Grouping.vue

@ -0,0 +1,165 @@
<template>
<view>
<view class="img-box">
<img src="static/blue.png"></img>
</view>
<view class="project-box">
<view class="project-info" v-for="(item,index) in list" :key="index" @click="jump(index)">
<view class="info-left">
{{ index + 1 }}
</view>
<view class="info-con">
<view class="info-con-left">{{ item.project }}</view>
<view>{{ item.time }}</view>
</view>
<view class="info-right">
<view class="right-con">
<text class="started" v-if="item.result - 0 === 0">未开始</text>
<text class="ongoing" v-if="item.result - 0 === 2">进行中</text>
<text class="success" v-if="item.result - 0 === 1">出成绩</text>
<icon class="cuIcon-right" type="icon"></icon>
<icon class="cuIcon-right" type="icon"></icon>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
list:[{
project:'30秒单摇跳',
time:'11月28日 09:00',
result:1
}, {
project:'3分钟单摇跳',
time:'11月28日 10:00',
result:1
}, {
project:'30秒双摇跳',
time:'11月28日 11:00',
result:2
}, {
project:'连续三摇跳',
time:'11月28日 14:00',
result:0
}, {
project:'30秒交互绳',
time:'11月28日 11:00',
result:2
}, {
project:'30秒交互绳',
time:'11月28日 11:00',
result:0
}, {
project:'30秒交互绳',
time:'11月28日 11:00',
result:0
}, {
project:'30秒交互绳',
time:'11月28日 11:00',
result:0
}, {
project:'30秒交互绳',
time:'11月28日 11:00',
result:0
}]
}
},
methods: {
jump(num) {
// console.log(num)
uni.navigateTo({
url: `/pages/Details/Details?num=${num}`
})
}
}
}
</script>
<style lang="scss" scoped>
.img-box {
position: relative;
width: 750rpx;
height: 500rpx;
img {
width: 100%;
}
}
.project-box {
position: absolute;
width: 630rpx;
left: 60rpx;
background: $white;
box-shadow: 0 0 30px $blue;
height: 62%;
top: 31%;
padding: 35rpx 35rpx;
border-radius: 20rpx;
overflow-y: auto;
}
.project-info {
height: 60px;
position: relative;
box-shadow: 0 0 10px $blueLight;
margin-bottom: 10px;
border-radius: 50px;
overflow-x: auto;
}
.info-left {
height: 60px;
width: 20%;
line-height: 60px;
text-align: center;
font-size: 30px;
color: $white;
position: absolute;
left: 0;
background: $blue;
}
.info-con {
position: absolute;
left: 20%;
width: 50%;
line-height: 30px;
padding-left: 20px;
font-size: 14px;
font-weight: 1000;
height: 100%;
}
.info-con-left {
letter-spacing:4px
}
.info-right {
position: absolute;
right: 0;
height: 100%;
width: 30%;
font-size: 14px;
icon {
font-size: 10px;
}
}
.right-con {
position: absolute;
bottom: 5px;
width: 85%;
text-align: justify !important;
text-align-last: justify;
font-size: 14px;
font-weight: bold;
}
.started {
color: #ADADAD;
}
.ongoing {
color: #F3CB77;
}
.success {
color: $blue;
}
</style>

112
plugins/request/index.js

@ -0,0 +1,112 @@
import { BASE_URL, ERR_CODE } from 'api/base';
import Request from './request';
import Fingerprint from 'fingerprintjs'
const test = new Request();
/**
* 获取token
* @return {string} 本地保存的token
*/
export const getToken = () => uni.getStorageSync('token');
test.setConfig(config => {
/* 设置全局配置 */
config.baseUrl = BASE_URL;
config.header = {
...config.header,
};
return config;
});
test.interceptor.request((config, cancel) => {
/* 请求之前拦截器 */
config.header = {
...config.header,
};
/*
if (!token) { // 如果token不存在,调用cancel 会取消本次请求,但是该函数的catch() 仍会执行
cancel('token 不存在') // 接收一个参数,会传给catch((err) => {}) err.errMsg === 'token 不存在'
}
*/
return config;
});
/**
* 自定义验证器如果返回true 则进入响应拦截器的响应成功函数(resolve)否则进入响应拦截器的响应错误函数(reject)
* @param { Number } statusCode - 请求响应体statusCode只读
* @return { Boolean } 如果为true, resolve, 否则 reject
*/
test.validateStatus = statusCode => {
return statusCode === ERR_CODE;
};
test.interceptor.response(
response => {
/* 请求之后拦截器 */
return response;
},
response => {
// 请求错误做点什么
return response;
},
);
const http = new Request();
http.setConfig(config => {
// 设置全局配置
config.baseUrl = BASE_URL; // 根域名不同
config.header = {
...config.header,
};
return config;
});
/**
* 自定义验证器如果返回true 则进入响应拦截器的响应成功函数(resolve)否则进入响应拦截器的响应错误函数(reject)
* @param { Number } statusCode - 请求响应体statusCode只读
* @return { Boolean } 如果为true, resolve, 否则 reject
*/
http.validateStatus = statusCode => {
return statusCode === ERR_CODE;
};
// console.log(fingerprint)
http.interceptor.request((config, cancel) => {
/* 请求之前拦截器 */
const token = getToken() ? { Authorization: `Bearer ${getToken()}` } : {};
// const fingerprint = { fingerprint: new Fingerprint().get() };
config.header = {
...config.header,
...token,
// ...fingerprint,
};
/*
if (!token) { // 如果token不存在,调用cancel 会取消本次请求,但是该函数的catch() 仍会执行
cancel('token 不存在') // 接收一个参数,会传给catch((err) => {}) err.errMsg === 'token 不存在'
}
*/
return config;
});
http.interceptor.response(
response => {
/* 请求之后拦截器 */
if (response.data.code !== ERR_CODE) {
// 服务端返回的状态码不等于200,则reject()
return Promise.reject(response.data.msg);
}
return response.data.data;
},
response => {
// 请求错误做点什么
return response;
},
);
export { http, test };

273
plugins/request/readme.md

@ -0,0 +1,273 @@
**插件使用说明**
- 基于 Promise 对象实现更简单的 request 使用方式,支持请求和响应拦截
- 支持全局挂载
- 支持多个全局配置实例
- 支持自定义验证器
- 支持文件上传(如不使用可以删除class里upload 方法)
- 支持` typescript `、` javascript ` 版本(如果不使用ts版本,则可以把luch-request-ts 文件夹删除)
- 下载后把 http-request 文件夹放到项目 utils/ 目录下
**Example**
---
创建实例
``` javascript
const http = new Request();
```
执行` GET `请求
``` javascript
http.get('/user/login', {params: {userName: 'name', password: '123456'}}).then(res => {
}).catch(err => {
})
// 局部修改配置,局部配置优先级高于全局配置
http.get('/user/login', {
params: {userName: 'name', password: '123456'}, /* 会加在url上 */
header: {}, /* 会覆盖全局header */
dataType: 'json',
responseType: 'text'
}).then(res => {
}).catch(err => {
})
```
执行` POST `请求
``` javascript
http.post('/user/login', {userName: 'name', password: '123456'} ).then(res => {
}).catch(err => {
})
// 局部修改配置,局部配置优先级高于全局配置
http.post('/user/login', {userName: 'name', password: '123456'}, {
params: {}, /* 会加在url上 */
header: {}, /* 会覆盖全局header */
dataType: 'json',
responseType: 'text'
}).then(res => {
}).catch(err => {
})
```
执行` upload `请求
``` javascript
http.upload('api/upload/img', {
files: [], // 仅5+App支持
fileType:'image/video/audio', // 仅支付宝小程序,且必填。
filePath: '', // 要上传文件资源的路径。
name: 'file', // 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
header: {},
formData: {}, // HTTP 请求中其他额外的 form data
}).then(res => {
}).catch(err => {
})
```
**luch-request API**
--
``` javascript
http.request({
method: 'POST', // 请求方法必须大写
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
},
params: { // 会拼接到url上
token: '1111'
}
})
具体参数说明:[uni.uploadFile](https://uniapp.dcloud.io/api/request/network-file)
http.upload('api/upload/img', {
files: [], // 仅5+App支持
fileType:'image/video/audio', // 仅支付宝小程序,且必填。
filePath: '', // 要上传文件资源的路径。
name: 'file', // 文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
header: {}, // 如填写,会覆盖全局header
formData: {}, // HTTP 请求中其他额外的 form data
})
```
请求方法别名 / 实例方法
``` javascript
http.request(config)
http.get(url[, config])
http.upload(url[, config])
http.delete(url[, data[, config]])
http.head(url[, data[, config]])
http.post(url[, data[, config]])
http.put(url[, data[, config]])
http.connect(url[, data[, config]])
http.options(url[, data[, config]])
http.trace(url[, data[, config]])
```
**全局请求配置**
--
``` javascript
{
baseUrl: '', /* 全局根路径,需要注意,如果请求的路径为绝对路径,则不会应用baseUrl */
header: {
'Content-Type': 'application/json;charset=UTF-8'
},
method: 'GET',
dataType: 'json',
responseType: 'text'
}
```
全局配置修改` setConfig `
``` javascript
/**
* @description 修改全局默认配置
* @param {Function}
*/
http.setConfig((config) => { /* config 为默认全局配置*/
config.baseUrl = 'http://www.bbb.cn'; /* 根域名 */
config.header = {
a: 1,
b: 2
}
return config
})
```
自定义验证器` validateStatus `
``` javascript
/**
* 自定义验证器,如果返回true 则进入响应拦截器的响应成功函数(resolve),否则进入响应拦截器的响应错误函数(reject)
* @param { Number } statusCode - 请求响应体statusCode(只读)
* @return { Boolean } 如果为true,则 resolve, 否则 reject
*/
http.validateStatus = (statusCode) => { // 默认
return statusCode === 200
}
// 举个栗子
http.validateStatus = (statusCode) => {
return statusCode >= 200 && statusCode < 300
}
```
**拦截器**
--
在请求之前拦截
``` javascript
/**
* @param { Function} cancel - 取消请求,调用cancel 会取消本次请求,但是该函数的catch() 仍会执行; 不会进入响应拦截器
*
* @param {String} text ['handle cancel'| any] - catch((err) => {}) err.errMsg === 'handle cancel'。非必传,默认'handle cancel'
* @cancel {Object} config - catch((err) => {}) err.config === config; 非必传,默认为request拦截器修改之前的config
* function cancel(text, config) {}
*/
http.interceptor.request((config, cancel) => { /* cancel 为函数,如果调用会取消本次请求。需要注意:调用cancel,本次请求的catch仍会执行。必须return config */
config.header = {
...config.header,
a: 1
}
/*
if (!token) { // 如果token不存在,调用cancel 会取消本次请求,但是该函数的catch() 仍会执行
cancel('token 不存在', config) // 把修改后的config传入,之后响应就可以拿到修改后的config。 如果调用了cancel但是不传修改后的config,则catch((err) => {}) err.config 为request拦截器修改之前的config
}
*/
return config;
})
```
在请求之后拦截
``` javascript
http.interceptor.response((response) => { /* 对响应成功做点什么 (statusCode === 200),必须return response*/
// if (response.data.code !== 200) { // 服务端返回的状态码不等于200,则reject()
// return Promise.reject(response)
// }
console.log(response)
return response
}, (response) => { /* 对响应错误做点什么 (statusCode !== 200),必须return response*/
console.log(response)
return response
})
```
**typescript使用**
--
在` request.ts `里还暴露了五个接口
```javascript
{
options, // request 方法配置参数
handleOptions, // get/post 方法配置参数
config, // init 全局config接口(setConfig 参数接口)
requestConfig, // 请求之前参数配置项
response // 响应体
}
```
**常见问题**
--
1. 为什么会请求两次?
- 总有些小白问这些很那啥的问题,有两种可能,一种是‘post三次握手’(不知道的请先给个五星好评,然后打自己一巴掌,并问自己,为什么这都不知道),还有一种可能是`本地访问接口时跨域请求,所以浏览器会先发一个option 去预测能否成功,然后再发一个真正的请求`(没有自己观察请求头,Request Method,就跑来问的,请再打自己一巴掌,并问自己,为什么这都不知道,不知道也行,为什么不百度)。
2. 如何跨域?
- 问的人不少,可以先百度了解一下。<a href="https://ask.dcloud.net.cn/article/35267" target="_blank">如何跨域</a>
3. post 怎么传不了数组的参数啊?
- <a href="https://uniapp.dcloud.io/api/request/request">uni-request</a> <br>
可以点击看一下uni-request 的api 文档,data支持的文件类型只有<code>Object/String/ArrayBuffer</code>这个真跟我没啥关系 0.0
4. 'Content-Type' 为什么要小写?
- hbuilderX 更新至‘2.3.0.20190919’ 后,uni.request post请求,如果 ‘Content-Type’ 大写,就会在后面自动拼接‘ application/json’,请求头变成
`Content-Type: application/json;charset=UTF-8 application/json`,导致后端无法解析类型,`Status Code 415`,post 请求失败。但是小写就不会出现这个问题。至于为什么我也没有深究,我现在也不清楚这是他们的bug,还是以后就这样规范了。我能做的只有立马兼容,至于后边uni官方会不会继续变动也不清楚。
**tip**
--
- 不想使用upload 可把class 里的upload 删除
**issue**
--
有任何问题或者建议可以=> <a href="https://ask.dcloud.net.cn/question/74922" target="_blank">issue提交</a>,先给个五星好评QAQ!!
**作者想说**
--
- 主体代码3kb
- 目前该插件已经上项目,遇到任何问题请先检查自己的代码(排除新版本发布的情况)。最近新上了` typescript ` 版本,因为本人没使用过ts,所以写的不好的地方,还请见谅~
- 写代码很容易,为了让你们看懂写文档真的很lei 0.0
- 最近发现有插件与我雷同,当初接触uni-app 就发现插件市场虽然有封装的不错的request库,但是都没有对多全局配置做处理,都是通过修改源码的方式配置。我首先推出通过class类,并仿照axios的api实现request请求库,并起名‘仿axios封装request网络请求库,支持拦截器全局配置’。他们虽然修改了部分代码,但是功能与性能并没有优化,反而使代码很冗余。希望能推出新的功能,和性能更加强悍的请求库。
- 任何形式的‘参考’、‘借鉴’,请标明作者
```javascript
<a href="https://ext.dcloud.net.cn/plugin?id=392">luch-request</a>
```
- 关于问问题
1. 首先请善于利用搜索引擎,不管百度,还是Google,遇到问题请先自己尝试解决。自己尝试过无法解决,再问。
2. 不要问类似为什么我的xx无法使用这种问题。请仔细阅读文档,检查代码,或者说明运行环境,把相关代码贴至评论或者发送至我的邮箱,还可以点击上面的issue提交,在里面提问,可能我在里面已经回答了。
3. 我的代码如果真的出现bug,或者你有好的建议、需求,可以提issue,我看到后会立即解决
4. 不要问一些弱智问题!!!
- 如何问问题
1. 仔细阅读文档,检查代码
2. 说明运行环境,比如:app端 ios、android 版本号、手机机型、普遍现象还是个别现象(越详细越好)
3. 发出代码片段或者截图至邮箱(很重要)
4. 或者可以在上方的'issue提交' 里发出详细的问题描述
5. 以上都觉得解决不了你的问题,可以加QQ:`370306150`
**土豪赞赏**
--
<img src="https://img-cdn-qiniu.dcloud.net.cn/uploads/answer/20191014/0d9fff1e6a57a83024787224593b39c1.png" width="150">
####创作不易,五星好评你懂得!

316
plugins/request/request.js

@ -0,0 +1,316 @@
/**
* Request 1.0.2
* @Class Request
* @description luch-request 1.0.2 http请求插件
* @Author lu-ch
* @Date 2019-10-14
* @Email webwork.s@qq.com
* http://ext.dcloud.net.cn/plugin?id=392
*/
export default class Request {
config = {
baseUrl: '',
header: {
'content-type': 'application/json;charset=UTF-8'
},
method: 'GET',
dataType: 'json',
responseType: 'text'
}
static posUrl (url) { /* 判断url是否为绝对路径 */
return /(http|https):\/\/([\w.]+\/?)\S*/.test(url)
}
static addQueryString (params) {
let paramsData = ''
Object.keys(params).forEach(function (key) {
paramsData += key + '=' + params[key] + '&'
})
return paramsData.substring(0, paramsData.length - 1)
}
/**
* @property {Function} request 请求拦截器
* @property {Function} response 响应拦截器
* @type {{request: Request.interceptor.request, response: Request.interceptor.response}}
*/
interceptor = {
/**
* @param {Request~requestCallback} cb - 请求之前拦截,接收一个函数config, cancel=> {return config}第一个参数为全局config,第二个参数为函数调用则取消本次请求
*/
request: (cb) => {
if (cb) {
this.requestBeforeFun = cb
}
},
/**
* @param {Request~responseCallback} cb 响应拦截器对响应数据做点什么
* @param {Request~responseErrCallback} ecb 响应拦截器对响应错误做点什么
*/
response: (cb, ecb) => {
if (cb && ecb) {
this.requestComFun = cb
this.requestComFail = ecb
}
}
}
requestBeforeFun (config) {
return config
}
requestComFun (response) {
return response
}
requestComFail (response) {
return response
}
/**
* 自定义验证器如果返回true 则进入响应拦截器的响应成功函数(resolve)否则进入响应拦截器的响应错误函数(reject)
* @param { Number } statusCode - 请求响应体statusCode只读
* @return { Boolean } 如果为true, resolve, 否则 reject
*/
validateStatus (statusCode) {
return statusCode === 200
}
/**
* @Function
* @param {Request~setConfigCallback} f - 设置全局默认配置
*/
setConfig (f) {
this.config = f(this.config)
}
/**
* @Function
* @param {Object} options - 请求配置项
* @prop {String} options.url - 请求路径
* @prop {Object} options.data - 请求参数
* @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型
* @prop {Object} [options.dataType = config.dataType] - 如果设为 json会尝试对返回的数据做一次 JSON.parse
* @prop {Object} [options.header = config.header] - 请求header
* @prop {Object} [options.method = config.method] - 请求方法
* @returns {Promise<unknown>}
*/
async request (options = {}) {
options.baseUrl = this.config.baseUrl
options.dataType = options.dataType || this.config.dataType
options.responseType = options.responseType || this.config.responseType
options.url = options.url || ''
options.data = options.data || {}
options.params = options.params || {}
options.header = options.header || this.config.header
options.method = options.method || this.config.method
return new Promise((resolve, reject) => {
let next = true
let handleRe = {}
options.complete = (response) => {
response.config = handleRe
if (this.validateStatus(response.statusCode)) { // 成功
response = this.requestComFun(response)
resolve(response)
} else {
response = this.requestComFail(response)
reject(response)
}
}
const cancel = (t = 'handle cancel', config = options) => {
const err = {
errMsg: t,
config: config
}
reject(err)
next = false
}
handleRe = { ...this.requestBeforeFun(options, cancel) }
const _config = { ...handleRe }
if (!next) return
let mergeUrl = Request.posUrl(options.url) ? options.url : (options.baseUrl + options.url)
if (JSON.stringify(options.params) !== '{}') {
const paramsH = Request.addQueryString(options.params)
mergeUrl += mergeUrl.indexOf('?') === -1 ? `?${paramsH}` : `&${paramsH}`
}
_config.url = mergeUrl
uni.request(_config)
})
}
get (url, options = {}) {
return this.request({
url,
method: 'GET',
...options
})
}
post (url, data, options = {}) {
return this.request({
url,
data,
method: 'POST',
...options
})
}
// #ifndef MP-ALIPAY
put (url, data, options = {}) {
return this.request({
url,
data,
method: 'PUT',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
delete (url, data, options = {}) {
return this.request({
url,
data,
method: 'DELETE',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN
connect (url, data, options = {}) {
return this.request({
url,
data,
method: 'CONNECT',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
head (url, data, options = {}) {
return this.request({
url,
data,
method: 'HEAD',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
options (url, data, options = {}) {
return this.request({
url,
data,
method: 'OPTIONS',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN
trace (url, data, options = {}) {
return this.request({
url,
data,
method: 'TRACE',
...options
})
}
// #endif
upload (url, {
// #ifdef APP-PLUS
files,
// #endif
// #ifdef MP-ALIPAY
fileType,
// #endif
filePath,
name,
header,
formData
}) {
return new Promise((resolve, reject) => {
let next = true
let handleRe = {}
const pubConfig = {
baseUrl: this.config.baseUrl,
url,
// #ifdef APP-PLUS
files,
// #endif
// #ifdef MP-ALIPAY
fileType,
// #endif
filePath,
method: 'UPLOAD',
name,
header: header || this.config.header,
formData,
complete: (response) => {
response.config = handleRe
if (response.statusCode === 200) { // 成功
response = this.requestComFun(response)
resolve(response)
} else {
response = this.requestComFail(response)
reject(response)
}
}
}
const cancel = (t = 'handle cancel', config = pubConfig) => {
const err = {
errMsg: t,
config: config
}
reject(err)
next = false
}
handleRe = { ...this.requestBeforeFun(pubConfig, cancel) }
const _config = { ...handleRe }
if (!next) return
_config.url = Request.posUrl(url) ? url : (this.config.baseUrl + url)
uni.uploadFile(_config)
})
}
}
/**
* setConfig回调
* @return {Object} - 返回操作后的config
* @callback Request~setConfigCallback
* @param {Object} config - 全局默认config
*/
/**
* 请求拦截器回调
* @return {Object} - 返回操作后的config
* @callback Request~requestCallback
* @param {Object} config - 全局config
* @param {Function} [cancel] - 取消请求钩子调用会取消本次请求
*/
/**
* 响应拦截器回调
* @return {Object} - 返回操作后的response
* @callback Request~responseCallback
* @param {Object} response - 请求结果 response
*/
/**
* 响应错误拦截器回调
* @return {Object} - 返回操作后的response
* @callback Request~responseErrCallback
* @param {Object} response - 请求结果 response
*/

BIN
static/blue.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

BIN
static/red.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

12
store/index.js

@ -0,0 +1,12 @@
import Vue from 'vue';
import Vuex from 'vuex';
import user from './modules/user/index';
Vue.use(Vuex);
const store = new Vuex.Store({
// modules:sessionStorage.getItem('state') ? JSON.parse(sessionStorage.getItem('state')): { user, project }
modules: { user },
});
export default store;

9
store/modules/user/actions.js

@ -0,0 +1,9 @@
import { showLoading, hideLoading, showModal, showToast } from 'utils/ui';
import { mpLogin, signIn, ddLogin } from 'utils/user';
import { http } from 'plugins/request/index';
const actions = {
};
export default actions;

5
store/modules/user/index.js

@ -0,0 +1,5 @@
import state from './state';
import mutations from './mutations';
import actions from './actions.js';
export default { namespaced: true, state, actions, mutations };

26
store/modules/user/mutations.js

@ -0,0 +1,26 @@
const mutations = {
/**
* 设置存储token
* @param {object} state
* @param {string} token
*/
setToken(state, token) {
if (!token) return;
state.token = token;
uni.setStorageSync('token', token);
},
/**
* 设置user数据
* @param {object} state
* @param {object} user
*/
setUser(state, user) {
if (!user) return;
state.user = { ...user };
uni.setStorageSync('user', JSON.stringify(user));
},
};
export default mutations;

6
store/modules/user/state.js

@ -0,0 +1,6 @@
const state = {
token: '',
user: null
};
export default state;

133
uni.scss

@ -0,0 +1,133 @@
/* 颜色变量 */
/* Color 可以自定义相关配色 */
/* 标准色 */
$red: #F26A1B;
$orange: #f37b1d;
$yellow: #fbbd08;
$olive: #76AC96;
$green: #11A20D; // 健康打卡 绿
$cyan: #0897C7; // 按钮蓝
$blue: #6D99F2;
$darkBlue: #0076FF;
$purple: #677DBF; // 校园打卡
$mauve: #9c26b0;
$pink: #e03997;
$brown: #a5673f;
$grey: #CDCDCD;
$black: #333333;
$darkGray: #666666;
$gray: #808080;
$ghostWhite: #f1f1f1;
$white: #ffffff;
/* 浅色 */
$redLight: #fadbd9;
$orangeLight: #fde6d2;
$yellowLight: #fef2ce;
$oliveLight: #e8f4d9;
$greenLight: #d7f0db;
$cyanLight: #d2f1f0;
$blueLight: #cce6ff;
$purpleLight: #e1d7f0;
$mauveLight: #ebd4ef;
$pinkLight: #f9d7ea;
$brownLight: #ede1d9;
$greyLight: #F5f5f5;
/* 渐变色 */
$gradualRed: linear-gradient(45deg, #f43f3b, #ec008c);
$gradualOrange: linear-gradient(45deg, #ff9700, #ed1c24);
$gradualGreen: linear-gradient(45deg, #39b54a, #8dc63f);
$gradualPurple: linear-gradient(45deg, #ec008c, #5e00ff);
$gradualPink: linear-gradient(45deg, #ec008c, #6739b6);
$gradualBlue: linear-gradient(45deg, #0081ff, #1cbbb4);
/* 阴影透明色 */
$ShadowSize: 6rpx 6rpx 8rpx;
$redShadow: rgba(204, 69, 59, 0.2);
$orangeShadow: rgba(217, 109, 26, 0.2);
$yellowShadow: rgba(224, 170, 7, 0.2);
$oliveShadow: rgba(124, 173, 55, 0.2);
$greenShadow: rgba(48, 156, 63, 0.2);
$cyanShadow: rgba(28, 187, 180, 0.2);
$blueShadow: rgba(0, 102, 204, 0.2);
$purpleShadow: rgba(88, 48, 156, 0.2);
$mauveShadow: rgba(133, 33, 150, 0.2);
$pinkShadow: rgba(199, 50, 134, 0.2);
$brownShadow: rgba(140, 88, 53, 0.2);
$greyShadow: rgba(114, 130, 138, 0.2);
$grayShadow: rgba(114, 130, 138, 0.2);
$blackShadow: rgba(26, 26, 26, 0.2);
$whiteShadow: rgba(255, 255, 255, 0.2);
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 首页四个配色 */
$iconGreen: #62D4A6;
$iconCyan: #9376F5;
$iconBlue: #58C9D1;
$iconPurple: #CF7FE6;
/* 文字基本颜色 */
$uni-text-color: #101010; //基本色
$uni-text-color-inverse: #fff; //反色
$uni-text-color-grey: #747474; //辅助灰色如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable: #c0c0c0;
/* 背景颜色 */
$uni-bg-color: #ffffff;
$uni-bg-color-grey: #f8f8f8;
$uni-bg-color-hover: #f1f1f1; //点击状态颜色
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); //遮罩颜色
/* 边框颜色 */
$uni-border-color: #c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size: 20rpx;
$uni-font-size-sm: 24rpx;
$uni-font-size-base: 28rpx;
$uni-font-size-lg: 32rpx;
$uni-font-size-xl: 36rpx;
$uni-font-size-xxl: 44rpx;
$uni-font-size-sl: 80rpx;
$uni-font-size-xsl: 120rpx;
/* 图片尺寸 */
$uni-img-size-sm: 40rpx;
$uni-img-size-base: 52rpx;
$uni-img-size-lg: 80rpx;
/* Border Radius */
$uni-border-radius-sm: 4rpx;
$uni-border-radius-base: 6rpx;
$uni-border-radius-lg: 12rpx;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 10px;
$uni-spacing-row-base: 20rpx;
$uni-spacing-row-lg: 30rpx;
/* 垂直间距 */
$uni-spacing-col-sm: 8rpx;
$uni-spacing-col-base: 16rpx;
$uni-spacing-col-lg: 24rpx;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2c405a; // 文章标题颜色
$uni-font-size-title: 40rpx;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle: 36rpx;
$uni-color-paragraph: #3f536e; // 文章段落颜色
$uni-font-size-paragraph: 30rpx;

31
utils/ui.js

@ -0,0 +1,31 @@
/**
* 显示模态框
* @param {string} msg
* @param {boolean} showCancel
* @param {string} confirmText
*/
export const showModal = (msg, showCancel = false, confirmText = '知道了') =>
uni.showModal({
title: '提示',
content: msg,
showCancel,
confirmText,
confirmColor: '#1890ff',
});
/**
* 显示toast
* @param {string} msg
*/
export const showToast = msg =>
uni.showToast({
title: msg,
icon: 'none',
duration: 3000,
});
// 显示加载动画
export const showLoading = (msg = '努力加载中...') => uni.showLoading({ title: msg });
// 隐藏加载动画
export const hideLoading = () => uni.hideLoading();

132
utils/user.js

@ -0,0 +1,132 @@
import { SIGN_IN } from 'api/user';
import { ERR_CODE } from 'config/config.default';
import { http as axios } from 'plugins/request/index';
import { SIGN_IN_CLIENTS, SIGN_IN_TYPES } from 'config/config.user';
/**
* 保存token
* @param {string} token 服务端返回的token数据
*/
export const setToken = token => {
uni.setStorageSync('token', token);
};
/**
* 获取token
* @return {string} 本地保存的token
*/
export const getToken = () => uni.getStorageSync('token');
/**
* 提交登录信息
* @param {object} params 提交的参数
*/
export const signIn = params => {
return new Promise((resolve, reject) => {
axios
.post(SIGN_IN, params)
.then(res => {
const { success, code, data, msg } = res.data;
if (success && code === ERR_CODE) {
if (!data || !data.token) {
reject('服务端返回数据不正确');
} else {
resolve(data);
}
} else {
reject(`登录请求失败 ${msg}`);
}
})
.catch(err => {
reject(err);
});
});
};
// 微信登录
export const wxLogin = () => {
return new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success(response) {
if (response.code) {
const params = {
client: SIGN_IN_CLIENTS['mp'],
type: SIGN_IN_TYPES['mp'],
data: { identifier: response.code, credential: 'health' },
// redirect: 'https://test.tall.wiki/gateway/health/initMsg',
redirect: 'https://www.tall.wiki/gateway/health/initMsg',
};
resolve(params);
} else {
reject(response.errMsg);
}
},
fail() {
console.log('fail');
reject('微信登录失败');
},
});
});
};
// 企业微信登录
export const wxWorkLogin = () => {
return new Promise((resolve, reject) => {
wx.qy.login({
provider: 'weixin',
success(response) {
if (response.code) {
const params = {
client: SIGN_IN_CLIENTS['wx_work'],
type: SIGN_IN_TYPES['wx_work'],
data: { identifier: response.code, credential: 'health' },
// redirect: 'https://test.tall.wiki/gateway/health/initMsg',
redirect: 'https://www.tall.wiki/gateway/health/initMsg',
};
resolve(params);
} else {
reject(response.errMsg);
}
},
fail() {
console.log('fail');
reject('微信登录失败');
},
});
});
};
// 小程序登录
export const mpLogin = () => {
try {
const res = uni.getSystemInfoSync();
if (res.environment === 'wxwork') {
return wxWorkLogin();
} else {
return wxLogin();
}
} catch (err) {
console.log('err: ', err);
}
};
// 钉钉登录
export const ddLogin = () => {
console.log('dingtalk login.........');
try {
dd.getAuthCode({
success(res) {
console.log('res: ', res);
/*{
authCode: 'hYLK98jkf0m' // string authCode
}*/
},
fail: function(err) {
console.log('err: ', err);
},
});
} catch (error) {
console.log('error: ', error);
}
};

20
utils/util.js

@ -0,0 +1,20 @@
/**
* 格式化 querystring
* taskId=3&scene=1011 ->
* {taskId:3,scene:1011}
*/
export const formatQuery = str => {
if (!str) return;
const result = {};
if (str.includes('&')) {
const arr = str.split('&');
arr.forEach(item => {
const arr1 = item.split('=');
result[arr1[0]] = arr1[1];
});
} else {
const arr = str.split('=');
result[arr[0]] = arr[1];
}
return result;
};

21
vue.config.js

@ -0,0 +1,21 @@
const path = require('path');
const resolve = dir => path.join(__dirname, dir);
module.exports = {
lintOnSave: true,
configureWebpack: {
resolve: {
alias: {
'~': __dirname,
config: resolve('config'),
api: resolve('api'),
store: resolve('store'),
components: resolve('components'),
pages: resolve('pages'),
common: resolve('common'),
plugins: resolve('plugins'),
utils: resolve('utils'),
},
},
},
};

8
yarn.lock

@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
moment@^2.24.0:
version "2.24.0"
resolved "https://registry.npm.taobao.org/moment/download/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha1-DQVdU/UFKqZTyfbraLtdEr9cK1s=
Loading…
Cancel
Save