整理
Some checks failed
/ depcheck (push) Successful in 2m24s
/ lint-build-and-check (push) Successful in 2m36s
/ build-and-deploy-to-vercel (push) Successful in 3m13s
/ surge (push) Successful in 2m40s
/ playwright (push) Failing after 8m16s

This commit is contained in:
严浩
2025-03-10 12:41:24 +08:00
parent a7b10809c1
commit 4542944f52
35 changed files with 66 additions and 60 deletions

View File

@ -0,0 +1,44 @@
<script setup lang="ts">
import type { PopconfirmProps } from 'ant-design-vue';
import type { HPopconfirmProps } from './types';
defineOptions({ inheritAttrs: true });
const props = defineProps<HPopconfirmProps>();
const _loading = shallowRef(false);
const onConfirm: PopconfirmProps['onConfirm'] = async (e) => {
if (props.onConfirm) {
try {
_loading.value = true;
await props.onConfirm(e);
} finally {
_loading.value = false;
}
}
};
</script>
<template>
<APopconfirm
:align="{
targetOffset: [0, 0],
}"
:arrow-point-at-center="!true"
:cancel-button-props="{ disabled: _loading }"
:description
:disabled="_loading"
:on-confirm
:title
placement="topRight"
>
<slot></slot>
</APopconfirm>
</template>
<style lang="less">
[class^='ant-'] .anticon svg {
vertical-align: unset; /* baseline */
}
</style>

View File

@ -0,0 +1,9 @@
import type { PopconfirmProps } from 'ant-design-vue';
export type HPopconfirmProps = {
description: PopconfirmProps['description'];
onConfirm?: (...args: PopconfirmOnConfirmParameters) => Promise<void>;
title: PopconfirmProps['title'];
};
type NotUndefined<T> = T extends undefined ? never : T;
type PopconfirmOnConfirmParameters = Parameters<NotUndefined<PopconfirmProps['onConfirm']>>;

View File

@ -0,0 +1,37 @@
<script setup lang="ts">
import { message } from 'ant-design-vue';
import HAPopconfirm from './HAPopconfirm/HAPopconfirm.vue';
async function handleConfirmAsync(e: MouseEvent) {
console.debug('handleConfirmAsync', e);
await new Promise((resolve) => {
setTimeout(resolve, 2000);
});
message.success('哇!数据已被成功送入黑洞 🕳️');
}
</script>
<template>
<ACard>
<ACard>
<HAPopconfirm title="你确定吗? 🤔 " description="别担心,我们只是假装很严肃 🎭" @confirm="handleConfirmAsync">
<AButton size="small" danger type="text">HAPopconfirm</AButton>
</HAPopconfirm>
</ACard>
<ACard class="mt-4">
<AForm name="basic" :label-col="{ style: { width: '7em' } }">
<AFormItem required label="Username">
<AInput />
</AFormItem>
<AFormItem label="Password">
<AInput />
</AFormItem>
<AFormItem>
<AButton html-type="submit" type="primary">Submit</AButton>
</AFormItem>
</AForm>
</ACard>
</ACard>
</template>
./HAPopconfirm.vue

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
const dialogRef = usePrimevueDialogRef();
</script>
<template>
<div mb-8>dialog-content.vue</div>
<div class="flex justify-end gap-2">
<Button type="button" label="关闭" severity="secondary" @click="dialogRef?.close"></Button>
<Button type="button" label="关闭" @click="dialogRef?.close"></Button>
</div>
</template>

View File

