提交 0ffb571f authored 作者: pengxiaohui's avatar pengxiaohui

前端页面交互

上级 542f684e
......@@ -9,7 +9,10 @@
"rules": {
"no-new": "off",
"no-debugger": "off",
"space-before-function-paren": "off"
"space-before-function-paren": "off",
"semi": "off", // 语句末尾分号检测
"eol-last": "off", // 文件末尾必须有空行(以换行符结束)
"prefer-promise-reject-errors": "off" // promise reject必须是error对象
},
"globals": {
"CKEDITOR": false,
......
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const fs = require('fs')
const path = require('path')
const request = require('request')
......
{
"name": "@god/vue-client",
"version": "3.0.14",
"description": "适应于公司全系统的纯客户端开发模型",
"main": "index.js",
"scripts": {
"lint": "eslint --ext .js --ext .jsx --ext .vue src/",
"lint:fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/",
"dev": "cross-env NODE_ENV=development node build/getSSL.js && cross-env NODE_ENV=development SERVER_PORT=3002 webpack-dev-server --inline --progress --config build/webpack.client.conf.js",
"build:test": "npm run check:node && cross-env NODE_ENV=test webpack --progress --config build/webpack.client.conf.js && cross-env NODE_ENV=test node ./build/uploadAliyunCDN.js",
"build:pro": "npm run check:node && cross-env NODE_ENV=production webpack --progress --config build/webpack.client.conf.js && cross-env NODE_ENV=production node ./build/uploadAliyunCDN.js",
"check:node": "node build/checkNodeVersion.js"
},
"repository": {
"type": "git",
"url": ""
},
"keywords": [
"vue-client"
],
"author": "zhangyanxin",
"license": "ISC",
"eslintIgnore": [
"client-dist/",
"node_modules/",
"assets/font-icons/"
],
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-jsx": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@babel/runtime-corejs3": "^7.11.2",
"acorn": "^7.1.1",
"ali-oss": "^6.11.2",
"autoprefixer": "^9.8.6",
"babel-eslint": "^10.1.0",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^1.0.1",
"copy-webpack-plugin": "^5.1.2",
"css-loader": "^4.3.0",
"dart-sass": "^1.25.0",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.1",
"eslint-loader": "^3.0.4",
"eslint-plugin-html": "^6.1.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^6.2.2",
"file-loader": "^6.1.1",
"html-replace-webpack-plugin": "^2.5.6",
"html-webpack-plugin": "^4.5.0",
"mini-css-extract-plugin": "^0.9.0",
"postcss-loader": "^3.0.0",
"request": "^2.88.2",
"sass-loader": "^10.0.3",
"semver": "^1.1.4",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1",
"vconsole-webpack-plugin": "^1.5.2",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2"
},
"dependencies": {
"axios": "^0.21.1",
"core-js": "^3.8.3",
"cross-env": "^7.0.3",
"element-ui": "^2.15.0",
"js-cookie": "^2.2.1",
"lodash": "^4.17.20",
"vue": "^2.6.12",
"vue-i18n": "^8.22.4",
"vue-loader": "^15.9.6",
"vue-meta-info": "^0.1.7",
"vue-router": "^3.4.9",
"vue-template-compiler": "^2.6.12",
"vuex": "^3.6.0"
},
"engines": {
"node": ">=8.9"
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -6,7 +6,7 @@
"scripts": {
"lint": "eslint --ext .js --ext .jsx --ext .vue src/",
"lint:fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/",
"dev": "cross-env NODE_ENV=development node build/getSSL.js && cross-env NODE_ENV=development SERVER_PORT=3001 webpack-dev-server --inline --progress --config build/webpack.client.conf.js",
"dev": "cross-env NODE_ENV=development node build/getSSL.js && cross-env NODE_ENV=development SERVER_PORT=3002 webpack-dev-server --inline --progress --config build/webpack.client.conf.js",
"build:test": "npm run check:node && cross-env NODE_ENV=test webpack --progress --config build/webpack.client.conf.js && cross-env NODE_ENV=test node ./build/uploadAliyunCDN.js",
"build:pro": "npm run check:node && cross-env NODE_ENV=production webpack --progress --config build/webpack.client.conf.js && cross-env NODE_ENV=production node ./build/uploadAliyunCDN.js",
"check:node": "node build/checkNodeVersion.js"
......
......@@ -3,3 +3,11 @@
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
date() {
return {}
}
}
</script>
\ No newline at end of file
<template>
<div class="app-container">
<div class="app-container-hd" v-if="title">
<div class="app-container-hd__title">{{ title }}</div>
<div class="app-container-hd__right">
<slot name="header-right"></slot>
</div>
</div>
<div class="app-container-bd">
<slot></slot>
</div>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
name: 'AppContainer',
props: { title: { type: String } }
}
</script>
<style lang="scss">
.app-container {
display: flex;
flex-direction: column;
height: 100%;
padding: 30px;
background-color: #fff;
border-radius: 8px;
box-sizing: border-box;
}
.app-container-hd {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 8px;
margin-bottom: 20px;
border-bottom: 1px solid #ccc;
}
.app-container-hd__title {
font-size: 18px;
line-height: 1;
}
.app-container-bd {
flex: 1;
}
.app-container-ft {
background-color: #fff;
position: sticky;
bottom: 0;
padding-top: 30px;
border-top: 1px solid #ccc;
}
</style>
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
// import pathToRegexp from 'path-to-regexp'
export default {
data() {
return {
levelList: null
}
},
watch: {
$route() {
this.getBreadcrumb()
}
},
created() {
this.getBreadcrumb()
},
methods: {
getBreadcrumb() {
// only show routes with meta.title
const matched = this.$route.matched.filter(item => item.meta && item.meta.title)
console.log(matched)
}
}
}
</script>
<style scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 8px;
}
.no-redirect {
color: #97a8be;
cursor: text;
}
</style>
export default function (date) {
if (!date || typeof date !== 'object') {
return
}
const arr = []
const y = date.getFullYear();
const m = date.getMonth();
const curMDays = new Date(y, m + 1, 0).getDate(); // 获取当前月的天数
let preMDays = new Date(y, m, 0).getDate(); // 获取上一个月的天数
const curMonthFirstDay = new Date(y, m, 1).getDay(); // 获取当前月第一天周几
const curMonthLastDay = new Date(y, m + 1, 0).getDay();
for (let i = 0; i < curMonthFirstDay; i++) {
arr.unshift(new Date(y, m - 1, preMDays));
preMDays--
}
for (let i = 1; i <= curMDays; i++) {
arr.push(new Date(y, m, i));
}
for (let i = 1; 6 - i >= curMonthLastDay; i++) {
arr.push(new Date(y, m + 1, i));
}
return arr
}
\ No newline at end of file
<template>
<div class="calendar">
<div class="calendar-h">
<el-button icon="el-icon-arrow-left" size="mini" circle @click="btnClick('pre')"></el-button>
<span>{{curYearMonth}}</span>
<el-button icon="el-icon-arrow-right" size="mini" circle @click="btnClick('next')"></el-button>
</div>
<div class="week-bar">
<span v-for="item in weekList" :key="item">{{item}}</span>
</div>
<div class="calendar-day-list">
<ul>
<li v-for="item in dayList" :key="item.getTime()" @click="selectDay(item)">
<div :class="getDayCellClasses(item)">
<span>{{item.getTime() === toDay ? '今' : item.getDate()}}</span>
</div>
</li>
</ul>
</div>
</div>
</template>
<script>
import { dateFormat } from '@/utils/dateAlgs'
import getDays from './getDays'
export default {
props: {
defaultDate: {
validator: (value) => {
return typeof value === 'object' && (value === null || value instanceof Date)
}
},
type: {
type: String,
default: 'date' // 'date', 'daterange'
}
},
data() {
return {
weekList: ['周', '一', '二', '三', '四', '五', '六'],
dayList: [],
activeDate: [],
monthFirstDate: '',
rangeState: {
endDate: null,
selecting: false
},
rangeStartDate: null,
rangeEndDate: null,
rangeDates: [],
selectFirstDate: null,
selectLastDate: null
}
},
computed: {
toDay() {
const date = new Date();
const todayTime = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime()
return todayTime
},
curYearMonth() {
return dateFormat(this.monthFirstDate, '{y}年{m}月')
},
activeYear() {
return this.monthFirstDate.getFullYear()
},
activeMonth() {
return this.monthFirstDate.getMonth()
}
},
watch: {
defaultDate: {
handler: function(nv, ov) {
if (!nv) {
const _date = new Date()
this.activeDate = ''
this.setMonthFirstDate(_date)
} else {
if (Array.isArray(nv)) {
const d = nv[0]
this.setMonthFirstDate(d)
} else if (typeof nv === 'object' && this.type === 'date') {
this.setMonthFirstDate(nv)
this.activeDate = nv
}
}
this.initDays()
},
immediate: true
}
},
created() {
},
methods: {
getDayCellClasses(date) {
const classes = {
grey: date.getMonth() !== this.activeMonth,
today: date.getTime() === this.toDay,
current: this.activeDate && date.getTime() === this.activeDate.getTime(),
default: this.selectFirstDate && !this.rangeStartDate && date.getTime() === this.selectFirstDate.getTime(),
'start-date': this.rangeStartDate && date.getTime() === this.rangeStartDate.getTime(),
'end-date': this.rangeStartDate && date.getTime() === this.rangeEndDate.getTime(),
'is-range': this.rangeDates.length > 0 && this.rangeDates.some(d => date.getTime() === d.getTime())
}
return classes
},
setMonthFirstDate(date) {
this.monthFirstDate = new Date(date.getFullYear(), date.getMonth(), 1)
this.initDays()
},
initDays() {
this.dayList = getDays(this.monthFirstDate)
},
btnClick(type) {
const month = type === 'pre' ? this.activeMonth - 1 : this.activeMonth + 1;
this.monthFirstDate = new Date(this.activeYear, month, 1);
this.initDays()
},
selectDay(date) {
const isCurMonth = date.getMonth() === this.activeMonth
if (this.type === 'date') {
this.activeDate = date
!isCurMonth && this.setMonthFirstDate(date)
this.$emit('change', this.activeDate)
} else if (this.type === 'daterange') {
if (!this.rangeState.selecting) {
this.selectFirstDate = date
!isCurMonth && this.setMonthFirstDate(date)
this.selectLastDate = this.rangeStartDate = this.rangeEndDate = null
this.rangeDates = []
this.rangeState.selecting = true
} else {
// if (!isCurMonth) {
// if (date.getMonth() < this.activeDate) {
// this.selectLastDate = this.monthFirstDate // 将当前月第一天设为选中
// } else {
// // 将当前月最后一天设为选中
// const monthMaxDays = new Date(date.getFullYear(), date.getMonth(), 0).getDate()
// this.selectLastDate = new Date(date.getFullYear(), date.getMonth() - 1, monthMaxDays)
// }
// } else {
// this.selectLastDate = date
// }
this.selectLastDate = date
const isReverse = this.selectLastDate.getTime() < this.selectFirstDate.getTime()
this.rangeStartDate = isReverse ? this.selectLastDate : this.selectFirstDate
this.rangeEndDate = isReverse ? this.selectFirstDate : this.selectLastDate
this.getRangeDates()
this.rangeState.selecting = false
this.$emit('change', [this.rangeStartDate, this.rangeEndDate])
}
} else {
}
},
getRangeDates() {
const sDate = this.rangeStartDate
const eDate = this.rangeEndDate
const sMonth = sDate.getMonth()
const eMonth = eDate.getMonth()
const sDay = sDate.getDate()
const eDay = eDate.getDate()
let rangeDayCount = 0
const datesArr = []
if (sMonth !== eMonth) {
const startMonthMaxDays = new Date(sDate.getFullYear(), sMonth + 1, 0).getDate()
rangeDayCount = startMonthMaxDays - sDay + eDay - 1
} else {
rangeDayCount = eDay - sDay - 1
}
for (let i = 0, day = sDay; i < rangeDayCount; i++) {
++day
datesArr.push(new Date(sDate.getFullYear(), sMonth, day))
}
this.rangeDates = datesArr
},
handleMouseMove(it) {
// if (!this.rangeState.selecting) return;
// console.log(it)
}
}
}
</script>
<style scoped>
.calendar{
color:#606266;
width:224px;
}
.calendar-h{
padding:10px 0;
width:204px;
margin:0 auto;
display:flex;
}
.calendar-h span{
display:block;
line-height:28px;
flex:1;
text-align:center;
}
.week-bar{
display:flex;
width:224px;
margin:0 auto;
}
.week-bar>span{
width:32px;
text-align:center;
font-size:12px;
}
.calendar-day-list{
width:252px;
margin:0 auto;
}
.calendar-day-list ul{
display:flex;
flex-wrap:wrap;
padding-top:10px;
}
.calendar-day-list li{
width:32px;
height:32px;
text-align:center;
font-size:12px;
position:relative;
cursor:pointer;
}
.calendar-day-list li div{
padding:2px 0;
}
.calendar-day-list li span{
display:inline-block;
width:24px;
height:24px;
line-height:24px;
border-radius: 50%;
-moz-user-select:none; /*火狐*/
-webkit-user-select:none; /*webkit浏览器*/
-ms-user-select:none; /*IE10*/
-khtml-user-select:none; /*早期浏览器*/
user-select:none;
}
.calendar-day-list li .grey span{
color:#c0c4cc;
}
.calendar-day-list li span:hover{
background-color: #f5f7fa;
}
.calendar-day-list li .today span{
background: #ecf5ff;
color: #409eff;
}
.calendar-day-list li .is-range,
.calendar-day-list li .start-date,
.calendar-day-list li .end-date{
background-color: #f2f6fc;
}
.calendar-day-list li .start-date{
border-radius:12px 0 0 12px;
}
.calendar-day-list li .end-date{
border-radius:0 12px 12px 0;
}
.calendar-day-list li .start-date.end-date{
border-radius:12px;
}
.calendar-day-list li .current span,
.calendar-day-list li .start-date span,
.calendar-day-list li .end-date span,
.calendar-day-list li .default span{
background-color: #409eff;
color:#fff;
}
</style>
\ No newline at end of file
<template>
<div style="padding: 0 15px;" @click="toggleClick">
<svg
:class="{'is-active':isActive}"
class="hamburger"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
>
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
</svg>
</div>
</template>
<script>
export default {
name: 'Hamburger',
props: {
isActive: {
type: Boolean,
default: false
}
},
methods: {
toggleClick () {
this.$emit('toggleClick')
}
}
}
</script>
<style scoped>
.hamburger {
display: inline-block;
vertical-align: middle;
width: 20px;
height: 20px;
cursor: pointer;
}
.hamburger.is-active {
transform: rotate(180deg);
}
</style>
<template>
<div class="schedule-item" :style="{'min-width': minWidth}">
<template v-for="ev in schedule.events" >
<div :key="ev.startTime.getTime()" class="schedule-event" :style="{ height: getHeight(ev) + 'px', top: getTop(ev) + 'px' }">
<slot name="content" :data="ev">
<div class="schedule-ev-inner">
<h5>{{ev.title}}</h5>
<p>{{ev.startTime | timeFormat}}-{{ev.endTime | timeFormat}}</p>
</div>
</slot>
<div class="shadow" @click.stop="detailsClick(ev, $event)"></div>
</div>
</template>
<div v-if="nowBeforeDisabled" class="now-before-disabled" :style="{height: getNowBeforeDisabledHeight}"></div>
<div v-for="dot in timeDots" :key="dot.time" :style="{height: timeLineHeight + 'px'}" class="time-block" @click="newSchedule(dot, $event)"></div>
</div>
</template>
<script>
import { dateFormat, timeTrans, getTimeDots, getCurHalfHourDate, isSameDate } from '@/utils/dateAlgs'
export default {
props: {
options: {
type: Object,
require: true
},
schedule: {
type: Object,
require: true
},
date: {
}
},
data() {
return {
}
},
computed: {
nowBeforeDisabled() {
return this.options.nowBeforeDisabled || false
},
minWidth() {
return this.options.minWidth || '300px'
},
nowDateTime() {
const now = new Date()
return new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()
},
getNowBeforeDisabledHeight() {
// if (!this.nowBeforeDisabled) return 0
const date = timeTrans(this.date)
if (date.getTime() < this.nowDateTime) {
return 'calc(100% - 10px)'
} else if (date.getTime() === this.nowDateTime) {
const nowHalfHourTime = getCurHalfHourDate('start').getTime()
const timeRange = nowHalfHourTime - this.startDotTimestamp
const h = timeRange / ((60 * 1000 * this.options.step) / this.timeLineHeight)
return h + 'px'
} else {
return 0
}
},
startDotTimestamp() {
const date = this.date
const startHour = parseInt(this.options.start)
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), startHour, 0, 0).getTime()
},
timeDots() {
return getTimeDots(this.options.start, this.options.end, this.options.step)
},
timeLineHeight() {
return this.options.lineHeight
}
},
filters: {
timeFormat(val) {
return dateFormat(val, '{h}:{i}')
}
},
created() {
// console.log(this.schedule)
},
methods: {
getTop(ev) {
const timeRange = timeTrans(ev.startTime).getTime() - this.startDotTimestamp
const top = timeRange / ((60 * 1000 * this.options.step) / this.timeLineHeight)
ev.top = top + 1
return top + 1
},
getHeight(ev) {
const start = timeTrans(ev.startTime)
let end = timeTrans(ev.endTime)
if (!isSameDate(start, end)) {
end = new Date(start.getFullYear(), start.getMonth(), start.getDate(), 24, 0, 0)
}
const timeRange = end.getTime() - start.getTime()
const h = timeRange / ((60 * 1000 * this.options.step) / this.timeLineHeight)
ev.height = h - 2
return h - 2
},
getClass(ev) {
const h = ev.height || this.getHeight(ev)
return {
'schedule-event': true,
'status-start': ev.status === 1,
'status-notstarted': ev.status === 3,
small: h < 120 && h >= 40,
medium: h >= 120 && h < 200,
large: h >= 200
}
},
detailsClick(ev, e) {
const o = {
ev,
id: this.schedule.id,
x: e.clientX - e.offsetX + 'px',
y: e.clientY - e.offsetY + 'px',
width: e.target.clientWidth + 'px',
height: e.target.clientHeight + 'px'
}
this.$emit('details', o)
},
newSchedule(dot, e) {
if (!this.options.disabledNew) {
dot.x = e.clientX - e.offsetX + 'px'
dot.y = e.clientY - e.offsetY + 'px'
dot.width = e.target.clientWidth + 'px'
dot.height = e.target.clientHeight + 'px'
this.$emit('new', dot)
}
}
}
}
</script>
<style scoped>
.schedule-item {
position: relative;
font-size: 12px;
flex: 1;
/* min-width:300px; */
padding-bottom:10px;
}
.schedule-event {
position: absolute;
left: 0;
top: 1px;
z-index: 9;
width: 100%;
}
.shadow{
width:100%;
height:100%;
position: absolute;
left: 0;
top: 0;
}
.schedule-ev-inner{
background: #f8f8f8;
width: 100%;
height: 100%;
border-left: #d8d8d8 3px solid;
border-radius: 2px 4px 4px 2px;
padding: 0 5px;
overflow: hidden;
box-sizing: border-box;
}
h5, p{
line-height:18px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.time-block:last-child {
height:1px !important;
}
.now-before-disabled{
position:absolute;
left:0;
top:0;
right:0;
z-index:8;
background:rgba(222,222,222,.5)
}
</style>
\ No newline at end of file
<template>
<div class="schedule">
<div class="multi-schedule-header" ref="scheduleHeader">
<div v-for="(schedule, idx) in scheduleList" :key="idx" :style="{'min-width': minWidth}">{{schedule.title}}</div>
</div>
<el-scrollbar ref="scheduleScroll" style="height: calc(100% - 50px)">
<div class="schedule-container">
<div class="time-line" :style="{width: containerWidth}">
<div class="time-dot" :ref="'timeDot-'+ index" v-for="(dot, index) in timeDots" :key="dot.time" :style="{height: timeLineHeight + 'px'}">
<span class="time">{{dot.time}}</span><el-divider></el-divider>
</div>
</div>
<div class="time-now" v-if="isToday" :style="{top: nowTimeTop + 'px', width: containerWidth}">
<span class="time">{{Date.now() | timeFormat}}</span>
<el-divider></el-divider>
</div>
<div class="multi-schedule">
<schedule-item :options="options" :schedule="schedule" :date="date" v-for="(schedule, idx) in scheduleList" :key="idx" @details="detailsHandle" @new="newScheduleHandle">
<template #content="scope">
<slot name="content" :data= "scope.data"/>
</template>
<template #details-popper="scope">
<slot name="details-popper" :data= "scope.data"/>
</template>
</schedule-item>
</div>
</div>
</el-scrollbar>
<el-popover
ref="newPopover"
:placement="popoverPos"
width="440"
trigger="manual"
popper-class="schedule-popover"
v-model="newPopoverVisible">
<div class="new-schedule-popover-container" style="position:relative;">
<slot name="new-schedule" :data="newScheduleData">
<div style="padding:0 10px;">
<h5 style="line-height:22px;margin-bottom:8px;">
新建日程
</h5>
<p>
日程主题: <el-input style="width:354px;" v-model="createTitle" placeholder="输入日程主题" size="mini"></el-input>
</p>
<p style="margin:10px 0;">
开始时间: <el-date-picker style="width:200px;" v-model="startDate" type="date" placeholder="选择日期" size="mini"></el-date-picker>
<el-time-select style="width:150px;" v-model="startTime" :picker-options="{ step: '00:30' }" placeholder="选择时间" size="mini"></el-time-select>
</p>
<p>
开始时间: <el-date-picker style="width:200px;" v-model="endDate" type="date" placeholder="选择日期" size="mini"></el-date-picker>
<el-time-select style="width:150px;" v-model="endTime" :picker-options="{ step: '00:30' }" placeholder="选择时间" size="mini"></el-time-select>
</p>
<p style="margin:10px 0;text-align:center;">
<el-button type="primary" size="mini">创建日程</el-button>
</p>
</div>
</slot>
<i class="el-icon-close" style="position:absolute;right:0;top:0;" @click="closeNewPopover"></i>
</div>
</el-popover>
<div v-show="newPopoverVisible" class="newPopoverBtn" :style="newPopoverBtnStyle" v-popover:newPopover>
<p></p>
</div>
<el-popover
ref="detailsPopover"
:placement="popoverPos"
width="360"
trigger="manual"
popper-class="schedule-popover"
v-model="detailsPopoverVisible">
<slot name="details-popover" :data="detailsData">
<div class="schedule-ev-details">
<h5>{{detailsData.ev.title}}</h5>
<p>{{detailsData.ev.startTime | timeFormat}}-{{detailsData.ev.endTime | timeFormat}}</p>
</div>
</slot>
</el-popover>
<div v-show="detailsPopoverVisible" class="detailsPopoverBtn" :style="detailsPopoverBtnStyle" v-popover:detailsPopover @click="detailsPopoverVisible = !detailsPopoverVisible">
</div>
<div v-show="newPopoverVisible" class="schedule-mask" @touchmove.prevent @mousewheel.prevent></div>
<div v-show="detailsPopoverVisible" class="details-mask" @click="detailsPopoverVisible = !detailsPopoverVisible" @touchmove.prevent @mousewheel.prevent></div>
</div>
</template>
<script>
import ScheduleItem from './ScheduleItem'
import { dateFormat, getTimeDots } from '@/utils/dateAlgs'
export default {
props: {
options: {
type: Object,
require: true
},
data: {
require: true
},
date: {
validator: (value) => {
return typeof value === 'object' && (value === null || value instanceof Date)
}
}
},
data() {
return {
scheduleList: [],
createTitle: '',
startDate: '',
startTime: '',
endDate: '',
endTime: '',
newScheduleData: null,
newPopoverVisible: false,
newPopoverBtnStyle: {
left: 0,
top: 0,
width: 0
},
detailsData: {
ev: ''
},
detailsPopoverVisible: false,
detailsPopoverBtnStyle: {
left: 0,
top: 0,
width: 0,
height: 0
},
containerWidth: '100%'
}
},
components: { ScheduleItem },
computed: {
minWidth() {
return this.options.minWidth || '200px'
},
native() {
return !this.options.isScroll || false
},
timeLineHeight() {
return this.options.lineHeight
},
popoverPos() {
return this.options.popoverPos || 'right'
},
timeDots() {
return getTimeDots(this.options.start, this.options.end, this.options.step)
},
startDotTimestamp() {
const date = this.date;
const startHour = parseInt(this.options.start)
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), startHour, 0, 0).getTime()
},
isToday() {
const now = new Date();
const date = this.date
return date.getTime() === new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime() && this.startDotTimestamp < Date.now()
},
nowTimeTop() {
const now = Date.now();
const timeRange = now - this.startDotTimestamp
const top = timeRange * this.timeLineHeight / (this.options.step * 60 * 1000) + 5
return top
}
},
filters: {
timeFormat(val) {
return dateFormat(val, '{h}:{i}')
}
},
watch: {
data: {
handler: function(nv) {
if (!this.options.multi && !Array.isArray(nv)) {
this.scheduleList = [nv]
} else {
this.scheduleList = nv
}
},
immediate: true,
deep: true
},
'options.refreshPopoverState': {
handler: function(nv, ov) {
if (nv !== ov) {
this.$nextTick(() => {
this.newPopoverVisible && this.$refs.newPopover.updatePopper()
this.detailsPopoverVisible && this.$refs.detailsPopover.updatePopper()
})
}
},
deep: true
},
date: {
handler: function(nv) {
if (nv) {
if (this.options.defaultViewTime) {
this.$nextTick(() => {
// this.$refs['timeDot-15'][0].scrollIntoView({
// behavior: 'smooth', // 平滑过渡
// block: 'start' // 上边框与视窗顶部平齐。默认值
// });
this.$refs.scheduleScroll.wrap.scrollTop = this.options.step * 21
})
}
}
},
immediate: true,
deep: true
},
newPopoverVisible: {
handler: function(nv) {
if (nv) {
document.addEventListener('click', this.documentClick)
document.addEventListener('touchmove', this.moveHandle)
} else {
document.addEventListener('click', this.documentClick)
document.addEventListener('touchmove', this.moveHandle)
}
},
immediate: true,
deep: true
}
},
mounted() {
// console.log(this.containerWidth)
this.$refs.scheduleScroll.wrap.addEventListener('scroll', this.scrollHandle);
this.containerWidth = this.calcWidth()
},
beforeDestroy() {
this.$refs.scheduleScroll.wrap.removeEventListener('scroll', this.scrollHandle);
},
methods: {
calcWidth() {
let minWidth = this.minWidth;
if (minWidth && typeof minWidth === 'string') {
minWidth = minWidth.replace('px', '')
minWidth = parseFloat(minWidth)
}
const count = this.scheduleList.length;
const viewWidth = this.$refs.scheduleScroll.wrap.clientWidth
if (viewWidth < minWidth * count) {
return minWidth * count + 'px'
} else {
return 'calc(100% - 50px)'
}
},
documentClick() {
this.newPopoverVisible = false
},
scrollHandle() {
this.$refs.scheduleHeader.scrollLeft = this.$refs.scheduleScroll.wrap.scrollLeft
},
detailsHandle(val) {
console.log(val)
this.detailsData = val
this.detailsPopoverBtnStyle.left = val.x
this.detailsPopoverBtnStyle.top = val.y
this.detailsPopoverBtnStyle.width = val.width
this.detailsPopoverBtnStyle.height = val.height
setTimeout(() => {
this.$refs.detailsPopover.updatePopper()
this.detailsPopoverVisible = true
}, 100)
},
newScheduleHandle(val) {
// console.log(val)
this.newScheduleData = Object.assign(val, { date: this.date })
this.newPopoverBtnStyle.left = val.x
this.newPopoverBtnStyle.top = val.y
this.newPopoverBtnStyle.width = val.width
this.newPopoverBtnStyle.height = val.height
setTimeout(() => {
this.$refs.newPopover.updatePopper()
this.newPopoverVisible = true
}, 100)
},
closeNewPopover() {
this.newPopoverVisible = false
}
}
}
</script>
<style scoped>
.schedule{
position:relative;
}
.multi-schedule-header{
display:flex;
height:50px;
padding-left:50px;
overflow-x: auto;
-ms-overflow-style: none;
overflow: -moz-scrollbars-none;
}
.multi-schedule-header::-webkit-scrollbar {
display: none;
}
.multi-schedule-header>div{
flex:1;
min-width:300px;
line-height:50px;
text-align:center;
-moz-user-select:none; /*火狐*/
-webkit-user-select:none; /*webkit浏览器*/
-ms-user-select:none; /*IE10*/
-khtml-user-select:none; /*早期浏览器*/
user-select:none;
}
.schedule-container{
position:relative;
/* height:calc(100% - 50px); */
}
.multi-schedule{
display:flex;
padding:5px 0 0 50px;
/* width:calc(100% - 50px); */
}
.multi-schedule>.schedule{
flex:1;
}
.time-line{
position:absolute;
width:100%;
z-index:0;
left:0;
top:5px;
padding:0 0 10px 50px;
}
.time-dot{
position:relative;
}
.time{
width:50px;
line-height:20px;
font-size:14px;
color:#BEBEBE;
text-align:center;
position:absolute;
left:-50px;
top:-10px;
}
.time-dot ::v-deep.el-divider{
margin:0;
background-color:#E7E7E7;
}
.time-dot:last-child{
height:1px !important;
}
.time-now{
width:calc(100% - 50px);
padding-left:50px;
position:absolute;
left:0;
top:10px;
z-index:1;
}
.time-now .time{
left: 0;
}
.time-now span{
color:#FD1E1E;
}
.time-now ::v-deep.el-divider{
background-color:#FD0000;
margin:0
}
.newPopoverBtn{
position:fixed;
box-sizing:border-box;
padding:4px
}
.newPopoverBtn p{
color:#fff;
background:#52b837;
border-radius:4px;
height:calc(100%);
position:relative;
}
.newPopoverBtn p:after{
content:"无主题";
position:absolute;
left:50%;
top:50%;
transform:translate(-50%, -50%)
}
.detailsPopoverBtn{
position:fixed;
z-index:99;
}
.schedule-mask{
position:absolute;
left:0;
top:0;
right:0;
bottom:0;
z-index:10;
}
.details-mask{
position:absolute;
left:0;
top:0;
right:0;
bottom:0;
z-index:8;
}
.schedule ::v-deep.el-scrollbar__bar{
z-index:9;
}
</style>
\ No newline at end of file
<template>
<el-menu-item v-if="hasChild(navItem)" :index="navItem.path">
<i v-if="navItem.icon" :class="navItem.icon"></i>
<span slot="title">{{navItem.title}}</span>
</el-menu-item>
<el-submenu v-else ref="subMenu" popper-append-to-body :index="navItem.path">
<template slot="title">
<i v-if="navItem.icon" :class="navItem.icon"></i>
<span>{{navItem.title}}</span>
</template>
<menu-item
v-for="child in navItem.children"
:key="child.path"
:navItem="child"
class="nest-menu"
/>
</el-submenu>
</template>
<script>
export default {
name: 'MenuItem',
props: {
navItem: {
type: Object,
required: true
}
},
data() {
return {}
},
methods: {
hasChild (item) {
return !item.children || (item.children && item.children.length === 0)
}
}
}
</script>
\ No newline at end of file
<template>
<div>
<div :class="{ logo: true, 'logo-collapse': isCollapse }">
<img
v-if="!isCollapse"
src="http://zws-imgs-pub.oss-cn-beijing.aliyuncs.com/static/ezijing/logo/ezijing-logo.png"
/>
<img
v-if="isCollapse"
src="http://zws-imgs-pub.oss-cn-beijing.aliyuncs.com/static/ezijing/logo/favicon.svg"
/>
</div>
<el-scrollbar wrap-class="scrollbar-wrapper sidebar-container-wrap">
<el-menu
:default-active="defaultActive"
class="sidebar-menu"
background-color="#EFF0F0"
text-color="#484848"
active-text-color="#409EFF"
:collapse="isCollapse"
@select="handlleSelect"
>
<menu-item v-for="item in menu" :key="item.path" :navItem="item" />
</el-menu>
</el-scrollbar>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import MenuItem from './MenuItem'
export default {
data() {
return {
defaultActive: '/calendar',
menu: [
// {
// title: '创建直播',
// icon: 'el-icon-video-camera',
// path: '/create-live'
// },
{
title: '日历列表',
icon: 'el-icon-date',
path: '/calendar'
},
{
title: '系统管理',
icon: 'el-icon-setting',
path: '/system',
children: [
{
title: '角色管理',
icon: 'el-icon-s-check',
path: '/system/role'
},
{
title: '账号管理',
icon: 'el-icon-key',
path: '/system/account'
}
]
},
{
title: '个人设置',
icon: 'el-icon-user',
path: '/my'
}
]
}
},
computed: {
...mapGetters(['sidebar']),
isCollapse() {
return !this.sidebar.opened
}
},
components: { MenuItem },
watch: {
$route: {
handler: function(nv) {
if (nv && nv.path) this.defaultActive = nv.path
else this.defaultActive = ''
},
immediate: true
}
},
methods: {
handlleSelect(path) {
this.$router.push(path)
}
}
}
</script>
<style scoped>
.logo {
height: 48px;
width: 166px;
background: #fff;
padding-top: 4px;
transition: width 0.295s ease-in-out;
margin-left:10px;
}
.logo-collapse {
width: 64px;
}
.logo img {
width: 140px;
height: 46px;
}
.logo-collapse img {
width: 46px;
height: 46px;
}
</style>
<style>
.sidebar-container-wrap {
overflow-x: hidden !important;
}
.sidebar-container .el-scrollbar {
height: 100%;
}
.sidebar-container .el-menu {
border: none;
/* width:166px; */
}
.sidebar-container .el-scrollbar__view {
height: 100%;
}
.sidebar-container .el-scrollbar__view ul {
height: 100%;
}
</style>
<template>
<div class="app-aside">
<div class="inner">
<div class="user" v-if="showUser">
<div class="user-avatar"><img :src="avatar" /></div>
<div class="user-tools">
<span><router-link to="/account/password">修改密码</router-link></span>
<span @click="logout">退出登录</span>
</div>
</div>
<el-menu class="nav" :unique-opened="true" :default-active="activeLink">
<template v-for="item in menus">
<el-submenu :index="item.title" :key="item.title" v-if="item.children">
<template #title>
<i class="iconfont" :class="item.icon"></i><span>{{ item.title }}</span>
</template>
<el-menu-item
:index="item.path"
:key="item.title"
v-for="item in item.children"
@click="handleClick(item.path, item)"
>
<template #title>
<template v-if="item.href">
<a :href="item.href" target="_blank">{{ item.title }}</a>
</template>
<template v-else>{{ item.title }}</template>
</template>
</el-menu-item>
</el-submenu>
<el-menu-item :index="item.path" :key="item.title" @click="handleClick(item.path, item)" v-else>
<i class="iconfont" :class="item.icon"></i>
<span slot="title">{{ item.title }}</span>
</el-menu-item>
</template>
</el-menu>
</div>
</div>
</template>
<script>
import defaultAvatar from '@/assets/images/avatar.png'
export default {
name: 'AppAside',
props: {
showUser: { type: Boolean, default: false }
},
data() {
return {
activeLink: '/calendar',
menus: [
{ title: '直播日历', path: '/calendar' },
{ title: '系统管理', path: '/system' },
{ title: '个人设置', path: '/my' }
]
}
},
computed: {
user() {
return this.$store.state.user
},
avatar() {
return this.user.avatar || defaultAvatar
}
},
watch: {
$route: {
immediate: true,
handler(to, from) {
this.activeLink = to.path
}
}
},
methods: {
genClasses(data) {
const isActive = this.$route.fullPath.includes(data.path)
return { 'is-active': isActive }
},
// 退出登录
logout() {
this.$store.dispatch('logout').then(() => {
window.location.href = `${webConf.others.loginUrl}?rd=${encodeURIComponent(window.location.href)}`
})
},
handleClick(path, item) {
if (item.href) return
path && this.$router.push(path)
}
}
}
</script>
<style lang="scss">
.app-aside {
width: 200px;
background-color: #fff;
.inner {
position: sticky;
top: 0;
}
.user {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 20px;
&:hover {
background-color: #fff4f7;
}
}
.user-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.user-tools {
padding: 10px 0;
span {
color: #999;
padding: 0 10px;
cursor: pointer;
}
}
.nav {
border: 0;
padding: 30px 0;
color: #ccc;
.iconfont {
display: inline-block;
width: 30px;
font-size: 16px;
color: currentColor;
}
.el-submenu__title:hover {
color: #c01540;
}
.is-active {
color: #c01540;
}
.is-active .el-submenu__title {
background: #fff4f7;
font-weight: bold;
color: #c01540;
}
.el-submenu__title {
height: 50px;
line-height: 50px;
padding-left: 25px !important;
}
.el-menu-item:hover,
.el-menu-item:focus {
color: #c01540;
background: transparent;
}
.el-submenu .el-menu-item {
height: 36px;
line-height: 36px;
padding-left: 55px !important;
}
}
}
</style>
<template>
<div class="app-header">
<div class="logo"><img src="https://zws-imgs-pub.ezijing.com/pc/base/logo.svg" height="40" /></div>
<!-- <div class="title">
<router-link to="/">{{ title }}</router-link>
</div> -->
<div class="tool">
<!-- <nav class="nav">
<router-link to="/">首页</router-link>
<router-link to="/my">我的</router-link>
<router-link to="/messages">通知</router-link>
</nav> -->
<div class="user">
<img :src="avatar" class="user-avatar" />
<span class="user-name" v-if="user.realname || user.nickname">{{ user.realname || user.nickname }}</span>
</div>
<div class="logout" @click="logout">退出登录</div>
<hamburger
id="hamburger-container"
:is-active="sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<div class="right-menu">
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<img :src="avatar" class="user-avatar" />
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<el-dropdown-item divided @click.native="logout">
<span style="display: block">Log Out</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<el-button type="primary" icon="el-icon-plus" size="mini" style="margin:12px 20px 0 0;float:right;" @click="toMeeting">创建会议</el-button>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import Hamburger from '@/components/Hamburger'
import Breadcrumb from '@/components/Breadcrumb'
import defaultAvatar from '@/assets/images/avatar.png'
export default {
name: 'AppHeader',
data() {
return {
title: 'Live'
sidebarFold: false
}
},
computed: {
...mapGetters(['sidebar']),
user() {
return this.$store.state.user
},
......@@ -36,68 +42,96 @@ export default {
return this.user.avatar || defaultAvatar
}
},
components: { Hamburger, Breadcrumb },
methods: {
// 退出登录
logout() {
this.$store.dispatch('logout').then(() => {
window.location.href = `${webConf.others.loginUrl}?rd=${encodeURIComponent(window.location.href)}`
})
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
toMeeting() {
this.$router.push('/meeting')
},
async logout() {
await this.$store.dispatch('user/logout')
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
}
}
}
</script>
<style lang="scss">
<style lang="scss" scoped>
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 60px;
padding: 0 20px;
background-color: #fff;
height: 52px;
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
.tool {
display: flex;
align-items: center;
}
.logo {
height: 40px;
}
.title {
flex: 1;
font-size: 24px;
font-weight: 600;
color: #222;
}
.hamburger-container {
line-height: 52px;
height: 100%;
float: left;
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
.nav {
margin-left: 20px;
font-size: 18px;
color: #333;
a {
padding: 0 20px;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
.user {
height: 80px;
padding: 0 10px;
display: flex;
align-items: center;
cursor: pointer;
.breadcrumb-container {
float: left;
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
object-fit: cover;
}
.user-name {
padding: 0 10px;
}
.logout {
cursor: pointer;
.right-menu {
float: right;
height: 100%;
line-height: 52px;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
}
.avatar-container {
margin-right: 30px;
.avatar-wrapper {
margin-top: 0;
position: relative;
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 22px;
font-size: 12px;
}
}
}
}
}
</style>
</style>
\ No newline at end of file
<template>
<div class="app-layout">
<app-header />
<div class="app-layout-bd">
<app-aside v-bind="$attrs" v-if="showAside" />
<div :class="{ 'app-wrapper': true, 'sidebar-collapse': isCollapse }">
<Sidebar class="sidebar-container" />
<div class="main-container">
<div class="fixed-haeder">
<app-header />
</div>
<app-main />
</div>
</div>
</template>
<script>
import AppHeader from './header'
import AppAside from './aside'
import AppMain from './main'
import { mapGetters } from 'vuex'
import Sidebar from './Sidebar'
import AppHeader from './Header'
import AppMain from './Main'
export default {
components: { AppHeader, AppAside, AppMain },
props: { showAside: { type: Boolean, default: true } }
data() {
return {}
},
computed: {
...mapGetters(['sidebar']),
isCollapse() {
return !this.sidebar.opened
}
},
components: { Sidebar, AppHeader, AppMain }
}
</script>
<style lang="scss">
.app-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
<style scoped>
.app-wrapper {
position: relative;
height: 100%;
width: 100%;
}
.app-layout-bd {
flex: 1;
display: flex;
.app-main {
flex: 1;
}
.sidebar-container {
/* transition: width 0.28s; */
width: 166px;
height: 100%;
position: fixed;
font-size: 0px;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
}
.sidebar-collapse .sidebar-container {
width: 64px;
}
.main-container {
min-height: 100%;
-webkit-transition: margin-left 0.28s;
transition: margin-left 0.28s;
margin-left: 166px;
position: relative;
}
.sidebar-collapse .main-container {
margin-left: 64px;
}
</style>
<template>
<div class="app-main">
<router-view :key="$route.fullPath" />
</div>
<section class="app-main">
<transition name="fade-transform" mode="out-in">
<router-view :key="key" />
</transition>
</section>
</template>
<script>
export default {}
export default {
name: 'AppMain',
computed: {
key () {
return this.$route.path
}
}
}
</script>
<style lang="scss">
<style scoped>
.app-main {
padding: 20px;
height: calc(100vh - 62px);
width: 100%;
position: relative;
overflow: hidden;
}
.fixed-header+.app-main {
padding-top: 50px;
}
</style>
<template>
<div class="details" v-if="ev">
<h5>会议详情</h5>
<p>
<i class="el-icon-tickets"></i>
<span>{{ev.title}}</span>
</p>
<p>
<i class="el-icon-time"></i>
<span v-if="ev.startTime.getDate() === ev.endTime.getDate()">{{ev.startTime | timeFormat('{m}月{d}日')}} {{ev.startTime | timeFormat('周{a}')}} {{ev.startTime | timeFormat}}-{{ev.endTime | timeFormat}}</span>
<span v-else>{{ev.title}}</span>
</p>
<p>
<i class="el-icon-s-custom"></i>
<span>{{ev.creator}}</span>
</p>
</div>
</template>
<script>
import { dateFormat } from '@/utils/dateAlgs'
export default {
props: {
data: {
}
},
filters: {
timeFormat(val, fmt) {
fmt = fmt || '{h}:{i}'
return dateFormat(val, fmt)
}
},
data() {
return {}
},
computed: {
ev() {
return this.data.ev
}
},
created() {
// console.log(this.data)
}
}
</script>
<style scoped>
h5{
font-size:14px;
line-height:20px;
color:#409eff;
margin-bottom:10px;
}
p{
display:flex;
line-height:22px;
}
p>i{
width:20px;
padding-top:4px;
}
p>span{
flex:1;
}
</style>
\ No newline at end of file
<template>
<div class="new">
<h5>新建会议</h5>
<el-form ref="ruleForm" :model="form" :rules="rules" label-width="84px">
<el-form-item label="会议主题" prop="theme">
<el-input v-model="form.theme" size="small" />
</el-form-item>
<el-form-item label="开始时间" required>
<el-col :span="11">
<el-form-item prop="startDate">
<el-date-picker v-model="form.startDate" type="date" placeholder="选择日期" style="width: 100%;" size="small" :clearable="timerClear" :editable="timerClear" :picker-options="startDateOptions" @change="startDateChange" />
</el-form-item>
</el-col>
<el-col :span="2" class="line">-</el-col>
<el-col :span="11">
<el-form-item prop="startTime">
<el-time-select v-model="form.startTime" style="width:100%;" :picker-options="startTimeOptions" placeholder="选择时间" size="small" :clearable="timerClear" :editable="timerClear" @change="startTimeChange" />
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="结束时间" required>
<el-col :span="11">
<el-form-item prop="endDate">
<el-date-picker v-model="form.endDate" type="date" placeholder="选择日期" style="width: 100%;" size="small" :clearable="timerClear" :editable="timerClear" :picker-options="endDateOptions" @change="endDateChange" />
</el-form-item>
</el-col>
<el-col :span="2" class="line">-</el-col>
<el-col :span="11">
<el-form-item prop="endTime">
<el-time-select v-model="form.endTime" style="width:100%;" :picker-options="endTimeOptions" placeholder="选择时间" size="small" :clearable="timerClear" :editable="timerClear"/>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="会议直播" style="margin-bottom:12px;">
<el-checkbox v-model="form.isLive" @change="updatePopper">开启会议直播(用于分享给观看直播的用户)</el-checkbox>
</el-form-item>
<el-form-item v-if="form.isLive" label="直播主题" :required="form.isLive" prop="liveTheme">
<el-input v-model="form.liveTheme" size="small" />
</el-form-item>
<el-form-item v-if="form.isLive" label="直播简介" :required="form.isLive" prop="liveDesc">
<el-input type="textarea" v-model="form.liveDesc"></el-input>
</el-form-item>
<el-form-item style="text-align:center;">
<el-button @click="more" size="mini">更多选项</el-button>
<el-button type="primary" @click="submitForm('ruleForm')" size="mini">立即创建</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { timeTrans, dateFormat, getTimestampYMD, getCurHalfHour } from '@/utils/dateAlgs'
const DAY_TIMESTAMP = 8.64e7
export default {
props: {
data: {
require: true
},
date: {
validator: (value) => {
return typeof value === 'object' && (value === null || value instanceof Date)
}
}
},
data() {
const startDateChecked = (rule, value, callback) => {
if (value) {
const timestamp = Date.parse(value);
if (timestamp < Date.now() - DAY_TIMESTAMP) {
callback(new Error('开始时间必须大于当前时间'));
}
}
}
const endDateChecked = (rule, value, callback) => {
if (value) {
if (this.endTimestamp <= this.startTimestamp) {
callback(new Error('结束时间必须大于开始时间'));
} else if (this.endTimestamp > this.startTimestamp + DAY_TIMESTAMP) {
callback(new Error('会议时间不能超过24小时'));
}
}
}
// 当开启会议直播时,启用验证,处理callback
const validateErrMsg = (rule, value, callback) => {
if (this.form.isLive) {
const errMsg = rule.field === 'liveTheme' ? '请输入直播主题' : '请输入直播简介'
callback(new Error(errMsg));
} else {
callback();
}
};
return {
timerClear: true,
form: {
theme: '',
startDate: this.date,
startTime: getCurHalfHour('start'),
endDate: this.date,
endTime: getCurHalfHour('end'),
timezone: 'beijing',
periodic: false,
repeatRate: 'everyday',
endType: 'endOneday',
periodicEndDate: '',
periodicTimes: 7,
isSecret: false,
secret: '',
openWaitingRoom: false,
joinAdvance: false,
mute: false,
recordVideo: false,
isLive: false,
liveTheme: '',
liveDesc: '',
isLiveSecret: false,
permitComment: false
},
rules: {
theme: [{ required: true, message: '请填写会议主题', trigger: 'blur' }],
startDate: [
{ type: 'date', required: true, message: '请选择开始日期', trigger: 'change' },
{ type: 'date', validator: startDateChecked, trigger: 'change' }
],
startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
endDate: [{ type: 'date', required: true, message: '请选择结束日期', trigger: 'change' }],
endTime: [
{ required: true, message: '请选择结束时间', trigger: 'change' },
{ validator: endDateChecked, trigger: 'change' }
],
liveTheme: [{ validator: validateErrMsg }],
liveDesc: [{ validator: validateErrMsg }]
},
startDateOptions: {
disabledDate(time) {
return time.getTime() < Date.now() - DAY_TIMESTAMP;
}
},
startTimeOptions: {
start: getCurHalfHour('start'),
step: '00:30',
end: '23:30'
},
endTimeOptions: {
start: getCurHalfHour('end'),
step: '00:30',
end: '23:30'
}
}
},
computed: {
startTimestamp() {
return this.getFullDateTime(this.form.startDate, this.form.startTime).getTime()
},
endTimestamp() {
return this.getFullDateTime(this.form.endDate, this.form.endTime).getTime()
},
endDateOptions() {
const _startDate = this.form.startDate;
return {
disabledDate(time) {
return getTimestampYMD(time) < getTimestampYMD(_startDate) || getTimestampYMD(time) > getTimestampYMD(_startDate) + DAY_TIMESTAMP;
}
}
}
},
watch: {
data: {
handler: function(nv) {
if (nv) {
this.form.startDate = nv.date
this.startDateChange(nv.date)
this.form.startTime = nv.time
this.startTimeChange(nv.time)
}
},
immediate: true,
deep: true
}
},
methods: {
updatePopper() {
this.$emit('refreshPopover', Date.now())
},
startDateChange(val) {
this.form.endDate = val;
const _startDate = timeTrans(this.form.startDate);
if (Date.parse(_startDate) > Date.now()) {
this.startTimeOptions = {
start: '00:00',
step: '00:30',
end: '23:30'
}
} else {
this.startTimeOptions = {
start: getCurHalfHour('start'),
step: '00:30',
end: '23:30'
}
}
},
startTimeChange(val) {
const { startDate, endDate } = this.form
const fullDate = this.getFullDateTime(startDate, val)
if (this.isSameDate(startDate, endDate)) {
const startTime = getCurHalfHour('end', fullDate)
this.form.endTime = startTime
this.endTimeOptions = {
start: startTime,
step: '00:30',
end: '23:30'
}
} else {
const endTime = getCurHalfHour('start', fullDate)
this.form.endTime = endTime;
this.endTimeOptions = {
start: '00:00',
step: '00:30',
end: endTime
}
}
},
endDateChange() {
console.log(12344)
this.startTimeChange(this.form.startTime)
},
getFullDateTime(date, timeStr) {
const hmArr = timeStr.split(':');
const h = parseInt(hmArr[0]);
const s = parseInt(hmArr[1]);
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), h, s, 0)
},
getCurTimeHalfHour(type, date) {
const day = date || new Date();
let h = day.getHours()
const m = day.getMinutes();
let time = ''
if (m === 0) {
h < 10 && (h = '0' + h)
time = type === 'start' ? h + ':00' : h + ':30'
} else if (m <= 30) {
const _h = h + 1 < 10 ? '0' + (h + 1) : h + 1;
h < 10 && (h = '0' + h)
time = type === 'start' ? h + ':30' : _h + ':00'
} else {
const _h = h + 1 < 10 ? '0' + (h + 1) : h + 1;
time = type === 'start' ? _h + ':00' : _h + ':30'
}
return time
},
isSameDate(start, end) {
return dateFormat(start, '{y}-{m}-{d}') === dateFormat(end, '{y}-{m}-{d}')
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
console.log(valid);
}
});
},
more() {
this.$router.push({ path: '/meeting', query: { type: '1' } })
}
}
}
</script>
<style scoped>
.new{
padding:0 10px;
}
h5{
font-size:16px;
line-height:20px;
color:#52b837;
margin-bottom:10px;
text-indent:15px;
}
.line{
text-align:center;
}
</style>
\ No newline at end of file
<template>
<div :class="getClass(ev)">
<div class="status">
{{ev.status | statusFilter}}
<span class="time-range">{{ev.startTime | timeFormat}}-{{ev.endTime | timeFormat}}</span>
</div>
<div class="event-cont">
<div class="title">
<span>{{ev.title}}</span>
</div>
<div class="creator">
<span><i class="el-icon-s-custom"></i>:{{ev.creator}}</span>
</div>
</div>
</div>
</template>
<script>
import { dateFormat, timeTrans } from '@/utils/dateAlgs'
export default {
props: {
ev: {
require: true
}
},
filters: {
timeFormat(val) {
return dateFormat(val, '{h}:{i}')
},
statusFilter(val) {
let result = ''
switch (val) {
case 1:
result = '进行中'
break
case 2:
result = '已完成'
break
case 3:
result = '未开始'
break
}
return result
}
},
methods: {
getHeight(ev) {
const timeRange = timeTrans(ev.endTime).getTime() - timeTrans(ev.startTime).getTime()
const h = timeRange / ((60 * 1000 * this.options.step) / 40)
ev.height = h - 2
return h - 2
},
getClass(ev) {
const h = ev.height || this.getHeight(ev)
return {
'schedule-ev-inner test': true,
'status-start': ev.status === 1,
'status-notstarted': ev.status === 3,
small: h < 120 && h >= 40,
medium: h >= 120 && h < 200,
large: h >= 200
}
}
}
}
</script>
<style scoped>
.schedule-ev-inner{
background: #f8f8f8;
width: 100%;
height: 100%;
border-left: #d8d8d8 3px solid;
border-radius: 2px 4px 4px 2px;
padding: 0 5px;
overflow: hidden;
box-sizing: border-box;
}
.status-start {
border-color: #409eff;
background: #ebf5ff;
}
.status-notstarted {
border-color: #52b837;
background: #f3fff0;
}
.status {
line-height: 18px;
padding-top: 2px;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-content: center;
}
.status-start .status {
color: #409eff;
}
.status-notstarted .status {
color: #52b837;
}
.status-end .status {
color: #bfbfbf;
}
.time-range {
color: #bebebe;
transform: scale(0.8);
}
.event-cont {
line-height: 18px;
display: flex;
height: calc(100% - 18px);
}
.title {
flex-grow: 1;
display: flex;
align-items: center;
}
.title span {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1; /*想省略几行就写几*/
-webkit-box-orient: vertical;
}
.creator {
display: flex;
align-items: center;
white-space: nowrap;
}
.small .status {
line-height: 30px;
}
.small .event-cont {
height: calc(100% - 32px);
}
.small .title span {
-webkit-line-clamp: 3;
}
.medium .status {
line-height: 38px;
}
.medium .event-cont {
display: block;
height: calc(100% - 40px);
}
.medium .title {
height: calc(100% - 32px);
}
.medium .title span {
-webkit-line-clamp: 5;
}
.medium .creator {
line-height: 32px;
}
.large .status {
line-height: 46px;
}
.large .event-cont {
display: block;
height: calc(100% - 48px);
}
.large .title {
height: calc(100% - 36px);
}
.large .title span {
-webkit-line-clamp: 9;
}
.large .creator {
line-height: 36px;
}
</style>
\ No newline at end of file
<template>
<div class="day">
<schedule :options="options" :data="scheduleList" :date="defaultDate" @eventClick="edit" @addEvent="add">
<template slot="content" slot-scope="scope">
<schedule-content :ev="scope.data"></schedule-content>
</template>
<template slot="details-popover" slot-scope="scope">
<details-popover :data="scope.data"/>
</template>
<template slot="new-schedule" slot-scope="scope">
<new-popover :data="scope.data" :date="defaultDate" @refreshPopover="refreshPop"/>
</template>
</schedule>
</div>
</template>
<script>
import Schedule from '@/components/Schedule'
import ScheduleContent from './components/ScheduleContent'
import DetailsPopover from './components/DetailsPopover'
import NewPopover from './components/NewPopover'
export default {
props: {
defaultDate: {
require: true,
validator: (value) => {
return typeof value === 'object' && (value === null || value instanceof Date)
}
}
},
data() {
return {
options: {
multi: false, // 是否为多日程
start: 0, // init number 0-23
end: 24, // init number 1-24且end>start
step: 30, // 只接收15, 30, 60三种步伐
lineHeight: 40, // init number 时间线间的高度
defaultViewTime: '8:00', // 滚动条滚动至需显示的起始时间,需要与step对应
nowBeforeDisabled: true,
minWidth: '200px',
refreshPopoverState: 0
},
schedule: {
id: 'live-1',
events: [
{
status: 2,
title: '开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会',
startTime: new Date(2021, 3, 13, 8, 30, 0),
endTime: new Date(2021, 3, 13, 9, 0, 0),
creator: '张三丰'
},
{
status: 1,
title: '开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会',
startTime: new Date(2021, 3, 13, 11, 0, 0),
endTime: new Date(2021, 3, 13, 13, 0, 0),
creator: '张三丰'
},
{
status: 3,
title: '开个周会开个周会开个周会',
startTime: new Date(2021, 3, 13, 14, 30, 0),
endTime: new Date(2021, 3, 13, 15, 30, 0),
creator: '张三丰'
}
]
},
scheduleList: [
{
id: 'live-1',
title: '会议室1',
events: [
{
status: 2,
title: '开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会',
startTime: new Date(2021, 3, 13, 8, 30, 0),
endTime: new Date(2021, 3, 13, 9, 0, 0),
creator: '张三丰'
},
{
status: 1,
title: '开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会',
startTime: new Date(2021, 3, 13, 11, 0, 0),
endTime: new Date(2021, 3, 13, 13, 0, 0),
creator: '张三丰'
},
{
status: 3,
title: '开个周会开个周会开个周会',
startTime: new Date(2021, 3, 13, 20, 30, 0),
endTime: new Date(2021, 3, 14, 1, 30, 0),
creator: '张三丰'
}
]
},
{
id: 'live-2',
title: '会议室2',
events: [
{
status: 2,
title: '开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会',
startTime: new Date(2021, 3, 13, 9, 30, 0),
endTime: new Date(2021, 3, 13, 10, 0, 0),
creator: '张三丰'
}
]
},
{
id: 'live-3',
title: '会议室3',
events: [
{
status: 3,
title: '开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会',
startTime: new Date(2021, 3, 13, 16, 30, 0),
endTime: new Date(2021, 3, 13, 18, 0, 0),
creator: '张三丰'
}
]
},
{
id: 'live-4',
title: '会议室4',
events: [
{
status: 3,
title: '开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会',
startTime: new Date(2021, 3, 13, 19, 30, 0),
endTime: new Date(2021, 3, 13, 20, 30, 0),
creator: '张三丰'
}
]
},
{
id: 'live-5',
title: '会议室5',
events: [
{
status: 3,
title: '开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会开个周会',
startTime: new Date(2021, 3, 8, 21, 0, 0),
endTime: new Date(2021, 3, 8, 22, 0, 0),
creator: '张三丰'
}
]
}
],
list: [
{
id: 'live-1',
title: '会议室1',
events: []
},
{
id: 'live-2',
title: '会议室2',
events: []
}
]
}
},
components: { Schedule, ScheduleContent, DetailsPopover, NewPopover },
watch: {
defaultDate: {
handler: function(nv, ov) {
if (nv) {
// console.log(nv)
}
},
immediate: true
}
},
methods: {
refreshPop(val) {
this.options.refreshPopoverState = val
},
edit(val) {
console.log(val)
},
add(val) {
console.log(val)
}
}
}
</script>
<style scoped>
.schedule{
height:100%;
}
.day .schedule ::v-deep.el-scrollbar__wrap{
overflow-x: hidden;
}
</style>
\ No newline at end of file
<template>
<app-container title="直播日历"></app-container>
<div class="calendar-main">
<h5>直播日历</h5>
<div class="calendar-inner">
<div class="left-calendar">
<div class="top">
<el-input placeholder="搜索会议" suffix-icon="el-icon-search" size="mini" @click.native="goSearch" readonly></el-input>
</div>
<calendar :type="dateType" style="margin-left:-10px;" :defaultDate="defaultDate" @change="calendarChange" />
<div class="bottom">
<div class="title">
直播日历 <el-checkbox style="margin-left:20px;" v-model="mineFilter">我的</el-checkbox>
</div>
<div class="status-filter">
<el-radio-group v-model="statusFilter">
<el-radio :label="3">进行中</el-radio>
<el-radio :label="6">未开始</el-radio>
<el-radio :label="9">已完成</el-radio>
</el-radio-group>
</div>
</div>
</div>
<div class="right-container">
<div class="top">
<div class="title">2021年04月</div>
<el-radio-group v-model="tabActive" size="mini">
<el-radio-button label="list">列表</el-radio-button>
<el-radio-button label="day"></el-radio-button>
<!-- <el-radio-button label="week"></el-radio-button>
<el-radio-button label="month"></el-radio-button> -->
</el-radio-group>
</div>
<list v-if="tabActive === 'list'" />
<day v-if="tabActive === 'day'" :defaultDate="defaultDate"/>
</div>
</div>
</div>
</template>
<script>
import AppContainer from '@/components/AppContainer'
import Calendar from '@/components/Calendar'
import List from './list/index'
import Day from './day/index.vue'
import { dateFormat } from '@/utils/dateAlgs'
export default {
components: { AppContainer }
data () {
return {
mineFilter: false,
statusFilter: '',
tabActive: 'list',
defaultDate: null,
dateType: 'date'
}
},
components: { Calendar, List, Day },
computed: {
now() {
const now = new Date()
return new Date(now.getFullYear(), now.getMonth(), now.getDate())
}
},
filters: {
dateFormat (value, fmt) {
return dateFormat(value, fmt)
},
statusFilter(val) {
let result = ''
switch (val) {
case 1:
result = '进行中'
break
case 2:
result = '已完成'
break
case 3:
result = '未开始'
break
}
return result
}
},
watch: {
tabActive: {
handler: function(nv) {
if (nv === 'day') {
this.defaultDate = this.now
this.dateType = 'date'
} else {
this.defaultDate = null
this.dateType = 'daterange'
}
},
immediate: true
}
},
methods: {
goSearch() {
this.$router.push('/search')
},
calendarChange(val) {
if (this.dateType === 'date') {
this.defaultDate = val
} else {
console.log(val)
}
},
joinMeeting(data) {
console.log(data)
},
pageChange(val) {
console.log(val)
}
}
}
</script>
<style scoped>
.calendar-main{
height:100%;
}
h5{
font-size: 16px;
font-family: PingFangSC-Regular, PingFang SC;
color:#333;
font-weight: 400;
line-height:50px;
text-indent:40px;
}
.calendar-inner{
height: calc(100% - 50px - 10px);
display:flex;
background: #FFFFFF;
border-radius: 10px;
margin:0 16px;
box-sizing:border-box;
padding:14px 14px 6px;
}
.left-calendar{
height:100%;
width:224px;
border-right:1px solid #eee;
box-sizing:border-box;
}
.status-filter ::v-deep.el-radio{
display:block;
margin-bottom:10px;
}
.status-filter ::v-deep.el-radio:first-child .el-radio__input.is-checked .el-radio__inner{
border-color:#409eff;
background:#409eff;
}
.status-filter ::v-deep.el-radio:last-child .el-radio__input.is-checked .el-radio__inner{
border-color:#BFBFBF;
background:#BFBFBF;
}
.status-filter ::v-deep.el-radio__input.is-checked .el-radio__inner{
border-color:#52B837;
background:#52B837;
}
.status-filter ::v-deep.el-radio__input + .el-radio__label{
color:#52B837;
}
.status-filter ::v-deep.el-radio:first-child .el-radio__input + .el-radio__label{
color:#409eff;
}
.status-filter ::v-deep.el-radio:last-child .el-radio__input + .el-radio__label{
color:#BFBFBF;
}
.right-container{
height:100%;
width:calc(100% - 224px);
}
.top{
height:38px;
padding-right:10px;
border-bottom:1px solid #eee;
}
.right-container .top{
display:flex;
flex-flow: row nowrap;
justify-content: space-between;
}
.right-container .top .title{
text-indent:20px;
line-height:28px;
}
.left-calendar .bottom{
padding-left:4px;
}
.left-calendar .bottom .title{
margin-bottom:10px;
}
.day{
height:calc(100% - 40px);
}
</style>
<template>
<el-form label-width="140px">
<el-form-item label="会议主题:">
某某某会议主题
</el-form-item>
<el-form-item label="会议时间:">
2021年11月06日 10:00-11:00(GMT+08:00)
</el-form-item>
<el-form-item label="会议链接:">
http://meeting.tencent.com/sS42KPDILN
</el-form-item>
<el-form-item label="会议号:">
526 430 840
</el-form-item>
<el-form-item label="会议直播:" v-if="hasLive">
http://meeting.tencent.com/sS42KPDILN
</el-form-item>
<el-form-item label="手机拨号入会:">
<p>+8675536550000,,526430840#(中国大陆)</p>
<p>+85230018898,,526430840#(中国香港)</p>
</el-form-item>
<el-form-item label="根据您的位置拨号:">
<p>+8675536550000(中国大陆)</p>
<p>++85230018898(中国香港)</p>
</el-form-item>
</el-form>
</template>
<script>
export default {
props: {
rowData: {}
},
computed: {
hasLive() {
return this.rowData.hasLive || false
}
}
}
</script>
<style scoped>
::v-deep.el-form-item{
margin:0;
}
</style>
\ No newline at end of file
<template>
<el-form label-width="140px">
<el-form-item label="会议主题:">
某某某会议主题
</el-form-item>
<el-form-item label="会议时间:">
2021年11月06日 10:00-11:00(GMT+08:00)
</el-form-item>
<el-form-item label="会议号:">
526 430 840
</el-form-item>
<el-form-item label="会议创建者:">
张三
</el-form-item>
<el-form-item label="会议主持人:">
李四
</el-form-item>
<el-form-item label="会议主持人:">
王五、赵⑥
</el-form-item>
<el-form-item label="会议直播主题:">
随便写点啥
</el-form-item>
<el-form-item label="会议直播简介:">
<p style="line-height:26px;padding:7px 10px 7px 0">随便写点啥随便写点啥随便写点啥随便写点啥随便写点啥随便写点啥随便写点啥随便写点啥随便写点啥随便写点啥</p>
</el-form-item>
<el-form-item label="参会成员:">
<el-button type="text">导出excel</el-button>
</el-form-item>
<el-form-item label="回放:">
<el-button type="text">下载</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
props: {
rowData: {},
dialogType: {}
},
computed: {
hasLive() {
return this.rowData.hasLive || false
}
}
}
</script>
<style scoped>
::v-deep.el-form-item{
margin:0;
}
</style>
\ No newline at end of file
<template>
<div class="btns">
<template v-if="status === 1">
<el-button type="text" size="small" v-if="operatable">进入会议</el-button>
<el-button type="text" size="small" v-if="operatable && hasLive">观看直播</el-button>
<el-button type="text" size="small" v-if="operatable" @click="dialogVisible = 'copy'">复制邀请</el-button>
<el-button type="text" size="small" v-if="operatable">终止</el-button>
<el-button type="text" size="small" v-if="!operatable" @click="dialogVisible = 'details'">查看</el-button>
</template>
<template v-if="status === 2">
<el-button type="text" size="small" v-if="operatable">进入会议</el-button>
<el-button type="text" size="small" v-if="operatable">复制邀请</el-button>
<el-button type="text" size="small" v-if="operatable && !isCycle">修改</el-button>
<el-dropdown v-if="operatable && isCycle">
<span class="dropdown-link">
修改<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>修改本次会议</el-dropdown-item>
<el-dropdown-item>修改周期会议</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button type="text" size="small" v-if="operatable">取消</el-button>
<el-button type="text" size="small" v-if="!operatable" @click="dialogVisible = 'details'">查看</el-button>
</template>
<template v-if="status === 3">
<el-button type="text" size="small" v-if="operatable">看回放</el-button>
<el-button type="text" size="small" @click="dialogVisible = 'details'">查看</el-button>
<el-button type="text" size="small" v-if="operatable">删除</el-button>
</template>
<el-dialog :title="domicTitle" :visible.sync="dialogVisible" width="520px" center>
<div slot="title" class="dialog-header">
<p class="meeting-status" v-show="dialogVisible === 'details'">会议进行中</p>
<p class="title">{{domicTitle}}</p>
</div>
<dialog-details :rowData="rowData" :dialogType="dialogVisible" v-show="dialogVisible === 'details'" />
<dialog-copy-invite :rowData="rowData" v-show="dialogVisible === 'copy'"/>
<div slot="footer" class="dialog-footer">
<el-button type="primary" size="mini" v-show="dialogVisible === 'copy'">复制</el-button>
<el-button @click="dialogVisible = false" size="mini">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import DialogDetails from './DialogDetails.vue'
import DialogCopyInvite from './DialogCopyInvite'
export default {
props: {
rowData: {}
},
data() {
return {
userId: '1234',
roleName: '', // general_admin
dialogVisible: false
}
},
computed: {
domicTitle() {
let title = ''
switch (this.dialogVisible) {
case 'copy':
title = '复制邀请'
break
case 'details':
title = '会议详情'
break
}
return title
},
status() {
return this.rowData.status
},
isCycle() {
return this.rowData.isCycle
},
isMyself() {
return this.rowData.creatorId === this.userId
},
isSuperAdmin() {
return this.roleName === 'administrator'
},
isGeneralAdmin() {
const admins = this.rowData.general_admin
let flag = false
for (let i = 0; i < admins.length; i++) {
if (admins[i].userId === this.userId) {
flag = true
break
}
}
return flag
},
hasLive() {
return this.rowData.hasLive || false
},
operatable() {
return this.isMyself || this.isSuperAdmin || this.isGeneralAdmin
}
},
components: { DialogDetails, DialogCopyInvite },
created() {
}
}
</script>
<style scoped>
.dropdown-link{
cursor: pointer;
color: #409EFF;
font-size:12px;
}
::v-deep.el-icon-arrow-down {
font-size: 12px;
}
.btns ::v-deep.el-dialog__header{
padding-top:14px;
}
.btns ::v-deep.el-dialog__headerbtn{
top:12px;
right:12px;
}
.btns ::v-deep.el-dialog__body{
padding:10px 0;
margin:0 20px;
border: 1px solid #DBDBDB;
}
.btns ::v-deep.el-dialog__footer{
padding-bottom:16px;
}
.dialog-header .meeting-status{
text-align:left;
}
.dialog-header .title{
font-size:16px;
}
</style>
\ No newline at end of file
<template>
<div class="list">
<el-table :data="listData" style="width: 100%" height="calc(100% - 32px)">
<el-table-column label="会议时间" min-width="160">
<template slot-scope="scope">
<p style="color:#AEAEAE;">
<span style="font-size:18px;color:#606266;">{{ scope.row.date | dateFormat('{d}')}}</span>
{{ scope.row.date | dateFormat('{m}月')}} {{ scope.row.date | dateFormat('周{a}')}}
<span style="font-size:16px;color:#606266;">{{ scope.row.date | dateFormat('{h}:{i}')}}</span>
</p>
<p style="color:#AEAEAE;">
<span style="font-size:18px;color:#606266;">{{ scope.row.endDate | dateFormat('{d}')}}</span>
{{ scope.row.endDate | dateFormat('{m}月')}} {{ scope.row.endDate | dateFormat('周{a}')}}
<span style="font-size:16px;color:#606266;">{{ scope.row.endDate | dateFormat('{h}:{i}')}}</span>
</p>
</template>
</el-table-column>
<el-table-column label="会议主题" min-width="180">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top" width="240">
<p>{{ scope.row.title }}</p>
<div slot="reference" class="name-wrapper">
<p style="display:flex;">
<span style="flex:1;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;">{{ scope.row.title }}</span>
<span v-if="scope.row.isCycle" style="color:#409DFF;width:40px;">(周期)</span>
</p>
</div>
</el-popover>
</template>
</el-table-column>
<!-- <el-table-column prop="resource" label="会议来源" width="80"></el-table-column> -->
<el-table-column prop="resource" label="会议状态" width="80">
<template slot-scope="scope">
<p :class="{'status-start': scope.row.status === 1, 'status-notstarted': scope.row.status === 2, 'status-end': scope.row.status === 3}">
<i></i>
<span style="margin-left: 10px">{{ scope.row.status | statusFilter}}</span>
</p>
</template>
</el-table-column>
<el-table-column prop="creator" label="创建人" width="80"></el-table-column>
<el-table-column label="操作" min-width="240">
<template slot-scope="scope">
<table-handles :rowData="scope.row"/>
</template>
</el-table-column>
</el-table>
<el-pagination :current-page.sync="curPage" :page-size="pageSize" layout="total, prev, pager, next" :total="total" @current-change="pageChange" style="float:right;"></el-pagination>
</div>
</template>
<script>
import { dateFormat } from '@/utils/dateAlgs'
import TableHandles from './components/TableHandles.vue'
export default {
data() {
return {
listData: [
{
date: new Date(2021, 3, 7, 10, 30, 0).getTime(),
endDate: new Date(2021, 3, 8, 8, 0, 0).getTime(),
title: 'CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课',
isCycle: false,
resource: '腾讯',
status: 1,
creator: '张三丰',
creatorId: '3211',
hasLive: true,
general_admin: [
{
userId: '1234'
},
{
userId: '1342'
}
]
},
{
date: new Date(2021, 3, 7, 10, 30, 0).getTime(),
endDate: new Date(2021, 3, 8, 8, 0, 0).getTime(),
title: 'CIIS直播课CII课',
isCycle: false,
resource: '腾讯',
status: 1,
creator: '张三三',
creatorId: '3211',
hasLive: true,
general_admin: [
{
userId: '2222'
},
{
userId: '1342'
}
]
},
{
date: new Date(2021, 3, 7, 10, 30, 0).getTime(),
endDate: new Date(2021, 3, 8, 8, 0, 0).getTime(),
title: 'CIIS直播课CII课',
isCycle: false,
resource: '腾讯',
status: 1,
creator: '张三多',
creatorId: '1234',
hasLive: false,
general_admin: [
{
userId: '2222'
},
{
userId: '1342'
}
]
},
{
date: new Date(2021, 10, 7).getTime(),
endDate: new Date(2021, 10, 8).getTime(),
title: '索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播',
isCycle: true,
resource: '腾讯',
status: 2,
creator: '黄飞鸿',
hasLive: false,
general_admin: [
{
userId: '1111'
},
{
userId: '1342'
}
]
},
{
date: new Date(2021, 10, 7).getTime(),
endDate: new Date(2021, 10, 8).getTime(),
title: '索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播',
isCycle: true,
resource: '腾讯',
status: 2,
creator: '黄飞鸿',
general_admin: [
{
userId: '1234'
},
{
userId: '1342'
}
]
},
{
date: new Date(2021, 10, 7).getTime(),
endDate: new Date(2021, 10, 8).getTime(),
title: '索菲亚直播索菲亚直播',
isCycle: false,
resource: '腾讯',
status: 3,
creator: '楚留香',
general_admin: [
{
userId: '1234'
},
{
userId: '1342'
}
]
},
{
date: new Date(2021, 10, 7).getTime(),
endDate: new Date(2021, 10, 8).getTime(),
title: '索菲亚直播索菲亚直播',
isCycle: false,
resource: '腾讯',
status: 3,
creator: '楚留香',
general_admin: [
{
userId: '3421'
},
{
userId: '1342'
}
]
}
],
curPage: 1,
pageSize: 20,
total: 200
}
},
filters: {
dateFormat (value, fmt) {
return dateFormat(value, fmt)
},
statusFilter(val) {
let result = ''
switch (val) {
case 1:
result = '进行中'
break
case 2:
result = '未开始'
break
case 3:
result = '已完成'
break
}
return result
}
},
components: { TableHandles },
methods: {
joinMeeting(data) {
console.log(data)
},
pageChange(val) {
console.log(val)
}
}
}
</script>
<style scoped>
.list{
padding: 5px 5px 0 10px;
height:calc(100% - 40px);
}
.status-start{
color:#409eff;
}
.status-notstarted{
color:#52B837;
}
.status-end{
color:#BFBFBF;
}
</style>
\ No newline at end of file
<template>
<div></div>
</template>
<template>
<div class="tx-meeting-container">
<div class="inner">
<el-form ref="ruleForm" :model="form" :rules="rules" label-width="120px">
<!-- <el-form-item label="会议选择" prop="meetingType">
<el-radio-group v-model="form.meetingType">
<el-radio label="tencent">腾讯</el-radio>
</el-radio-group>
</el-form-item> -->
<el-form-item label="会议主题" prop="theme">
<el-input v-model="form.theme" size="small" />
</el-form-item>
<el-form-item label="开始时间" required>
<el-col :span="11">
<el-form-item prop="startDate">
<el-date-picker v-model="form.startDate" type="date" placeholder="选择日期" style="width: 100%;" size="small" :clearable="timerClear" :editable="timerClear" :picker-options="startDateOptions" @change="startDateChange" />
</el-form-item>
</el-col>
<el-col :span="2" class="line">-</el-col>
<el-col :span="11">
<el-form-item prop="startTime">
<el-time-select v-model="form.startTime" style="width:100%;" :picker-options="startTimeOptions" placeholder="选择时间" size="small" :clearable="timerClear" :editable="timerClear" @change="startTimeChange" />
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="结束时间" required>
<el-col :span="11">
<el-form-item prop="endDate">
<el-date-picker v-model="form.endDate" type="date" placeholder="选择日期" style="width: 100%;" size="small" :clearable="timerClear" :editable="timerClear" :picker-options="endDateOptions" @change="endDateChange" />
</el-form-item>
</el-col>
<el-col :span="2" class="line">-</el-col>
<el-col :span="11">
<el-form-item prop="endTime">
<el-time-select v-model="form.endTime" style="width:100%;" :picker-options="endTimeOptions" placeholder="选择时间" size="small" :clearable="timerClear" :editable="timerClear"/>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="时区" required>
<el-col :span="11">
<el-select style="width:100%" v-model="form.timezone" placeholder="选择时区" size="small">
<el-option label="(GMT+8:00)中国标准时间-北京" value="beijing" />
</el-select>
</el-col>
</el-form-item>
<el-form-item label="周期会议">
<el-switch v-model="form.periodic" />
</el-form-item>
<el-form-item label="重复频率" v-if="form.periodic" required>
<el-col :span="11">
<el-select style="width:100%" v-model="form.repeatRate" placeholder="选择重复频率" size="small" @change="repaeatRateChange">
<el-option label="每天" value="everyday" />
<el-option label="每个工作日" value="everyworkday" />
<el-option label="每周" value="everyweek" />
<el-option label="每两周" value="every2weeks" />
<el-option label="每月" value="everymonth" />
</el-select>
</el-col>
</el-form-item>
<el-form-item label="结束重复" v-if="form.periodic" required>
<el-col :span="11">
<el-select style="width:100%" v-model="form.endType" placeholder="选择时区" size="small">
<el-option label="结束于某天" value="endOneday" />
<el-option label="限制会议次数" value="limitTimes" />
</el-select>
</el-col>
<el-col :span="2" class="line">-</el-col>
<el-col :span="11">
<el-date-picker v-if="form.endType === 'endOneday'" v-model="getCycleMeetingEndDate" type="date" placeholder="选择结束日期" style="width: 100%;" size="small" :clearable="timerClear" :editable="timerClear" :picker-options="cycleMeetingEndDateOptions" />
<el-input-number v-else v-model="form.periodicTimes" :min="1" :max="50" size="small" @change="handleChange"></el-input-number>
</el-col>
</el-form-item>
<el-form-item label="指定主持人">
<el-col :span="11">
<el-select style="width:100%" v-model="form.moderator" placeholder="选择主持人" size="small">
<el-option label="张三" value="zhangsan" />
<el-option label="李四" value="lisi" />
</el-select>
</el-col>
</el-form-item>
<el-form-item label="指定会议管理员">
<el-col :span="11">
<el-select style="width:100%" v-model="form.administrators" placeholder="选择管理员" size="small" multiple>
<el-option label="张三" value="zhangsan" />
<el-option label="李四" value="lisi" />
</el-select>
</el-col>
<el-col :span="13" style="color:#999;height:40px;position:relative;font-size:12px;line-height:18px;">
<span style="position:absolute;left:5px;top:50%;transform:translateY(-50%)">(会议管理员有修改会议,复制、取消会议等所有管理本次会议的权限)</span>
</el-col>
</el-form-item>
<el-form-item label="会议设置">
<el-checkbox style="width:120px;" v-model="form.isSecret">开启会议密码</el-checkbox>
<el-input style="width:170px;" v-model="form.secret" v-if="form.isSecret" placeholder="请输入4-6位数字密码" type="password" suffix-icon="el-icon-lock" size="small">
</el-input>
<el-checkbox style="display:block;" v-model="form.openWaitingRoom">开启等候室</el-checkbox>
<el-checkbox style="display:block;" v-model="form.joinAdvance">准许成员在主持人开始前进入会议</el-checkbox>
<el-checkbox style="display:block;" v-model="form.mute">入会自动静音</el-checkbox>
<el-checkbox style="display:block;" v-model="form.recordVideo">自动录制会议</el-checkbox>
</el-form-item>
<el-form-item label="会议直播">
<el-checkbox v-model="form.isLive">开启会议直播(用于分享给观看直播的用户)</el-checkbox>
</el-form-item>
<el-form-item label="直播主题" v-if="form.isLive" :required="form.isLive" prop="liveTheme">
<el-input v-model="form.liveTheme" size="small" />
</el-form-item>
<el-form-item label="直播简介" v-if="form.isLive" :required="form.isLive" prop="liveDesc">
<el-input type="textarea" v-model="form.liveDesc"></el-input>
</el-form-item>
<el-form-item label="直播设置" v-if="form.isLive" >
<el-checkbox style="display:block;" v-model="form.isLiveSecret">开启观看直播密码</el-checkbox>
<el-checkbox v-model="form.permitComment">准许观众讨论</el-checkbox>
</el-form-item>
<el-form-item style="padding-left:100px;">
<el-button type="primary" @click="submitForm('ruleForm')" size="mini">立即创建</el-button>
<el-button @click="resetForm('ruleForm')" size="mini">重置</el-button>
</el-form-item>
</el-form>
<div class="right-container" v-if="showSchedule">
<div class="title">{{form.startDate | timeFormat}} {{form.startDate | timeFormat('星期{a}')}}</div>
<schedule :options="options" :data="schedule" :date="form.startDate" />
<div class="pre-time-range" :style="{top: getTop, height: getHeight}"></div>
</div>
</div>
</div>
</template>
<script>
import Schedule from '@/components/Schedule'
import { timeTrans, dateFormat, getTimestampYMD, computedDateByRateTimes, computedTimesByRateDate, getCurHalfHour, isSameDate } from '@/utils/dateAlgs'
const DAY_TIMESTAMP = 8.64e7
export default {
data () {
const now = new Date()
const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const startDateChecked = (rule, value, callback) => {
if (value) {
const timestamp = Date.parse(value);
if (timestamp < Date.now() - DAY_TIMESTAMP) {
callback(new Error('开始时间必须大于当前时间'));
}
}
}
const endDateChecked = (rule, value, callback) => {
if (value) {
if (this.endTimestamp < this.startTimestamp) {
callback(new Error('结束时间必须大于开始时间'));
} else if (this.endTimestamp > this.startTimestamp + DAY_TIMESTAMP) {
callback(new Error('会议时间不能超过24小时'));
}
}
}
const validateErrMsg = (rule, value, callback) => {
// 当开启会议直播时,启用验证,处理callback
if (this.form.isLive) {
const errMsg = rule.field === 'liveTheme' ? '请输入直播主题' : '请输入直播简介'
callback(new Error(errMsg));
} else {
callback();
}
};
return {
tabActive: 'tx',
timerClear: false,
time: '',
form: {
meetingType: 'tencent',
theme: '',
startDate: nowDate,
startTime: getCurHalfHour('start'),
endDate: nowDate,
endTime: getCurHalfHour('end'),
timezone: 'beijing',
periodic: true,
repeatRate: 'everyday',
endType: 'endOneday',
periodicEndDate: '',
periodicTimes: 7,
isSecret: false,
secret: '',
openWaitingRoom: false,
joinAdvance: false,
mute: false,
recordVideo: false,
isLive: false,
liveTheme: '',
liveDesc: '',
isLiveSecret: false,
permitComment: false
},
rules: {
meetingType: [{ required: true, message: '请选择会议类型', trigger: 'blur' }],
theme: [{ required: true, message: '请填写会议主题', trigger: 'blur' }],
startDate: [
{ type: 'date', required: true, message: '请选择开始日期', trigger: 'change' },
{ type: 'date', validator: startDateChecked, trigger: 'change' }
],
startTime: [
{ required: true, message: '请选择开始时间', trigger: 'change' }
],
endDate: [{ type: 'date', required: true, message: '请选择结束日期', trigger: 'change' }],
endTime: [
{ required: true, message: '请选择结束时间', trigger: 'change' },
{ validator: endDateChecked, trigger: 'change' }
],
moderator: [{ required: true, message: '请选择主持人', trigger: 'change' }],
liveTheme: [{ validator: validateErrMsg }],
liveDesc: [{ validator: validateErrMsg }]
},
startDateOptions: {
disabledDate(time) {
return time.getTime() < Date.now() - DAY_TIMESTAMP;
}
},
startTimeOptions: {
start: getCurHalfHour('start'),
step: '00:30',
end: '23:30'
},
endTimeOptions: {
start: getCurHalfHour('end'),
step: '00:30',
end: '23:30'
},
monthMaxDay: 28,
cycleMeetingEndDateOptions: {
disabledDate: (time) => {
let flag = false
switch (this.form.repeatRate) {
case 'everyday': {
break
}
case 'everyworkday': {
if (time.getDay() === 5 || time.getDay() === 6) flag = true
break
}
case 'everyweek': {
const startDate = timeTrans(this.form.startDate)
if (startDate.getDay() !== time.getDay() || getTimestampYMD(time) < getTimestampYMD(startDate)) flag = true
break
}
case 'every2weeks': {
const startDate = timeTrans(this.form.startDate)
const disabledDay = ((getTimestampYMD(time) - getTimestampYMD(startDate)) / (DAY_TIMESTAMP * 7)) % 2 !== 0
if (disabledDay || getTimestampYMD(time) < getTimestampYMD(startDate)) flag = true
break
}
case 'everymonth': {
const startDate = timeTrans(this.form.startDate)
const day = startDate.getDate()
const targetDay = time.getDate()
let disabledDay = false
if (day > 28) {
const year = time.getFullYear();
const month = time.getMonth() + 1;
const targetMDays = new Date(year, month, 0).getDate();
if (targetDay !== targetMDays) disabledDay = true
} else if (startDate.getDate() !== targetDay) {
disabledDay = true
}
if (disabledDay || getTimestampYMD(time) < getTimestampYMD(startDate)) flag = true
break
}
}
return flag
}
},
showSchedule: false,
options: {
multi: false, // 是否为多日程
start: 0, // init number 0-23
end: 24, // init number 1-24且end>start
step: 60, // 只接收15, 30, 60三种步伐
lineHeight: 40, // init number 时间线间的高度
nowBeforeDisabled: true,
disabledNew: true,
popoverPos: 'bottom'
},
schedule: {
id: 'live-1',
events: [
]
}
}
},
computed: {
getTop() {
const timeRange = this.startTimestamp - this.startDotTimestamp
const top = timeRange / ((60 * 1000 * this.options.step) / this.options.lineHeight)
return (top + 56) + 'px'
},
getHeight() {
const start = timeTrans(this.startTimestamp)
let end = timeTrans(this.endTimestamp)
if (!isSameDate(start, end)) {
end = new Date(start.getFullYear(), start.getMonth(), start.getDate(), 24, 0, 0)
}
const timeRange = end.getTime() - start.getTime()
const h = timeRange / ((60 * 1000 * this.options.step) / this.options.lineHeight)
return (h - 2) + 'px'
},
startDotTimestamp() {
const date = this.form.startDate
const startHour = parseInt(this.options.start)
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), startHour, 0, 0).getTime()
},
startTimestamp() {
return this.getFullDateTime(this.form.startDate, this.form.startTime).getTime()
},
endTimestamp() {
return this.getFullDateTime(this.form.endDate, this.form.endTime).getTime()
},
getCycleMeetingEndDate: {
get: function() {
const startDate = this.form.startDate ? this.form.startDate : Date.now()
return computedDateByRateTimes(startDate, this.form.periodicTimes, this.form.repeatRate)
},
set: function (newValue) {
const times = computedTimesByRateDate(this.form.startDate, newValue, this.form.repeatRate)
this.form.periodicTimes = times
}
},
endDateOptions() {
const _startDate = this.form.startDate;
return {
disabledDate(time) {
return getTimestampYMD(time) < getTimestampYMD(_startDate) || getTimestampYMD(time) > getTimestampYMD(_startDate) + DAY_TIMESTAMP;
}
}
}
},
filters: {
timeFormat(val, fmt) {
fmt = fmt || '{y}年{m}月{d}日'
return dateFormat(val, fmt)
}
},
components: { Schedule },
created() {
const type = this.$route.query.type
switch (type) {
case '1':
this.showSchedule = true
}
console.log(this.$route.query)
},
watch: {
getCycleMeetingEndDate: {
handler: function(nv, ov) {
this.form.periodicEndDate = Date.parse(nv)
},
immediate: true
}
},
methods: {
startDateChange(val) {
this.form.endDate = val;
const _startDate = timeTrans(this.form.startDate);
if (Date.parse(_startDate) > Date.now()) {
this.startTimeOptions = {
start: '00:00',
step: '00:30',
end: '23:30'
}
} else {
this.startTimeOptions = {
start: getCurHalfHour('start'),
step: '00:30',
end: '23:30'
}
}
},
startTimeChange(val) {
const { startDate, endDate } = this.form
const fullDate = this.getFullDateTime(startDate, val)
if (this.isSameDate(startDate, endDate)) {
const startTime = getCurHalfHour('end', fullDate)
this.form.endTime = startTime
this.endTimeOptions = {
start: startTime,
step: '00:30',
end: '23:30'
}
} else {
const endTime = getCurHalfHour('start', fullDate)
this.form.endTime = endTime;
this.endTimeOptions = {
start: '00:00',
step: '00:30',
end: endTime
}
}
},
endDateChange() {
this.startTimeChange(this.form.startTime)
},
repaeatRateChange(val) {
if (val === 'everyworkday' && this.form.endType === 'endOneday') {
this.form.periodicTimes = 7;
}
},
getFullDateTime(date, timeStr) {
const hmArr = timeStr.split(':');
const h = parseInt(hmArr[0]);
const s = parseInt(hmArr[1]);
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), h, s, 0)
},
isSameDate(start, end) {
return dateFormat(start, '{y}-{m}-{d}') === dateFormat(end, '{y}-{m}-{d}')
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
console.log(valid);
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
},
handleChange(val) {
console.log(val)
}
}
}
</script>
<style scoped>
.tx-meeting-container{
overflow-y:auto;
height:calc(100% - 52px);
}
.inner{
display:flex;
background:#fff;
margin:0 15px;
border-radius:4px;
}
.el-form{
flex: 1;
max-width:600px;
padding-bottom:30px;
}
.line{
text-align:center;
}
.right-container{
position:relative;
width:45%;
box-sizing:border-box;
padding-left: 20px;
}
.right-container .title{
position:absolute;
left:0;
top:0;
width:100%;
line-height:50px;
font-size:16px;
text-align:center;
}
.pre-time-range{
position:absolute;
left:70px;
width:calc(100% - 70px);
background:#52b837;
}
.right-container .el-scrollbar ::v-deep.el-scrollbar__wrap{
/* overflow:hidden; */
}
</style>
<template>
<div class="create-meeting">
<el-radio-group v-model="tabActive" size="mini" style="margin:14px 0 10px 40px;">
<el-radio-button label="tx">腾讯</el-radio-button>
<el-radio-button label="cc">cc</el-radio-button>
<!-- <el-radio-button label="week"></el-radio-button>
<el-radio-button label="month"></el-radio-button> -->
</el-radio-group>
<tencent-meeting v-if="tabActive === 'tx'" />
</div>
</template>
<script>
import TencentMeeting from './components/tencentMeeting.vue'
export default {
data() {
return {
tabActive: 'tx'
}
},
components: { TencentMeeting }
}
</script>
<style scope>
.create-meeting{
height:100%;
}
</style>
\ No newline at end of file
<template>
<app-container title="系统管理"></app-container>
<div>个人中心</div>
</template>
<script>
import AppContainer from '@/components/AppContainer'
export default {
components: { AppContainer }
data () {
return {}
}
}
</script>
<style scoped>
</style>
<template>
<div class="search-main">
<h5>搜索会议</h5>
<div class="search-inner">
<div class="search-filter">
<el-row style="margin-bottom:14px;">
<el-col :span="8">
<label>会议ID</label>
<el-input placeholder="请输入会议ID" v-model="filter.meetingId" size="mini" clearable></el-input>
</el-col>
<el-col :span="8">
<label>会议主题</label>
<el-input placeholder="请输入会议主题" v-model="filter.meetingTitle" size="mini" clearable></el-input>
</el-col>
<el-col :span="8">
<label>会议状态</label>
<el-select v-model="filter.status" placeholder="请选择" size="mini" clearable>
<el-option label="进行中" value="1" />
<el-option label="未开始" value="2" />
<el-option label="已完成" value="3" />
</el-select>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<label>会议类型</label>
<el-select v-model="filter.meetingType" placeholder="请选择" size="mini" clearable>
<el-option label="腾讯" value="1" />
<el-option label="cc" value="2" />
</el-select>
</el-col>
<el-col :span="11">
<label>会议时间</label>
<el-date-picker v-model="filter.timeRange" type="datetimerange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" size="mini" clearable></el-date-picker>
</el-col>
<el-col :span="5">
<el-button type="primary" icon="el-icon-search" size="mini">查询</el-button>
<el-button icon="el-icon-refresh-left" size="mini" @click="reset">重置</el-button>
</el-col>
</el-row>
</div>
<el-table :data="listData" style="width: 100%" height="calc(100% - 116px)">
<el-table-column label="会议时间" min-width="180">
<template slot-scope="scope">
<p style="color:#AEAEAE;">
<span style="font-size:18px;color:#606266;">{{ scope.row.date | dateFormat('{d}')}}</span>
{{ scope.row.date | dateFormat('{m}月')}} {{ scope.row.date | dateFormat('周{a}')}}
<span style="font-size:16px;color:#606266;">{{ scope.row.date | dateFormat('{h}:{i}')}}</span>
</p>
<p style="color:#AEAEAE;">
<span style="font-size:18px;color:#606266;">{{ scope.row.endDate | dateFormat('{d}')}}</span>
{{ scope.row.endDate | dateFormat('{m}月')}} {{ scope.row.endDate | dateFormat('周{a}')}}
<span style="font-size:16px;color:#606266;">{{ scope.row.endDate | dateFormat('{h}:{i}')}}</span>
</p>
</template>
</el-table-column>
<el-table-column label="会议主题" min-width="170">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top" width="240">
<p>{{ scope.row.title }}</p>
<div slot="reference" class="name-wrapper">
<p style="display:flex;">
<span style="flex:1;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;">{{ scope.row.title }}</span>
<span v-if="scope.row.isCycle" style="color:#409DFF;width:40px;">(周期)</span>
</p>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="resource" label="会议来源" min-width="80"></el-table-column>
<el-table-column prop="resource" label="会议状态" min-width="80">
<template slot-scope="scope">
<p :class="{'status-start': scope.row.status === 1, 'status-notstarted': scope.row.status === 2, 'status-end': scope.row.status === 3}">
<i></i>
<span style="margin-left: 10px">{{ scope.row.status | statusFilter}}</span>
</p>
</template>
</el-table-column>
<el-table-column prop="creator" label="创建人" min-width="80"></el-table-column>
<el-table-column label="操作" min-width="240">
<template slot-scope="scope">
<p>
<el-button @click="joinMeeting(scope.row)" type="text" size="small">进入会议</el-button>
<el-button type="text" size="small">观看会议直播</el-button>
<el-button type="text" size="small">复制邀请</el-button>
</p>
<el-button type="text" size="small">查看</el-button>
<el-button type="text" size="small">修改</el-button>
<el-button type="text" size="small">取消</el-button>
<el-button type="text" size="small">终止</el-button>
<el-button type="text" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination :current-page.sync="curPage" :page-size="pageSize" layout="total, prev, pager, next" :total="total" @current-change="pageChange" style="float:right;"></el-pagination>
</div>
</div>
</template>
<script>
import { dateFormat } from '@/utils/dateAlgs'
export default {
data() {
return {
filter: {
meetingId: '',
meetingTitle: '',
status: '',
meetingType: '',
timeRange: ''
},
listData: [
{
date: new Date(2021, 3, 7, 10, 30, 0).getTime(),
endDate: new Date(2021, 3, 8, 8, 0, 0).getTime(),
title: 'CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课CIIS直播课',
isCycle: false,
resource: '腾讯',
status: 1,
creator: '张三丰'
},
{
date: new Date(2021, 10, 7).getTime(),
endDate: new Date(2021, 10, 8).getTime(),
title: '索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播索菲亚直播',
isCycle: true,
resource: '腾讯',
status: 2,
creator: '黄飞鸿'
},
{
date: new Date(2021, 10, 7).getTime(),
endDate: new Date(2021, 10, 8).getTime(),
title: '索菲亚直播索菲亚直播',
isCycle: false,
resource: '腾讯',
status: 3,
creator: '楚留香'
}
],
curPage: 1,
pageSize: 20,
total: 200
}
},
filters: {
dateFormat (value, fmt) {
return dateFormat(value, fmt)
},
statusFilter(val) {
let result = ''
switch (val) {
case 1:
result = '进行中'
break
case 2:
result = '已完成'
break
case 3:
result = '未开始'
break
}
return result
}
},
methods: {
reset() {
// this.filter
Object.keys(this.filter).map(key => { this.filter[key] = '' })
},
joinMeeting(data) {
console.log(data)
},
pageChange(val) {
console.log(val)
}
}
}
</script>
<style scoped>
.search-main{
height:100%;
}
h5{
font-size: 16px;
font-family: PingFangSC-Regular, PingFang SC;
color:#333;
font-weight: 400;
line-height:50px;
text-indent:40px;
}
.search-inner{
height: calc(100% - 50px - 10px);
background: #FFFFFF;
border-radius: 10px;
margin:0 16px;
box-sizing:border-box;
padding:20px 14px 0;
}
.search-filter{
margin-bottom:14px;
}
.search-filter label{
display:inline-block;
width:80px;
text-align:right;
padding-right:10px;
}
.search-filter .el-col>::v-deep.el-input{
width: calc(100% - 120px);
min-width:160px;
}
.search-filter ::v-deep.el-select{
width: calc(100% - 120px);
min-width:160px;
}
.search-filter ::v-deep.el-date-editor--datetimerange.el-input__inner{
width:300px;
padding:3px;
}
.status-start{
color:#409eff;
}
.status-notstarted{
color:#52B837;
}
.status-end{
color:#BFBFBF;
}
</style>
\ No newline at end of file
<template>
<div class="account">
<h5>账号管理 <el-button style="float:right;margin:12px 30px 0 0" size="mini" type="primary" plain @click="dialogVisible = 'add'">添加账号</el-button></h5>
<div class="inner">
<el-table :data="listData" style="width: 100%" height="calc(100% - 32px)">
<el-table-column prop="id" label="ID" min-width="120"></el-table-column>
<el-table-column prop="name" label="名称" min-width="120"></el-table-column>
<el-table-column prop="mobile" label="手机号码" min-width="120"></el-table-column>
<el-table-column prop="email" label="邮箱" min-width="120"></el-table-column>
<el-table-column label="操作" min-width="140">
<template slot-scope="scope">
<el-button type="text" size="small" @click="edit(scope.row)">编辑</el-button>
<el-button type="text" size="small" @click="dialogVisible = 'delete'">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination :current-page.sync="curPage" :page-size="pageSize" layout="total, prev, pager, next" :total="total" @current-change="pageChange" style="float:right;"></el-pagination>
<el-dialog :title="domicTitle" :visible.sync="dialogVisible" width="30%" center>
<el-form :model="form" :rules="rules" ref="ruleForm" label-width="70px" class="demo-ruleForm" v-show="dialogVisible === 'add' || dialogVisible === 'edit'">
<el-form-item label="ID" prop="id">
<el-input v-model="form.id"></el-input>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input v-model="form.mobile"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email"></el-input>
</el-form-item>
</el-form>
<p v-show="dialogVisible === 'delete'">确认删除此账号吗?</p>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="addAccount" size="mini">确 定</el-button>
<el-button @click="dialogVisible = null" size="mini">取 消</el-button>
</span>
</el-dialog>
</div>
</div>
</template>
<script>
export default {
data () {
const MOBILE_REG = /^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$/
const EMAIL_REG = /^[A-Za-z0-9]+([_.][A-Za-z0-9]+)*@([A-Za-z0-9-]+\.)+[A-Za-z]{2,6}$/
const checkMobile = (rule, value, callback) => {
if (value) {
if (!MOBILE_REG.test(value)) {
callback(new Error('手机号格式错误'));
}
}
}
const checkEmail = (rule, value, callback) => {
if (value) {
if (!EMAIL_REG.test(value)) {
callback(new Error('邮箱格式错误'));
}
}
}
return {
listData: [
{
id: '1234567890',
name: '张三',
mobile: '13111112222',
email: '1243@qq.com'
},
{
id: '1234567891',
name: '张三',
mobile: '13111112222',
email: '1243@qq.com'
},
{
id: '1234567892',
name: '张三',
mobile: '13111112222',
email: '1243@qq.com'
}
],
curPage: 1,
pageSize: 20,
total: 200,
dialogVisible: null,
form: {
id: '',
name: '',
mobile: '',
email: ''
},
rules: {
id: [{ required: true, message: '请输入ID', trigger: 'blur' }],
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
mobile: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ validator: checkMobile, trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ validator: checkEmail, trigger: 'blur' }
]
}
}
},
computed: {
domicTitle() {
let title = '新增'
switch (this.dialogVisible) {
case 'edit':
title = '编辑'
break
case 'delete':
title = '删除'
break
}
return title + '账号'
}
},
methods: {
pageChange(val) {
console.log(val)
},
edit(data) {
this.form = data
this.dialogVisible = 'edit'
},
addAccount() {
this.$refs.ruleForm.validate((valid) => {
if (valid) {
console.log('submit!');
}
});
}
}
}
</script>
<style scoped>
.account{
height:100%;
}
h5{
font-size: 16px;
font-family: PingFangSC-Regular, PingFang SC;
color:#333;
font-weight: 400;
line-height:50px;
text-indent:40px;
}
.inner{
height: calc(100% - 50px - 10px);
background: #FFFFFF;
border-radius: 10px;
margin:0 16px;
box-sizing:border-box;
padding:14px 14px 6px;
}
.account ::v-deep.el-dialog__header{
padding-top:14px;
}
.account ::v-deep.el-dialog__headerbtn{
top:12px;
right:12px;
}
.account ::v-deep.el-dialog__body{
padding:10px 20px
}
.account ::v-deep.el-dialog__footer{
padding-bottom:16px;
}
</style>
\ No newline at end of file
<template>
<div class="account">
<h5>角色管理</h5>
<div class="inner">
</div>
</div>
</template>
<script>
export default {
data () {
return {}
}
}
</script>
<style scoped>
.account{
height:100%;
}
h5{
font-size: 16px;
font-family: PingFangSC-Regular, PingFang SC;
color:#333;
font-weight: 400;
line-height:50px;
text-indent:40px;
}
.inner{
height: calc(100% - 50px - 10px);
display:flex;
background: #FFFFFF;
border-radius: 10px;
margin:0 16px;
box-sizing:border-box;
padding:14px 14px 6px;
}
</style>
\ No newline at end of file
import Layout from '@/components/layout'
export default [
{ path: '*', redirect: '/index' },
{
path: '/',
name: 'Layout',
component: Layout,
redirect: '/calendar'
},
{
path: '/meeting',
component: Layout,
children: [
{
path: '',
component: () => import('@/pages/meeting/index.vue'),
name: 'CreateLive',
meta: { title: '创建直播', affix: true }
}
]
},
{
path: '/calendar',
component: Layout,
children: [
{
path: '',
name: 'Calendar',
component: () => import('@/pages/calendar/index.vue'),
meta: { title: '日历列表', icon: 'el-icon-date' }
}
]
},
{
path: '/system',
component: Layout,
redirect: '/system/role',
name: 'System',
meta: { title: '系统管理', icon: 'el-icon-setting' },
children: [
{
path: 'role',
name: 'Role',
component: () => import('@/pages/system/role/index'),
meta: { title: '角色管理', icon: 'el-icon-s-check' }
},
{
path: 'account',
name: 'Account',
component: () => import('@/pages/system/account/index'),
meta: { title: '账号管理', icon: 'el-icon-key' }
}
]
},
{
path: '/my',
component: Layout,
children: [
{
path: '',
name: 'My',
component: () => import('@/pages/my/index.vue'),
meta: { title: '个人中心', icon: 'el-icon-user' }
}
]
},
{
path: '/search',
component: Layout,
children: [
/* 首页 */
{ path: '/index', component: () => import(/* webpackChunkName: "home" */ '@/pages/home') },
{ path: '/calendar', component: () => import(/* webpackChunkName: "calendar" */ '@/pages/calendar') },
{ path: '/system', component: () => import(/* webpackChunkName: "system" */ '@/pages/system') }
{
path: '',
name: 'Search',
component: () => import('@/pages/search/index.vue'),
meta: { title: '搜索' }
}
]
}
]
// export default [
// { path: '*', redirect: '/index' },
// {
// path: '/',
// component: Layout,
// children: [
// /* 首页 */
// { path: '/index', component: () => import(/* webpackChunkName: "home" */ '@/pages/home') },
// { path: '/calendar', component: () => import(/* webpackChunkName: "calendar" */ '@/pages/calendar') },
// { path: '/system', component: () => import(/* webpackChunkName: "system" */ '@/pages/system') },
// { path: '/create-live', component: () => import(/* webpackChunkName: "create-live" */ '@/pages/create-live') }
// ]
// }
// ]
const getters = {
token: state => state.user.token,
sidebar: state => state.app.sidebar
}
export default getters
import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import user from './modules/user'
import getters from './getters'
import { getUser, logout } from '@/api/account'
// import { getUser, logout } from '@/api/account'
Vue.use(Vuex)
const store = new Vuex.Store({
namespaced: true,
modules: {
app,
user
},
getters
})
export default store
/* const store = new Vuex.Store({
state: {
user: {},
isLogin: false
......@@ -15,6 +28,15 @@ const store = new Vuex.Store({
},
setIsLogin(state, isLogin) {
state.isLogin = isLogin
},
TOGGLE_SIDEBAR: state => {
if (state.sidebar.opened) {
Cookies.set('sidebarStatus', 1)
} else {
Cookies.set('sidebarStatus', 0)
}
state.sidebar.opened = !state.sidebar.opened
state.sidebar.withoutAnimation = false
}
},
actions: {
......@@ -49,3 +71,4 @@ const store = new Vuex.Store({
})
export default store
*/
import Cookies from 'js-cookie'
const state = {
sidebar: {
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
withoutAnimation: false
}
}
const mutations = {
TOGGLE_SIDEBAR: state => {
state.sidebar.opened = !state.sidebar.opened
state.sidebar.withoutAnimation = false
if (state.sidebar.opened) {
Cookies.set('sidebarStatus', 1)
} else {
Cookies.set('sidebarStatus', 0)
}
}
}
const actions = {
toggleSideBar({ commit }) {
commit('TOGGLE_SIDEBAR')
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
import { getUser, logout } from '@/api/account'
const user = {
state: {
user: {},
isLogin: false
},
mutations: {
setUser(state, user) {
state.user = user
},
setIsLogin(state, isLogin) {
state.isLogin = isLogin
}
},
actions: {
getUser({ commit }) {
getUser().then(response => {
commit('setUser', response)
})
},
// 退出登录
logout({ commit }) {
return logout().then(response => {
commit('setUser', {})
commit('setIsLogin', false)
return response
})
},
// 检测登录状态
async checkLogin({ commit }) {
// await getUserGrade()
// .then(response => {
// commit('setUserGrade', response.data.level)
// })
// .catch(() => {
// })
const isLogin = await getUser()
.then(response => {
commit('setUser', response.data)
return true
})
.catch(() => {
commit('setUser', {})
return false
})
commit('setIsLogin', isLogin)
return isLogin
}
}
}
export default user
$--color-primary: #c01540;
// $--color-primary: #c01540;
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
......@@ -90,8 +90,8 @@ html {
body {
font-size: 14px;
line-height: 1.4;
color: #222;
font-family: 'PingFang SC', 'Source Han Sans CN', -apple-system, 'Microsoft YaHei', 'Helvetica', 'Arial', Verdana,
color: #606266;
font-family: 'PingFang SC', 'PingFangSC-Regular', 'Source Han Sans CN', -apple-system, 'Microsoft YaHei', 'Helvetica', 'Arial', Verdana,
'Hiragino Sans GB', 'Wenquanyi Micro Hei', sans-serif;
background-color: #f9f9f9;
}
......@@ -36,6 +36,7 @@ httpRequest.interceptors.response.use(
return data
},
function(error) {
console.log(error)
if (error.response) {
const { status, message, code } = error.response.data
// 未登录
......@@ -44,10 +45,11 @@ httpRequest.interceptors.response.use(
} else if (status === 400 && code === 401) {
router.push('/role')
} else {
Message.error(message || error.response.data)
console.log(message)
// Message.error(message || error.response.data)
}
return Promise.reject(error.response)
} else {
} else if (typeof error === 'string') {
Message.error(error)
}
return Promise.reject(error)
......
import { recentWeekdays, rangeCount } from '@/utils/weekday/index'
const dayTimeStamp = 8.64e7
/**
* 将时间戳转成年月日时分秒
* @param {(Object|string|number)} time
* @returns {Date Object}
*/
export function timeTrans(time) {
let date
if (typeof time === 'object') {
date = time
} else {
if (typeof time === 'string') {
if (/^[0-9]+$/.test(time)) {
// support "1548221490638"
time = parseInt(time)
} else {
// support safari
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari
time = time.replace(new RegExp(/-/gm), '/')
}
}
if (typeof time === 'number' && time.toString().length === 10) {
time = time * 1000
}
date = new Date(time)
}
return date
}
/**
* 获取随机字符串
* @returns {String}
*/
export function nounceStr() {
const chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
let str = ''
for (let i = 0; i < 16; i++) {
const idx = parseInt(36 * Math.random())
str += chars[idx]
}
return str
}
/**
* 获取一天内从某小时至某小时结束的时间点
* @param {number} start
* @param {number} end
* @param {number} step (60分钟/30分钟/15分钟)
* @returns {Date Object}
*/
export function getTimeDots(start, end, step) {
const arr = []
start = parseInt(start)
end = parseInt(end)
for (let i = start; i <= end; i++) {
arr.push({
time: i < 10 ? '0' + i + ':00' : i + ':00'
})
if (i < 24 && step === 15) {
arr.push({
time: i < 10 ? '0' + i + ':15' : i + ':15'
})
}
if (i < 24 && (step === 30 || step === 15)) {
arr.push({
time: i < 10 ? '0' + i + ':30' : i + ':30'
})
}
if (i < 24 && step === 15) {
arr.push({
time: i < 10 ? '0' + i + ':45' : i + ':45'
})
}
}
return arr
}
/**
* 获取当前date时间对应的半小时
* @param {string} type ['start', 'end']
* @param {Date Object} date
* @returns {string}
*/
export function getCurHalfHour(type, date) {
const day = date || new Date();
let h = day.getHours()
const m = day.getMinutes();
let time = ''
if (m === 0) {
h < 10 && (h = '0' + h)
time = type === 'start' ? h + ':00' : h + ':30'
} else if (m <= 30) {
const _h = h + 1 < 10 ? '0' + (h + 1) : h + 1;
h < 10 && (h = '0' + h)
time = type === 'start' ? h + ':30' : _h + ':00'
} else {
const _h = h + 1 < 10 ? '0' + (h + 1) : h + 1;
time = type === 'start' ? _h + ':00' : _h + ':30'
}
return time
}
/**
* 获取当前date时间对应的半小时的日期
* @param {string} type ['start', 'end']
* @param {Date Object} date
* @returns {Date Object}
*/
export function getCurHalfHourDate(type, date) {
date = date ? timeTrans(date) : new Date()
const time = getCurHalfHour(type, date)
const hmArr = time.split(':')
const h = parseInt(hmArr[0])
const m = parseInt(hmArr[1])
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), h, m, 0)
}
/**
* 将日期转换为年月日整时
* @param {(Object|string|number)} date
* @returns {Date Object}
*/
export function getTimestampYMD(date) {
date = timeTrans(date)
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime()
}
/**
* 获取从起始日之后的N个月范围的日期
* @param {(Object|string|number)} startDate
* @param {number} times
* @returns {Date Object}
*/
function getDateByMouths(startDate, times) {
startDate = timeTrans(startDate);
let year = startDate.getFullYear();
let month = startDate.getMonth() + 1;
const day = startDate.getDate();
let targetMDays = 30;
let targetDate = null
// startDate.setMonth(month + times)
if ((month + times - 1) / 12 > 1) {
month = (month + times - 1) % 12
year += Math.floor((month + times - 1) / 12)
} else {
month = month + times - 1
}
targetMDays = new Date(year, month, 0).getDate()
if (targetMDays < day) {
targetDate = new Date(year, month - 1, targetMDays)
} else {
targetDate = new Date(year, month - 1, day)
}
return targetDate
}
/**
* 获取从起始日至结束日之间的月数
* @param {(Object|string|number)} startDate
* @param {number} times
* @returns {Date Object}
*/
function getMonthsByDate(startDate, endDate) {
let year = startDate.getFullYear();
let month = startDate.getMonth() + 1;
let monthDays = new Date(year, month, 0).getDate();
let times = 1;
const rangeTimeStamp = getTimestampYMD(endDate) - getTimestampYMD(startDate);
let totalDays = rangeTimeStamp / dayTimeStamp;
while (totalDays >= monthDays) {
totalDays -= monthDays;
++month
++times
if (month / 12 > 1) {
month = month % 12
++year
}
monthDays = new Date(year, month, 0).getDate();
}
return times
}
/**
* 将时间戳转成年月日时分秒
* @param {(Object|string|number)} time
* @param {string} cFormat
* @returns {string | null}
*/
export function dateFormat(time, cFormat) {
if (arguments.length === 0 || !time) {
return null
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
const date = timeTrans(time)
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const timeStr = format.replace(/{([ymdhisa])+}/g, (result, key) => {
const value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value]
}
return value.toString().padStart(2, '0')
})
return timeStr
}
/**
* 周期会议通过重复频率和重复次数计算结束日期
* @param {(Object|string|number)} startDate
* @param {number} times
* @param {string} repeatRate
* @returns {string | null}
*/
export function computedDateByRateTimes(startDate, times, repeatRate) {
let rangeTimeStamp = 0
let endTime
startDate = timeTrans(startDate)
switch (repeatRate) {
case 'everyday': {
rangeTimeStamp = (times - 1) * dayTimeStamp
endTime = Date.parse(startDate) + rangeTimeStamp
break
}
case 'everyworkday': {
const dateArr = recentWeekdays(times, startDate)
endTime = dateArr[dateArr.length - 1]
break
}
case 'everyweek': {
rangeTimeStamp = (times - 1) * 7 * dayTimeStamp
endTime = Date.parse(startDate) + rangeTimeStamp
break
}
case 'every2weeks': {
rangeTimeStamp = (times - 1) * 14 * dayTimeStamp
endTime = Date.parse(startDate) + rangeTimeStamp
break
}
case 'everymonth': {
endTime = getDateByMouths(startDate, times)
break
}
}
const endDate = dateFormat(endTime, '{y}-{m}-{d}')
return endDate
}
/**
* 判断两个date是否是同一天
* @param {(Object|string|number)} startDate
* @param {(Object|string|number)} endDate
* @returns {Boolean}
*/
export function isSameDate(startDate, endDate) {
const start = timeTrans(startDate)
const end = timeTrans(endDate)
return dateFormat(start, '{y}-{m}-{d}') === dateFormat(end, '{y}-{m}-{d}')
}
/**
* 周期会议通过重复频率和起、始日期计算重复次数
* @param {(Object|string|number)} startDate
* @param {(Object|string|number)} endDate
* @param {string} repeatRate
* @returns {string | null}
*/
export function computedTimesByRateDate(startDate, endDate, repeatRate) {
const rangeTimeStamp = getTimestampYMD(endDate) - getTimestampYMD(startDate)
let times = 0
switch (repeatRate) {
case 'everyday': {
times = rangeTimeStamp / dayTimeStamp + 1
break
}
case 'everyworkday': {
times = rangeCount(timeTrans(startDate), timeTrans(endDate))
break
}
case 'everyweek': {
times = rangeTimeStamp / (dayTimeStamp * 7) + 1
break
}
case 'every2weeks': {
times = rangeTimeStamp / (dayTimeStamp * 14) + 1
break
}
case 'everymonth': {
times = getMonthsByDate(startDate, endDate)
break
}
}
return times
}
// regex
export const REGEX_FORMAT = /d{1,2}|m{1,4}|yy(?:yy)?|([HhMsTtW])\1?|[LlSZ]|"[^"]*"|'[^']*'/g
// 一天的时间
export const ONE_DAY = 1000 * 60 * 60 * 24
// 一周的时间
export const ONE_WEEK = ONE_DAY * 7
import instance from '../utils/instance'
import rangeCount from '../rangeCount/index'
/**
* @name distanceCount
* @summary Get weekday count from original date
*
* @description
* Get weekday count from original date
*
* @param {Number} offset - the end date
* @param {Date} origin - the original date
* @returns {Number} the count
*
* @example
* // For the weekday date:
* var result = distanceCount(10, new Date(2018, 5, 20))
* //=> 8
*/
export default function distanceCount (offset, origin = new Date()) {
if (arguments.length > 2) {
throw new TypeError('2 argument required, but only ' + arguments.length + ' present')
}
if (!(typeof offset === 'number' && origin instanceof Date)) {
throw new TypeError(`(Number, Date) required, but got (${instance(offset)}, ${instance(origin)})`)
}
const endDate = new Date(origin.getTime())
endDate.setDate(origin.getDate() + offset)
return rangeCount(origin, endDate)
}
import distanceCount from '.'
describe('distanceCount', () => {
test('offset is Positive', function () {
expect(distanceCount(10, new Date(2018, 5, 20))).toBe(8)
})
test('offset is negative', function () {
expect(distanceCount(-10, new Date(2018, 5, 20))).toBe(8)
})
test('offset is zero', function () {
expect(distanceCount(0, new Date(2018, 5, 20))).toBe(1)
})
test('use default date', () => {
expect(distanceCount(10)).toBeGreaterThan(0)
})
test('throws TypeError exception if passed error type params', () => {
expect(() => distanceCount(10, 10)).toThrow(/\(Number, Date\) required/)
})
test('throws TypeError exception if passed more than 2 argument', () => {
expect(() => distanceCount(10, new Date(2018, 5, 20), 10)).toThrow(/2 argument required/)
})
})
import rangeWeekdays from '../rangeWeekdays'
import instance from '../utils/instance'
import { isDefaultDate, isDefaultString, isNumber } from '../utils/type'
/**
* @name distanceWeekdays
* @summary Get weekday list from original date
*
* @description
* Get weekday list from original date
*
* @param {Date} offset - the distance
* @param {Date} origin - the origin date
* @param {String} fmt - result format
* @returns {Array} weekday list
*
* @example
* var result = distanceWeekdays(5, new Date(2018, 5, 25))
* //=> [
* new Date(2018, 5, 20),
new Date(2018, 5, 21),
new Date(2018, 5, 22),
new Date(2018, 5, 25),
]
*/
export default function distanceWeekdays (offset, origin, fmt) {
if (arguments.length > 3) {
throw new TypeError('less than 3 argument required, but ' + arguments.length + ' present')
}
if (!(isNumber(offset) && isDefaultDate(origin) && isDefaultString(fmt))) {
throw new TypeError(`(Number, Date | Undefined, String | Undefined) required, but got (${instance(offset)}, ${instance(origin)}, ${instance(fmt)})`)
}
origin = origin || new Date()
const endDate = new Date(origin.getTime())
endDate.setDate(origin.getDate() + offset)
return rangeWeekdays(origin, endDate, fmt)
}
import distanceWeekdays from '.'
describe('distanceWeekdays', () => {
test('offset is Positive', function () {
expect(distanceWeekdays(5, new Date(2018, 5, 20))).toEqual([
new Date(2018, 5, 20),
new Date(2018, 5, 21),
new Date(2018, 5, 22),
new Date(2018, 5, 25),
])
})
test('offset is negative', function () {
expect(distanceWeekdays(-5, new Date(2018, 5, 20))).toEqual([
new Date(2018, 5, 15),
new Date(2018, 5, 18),
new Date(2018, 5, 19),
new Date(2018, 5, 20),
])
})
test('offset is zero', function () {
expect(distanceWeekdays(0, new Date(2018, 5, 20))).toEqual([
new Date(2018, 5, 20),
])
})
test('offset is zero', function () {
expect(distanceWeekdays(0, new Date(2018, 5, 10))).toEqual([])
})
test('use default date', () => {
expect(distanceWeekdays(5).length).toBeGreaterThan(0)
})
test('Format weekdays between given dates', function () {
expect(distanceWeekdays(5, new Date(2018, 5, 20), 'yyyy/mm/dd')).toEqual([
'2018/06/20',
'2018/06/21',
'2018/06/22',
'2018/06/25'
])
})
test('throws TypeError exception if passed error type params', () => {
expect(() => distanceWeekdays(10, 10)).toThrow(/\(Number, Date | Undefined, String | Undefined\) required/)
})
test('throws TypeError exception if passed more than 3 argument', () => {
expect(() => distanceWeekdays(10, new Date(2018, 5, 20), 10, 10)).toThrow(/3 argument required/)
})
})
import padZero from '../utils/padZero'
import { REGEX_FORMAT } from '../constant'
/**
* @name format
* @summary Format date to given mask
*
* @description
* Format date to given mask
*
* @param {Date} date - the date to format
* @param {String} mask - format mask
* @param {String} utc - use utc or not
* @returns {String} formated date
*
* @example
* // For the weekday date:
* var result = format(new Date(2018, 5, 20), 'yyyy/mm/dd')
* //=> 2018/06/20
*/
export default function format(date, mask, utc) {
const _ = utc ? 'getUTC' : 'get'
const d = date[_ + 'Date']()
const D = date[_ + 'Day']()
const m = date[_ + 'Month']()
const y = date[_ + 'FullYear']()
const H = date[_ + 'Hours']()
const M = date[_ + 'Minutes']()
const s = date[_ + 'Seconds']()
const L = date[_ + 'Milliseconds']()
const W = D === 0 ? 7 : D
const flags = {
d: d,
dd: padZero(d),
m: m + 1,
mm: padZero(m + 1),
mmm: format.i18n.monthNames[m],
mmmm: format.i18n.monthNames[m + 12],
yy: String(y).slice(2),
yyyy: y,
h: H % 12 || 12,
hh: padZero(H % 12 || 12),
H: H,
HH: padZero(H),
M: M,
MM: padZero(M),
s: s,
ss: padZero(s),
l: padZero(L, 3),
L: padZero(Math.round(L / 10)),
t: H < 12 ? format.i18n.timeNames[0] : format.i18n.timeNames[1],
tt: H < 12 ? format.i18n.timeNames[2] : format.i18n.timeNames[3],
T: H < 12 ? format.i18n.timeNames[4] : format.i18n.timeNames[5],
TT: H < 12 ? format.i18n.timeNames[6] : format.i18n.timeNames[7],
W: format.i18n.dayNames[W],
WW: format.i18n.dayNames[W + 7]
}
return mask.replace(REGEX_FORMAT, function(match) {
if (match in flags) {
return flags[match]
}
return match.slice(1, match.length - 1)
})
}
format.i18n = {
dayNames: [
'Sun',
'Mon',
'Tue',
'Wed',
'Thu',
'Fri',
'Sat',
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday'
],
monthNames: [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
],
timeNames: ['a', 'p', 'am', 'pm', 'A', 'P', 'AM', 'PM']
}
import format from '.'
describe('format', () => {
test('format normal', function () {
expect(format(new Date(2018, 5, 20), 'yyyy/mm/dd')).toBe('2018/06/20')
})
test('format different', function () {
expect(format(new Date(2018, 5, 20), 'yyyy-mm-dd HH:MM:ss')).toBe('2018-06-20 00:00:00')
})
test('format with week', function () {
expect(format(new Date(2018, 5, 20), 'yyyy/mm/dd WW')).toBe('2018/06/20 Wednesday')
})
test('format with named month', function () {
expect(format(new Date(2018, 5, 20), 'yyyy/mmmm/dd')).toBe('2018/June/20')
})
test('format random order', function () {
expect(format(new Date(2018, 5, 20), 'mmmm dd yyyy')).toBe('June 20 2018')
})
})
import format from './format'
import isWeekday from './isWeekday'
import distanceCount from './distanceCount'
import distanceWeekdays from './distanceWeekdays'
import rangeCount from './rangeCount'
import rangeWeekdays from './rangeWeekdays'
import recentWeekdays from './recentWeekdays'
export {
format,
isWeekday,
distanceCount,
distanceWeekdays,
rangeCount,
rangeWeekdays,
recentWeekdays
}
import { isDate } from '../utils/type'
import instance from '../utils/instance'
/**
* @name isWeekday
* @summary Does the given date fall on a weekday?
*
* @description
* Does the given date fall on a weekday?
*
* @param {Date} date - the date to check
* @returns {Boolean} the date is weekday
*
* @example
* // For the weekday date:
* var result = isWeekday(new Date(2018, 5, 20))
* //=> true
*/
export default function isWeekday (date) {
if (!isDate(date)) {
throw new TypeError(`Date type require, but got ${instance(date)}`)
}
const day = date.getDay()
return day > 0 && day < 6
}
import isWeekday from '.'
describe('isWeekend', () => {
test('returns true if the given date is in a weekday', function () {
expect(isWeekday(new Date(2018, 5 /* Jun */, 20))).toEqual(true)
})
test('returns false if the given date is not in a weekday', function () {
expect(isWeekday(new Date(2018, 5 /* Jun */, 23))).toEqual(false)
})
test('param is not date type', () => {
expect(() => isWeekday('aaa')).toThrow(/Date type require/)
})
})
import instance from '../utils/instance'
import { ONE_WEEK } from '../constant'
import { isDate } from '../utils/type'
/**
* @name rangeCount
* @summary Get weekday count between two dates
*
* @description
* Get weekday count between two dates
*
* @param {Date} start - the start date
* @param {Date} end - the end date
* @returns {Number} the count between start and end
*
* @example
* // How many weekdays between 2018-06-20 and 2018-06-30:
* var result = rangeCount(new Date(2018, 5, 20), new Date(2018, 5, 30))
* //=> 8
*/
export default function rangeCount (start, end) {
if (arguments.length !== 2) {
throw new TypeError('2 argument required, but only ' + arguments.length + ' present')
}
if (!(isDate(start) && isDate(end))) {
throw new TypeError(`(Date, Date) required, but got (${instance(start)}, ${instance(end)})`)
}
let startDate = new Date(start.getTime())
let endDate = new Date(end.getTime())
if (startDate > endDate) {
const tmp = startDate
startDate = endDate
endDate = tmp
}
const startWeek = startDate.getDay()
const startOffset = startWeek === 0 ? 0 : startWeek - 1
startDate.setDate(startDate.getDate() - startWeek)
const endWeek = endDate.getDay()
const endOffset = (endWeek === 0 || endWeek === 6) ? 0 : 5 - endWeek
endWeek !== 0 && endDate.setDate(endDate.getDate() + (7 - endWeek))
return Math.floor((endDate - startDate) / ONE_WEEK + 0.5) * 5 - startOffset - endOffset
}
import rangeCount from '.'
describe('rangeCount', () => {
test('start is earlier then end', function () {
expect(rangeCount(new Date(2018, 5, 24), new Date(2018, 5, 30))).toEqual(5)
})
test('start is later then end', function () {
expect(rangeCount(new Date(2018, 5, 30), new Date(2018, 5, 20))).toEqual(8)
})
test('returns 1 if the start date and end date are same day', function () {
expect(rangeCount(new Date(2018, 5, 20), new Date(2018, 5, 20))).toEqual(1)
})
test('throws TypeError exception if passed error type params', () => {
expect(() => rangeCount(12, new Date(2018, 5, 20))).toThrow(/\(Date, Date\) required/)
})
test('throws TypeError exception if passed less than 2 argument', () => {
expect(() => rangeCount(new Date(2018, 5, 20))).toThrow(/2 argument required/)
})
test('throws TypeError exception if passed more than 2 argument', () => {
expect(() => rangeCount(new Date(2018, 5, 20), new Date(2018, 5, 20), new Date(2018, 5, 20))).toThrow(/2 argument required/)
})
})
import format from '../format/index'
import { ONE_DAY } from '../constant'
import instance from '../utils/instance'
import { isDate, isDefaultString } from '../utils/type'
/**
* @name rangeWeekdays
* @summary Get weekdays between given dates
*
* @description
* Get weekdays between given dates
*
* @param {Date} start - the start date
* @param {Date} end - the end date
* @param {String} fmt - result format
* @returns {Array} weekday list between given dates
*
* @example
* var result = rangeWeekdays(new Date(2018, 5, 20), new Date(2018, 5, 25))
* //=> [
* new Date(2018, 5, 20),
new Date(2018, 5, 21),
new Date(2018, 5, 22),
new Date(2018, 5, 25),
]
*/
export default function rangeWeekdays (start, end, fmt) {
if (arguments.length !== 2 && arguments.length !== 3) {
throw new TypeError('2 or 3 argument required, but only ' + arguments.length + ' present')
}
if (!(isDate(start) && isDate(end) && isDefaultString(fmt))) {
throw new TypeError(`(Date, Date, String | Undefined) required, but got (${instance(start)}, ${instance(end)}, ${instance(fmt)})`)
}
let startDate = new Date(start.getTime())
let endDate = new Date(end.getTime())
if (startDate > endDate) {
const tmp = startDate
startDate = endDate
endDate = tmp
}
const weekdayList = []
while (startDate <= endDate) {
const week = startDate.getDay()
if (week > 0 && week < 6) {
const date = fmt ? format(startDate, fmt) : startDate
weekdayList.push(date)
}
startDate = new Date(startDate.getTime() + ONE_DAY)
}
return weekdayList
}
import rangeWeekdays from '.'
describe('rangeWeekdays', () => {
test('Weekdays between given dates', function () {
expect(rangeWeekdays(new Date(2018, 5, 20), new Date(2018, 5, 25))).toEqual([
new Date(2018, 5, 20),
new Date(2018, 5, 21),
new Date(2018, 5, 22),
new Date(2018, 5, 25),
])
})
test('Weekdays between given dates', function () {
expect(rangeWeekdays(new Date(2018, 5, 25), new Date(2018, 5, 20))).toEqual([
new Date(2018, 5, 20),
new Date(2018, 5, 21),
new Date(2018, 5, 22),
new Date(2018, 5, 25),
])
})
test('Same weekday', function () {
expect(rangeWeekdays(new Date(2018, 5, 20), new Date(2018, 5, 20))).toEqual([
new Date(2018, 5, 20),
])
})
test('Same weekend', function () {
expect(rangeWeekdays(new Date(2018, 5, 23), new Date(2018, 5, 23))).toEqual([])
})
test('Format weekdays between given dates', function () {
expect(rangeWeekdays(new Date(2018, 5, 20), new Date(2018, 5, 25), 'yyyy/mm/dd')).toEqual([
'2018/06/20',
'2018/06/21',
'2018/06/22',
'2018/06/25'
])
})
test('throws TypeError exception if passed error argument', () => {
expect(() => rangeWeekdays(new Date(2018, 5, 20))).toThrow(/2 or 3 argument required/)
})
test('throws TypeError exception if passed error argument', () => {
expect(() => rangeWeekdays(new Date(2018, 5, 20), 1, new Date())).toThrow(/\(Date, Date, String \| Undefined\) required/)
})
})
import instance from '../utils/instance'
import { isDefaultDate, isDefaultString, isNumber } from '../utils/type'
import isWeekday from '../isWeekday/index'
import { ONE_DAY } from '../constant'
import format from '../format'
/**
* @name recentWeekdays
* @summary Get recent weekday list from the given date
*
* @description
* Get recent weekday list from the given date
* @param {Number} days - weekday count
* @param {Date} origin - the original date, default today
* @param {String} fmt - result format
* @returns {Array} recent weekday list
*
* @example
* // For the weekday date:
* var result = recentWeekdays(3, new Date(2018, 5, 20))
* //=> [
* new Date(2018, 5, 20),
new Date(2018, 5, 21),
new Date(2018, 5, 22),
]
*/
export default function recentWeekdays(days, origin, fmt) {
if (arguments.length > 3) {
throw new TypeError('less than 3 argument required, but ' + arguments.length + ' present')
}
if (!(isNumber(days) && isDefaultDate(origin) && isDefaultString(fmt))) {
throw new TypeError(
`(Number, Date | Undefined, String | Undefined) required, but got (${instance(days)}, ${instance(
origin
)}, ${instance(fmt)})`
)
}
origin = origin || new Date()
let originDate = new Date(origin.getTime())
const oneDay = days > 0 ? ONE_DAY : -ONE_DAY
const weekdayList = []
const offset = Math.abs(days)
while (weekdayList.length < offset) {
if (isWeekday(originDate)) {
const date = fmt ? format(originDate, fmt) : originDate
days >= 0 && weekdayList.push(date)
days < 0 && weekdayList.unshift(date)
}
originDate = new Date(originDate.getTime() + oneDay)
}
return weekdayList
}
import recentWeekdays from '.'
describe('recentWeekdays', () => {
test('only one param', function () {
expect(recentWeekdays(5).length).toBe(5)
})
test('offset is positive', function () {
expect(recentWeekdays(5, new Date(2018, 5, 20))).toEqual([
new Date(2018, 5, 20),
new Date(2018, 5, 21),
new Date(2018, 5, 22),
new Date(2018, 5, 25),
new Date(2018, 5, 26),
])
})
test('offset is negative', function () {
expect(recentWeekdays(-5, new Date(2018, 5, 20))).toEqual([
new Date(2018, 5, 14),
new Date(2018, 5, 15),
new Date(2018, 5, 18),
new Date(2018, 5, 19),
new Date(2018, 5, 20),
])
})
test('offset is zero', function () {
expect(recentWeekdays(0, new Date(2018, 5, 20))).toEqual([])
})
test('offset is zero', function () {
expect(recentWeekdays(0, new Date(2018, 5, 23))).toEqual([])
})
test('format result', function () {
expect(recentWeekdays(5, new Date(2018, 5, 20), 'yyyy/mm/dd')).toEqual([
'2018/06/20',
'2018/06/21',
'2018/06/22',
'2018/06/25',
'2018/06/26',
])
})
test('throws TypeError exception if passed error type params', () => {
expect(() => recentWeekdays(10, 10)).toThrow(/\(Number, Date | Undefined, String | Undefined\) required/)
})
test('throws TypeError exception if passed more than 3 argument', () => {
expect(() => recentWeekdays(10, new Date(2018, 5, 20), 'yyyymmdd', 10)).toThrow(/3 argument required/)
})
})
/**
* @name instance
* @summary Get instance of value
*
* @description
* Get instance of value
*
* @param {Date} param
* @returns {String} Get instance of value
*
* @example
* // For date type:
* var result = instance(new Date())
* //=> Date
*/
export default function instance(param) {
const type = Object.prototype.toString.call(param)
const reg = /\[object (.*)\]/
return reg.exec(type)[1]
}
/**
* @name padZero
* @summary pads the string with '0'
*
* @description
* pads the current string with '0' so that the resulting string reaches the given length
*
* @param {String} val - value to be handled
* @param {Number} len - the final length of result
* @returns {String}
*
* @example
* var result = padZero('1', 3)
* //=> '001'
*/
export default function padZero(val, len = 2) {
val = String(val)
while (val.length < len) {
val = '0' + val
}
return val
}
export function isDate(date) {
return date instanceof Date
}
export function isDefaultDate(date) {
return date === undefined || isDate(date)
}
export function isString(val) {
return typeof val === 'string'
}
export function isDefaultString(val) {
return val === undefined || isString(val)
}
export function isNumber(val) {
return typeof val === 'number'
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论