master
parent
3786429be4
commit
b7150b6b29
|
@ -0,0 +1,50 @@
|
|||
package com.jing.common.core.constant;
|
||||
|
||||
/**
|
||||
* 任务调度通用常量
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class ScheduleConstants
|
||||
{
|
||||
public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";
|
||||
|
||||
/** 执行目标key */
|
||||
public static final String TASK_PROPERTIES = "TASK_PROPERTIES";
|
||||
|
||||
/** 默认 */
|
||||
public static final String MISFIRE_DEFAULT = "0";
|
||||
|
||||
/** 立即触发执行 */
|
||||
public static final String MISFIRE_IGNORE_MISFIRES = "1";
|
||||
|
||||
/** 触发一次执行 */
|
||||
public static final String MISFIRE_FIRE_AND_PROCEED = "2";
|
||||
|
||||
/** 不触发立即执行 */
|
||||
public static final String MISFIRE_DO_NOTHING = "3";
|
||||
|
||||
public enum Status
|
||||
{
|
||||
/**
|
||||
* 正常
|
||||
*/
|
||||
NORMAL("0"),
|
||||
/**
|
||||
* 暂停
|
||||
*/
|
||||
PAUSE("1");
|
||||
|
||||
private String value;
|
||||
|
||||
private Status(String value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package com.jing.common.core.constant;
|
||||
|
||||
/**
|
||||
* 权限相关通用常量
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class SecurityConstants
|
||||
{
|
||||
/**
|
||||
* 用户ID字段
|
||||
*/
|
||||
public static final String DETAILS_USER_ID = "user_id";
|
||||
|
||||
/**
|
||||
* 用户名字段
|
||||
*/
|
||||
public static final String DETAILS_USERNAME = "username";
|
||||
|
||||
/**
|
||||
* 授权信息字段
|
||||
*/
|
||||
public static final String AUTHORIZATION_HEADER = "authorization";
|
||||
|
||||
/**
|
||||
* 请求来源
|
||||
*/
|
||||
public static final String FROM_SOURCE = "from-source";
|
||||
|
||||
/**
|
||||
* 内部请求
|
||||
*/
|
||||
public static final String INNER = "inner";
|
||||
|
||||
/**
|
||||
* 用户标识
|
||||
*/
|
||||
public static final String USER_KEY = "user_key";
|
||||
|
||||
/**
|
||||
* 登录用户
|
||||
*/
|
||||
public static final String LOGIN_USER = "login_user";
|
||||
|
||||
/**
|
||||
* 角色权限
|
||||
*/
|
||||
public static final String ROLE_PERMISSION = "role_permission";
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package com.jing.common.core.context;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import com.jing.common.core.constant.SecurityConstants;
|
||||
import com.jing.common.core.text.Convert;
|
||||
import com.jing.common.core.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* 获取当前线程变量中的 用户id、用户名称、Token等信息
|
||||
* 注意: 必须在网关通过请求头的方法传入,同时在HeaderInterceptor拦截器设置值。 否则这里无法获取
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class SecurityContextHolder
|
||||
{
|
||||
private static final TransmittableThreadLocal<Map<String, Object>> THREAD_LOCAL = new TransmittableThreadLocal<>();
|
||||
|
||||
public static void set(String key, Object value)
|
||||
{
|
||||
Map<String, Object> map = getLocalMap();
|
||||
map.put(key, value == null ? StringUtils.EMPTY : value);
|
||||
}
|
||||
|
||||
public static String get(String key)
|
||||
{
|
||||
Map<String, Object> map = getLocalMap();
|
||||
return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY));
|
||||
}
|
||||
|
||||
public static <T> T get(String key, Class<T> clazz)
|
||||
{
|
||||
Map<String, Object> map = getLocalMap();
|
||||
return StringUtils.cast(map.getOrDefault(key, null));
|
||||
}
|
||||
|
||||
public static Map<String, Object> getLocalMap()
|
||||
{
|
||||
Map<String, Object> map = THREAD_LOCAL.get();
|
||||
if (map == null)
|
||||
{
|
||||
map = new ConcurrentHashMap<String, Object>();
|
||||
THREAD_LOCAL.set(map);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static void setLocalMap(Map<String, Object> threadLocalMap)
|
||||
{
|
||||
THREAD_LOCAL.set(threadLocalMap);
|
||||
}
|
||||
|
||||
public static Long getUserId()
|
||||
{
|
||||
return Convert.toLong(get(SecurityConstants.DETAILS_USER_ID), 0L);
|
||||
}
|
||||
|
||||
public static void setUserId(String account)
|
||||
{
|
||||
set(SecurityConstants.DETAILS_USER_ID, account);
|
||||
}
|
||||
|
||||
public static String getUserName()
|
||||
{
|
||||
return get(SecurityConstants.DETAILS_USERNAME);
|
||||
}
|
||||
|
||||
public static void setUserName(String username)
|
||||
{
|
||||
set(SecurityConstants.DETAILS_USERNAME, username);
|
||||
}
|
||||
|
||||
public static String getUserKey()
|
||||
{
|
||||
return get(SecurityConstants.USER_KEY);
|
||||
}
|
||||
|
||||
public static void setUserKey(String userKey)
|
||||
{
|
||||
set(SecurityConstants.USER_KEY, userKey);
|
||||
}
|
||||
|
||||
public static String getPermission()
|
||||
{
|
||||
return get(SecurityConstants.ROLE_PERMISSION);
|
||||
}
|
||||
|
||||
public static void setPermission(String permissions)
|
||||
{
|
||||
set(SecurityConstants.ROLE_PERMISSION, permissions);
|
||||
}
|
||||
|
||||
public static void remove()
|
||||
{
|
||||
THREAD_LOCAL.remove();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
//package com.jing.job.config;
|
||||
//
|
||||
//import java.util.Properties;
|
||||
//import javax.sql.DataSource;
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//import org.springframework.scheduling.quartz.SchedulerFactoryBean;
|
||||
//
|
||||
///**
|
||||
// * 定时任务配置(单机部署建议删除此类和qrtz数据库表,默认走内存会最高效)
|
||||
// *
|
||||
// * @author ruoyi
|
||||
// */
|
||||
//@Configuration
|
||||
//public class ScheduleConfig
|
||||
//{
|
||||
// @Bean
|
||||
// public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource)
|
||||
// {
|
||||
// SchedulerFactoryBean factory = new SchedulerFactoryBean();
|
||||
// factory.setDataSource(dataSource);
|
||||
//
|
||||
// // quartz参数
|
||||
// Properties prop = new Properties();
|
||||
// prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler");
|
||||
// prop.put("org.quartz.scheduler.instanceId", "AUTO");
|
||||
// // 线程池配置
|
||||
// prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
|
||||
// prop.put("org.quartz.threadPool.threadCount", "20");
|
||||
// prop.put("org.quartz.threadPool.threadPriority", "5");
|
||||
// // JobStore配置
|
||||
// prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore");
|
||||
// // 集群配置
|
||||
// prop.put("org.quartz.jobStore.isClustered", "true");
|
||||
// prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
|
||||
// prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "10");
|
||||
// prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
|
||||
//
|
||||
// // sqlserver 启用
|
||||
// // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
|
||||
// prop.put("org.quartz.jobStore.misfireThreshold", "12000");
|
||||
// prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
|
||||
// factory.setQuartzProperties(prop);
|
||||
//
|
||||
// factory.setSchedulerName("RuoyiScheduler");
|
||||
// // 延时启动
|
||||
// factory.setStartupDelay(1);
|
||||
// factory.setApplicationContextSchedulerContextKey("applicationContextKey");
|
||||
// // 可选,QuartzScheduler
|
||||
// // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
|
||||
// factory.setOverwriteExistingJobs(true);
|
||||
// // 设置自动启动,默认为true
|
||||
// factory.setAutoStartup(true);
|
||||
//
|
||||
// return factory;
|
||||
// }
|
||||
//}
|
|
@ -0,0 +1,28 @@
|
|||
package com.jing.job.task;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.jing.common.core.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* 定时任务调度测试
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component("ryTask")
|
||||
public class RyTask
|
||||
{
|
||||
public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i)
|
||||
{
|
||||
System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
|
||||
}
|
||||
|
||||
public void ryParams(String params)
|
||||
{
|
||||
System.out.println("执行有参方法:" + params);
|
||||
}
|
||||
|
||||
public void ryNoParams()
|
||||
{
|
||||
System.out.println("执行无参方法");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
package com.jing.job.util;
|
||||
|
||||
import org.quartz.CronScheduleBuilder;
|
||||
import org.quartz.CronTrigger;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobBuilder;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobKey;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.TriggerBuilder;
|
||||
import org.quartz.TriggerKey;
|
||||
import com.jing.common.core.constant.Constants;
|
||||
import com.jing.common.core.constant.ScheduleConstants;
|
||||
import com.jing.common.core.exception.job.TaskException;
|
||||
import com.jing.common.core.exception.job.TaskException.Code;
|
||||
import com.jing.common.core.utils.SpringUtils;
|
||||
import com.jing.common.core.utils.StringUtils;
|
||||
import com.jing.job.domain.SysJob;
|
||||
|
||||
/**
|
||||
* 定时任务工具类
|
||||
*
|
||||
* @author ruoyi
|
||||
*
|
||||
*/
|
||||
public class ScheduleUtils
|
||||
{
|
||||
/**
|
||||
* 得到quartz任务类
|
||||
*
|
||||
* @param sysJob 执行计划
|
||||
* @return 具体执行任务类
|
||||
*/
|
||||
private static Class<? extends Job> getQuartzJobClass(SysJob sysJob)
|
||||
{
|
||||
boolean isConcurrent = "0".equals(sysJob.getConcurrent());
|
||||
return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建任务触发对象
|
||||
*/
|
||||
public static TriggerKey getTriggerKey(Long jobId, String jobGroup)
|
||||
{
|
||||
return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建任务键对象
|
||||
*/
|
||||
public static JobKey getJobKey(Long jobId, String jobGroup)
|
||||
{
|
||||
return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建定时任务
|
||||
*/
|
||||
public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
|
||||
{
|
||||
Class<? extends Job> jobClass = getQuartzJobClass(job);
|
||||
// 构建job信息
|
||||
Long jobId = job.getJobId();
|
||||
String jobGroup = job.getJobGroup();
|
||||
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();
|
||||
|
||||
// 表达式调度构建器
|
||||
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
|
||||
cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);
|
||||
|
||||
// 按新的cronExpression表达式构建一个新的trigger
|
||||
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
|
||||
.withSchedule(cronScheduleBuilder).build();
|
||||
|
||||
// 放入参数,运行时的方法可以获取
|
||||
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);
|
||||
|
||||
// 判断是否存在
|
||||
if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
|
||||
{
|
||||
// 防止创建时存在数据问题 先移除,然后在执行创建操作
|
||||
scheduler.deleteJob(getJobKey(jobId, jobGroup));
|
||||
}
|
||||
|
||||
// 判断任务是否过期
|
||||
if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
|
||||
{
|
||||
// 执行调度任务
|
||||
scheduler.scheduleJob(jobDetail, trigger);
|
||||
}
|
||||
|
||||
// 暂停任务
|
||||
if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
|
||||
{
|
||||
scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置定时任务策略
|
||||
*/
|
||||
public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
|
||||
throws TaskException
|
||||
{
|
||||
switch (job.getMisfirePolicy())
|
||||
{
|
||||
case ScheduleConstants.MISFIRE_DEFAULT:
|
||||
return cb;
|
||||
case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
|
||||
return cb.withMisfireHandlingInstructionIgnoreMisfires();
|
||||
case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
|
||||
return cb.withMisfireHandlingInstructionFireAndProceed();
|
||||
case ScheduleConstants.MISFIRE_DO_NOTHING:
|
||||
return cb.withMisfireHandlingInstructionDoNothing();
|
||||
default:
|
||||
throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
|
||||
+ "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查包名是否为白名单配置
|
||||
*
|
||||
* @param invokeTarget 目标字符串
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean whiteList(String invokeTarget)
|
||||
{
|
||||
String packageName = StringUtils.substringBefore(invokeTarget, "(");
|
||||
int count = StringUtils.countMatches(packageName, ".");
|
||||
if (count > 1)
|
||||
{
|
||||
return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR);
|
||||
}
|
||||
Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]);
|
||||
String beanPackageName = obj.getClass().getPackage().getName();
|
||||
return StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR)
|
||||
&& !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M124.884 109.812L94.256 79.166c-.357-.357-.757-.629-1.129-.914a50.366 50.366 0 0 0 8.186-27.59C101.327 22.689 78.656 0 50.67 0 22.685 0 0 22.688 0 50.663c0 27.989 22.685 50.663 50.656 50.663 10.186 0 19.643-3.03 27.6-8.201.286.385.557.771.9 1.114l30.628 30.632a10.633 10.633 0 0 0 7.543 3.129c2.728 0 5.457-1.043 7.543-3.115 4.171-4.157 4.171-10.915.014-15.073M50.671 85.338C31.557 85.338 16 69.78 16 50.663c0-19.102 15.557-34.661 34.67-34.661 19.115 0 34.657 15.559 34.657 34.675 0 19.102-15.557 34.661-34.656 34.661"/></svg>
|
After Width: | Height: | Size: 600 B |
|
@ -0,0 +1,117 @@
|
|||
<template>
|
||||
<el-form size="small">
|
||||
<el-form-item>
|
||||
<el-radio v-model='radioValue' :label="1">
|
||||
秒,允许的通配符[, - * /]
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model='radioValue' :label="2">
|
||||
周期从
|
||||
<el-input-number v-model='cycle01' :min="0" :max="58" /> -
|
||||
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" /> 秒
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model='radioValue' :label="3">
|
||||
从
|
||||
<el-input-number v-model='average01' :min="0" :max="58" /> 秒开始,每
|
||||
<el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 秒执行一次
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-radio v-model='radioValue' :label="4">
|
||||
指定
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||
<el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
radioValue: 1,
|
||||
cycle01: 1,
|
||||
cycle02: 2,
|
||||
average01: 0,
|
||||
average02: 1,
|
||||
checkboxList: [],
|
||||
checkNum: this.$options.propsData.check
|
||||
}
|
||||
},
|
||||
name: 'crontab-second',
|
||||
props: ['check', 'radioParent'],
|
||||
methods: {
|
||||
// 单选按钮值变化时
|
||||
radioChange() {
|
||||
switch (this.radioValue) {
|
||||
case 1:
|
||||
this.$emit('update', 'second', '*', 'second');
|
||||
break;
|
||||
case 2:
|
||||
this.$emit('update', 'second', this.cycleTotal);
|
||||
break;
|
||||
case 3:
|
||||
this.$emit('update', 'second', this.averageTotal);
|
||||
break;
|
||||
case 4:
|
||||
this.$emit('update', 'second', this.checkboxString);
|
||||
break;
|
||||
}
|
||||
},
|
||||
// 周期两个值变化时
|
||||
cycleChange() {
|
||||
if (this.radioValue == '2') {
|
||||
this.$emit('update', 'second', this.cycleTotal);
|
||||
}
|
||||
},
|
||||
// 平均两个值变化时
|
||||
averageChange() {
|
||||
if (this.radioValue == '3') {
|
||||
this.$emit('update', 'second', this.averageTotal);
|
||||
}
|
||||
},
|
||||
// checkbox值变化时
|
||||
checkboxChange() {
|
||||
if (this.radioValue == '4') {
|
||||
this.$emit('update', 'second', this.checkboxString);
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'radioValue': 'radioChange',
|
||||
'cycleTotal': 'cycleChange',
|
||||
'averageTotal': 'averageChange',
|
||||
'checkboxString': 'checkboxChange',
|
||||
radioParent() {
|
||||
this.radioValue = this.radioParent
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算两个周期值
|
||||
cycleTotal: function () {
|
||||
const cycle01 = this.checkNum(this.cycle01, 0, 58)
|
||||
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59)
|
||||
return cycle01 + '-' + cycle02;
|
||||
},
|
||||
// 计算平均用到的值
|
||||
averageTotal: function () {
|
||||
const average01 = this.checkNum(this.average01, 0, 58)
|
||||
const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0)
|
||||
return average01 + '/' + average02;
|
||||
},
|
||||
// 计算勾选的checkbox值合集
|
||||
checkboxString: function () {
|
||||
let str = this.checkboxList.join();
|
||||
return str == '' ? '*' : str;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
|
||||
<slot />
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const tagAndTagSpacing = 4 // tagAndTagSpacing
|
||||
|
||||
export default {
|
||||
name: 'ScrollPane',
|
||||
data() {
|
||||
return {
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
scrollWrapper() {
|
||||
return this.$refs.scrollContainer.$refs.wrap
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.scrollWrapper.addEventListener('scroll', this.emitScroll, true)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.scrollWrapper.removeEventListener('scroll', this.emitScroll)
|
||||
},
|
||||
methods: {
|
||||
handleScroll(e) {
|
||||
const eventDelta = e.wheelDelta || -e.deltaY * 40
|
||||
const $scrollWrapper = this.scrollWrapper
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
|
||||
},
|
||||
emitScroll() {
|
||||
this.$emit('scroll')
|
||||
},
|
||||
moveToTarget(currentTag) {
|
||||
const $container = this.$refs.scrollContainer.$el
|
||||
const $containerWidth = $container.offsetWidth
|
||||
const $scrollWrapper = this.scrollWrapper
|
||||
const tagList = this.$parent.$refs.tag
|
||||
|
||||
let firstTag = null
|
||||
let lastTag = null
|
||||
|
||||
// find first tag and last tag
|
||||
if (tagList.length > 0) {
|
||||
firstTag = tagList[0]
|
||||
lastTag = tagList[tagList.length - 1]
|
||||
}
|
||||
|
||||
if (firstTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = 0
|
||||
} else if (lastTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
|
||||
} else {
|
||||
// find preTag and nextTag
|
||||
const currentIndex = tagList.findIndex(item => item === currentTag)
|
||||
const prevTag = tagList[currentIndex - 1]
|
||||
const nextTag = tagList[currentIndex + 1]
|
||||
|
||||
// the tag's offsetLeft after of nextTag
|
||||
const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
|
||||
|
||||
// the tag's offsetLeft before of prevTag
|
||||
const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
|
||||
|
||||
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
||||
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
|
||||
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
||||
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.scroll-container {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
::v-deep {
|
||||
.el-scrollbar__bar {
|
||||
bottom: 0px;
|
||||
}
|
||||
.el-scrollbar__wrap {
|
||||
height: 49px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,58 @@
|
|||
Math.easeInOutQuad = function(t, b, c, d) {
|
||||
t /= d / 2
|
||||
if (t < 1) {
|
||||
return c / 2 * t * t + b
|
||||
}
|
||||
t--
|
||||
return -c / 2 * (t * (t - 2) - 1) + b
|
||||
}
|
||||
|
||||
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
|
||||
var requestAnimFrame = (function() {
|
||||
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
|
||||
})()
|
||||
|
||||
/**
|
||||
* Because it's so fucking difficult to detect the scrolling element, just move them all
|
||||
* @param {number} amount
|
||||
*/
|
||||
function move(amount) {
|
||||
document.documentElement.scrollTop = amount
|
||||
document.body.parentNode.scrollTop = amount
|
||||
document.body.scrollTop = amount
|
||||
}
|
||||
|
||||
function position() {
|
||||
return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} to
|
||||
* @param {number} duration
|
||||
* @param {Function} callback
|
||||
*/
|
||||
export function scrollTo(to, duration, callback) {
|
||||
const start = position()
|
||||
const change = to - start
|
||||
const increment = 20
|
||||
let currentTime = 0
|
||||
duration = (typeof (duration) === 'undefined') ? 500 : duration
|
||||
var animateScroll = function() {
|
||||
// increment the time
|
||||
currentTime += increment
|
||||
// find the value with the quadratic in-out easing function
|
||||
var val = Math.easeInOutQuad(currentTime, start, change, duration)
|
||||
// move the document.body
|
||||
move(val)
|
||||
// do the animation unless its over
|
||||
if (currentTime < duration) {
|
||||
requestAnimFrame(animateScroll)
|
||||
} else {
|
||||
if (callback && typeof (callback) === 'function') {
|
||||
// the animation is done so lets callback
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
animateScroll()
|
||||
}
|
Loading…
Reference in New Issue