@ -0,0 +1,74 @@
import dialogContent from './__dialog-content.vue';
const dynamicComponent = defineComponent({
props: {
'defining-props': {
default: () => ({}),
type: Object as PropType<Record<string, unknown>>,
},
},
setup(props, ctx) {
const dialogRef = usePrimevueDialogRef();
return () => (
<div>
<button onClick={() => ctx.emit('close')}>emit('close')</button> <hr />
<pre>{JSON.stringify({ 'dialogRef?.data': dialogRef!.value.data }, null, 2)}</pre> <hr />
<div>PrimeVue DynamicDialog</div> <hr />
<pre>{`${JSON.stringify({ 'ctx.attrs': ctx, props }, null, 2)}`}</pre> <hr />
<pre>{JSON.stringify({ "inject('dialogRef')": dialogRef!.value }, null, 2)}</pre> <hr />
</div>
);
},
});
// https://primevue.org/dynamicdialog/
export const openDialog = async () => {
const dialog1 = DialogService.open(dynamicComponent, {
data: {
inject接收: '通过 DynamicDialogOptions.data 传递的数据',
},
emits: {
// https://github.com/primefaces/primevue/blob/bd7161298a472c8cd954e35e6a538a8bd1b1b386/packages/primevue/src/dynamicdialog/DynamicDialog.vue#L5
// ↑v-bind="instance.options.emits", 所以 props 也可以通过 emits 传递给 content 的组件
'defining-props': {
'用-props-接收': '定义在-DynamicDialogOptions.emits-的数据',
},
onClose: () => dialog1.close(),
attrs: '定义在-DynamicDialogOptions.emits-的数据,',
},
props: {
draggable: true,
header: '对话框1 可以拖动',
position: 'bottomleft',
pt: { mask: { class: 'backdrop-blur-sm' } }, // 相当于: pt:mask:class="backdrop-blur-sm"
},
});
await nextTick();
// if ($__DEV__) return;
await new Promise((resolve) => setTimeout(resolve, 300));
DialogService.open(dialogContent, {
props: {
header: '对话框2',
// draggable: false, // Header1的 draggable: true 会影响 Header2如果指定 draggable: false则不会受到影响。
},
});
};
export const openConfirm = async () => {
ConfirmationService.require({
accept: () => {
ToastService.add({ detail: '您已同意操作', life: 3000, severity: 'info', summary: '已确认' });
},
// rejectProps: { style: { display: 'none' } },
acceptProps: { label: '确定' },
header: '确认',
icon: 'pi pi-exclamation-triangle',
message: '确定要继续吗?',
reject: () => {
ToastService.add({ detail: '您已取消操作', life: 3000, severity: 'error', summary: '已取消' });
},
rejectProps: { label: '取消', outlined: true, severity: 'secondary' },
});
};

View File

@ -0,0 +1,91 @@
<script lang="ts"></script>
<script setup lang="tsx">
import type { ToastMessageOptions } from 'primevue/toast';
import { openConfirm, openDialog } from './fns';
</script>
<template>
<div class="primevue py-4 flex items-start flex-wrap gap-6">
<FloatLabel>
<InputText default-value="DEFAULT_VALUE" id="username" />
<label for="username">Username</label>
</FloatLabel>
<FloatLabel>
<DatePicker :manualInput="false" inputId="datepicker" showButtonBar dateFormat="dd/mm/yy" />
<label for="datepicker">DatePicker</label>
</FloatLabel>
<FloatLabel>
<Select
:options="[
{ name: '纽约', code: 'NY' },
{ name: '罗马', code: 'RM' },
{ name: '伦敦', code: 'LDN' },
{ name: '伊斯坦布尔', code: 'IST' },
{ name: '巴黎', code: 'PRS' },
]"
optionLabel="name"
class="min-w-[200px]"
/>
<label>SELECT</label>
</FloatLabel>
<FloatLabel w-full>
<UploadDemo />
<label>FileUpload</label>
</FloatLabel>
<FloatLabel>
<FileUpload name="demo[]" url="/api/upload" :maxFileSize="1000000" />
<label>FileUpload</label>
</FloatLabel>
<!-- <Button @click="openToast">提示服务</Button> -->
<Card>
<template #title>提示服务</template>
<template #content>
<div flex="~ wrap" gap="4">
<!-- oastService.add({ severity: 'info', summary: '提示', life: 3000, detail: '消息内容' }); -->
<!-- oastService.add({ severity: 'info', summary: '提示', life: 0, detail: '消息内容' }); -->
<Button
outlined
v-for="severity in [
'success',
'info',
'warn',
'error',
'secondary',
'contrast',
undefined,
] satisfies ToastMessageOptions['severity'][]"
:key="severity"
@click="
ToastService.add({ severity: severity, summary: `severity: ${severity}`, life: 5000, detail: '消息内容' })
"
>
{{ `${severity}` }}
</Button>
</div>
</template>
</Card>
<Paginator
:template="{
'640px': 'PrevPageLink CurrentPageReport NextPageLink',
'960px': 'FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink',
'1300px': 'FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink',
default: 'FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink JumpToPageDropdown JumpToPageInput',
}"
:rows="10"
:totalRecords="120"
>
</Paginator>
<ProgressSpinner class="w-6! h-6! m-0!" />
<Button @click="openDialog">对话框服务</Button>
<Button @click="openConfirm">确认服务</Button>
</div>
</template>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
const route = useRoute();
definePage({
meta: {
hidden: true,
},
});
</script>
<template>
<h1>Detail</h1>
<p>id: {{ route.query.id }}</p>
</template>

