小程序初始提交
This commit is contained in:
@@ -0,0 +1,447 @@
|
||||
<template>
|
||||
<view class="cl-upload-list" :class="[pt.className]">
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="item.uid"
|
||||
class="cl-upload"
|
||||
:class="[
|
||||
{
|
||||
'is-dark': isDark,
|
||||
'is-disabled': isDisabled
|
||||
},
|
||||
pt.item?.className
|
||||
]"
|
||||
:style="uploadStyle"
|
||||
@tap="choose(index)"
|
||||
>
|
||||
<image
|
||||
class="cl-upload__image"
|
||||
:class="[
|
||||
{
|
||||
'is-uploading': item.progress < 100
|
||||
},
|
||||
pt.image?.className
|
||||
]"
|
||||
:src="item.preview"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
|
||||
<cl-icon
|
||||
name="close-line"
|
||||
color="white"
|
||||
:pt="{
|
||||
className: 'cl-upload__close'
|
||||
}"
|
||||
@tap.stop="remove(item.uid)"
|
||||
v-if="!isDisabled"
|
||||
></cl-icon>
|
||||
|
||||
<view class="cl-upload__progress" v-if="item.progress < 100">
|
||||
<cl-progress :value="item.progress" :show-text="false"></cl-progress>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="isAdd"
|
||||
class="cl-upload is-add"
|
||||
:class="[
|
||||
{
|
||||
'is-dark': isDark,
|
||||
'is-disabled': isDisabled
|
||||
},
|
||||
pt.add?.className
|
||||
]"
|
||||
:style="uploadStyle"
|
||||
@tap="choose(-1)"
|
||||
>
|
||||
<cl-icon
|
||||
:name="icon"
|
||||
:pt="{
|
||||
className: parseClass([
|
||||
[isDark, 'text-white', 'text-surface-400'],
|
||||
pt.icon?.className
|
||||
])
|
||||
}"
|
||||
:size="50"
|
||||
></cl-icon>
|
||||
|
||||
<cl-text
|
||||
:pt="{
|
||||
className: parseClass([
|
||||
[isDark, 'text-white', 'text-surface-500'],
|
||||
'text-xs mt-1 text-center',
|
||||
pt.text?.className
|
||||
])
|
||||
}"
|
||||
>{{ text }}</cl-text
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { forInObject, isDark, parseClass, parsePt, parseRpx, uploadFile, uuid } from "@/cool";
|
||||
import { t } from "@/locale";
|
||||
import { computed, reactive, ref, watch, type PropType } from "vue";
|
||||
import type { ClUploadItem, PassThroughProps } from "../../types";
|
||||
import { useForm } from "../../hooks";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-upload"
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
// 透传属性,用于自定义样式类名
|
||||
pt: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 双向绑定的值,支持字符串或字符串数组
|
||||
modelValue: {
|
||||
type: [Array, String] as PropType<string[] | string>,
|
||||
default: () => []
|
||||
},
|
||||
// 上传按钮的图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: "camera-fill"
|
||||
},
|
||||
// 上传按钮显示的文本
|
||||
text: {
|
||||
type: String,
|
||||
default: () => t("上传 / 拍摄")
|
||||
},
|
||||
// 图片压缩方式:original原图,compressed压缩图
|
||||
sizeType: {
|
||||
type: [String, Array] as PropType<string[] | string>,
|
||||
default: () => ["original", "compressed"]
|
||||
},
|
||||
// 图片选择来源:album相册,camera拍照
|
||||
sourceType: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => ["album", "camera"]
|
||||
},
|
||||
// 上传区域高度
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 150
|
||||
},
|
||||
// 上传区域宽度
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 150
|
||||
},
|
||||
// 是否支持多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 最大上传数量限制
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 9
|
||||
},
|
||||
// 是否禁用组件
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 演示用,本地预览
|
||||
test: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
"update:modelValue", // 更新modelValue值
|
||||
"change", // 值发生变化时触发
|
||||
"exceed", // 超出数量限制时触发
|
||||
"success", // 上传成功时触发
|
||||
"error", // 上传失败时触发
|
||||
"progress" // 上传进度更新时触发
|
||||
]);
|
||||
|
||||
// cl-form 上下文
|
||||
const { disabled } = useForm();
|
||||
|
||||
// 是否禁用
|
||||
const isDisabled = computed(() => {
|
||||
return disabled.value || props.disabled;
|
||||
});
|
||||
|
||||
// 透传属性的类型定义
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
item?: PassThroughProps;
|
||||
add?: PassThroughProps;
|
||||
image?: PassThroughProps;
|
||||
icon?: PassThroughProps;
|
||||
text?: PassThroughProps;
|
||||
};
|
||||
|
||||
// 解析透传属性
|
||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// 上传文件列表
|
||||
const list = ref<ClUploadItem[]>([]);
|
||||
|
||||
// 当前操作的文件索引,-1表示新增,其他表示替换指定索引的文件
|
||||
const activeIndex = ref(0);
|
||||
|
||||
// 计算是否显示添加按钮
|
||||
const isAdd = computed(() => {
|
||||
const n = list.value.length;
|
||||
|
||||
if (isDisabled.value) {
|
||||
// 禁用状态下,只有没有文件时才显示添加按钮
|
||||
return n == 0;
|
||||
} else {
|
||||
// 根据multiple模式判断是否可以继续添加
|
||||
return n < (props.multiple ? props.limit : 1);
|
||||
}
|
||||
});
|
||||
|
||||
// 计算上传区域的样式
|
||||
const uploadStyle = computed(() => {
|
||||
return {
|
||||
height: parseRpx(props.height),
|
||||
width: parseRpx(props.width)
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取已成功上传的文件URL列表
|
||||
*/
|
||||
function getUrls() {
|
||||
return list.value.filter((e) => e.url != "" && e.progress == 100).map((e) => e.url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的值,根据multiple模式返回不同格式
|
||||
*/
|
||||
function getValue() {
|
||||
const urls = getUrls();
|
||||
|
||||
if (props.multiple) {
|
||||
return urls;
|
||||
} else {
|
||||
return urls[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加新的上传项或更新已有项
|
||||
* @param {string} url - 预览图片的本地路径
|
||||
*/
|
||||
function append(url: string) {
|
||||
// 创建新的上传项
|
||||
const item =
|
||||
activeIndex.value == -1
|
||||
? reactive<ClUploadItem>({
|
||||
uid: uuid(), // 生成唯一ID
|
||||
preview: url, // 预览图片路径
|
||||
url: "", // 最终上传后的URL
|
||||
progress: 0 // 上传进度
|
||||
})
|
||||
: list.value[activeIndex.value];
|
||||
|
||||
// 更新已有项或添加新项
|
||||
if (activeIndex.value == -1) {
|
||||
// 添加新项到列表末尾
|
||||
list.value.push(item);
|
||||
} else {
|
||||
// 替换已有项的内容
|
||||
item.progress = 0;
|
||||
item.preview = url;
|
||||
item.url = "";
|
||||
}
|
||||
|
||||
return item.uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发值变化事件
|
||||
*/
|
||||
function change() {
|
||||
const value = getValue();
|
||||
|
||||
emit("update:modelValue", value);
|
||||
emit("change", value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新指定上传项的数据
|
||||
* @param {string} uid - 上传项的唯一ID
|
||||
* @param {any} data - 要更新的数据对象
|
||||
*/
|
||||
function update(uid: string, data: any) {
|
||||
const item = list.value.find((e) => e.uid == uid);
|
||||
|
||||
if (item != null) {
|
||||
// 遍历更新数据对象的所有属性
|
||||
forInObject(data, (value, key) => {
|
||||
item[key] = value;
|
||||
});
|
||||
|
||||
// 当上传完成且有URL时,触发change事件
|
||||
if (item.progress == 100 && item.url != "") {
|
||||
change();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定的上传项
|
||||
* @param {string} uid - 要删除的上传项唯一ID
|
||||
*/
|
||||
function remove(uid: string) {
|
||||
list.value.splice(
|
||||
list.value.findIndex((e) => e.uid == uid),
|
||||
1
|
||||
);
|
||||
change();
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择图片文件
|
||||
* @param {number} index - 操作的索引,-1表示新增,其他表示替换
|
||||
*/
|
||||
function choose(index: number) {
|
||||
if (isDisabled.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeIndex.value = index;
|
||||
|
||||
// 计算可选择的图片数量
|
||||
const count = activeIndex.value == -1 ? props.limit - list.value.length : 1;
|
||||
|
||||
if (count <= 0) {
|
||||
// 超出数量限制
|
||||
emit("exceed", list.value);
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用uni-app的选择图片API
|
||||
uni.chooseImage({
|
||||
count: count, // 最多可以选择的图片张数
|
||||
sizeType: props.sizeType as string[], // 压缩方式
|
||||
sourceType: props.sourceType as string[], // 图片来源
|
||||
success(res) {
|
||||
// 选择成功后处理每个文件
|
||||
if (Array.isArray(res.tempFiles)) {
|
||||
(res.tempFiles as ChooseImageTempFile[]).forEach((file) => {
|
||||
// 添加到列表并获取唯一ID
|
||||
const uid = append(file.path);
|
||||
|
||||
// 测试用,本地预览
|
||||
if (props.test) {
|
||||
update(uid, { url: file.path, progress: 100 });
|
||||
emit("success", file.path, uid);
|
||||
return;
|
||||
}
|
||||
|
||||
// 开始上传文件
|
||||
uploadFile(file, {
|
||||
// 上传进度回调
|
||||
onProgressUpdate: ({ progress }) => {
|
||||
update(uid, { progress });
|
||||
emit("progress", progress);
|
||||
}
|
||||
})
|
||||
.then((url) => {
|
||||
// 上传成功,更新URL和进度
|
||||
update(uid, { url, progress: 100 });
|
||||
emit("success", url, uid);
|
||||
})
|
||||
.catch((err) => {
|
||||
// 上传失败,触发错误事件并删除该项
|
||||
emit("error", err as string);
|
||||
remove(uid);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
// 选择图片失败
|
||||
console.error(err);
|
||||
emit("error", err.errMsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 监听modelValue的变化,同步更新内部列表
|
||||
watch(
|
||||
computed(() => props.modelValue!),
|
||||
(val: string | string[]) => {
|
||||
// 将当前列表的URL转为字符串用于比较
|
||||
const currentUrls = getUrls().join(",");
|
||||
|
||||
// 将传入的值标准化为字符串进行比较
|
||||
const newUrls = Array.isArray(val) ? val.join(",") : val;
|
||||
|
||||
// 如果值发生变化,更新内部列表
|
||||
if (currentUrls != newUrls) {
|
||||
// 标准化为数组格式
|
||||
const urls = Array.isArray(val) ? val : [val];
|
||||
|
||||
// 过滤空值并转换为Item对象
|
||||
list.value = urls
|
||||
.filter((url) => url != "")
|
||||
.map((url) => {
|
||||
return {
|
||||
uid: uuid(),
|
||||
preview: url,
|
||||
url,
|
||||
progress: 100 // 外部传入的URL认为已上传完成
|
||||
} as ClUploadItem;
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true // 立即执行一次
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-upload-list {
|
||||
@apply flex flex-row flex-wrap;
|
||||
|
||||
.cl-upload {
|
||||
@apply relative bg-surface-100 rounded-xl flex flex-col items-center justify-center;
|
||||
@apply mr-2 mb-2;
|
||||
|
||||
&.is-dark {
|
||||
@apply bg-surface-700;
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
@apply opacity-50;
|
||||
}
|
||||
|
||||
&.is-add {
|
||||
@apply p-1;
|
||||
}
|
||||
|
||||
&__image {
|
||||
@apply w-full h-full absolute top-0 left-0;
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.2s;
|
||||
|
||||
&.is-uploading {
|
||||
@apply opacity-70;
|
||||
}
|
||||
}
|
||||
|
||||
&__close {
|
||||
@apply absolute top-1 right-1;
|
||||
}
|
||||
|
||||
&__progress {
|
||||
@apply absolute bottom-2 left-0 w-full z-10 px-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
26
cool-unix/uni_modules/cool-ui/components/cl-upload/props.ts
Normal file
26
cool-unix/uni_modules/cool-ui/components/cl-upload/props.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { ClUploadItem, PassThroughProps } from "../../types";
|
||||
|
||||
export type ClUploadPassThrough = {
|
||||
className?: string;
|
||||
item?: PassThroughProps;
|
||||
add?: PassThroughProps;
|
||||
image?: PassThroughProps;
|
||||
icon?: PassThroughProps;
|
||||
text?: PassThroughProps;
|
||||
};
|
||||
|
||||
export type ClUploadProps = {
|
||||
className?: string;
|
||||
pt?: ClUploadPassThrough;
|
||||
modelValue?: string[] | string;
|
||||
icon?: string;
|
||||
text?: string;
|
||||
sizeType?: string[] | string;
|
||||
sourceType?: string[];
|
||||
height?: any;
|
||||
width?: any;
|
||||
multiple?: boolean;
|
||||
limit?: number;
|
||||
disabled?: boolean;
|
||||
test?: boolean;
|
||||
};
|
||||
Reference in New Issue
Block a user