352 lines
13 KiB
Vue
352 lines
13 KiB
Vue
<script>
|
||
/** 磁盘容量选择卡:options 定义离散值或步长区间,v-model 保存容量,change 通知父级重算报价。 */
|
||
import "~/assets/styles/ConfigDiskCard.Dx9IqqQ_.css";
|
||
import { nextTick } from "vue";
|
||
import { toDisplayString } from "vue";
|
||
import { renderSlot } from "vue";
|
||
import { createCommentVNode } from "vue";
|
||
import { computed } from "vue";
|
||
import { createElementBlock } from "vue";
|
||
import { Fragment } from "vue";
|
||
import { createElementVNode } from "vue";
|
||
import { unref } from "vue";
|
||
import { defineComponent } from "vue";
|
||
import { createVNode } from "vue";
|
||
import { openBlock } from "vue";
|
||
import { t as _plugin_vue_export_helper_t } from "~/lib/_plugin-vue_export-helper.js";
|
||
import inputnumber from "primevue/inputnumber";
|
||
var m = {
|
||
class: `storage-field`,
|
||
},
|
||
h = {
|
||
key: 0,
|
||
class: `storage-label`,
|
||
},
|
||
g = [`aria-label`],
|
||
_ = {
|
||
class: `storage-card-header`,
|
||
},
|
||
v = [`disabled`, `aria-label`],
|
||
y = {
|
||
class: `storage-unit`,
|
||
"aria-hidden": `true`,
|
||
},
|
||
b = [`aria-label`, `aria-valuetext`, `disabled`, `value`, `min`, `max`],
|
||
x = {
|
||
key: 0,
|
||
class: `storage-description`,
|
||
},
|
||
S = Object.assign(
|
||
_plugin_vue_export_helper_t(
|
||
defineComponent({
|
||
__name: `ConfigDiskCard`,
|
||
props: {
|
||
label: {},
|
||
hideLabel: {
|
||
type: Boolean,
|
||
},
|
||
title: {},
|
||
modelValue: {},
|
||
options: {
|
||
default: () => [],
|
||
},
|
||
unit: {
|
||
default: `GB`,
|
||
},
|
||
description: {},
|
||
disabled: {
|
||
type: Boolean,
|
||
},
|
||
removable: {
|
||
type: Boolean,
|
||
},
|
||
},
|
||
emits: [`update:modelValue`, `change`, `remove`],
|
||
setup(l, { emit: f }) {
|
||
let S = l,
|
||
C = f,
|
||
w = computed(() =>
|
||
S.options
|
||
.map((e) => {
|
||
let t = e.type === `step` || e.type === `total`,
|
||
n = Number(t ? (e.min_value ?? e.value) : e.value),
|
||
r = Number(
|
||
t ? (e.max_value ?? e.min_value ?? e.value) : e.value,
|
||
),
|
||
i = Number(e.step) > 0 ? Number(e.step) : 1;
|
||
return {
|
||
min: n,
|
||
max: n + Math.floor((r - n) / i + 1e-7) * i,
|
||
step: i,
|
||
};
|
||
})
|
||
.filter(
|
||
(e) =>
|
||
Number.isFinite(e.min) &&
|
||
Number.isFinite(e.max) &&
|
||
e.max >= e.min,
|
||
),
|
||
),
|
||
T = computed(() => Math.min(...w.value.map((e) => e.min))),
|
||
E = computed(() => Math.max(...w.value.map((e) => e.max))),
|
||
D = computed(() => T.value === E.value),
|
||
O = computed(() => Number(S.modelValue) || 0),
|
||
k = computed(() =>
|
||
D.value
|
||
? `容量 ${T.value} ${S.unit}`
|
||
: `容量 ${T.value}–${E.value} ${S.unit}`,
|
||
);
|
||
function A(e) {
|
||
// 在所有有效区间中寻找最近合法档位,不能只按全局 min/max 截断后提交任意数值。
|
||
let t = T.value,
|
||
n = 1 / 0;
|
||
for (let r of w.value) {
|
||
let i = Number(
|
||
Math.min(
|
||
r.max,
|
||
Math.max(
|
||
r.min,
|
||
r.min + Math.round((e - r.min) / r.step) * r.step,
|
||
),
|
||
).toFixed(8),
|
||
),
|
||
a = Math.abs(i - e);
|
||
a < n && ((t = i), (n = a));
|
||
}
|
||
return t;
|
||
}
|
||
function j(e, t = !0) {
|
||
if (
|
||
S.disabled ||
|
||
D.value ||
|
||
e === null ||
|
||
!Number.isFinite(e) ||
|
||
!w.value.length
|
||
)
|
||
return;
|
||
let n = A(e);
|
||
Number(S.modelValue) !== n &&
|
||
(C(`update:modelValue`, n), t && C(`change`));
|
||
}
|
||
function M(e) {
|
||
// 拖动时更新容量但不逐帧重算;结束交互由 N 单独发出 change。
|
||
let t = e.target,
|
||
n = Number(t.value);
|
||
(j(n, !1), (t.value = String(A(n))));
|
||
}
|
||
function N() {
|
||
!S.disabled && !D.value && C(`change`);
|
||
}
|
||
function P(e) {
|
||
// 方向键跨区间寻找下一合法档位,避免跨越不允许购买的容量空档。
|
||
let t = [`ArrowRight`, `ArrowUp`].includes(e.key)
|
||
? 1
|
||
: [`ArrowLeft`, `ArrowDown`].includes(e.key)
|
||
? -1
|
||
: 0;
|
||
if (!t || S.disabled || D.value) return;
|
||
e.preventDefault();
|
||
let n = O.value,
|
||
r = w.value.flatMap((e) => {
|
||
let r =
|
||
t > 0
|
||
? Math.max(
|
||
e.min,
|
||
e.min +
|
||
(Math.floor((n - e.min) / e.step + 1e-7) + 1) *
|
||
e.step,
|
||
)
|
||
: Math.min(
|
||
e.max,
|
||
e.min +
|
||
(Math.ceil((n - e.min) / e.step - 1e-7) - 1) * e.step,
|
||
);
|
||
return r >= e.min && r <= e.max ? [r] : [];
|
||
});
|
||
r.length && j(t > 0 ? Math.min(...r) : Math.max(...r));
|
||
}
|
||
function F() {
|
||
!S.disabled && S.removable && C(`remove`);
|
||
}
|
||
async function I(t) {
|
||
// PrimeVue 完成内部输入更新后再同步合法值与 aria 状态,避免显示值和实际容量不一致。
|
||
(await Promise.resolve(), await nextTick());
|
||
let n = t.originalEvent.target;
|
||
((n.value = String(O.value)),
|
||
n.setAttribute(`aria-valuenow`, String(O.value)));
|
||
}
|
||
function L(e) {
|
||
e.key === `ArrowUp` || e.key === `ArrowDown`
|
||
? (e.stopPropagation(),
|
||
P(e),
|
||
I({
|
||
originalEvent: e,
|
||
}))
|
||
: e.key === `Enter` &&
|
||
I({
|
||
originalEvent: e,
|
||
});
|
||
}
|
||
return (e, i) => {
|
||
let f = inputnumber;
|
||
return (
|
||
openBlock(),
|
||
createElementBlock(`div`, m, [
|
||
l.hideLabel
|
||
? createCommentVNode(``, !0)
|
||
: (openBlock(),
|
||
createElementBlock(`div`, h, toDisplayString(l.label), 1)),
|
||
createElementVNode(
|
||
`div`,
|
||
{
|
||
class: `storage-card`,
|
||
role: `group`,
|
||
"aria-label": l.label,
|
||
},
|
||
[
|
||
createElementVNode(`div`, _, [
|
||
createElementVNode(
|
||
`strong`,
|
||
null,
|
||
toDisplayString(l.title || `标准存储`),
|
||
1,
|
||
),
|
||
l.removable
|
||
? (openBlock(),
|
||
createElementBlock(
|
||
`button`,
|
||
{
|
||
key: 0,
|
||
class: `storage-remove`,
|
||
type: `button`,
|
||
disabled: l.disabled,
|
||
"aria-label": `删除${l.label}`,
|
||
onClick: F,
|
||
},
|
||
[
|
||
...(i[0] ||= [
|
||
createElementVNode(
|
||
`i`,
|
||
{
|
||
class: `pi pi-trash`,
|
||
"aria-hidden": `true`,
|
||
},
|
||
null,
|
||
-1,
|
||
),
|
||
]),
|
||
],
|
||
8,
|
||
v,
|
||
))
|
||
: createCommentVNode(``, !0),
|
||
]),
|
||
renderSlot(e.$slots, `type`, {}, void 0, !0),
|
||
renderSlot(
|
||
e.$slots,
|
||
`default`,
|
||
{},
|
||
() => [
|
||
unref(w).length
|
||
? (openBlock(),
|
||
createElementBlock(
|
||
Fragment,
|
||
{
|
||
key: 0,
|
||
},
|
||
[
|
||
createElementVNode(
|
||
`div`,
|
||
{
|
||
class: `storage-capacity`,
|
||
onKeydownCapture: L,
|
||
},
|
||
[
|
||
createVNode(
|
||
f,
|
||
{
|
||
"aria-label": l.label,
|
||
disabled: l.disabled || unref(D),
|
||
"model-value": unref(O),
|
||
min: unref(T),
|
||
max: unref(E),
|
||
"use-grouping": !1,
|
||
"onUpdate:modelValue": j,
|
||
onBlur: I,
|
||
},
|
||
null,
|
||
8,
|
||
[
|
||
`aria-label`,
|
||
`disabled`,
|
||
`model-value`,
|
||
`min`,
|
||
`max`,
|
||
],
|
||
),
|
||
createElementVNode(
|
||
`span`,
|
||
y,
|
||
toDisplayString(l.unit),
|
||
1,
|
||
),
|
||
],
|
||
32,
|
||
),
|
||
createElementVNode(
|
||
`input`,
|
||
{
|
||
class: `storage-slider`,
|
||
type: `range`,
|
||
"aria-label": `${l.label}容量滑块`,
|
||
"aria-valuetext": `${unref(O)} ${l.unit}`,
|
||
disabled: l.disabled || unref(D),
|
||
value: unref(O),
|
||
min: unref(T),
|
||
max: unref(E),
|
||
step: `any`,
|
||
onInput: M,
|
||
onChange: N,
|
||
onKeydown: P,
|
||
},
|
||
null,
|
||
40,
|
||
b,
|
||
),
|
||
],
|
||
64,
|
||
))
|
||
: createCommentVNode(``, !0),
|
||
],
|
||
!0,
|
||
),
|
||
l.description || unref(w).length
|
||
? (openBlock(),
|
||
createElementBlock(
|
||
`p`,
|
||
x,
|
||
toDisplayString(l.description || unref(k)),
|
||
1,
|
||
))
|
||
: createCommentVNode(``, !0),
|
||
renderSlot(e.$slots, `extra`, {}, void 0, !0),
|
||
],
|
||
8,
|
||
g,
|
||
),
|
||
renderSlot(e.$slots, `hint`, {}, void 0, !0),
|
||
])
|
||
);
|
||
};
|
||
},
|
||
}),
|
||
[[`__scopeId`, `data-v-83c644c5`]],
|
||
),
|
||
{
|
||
__name: `ConfigDiskCard`,
|
||
},
|
||
);
|
||
export {};
|
||
export default S;
|
||
</script>
|