使用场景 ​
默认 resource 模式上传后保存规范化资源,显式 native 模式维护原始 fileList。两种模式的模型、事件与实例方法各有契约。
资源上传、校验与取消 ​
示例只使用本地异步 handler,不发送网络请求。upload 返回包含 id/name/url/mimeType/size 的资源;context 提供取消信号与进度报告。maxSizeBytes/validate 在上传前检查文件。复制源码包括 handler。
资源上传、校验与取消
<template><StandaloneDemo /></template>
<script lang="ts" setup>
import * as __external0 from "vue";
import * as __external1 from "@asam-ui/ui";
const __external: any = {"vue": { ...__external0, __esModule: true },
"@asam-ui/ui": { ...__external1, __esModule: true }};
const __modules: any = {
"Demo.vue": (module: any, exports: any, require: any) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1: any = require("vue");
const vue_2: any = require("vue");
const _hoisted_1: any = { class: "example" };
const _hoisted_2: any = { class: "row" };
const vue_3: any = require("vue");
const ui_1: any = require("@asam-ui/ui");
const upload_1: any = require("./upload");
exports.default = (0, vue_1.defineComponent)({
...{ name: 'AsamUploadResourceExample' },
__name: 'Demo',
setup(__props?: any) {
const control: any = (0, vue_3.ref)();
const value: any = (0, vue_3.ref)(null);
const event: any = (0, vue_3.ref)('尚未上传');
function validate(file?: any) {
return file.name.endsWith('.txt') || '仅接受 TXT';
}
return (_ctx?: any, _cache?: any) => {
return ((0, vue_2.openBlock)(), (0, vue_2.createElementBlock)("div", _hoisted_1, [
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamUpload), (0, vue_2.mergeProps)({
ref_key: "control",
ref: control
}, {
...{ accept: '.txt' },
...{
upload: (0, vue_2.unref)(upload_1.localUpload),
'max-size-bytes': 1048576,
validate: validate,
'auto-upload': false
}
}, {
modelValue: value.value,
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event?: any) => ((value).value = $event)),
onSuccess: _cache[1] || (_cache[1] = ($event?: any) => (event.value = 'success')),
onError: _cache[2] || (_cache[2] = (error?: any) => (event.value = error.message)),
onCancelled: _cache[3] || (_cache[3] = ($event?: any) => (event.value = 'cancelled'))
}), {
trigger: (0, vue_2.withCtx)(() => [
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamButton), { type: "default" }, {
default: (0, vue_2.withCtx)(() => [...(_cache[7] || (_cache[7] = [
(0, vue_2.createTextVNode)(" 选择文本文件 ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
})
]),
tip: (0, vue_2.withCtx)(() => [...(_cache[8] || (_cache[8] = [
(0, vue_2.createTextVNode)(" 本地演示,单个 TXT,最大 1 MiB ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
}, 16 /* FULL_PROPS */, ["modelValue"]),
(0, vue_2.createElementVNode)("div", _hoisted_2, [
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamButton), {
type: "default",
onClick: _cache[4] || (_cache[4] = ($event?: any) => (control.value?.submit()))
}, {
default: (0, vue_2.withCtx)(() => [...(_cache[9] || (_cache[9] = [
(0, vue_2.createTextVNode)(" 上传 ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
}),
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamButton), {
type: "default",
onClick: _cache[5] || (_cache[5] = ($event?: any) => (control.value?.cancel()))
}, {
default: (0, vue_2.withCtx)(() => [...(_cache[10] || (_cache[10] = [
(0, vue_2.createTextVNode)(" 取消 ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
}),
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamButton), {
type: "default",
onClick: _cache[6] || (_cache[6] = ($event?: any) => (control.value?.clear()))
}, {
default: (0, vue_2.withCtx)(() => [...(_cache[11] || (_cache[11] = [
(0, vue_2.createTextVNode)(" 清除 ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
})
]),
(0, vue_2.createElementVNode)("output", null, "资源:" + (0, vue_2.toDisplayString)(JSON.stringify(value.value)) + ";" + (0, vue_2.toDisplayString)(event.value), 1 /* TEXT */)
]));
};
}
});
exports.default.__scopeId = "data-v-0900544a";
},
"upload.ts": (module: any, exports: any, require: any) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.localUpload = void 0;
const localUpload: any = async ({ file }: any, context?: any) => {
context.reportProgress({ loaded: 0, total: file.size });
await new Promise((resolve?: any, reject?: any) => {
const timer: any = setTimeout(resolve, 250);
context.signal.addEventListener('abort', () => {
clearTimeout(timer);
reject(new Error('cancelled'));
}, { once: true });
});
if (context.signal.aborted)
throw new Error('cancelled');
context.reportProgress({ loaded: file.size, total: file.size });
return {
id: file.name + ':' + file.size,
name: file.name,
url: '/local-demo/' + encodeURIComponent(file.name),
mimeType: file.type || 'application/octet-stream',
size: file.size
};
};
exports.localUpload = localUpload;
},
};
const __dependencies: any = {"Demo.vue":{"./upload":"upload.ts"},"upload.ts":{}};
const __cache: any = Object.create(null);
function __require(id: any): any {
if (__external[id]) return __external[id];
if (__cache[id]) return __cache[id].exports;
const module: any = { exports: {} };
__cache[id] = module;
__modules[id](module, module.exports, (dependency: any) => __require(__dependencies[id][dependency] || dependency));
return module.exports;
}
const StandaloneDemo = __require('Demo.vue').default;
</script>
<style>
.example[data-v-0900544a] {
display: grid;
gap: var(--asam-space-md, 16px);
min-width: 0;
}
.row[data-v-0900544a] {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--asam-space-sm, 8px);
}
.density-grid[data-v-0900544a] {
display: grid;
gap: var(--asam-space-md, 16px);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}
section[data-v-0900544a] {
display: grid;
gap: var(--asam-space-sm, 8px);
min-width: 0;
}
output[data-v-0900544a] {
overflow-wrap: anywhere;
color: var(--asam-color-text-secondary);
}
</style>
<template><StandaloneDemo /></template>
<script setup>
import * as __external0 from "vue";
import * as __external1 from "@asam-ui/ui";
const __external = {"vue": { ...__external0, __esModule: true },
"@asam-ui/ui": { ...__external1, __esModule: true }};
const __modules = {
"Demo.vue": (module, exports, require) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1 = require("vue");
const _hoisted_1 = { class: "example" };
const _hoisted_2 = { class: "row" };
const vue_2 = require("vue");
const ui_1 = require("@asam-ui/ui");
const upload_1 = require("./upload");
exports.default = Object.assign({ name: 'AsamUploadResourceExample' }, {
__name: 'Demo',
setup(__props) {
const control = (0, vue_2.ref)();
const value = (0, vue_2.ref)(null);
const event = (0, vue_2.ref)('尚未上传');
function validate(file) {
return file.name.endsWith('.txt') || '仅接受 TXT';
}
return (_ctx, _cache) => {
return ((0, vue_1.openBlock)(), (0, vue_1.createElementBlock)("div", _hoisted_1, [
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamUpload), (0, vue_1.mergeProps)({
ref_key: "control",
ref: control
}, {
...{ accept: '.txt' },
...{
upload: (0, vue_1.unref)(upload_1.localUpload),
'max-size-bytes': 1048576,
validate: validate,
'auto-upload': false
}
}, {
modelValue: value.value,
"onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((value).value = $event)),
onSuccess: _cache[1] || (_cache[1] = $event => (event.value = 'success')),
onError: _cache[2] || (_cache[2] = (error) => (event.value = error.message)),
onCancelled: _cache[3] || (_cache[3] = $event => (event.value = 'cancelled'))
}), {
trigger: (0, vue_1.withCtx)(() => [
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamButton), { type: "default" }, {
default: (0, vue_1.withCtx)(() => [...(_cache[7] || (_cache[7] = [
(0, vue_1.createTextVNode)(" 选择文本文件 ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
})
]),
tip: (0, vue_1.withCtx)(() => [...(_cache[8] || (_cache[8] = [
(0, vue_1.createTextVNode)(" 本地演示,单个 TXT,最大 1 MiB ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
}, 16 /* FULL_PROPS */, ["modelValue"]),
(0, vue_1.createElementVNode)("div", _hoisted_2, [
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamButton), {
type: "default",
onClick: _cache[4] || (_cache[4] = $event => (control.value?.submit()))
}, {
default: (0, vue_1.withCtx)(() => [...(_cache[9] || (_cache[9] = [
(0, vue_1.createTextVNode)(" 上传 ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
}),
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamButton), {
type: "default",
onClick: _cache[5] || (_cache[5] = $event => (control.value?.cancel()))
}, {
default: (0, vue_1.withCtx)(() => [...(_cache[10] || (_cache[10] = [
(0, vue_1.createTextVNode)(" 取消 ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
}),
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamButton), {
type: "default",
onClick: _cache[6] || (_cache[6] = $event => (control.value?.clear()))
}, {
default: (0, vue_1.withCtx)(() => [...(_cache[11] || (_cache[11] = [
(0, vue_1.createTextVNode)(" 清除 ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
})
]),
(0, vue_1.createElementVNode)("output", null, "资源:" + (0, vue_1.toDisplayString)(JSON.stringify(value.value)) + ";" + (0, vue_1.toDisplayString)(event.value), 1 /* TEXT */)
]));
};
}
});
exports.default.__scopeId = "data-v-5c26fb59";
},
"upload.js": (module, exports, require) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.localUpload = void 0;
const localUpload = async ({ file }, context) => {
context.reportProgress({ loaded: 0, total: file.size });
await new Promise((resolve, reject) => {
const timer = setTimeout(resolve, 250);
context.signal.addEventListener('abort', () => {
clearTimeout(timer);
reject(new Error('cancelled'));
}, { once: true });
});
if (context.signal.aborted)
throw new Error('cancelled');
context.reportProgress({ loaded: file.size, total: file.size });
return {
id: file.name + ':' + file.size,
name: file.name,
url: '/local-demo/' + encodeURIComponent(file.name),
mimeType: file.type || 'application/octet-stream',
size: file.size
};
};
exports.localUpload = localUpload;
},
};
const __dependencies = {"Demo.vue":{"./upload":"upload.js"},"upload.js":{}};
const __cache = Object.create(null);
function __require(id) {
if (__external[id]) return __external[id];
if (__cache[id]) return __cache[id].exports;
const module = { exports: {} };
__cache[id] = module;
__modules[id](module, module.exports, (dependency) => __require(__dependencies[id][dependency] || dependency));
return module.exports;
}
const StandaloneDemo = __require('Demo.vue').default;
</script>
<style>
.example[data-v-5c26fb59] {
display: grid;
gap: var(--asam-space-md, 16px);
min-width: 0;
}
.row[data-v-5c26fb59] {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--asam-space-sm, 8px);
}
.density-grid[data-v-5c26fb59] {
display: grid;
gap: var(--asam-space-md, 16px);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}
section[data-v-5c26fb59] {
display: grid;
gap: var(--asam-space-sm, 8px);
min-width: 0;
}
output[data-v-5c26fb59] {
overflow-wrap: anywhere;
color: var(--asam-color-text-secondary);
}
</style>
多文件与文件插槽 ​
multiple 模型必须是资源数组。file 插槽提供 item、file、index、remove、cancel;保留移除和取消入口。progress 由 handler 的 reportProgress 更新。
多文件与文件插槽
<template><StandaloneDemo /></template>
<script lang="ts" setup>
import * as __external0 from "vue";
import * as __external1 from "@asam-ui/ui";
const __external: any = {"vue": { ...__external0, __esModule: true },
"@asam-ui/ui": { ...__external1, __esModule: true }};
const __modules: any = {
"Demo.vue": (module: any, exports: any, require: any) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1: any = require("vue");
const vue_2: any = require("vue");
const _hoisted_1: any = { class: "example" };
const vue_3: any = require("vue");
const ui_1: any = require("@asam-ui/ui");
const upload_1: any = require("./upload");
exports.default = (0, vue_1.defineComponent)({
...{ name: 'AsamUploadMultipleExample' },
__name: 'Demo',
setup(__props?: any) {
const value: any = (0, vue_3.ref)([]);
return (_ctx?: any, _cache?: any) => {
return ((0, vue_2.openBlock)(), (0, vue_2.createElementBlock)("div", _hoisted_1, [
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamUpload), (0, vue_2.mergeProps)({
modelValue: value.value,
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event?: any) => ((value).value = $event))
}, { ...{ upload: (0, vue_2.unref)(upload_1.localUpload), limit: 3 }, ...{ multiple: true, drag: true } }), {
trigger: (0, vue_2.withCtx)(() => [...(_cache[1] || (_cache[1] = [
(0, vue_2.createTextVNode)(" 拖入文件或点击选择(最多三个) ", -1 /* CACHED */)
]))]),
file: (0, vue_2.withCtx)(({ item, remove, cancel }: any) => [
(0, vue_2.createElementVNode)("span", null, (0, vue_2.toDisplayString)(item.name) + " · " + (0, vue_2.toDisplayString)(item.status) + " · " + (0, vue_2.toDisplayString)(item.percent ?? 0) + "% ", 1 /* TEXT */),
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamButton), {
type: "default",
onClick: remove
}, {
default: (0, vue_2.withCtx)(() => [...(_cache[2] || (_cache[2] = [
(0, vue_2.createTextVNode)(" 移除 ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
}, 8 /* PROPS */, ["onClick"]),
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamButton), {
type: "default",
onClick: cancel
}, {
default: (0, vue_2.withCtx)(() => [...(_cache[3] || (_cache[3] = [
(0, vue_2.createTextVNode)(" 取消 ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
}, 8 /* PROPS */, ["onClick"])
]),
_: 1 /* STABLE */
}, 16 /* FULL_PROPS */, ["modelValue"]),
(0, vue_2.createElementVNode)("output", null, "资源:" + (0, vue_2.toDisplayString)(value.value), 1 /* TEXT */)
]));
};
}
});
exports.default.__scopeId = "data-v-55dad38a";
},
"upload.ts": (module: any, exports: any, require: any) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.localUpload = void 0;
const localUpload: any = async ({ file }: any, context?: any) => {
context.reportProgress({ loaded: 0, total: file.size });
await new Promise((resolve?: any, reject?: any) => {
const timer: any = setTimeout(resolve, 250);
context.signal.addEventListener('abort', () => {
clearTimeout(timer);
reject(new Error('cancelled'));
}, { once: true });
});
if (context.signal.aborted)
throw new Error('cancelled');
context.reportProgress({ loaded: file.size, total: file.size });
return {
id: file.name + ':' + file.size,
name: file.name,
url: '/local-demo/' + encodeURIComponent(file.name),
mimeType: file.type || 'application/octet-stream',
size: file.size
};
};
exports.localUpload = localUpload;
},
};
const __dependencies: any = {"Demo.vue":{"./upload":"upload.ts"},"upload.ts":{}};
const __cache: any = Object.create(null);
function __require(id: any): any {
if (__external[id]) return __external[id];
if (__cache[id]) return __cache[id].exports;
const module: any = { exports: {} };
__cache[id] = module;
__modules[id](module, module.exports, (dependency: any) => __require(__dependencies[id][dependency] || dependency));
return module.exports;
}
const StandaloneDemo = __require('Demo.vue').default;
</script>
<style>
.example[data-v-55dad38a] {
display: grid;
gap: var(--asam-space-md, 16px);
min-width: 0;
}
.row[data-v-55dad38a] {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--asam-space-sm, 8px);
}
.density-grid[data-v-55dad38a] {
display: grid;
gap: var(--asam-space-md, 16px);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}
section[data-v-55dad38a] {
display: grid;
gap: var(--asam-space-sm, 8px);
min-width: 0;
}
output[data-v-55dad38a] {
overflow-wrap: anywhere;
color: var(--asam-color-text-secondary);
}
</style>
<template><StandaloneDemo /></template>
<script setup>
import * as __external0 from "vue";
import * as __external1 from "@asam-ui/ui";
const __external = {"vue": { ...__external0, __esModule: true },
"@asam-ui/ui": { ...__external1, __esModule: true }};
const __modules = {
"Demo.vue": (module, exports, require) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1 = require("vue");
const _hoisted_1 = { class: "example" };
const vue_2 = require("vue");
const ui_1 = require("@asam-ui/ui");
const upload_1 = require("./upload");
exports.default = Object.assign({ name: 'AsamUploadMultipleExample' }, {
__name: 'Demo',
setup(__props) {
const value = (0, vue_2.ref)([]);
return (_ctx, _cache) => {
return ((0, vue_1.openBlock)(), (0, vue_1.createElementBlock)("div", _hoisted_1, [
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamUpload), (0, vue_1.mergeProps)({
modelValue: value.value,
"onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((value).value = $event))
}, { ...{ upload: (0, vue_1.unref)(upload_1.localUpload), limit: 3 }, ...{ multiple: true, drag: true } }), {
trigger: (0, vue_1.withCtx)(() => [...(_cache[1] || (_cache[1] = [
(0, vue_1.createTextVNode)(" 拖入文件或点击选择(最多三个) ", -1 /* CACHED */)
]))]),
file: (0, vue_1.withCtx)(({ item, remove, cancel }) => [
(0, vue_1.createElementVNode)("span", null, (0, vue_1.toDisplayString)(item.name) + " · " + (0, vue_1.toDisplayString)(item.status) + " · " + (0, vue_1.toDisplayString)(item.percent ?? 0) + "% ", 1 /* TEXT */),
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamButton), {
type: "default",
onClick: remove
}, {
default: (0, vue_1.withCtx)(() => [...(_cache[2] || (_cache[2] = [
(0, vue_1.createTextVNode)(" 移除 ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
}, 8 /* PROPS */, ["onClick"]),
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamButton), {
type: "default",
onClick: cancel
}, {
default: (0, vue_1.withCtx)(() => [...(_cache[3] || (_cache[3] = [
(0, vue_1.createTextVNode)(" 取消 ", -1 /* CACHED */)
]))]),
_: 1 /* STABLE */
}, 8 /* PROPS */, ["onClick"])
]),
_: 1 /* STABLE */
}, 16 /* FULL_PROPS */, ["modelValue"]),
(0, vue_1.createElementVNode)("output", null, "资源:" + (0, vue_1.toDisplayString)(value.value), 1 /* TEXT */)
]));
};
}
});
exports.default.__scopeId = "data-v-0c101ac8";
},
"upload.js": (module, exports, require) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.localUpload = void 0;
const localUpload = async ({ file }, context) => {
context.reportProgress({ loaded: 0, total: file.size });
await new Promise((resolve, reject) => {
const timer = setTimeout(resolve, 250);
context.signal.addEventListener('abort', () => {
clearTimeout(timer);
reject(new Error('cancelled'));
}, { once: true });
});
if (context.signal.aborted)
throw new Error('cancelled');
context.reportProgress({ loaded: file.size, total: file.size });
return {
id: file.name + ':' + file.size,
name: file.name,
url: '/local-demo/' + encodeURIComponent(file.name),
mimeType: file.type || 'application/octet-stream',
size: file.size
};
};
exports.localUpload = localUpload;
},
};
const __dependencies = {"Demo.vue":{"./upload":"upload.js"},"upload.js":{}};
const __cache = Object.create(null);
function __require(id) {
if (__external[id]) return __external[id];
if (__cache[id]) return __cache[id].exports;
const module = { exports: {} };
__cache[id] = module;
__modules[id](module, module.exports, (dependency) => __require(__dependencies[id][dependency] || dependency));
return module.exports;
}
const StandaloneDemo = __require('Demo.vue').default;
</script>
<style>
.example[data-v-0c101ac8] {
display: grid;
gap: var(--asam-space-md, 16px);
min-width: 0;
}
.row[data-v-0c101ac8] {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--asam-space-sm, 8px);
}
.density-grid[data-v-0c101ac8] {
display: grid;
gap: var(--asam-space-md, 16px);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}
section[data-v-0c101ac8] {
display: grid;
gap: var(--asam-space-sm, 8px);
min-width: 0;
}
output[data-v-0c101ac8] {
overflow-wrap: anywhere;
color: var(--asam-color-text-secondary);
}
</style>
原始文件列表与手动提交 ​
mode=native 使用 v-model:fileList 和原始 httpRequest/hooks。本地 httpRequest 调用 onSuccess,不发送请求;没有 resource modelValue 或资源事件。abort/clearFiles 操作原始上传实例。
原始文件列表与手动提交
<template>
<div class="example">
<AsamUpload
ref="control"
v-bind="{
...{ mode: 'native' },
...{
'http-request': upload,
'auto-upload': false,
'on-success': () => (event = 'native success')
}
}"
v-model:file-list="files"
>
<template #trigger>
<AsamButton type="default"> 选择文件 </AsamButton>
</template>
<template #tip> 原始文件列表本地演示 </template>
</AsamUpload>
<div class="row">
<AsamButton type="default" @click="control?.submit()"> 提交列表 </AsamButton>
<AsamButton type="default" @click="control?.abort()"> 取消原始请求 </AsamButton>
<AsamButton type="default" @click="control?.clearFiles()"> 清空列表 </AsamButton>
</div>
<output>数量:{{ files.length }};{{ event }}</output>
</div>
</template>
<script lang="ts" setup>
defineOptions({ name: 'AsamUploadNativeExample' });
import { ref } from 'vue';
import { AsamUpload, AsamButton } from '@asam-ui/ui';
import type { AsamNativeUploadExpose, AsamNativeUploadProps } from '@asam-ui/ui';
const control = ref<AsamNativeUploadExpose>();
const files = ref<NonNullable<AsamNativeUploadProps['fileList']>>([]);
const event = ref('尚未上传');
const upload: NonNullable<AsamNativeUploadProps['httpRequest']> = (options) => {
options.onSuccess({ local: true });
return Promise.resolve({ local: true });
};
</script>
<style scoped>
.example {
display: grid;
gap: var(--asam-space-md, 16px);
min-width: 0;
}
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--asam-space-sm, 8px);
}
.density-grid {
display: grid;
gap: var(--asam-space-md, 16px);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}
section {
display: grid;
gap: var(--asam-space-sm, 8px);
min-width: 0;
}
output {
overflow-wrap: anywhere;
color: var(--asam-color-text-secondary);
}
</style>
<template>
<div class="example">
<AsamUpload
ref="control"
v-bind="{
...{ mode: 'native' },
...{
'http-request': upload,
'auto-upload': false,
'on-success': () => (event = 'native success')
}
}"
v-model:file-list="files"
>
<template #trigger>
<AsamButton type="default"> 选择文件 </AsamButton>
</template>
<template #tip> 原始文件列表本地演示 </template>
</AsamUpload>
<div class="row">
<AsamButton type="default" @click="control?.submit()"> 提交列表 </AsamButton>
<AsamButton type="default" @click="control?.abort()"> 取消原始请求 </AsamButton>
<AsamButton type="default" @click="control?.clearFiles()"> 清空列表 </AsamButton>
</div>
<output>数量:{{ files.length }};{{ event }}</output>
</div>
</template>
<script setup>
defineOptions({ name: 'AsamUploadNativeExample' });
import { ref } from 'vue';
import { AsamUpload, AsamButton } from '@asam-ui/ui';
const control = ref();
const files = ref([]);
const event = ref('尚未上传');
const upload = (options) => {
options.onSuccess({ local: true });
return Promise.resolve({ local: true });
};
</script>
<style scoped>
.example {
display: grid;
gap: var(--asam-space-md, 16px);
min-width: 0;
}
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--asam-space-sm, 8px);
}
.density-grid {
display: grid;
gap: var(--asam-space-md, 16px);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}
section {
display: grid;
gap: var(--asam-space-sm, 8px);
min-width: 0;
}
output {
overflow-wrap: anywhere;
color: var(--asam-color-text-secondary);
}
</style>
四档尺寸与上下文继承 ​
四个实例分别显式使用 large、big、default、small。下面省略 size 的实例随 ConfigProvider 响应切换;显式 size 覆盖祖先。面板本身没有 size 契约的入口不附加虚构尺寸属性。
四档尺寸与上下文继承
<template>
<div class="example">
<div class="density-grid">
<section data-density="large">
<strong> large </strong>
<div data-mode="native">
<AsamUpload size="large" mode="native" :auto-upload="false" />
</div>
<div data-mode="resource">
<AsamUpload
v-model="models.large"
size="large"
:upload="upload"
:auto-upload="false"
/>
</div>
</section>
<section data-density="big">
<strong> big </strong>
<div data-mode="native">
<AsamUpload size="big" mode="native" :auto-upload="false" />
</div>
<div data-mode="resource">
<AsamUpload
v-model="models.big"
size="big"
:upload="upload"
:auto-upload="false"
/>
</div>
</section>
<section data-density="default">
<strong> default </strong>
<div data-mode="native">
<AsamUpload size="default" mode="native" :auto-upload="false" />
</div>
<div data-mode="resource">
<AsamUpload
v-model="models.default"
size="default"
:upload="upload"
:auto-upload="false"
/>
</div>
</section>
<section data-density="small">
<strong> small </strong>
<div data-mode="native">
<AsamUpload size="small" mode="native" :auto-upload="false" />
</div>
<div data-mode="resource">
<AsamUpload
v-model="models.small"
size="small"
:upload="upload"
:auto-upload="false"
/>
</div>
</section>
</div>
<AsamConfigProvider :size="density">
<section data-density="inherited">
<div data-mode="native"><AsamUpload mode="native" :auto-upload="false" /></div>
<div data-mode="resource">
<AsamUpload v-model="inheritedModel" :upload="upload" :auto-upload="false" />
</div>
<div data-mode="override">
<AsamUpload size="small" mode="native" :auto-upload="false" />
</div>
</section>
</AsamConfigProvider>
<div class="row">
<AsamButton type="default" @click="density = 'large'">继承 large</AsamButton>
<AsamButton type="default" @click="density = 'default'">继承 default</AsamButton>
<AsamButton type="default" @click="density = 'small'"> 继承 small </AsamButton>
<AsamButton type="default" @click="density = 'big'"> 继承 big </AsamButton>
</div>
<output>配置提供器:{{ density }}</output>
</div>
</template>
<script lang="ts" setup>
defineOptions({ name: 'AsamUploadSizesExample' });
import { reactive, ref } from 'vue';
import { AsamUpload, AsamConfigProvider, AsamButton } from '@asam-ui/ui';
import type { AsamControlSize, AsamUploadedResource, AsamUploadHandler } from '@asam-ui/ui';
const models = reactive<Record<AsamControlSize, AsamUploadedResource | null>>({
large: null,
big: null,
default: null,
small: null
});
const inheritedModel = ref<AsamUploadedResource | null>(null);
const upload: AsamUploadHandler = async ({ file }) => ({
id: file.name,
name: file.name,
url: '/local-demo',
mimeType: file.type,
size: file.size
});
const density = ref<AsamControlSize>('big');
</script>
<style scoped>
.example {
display: grid;
gap: var(--asam-space-md, 16px);
min-width: 0;
}
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--asam-space-sm, 8px);
}
.density-grid {
display: grid;
gap: var(--asam-space-md, 16px);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}
section {
display: grid;
gap: var(--asam-space-sm, 8px);
min-width: 0;
}
output {
overflow-wrap: anywhere;
color: var(--asam-color-text-secondary);
}
</style>
<template>
<div class="example">
<div class="density-grid">
<section data-density="large">
<strong> large </strong>
<div data-mode="native">
<AsamUpload size="large" mode="native" :auto-upload="false" />
</div>
<div data-mode="resource">
<AsamUpload
v-model="models.large"
size="large"
:upload="upload"
:auto-upload="false"
/>
</div>
</section>
<section data-density="big">
<strong> big </strong>
<div data-mode="native">
<AsamUpload size="big" mode="native" :auto-upload="false" />
</div>
<div data-mode="resource">
<AsamUpload
v-model="models.big"
size="big"
:upload="upload"
:auto-upload="false"
/>
</div>
</section>
<section data-density="default">
<strong> default </strong>
<div data-mode="native">
<AsamUpload size="default" mode="native" :auto-upload="false" />
</div>
<div data-mode="resource">
<AsamUpload
v-model="models.default"
size="default"
:upload="upload"
:auto-upload="false"
/>
</div>
</section>
<section data-density="small">
<strong> small </strong>
<div data-mode="native">
<AsamUpload size="small" mode="native" :auto-upload="false" />
</div>
<div data-mode="resource">
<AsamUpload
v-model="models.small"
size="small"
:upload="upload"
:auto-upload="false"
/>
</div>
</section>
</div>
<AsamConfigProvider :size="density">
<section data-density="inherited">
<div data-mode="native"><AsamUpload mode="native" :auto-upload="false" /></div>
<div data-mode="resource">
<AsamUpload v-model="inheritedModel" :upload="upload" :auto-upload="false" />
</div>
<div data-mode="override">
<AsamUpload size="small" mode="native" :auto-upload="false" />
</div>
</section>
</AsamConfigProvider>
<div class="row">
<AsamButton type="default" @click="density = 'large'">继承 large</AsamButton>
<AsamButton type="default" @click="density = 'default'">继承 default</AsamButton>
<AsamButton type="default" @click="density = 'small'"> 继承 small </AsamButton>
<AsamButton type="default" @click="density = 'big'"> 继承 big </AsamButton>
</div>
<output>配置提供器:{{ density }}</output>
</div>
</template>
<script setup>
defineOptions({ name: 'AsamUploadSizesExample' });
import { reactive, ref } from 'vue';
import { AsamUpload, AsamConfigProvider, AsamButton } from '@asam-ui/ui';
const models = reactive({ large: null, big: null, default: null, small: null });
const inheritedModel = ref(null);
const upload = async ({ file }) => ({
id: file.name,
name: file.name,
url: '/local-demo',
mimeType: file.type,
size: file.size
});
const density = ref('big');
</script>
<style scoped>
.example {
display: grid;
gap: var(--asam-space-md, 16px);
min-width: 0;
}
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--asam-space-sm, 8px);
}
.density-grid {
display: grid;
gap: var(--asam-space-md, 16px);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}
section {
display: grid;
gap: var(--asam-space-sm, 8px);
min-width: 0;
}
output {
overflow-wrap: anywhere;
color: var(--asam-color-text-secondary);
}
</style>
原生照片墙、缩略图与受控重排 ​
native 模式完整承接 text、picture、picture-card 三种 listType。 完整说明。
照片墙、缩略图、校验与重排
<template>
<div class="example">
<div class="controls">
<div class="control-field">
<label for="control-listType">列表类型</label>
<AsamSelect id="control-listType" v-model="listType">
<AsamOption :label="'text'" :value="'text'">text</AsamOption>
<AsamOption :label="'picture'" :value="'picture'">picture</AsamOption>
<AsamOption :label="'picture-card'" :value="'picture-card'"
>picture-card</AsamOption
>
</AsamSelect>
</div>
<AsamCheckboxItem v-model="allowRemoval"> 允许移除</AsamCheckboxItem>
<AsamCheckboxItem v-model="custom"> 自定义缩略图</AsamCheckboxItem>
<AsamCheckboxItem v-model="disabled"> 禁用</AsamCheckboxItem>
</div>
<AsamUpload
ref="control"
v-model:file-list="files"
mode="native"
:list-type="listType"
:auto-upload="false"
:http-request="upload"
:disabled="disabled"
accept="image/*"
crossorigin="anonymous"
:on-progress="(event) => progresses.push(event.percent)"
:on-change="() => changes++"
:before-remove="beforeRemove"
:before-upload="beforeUpload"
:on-preview="preview"
:on-success="(_response, file) => (result = `已上传 ${file.name}`)"
:on-error="(_error, file) => (result = `上传失败 ${file.name}`)"
:on-remove="(file) => (result = `已移除 ${file.name}`)"
>
<template #tip>选择图片后可手动提交;移除由异步 beforeRemove 判定。</template>
<template v-if="custom" #file="{ file, index }">
<div class="thumbnail">
<AsamImage v-if="file.url" :src="file.url" :alt="file.name" />
<span>{{ index }}:{{ file.name }} / {{ file.status }}</span>
<AsamButton type="default" :disabled="disabled" @click="preview(file)">
预览 {{ file.name }}
</AsamButton>
<AsamButton
type="default"
:disabled="disabled"
@click="control?.handleRemove(file)"
>
移除 {{ file.name }}
</AsamButton>
<AsamButton type="default" :disabled="disabled" @click="control?.abort(file)">
取消 {{ file.name }}
</AsamButton>
</div>
</template>
</AsamUpload>
<div class="controls">
<AsamButton type="default" @click="control?.submit()">提交照片</AsamButton>
<AsamButton type="default" @click="files.reverse()">重排照片</AsamButton>
<AsamButton type="default" @click="control?.clearFiles(['success'])"
>清除成功照片</AsamButton
>
</div>
<output>
{{ result }};{{ files.map((file) => file.name).join(',') }};进度
{{ progresses.join(',') }};change 次数 {{ changes }}
</output>
</div>
</template>
<script lang="ts" setup>
defineOptions({ name: 'AsamUploadGalleryExample' });
import { ref } from 'vue';
import {
AsamImage,
AsamSelect,
AsamOption,
AsamCheckboxItem,
AsamUpload,
AsamButton
} from '@asam-ui/ui';
import type { AsamNativeUploadProps, AsamNativeUploadExpose } from '@asam-ui/ui';
const control = ref<AsamNativeUploadExpose>();
const files = ref<NonNullable<AsamNativeUploadProps['fileList']>>([]);
const listType = ref<'text' | 'picture' | 'picture-card'>('picture-card');
const custom = ref(false);
const disabled = ref(false);
const allowRemoval = ref(false);
const result = ref('照片墙与受控重排');
const progresses = ref<number[]>([]);
const changes = ref(0);
const upload: NonNullable<AsamNativeUploadProps['httpRequest']> = async (options) => {
options.onProgress(Object.assign(new ProgressEvent('progress'), { percent: 50 }));
if (options.file.name.includes('fail')) throw new Error('本地失败演示');
return { local: true, name: options.file.name };
};
const beforeUpload: NonNullable<AsamNativeUploadProps['beforeUpload']> = (file) => {
const valid = file.type.startsWith('image/') && file.size <= 1024 * 1024;
if (!valid) result.value = '只接受 1MB 以内图片';
return valid;
};
const beforeRemove: NonNullable<AsamNativeUploadProps['beforeRemove']> = async () => {
await new Promise((resolve) => setTimeout(resolve, 50));
if (!allowRemoval.value) result.value = '移除被 beforeRemove 拒绝';
return allowRemoval.value;
};
const preview: NonNullable<AsamNativeUploadProps['onPreview']> = (file) => {
result.value = `预览 ${file.name}`;
};
</script>
<style scoped>
.control-field {
display: grid;
gap: var(--asam-space-sm);
}
.example {
display: grid;
gap: var(--asam-space-md);
}
.controls {
display: flex;
flex-wrap: wrap;
gap: var(--asam-space-sm);
}
.thumbnail {
display: grid;
gap: var(--asam-space-xs);
}
.thumbnail img {
width: 80px;
height: 60px;
object-fit: contain;
}
</style>
<template>
<div class="example">
<div class="controls">
<div class="control-field">
<label for="control-listType">列表类型</label>
<AsamSelect id="control-listType" v-model="listType">
<AsamOption :label="'text'" :value="'text'">text</AsamOption>
<AsamOption :label="'picture'" :value="'picture'">picture</AsamOption>
<AsamOption :label="'picture-card'" :value="'picture-card'"
>picture-card</AsamOption
>
</AsamSelect>
</div>
<AsamCheckboxItem v-model="allowRemoval"> 允许移除</AsamCheckboxItem>
<AsamCheckboxItem v-model="custom"> 自定义缩略图</AsamCheckboxItem>
<AsamCheckboxItem v-model="disabled"> 禁用</AsamCheckboxItem>
</div>
<AsamUpload
ref="control"
v-model:file-list="files"
mode="native"
:list-type="listType"
:auto-upload="false"
:http-request="upload"
:disabled="disabled"
accept="image/*"
crossorigin="anonymous"
:on-progress="(event) => progresses.push(event.percent)"
:on-change="() => changes++"
:before-remove="beforeRemove"
:before-upload="beforeUpload"
:on-preview="preview"
:on-success="(_response, file) => (result = `已上传 ${file.name}`)"
:on-error="(_error, file) => (result = `上传失败 ${file.name}`)"
:on-remove="(file) => (result = `已移除 ${file.name}`)"
>
<template #tip>选择图片后可手动提交;移除由异步 beforeRemove 判定。</template>
<template v-if="custom" #file="{ file, index }">
<div class="thumbnail">
<AsamImage v-if="file.url" :src="file.url" :alt="file.name" />
<span>{{ index }}:{{ file.name }} / {{ file.status }}</span>
<AsamButton type="default" :disabled="disabled" @click="preview(file)">
预览 {{ file.name }}
</AsamButton>
<AsamButton
type="default"
:disabled="disabled"
@click="control?.handleRemove(file)"
>
移除 {{ file.name }}
</AsamButton>
<AsamButton type="default" :disabled="disabled" @click="control?.abort(file)">
取消 {{ file.name }}
</AsamButton>
</div>
</template>
</AsamUpload>
<div class="controls">
<AsamButton type="default" @click="control?.submit()">提交照片</AsamButton>
<AsamButton type="default" @click="files.reverse()">重排照片</AsamButton>
<AsamButton type="default" @click="control?.clearFiles(['success'])"
>清除成功照片</AsamButton
>
</div>
<output>
{{ result }};{{ files.map((file) => file.name).join(',') }};进度
{{ progresses.join(',') }};change 次数 {{ changes }}
</output>
</div>
</template>
<script setup>
defineOptions({ name: 'AsamUploadGalleryExample' });
import { ref } from 'vue';
import {
AsamImage,
AsamSelect,
AsamOption,
AsamCheckboxItem,
AsamUpload,
AsamButton
} from '@asam-ui/ui';
const control = ref();
const files = ref([]);
const listType = ref('picture-card');
const custom = ref(false);
const disabled = ref(false);
const allowRemoval = ref(false);
const result = ref('照片墙与受控重排');
const progresses = ref([]);
const changes = ref(0);
const upload = async (options) => {
options.onProgress(Object.assign(new ProgressEvent('progress'), { percent: 50 }));
if (options.file.name.includes('fail')) throw new Error('本地失败演示');
return { local: true, name: options.file.name };
};
const beforeUpload = (file) => {
const valid = file.type.startsWith('image/') && file.size <= 1024 * 1024;
if (!valid) result.value = '只接受 1MB 以内图片';
return valid;
};
const beforeRemove = async () => {
await new Promise((resolve) => setTimeout(resolve, 50));
if (!allowRemoval.value) result.value = '移除被 beforeRemove 拒绝';
return allowRemoval.value;
};
const preview = (file) => {
result.value = `预览 ${file.name}`;
};
</script>
<style scoped>
.control-field {
display: grid;
gap: var(--asam-space-sm);
}
.example {
display: grid;
gap: var(--asam-space-md);
}
.controls {
display: flex;
flex-wrap: wrap;
gap: var(--asam-space-sm);
}
.thumbnail {
display: grid;
gap: var(--asam-space-xs);
}
.thumbnail img {
width: 80px;
height: 60px;
object-fit: contain;
}
</style>
v-model:fileList 为受控原始列表,使用方可重排或外部替换;uid 保持文件身份。 完整说明。
隐藏列表后的头像替换 ​
下面使用 native、showFileList=false 和 default 插槽呈现选择入口,成功后以头像替换入口内容。 完整说明。
头像替换、类型大小校验与对象 URL 回收
<template><StandaloneDemo /></template>
<script lang="ts" setup>
import * as __external0 from "vue";
import * as __external1 from "@asam-ui/ui";
const __external: any = {"vue": { ...__external0, __esModule: true },
"@asam-ui/ui": { ...__external1, __esModule: true }};
const __modules: any = {
"Demo.vue": (module: any, exports: any, require: any) => {
"use strict";
var __importDefault: any = function (mod?: any) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1: any = require("vue");
const vue_2: any = require("vue");
const _hoisted_1: any = { class: "example" };
const _hoisted_2: any = { "aria-live": "polite" };
const vue_3: any = require("vue");
const ui_1: any = require("@asam-ui/ui");
const Avatar_vue_1: any = __importDefault(require("./Avatar.vue"));
exports.default = (0, vue_1.defineComponent)({
...{ name: 'AsamUploadAvatarExample' },
__name: 'Demo',
setup(__props?: any) {
const mounted: any = (0, vue_3.ref)(true);
const notice: any = (0, vue_3.ref)('选择 JPG 头像;演示使用本地请求,不发送网络数据。');
return (_ctx?: any, _cache?: any) => {
return ((0, vue_2.openBlock)(), (0, vue_2.createElementBlock)("div", _hoisted_1, [
(mounted.value)
? ((0, vue_2.openBlock)(), (0, vue_2.createBlock)(Avatar_vue_1.default, {
key: 0,
onNotice: _cache[0] || (_cache[0] = ($event?: any) => (notice.value = $event))
}))
: (0, vue_2.createCommentVNode)("v-if", true),
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamButton), {
type: "default",
onClick: _cache[1] || (_cache[1] = ($event?: any) => (mounted.value = !mounted.value))
}, {
default: (0, vue_2.withCtx)(() => [
(0, vue_2.createTextVNode)((0, vue_2.toDisplayString)(mounted.value ? '卸载头像控件' : '重新挂载头像控件'), 1 /* TEXT */)
]),
_: 1 /* STABLE */
}),
(0, vue_2.createElementVNode)("output", _hoisted_2, (0, vue_2.toDisplayString)(notice.value), 1 /* TEXT */)
]));
};
}
});
exports.default.__scopeId = "data-v-c2f7c53d";
},
"Avatar.vue": (module: any, exports: any, require: any) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1: any = require("vue");
const vue_2: any = require("vue");
const _hoisted_1: any = { class: "avatar-example" };
const _hoisted_2: any = { class: "avatar-frame" };
const _hoisted_3: any = { key: 1 };
const _hoisted_4: any = {
"aria-live": "polite",
"data-avatar-result": ""
};
const vue_3: any = require("vue");
const ui_1: any = require("@asam-ui/ui");
exports.default = (0, vue_1.defineComponent)({
...{ name: 'AsamUploadAvatarControlExample' },
__name: 'Avatar',
emits: { notice: (message?: any) => typeof message === 'string' },
setup(__props: any, { emit: __emit }: any) {
const emit: any = __emit;
const uploadRef: any = (0, vue_3.ref)();
const imageUrl: any = (0, vue_3.ref)('');
const result: any = (0, vue_3.ref)('尚未选择头像');
const requests: any = (0, vue_3.ref)(0);
const successes: any = (0, vue_3.ref)(0);
let alive: any = true;
function notice(message?: any) {
result.value = message;
emit('notice', message);
}
const beforeUpload: any = (file?: any) => {
if (file.type !== 'image/jpeg') {
notice('拒绝:只接受 JPG');
return false;
}
if (file.size > 2 * 1024 * 1024) {
notice('拒绝:文件超过 2 MiB');
return false;
}
return true;
};
const upload: any = async () => {
requests.value++;
notice('本地上传中');
await new Promise((resolve?: any) => setTimeout(resolve, 150));
return { local: true };
};
const onSuccess: any = (_response?: any, file?: any) => {
if (!alive || !file.raw)
return;
const nextUrl: any = URL.createObjectURL(file.raw);
if (imageUrl.value)
URL.revokeObjectURL(imageUrl.value);
imageUrl.value = nextUrl;
successes.value++;
notice(`头像已更新:${file.name}`);
uploadRef.value?.clearFiles();
};
(0, vue_3.onBeforeUnmount)(() => {
alive = false;
uploadRef.value?.abort();
if (imageUrl.value)
URL.revokeObjectURL(imageUrl.value);
imageUrl.value = '';
});
return (_ctx?: any, _cache?: any) => {
return ((0, vue_2.openBlock)(), (0, vue_2.createElementBlock)("div", _hoisted_1, [
(0, vue_2.createVNode)((0, vue_2.unref)(ui_1.AsamUpload), {
ref_key: "uploadRef",
ref: uploadRef,
mode: "native",
"show-file-list": false,
accept: "image/jpeg",
"http-request": upload,
"before-upload": beforeUpload,
"on-success": onSuccess
}, {
default: (0, vue_2.withCtx)(() => [
(0, vue_2.createElementVNode)("div", _hoisted_2, [
(imageUrl.value)
? ((0, vue_2.openBlock)(), (0, vue_2.createBlock)((0, vue_2.unref)(ui_1.AsamImage), {
key: 0,
src: imageUrl.value,
alt: "当前头像",
class: "avatar-image"
}, null, 8 /* PROPS */, ["src"]))
: ((0, vue_2.openBlock)(), (0, vue_2.createElementBlock)("span", _hoisted_3, "选择 JPG 头像"))
])
]),
_: 1 /* STABLE */
}, 512 /* NEED_PATCH */),
_cache[0] || (_cache[0] = (0, vue_2.createElementVNode)("p", { class: "example-paragraph" }, "只接受 JPG,大小不超过 2 MiB。成功后替换头像,拒绝的文件保留原头像。", -1 /* CACHED */)),
(0, vue_2.createElementVNode)("output", _hoisted_4, (0, vue_2.toDisplayString)(result.value) + ";本地请求 " + (0, vue_2.toDisplayString)(requests.value) + ";成功 " + (0, vue_2.toDisplayString)(successes.value), 1 /* TEXT */)
]));
};
}
});
exports.default.__scopeId = "data-v-7bb1dbf9";
},
};
const __dependencies: any = {"Demo.vue":{"./Avatar.vue":"Avatar.vue"},"Avatar.vue":{}};
const __cache: any = Object.create(null);
function __require(id: any): any {
if (__external[id]) return __external[id];
if (__cache[id]) return __cache[id].exports;
const module: any = { exports: {} };
__cache[id] = module;
__modules[id](module, module.exports, (dependency: any) => __require(__dependencies[id][dependency] || dependency));
return module.exports;
}
const StandaloneDemo = __require('Demo.vue').default;
</script>
<style>
.example[data-v-c2f7c53d] {
display: grid;
justify-items: start;
gap: var(--asam-space-md);
}
.avatar-example[data-v-7bb1dbf9] {
display: grid;
gap: var(--asam-space-sm);
}
.avatar-frame[data-v-7bb1dbf9] {
display: grid;
width: 160px;
height: 160px;
place-items: center;
overflow: hidden;
color: var(--asam-color-text-secondary);
border: 1px dashed var(--asam-color-border);
border-radius: var(--asam-radius-md);
}
.avatar-image[data-v-7bb1dbf9] {
width: 100%;
height: 100%;
object-fit: cover;
}
p[data-v-7bb1dbf9]:where(.example-paragraph) {
margin-block: 16px;
line-height: 1.8;
overflow-wrap: anywhere;
}
</style>
<template><StandaloneDemo /></template>
<script setup>
import * as __external0 from "vue";
import * as __external1 from "@asam-ui/ui";
const __external = {"vue": { ...__external0, __esModule: true },
"@asam-ui/ui": { ...__external1, __esModule: true }};
const __modules = {
"Demo.vue": (module, exports, require) => {
"use strict";
var __importDefault = function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1 = require("vue");
const _hoisted_1 = { class: "example" };
const _hoisted_2 = { "aria-live": "polite" };
const vue_2 = require("vue");
const ui_1 = require("@asam-ui/ui");
const Avatar_vue_1 = __importDefault(require("./Avatar.vue"));
exports.default = Object.assign({ name: 'AsamUploadAvatarExample' }, {
__name: 'Demo',
setup(__props) {
const mounted = (0, vue_2.ref)(true);
const notice = (0, vue_2.ref)('选择 JPG 头像;演示使用本地请求,不发送网络数据。');
return (_ctx, _cache) => {
return ((0, vue_1.openBlock)(), (0, vue_1.createElementBlock)("div", _hoisted_1, [
(mounted.value)
? ((0, vue_1.openBlock)(), (0, vue_1.createBlock)(Avatar_vue_1.default, {
key: 0,
onNotice: _cache[0] || (_cache[0] = $event => (notice.value = $event))
}))
: (0, vue_1.createCommentVNode)("v-if", true),
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamButton), {
type: "default",
onClick: _cache[1] || (_cache[1] = $event => (mounted.value = !mounted.value))
}, {
default: (0, vue_1.withCtx)(() => [
(0, vue_1.createTextVNode)((0, vue_1.toDisplayString)(mounted.value ? '卸载头像控件' : '重新挂载头像控件'), 1 /* TEXT */)
]),
_: 1 /* STABLE */
}),
(0, vue_1.createElementVNode)("output", _hoisted_2, (0, vue_1.toDisplayString)(notice.value), 1 /* TEXT */)
]));
};
}
});
exports.default.__scopeId = "data-v-2a10eee9";
},
"Avatar.vue": (module, exports, require) => {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vue_1 = require("vue");
const _hoisted_1 = { class: "avatar-example" };
const _hoisted_2 = { class: "avatar-frame" };
const _hoisted_3 = { key: 1 };
const _hoisted_4 = {
"aria-live": "polite",
"data-avatar-result": ""
};
const vue_2 = require("vue");
const ui_1 = require("@asam-ui/ui");
exports.default = Object.assign({ name: 'AsamUploadAvatarControlExample' }, {
__name: 'Avatar',
emits: { notice: (message) => typeof message === 'string' },
setup(__props, { emit: __emit }) {
const emit = __emit;
const uploadRef = (0, vue_2.ref)();
const imageUrl = (0, vue_2.ref)('');
const result = (0, vue_2.ref)('尚未选择头像');
const requests = (0, vue_2.ref)(0);
const successes = (0, vue_2.ref)(0);
let alive = true;
function notice(message) {
result.value = message;
emit('notice', message);
}
const beforeUpload = (file) => {
if (file.type !== 'image/jpeg') {
notice('拒绝:只接受 JPG');
return false;
}
if (file.size > 2 * 1024 * 1024) {
notice('拒绝:文件超过 2 MiB');
return false;
}
return true;
};
const upload = async () => {
requests.value++;
notice('本地上传中');
await new Promise((resolve) => setTimeout(resolve, 150));
return { local: true };
};
const onSuccess = (_response, file) => {
if (!alive || !file.raw)
return;
const nextUrl = URL.createObjectURL(file.raw);
if (imageUrl.value)
URL.revokeObjectURL(imageUrl.value);
imageUrl.value = nextUrl;
successes.value++;
notice(`头像已更新:${file.name}`);
uploadRef.value?.clearFiles();
};
(0, vue_2.onBeforeUnmount)(() => {
alive = false;
uploadRef.value?.abort();
if (imageUrl.value)
URL.revokeObjectURL(imageUrl.value);
imageUrl.value = '';
});
return (_ctx, _cache) => {
return ((0, vue_1.openBlock)(), (0, vue_1.createElementBlock)("div", _hoisted_1, [
(0, vue_1.createVNode)((0, vue_1.unref)(ui_1.AsamUpload), {
ref_key: "uploadRef",
ref: uploadRef,
mode: "native",
"show-file-list": false,
accept: "image/jpeg",
"http-request": upload,
"before-upload": beforeUpload,
"on-success": onSuccess
}, {
default: (0, vue_1.withCtx)(() => [
(0, vue_1.createElementVNode)("div", _hoisted_2, [
(imageUrl.value)
? ((0, vue_1.openBlock)(), (0, vue_1.createBlock)((0, vue_1.unref)(ui_1.AsamImage), {
key: 0,
src: imageUrl.value,
alt: "当前头像",
class: "avatar-image"
}, null, 8 /* PROPS */, ["src"]))
: ((0, vue_1.openBlock)(), (0, vue_1.createElementBlock)("span", _hoisted_3, "选择 JPG 头像"))
])
]),
_: 1 /* STABLE */
}, 512 /* NEED_PATCH */),
_cache[0] || (_cache[0] = (0, vue_1.createElementVNode)("p", { class: "example-paragraph" }, "只接受 JPG,大小不超过 2 MiB。成功后替换头像,拒绝的文件保留原头像。", -1 /* CACHED */)),
(0, vue_1.createElementVNode)("output", _hoisted_4, (0, vue_1.toDisplayString)(result.value) + ";本地请求 " + (0, vue_1.toDisplayString)(requests.value) + ";成功 " + (0, vue_1.toDisplayString)(successes.value), 1 /* TEXT */)
]));
};
}
});
exports.default.__scopeId = "data-v-58f65680";
},
};
const __dependencies = {"Demo.vue":{"./Avatar.vue":"Avatar.vue"},"Avatar.vue":{}};
const __cache = Object.create(null);
function __require(id) {
if (__external[id]) return __external[id];
if (__cache[id]) return __cache[id].exports;
const module = { exports: {} };
__cache[id] = module;
__modules[id](module, module.exports, (dependency) => __require(__dependencies[id][dependency] || dependency));
return module.exports;
}
const StandaloneDemo = __require('Demo.vue').default;
</script>
<style>
.example[data-v-2a10eee9] {
display: grid;
justify-items: start;
gap: var(--asam-space-md);
}
.avatar-example[data-v-58f65680] {
display: grid;
gap: var(--asam-space-sm);
}
.avatar-frame[data-v-58f65680] {
display: grid;
width: 160px;
height: 160px;
place-items: center;
overflow: hidden;
color: var(--asam-color-text-secondary);
border: 1px dashed var(--asam-color-border);
border-radius: var(--asam-radius-md);
}
.avatar-image[data-v-58f65680] {
width: 100%;
height: 100%;
object-fit: cover;
}
p[data-v-58f65680]:where(.example-paragraph) {
margin-block: 16px;
line-height: 1.8;
overflow-wrap: anywhere;
}
</style>
此 default 插槽的对象 URL 由示例自己创建,使用方负责替换时回收旧 URL、卸载时回收最后一个 URL。原生列表自动创建的缩略图 URL 与这条路径不同。示例拆出 Avatar.vue,在卸载时中止上传实例并忽略迟到回调;自定义 Promise 的物理取消仍由请求实现负责。重新挂载会回到空头像状态。
目录选择和拖放路径 ​
目录上传由 native 的 directory 承接。 完整说明。
目录选择与拖放相对路径
<template>
<div class="example">
<AsamUpload
v-model:file-list="files"
mode="native"
directory
drag
multiple
:http-request="upload"
:on-change="change"
:on-success="(_response, file) => completed.push(file.name)"
>
<template #default>拖入目录,或点击选择目录</template>
<template #tip>
目录展开为文件;相对路径直接来自 UploadRawFile.webkitRelativePath。
</template>
</AsamUpload>
<output>路径:{{ paths.join(' | ') }};完成:{{ completed.join(',') }}</output>
</div>
</template>
<script lang="ts" setup>
defineOptions({ name: 'AsamUploadDirectoryExample' });
import { ref } from 'vue';
import { AsamUpload } from '@asam-ui/ui';
import type { AsamNativeUploadProps } from '@asam-ui/ui';
const files = ref<NonNullable<AsamNativeUploadProps['fileList']>>([]);
const paths = ref<string[]>([]);
const completed = ref<string[]>([]);
const change: NonNullable<AsamNativeUploadProps['onChange']> = (_file, list) => {
paths.value = list.map((file) => file.raw?.webkitRelativePath || file.name);
};
const upload: NonNullable<AsamNativeUploadProps['httpRequest']> = async (options) => {
return { local: true, path: options.file.webkitRelativePath };
};
</script>
<style scoped>
.example {
display: grid;
gap: var(--asam-space-md);
}
output {
overflow-wrap: anywhere;
}
</style>
<template>
<div class="example">
<AsamUpload
v-model:file-list="files"
mode="native"
directory
drag
multiple
:http-request="upload"
:on-change="change"
:on-success="(_response, file) => completed.push(file.name)"
>
<template #default>拖入目录,或点击选择目录</template>
<template #tip>
目录展开为文件;相对路径直接来自 UploadRawFile.webkitRelativePath。
</template>
</AsamUpload>
<output>路径:{{ paths.join(' | ') }};完成:{{ completed.join(',') }}</output>
</div>
</template>
<script setup>
defineOptions({ name: 'AsamUploadDirectoryExample' });
import { ref } from 'vue';
import { AsamUpload } from '@asam-ui/ui';
const files = ref([]);
const paths = ref([]);
const completed = ref([]);
const change = (_file, list) => {
paths.value = list.map((file) => file.raw?.webkitRelativePath || file.name);
};
const upload = async (options) => {
return { local: true, path: options.file.webkitRelativePath };
};
</script>
<style scoped>
.example {
display: grid;
gap: var(--asam-space-md);
}
output {
overflow-wrap: anywhere;
}
</style>
resource 模式保持规范化资源模型,其 transport 输入是原始 File;不会擅自将目录层次塞入资源 id/url。 完整说明。
数量限制、覆盖和请求钩子 ​
limit 与 onExceed 组合控制超量选择。 完整说明。
限制覆盖、原生请求与手动提交
<template>
<div class="example">
<AsamUpload
ref="control"
v-model:file-list="files"
mode="native"
:limit="1"
:auto-upload="false"
:http-request="upload"
:on-exceed="replace"
action="/local-demonstration"
method="put"
:headers="{ 'X-Demo': 'local' }"
:data="data"
name="attachment"
with-credentials
:show-file-list="true"
:on-change="(file) => (result = `已选择 ${file.name}`)"
>
<template #trigger><AsamButton type="default">选择或替换文件</AsamButton></template>
<AsamButton type="default" @click="control?.submit()">手动发送</AsamButton>
<template #tip>
超过 limit 后先 clearFiles,再 handleStart;本地 handler 显示实际请求参数。
</template>
</AsamUpload>
<AsamButton type="default" @click="control?.abort()">abort 实例</AsamButton>
<AsamButton type="default" @click="control?.clearFiles()">清空原始文件</AsamButton>
<output>{{ result }}</output>
<pre>{{ request }}</pre>
</div>
</template>
<script lang="ts" setup>
defineOptions({ name: 'AsamUploadHooksExample' });
import { ref } from 'vue';
import { AsamUpload, AsamButton } from '@asam-ui/ui';
import type { AsamNativeUploadProps, AsamNativeUploadExpose } from '@asam-ui/ui';
const control = ref<AsamNativeUploadExpose>();
const files = ref<NonNullable<AsamNativeUploadProps['fileList']>>([]);
const result = ref('原生请求参数与覆盖前一个文件');
const request = ref('');
let uid = Date.now();
const replace: NonNullable<AsamNativeUploadProps['onExceed']> = (selected) => {
const file = selected[0];
if (!file) return;
control.value?.clearFiles();
control.value?.handleStart(Object.assign(file, { uid: ++uid }));
};
const data: NonNullable<AsamNativeUploadProps['data']> = async (file) => ({
category: 'demo',
original: file.name
});
const upload: NonNullable<AsamNativeUploadProps['httpRequest']> = async (options) => {
request.value = JSON.stringify({
action: options.action,
method: options.method,
headers: options.headers,
data: options.data,
name: options.filename,
withCredentials: options.withCredentials,
file: options.file.name
});
result.value = '本地请求已完成';
return { local: true };
};
</script>
<style scoped>
.example {
display: grid;
gap: var(--asam-space-md);
}
pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
</style>
<template>
<div class="example">
<AsamUpload
ref="control"
v-model:file-list="files"
mode="native"
:limit="1"
:auto-upload="false"
:http-request="upload"
:on-exceed="replace"
action="/local-demonstration"
method="put"
:headers="{ 'X-Demo': 'local' }"
:data="data"
name="attachment"
with-credentials
:show-file-list="true"
:on-change="(file) => (result = `已选择 ${file.name}`)"
>
<template #trigger><AsamButton type="default">选择或替换文件</AsamButton></template>
<AsamButton type="default" @click="control?.submit()">手动发送</AsamButton>
<template #tip>
超过 limit 后先 clearFiles,再 handleStart;本地 handler 显示实际请求参数。
</template>
</AsamUpload>
<AsamButton type="default" @click="control?.abort()">abort 实例</AsamButton>
<AsamButton type="default" @click="control?.clearFiles()">清空原始文件</AsamButton>
<output>{{ result }}</output>
<pre>{{ request }}</pre>
</div>
</template>
<script setup>
defineOptions({ name: 'AsamUploadHooksExample' });
import { ref } from 'vue';
import { AsamUpload, AsamButton } from '@asam-ui/ui';
const control = ref();
const files = ref([]);
const result = ref('原生请求参数与覆盖前一个文件');
const request = ref('');
let uid = Date.now();
const replace = (selected) => {
const file = selected[0];
if (!file) return;
control.value?.clearFiles();
control.value?.handleStart(Object.assign(file, { uid: ++uid }));
};
const data = async (file) => ({
category: 'demo',
original: file.name
});
const upload = async (options) => {
request.value = JSON.stringify({
action: options.action,
method: options.method,
headers: options.headers,
data: options.data,
name: options.filename,
withCredentials: options.withCredentials,
file: options.file.name
});
result.value = '本地请求已完成';
return { local: true };
};
</script>
<style scoped>
.example {
display: grid;
gap: var(--asam-space-md);
}
pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
</style>
原生请求参数 action/method/headers/name/data/withCredentials 交给 httpRequest;data 支持对象、函数和 Promise。 完整说明。
资源 transport、转换、取消与回填 ​
upload handler 和 request 二选一。 完整说明。
资源 transport、转换、取消和回填
<template>
<div class="example">
<div class="controls">
<AsamCheckboxItem v-model="disabled"> 禁用资源操作</AsamCheckboxItem>
<AsamCheckboxItem v-model="failure"> transport 失败</AsamCheckboxItem>
<AsamCheckboxItem v-model="invalid"> transform 失败</AsamCheckboxItem>
</div>
<AsamUpload
ref="control"
v-model="value"
multiple
:request="request"
:limit="2"
:auto-upload="false"
:disabled="disabled"
:max-size-bytes="1024"
field-name="document"
:data="{ category: 'demo' }"
:validate="(file) => file.name.endsWith('.txt') || '只接受 TXT'"
@success="(_resource, item) => (event = `success:${item.name}`)"
@progress="(item) => (event = `progress:${item.percent}`)"
@error="(error) => (event = `${error.kind}:${error.message}`)"
@cancelled="(item) => (event = `cancelled:${item.name}`)"
@remove="(resource) => (event = `remove:${resource.name}`)"
>
<template #tip>独立 provider,不影响页面中其他示例;最多两个资源。</template>
</AsamUpload>
<div class="controls">
<AsamButton type="default" @click="control?.submit()">提交资源</AsamButton>
<AsamButton type="default" @click="control?.cancel()">取消资源请求</AsamButton>
<AsamButton type="default" @click="control?.clear()">清空资源</AsamButton>
<AsamButton type="default" @click="runtime.setScope(String(++scope))"
>切换资源作用域</AsamButton
>
<AsamButton type="default" @click="fill">外部回填</AsamButton>
</div>
<output>{{ event }};请求:{{ input }};资源:{{ JSON.stringify(value) }}</output>
</div>
</template>
<script lang="ts" setup>
defineOptions({ name: 'AsamUploadRequestExample' });
import { onBeforeUnmount, provide, ref } from 'vue';
import {
AsamCheckboxItem,
AsamUpload,
AsamButton,
asamDataRuntimeKey,
createAsamDataRuntime
} from '@asam-ui/ui';
import type { AsamUploadedResource, AsamUploadExpose, AsamUploadRequestConfig } from '@asam-ui/ui';
const value = ref<AsamUploadedResource[]>([]);
const control = ref<AsamUploadExpose>();
const disabled = ref(false);
const failure = ref(false);
const invalid = ref(false);
const event = ref('尚未提交');
const input = ref('');
const scope = ref(0);
const runtime = createAsamDataRuntime();
runtime.install({
upload: async (config, payload, context) => {
input.value = `${config.method}:${config.url}:${payload.fieldName}:${payload.data?.category}`;
context.reportProgress({ loaded: 0, total: payload.file.size });
await new Promise<void>((resolve, reject) => {
const timer = setTimeout(resolve, 150);
context.signal.addEventListener(
'abort',
() => {
clearTimeout(timer);
reject(new Error('cancelled'));
},
{ once: true }
);
});
if (failure.value) throw new Error('本地 transport 失败');
context.reportProgress({ loaded: payload.file.size, total: payload.file.size });
return invalid.value ? null : { key: payload.file.name };
}
});
provide(asamDataRuntimeKey, runtime);
onBeforeUnmount(() => runtime.invalidate());
const request: AsamUploadRequestConfig = {
url: '/local-resource',
method: 'put',
params: { demo: true },
transform(response, { file }) {
if (
!response ||
typeof response !== 'object' ||
!('key' in response) ||
typeof response.key !== 'string'
)
throw new Error('响应缺少资源 key');
return {
id: response.key,
name: file.name,
url: '/local/' + encodeURIComponent(file.name),
mimeType: file.type || 'application/octet-stream',
size: file.size
};
}
};
function fill(): void {
value.value = [
{ id: 'seed', name: 'seed.txt', url: '/seed.txt', mimeType: 'text/plain', size: 4 }
];
}
</script>
<style scoped>
.example {
display: grid;
gap: var(--asam-space-md);
}
.controls {
display: flex;
flex-wrap: wrap;
gap: var(--asam-space-sm);
}
output {
overflow-wrap: anywhere;
}
</style>
<template>
<div class="example">
<div class="controls">
<AsamCheckboxItem v-model="disabled"> 禁用资源操作</AsamCheckboxItem>
<AsamCheckboxItem v-model="failure"> transport 失败</AsamCheckboxItem>
<AsamCheckboxItem v-model="invalid"> transform 失败</AsamCheckboxItem>
</div>
<AsamUpload
ref="control"
v-model="value"
multiple
:request="request"
:limit="2"
:auto-upload="false"
:disabled="disabled"
:max-size-bytes="1024"
field-name="document"
:data="{ category: 'demo' }"
:validate="(file) => file.name.endsWith('.txt') || '只接受 TXT'"
@success="(_resource, item) => (event = `success:${item.name}`)"
@progress="(item) => (event = `progress:${item.percent}`)"
@error="(error) => (event = `${error.kind}:${error.message}`)"
@cancelled="(item) => (event = `cancelled:${item.name}`)"
@remove="(resource) => (event = `remove:${resource.name}`)"
>
<template #tip>独立 provider,不影响页面中其他示例;最多两个资源。</template>
</AsamUpload>
<div class="controls">
<AsamButton type="default" @click="control?.submit()">提交资源</AsamButton>
<AsamButton type="default" @click="control?.cancel()">取消资源请求</AsamButton>
<AsamButton type="default" @click="control?.clear()">清空资源</AsamButton>
<AsamButton type="default" @click="runtime.setScope(String(++scope))"
>切换资源作用域</AsamButton
>
<AsamButton type="default" @click="fill">外部回填</AsamButton>
</div>
<output>{{ event }};请求:{{ input }};资源:{{ JSON.stringify(value) }}</output>
</div>
</template>
<script setup>
defineOptions({ name: 'AsamUploadRequestExample' });
import { onBeforeUnmount, provide, ref } from 'vue';
import {
AsamCheckboxItem,
AsamUpload,
AsamButton,
asamDataRuntimeKey,
createAsamDataRuntime
} from '@asam-ui/ui';
const value = ref([]);
const control = ref();
const disabled = ref(false);
const failure = ref(false);
const invalid = ref(false);
const event = ref('尚未提交');
const input = ref('');
const scope = ref(0);
const runtime = createAsamDataRuntime();
runtime.install({
upload: async (config, payload, context) => {
input.value = `${config.method}:${config.url}:${payload.fieldName}:${payload.data?.category}`;
context.reportProgress({ loaded: 0, total: payload.file.size });
await new Promise((resolve, reject) => {
const timer = setTimeout(resolve, 150);
context.signal.addEventListener(
'abort',
() => {
clearTimeout(timer);
reject(new Error('cancelled'));
},
{ once: true }
);
});
if (failure.value) throw new Error('本地 transport 失败');
context.reportProgress({ loaded: payload.file.size, total: payload.file.size });
return invalid.value ? null : { key: payload.file.name };
}
});
provide(asamDataRuntimeKey, runtime);
onBeforeUnmount(() => runtime.invalidate());
const request = {
url: '/local-resource',
method: 'put',
params: { demo: true },
transform(response, { file }) {
if (
!response ||
typeof response !== 'object' ||
!('key' in response) ||
typeof response.key !== 'string'
)
throw new Error('响应缺少资源 key');
return {
id: response.key,
name: file.name,
url: '/local/' + encodeURIComponent(file.name),
mimeType: file.type || 'application/octet-stream',
size: file.size
};
}
};
function fill() {
value.value = [
{ id: 'seed', name: 'seed.txt', url: '/seed.txt', mimeType: 'text/plain', size: 4 }
];
}
</script>
<style scoped>
.example {
display: grid;
gap: var(--asam-space-md);
}
.controls {
display: flex;
flex-wrap: wrap;
gap: var(--asam-space-sm);
}
output {
overflow-wrap: anywhere;
}
</style>
transform 返回合法 id/name/url/mimeType/size 资源,失败、重复 id 和错误 metadata 不写入模型。 完整说明。
cancel/clear/作用域失效及卸载中止 context,迟到完成不会写回;进度、成功、错误、cancelled、remove 事件保持各自 payload。 完整说明。
ASAM 扩展与迁移 ​
ASAM 扩展为 resource/native 双模式与四档密度。 完整说明。
API 阅读方式 ​
下方 Props、Events、Slots、Exposes 按当前实际公开类型与运行默认值生成。组合属性按分支生效,不能任意混搭值模式。标为“未知 / 未声明”的默认值不代表 false 或空值。源码区域提供对应语言的独立 Demo.vue,可直接复制或下载;本地辅助组件、数据和素材已内联到当前文件。
API
属性 AsamUploadProps
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
disabled | 是否禁用上传 | (AsamUploadProps)["disabled"] | boolean | undefined | undefined |
data | 上传时附带的额外参数从 v2.3.13 支持 Awaitable 数据,和 Function | (AsamUploadProps)["data"] | Readonly<Record<string, string | Blob>> | undefined | 工厂函数(每个实例创建) |
name | 上传的文件字段名 | string | undefined | "file" |
onChange | 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 | PublicMember<AsamUploadProps, "onChange"> | 函数默认值(作为值传递) |
onError | 文件上传失败时的钩子 | PublicMember<AsamUploadProps, "onError"> | 函数默认值(作为值传递) |
crossorigin | 原生属性 crossorigin | enum"" | "anonymous" | "use-credentials"完整类型:PublicMember<AsamUploadProps, "crossorigin"> | — |
onProgress | 文件上传时的钩子 | PublicMember<AsamUploadProps, "onProgress"> | 函数默认值(作为值传递) |
multiple | 是否支持多选文件 | boolean | undefined | false | undefined | true | false |
beforeUpload | 上传文件之前的钩子,参数为上传的文件,若返回false或者返回 Promise 且被 reject,则停止上传。 | PublicMember<AsamUploadProps, "beforeUpload"> | 函数默认值(作为值传递) |
beforeRemove | 删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被 reject,则停止删除。 | PublicMember<AsamUploadProps, "beforeRemove"> | — |
onRemove | 文件列表移除文件时的钩子 | PublicMember<AsamUploadProps, "onRemove"> | 函数默认值(作为值传递) |
onPreview | 点击文件列表中已上传的文件时的钩子 | PublicMember<AsamUploadProps, "onPreview"> | 函数默认值(作为值传递) |
onSuccess | 文件上传成功时的钩子 | PublicMember<AsamUploadProps, "onSuccess"> | 函数默认值(作为值传递) |
onExceed | 当超出限制时,执行的钩子函数 | PublicMember<AsamUploadProps, "onExceed"> | 函数默认值(作为值传递) |
action | 请求 URL | string | undefined | "#" |
headers | 设置上传的请求头部 | PublicMember<AsamUploadProps, "headers"> | — |
method | 设置上传请求方法 | string | undefined | "post" |
drag | 是否启用拖拽上传 | boolean | undefined | false |
withCredentials | 支持发送 cookie 凭证信息 | boolean | undefined | false |
showFileList | 是否显示已上传文件列表 | PublicMember<AsamUploadProps, "showFileList"> | true |
accept | 接受上传的文件类型(thumbnail-mode 模式下此参数无效) | string | undefined | "" |
fileList | 默认上传文件 | PublicMember<AsamUploadProps, "fileList"> | 工厂函数(每个实例创建) |
autoUpload | 是否自动上传文件 | (AsamUploadProps)["autoUpload"] | boolean | undefined | true |
listType | 文件列表的类型 | enum"text" | "picture" | "picture-card"完整类型:PublicMember<AsamUploadProps, "listType"> | "text" |
httpRequest | 覆盖默认的 Xhr 行为,允许自行实现上传文件的请求 | PublicMember<AsamUploadProps, "httpRequest"> | 函数默认值(作为值传递) |
limit | 允许上传文件的最大数量 | number | undefined | — |
directory | 是否支持上传文件夹。启用后,只能选择文件夹;选择文件夹后,文件夹内的文件将被扁平化处理。 | boolean | undefined | false |
size | — | enum"large" | "big" | "default" | "small"完整类型:AsamControlSize | undefined | undefined |
mode | — | enum"native" | "resource"完整类型:"native" | "resource" | undefined | 'resource' |
upload | — | undefined | AsamUploadHandler | undefined |
request | — | undefined | AsamUploadRequestConfig | undefined |
modelValue | — | undefined | AsamUploadedResource | null | readonly AsamUploadedResource[] | null |
fieldName | — | undefined | string | undefined | 'file' |
maxSizeBytes | — | undefined | number | undefined | undefined |
validate | — | undefined | ((file: AsamUploadInput["file"]) => boolean | string) | undefined | undefined |
事件 AsamUploadEmits
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
update:modelValue | — | [value: AsamUploadValue] | — |
update:fileList | — | (AsamUploadEmits)["update:fileList"] | — |
change | — | [value: AsamUploadValue] | — |
progress | — | [item: AsamUploadItem] | — |
success | — | [resource: AsamUploadedResource, item: AsamUploadItem] | — |
error | — | [error: AsamDataError, item: AsamUploadItem] | — |
cancelled | — | [item: AsamUploadItem] | — |
remove | — | [resource: AsamUploadedResource] | — |
插槽 AsamUploadSlots
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
default | — | (() => unknown) | undefined | — |
trigger | — | (() => unknown) | undefined | — |
tip | — | (() => unknown) | undefined | — |
file | — | (AsamUploadSlots)["file"] | — |
插槽 AsamNativeUploadSlots
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
file | — | (AsamNativeUploadSlots)["file"] | — |
trigger | — | (AsamNativeUploadSlots)["trigger"] | — |
default | — | (AsamNativeUploadSlots)["default"] | — |
tip | — | (AsamNativeUploadSlots)["tip"] | — |
实例方法与状态 AsamUploadExpose
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
submit | — | (() => void) & (() => void) | — |
abort | 取消上传请求。 | (AsamUploadExpose)["abort"] | — |
clearFiles | — | (AsamUploadExpose)["clearFiles"] | — |
handleStart | — | (AsamUploadExpose)["handleStart"] | — |
handleRemove | — | (AsamUploadExpose)["handleRemove"] | — |
cancel | — | (clientId?: string) => void | — |
clear | — | () => void | — |
用法与契约补充说明 ​
原生照片墙、缩略图与受控重排 ​
native 模式完整承接 text、picture、picture-card 三种 listType。onPreview 处理预览动作;beforeUpload 可同步/异步拒绝文件或返回替换后的 File/Blob,返回 false/Promise rejection 会阻止上传。beforeRemove 可同步/异步拒绝移除,onRemove 在实际移除后调用;onChange 会在选择、成功、失败时收到当前列表。native file 插槽原样接收原生 { file, index },移除和取消通过实例 handleRemove(file) / abort(file) 调用。item/remove/cancel 仅属于 resource file 插槽;旧 native 用法需按照片墙示例迁移。
原生照片墙、缩略图与受控重排 ​
v-model:fileList 为受控原始列表,使用方可重排或外部替换;uid 保持文件身份。picture 和 picture-card 的缩略图使用 file.url;组件负责自身生成的对象 URL 生命周期。crossorigin 按实际图片来源配置。disabled 禁止选择和原生列表操作;资源模式也禁止默认取消、移除按钮,实例方法仍由使用方控制。
隐藏列表后的头像替换 ​
下面使用 native、showFileList=false 和 default 插槽呈现选择入口,成功后以头像替换入口内容。beforeUpload 拒绝非 JPG 或超过 2 MiB 的文件;accept 是选择器提示,不能替代验证。httpRequest 是本地 Promise,onSuccess 只在成功后用原始 File 创建预览,不发网络请求。拒绝文件不会替换已有头像。
目录选择和拖放路径 ​
目录上传由 native 的 directory 承接。目录选择依赖浏览器 webkitdirectory 支持;拖放目录展开后保留原始文件的 webkitRelativePath,而不是只留下最后一级文件名。multiple 接收展开的文件集合,httpRequest 的 options.file 同样保留路径,存储端可以据此构造相对目录。
目录选择和拖放路径 ​
resource 模式保持规范化资源模型,其 transport 输入是原始 File;不会擅自将目录层次塞入资源 id/url。目录选择 UI 和照片墙由 native 明确承接;resource 的 file 插槽可呈现宿主已有资源缩略图。需要资源路径时由 upload/request transform 按宿主协议生成,不能将 native fileList 与 resource modelValue 混搭。
数量限制、覆盖和请求钩子 ​
limit 与 onExceed 组合控制超量选择。覆盖单文件时先 clearFiles,再给新 File 赋 uid 并 handleStart;handleRemove 走原始移除流程。autoUpload=false 将文件留在 ready,submit 发送待传列表。showFileList、accept、drag、multiple、disabled 直接控制原生 UI。trigger 是选择入口,存在 trigger 时 default 可放独立提交动作,tip 为提示区域。
数量限制、覆盖和请求钩子 ​
原生请求参数 action/method/headers/name/data/withCredentials 交给 httpRequest;data 支持对象、函数和 Promise。onProgress 接收进度事件,onSuccess/onError 接收结果和原始列表。自定义 httpRequest 可返回 Promise(由组件调用成功/失败钩子)或 XMLHttpRequest(abort 可中止其请求);Promise 的物理取消由自定义请求实现负责,不能仅凭调用 abort 宣称异步操作已终止。clearFiles 可按 ready/uploading/success/fail 过滤,禁止在 beforeUpload 中清列表。
资源 transport、转换、取消与回填 ​
upload handler 和 request 二选一。request 包含 url、post/put、params、同步 transform,实际上传由注入的 provider.upload 执行;示例以独立 runtime/provide 隔离,不改变其他示例。transport 输入保留 File、fieldName 和字符串/Blob data;context.signal 与 reportProgress 分别支持取消和进度。loaded/total 必须有效,未知 total 显示无百分比状态;超量进度不当成成功。
资源 transport、转换、取消与回填 ​
transform 返回合法 id/name/url/mimeType/size 资源,失败、重复 id 和错误 metadata 不写入模型。maxSizeBytes/validate/limit 在传输前判定;配置错误、transport 错误和 transform 错误分别通过 error.kind 表明来源。未完成或失败文件不进入 modelValue。多文件模型必须数组,单文件空值为 null;外部回填重新显示已有资源,资源模型与 native 原始文件列表保持分离。
资源 transport、转换、取消与回填 ​
cancel/clear/作用域失效及卸载中止 context,迟到完成不会写回;进度、成功、错误、cancelled、remove 事件保持各自 payload。disabled 禁止默认资源选择、取消和移除操作;自定义 file 插槽的按钮和外部实例方法由使用方控制。实例 abort/clearFiles/handleStart/handleRemove 为原生文件桥接,submit/cancel/clear 为资源操作;全部操作的结果以真实模型与列表为准。
ASAM 扩展与迁移 ​
ASAM 扩展为 resource/native 双模式与四档密度。resource 的 upload 与 request 二选一;request 需要 provider 上传 transport 与 transform,不混用 native action。resource 公开 submit/cancel/clear;native 公开 abort、submit、clearFiles、handleStart、handleRemove。单个资源空值为 null,多资源为空数组。
关联组件
参数与事件补充说明
组合契约按分支生效;请同时查看公开类型。
参数与事件说明
disabled
必填:否;声明来源:上游声明。
disabled 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:false / true
枚举:false / true
默认值与来源
undefined(已安装运行时;已安装组件)来源分支 1
固定上游文档默认:false(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:undefined(2.14.7;源码事实,未作为 ASAM 运行实测)
data
必填:否;声明来源:上游声明。
data 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(rawFile: Parameters<Extract<NonNullable<(AsamUploadProps)["data"]>, (...args: never[]) => unknown>>[0]): ReturnType<Extract<NonNullable<(AsamUploadProps)["data"]>, (...args: never[]) => unknown>>
data.then:PublicMember<NonNullable<(AsamUploadProps)["data"]>, "then">— 依赖声明添加 Promise 兑现或拒绝时执行的回调。
事件参数完整签名见公开类型视图。
嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
data.catch:PublicMember<NonNullable<(AsamUploadProps)["data"]>, "catch">— 依赖声明添加仅在 Promise 拒绝时执行的回调。
事件参数:
<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined): ReturnType<Extract<NonNullable<PublicMember<NonNullable<(AsamUploadProps)["data"]>, "catch">>, (...args: never[]) => unknown>>嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
data.finally:PublicMember<NonNullable<(AsamUploadProps)["data"]>, "finally">— 依赖声明添加 Promise 完成或拒绝时执行的回调;该回调不能修改兑现值。
事件参数:
(onfinally?: (() => void) | null | undefined): ReturnType<Extract<NonNullable<PublicMember<NonNullable<(AsamUploadProps)["data"]>, "finally">>, (...args: never[]) => unknown>>嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
data.__@toStringTag@9091:string— 依赖声明
默认值与来源
工厂函数(每个实例创建)(实例工厂;已安装组件)来源分支 1
固定上游文档默认:{}(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码工厂(未执行):() => mutable({} as const)(2.14.7;源码事实,未作为 ASAM 运行实测)
name
必填:按分支;声明来源:上游声明。
默认值与来源
"file"(已安装运行时;已安装组件)固定上游文档默认:file(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:'file'(2.14.7;源码事实,未作为 ASAM 运行实测)
onChange
必填:按分支;声明来源:上游声明。
onChange 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
事件参数:(uploadFile: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onChange">>, (...args: never[]) => unknown>>[0], uploadFiles: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onChange">>, (...args: never[]) => unknown>>[1]): void
默认值与来源
函数默认值(作为值传递)(函数值(不执行);已安装组件)固定上游文档默认:未声明(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:NOOP(2.14.7;源码事实,未作为 ASAM 运行实测)
onError
必填:按分支;声明来源:上游声明。
onError 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
事件参数:(error: Error, uploadFile: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onError">>, (...args: never[]) => unknown>>[1], uploadFiles: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onError">>, (...args: never[]) => unknown>>[2]): void
默认值与来源
函数默认值(作为值传递)(函数值(不执行);已安装组件)固定上游文档默认:未声明(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:NOOP(2.14.7;源码事实,未作为 ASAM 运行实测)
crossorigin
必填:按分支;声明来源:上游声明。
crossorigin 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
枚举:"" / "anonymous" / "use-credentials"
默认值与来源
未知 / 未声明固定上游文档默认:未声明(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码未声明 default(2.14.7;源码事实,未作为 ASAM 运行实测)
onProgress
必填:按分支;声明来源:上游声明。
onProgress 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
事件参数:(evt: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onProgress">>, (...args: never[]) => unknown>>[0], uploadFile: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onProgress">>, (...args: never[]) => unknown>>[1], uploadFiles: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onProgress">>, (...args: never[]) => unknown>>[2]): void
默认值与来源
函数默认值(作为值传递)(函数值(不执行);已安装组件)固定上游文档默认:未声明(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:NOOP(2.14.7;源码事实,未作为 ASAM 运行实测)
multiple
必填:否;声明来源:上游声明。
multiple 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:false / true
枚举:false
枚举:true
默认值与来源
false(隐式 Boolean 默认;已安装组件)来源分支 1
固定上游文档默认:false(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
beforeUpload
必填:按分支;声明来源:上游声明。
beforeUpload 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
事件参数:(rawFile: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "beforeUpload">>, (...args: never[]) => unknown>>[0]): ReturnType<Extract<NonNullable<PublicMember<AsamUploadProps, "beforeUpload">>, (...args: never[]) => unknown>>
默认值与来源
函数默认值(作为值传递)(函数值(不执行);已安装组件)固定上游文档默认:未声明(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:NOOP(2.14.7;源码事实,未作为 ASAM 运行实测)
beforeRemove
必填:按分支;声明来源:上游声明。
beforeRemove 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
事件参数:(uploadFile: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "beforeRemove">>, (...args: never[]) => unknown>>[0], uploadFiles: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "beforeRemove">>, (...args: never[]) => unknown>>[1]): ReturnType<Extract<NonNullable<PublicMember<AsamUploadProps, "beforeRemove">>, (...args: never[]) => unknown>>
默认值与来源
未知 / 未声明固定上游文档默认:未声明(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码未声明 default(2.14.7;源码事实,未作为 ASAM 运行实测)
onRemove
必填:按分支;声明来源:上游声明。
onRemove 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
事件参数:(uploadFile: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onRemove">>, (...args: never[]) => unknown>>[0], uploadFiles: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onRemove">>, (...args: never[]) => unknown>>[1]): void
默认值与来源
函数默认值(作为值传递)(函数值(不执行);已安装组件)固定上游文档默认:未声明(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:NOOP(2.14.7;源码事实,未作为 ASAM 运行实测)
onPreview
必填:按分支;声明来源:上游声明。
onPreview 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
事件参数:(uploadFile: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onPreview">>, (...args: never[]) => unknown>>[0]): void
默认值与来源
函数默认值(作为值传递)(函数值(不执行);已安装组件)固定上游文档默认:未声明(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:NOOP(2.14.7;源码事实,未作为 ASAM 运行实测)
onSuccess
必填:按分支;声明来源:上游声明。
onSuccess 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
事件参数:(response: any, uploadFile: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onSuccess">>, (...args: never[]) => unknown>>[1], uploadFiles: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onSuccess">>, (...args: never[]) => unknown>>[2]): void
默认值与来源
函数默认值(作为值传递)(函数值(不执行);已安装组件)固定上游文档默认:未声明(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:NOOP(2.14.7;源码事实,未作为 ASAM 运行实测)
onExceed
必填:按分支;声明来源:上游声明。
onExceed 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
事件参数:(files: AsamUploadInput["file"][], uploadFiles: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "onExceed">>, (...args: never[]) => unknown>>[1]): void
默认值与来源
函数默认值(作为值传递)(函数值(不执行);已安装组件)固定上游文档默认:未声明(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:NOOP(2.14.7;源码事实,未作为 ASAM 运行实测)
action
必填:按分支;声明来源:上游声明。
默认值与来源
"#"(已安装运行时;已安装组件)固定上游文档默认:#(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:'#'(2.14.7;源码事实,未作为 ASAM 运行实测)
headers
必填:按分支;声明来源:上游声明。
headers 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
headers.append:(name: string, value: string) => void— 依赖声明Headers.append():向现有请求头追加值;不存在时创建该请求头。
事件参数:
(name: string, value: string): void嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
headers.delete:(name: string) => void— 依赖声明Headers.delete():删除指定请求头。
事件参数:
(name: string): void嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
headers.get:(name: string) => string | null— 依赖声明Headers.get():返回指定请求头全部值的字符串;不存在时返回 null。
事件参数:
(name: string): string | null嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
headers.getSetCookie:() => string[]— 依赖声明Headers.getSetCookie():返回响应中全部 Set-Cookie 请求头值的数组。
事件参数:
(): string[]嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
headers.has:(name: string) => boolean— 依赖声明Headers.has():返回布尔值,表示是否存在指定请求头。
事件参数:
(name: string): boolean嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
headers.set:(name: string, value: string) => void— 依赖声明Headers.set():设置指定请求头的值;不存在时创建该请求头。
事件参数:
(name: string, value: string): void嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
headers.forEach:(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: any) => void— 依赖声明事件参数:
(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: any): void嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
headers.entries:() => HeadersIterator<[string, string]>— 依赖声明返回遍历此对象全部键值对的迭代器。
事件参数:
(): HeadersIterator<[string, string]>嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
headers.keys:() => HeadersIterator<string>— 依赖声明返回遍历此对象全部键的迭代器。
事件参数:
(): HeadersIterator<string>嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
headers.values:() => HeadersIterator<string>— 依赖声明返回遍历此对象全部值的迭代器。
事件参数:
(): HeadersIterator<string>嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
headers.__@iterator@8659:() => HeadersIterator<[string, string]>— 依赖声明事件参数:
(): HeadersIterator<[string, string]>嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
默认值与来源
未知 / 未声明固定上游文档默认:未声明(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码未声明 default(2.14.7;源码事实,未作为 ASAM 运行实测)
method
必填:按分支;声明来源:上游声明。
默认值与来源
"post"(已安装运行时;已安装组件)固定上游文档默认:post(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:'post'(2.14.7;源码事实,未作为 ASAM 运行实测)
drag
必填:否;声明来源:上游声明。
drag 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:false / true
枚举:false / true
默认值与来源
false(隐式 Boolean 默认;已安装组件)来源分支 1
固定上游文档默认:false(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
withCredentials
必填:按分支;声明来源:上游声明。
withCredentials 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
枚举:false / true
默认值与来源
false(隐式 Boolean 默认;已安装组件)固定上游文档默认:false(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
showFileList
必填:按分支;声明来源:上游声明。
showFileList 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
枚举:false / true
默认值与来源
true(已安装运行时;已安装组件)固定上游文档默认:true(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:true(2.14.7;源码事实,未作为 ASAM 运行实测)
accept
必填:否;声明来源:上游声明。
默认值与来源
""(已安装运行时;已安装组件)来源分支 1
固定上游文档默认:''(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:''(2.14.7;源码事实,未作为 ASAM 运行实测)
fileList
必填:按分支;声明来源:上游声明。
默认值与来源
工厂函数(每个实例创建)(实例工厂;已安装组件)固定上游文档默认:[](2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码工厂(未执行):() => mutable([] as const)(2.14.7;源码事实,未作为 ASAM 运行实测)
autoUpload
必填:否;声明来源:上游声明。
autoUpload 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:false / true
枚举:false / true
默认值与来源
true(已安装运行时;已安装组件)来源分支 1
固定上游文档默认:true(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:true(2.14.7;源码事实,未作为 ASAM 运行实测)
listType
必填:按分支;声明来源:上游声明。
listType 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
枚举:"text" / "picture" / "picture-card"
默认值与来源
"text"(已安装运行时;已安装组件)固定上游文档默认:text(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:'text'(2.14.7;源码事实,未作为 ASAM 运行实测)
httpRequest
必填:按分支;声明来源:上游声明。
httpRequest 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
事件参数:(options: Parameters<Extract<NonNullable<PublicMember<AsamUploadProps, "httpRequest">>, (...args: never[]) => unknown>>[0]): XMLHttpRequest | Promise<unknown>
默认值与来源
函数默认值(作为值传递)(函数值(不执行);已安装组件)固定上游文档默认:原始表达式见机器资料(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
固定上游源码默认表达式:ajaxUpload(2.14.7;源码事实,未作为 ASAM 运行实测)
limit
必填:否;声明来源:上游声明。
默认值与来源
未知 / 未声明来源分支 1
固定上游文档默认:未声明(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
directory
必填:按分支;声明来源:上游声明。
directory 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
契约分支 1
枚举:false / true
默认值与来源
false(隐式 Boolean 默认;已安装组件)固定上游文档默认:false(2.14.7;文档事实,实际默认以 ASAM 源码 / 安装包为准)
size
必填:否;声明来源:ASAM 声明。
size 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:"large" / "big" / "default" / "small"
默认值与来源
undefined(源码表达式;ASAM 声明)mode
必填:否;声明来源:ASAM 声明。
mode 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
枚举:"native"
枚举:"resource"
默认值与来源
'resource'(源码表达式;ASAM 声明)upload
必填:否;声明来源:ASAM 声明。
upload 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(input: AsamUploadInput, context: AsamUploadContext): Promise<AsamUploadedResource>
默认值与来源
undefined(源码表达式;ASAM 声明)request
必填:否;声明来源:ASAM 声明。
request 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
request.url:string— 数据 / provider 协议request.method:"post" | "put" | undefined— 数据 / provider 协议枚举:
"post"/"put"request.params:Readonly<Record<string, unknown>> | undefined— 数据 / provider 协议request.transform:(response: unknown, input: AsamUploadInput) => AsamUploadedResource— 数据 / provider 协议事件参数:
(response: unknown, input: AsamUploadInput): AsamUploadedResource
默认值与来源
undefined(源码表达式;ASAM 声明)modelValue
必填:否;声明来源:ASAM 声明。
modelValue 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
modelValue.id:string— 数据 / provider 协议modelValue.name:string— 数据 / provider 协议modelValue.url:string— 数据 / provider 协议modelValue.mimeType:string— 数据 / provider 协议modelValue.size:number— 数据 / provider 协议
嵌套展开边界:框架 / 平台类型;完整类型见公开类型视图。
默认值与来源
null(源码表达式;ASAM 声明)fieldName
必填:否;声明来源:ASAM 声明。
默认值与来源
'file'(源码表达式;ASAM 声明)maxSizeBytes
必填:否;声明来源:ASAM 声明。
默认值与来源
undefined(源码表达式;ASAM 声明)validate
必填:否;声明来源:ASAM 声明。
validate 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(file: AsamUploadInput["file"]): string | boolean
默认值与来源
undefined(源码表达式;ASAM 声明)AsamUploadProps 公开类型视图
import type { AsamUploadProps, AsamUploadInput, AsamUploadProgress, AsamControlSize, AsamUploadHandler, AsamUploadContext, AsamUploadedResource, AsamUploadRequestConfig } from '@asam-ui/ui';
type PublicMember<T, K extends PropertyKey> = T extends unknown ? K extends keyof T ? T[K] : never : never;
// AsamUploadProps
type PublicContract = AsamUploadProps;
参数与事件说明
update:modelValue
必填:是;声明来源:ASAM 声明。
update:fileList
必填:是;声明来源:ASAM 声明。
change
必填:是;声明来源:ASAM 声明。
progress
必填:是;声明来源:ASAM 声明。
success
必填:是;声明来源:ASAM 声明。
error
必填:是;声明来源:ASAM 声明。
cancelled
必填:是;声明来源:ASAM 声明。
remove
必填:是;声明来源:ASAM 声明。
AsamUploadEmits 公开类型视图
import type { AsamUploadEmits, AsamUploadValue, AsamUploadItem, AsamUploadedResource, AsamDataError } from '@asam-ui/ui';
// AsamUploadEmits
type PublicContract = {
"update:modelValue": [value: AsamUploadValue];
"update:fileList": (AsamUploadEmits)["update:fileList"];
"change": [value: AsamUploadValue];
"progress": [item: AsamUploadItem];
"success": [resource: AsamUploadedResource, item: AsamUploadItem];
"error": [error: AsamDataError, item: AsamUploadItem];
"cancelled": [item: AsamUploadItem];
"remove": [resource: AsamUploadedResource];
};
资源模式插槽;基础模式使用不包含资源操作的 AsamNativeUploadSlots。
参数与事件说明
default
必填:否;声明来源:ASAM 声明。
default 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(): unknown
trigger
必填:否;声明来源:ASAM 声明。
trigger 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(): unknown
tip
必填:否;声明来源:ASAM 声明。
tip 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(): unknown
file
必填:否;声明来源:ASAM 声明。
file 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(scope: Parameters<Extract<NonNullable<(AsamUploadSlots)["file"]>, (...args: never[]) => unknown>>[0]): unknown
AsamUploadSlots 公开类型视图
import type { AsamUploadSlots, AsamUploadItem } from '@asam-ui/ui';
// AsamUploadSlots
type PublicContract = {
"default"?: (() => unknown) | undefined;
"trigger"?: (() => unknown) | undefined;
"tip"?: (() => unknown) | undefined;
"file"?: (AsamUploadSlots)["file"];
};
基础文件插槽沿用依赖的插槽;资源操作属于资源模式。
参数与事件说明
file
必填:否;声明来源:上游声明。
file 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(props: Parameters<Extract<NonNullable<(AsamNativeUploadSlots)["file"]>, (...args: never[]) => unknown>>[0]): any
trigger
必填:否;声明来源:上游声明。
trigger 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(props: AsamTableColumnExpose): any
default
必填:否;声明来源:上游声明。
default 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(props: AsamTableColumnExpose): any
tip
必填:否;声明来源:上游声明。
tip 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(props: AsamTableColumnExpose): any
AsamNativeUploadSlots 公开类型视图
import type { AsamNativeUploadSlots, AsamTableColumnExpose } from '@asam-ui/ui';
// AsamNativeUploadSlots
type PublicContract = {
"file"?: (AsamNativeUploadSlots)["file"];
"trigger"?: (AsamNativeUploadSlots)["trigger"];
"default"?: (AsamNativeUploadSlots)["default"];
"tip"?: (AsamNativeUploadSlots)["tip"];
readonly [key: string]: import("vue").Slot<any> | undefined;
};
参数与事件说明
submit
必填:是;声明来源:ASAM 声明。
submit 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(): void
abort
必填:是;声明来源:上游声明。
abort 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(file?: Parameters<Extract<NonNullable<(AsamUploadExpose)["abort"]>, (...args: never[]) => unknown>>[0]): void
clearFiles
必填:是;声明来源:上游声明。
clearFiles 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(states?: Parameters<Extract<NonNullable<(AsamUploadExpose)["clearFiles"]>, (...args: never[]) => unknown>>[0]): void
handleStart
必填:是;声明来源:上游声明。
handleStart 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(rawFile: Parameters<Extract<NonNullable<(AsamUploadExpose)["handleStart"]>, (...args: never[]) => unknown>>[0]): void
handleRemove
必填:是;声明来源:上游声明。
handleRemove 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(file: Parameters<Extract<NonNullable<(AsamUploadExpose)["handleRemove"]>, (...args: never[]) => unknown>>[0]): void
cancel
必填:是;声明来源:ASAM 声明。
cancel 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(clientId?: string | undefined): void
clear
必填:是;声明来源:ASAM 声明。
clear 参数与事件数据
下列为声明可读视图;可导入类型以公开类型视图为准。分支条件与父属性一致。
事件参数:(): void
AsamUploadExpose 公开类型视图
import type { AsamUploadExpose, AsamUploadProps } from '@asam-ui/ui';
// AsamUploadExpose
type PublicContract = {
"submit": (() => void) & (() => void);
"abort": (AsamUploadExpose)["abort"];
"clearFiles": (AsamUploadExpose)["clearFiles"];
"handleStart": (AsamUploadExpose)["handleStart"];
"handleRemove": (AsamUploadExpose)["handleRemove"];
"cancel": (clientId?: string) => void;
"clear": () => void;
};