fineui是帆软报表和BI产品线所使用的前端框架。
 
 
 

673 lines
21 KiB

import {
shortcut,
createWidget,
toPix,
parseFloat,
Layout,
AbsoluteLayout,
clamp,
VerticalLayout,
isEmptyString,
isNumeric,
isNull,
isInteger,
i18nText,
size,
parseInt,
isNotEmptyString,
MouseMoveTracker
} from "@/core";
import { Single, Editor } from "@/base";
import { AccurateCalculationModel } from "./model.accuratecalculation";
import { SignTextEditor, SliderIconButton } from "../singleslider";
@shortcut()
export class IntervalSlider extends Single {
static xtype = "bi.interval_slider";
_constant = {
EDITOR_WIDTH: 58,
EDITOR_R_GAP: 60,
EDITOR_HEIGHT: 20,
SLIDER_WIDTH_HALF: 15,
SLIDER_WIDTH: 30,
SLIDER_HEIGHT: 30,
TRACK_HEIGHT: 24,
}
props = {
baseCls: "bi-interval-slider bi-slider-track",
digit: false,
unit: "",
min: 0,
max: 100,
value: { min: "", max: "" },
};
static EVENT_CHANGE = "EVENT_CHANGE";
beforeMount() {
const { value, min, max } = this.options;
this._setMinAndMax({
min,
max,
});
this.setValue(value);
this.populate();
}
render() {
const c = this._constant;
this.enable = false;
this.valueOne = "";
this.valueTwo = "";
this.calculation = new AccurateCalculationModel();
this.grayTrack = createWidget({
type: Layout.xtype,
cls: "gray-track",
height: 6,
});
this.blueTrack = createWidget({
type: Layout.xtype,
cls: "blue-track bi-high-light-background",
height: 6,
});
this.track = this._createTrackWrapper();
this.labelOne = createWidget({
type: SignTextEditor.xtype,
cls: "slider-editor-button",
text: this.options.unit,
allowBlank: false,
width: toPix(c.EDITOR_WIDTH, 2),
height: toPix(c.EDITOR_HEIGHT, 2),
validationChecker: v => this._checkValidation(v),
});
this.labelOne.element.hover(
() => {
this.labelOne.element
.removeClass("bi-border")
.addClass("bi-border");
},
() => {
this.labelOne.element.removeClass("bi-border");
}
);
this.labelOne.on(Editor.EVENT_CONFIRM, () => {
const oldValueOne = this.valueOne;
const v = parseFloat(this.getValue());
this.valueOne = v;
const percent = this._getPercentByValue(v);
const significantPercent = parseFloat(percent.toFixed(1)); // 分成1000份
this._setSliderOnePosition(significantPercent);
this._setBlueTrack();
this._checkLabelPosition(
oldValueOne,
this.valueTwo,
this.valueOne,
this.valueTwo
);
this.fireEvent(IntervalSlider.EVENT_CHANGE);
});
this.labelTwo = createWidget({
type: SignTextEditor.xtype,
cls: "slider-editor-button",
text: this.options.unit,
allowBlank: false,
width: toPix(c.EDITOR_WIDTH, 2),
height: toPix(c.EDITOR_HEIGHT, 2),
validationChecker: v => this._checkValidation(v),
});
this.labelTwo.element.hover(
() => {
this.labelTwo.element
.removeClass("bi-border")
.addClass("bi-border");
},
() => {
this.labelTwo.element.removeClass("bi-border");
}
);
this.labelTwo.on(Editor.EVENT_CONFIRM, () => {
const oldValueTwo = this.valueTwo;
const v = parseFloat(this.getValue());
this.valueTwo = v;
const percent = this._getPercentByValue(v);
const significantPercent = parseFloat(percent.toFixed(1));
this._setSliderTwoPosition(significantPercent);
this._setBlueTrack();
this._checkLabelPosition(
this.valueOne,
oldValueTwo,
this.valueOne,
this.valueTwo
);
this.fireEvent(IntervalSlider.EVENT_CHANGE);
});
this.sliderOne = createWidget({
type: SliderIconButton.xtype,
});
this.sliderTwo = createWidget({
type: SliderIconButton.xtype,
});
this._draggable(this.sliderOne, true);
this._draggable(this.sliderTwo, false);
this._setVisible(false);
return {
type: "bi.vertical_fill",
rowSize: [30, 30],
items: [
this._createLabelWrapper(),
{
type: AbsoluteLayout.xtype,
items: [
{
el: {
type: "bi.horizontal",
horizontalAlign: "stretch",
verticalAlign: "middle",
columnSize: ["fill"],
items: [
{
el: this.track,
}
],
hgap: 10,
},
inset: 0,
},
this._createSliderWrapper()
],
}
],
};
}
_rePosBySizeAfterMove(size, isLeft) {
const o = this.options;
const percent = (size * 100) / this._getGrayTrackLength();
const significantPercent = parseFloat(percent.toFixed(1));
let v = this._getValueByPercent(significantPercent);
v = this._assertValue(v);
v = o.digit === false ? v : v.toFixed(o.digit);
const oldValueOne = this.valueOne,
oldValueTwo = this.valueTwo;
if (isLeft) {
this._setSliderOnePosition(significantPercent);
this.labelOne.setValue(v);
this.valueOne = v;
this._checkLabelPosition(
oldValueOne,
oldValueTwo,
v,
this.valueTwo
);
} else {
this._setSliderTwoPosition(significantPercent);
this.labelTwo.setValue(v);
this.valueTwo = v;
this._checkLabelPosition(
oldValueOne,
oldValueTwo,
this.valueOne,
v
);
}
this._setBlueTrack();
}
_rePosBySizeAfterStop(size, isLeft) {
const percent = (size * 100) / this._getGrayTrackLength();
const significantPercent = parseFloat(percent.toFixed(1));
isLeft
? this._setSliderOnePosition(significantPercent)
: this._setSliderTwoPosition(significantPercent);
}
_draggable(widget, isLeft) {
let startDrag = false;
let size = 0,
offset = 0,
defaultSize = 0;
const mouseMoveTracker = new MouseMoveTracker(
(deltaX => {
if (mouseMoveTracker.isDragging()) {
startDrag = true;
offset += deltaX;
size = optimizeSize(defaultSize + offset);
widget.element.addClass("dragging");
this._rePosBySizeAfterMove(size, isLeft);
}
}),
(() => {
if (startDrag === true) {
size = optimizeSize(size);
this._rePosBySizeAfterStop(size, isLeft);
size = 0;
offset = 0;
defaultSize = size;
startDrag = false;
}
widget.element.removeClass("dragging");
mouseMoveTracker.releaseMouseMoves();
this.fireEvent(IntervalSlider.EVENT_CHANGE);
}),
window
);
widget.element.on("mousedown", function (event) {
if (!widget.isEnabled()) {
return;
}
defaultSize = this.offsetLeft;
optimizeSize(defaultSize);
mouseMoveTracker.captureMouseMoves(event);
});
const optimizeSize = s => clamp(s, 0, this._getGrayTrackLength());
}
_createLabelWrapper() {
const c = this._constant;
return {
el: {
type: VerticalLayout.xtype,
items: [
{
type: AbsoluteLayout.xtype,
items: [
{
el: this.labelOne,
top: 0,
left: 0,
}
],
},
{
type: AbsoluteLayout.xtype,
items: [
{
el: this.labelTwo,
top: 0,
right: 0,
}
],
}
],
rgap: c.EDITOR_R_GAP,
height: c.SLIDER_HEIGHT,
},
top: 0,
left: 0,
width: "100%",
};
}
_createSliderWrapper() {
return {
el: {
type: "bi.horizontal",
horizontalAlign: "stretch",
verticalAlign: "middle",
items: [
{
type: AbsoluteLayout.xtype,
height: 12,
width: "fill",
items: [
{
el: this.sliderOne,
top: 0,
bottom: 0,
left: 0,
},
{
el: this.sliderTwo,
top: 0,
bottom: 0,
left: "100%",
}
],
}
],
hgap: 10,
},
inset: 0,
};
}
_createTrackWrapper() {
return createWidget({
type: "bi.horizontal",
cls: "track-wrapper",
horizontalAlign: "stretch",
verticalAlign: "middle",
columnSize: ["fill"],
scrollx: false,
items: [
{
type: AbsoluteLayout.xtype,
height: 6,
items: [
{
el: this.grayTrack,
top: 0,
left: 0,
bottom: 0,
width: "100%",
},
{
el: this.blueTrack,
top: 0,
left: 0,
bottom: 0,
width: "0%",
}
],
}
],
});
}
_checkValidation(v) {
const o = this.options;
let valid = false;
// 像90.这样的既不属于整数又不属于小数,是不合法的值
let dotText = (`${v}`).split(".")[1];
// eslint-disable-next-line no-empty
if (isEmptyString(dotText)) {
} else {
if (isNumeric(v) && !(isNull(v) || v < this.min || v > this.max)) {
// 虽然规定了所填写的小数位数,但是我们认为所有的整数都是满足设置的小数位数的
// 100等价于100.0 100.00 100.000
if (o.digit === false || isInteger(v)) {
valid = true;
} else {
dotText = dotText || "";
valid = dotText.length === o.digit;
}
}
}
return valid;
}
_checkOverlap() {
const labelOneLeft = this.labelOne.element[0].offsetLeft;
const labelTwoLeft = this.labelTwo.element[0].offsetLeft;
if (labelOneLeft <= labelTwoLeft) {
if (labelTwoLeft - labelOneLeft < 90) {
this.labelTwo.element.css({ top: 40 });
} else {
this.labelTwo.element.css({ top: 0 });
}
} else {
if (labelOneLeft - labelTwoLeft < 90) {
this.labelTwo.element.css({ top: 40 });
} else {
this.labelTwo.element.css({ top: 0 });
}
}
}
_checkLabelPosition(oldValueOne, oldValueTwo, valueOne, valueTwo, isLeft) {
oldValueOne = parseFloat(oldValueOne);
oldValueTwo = parseFloat(oldValueTwo);
valueOne = parseFloat(valueOne);
valueTwo = parseFloat(valueTwo);
if (
(oldValueOne <= oldValueTwo && valueOne > valueTwo) ||
(oldValueOne >= oldValueTwo && valueOne < valueTwo)
) {
const isSliderOneLeft =
parseFloat(this.labelOne.getValue()) <
parseFloat(this.labelTwo.getValue());
this._resetLabelPosition(!isSliderOneLeft);
}
}
_resetLabelPosition(needReverse) {
this.labelOne.element.css({ left: needReverse ? "100%" : "0%" });
this.labelTwo.element.css({ left: needReverse ? "0%" : "100%" });
}
_setSliderOnePosition(percent) {
this.sliderOne.element.css({ left: `${percent}%` });
}
_setSliderTwoPosition(percent) {
this.sliderTwo.element.css({ left: `${percent}%` });
}
_setBlueTrackLeft(percent) {
this.blueTrack.element.css({ left: `${percent}%` });
}
_setBlueTrackWidth(percent) {
this.blueTrack.element.css({ width: `${percent}%` });
}
_setBlueTrack() {
const percentOne = this._getPercentByValue(this.labelOne.getValue());
const percentTwo = this._getPercentByValue(this.labelTwo.getValue());
if (percentOne <= percentTwo) {
this._setBlueTrackLeft(percentOne);
this._setBlueTrackWidth(percentTwo - percentOne);
} else {
this._setBlueTrackLeft(percentTwo);
this._setBlueTrackWidth(percentOne - percentTwo);
}
}
_setAllPosition(one, two) {
this._setSliderOnePosition(one);
this._setSliderTwoPosition(two);
this._setBlueTrack();
}
_setVisible(visible) {
this.sliderOne.setVisible(visible);
this.sliderTwo.setVisible(visible);
this.labelOne.setVisible(visible);
this.labelTwo.setVisible(visible);
}
_setErrorText() {
const errorText = i18nText(
"BI-Basic_Please_Enter_Number_Between",
this.min,
this.max
);
this.labelOne.setErrorText(errorText);
this.labelTwo.setErrorText(errorText);
}
_getGrayTrackLength() {
return this.grayTrack.element[0].scrollWidth;
}
_getValueByPercent(percent) {
// return (((max-min)*percent)/100+min)
if (percent === 0) {
return this.min;
}
if (percent === 100) {
return this.max;
}
const sub = this.calculation.accurateSubtraction(this.max, this.min);
const mul = this.calculation.accurateMultiplication(sub, percent);
const div = this.calculation.accurateDivisionTenExponent(mul, 2);
if (this.precision < 0) {
const value = parseFloat(
this.calculation.accurateAddition(div, this.min)
);
const reduceValue = Math.round(
this.calculation.accurateDivisionTenExponent(
value,
-this.precision
)
);
return this.calculation.accurateMultiplication(
reduceValue,
Math.pow(10, -this.precision)
);
}
return parseFloat(
this.calculation
.accurateAddition(div, this.min)
.toFixed(this.precision)
);
}
_getPercentByValue(v) {
return ((v - this.min) * 100) / (this.max - this.min);
}
_getPrecision() {
// 计算每一份值的精度(最大值和最小值的差值保留4为有效数字后的精度)
// 如果差值的整数位数大于4,toPrecision(4)得到的是科学计数法123456 => 1.235e+5
// 返回非负值: 保留的小数位数
// 返回负值: 保留的10^n精度中的n
const sub = this.calculation.accurateSubtraction(this.max, this.min);
const pre = sub.toPrecision(4);
// 科学计数法
const eIndex = pre.indexOf("e");
let arr = [];
if (eIndex > -1) {
arr = pre.split("e");
const decimalPartLength = size(arr[0].split(".")[1]);
const sciencePartLength = parseInt(arr[1].substring(1));
return decimalPartLength - sciencePartLength;
}
arr = pre.split(".");
return arr.length > 1 ? arr[1].length : 0;
}
_assertValue(value) {
if (value <= this.min) {
return this.min;
}
if (value >= this.max) {
return this.max;
}
return value;
}
_setEnable(b) {
super._setEnable.apply(this, [b]);
if (b) {
this.blueTrack.element
.removeClass("disabled-blue-track")
.addClass("blue-track");
} else {
this.blueTrack.element
.removeClass("blue-track")
.addClass("disabled-blue-track");
}
}
getValue() {
if (this.valueOne <= this.valueTwo) {
return { min: this.valueOne, max: this.valueTwo };
}
return { min: this.valueTwo, max: this.valueOne };
}
_setMinAndMax(v) {
const minNumber = parseFloat(v.min);
const maxNumber = parseFloat(v.max);
if (!isNaN(minNumber) && !isNaN(maxNumber) && maxNumber >= minNumber) {
this.min = minNumber;
this.max = maxNumber;
this.valueOne = minNumber;
this.valueTwo = maxNumber;
this.precision = this._getPrecision();
}
}
setMinAndMax(v) {
this._setMinAndMax(v);
this.setEnable(v.min <= v.max);
}
setValue(v) {
const o = this.options;
let valueOne = parseFloat(v.min);
let valueTwo = parseFloat(v.max);
valueOne =
o.digit === false
? valueOne
: parseFloat(valueOne.toFixed(o.digit));
valueTwo =
o.digit === false
? valueTwo
: parseFloat(valueTwo.toFixed(o.digit));
if (!isNaN(valueOne) && !isNaN(valueTwo)) {
if (this._checkValidation(valueOne)) {
this.valueOne =
this.valueOne <= this.valueTwo ? valueOne : valueTwo;
}
if (this._checkValidation(valueTwo)) {
this.valueTwo =
this.valueOne <= this.valueTwo ? valueTwo : valueOne;
}
if (valueOne < this.min) {
this.valueOne = this.min;
}
if (valueTwo > this.max) {
this.valueTwo = this.max;
}
}
}
reset() {
this._setVisible(false);
this.enable = false;
this.valueOne = "";
this.valueTwo = "";
this.min = NaN;
this.max = NaN;
this._setBlueTrackWidth(0);
}
populate() {
const o = this.options;
if (!isNaN(this.min) && !isNaN(this.max)) {
this.enable = true;
this._setVisible(true);
this._setErrorText();
if (
(isNumeric(this.valueOne) || isNotEmptyString(this.valueOne)) &&
(isNumeric(this.valueTwo) || isNotEmptyString(this.valueTwo))
) {
this.labelOne.setValue(
o.digit === false
? this.valueOne
: parseFloat(this.valueOne).toFixed(o.digit)
);
this.labelTwo.setValue(
o.digit === false
? this.valueTwo
: parseFloat(this.valueTwo).toFixed(o.digit)
);
this._setAllPosition(
this._getPercentByValue(this.valueOne),
this._getPercentByValue(this.valueTwo)
);
} else {
this.labelOne.setValue(this.min);
this.labelTwo.setValue(this.max);
this._setAllPosition(0, 100);
}
this._resetLabelPosition(this.valueOne > this.valueTwo);
}
}
}