View File

@ -0,0 +1,130 @@
<script lang="ts">
const structuredClone = window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj)));
const K_INITIAL_STATE = deepFreeze({
complete: false,
error: null as unknown,
list: [] as Record<string, never>[],
loading: false,
page: 0,
});
let page3ShouldError = true;
const state = shallowReactive(structuredClone(K_INITIAL_STATE));
</script>
<script setup lang="ts">
defineOptions({
beforeRouteEnter: (to, from) => {
if (from.name !== 'UIComponentsInfiniteLoadingDetail') {
Object.assign(state, structuredClone(K_INITIAL_STATE));
}
},
beforeRouteLeave: (to, from) => {
if (to.name !== 'UIComponentsInfiniteLoadingDetail') {
Object.assign(state, structuredClone(K_INITIAL_STATE));
}
},
});
const refresh = () => {
state.list.splice(0, state.list.length);
state.page = 0;
state.complete = false;
loadMore();
};
type CurrentUse = 'use-intersection-observer-infinite-loading' | 'van-list';
async function loadMore() {
if (state.loading) return;
try {
state.loading = true;
const response = await fetch(
`https://jsonplaceholder.typicode.com/comments?_page=${++state.page}&_limit=1&timestamp=${Date.now()}`,
).then((res) => {
if (state.page === 3 && page3ShouldError) {
page3ShouldError = false;
throw new Error('test error');
} else {
return res;
}
});
const data = await response.json();
state.list = state.list.concat(data);
if ($__DEV__) await new Promise((resolve) => setTimeout(resolve, 500));
state.complete = state.list.length >= 5;
} catch (error) {
state.error = error;
state.page--;
} finally {
state.loading = false;
}
}
const currentUse = ref<CurrentUse>((sessionStorage.getItem('currentUse') as CurrentUse) || 'van-list');
watchEffect(() => {
sessionStorage.setItem('currentUse', currentUse.value);
});
</script>
<template>
<div mb-4 class="flex justify-end gap-4 flex-wrap items-start">
<Button label="刷新" @click="refresh" />
<Button
label="push"
@click="state.list.push({ id: state.list.length + 1, name: 'name', body: 'body', email: 'email' } as any)"
/>
<Button
:label="currentUse"
@click="currentUse = currentUse === 'van-list' ? 'use-intersection-observer-infinite-loading' : 'van-list'"
class="mb-4"
/>
</div>
<template v-if="currentUse === 'van-list'">
<VanList
@load="loadMore"
:loading="state.loading"
:error="!!state.error"
:error-text="`${state.error}`"
:onUpdate:error="() => (state.error = null)"
:finished="state.complete"
>
<template v-for="item in state.list" :key="item.id">
<div
class="border p-4 mb-[16px]"
@click="$router.push({ name: 'UIComponentsInfiniteLoadingDetail', query: { id: item.id } })"
>
<div>id:{{ item.id }}</div>
</div>
</template>
</VanList>
</template>
<template v-if="currentUse === 'use-intersection-observer-infinite-loading'">
<Card
v-for="item in state.list"
:key="item.id"
class="mb-[16px]"
@click="$router.push({ name: 'UIComponentsInfiniteLoadingDetail', query: { id: item.id } })"
>
<template #title>{{ item.name }}</template>
<template #content>
<p class="mt-[8px]">{{ item.body }}</p>
</template>
<template #subtitle>id:{{ item.id }} </template>
<template #footer>{{ item.email }}</template>
</Card>
<UseIntersectionObserverInfiniteLoading
@load="loadMore"
:loading="state.loading"
:error="!!state.error"
:error-text="`${state.error}`"
@clickError="state.error = null"
:complete="state.complete"
/>
<div border="1 px solid red rounded-lg" class="p-4 mb-[16px]">
{{ { 'list.length': state.list.length } }}
</div>
</template>
<ScrollTop />
</template>