feat: add loading state for switch component (#4688)

#### What type of PR is this?

/area console
/kind feature
/milestone 2.10.x

#### What this PR does / why we need it:

VSwitch 组件支持传入 loading 属性以显示加载状态。

此外,此 PR 为插件启动/停止的开关适配了这个特性用于测试。

<img width="460" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/b78221fe-6b53-4f8c-ba00-6cea2c45b5de">

#### Which issue(s) this PR fixes:

Fixes #4687 

#### Special notes for your reviewer:

测试插件启动/停止时是否显示加载状态

#### Does this PR introduce a user-facing change?

```release-note
Console 端的 VSwitch 组件支持传入 loading 属性以显示加载状态。
```
pull/4680/head
Ryan Wang 2023-10-08 17:42:19 +08:00 committed by GitHub
parent b2d7221316
commit df22b4b5ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 45 additions and 6 deletions

View File

@ -3,10 +3,12 @@ const props = withDefaults(
defineProps<{
modelValue?: boolean;
disabled?: boolean;
loading?: boolean;
}>(),
{
modelValue: false,
disabled: false,
loading: false,
}
);
@ -16,7 +18,7 @@ const emit = defineEmits<{
}>();
const handleChange = () => {
if (props.disabled) return;
if (props.disabled || props.loading) return;
emit("update:modelValue", !props.modelValue);
emit("change", !props.modelValue);
@ -28,13 +30,13 @@ const handleChange = () => {
:class="{
'bg-gray-200': !modelValue,
'!bg-primary': modelValue,
'switch-disabled': disabled,
'switch-disabled': disabled || loading,
}"
aria-checked="false"
class="switch-inner"
role="switch"
type="button"
:disabled="disabled"
:disabled="disabled || loading"
@click="handleChange"
>
<span
@ -45,7 +47,28 @@ const handleChange = () => {
aria-hidden="true"
class="switch-indicator"
>
<slot name="icon" />
<svg
v-if="loading"
class="animate-spin"
fill="none"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<circle
class="opacity-0"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-30"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
fill="currentColor"
></path>
</svg>
<slot v-else name="icon" />
</span>
</button>
</div>

View File

@ -1,9 +1,25 @@
import { describe, expect, it } from "vitest";
import { VSwitch } from "../index";
import { mount } from "@vue/test-utils";
import { mount, shallowMount } from "@vue/test-utils";
describe("Switch", () => {
it("should render", () => {
expect(mount(VSwitch)).toBeDefined();
});
it("emits the correct events when clicked", () => {
const wrapper = shallowMount(VSwitch);
wrapper.find("button").trigger("click");
expect(wrapper.emitted("update:modelValue")).toBeTruthy();
expect(wrapper.emitted("change")).toBeTruthy();
});
it("does not emit events when disabled or loading", () => {
const wrapper = shallowMount(VSwitch, {
props: { disabled: true, loading: true },
});
wrapper.find("button").trigger("click");
expect(wrapper.emitted("update:modelValue")).toBeFalsy();
expect(wrapper.emitted("change")).toBeFalsy();
});
});

View File

@ -21,7 +21,7 @@ const { changingStatus, changeStatus } = usePluginLifeCycle(plugin);
<div class="flex items-center">
<VSwitch
:model-value="plugin.spec.enabled"
:disabled="changingStatus"
:loading="changingStatus"
@click="changeStatus"
/>
</div>